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.

36179 lines
1.1 MiB

  1. /**
  2. * vis.js
  3. * https://github.com/almende/vis
  4. *
  5. * A dynamic, browser-based visualization library.
  6. *
  7. * @version 3.12.0
  8. * @date 2015-04-07
  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. "use strict";
  26. (function webpackUniversalModuleDefinition(root, factory) {
  27. if(typeof exports === 'object' && typeof module === 'object')
  28. module.exports = factory();
  29. else if(typeof define === 'function' && define.amd)
  30. define(factory);
  31. else if(typeof exports === 'object')
  32. exports["vis"] = factory();
  33. else
  34. root["vis"] = factory();
  35. })(this, function() {
  36. return /******/ (function(modules) { // webpackBootstrap
  37. /******/ // The module cache
  38. /******/ var installedModules = {};
  39. /******/ // The require function
  40. /******/ function __webpack_require__(moduleId) {
  41. /******/ // Check if module is in cache
  42. /******/ if(installedModules[moduleId])
  43. /******/ return installedModules[moduleId].exports;
  44. /******/ // Create a new module (and put it into the cache)
  45. /******/ var module = installedModules[moduleId] = {
  46. /******/ exports: {},
  47. /******/ id: moduleId,
  48. /******/ loaded: false
  49. /******/ };
  50. /******/ // Execute the module function
  51. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  52. /******/ // Flag the module as loaded
  53. /******/ module.loaded = true;
  54. /******/ // Return the exports of the module
  55. /******/ return module.exports;
  56. /******/ }
  57. /******/ // expose the modules object (__webpack_modules__)
  58. /******/ __webpack_require__.m = modules;
  59. /******/ // expose the module cache
  60. /******/ __webpack_require__.c = installedModules;
  61. /******/ // __webpack_public_path__
  62. /******/ __webpack_require__.p = "";
  63. /******/ // Load entry module and return exports
  64. /******/ return __webpack_require__(0);
  65. /******/ })
  66. /************************************************************************/
  67. /******/ ([
  68. /* 0 */
  69. /***/ function(module, exports, __webpack_require__) {
  70. // utils
  71. exports.util = __webpack_require__(1);
  72. exports.DOMutil = __webpack_require__(2);
  73. // data
  74. exports.DataSet = __webpack_require__(3);
  75. exports.DataView = __webpack_require__(4);
  76. exports.Queue = __webpack_require__(5);
  77. // Graph3d
  78. exports.Graph3d = __webpack_require__(6);
  79. exports.graph3d = {
  80. Camera: __webpack_require__(7),
  81. Filter: __webpack_require__(8),
  82. Point2d: __webpack_require__(9),
  83. Point3d: __webpack_require__(10),
  84. Slider: __webpack_require__(11),
  85. StepNumber: __webpack_require__(12)
  86. };
  87. // Timeline
  88. exports.Timeline = __webpack_require__(13);
  89. exports.Graph2d = __webpack_require__(14);
  90. exports.timeline = {
  91. DateUtil: __webpack_require__(15),
  92. DataStep: __webpack_require__(16),
  93. Range: __webpack_require__(17),
  94. stack: __webpack_require__(18),
  95. TimeStep: __webpack_require__(19),
  96. components: {
  97. items: {
  98. Item: __webpack_require__(20),
  99. BackgroundItem: __webpack_require__(21),
  100. BoxItem: __webpack_require__(22),
  101. PointItem: __webpack_require__(23),
  102. RangeItem: __webpack_require__(24)
  103. },
  104. Component: __webpack_require__(25),
  105. CurrentTime: __webpack_require__(26),
  106. CustomTime: __webpack_require__(27),
  107. DataAxis: __webpack_require__(28),
  108. GraphGroup: __webpack_require__(29),
  109. Group: __webpack_require__(30),
  110. BackgroundGroup: __webpack_require__(31),
  111. ItemSet: __webpack_require__(32),
  112. Legend: __webpack_require__(33),
  113. LineGraph: __webpack_require__(34),
  114. TimeAxis: __webpack_require__(35)
  115. }
  116. };
  117. // Network
  118. exports.Network = __webpack_require__(36);
  119. exports.network = {
  120. Edge: __webpack_require__(37),
  121. Groups: __webpack_require__(38),
  122. Images: __webpack_require__(39),
  123. Node: __webpack_require__(40),
  124. Popup: __webpack_require__(41),
  125. dotparser: __webpack_require__(42),
  126. gephiParser: __webpack_require__(43)
  127. };
  128. // Deprecated since v3.0.0
  129. exports.Graph = function () {
  130. throw new Error('Graph is renamed to Network. Please create a graph as new vis.Network(...)');
  131. };
  132. // bundled external libraries
  133. exports.moment = __webpack_require__(44);
  134. exports.hammer = __webpack_require__(45);
  135. /***/ },
  136. /* 1 */
  137. /***/ function(module, exports, __webpack_require__) {
  138. // utility functions
  139. // first check if moment.js is already loaded in the browser window, if so,
  140. // use this instance. Else, load via commonjs.
  141. var moment = __webpack_require__(44);
  142. /**
  143. * Test whether given object is a number
  144. * @param {*} object
  145. * @return {Boolean} isNumber
  146. */
  147. exports.isNumber = function(object) {
  148. return (object instanceof Number || typeof object == 'number');
  149. };
  150. /**
  151. * this function gives you a range between 0 and 1 based on the min and max values in the set, the total sum of all values and the current value.
  152. *
  153. * @param min
  154. * @param max
  155. * @param total
  156. * @param value
  157. * @returns {number}
  158. */
  159. exports.giveRange = function(min,max,total,value) {
  160. if (max == min) {
  161. return 0.5;
  162. }
  163. else {
  164. var scale = 1 / (max - min);
  165. return Math.max(0,(value - min)*scale);
  166. }
  167. }
  168. /**
  169. * Test whether given object is a string
  170. * @param {*} object
  171. * @return {Boolean} isString
  172. */
  173. exports.isString = function(object) {
  174. return (object instanceof String || typeof object == 'string');
  175. };
  176. /**
  177. * Test whether given object is a Date, or a String containing a Date
  178. * @param {Date | String} object
  179. * @return {Boolean} isDate
  180. */
  181. exports.isDate = function(object) {
  182. if (object instanceof Date) {
  183. return true;
  184. }
  185. else if (exports.isString(object)) {
  186. // test whether this string contains a date
  187. var match = ASPDateRegex.exec(object);
  188. if (match) {
  189. return true;
  190. }
  191. else if (!isNaN(Date.parse(object))) {
  192. return true;
  193. }
  194. }
  195. return false;
  196. };
  197. /**
  198. * Test whether given object is an instance of google.visualization.DataTable
  199. * @param {*} object
  200. * @return {Boolean} isDataTable
  201. */
  202. exports.isDataTable = function(object) {
  203. return (typeof (google) !== 'undefined') &&
  204. (google.visualization) &&
  205. (google.visualization.DataTable) &&
  206. (object instanceof google.visualization.DataTable);
  207. };
  208. /**
  209. * Create a semi UUID
  210. * source: http://stackoverflow.com/a/105074/1262753
  211. * @return {String} uuid
  212. */
  213. exports.randomUUID = function() {
  214. var S4 = function () {
  215. return Math.floor(
  216. Math.random() * 0x10000 /* 65536 */
  217. ).toString(16);
  218. };
  219. return (
  220. S4() + S4() + '-' +
  221. S4() + '-' +
  222. S4() + '-' +
  223. S4() + '-' +
  224. S4() + S4() + S4()
  225. );
  226. };
  227. /**
  228. * Extend object a with the properties of object b or a series of objects
  229. * Only properties with defined values are copied
  230. * @param {Object} a
  231. * @param {... Object} b
  232. * @return {Object} a
  233. */
  234. exports.extend = function (a, b) {
  235. for (var i = 1, len = arguments.length; i < len; i++) {
  236. var other = arguments[i];
  237. for (var prop in other) {
  238. if (other.hasOwnProperty(prop)) {
  239. a[prop] = other[prop];
  240. }
  241. }
  242. }
  243. return a;
  244. };
  245. /**
  246. * Extend object a with selected properties of object b or a series of objects
  247. * Only properties with defined values are copied
  248. * @param {Array.<String>} props
  249. * @param {Object} a
  250. * @param {... Object} b
  251. * @return {Object} a
  252. */
  253. exports.selectiveExtend = function (props, a, b) {
  254. if (!Array.isArray(props)) {
  255. throw new Error('Array with property names expected as first argument');
  256. }
  257. for (var i = 2; i < arguments.length; i++) {
  258. var other = arguments[i];
  259. for (var p = 0; p < props.length; p++) {
  260. var prop = props[p];
  261. if (other.hasOwnProperty(prop)) {
  262. a[prop] = other[prop];
  263. }
  264. }
  265. }
  266. return a;
  267. };
  268. /**
  269. * Extend object a with selected properties of object b or a series of objects
  270. * Only properties with defined values are copied
  271. * @param {Array.<String>} props
  272. * @param {Object} a
  273. * @param {... Object} b
  274. * @return {Object} a
  275. */
  276. exports.selectiveDeepExtend = function (props, a, b) {
  277. // TODO: add support for Arrays to deepExtend
  278. if (Array.isArray(b)) {
  279. throw new TypeError('Arrays are not supported by deepExtend');
  280. }
  281. for (var i = 2; i < arguments.length; i++) {
  282. var other = arguments[i];
  283. for (var p = 0; p < props.length; p++) {
  284. var prop = props[p];
  285. if (other.hasOwnProperty(prop)) {
  286. if (b[prop] && b[prop].constructor === Object) {
  287. if (a[prop] === undefined) {
  288. a[prop] = {};
  289. }
  290. if (a[prop].constructor === Object) {
  291. exports.deepExtend(a[prop], b[prop]);
  292. }
  293. else {
  294. a[prop] = b[prop];
  295. }
  296. } else if (Array.isArray(b[prop])) {
  297. throw new TypeError('Arrays are not supported by deepExtend');
  298. } else {
  299. a[prop] = b[prop];
  300. }
  301. }
  302. }
  303. }
  304. return a;
  305. };
  306. /**
  307. * Extend object a with selected properties of object b or a series of objects
  308. * Only properties with defined values are copied
  309. * @param {Array.<String>} props
  310. * @param {Object} a
  311. * @param {... Object} b
  312. * @return {Object} a
  313. */
  314. exports.selectiveNotDeepExtend = function (props, a, b) {
  315. // TODO: add support for Arrays to deepExtend
  316. if (Array.isArray(b)) {
  317. throw new TypeError('Arrays are not supported by deepExtend');
  318. }
  319. for (var prop in b) {
  320. if (b.hasOwnProperty(prop)) {
  321. if (props.indexOf(prop) == -1) {
  322. if (b[prop] && b[prop].constructor === Object) {
  323. if (a[prop] === undefined) {
  324. a[prop] = {};
  325. }
  326. if (a[prop].constructor === Object) {
  327. exports.deepExtend(a[prop], b[prop]);
  328. }
  329. else {
  330. a[prop] = b[prop];
  331. }
  332. } else if (Array.isArray(b[prop])) {
  333. throw new TypeError('Arrays are not supported by deepExtend');
  334. } else {
  335. a[prop] = b[prop];
  336. }
  337. }
  338. }
  339. }
  340. return a;
  341. };
  342. /**
  343. * Deep extend an object a with the properties of object b
  344. * @param {Object} a
  345. * @param {Object} b
  346. * @returns {Object}
  347. */
  348. exports.deepExtend = function(a, b) {
  349. // TODO: add support for Arrays to deepExtend
  350. if (Array.isArray(b)) {
  351. throw new TypeError('Arrays are not supported by deepExtend');
  352. }
  353. for (var prop in b) {
  354. if (b.hasOwnProperty(prop)) {
  355. if (b[prop] && b[prop].constructor === Object) {
  356. if (a[prop] === undefined) {
  357. a[prop] = {};
  358. }
  359. if (a[prop].constructor === Object) {
  360. exports.deepExtend(a[prop], b[prop]);
  361. }
  362. else {
  363. a[prop] = b[prop];
  364. }
  365. } else if (Array.isArray(b[prop])) {
  366. throw new TypeError('Arrays are not supported by deepExtend');
  367. } else {
  368. a[prop] = b[prop];
  369. }
  370. }
  371. }
  372. return a;
  373. };
  374. /**
  375. * Test whether all elements in two arrays are equal.
  376. * @param {Array} a
  377. * @param {Array} b
  378. * @return {boolean} Returns true if both arrays have the same length and same
  379. * elements.
  380. */
  381. exports.equalArray = function (a, b) {
  382. if (a.length != b.length) return false;
  383. for (var i = 0, len = a.length; i < len; i++) {
  384. if (a[i] != b[i]) return false;
  385. }
  386. return true;
  387. };
  388. /**
  389. * Convert an object to another type
  390. * @param {Boolean | Number | String | Date | Moment | Null | undefined} object
  391. * @param {String | undefined} type Name of the type. Available types:
  392. * 'Boolean', 'Number', 'String',
  393. * 'Date', 'Moment', ISODate', 'ASPDate'.
  394. * @return {*} object
  395. * @throws Error
  396. */
  397. exports.convert = function(object, type) {
  398. var match;
  399. if (object === undefined) {
  400. return undefined;
  401. }
  402. if (object === null) {
  403. return null;
  404. }
  405. if (!type) {
  406. return object;
  407. }
  408. if (!(typeof type === 'string') && !(type instanceof String)) {
  409. throw new Error('Type must be a string');
  410. }
  411. //noinspection FallthroughInSwitchStatementJS
  412. switch (type) {
  413. case 'boolean':
  414. case 'Boolean':
  415. return Boolean(object);
  416. case 'number':
  417. case 'Number':
  418. return Number(object.valueOf());
  419. case 'string':
  420. case 'String':
  421. return String(object);
  422. case 'Date':
  423. if (exports.isNumber(object)) {
  424. return new Date(object);
  425. }
  426. if (object instanceof Date) {
  427. return new Date(object.valueOf());
  428. }
  429. else if (moment.isMoment(object)) {
  430. return new Date(object.valueOf());
  431. }
  432. if (exports.isString(object)) {
  433. match = ASPDateRegex.exec(object);
  434. if (match) {
  435. // object is an ASP date
  436. return new Date(Number(match[1])); // parse number
  437. }
  438. else {
  439. return moment(object).toDate(); // parse string
  440. }
  441. }
  442. else {
  443. throw new Error(
  444. 'Cannot convert object of type ' + exports.getType(object) +
  445. ' to type Date');
  446. }
  447. case 'Moment':
  448. if (exports.isNumber(object)) {
  449. return moment(object);
  450. }
  451. if (object instanceof Date) {
  452. return moment(object.valueOf());
  453. }
  454. else if (moment.isMoment(object)) {
  455. return moment(object);
  456. }
  457. if (exports.isString(object)) {
  458. match = ASPDateRegex.exec(object);
  459. if (match) {
  460. // object is an ASP date
  461. return moment(Number(match[1])); // parse number
  462. }
  463. else {
  464. return moment(object); // parse string
  465. }
  466. }
  467. else {
  468. throw new Error(
  469. 'Cannot convert object of type ' + exports.getType(object) +
  470. ' to type Date');
  471. }
  472. case 'ISODate':
  473. if (exports.isNumber(object)) {
  474. return new Date(object);
  475. }
  476. else if (object instanceof Date) {
  477. return object.toISOString();
  478. }
  479. else if (moment.isMoment(object)) {
  480. return object.toDate().toISOString();
  481. }
  482. else if (exports.isString(object)) {
  483. match = ASPDateRegex.exec(object);
  484. if (match) {
  485. // object is an ASP date
  486. return new Date(Number(match[1])).toISOString(); // parse number
  487. }
  488. else {
  489. return new Date(object).toISOString(); // parse string
  490. }
  491. }
  492. else {
  493. throw new Error(
  494. 'Cannot convert object of type ' + exports.getType(object) +
  495. ' to type ISODate');
  496. }
  497. case 'ASPDate':
  498. if (exports.isNumber(object)) {
  499. return '/Date(' + object + ')/';
  500. }
  501. else if (object instanceof Date) {
  502. return '/Date(' + object.valueOf() + ')/';
  503. }
  504. else if (exports.isString(object)) {
  505. match = ASPDateRegex.exec(object);
  506. var value;
  507. if (match) {
  508. // object is an ASP date
  509. value = new Date(Number(match[1])).valueOf(); // parse number
  510. }
  511. else {
  512. value = new Date(object).valueOf(); // parse string
  513. }
  514. return '/Date(' + value + ')/';
  515. }
  516. else {
  517. throw new Error(
  518. 'Cannot convert object of type ' + exports.getType(object) +
  519. ' to type ASPDate');
  520. }
  521. default:
  522. throw new Error('Unknown type "' + type + '"');
  523. }
  524. };
  525. // parse ASP.Net Date pattern,
  526. // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
  527. // code from http://momentjs.com/
  528. var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
  529. /**
  530. * Get the type of an object, for example exports.getType([]) returns 'Array'
  531. * @param {*} object
  532. * @return {String} type
  533. */
  534. exports.getType = function(object) {
  535. var type = typeof object;
  536. if (type == 'object') {
  537. if (object == null) {
  538. return 'null';
  539. }
  540. if (object instanceof Boolean) {
  541. return 'Boolean';
  542. }
  543. if (object instanceof Number) {
  544. return 'Number';
  545. }
  546. if (object instanceof String) {
  547. return 'String';
  548. }
  549. if (Array.isArray(object)) {
  550. return 'Array';
  551. }
  552. if (object instanceof Date) {
  553. return 'Date';
  554. }
  555. return 'Object';
  556. }
  557. else if (type == 'number') {
  558. return 'Number';
  559. }
  560. else if (type == 'boolean') {
  561. return 'Boolean';
  562. }
  563. else if (type == 'string') {
  564. return 'String';
  565. }
  566. return type;
  567. };
  568. /**
  569. * Retrieve the absolute left value of a DOM element
  570. * @param {Element} elem A dom element, for example a div
  571. * @return {number} left The absolute left position of this element
  572. * in the browser page.
  573. */
  574. exports.getAbsoluteLeft = function(elem) {
  575. return elem.getBoundingClientRect().left + window.pageXOffset;
  576. };
  577. /**
  578. * Retrieve the absolute top value of a DOM element
  579. * @param {Element} elem A dom element, for example a div
  580. * @return {number} top The absolute top position of this element
  581. * in the browser page.
  582. */
  583. exports.getAbsoluteTop = function(elem) {
  584. return elem.getBoundingClientRect().top + window.pageYOffset;
  585. };
  586. /**
  587. * add a className to the given elements style
  588. * @param {Element} elem
  589. * @param {String} className
  590. */
  591. exports.addClassName = function(elem, className) {
  592. var classes = elem.className.split(' ');
  593. if (classes.indexOf(className) == -1) {
  594. classes.push(className); // add the class to the array
  595. elem.className = classes.join(' ');
  596. }
  597. };
  598. /**
  599. * add a className to the given elements style
  600. * @param {Element} elem
  601. * @param {String} className
  602. */
  603. exports.removeClassName = function(elem, className) {
  604. var classes = elem.className.split(' ');
  605. var index = classes.indexOf(className);
  606. if (index != -1) {
  607. classes.splice(index, 1); // remove the class from the array
  608. elem.className = classes.join(' ');
  609. }
  610. };
  611. /**
  612. * For each method for both arrays and objects.
  613. * In case of an array, the built-in Array.forEach() is applied.
  614. * In case of an Object, the method loops over all properties of the object.
  615. * @param {Object | Array} object An Object or Array
  616. * @param {function} callback Callback method, called for each item in
  617. * the object or array with three parameters:
  618. * callback(value, index, object)
  619. */
  620. exports.forEach = function(object, callback) {
  621. var i,
  622. len;
  623. if (Array.isArray(object)) {
  624. // array
  625. for (i = 0, len = object.length; i < len; i++) {
  626. callback(object[i], i, object);
  627. }
  628. }
  629. else {
  630. // object
  631. for (i in object) {
  632. if (object.hasOwnProperty(i)) {
  633. callback(object[i], i, object);
  634. }
  635. }
  636. }
  637. };
  638. /**
  639. * Convert an object into an array: all objects properties are put into the
  640. * array. The resulting array is unordered.
  641. * @param {Object} object
  642. * @param {Array} array
  643. */
  644. exports.toArray = function(object) {
  645. var array = [];
  646. for (var prop in object) {
  647. if (object.hasOwnProperty(prop)) array.push(object[prop]);
  648. }
  649. return array;
  650. }
  651. /**
  652. * Update a property in an object
  653. * @param {Object} object
  654. * @param {String} key
  655. * @param {*} value
  656. * @return {Boolean} changed
  657. */
  658. exports.updateProperty = function(object, key, value) {
  659. if (object[key] !== value) {
  660. object[key] = value;
  661. return true;
  662. }
  663. else {
  664. return false;
  665. }
  666. };
  667. /**
  668. * Add and event listener. Works for all browsers
  669. * @param {Element} element An html element
  670. * @param {string} action The action, for example "click",
  671. * without the prefix "on"
  672. * @param {function} listener The callback function to be executed
  673. * @param {boolean} [useCapture]
  674. */
  675. exports.addEventListener = function(element, action, listener, useCapture) {
  676. if (element.addEventListener) {
  677. if (useCapture === undefined)
  678. useCapture = false;
  679. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  680. action = "DOMMouseScroll"; // For Firefox
  681. }
  682. element.addEventListener(action, listener, useCapture);
  683. } else {
  684. element.attachEvent("on" + action, listener); // IE browsers
  685. }
  686. };
  687. /**
  688. * Remove an event listener from an element
  689. * @param {Element} element An html dom element
  690. * @param {string} action The name of the event, for example "mousedown"
  691. * @param {function} listener The listener function
  692. * @param {boolean} [useCapture]
  693. */
  694. exports.removeEventListener = function(element, action, listener, useCapture) {
  695. if (element.removeEventListener) {
  696. // non-IE browsers
  697. if (useCapture === undefined)
  698. useCapture = false;
  699. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  700. action = "DOMMouseScroll"; // For Firefox
  701. }
  702. element.removeEventListener(action, listener, useCapture);
  703. } else {
  704. // IE browsers
  705. element.detachEvent("on" + action, listener);
  706. }
  707. };
  708. /**
  709. * Cancels the event if it is cancelable, without stopping further propagation of the event.
  710. */
  711. exports.preventDefault = function (event) {
  712. if (!event)
  713. event = window.event;
  714. if (event.preventDefault) {
  715. event.preventDefault(); // non-IE browsers
  716. }
  717. else {
  718. event.returnValue = false; // IE browsers
  719. }
  720. };
  721. /**
  722. * Get HTML element which is the target of the event
  723. * @param {Event} event
  724. * @return {Element} target element
  725. */
  726. exports.getTarget = function(event) {
  727. // code from http://www.quirksmode.org/js/events_properties.html
  728. if (!event) {
  729. event = window.event;
  730. }
  731. var target;
  732. if (event.target) {
  733. target = event.target;
  734. }
  735. else if (event.srcElement) {
  736. target = event.srcElement;
  737. }
  738. if (target.nodeType != undefined && target.nodeType == 3) {
  739. // defeat Safari bug
  740. target = target.parentNode;
  741. }
  742. return target;
  743. };
  744. /**
  745. * Check if given element contains given parent somewhere in the DOM tree
  746. * @param {Element} element
  747. * @param {Element} parent
  748. */
  749. exports.hasParent = function (element, parent) {
  750. var e = element;
  751. while (e) {
  752. if (e === parent) {
  753. return true;
  754. }
  755. e = e.parentNode;
  756. }
  757. return false;
  758. };
  759. exports.option = {};
  760. /**
  761. * Convert a value into a boolean
  762. * @param {Boolean | function | undefined} value
  763. * @param {Boolean} [defaultValue]
  764. * @returns {Boolean} bool
  765. */
  766. exports.option.asBoolean = function (value, defaultValue) {
  767. if (typeof value == 'function') {
  768. value = value();
  769. }
  770. if (value != null) {
  771. return (value != false);
  772. }
  773. return defaultValue || null;
  774. };
  775. /**
  776. * Convert a value into a number
  777. * @param {Boolean | function | undefined} value
  778. * @param {Number} [defaultValue]
  779. * @returns {Number} number
  780. */
  781. exports.option.asNumber = function (value, defaultValue) {
  782. if (typeof value == 'function') {
  783. value = value();
  784. }
  785. if (value != null) {
  786. return Number(value) || defaultValue || null;
  787. }
  788. return defaultValue || null;
  789. };
  790. /**
  791. * Convert a value into a string
  792. * @param {String | function | undefined} value
  793. * @param {String} [defaultValue]
  794. * @returns {String} str
  795. */
  796. exports.option.asString = function (value, defaultValue) {
  797. if (typeof value == 'function') {
  798. value = value();
  799. }
  800. if (value != null) {
  801. return String(value);
  802. }
  803. return defaultValue || null;
  804. };
  805. /**
  806. * Convert a size or location into a string with pixels or a percentage
  807. * @param {String | Number | function | undefined} value
  808. * @param {String} [defaultValue]
  809. * @returns {String} size
  810. */
  811. exports.option.asSize = function (value, defaultValue) {
  812. if (typeof value == 'function') {
  813. value = value();
  814. }
  815. if (exports.isString(value)) {
  816. return value;
  817. }
  818. else if (exports.isNumber(value)) {
  819. return value + 'px';
  820. }
  821. else {
  822. return defaultValue || null;
  823. }
  824. };
  825. /**
  826. * Convert a value into a DOM element
  827. * @param {HTMLElement | function | undefined} value
  828. * @param {HTMLElement} [defaultValue]
  829. * @returns {HTMLElement | null} dom
  830. */
  831. exports.option.asElement = function (value, defaultValue) {
  832. if (typeof value == 'function') {
  833. value = value();
  834. }
  835. return value || defaultValue || null;
  836. };
  837. /**
  838. * http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
  839. *
  840. * @param {String} hex
  841. * @returns {{r: *, g: *, b: *}} | 255 range
  842. */
  843. exports.hexToRGB = function(hex) {
  844. // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
  845. var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
  846. hex = hex.replace(shorthandRegex, function(m, r, g, b) {
  847. return r + r + g + g + b + b;
  848. });
  849. var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  850. return result ? {
  851. r: parseInt(result[1], 16),
  852. g: parseInt(result[2], 16),
  853. b: parseInt(result[3], 16)
  854. } : null;
  855. };
  856. /**
  857. * This function takes color in hex format or rgb() or rgba() format and overrides the opacity. Returns rgba() string.
  858. * @param color
  859. * @param opacity
  860. * @returns {*}
  861. */
  862. exports.overrideOpacity = function(color,opacity) {
  863. if (color.indexOf("rgb") != -1) {
  864. var rgb = color.substr(color.indexOf("(")+1).replace(")","").split(",");
  865. return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + opacity + ")"
  866. }
  867. else {
  868. var rgb = exports.hexToRGB(color);
  869. if (rgb == null) {
  870. return color;
  871. }
  872. else {
  873. return "rgba(" + rgb.r + "," + rgb.g + "," + rgb.b + "," + opacity + ")"
  874. }
  875. }
  876. }
  877. /**
  878. *
  879. * @param red 0 -- 255
  880. * @param green 0 -- 255
  881. * @param blue 0 -- 255
  882. * @returns {string}
  883. * @constructor
  884. */
  885. exports.RGBToHex = function(red,green,blue) {
  886. return "#" + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1);
  887. };
  888. /**
  889. * Parse a color property into an object with border, background, and
  890. * highlight colors
  891. * @param {Object | String} color
  892. * @return {Object} colorObject
  893. */
  894. exports.parseColor = function(color) {
  895. var c;
  896. if (exports.isString(color)) {
  897. if (exports.isValidRGB(color)) {
  898. var rgb = color.substr(4).substr(0,color.length-5).split(',');
  899. color = exports.RGBToHex(rgb[0],rgb[1],rgb[2]);
  900. }
  901. if (exports.isValidHex(color)) {
  902. var hsv = exports.hexToHSV(color);
  903. var lighterColorHSV = {h:hsv.h,s:hsv.s * 0.45,v:Math.min(1,hsv.v * 1.05)};
  904. var darkerColorHSV = {h:hsv.h,s:Math.min(1,hsv.v * 1.25),v:hsv.v*0.6};
  905. var darkerColorHex = exports.HSVToHex(darkerColorHSV.h ,darkerColorHSV.h ,darkerColorHSV.v);
  906. var lighterColorHex = exports.HSVToHex(lighterColorHSV.h,lighterColorHSV.s,lighterColorHSV.v);
  907. c = {
  908. background: color,
  909. border:darkerColorHex,
  910. highlight: {
  911. background:lighterColorHex,
  912. border:darkerColorHex
  913. },
  914. hover: {
  915. background:lighterColorHex,
  916. border:darkerColorHex
  917. }
  918. };
  919. }
  920. else {
  921. c = {
  922. background:color,
  923. border:color,
  924. highlight: {
  925. background:color,
  926. border:color
  927. },
  928. hover: {
  929. background:color,
  930. border:color
  931. }
  932. };
  933. }
  934. }
  935. else {
  936. c = {};
  937. c.background = color.background || 'white';
  938. c.border = color.border || c.background;
  939. if (exports.isString(color.highlight)) {
  940. c.highlight = {
  941. border: color.highlight,
  942. background: color.highlight
  943. }
  944. }
  945. else {
  946. c.highlight = {};
  947. c.highlight.background = color.highlight && color.highlight.background || c.background;
  948. c.highlight.border = color.highlight && color.highlight.border || c.border;
  949. }
  950. if (exports.isString(color.hover)) {
  951. c.hover = {
  952. border: color.hover,
  953. background: color.hover
  954. }
  955. }
  956. else {
  957. c.hover = {};
  958. c.hover.background = color.hover && color.hover.background || c.background;
  959. c.hover.border = color.hover && color.hover.border || c.border;
  960. }
  961. }
  962. return c;
  963. };
  964. /**
  965. * http://www.javascripter.net/faq/rgb2hsv.htm
  966. *
  967. * @param red
  968. * @param green
  969. * @param blue
  970. * @returns {*}
  971. * @constructor
  972. */
  973. exports.RGBToHSV = function(red,green,blue) {
  974. red=red/255; green=green/255; blue=blue/255;
  975. var minRGB = Math.min(red,Math.min(green,blue));
  976. var maxRGB = Math.max(red,Math.max(green,blue));
  977. // Black-gray-white
  978. if (minRGB == maxRGB) {
  979. return {h:0,s:0,v:minRGB};
  980. }
  981. // Colors other than black-gray-white:
  982. var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
  983. var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
  984. var hue = 60*(h - d/(maxRGB - minRGB))/360;
  985. var saturation = (maxRGB - minRGB)/maxRGB;
  986. var value = maxRGB;
  987. return {h:hue,s:saturation,v:value};
  988. };
  989. var cssUtil = {
  990. // split a string with css styles into an object with key/values
  991. split: function (cssText) {
  992. var styles = {};
  993. cssText.split(';').forEach(function (style) {
  994. if (style.trim() != '') {
  995. var parts = style.split(':');
  996. var key = parts[0].trim();
  997. var value = parts[1].trim();
  998. styles[key] = value;
  999. }
  1000. });
  1001. return styles;
  1002. },
  1003. // build a css text string from an object with key/values
  1004. join: function (styles) {
  1005. return Object.keys(styles)
  1006. .map(function (key) {
  1007. return key + ': ' + styles[key];
  1008. })
  1009. .join('; ');
  1010. }
  1011. };
  1012. /**
  1013. * Append a string with css styles to an element
  1014. * @param {Element} element
  1015. * @param {String} cssText
  1016. */
  1017. exports.addCssText = function (element, cssText) {
  1018. var currentStyles = cssUtil.split(element.style.cssText);
  1019. var newStyles = cssUtil.split(cssText);
  1020. var styles = exports.extend(currentStyles, newStyles);
  1021. element.style.cssText = cssUtil.join(styles);
  1022. };
  1023. /**
  1024. * Remove a string with css styles from an element
  1025. * @param {Element} element
  1026. * @param {String} cssText
  1027. */
  1028. exports.removeCssText = function (element, cssText) {
  1029. var styles = cssUtil.split(element.style.cssText);
  1030. var removeStyles = cssUtil.split(cssText);
  1031. for (var key in removeStyles) {
  1032. if (removeStyles.hasOwnProperty(key)) {
  1033. delete styles[key];
  1034. }
  1035. }
  1036. element.style.cssText = cssUtil.join(styles);
  1037. };
  1038. /**
  1039. * https://gist.github.com/mjijackson/5311256
  1040. * @param h
  1041. * @param s
  1042. * @param v
  1043. * @returns {{r: number, g: number, b: number}}
  1044. * @constructor
  1045. */
  1046. exports.HSVToRGB = function(h, s, v) {
  1047. var r, g, b;
  1048. var i = Math.floor(h * 6);
  1049. var f = h * 6 - i;
  1050. var p = v * (1 - s);
  1051. var q = v * (1 - f * s);
  1052. var t = v * (1 - (1 - f) * s);
  1053. switch (i % 6) {
  1054. case 0: r = v, g = t, b = p; break;
  1055. case 1: r = q, g = v, b = p; break;
  1056. case 2: r = p, g = v, b = t; break;
  1057. case 3: r = p, g = q, b = v; break;
  1058. case 4: r = t, g = p, b = v; break;
  1059. case 5: r = v, g = p, b = q; break;
  1060. }
  1061. return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
  1062. };
  1063. exports.HSVToHex = function(h, s, v) {
  1064. var rgb = exports.HSVToRGB(h, s, v);
  1065. return exports.RGBToHex(rgb.r, rgb.g, rgb.b);
  1066. };
  1067. exports.hexToHSV = function(hex) {
  1068. var rgb = exports.hexToRGB(hex);
  1069. return exports.RGBToHSV(rgb.r, rgb.g, rgb.b);
  1070. };
  1071. exports.isValidHex = function(hex) {
  1072. var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
  1073. return isOk;
  1074. };
  1075. exports.isValidRGB = function(rgb) {
  1076. rgb = rgb.replace(" ","");
  1077. var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb);
  1078. return isOk;
  1079. }
  1080. /**
  1081. * This recursively redirects the prototype of JSON objects to the referenceObject
  1082. * This is used for default options.
  1083. *
  1084. * @param referenceObject
  1085. * @returns {*}
  1086. */
  1087. exports.selectiveBridgeObject = function(fields, referenceObject) {
  1088. if (typeof referenceObject == "object") {
  1089. var objectTo = Object.create(referenceObject);
  1090. for (var i = 0; i < fields.length; i++) {
  1091. if (referenceObject.hasOwnProperty(fields[i])) {
  1092. if (typeof referenceObject[fields[i]] == "object") {
  1093. objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]);
  1094. }
  1095. }
  1096. }
  1097. return objectTo;
  1098. }
  1099. else {
  1100. return null;
  1101. }
  1102. };
  1103. /**
  1104. * This recursively redirects the prototype of JSON objects to the referenceObject
  1105. * This is used for default options.
  1106. *
  1107. * @param referenceObject
  1108. * @returns {*}
  1109. */
  1110. exports.bridgeObject = function(referenceObject) {
  1111. if (typeof referenceObject == "object") {
  1112. var objectTo = Object.create(referenceObject);
  1113. for (var i in referenceObject) {
  1114. if (referenceObject.hasOwnProperty(i)) {
  1115. if (typeof referenceObject[i] == "object") {
  1116. objectTo[i] = exports.bridgeObject(referenceObject[i]);
  1117. }
  1118. }
  1119. }
  1120. return objectTo;
  1121. }
  1122. else {
  1123. return null;
  1124. }
  1125. };
  1126. /**
  1127. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  1128. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  1129. *
  1130. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  1131. * @param [object] options | options
  1132. * @param [String] option | this is the option key in the options argument
  1133. * @private
  1134. */
  1135. exports.mergeOptions = function (mergeTarget, options, option) {
  1136. if (options[option] !== undefined) {
  1137. if (typeof options[option] == 'boolean') {
  1138. mergeTarget[option].enabled = options[option];
  1139. }
  1140. else {
  1141. mergeTarget[option].enabled = true;
  1142. for (var prop in options[option]) {
  1143. if (options[option].hasOwnProperty(prop)) {
  1144. mergeTarget[option][prop] = options[option][prop];
  1145. }
  1146. }
  1147. }
  1148. }
  1149. }
  1150. /**
  1151. * This function does a binary search for a visible item in a sorted list. If we find a visible item, the code that uses
  1152. * this function will then iterate in both directions over this sorted list to find all visible items.
  1153. *
  1154. * @param {Item[]} orderedItems | Items ordered by start
  1155. * @param {function} searchFunction | -1 is lower, 0 is found, 1 is higher
  1156. * @param {String} field
  1157. * @param {String} field2
  1158. * @returns {number}
  1159. * @private
  1160. */
  1161. exports.binarySearchCustom = function(orderedItems, searchFunction, field, field2) {
  1162. var maxIterations = 10000;
  1163. var iteration = 0;
  1164. var low = 0;
  1165. var high = orderedItems.length - 1;
  1166. while (low <= high && iteration < maxIterations) {
  1167. var middle = Math.floor((low + high) / 2);
  1168. var item = orderedItems[middle];
  1169. var value = (field2 === undefined) ? item[field] : item[field][field2];
  1170. var searchResult = searchFunction(value);
  1171. if (searchResult == 0) { // jihaa, found a visible item!
  1172. return middle;
  1173. }
  1174. else if (searchResult == -1) { // it is too small --> increase low
  1175. low = middle + 1;
  1176. }
  1177. else { // it is too big --> decrease high
  1178. high = middle - 1;
  1179. }
  1180. iteration++;
  1181. }
  1182. return -1;
  1183. };
  1184. /**
  1185. * This function does a binary search for a specific value in a sorted array. If it does not exist but is in between of
  1186. * two values, we return either the one before or the one after, depending on user input
  1187. * If it is found, we return the index, else -1.
  1188. *
  1189. * @param {Array} orderedItems
  1190. * @param {{start: number, end: number}} target
  1191. * @param {String} field
  1192. * @param {String} sidePreference 'before' or 'after'
  1193. * @returns {number}
  1194. * @private
  1195. */
  1196. exports.binarySearchValue = function(orderedItems, target, field, sidePreference) {
  1197. var maxIterations = 10000;
  1198. var iteration = 0;
  1199. var low = 0;
  1200. var high = orderedItems.length - 1;
  1201. var prevValue, value, nextValue, middle;
  1202. while (low <= high && iteration < maxIterations) {
  1203. // get a new guess
  1204. middle = Math.floor(0.5*(high+low));
  1205. prevValue = orderedItems[Math.max(0,middle - 1)][field];
  1206. value = orderedItems[middle][field];
  1207. nextValue = orderedItems[Math.min(orderedItems.length-1,middle + 1)][field];
  1208. if (value == target) { // we found the target
  1209. return middle;
  1210. }
  1211. else if (prevValue < target && value > target) { // target is in between of the previous and the current
  1212. return sidePreference == 'before' ? Math.max(0,middle - 1) : middle;
  1213. }
  1214. else if (value < target && nextValue > target) { // target is in between of the current and the next
  1215. return sidePreference == 'before' ? middle : Math.min(orderedItems.length-1,middle + 1);
  1216. }
  1217. else { // didnt find the target, we need to change our boundaries.
  1218. if (value < target) { // it is too small --> increase low
  1219. low = middle + 1;
  1220. }
  1221. else { // it is too big --> decrease high
  1222. high = middle - 1;
  1223. }
  1224. }
  1225. iteration++;
  1226. }
  1227. // didnt find anything. Return -1.
  1228. return -1;
  1229. };
  1230. /**
  1231. * Quadratic ease-in-out
  1232. * http://gizma.com/easing/
  1233. * @param {number} t Current time
  1234. * @param {number} start Start value
  1235. * @param {number} end End value
  1236. * @param {number} duration Duration
  1237. * @returns {number} Value corresponding with current time
  1238. */
  1239. exports.easeInOutQuad = function (t, start, end, duration) {
  1240. var change = end - start;
  1241. t /= duration/2;
  1242. if (t < 1) return change/2*t*t + start;
  1243. t--;
  1244. return -change/2 * (t*(t-2) - 1) + start;
  1245. };
  1246. /*
  1247. * Easing Functions - inspired from http://gizma.com/easing/
  1248. * only considering the t value for the range [0, 1] => [0, 1]
  1249. * https://gist.github.com/gre/1650294
  1250. */
  1251. exports.easingFunctions = {
  1252. // no easing, no acceleration
  1253. linear: function (t) {
  1254. return t
  1255. },
  1256. // accelerating from zero velocity
  1257. easeInQuad: function (t) {
  1258. return t * t
  1259. },
  1260. // decelerating to zero velocity
  1261. easeOutQuad: function (t) {
  1262. return t * (2 - t)
  1263. },
  1264. // acceleration until halfway, then deceleration
  1265. easeInOutQuad: function (t) {
  1266. return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t
  1267. },
  1268. // accelerating from zero velocity
  1269. easeInCubic: function (t) {
  1270. return t * t * t
  1271. },
  1272. // decelerating to zero velocity
  1273. easeOutCubic: function (t) {
  1274. return (--t) * t * t + 1
  1275. },
  1276. // acceleration until halfway, then deceleration
  1277. easeInOutCubic: function (t) {
  1278. return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
  1279. },
  1280. // accelerating from zero velocity
  1281. easeInQuart: function (t) {
  1282. return t * t * t * t
  1283. },
  1284. // decelerating to zero velocity
  1285. easeOutQuart: function (t) {
  1286. return 1 - (--t) * t * t * t
  1287. },
  1288. // acceleration until halfway, then deceleration
  1289. easeInOutQuart: function (t) {
  1290. return t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t
  1291. },
  1292. // accelerating from zero velocity
  1293. easeInQuint: function (t) {
  1294. return t * t * t * t * t
  1295. },
  1296. // decelerating to zero velocity
  1297. easeOutQuint: function (t) {
  1298. return 1 + (--t) * t * t * t * t
  1299. },
  1300. // acceleration until halfway, then deceleration
  1301. easeInOutQuint: function (t) {
  1302. return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
  1303. }
  1304. };
  1305. /***/ },
  1306. /* 2 */
  1307. /***/ function(module, exports, __webpack_require__) {
  1308. // DOM utility methods
  1309. /**
  1310. * this prepares the JSON container for allocating SVG elements
  1311. * @param JSONcontainer
  1312. * @private
  1313. */
  1314. exports.prepareElements = function(JSONcontainer) {
  1315. // cleanup the redundant svgElements;
  1316. for (var elementType in JSONcontainer) {
  1317. if (JSONcontainer.hasOwnProperty(elementType)) {
  1318. JSONcontainer[elementType].redundant = JSONcontainer[elementType].used;
  1319. JSONcontainer[elementType].used = [];
  1320. }
  1321. }
  1322. };
  1323. /**
  1324. * this cleans up all the unused SVG elements. By asking for the parentNode, we only need to supply the JSON container from
  1325. * which to remove the redundant elements.
  1326. *
  1327. * @param JSONcontainer
  1328. * @private
  1329. */
  1330. exports.cleanupElements = function(JSONcontainer) {
  1331. // cleanup the redundant svgElements;
  1332. for (var elementType in JSONcontainer) {
  1333. if (JSONcontainer.hasOwnProperty(elementType)) {
  1334. if (JSONcontainer[elementType].redundant) {
  1335. for (var i = 0; i < JSONcontainer[elementType].redundant.length; i++) {
  1336. JSONcontainer[elementType].redundant[i].parentNode.removeChild(JSONcontainer[elementType].redundant[i]);
  1337. }
  1338. JSONcontainer[elementType].redundant = [];
  1339. }
  1340. }
  1341. }
  1342. };
  1343. /**
  1344. * Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer
  1345. * the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this.
  1346. *
  1347. * @param elementType
  1348. * @param JSONcontainer
  1349. * @param svgContainer
  1350. * @returns {*}
  1351. * @private
  1352. */
  1353. exports.getSVGElement = function (elementType, JSONcontainer, svgContainer) {
  1354. var element;
  1355. // allocate SVG element, if it doesnt yet exist, create one.
  1356. if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
  1357. // check if there is an redundant element
  1358. if (JSONcontainer[elementType].redundant.length > 0) {
  1359. element = JSONcontainer[elementType].redundant[0];
  1360. JSONcontainer[elementType].redundant.shift();
  1361. }
  1362. else {
  1363. // create a new element and add it to the SVG
  1364. element = document.createElementNS('http://www.w3.org/2000/svg', elementType);
  1365. svgContainer.appendChild(element);
  1366. }
  1367. }
  1368. else {
  1369. // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it.
  1370. element = document.createElementNS('http://www.w3.org/2000/svg', elementType);
  1371. JSONcontainer[elementType] = {used: [], redundant: []};
  1372. svgContainer.appendChild(element);
  1373. }
  1374. JSONcontainer[elementType].used.push(element);
  1375. return element;
  1376. };
  1377. /**
  1378. * Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer
  1379. * the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this.
  1380. *
  1381. * @param elementType
  1382. * @param JSONcontainer
  1383. * @param DOMContainer
  1384. * @returns {*}
  1385. * @private
  1386. */
  1387. exports.getDOMElement = function (elementType, JSONcontainer, DOMContainer, insertBefore) {
  1388. var element;
  1389. // allocate DOM element, if it doesnt yet exist, create one.
  1390. if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
  1391. // check if there is an redundant element
  1392. if (JSONcontainer[elementType].redundant.length > 0) {
  1393. element = JSONcontainer[elementType].redundant[0];
  1394. JSONcontainer[elementType].redundant.shift();
  1395. }
  1396. else {
  1397. // create a new element and add it to the SVG
  1398. element = document.createElement(elementType);
  1399. if (insertBefore !== undefined) {
  1400. DOMContainer.insertBefore(element, insertBefore);
  1401. }
  1402. else {
  1403. DOMContainer.appendChild(element);
  1404. }
  1405. }
  1406. }
  1407. else {
  1408. // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it.
  1409. element = document.createElement(elementType);
  1410. JSONcontainer[elementType] = {used: [], redundant: []};
  1411. if (insertBefore !== undefined) {
  1412. DOMContainer.insertBefore(element, insertBefore);
  1413. }
  1414. else {
  1415. DOMContainer.appendChild(element);
  1416. }
  1417. }
  1418. JSONcontainer[elementType].used.push(element);
  1419. return element;
  1420. };
  1421. /**
  1422. * draw a point object. this is a seperate function because it can also be called by the legend.
  1423. * The reason the JSONcontainer and the target SVG svgContainer have to be supplied is so the legend can use these functions
  1424. * as well.
  1425. *
  1426. * @param x
  1427. * @param y
  1428. * @param group
  1429. * @param JSONcontainer
  1430. * @param svgContainer
  1431. * @param labelObj
  1432. * @returns {*}
  1433. */
  1434. exports.drawPoint = function(x, y, group, JSONcontainer, svgContainer, labelObj) {
  1435. var point;
  1436. if (group.options.drawPoints.style == 'circle') {
  1437. point = exports.getSVGElement('circle',JSONcontainer,svgContainer);
  1438. point.setAttributeNS(null, "cx", x);
  1439. point.setAttributeNS(null, "cy", y);
  1440. point.setAttributeNS(null, "r", 0.5 * group.options.drawPoints.size);
  1441. }
  1442. else {
  1443. point = exports.getSVGElement('rect',JSONcontainer,svgContainer);
  1444. point.setAttributeNS(null, "x", x - 0.5*group.options.drawPoints.size);
  1445. point.setAttributeNS(null, "y", y - 0.5*group.options.drawPoints.size);
  1446. point.setAttributeNS(null, "width", group.options.drawPoints.size);
  1447. point.setAttributeNS(null, "height", group.options.drawPoints.size);
  1448. }
  1449. if(group.options.drawPoints.styles !== undefined) {
  1450. point.setAttributeNS(null, "style", group.group.options.drawPoints.styles);
  1451. }
  1452. point.setAttributeNS(null, "class", group.className + " point");
  1453. //handle label
  1454. var label = exports.getSVGElement('text',JSONcontainer,svgContainer);
  1455. if (labelObj){
  1456. if (labelObj.xOffset) {
  1457. x = x + labelObj.xOffset;
  1458. }
  1459. if (labelObj.yOffset) {
  1460. y = y + labelObj.yOffset;
  1461. }
  1462. if (labelObj.content) {
  1463. label.textContent = labelObj.content;
  1464. }
  1465. if (labelObj.className) {
  1466. label.setAttributeNS(null, "class", labelObj.className + " label");
  1467. }
  1468. }
  1469. label.setAttributeNS(null, "x", x);
  1470. label.setAttributeNS(null, "y", y);
  1471. return point;
  1472. };
  1473. /**
  1474. * draw a bar SVG element centered on the X coordinate
  1475. *
  1476. * @param x
  1477. * @param y
  1478. * @param className
  1479. */
  1480. exports.drawBar = function (x, y, width, height, className, JSONcontainer, svgContainer) {
  1481. if (height != 0) {
  1482. if (height < 0) {
  1483. height *= -1;
  1484. y -= height;
  1485. }
  1486. var rect = exports.getSVGElement('rect',JSONcontainer, svgContainer);
  1487. rect.setAttributeNS(null, "x", x - 0.5 * width);
  1488. rect.setAttributeNS(null, "y", y);
  1489. rect.setAttributeNS(null, "width", width);
  1490. rect.setAttributeNS(null, "height", height);
  1491. rect.setAttributeNS(null, "class", className);
  1492. }
  1493. };
  1494. /***/ },
  1495. /* 3 */
  1496. /***/ function(module, exports, __webpack_require__) {
  1497. var util = __webpack_require__(1);
  1498. var Queue = __webpack_require__(5);
  1499. /**
  1500. * DataSet
  1501. *
  1502. * Usage:
  1503. * var dataSet = new DataSet({
  1504. * fieldId: '_id',
  1505. * type: {
  1506. * // ...
  1507. * }
  1508. * });
  1509. *
  1510. * dataSet.add(item);
  1511. * dataSet.add(data);
  1512. * dataSet.update(item);
  1513. * dataSet.update(data);
  1514. * dataSet.remove(id);
  1515. * dataSet.remove(ids);
  1516. * var data = dataSet.get();
  1517. * var data = dataSet.get(id);
  1518. * var data = dataSet.get(ids);
  1519. * var data = dataSet.get(ids, options, data);
  1520. * dataSet.clear();
  1521. *
  1522. * A data set can:
  1523. * - add/remove/update data
  1524. * - gives triggers upon changes in the data
  1525. * - can import/export data in various data formats
  1526. *
  1527. * @param {Array | DataTable} [data] Optional array with initial data
  1528. * @param {Object} [options] Available options:
  1529. * {String} fieldId Field name of the id in the
  1530. * items, 'id' by default.
  1531. * {Object.<String, String} type
  1532. * A map with field names as key,
  1533. * and the field type as value.
  1534. * {Object} queue Queue changes to the DataSet,
  1535. * flush them all at once.
  1536. * Queue options:
  1537. * - {number} delay Delay in ms, null by default
  1538. * - {number} max Maximum number of entries in the queue, Infinity by default
  1539. * @constructor DataSet
  1540. */
  1541. // TODO: add a DataSet constructor DataSet(data, options)
  1542. function DataSet (data, options) {
  1543. // correctly read optional arguments
  1544. if (data && !Array.isArray(data) && !util.isDataTable(data)) {
  1545. options = data;
  1546. data = null;
  1547. }
  1548. this._options = options || {};
  1549. this._data = {}; // map with data indexed by id
  1550. this.length = 0; // number of items in the DataSet
  1551. this._fieldId = this._options.fieldId || 'id'; // name of the field containing id
  1552. this._type = {}; // internal field types (NOTE: this can differ from this._options.type)
  1553. // all variants of a Date are internally stored as Date, so we can convert
  1554. // from everything to everything (also from ISODate to Number for example)
  1555. if (this._options.type) {
  1556. for (var field in this._options.type) {
  1557. if (this._options.type.hasOwnProperty(field)) {
  1558. var value = this._options.type[field];
  1559. if (value == 'Date' || value == 'ISODate' || value == 'ASPDate') {
  1560. this._type[field] = 'Date';
  1561. }
  1562. else {
  1563. this._type[field] = value;
  1564. }
  1565. }
  1566. }
  1567. }
  1568. // TODO: deprecated since version 1.1.1 (or 2.0.0?)
  1569. if (this._options.convert) {
  1570. throw new Error('Option "convert" is deprecated. Use "type" instead.');
  1571. }
  1572. this._subscribers = {}; // event subscribers
  1573. // add initial data when provided
  1574. if (data) {
  1575. this.add(data);
  1576. }
  1577. this.setOptions(options);
  1578. }
  1579. /**
  1580. * @param {Object} [options] Available options:
  1581. * {Object} queue Queue changes to the DataSet,
  1582. * flush them all at once.
  1583. * Queue options:
  1584. * - {number} delay Delay in ms, null by default
  1585. * - {number} max Maximum number of entries in the queue, Infinity by default
  1586. * @param options
  1587. */
  1588. DataSet.prototype.setOptions = function(options) {
  1589. if (options && options.queue !== undefined) {
  1590. if (options.queue === false) {
  1591. // delete queue if loaded
  1592. if (this._queue) {
  1593. this._queue.destroy();
  1594. delete this._queue;
  1595. }
  1596. }
  1597. else {
  1598. // create queue and update its options
  1599. if (!this._queue) {
  1600. this._queue = Queue.extend(this, {
  1601. replace: ['add', 'update', 'remove']
  1602. });
  1603. }
  1604. if (typeof options.queue === 'object') {
  1605. this._queue.setOptions(options.queue);
  1606. }
  1607. }
  1608. }
  1609. };
  1610. /**
  1611. * Subscribe to an event, add an event listener
  1612. * @param {String} event Event name. Available events: 'put', 'update',
  1613. * 'remove'
  1614. * @param {function} callback Callback method. Called with three parameters:
  1615. * {String} event
  1616. * {Object | null} params
  1617. * {String | Number} senderId
  1618. */
  1619. DataSet.prototype.on = function(event, callback) {
  1620. var subscribers = this._subscribers[event];
  1621. if (!subscribers) {
  1622. subscribers = [];
  1623. this._subscribers[event] = subscribers;
  1624. }
  1625. subscribers.push({
  1626. callback: callback
  1627. });
  1628. };
  1629. // TODO: make this function deprecated (replaced with `on` since version 0.5)
  1630. DataSet.prototype.subscribe = DataSet.prototype.on;
  1631. /**
  1632. * Unsubscribe from an event, remove an event listener
  1633. * @param {String} event
  1634. * @param {function} callback
  1635. */
  1636. DataSet.prototype.off = function(event, callback) {
  1637. var subscribers = this._subscribers[event];
  1638. if (subscribers) {
  1639. this._subscribers[event] = subscribers.filter(function (listener) {
  1640. return (listener.callback != callback);
  1641. });
  1642. }
  1643. };
  1644. // TODO: make this function deprecated (replaced with `on` since version 0.5)
  1645. DataSet.prototype.unsubscribe = DataSet.prototype.off;
  1646. /**
  1647. * Trigger an event
  1648. * @param {String} event
  1649. * @param {Object | null} params
  1650. * @param {String} [senderId] Optional id of the sender.
  1651. * @private
  1652. */
  1653. DataSet.prototype._trigger = function (event, params, senderId) {
  1654. if (event == '*') {
  1655. throw new Error('Cannot trigger event *');
  1656. }
  1657. var subscribers = [];
  1658. if (event in this._subscribers) {
  1659. subscribers = subscribers.concat(this._subscribers[event]);
  1660. }
  1661. if ('*' in this._subscribers) {
  1662. subscribers = subscribers.concat(this._subscribers['*']);
  1663. }
  1664. for (var i = 0; i < subscribers.length; i++) {
  1665. var subscriber = subscribers[i];
  1666. if (subscriber.callback) {
  1667. subscriber.callback(event, params, senderId || null);
  1668. }
  1669. }
  1670. };
  1671. /**
  1672. * Add data.
  1673. * Adding an item will fail when there already is an item with the same id.
  1674. * @param {Object | Array | DataTable} data
  1675. * @param {String} [senderId] Optional sender id
  1676. * @return {Array} addedIds Array with the ids of the added items
  1677. */
  1678. DataSet.prototype.add = function (data, senderId) {
  1679. var addedIds = [],
  1680. id,
  1681. me = this;
  1682. if (Array.isArray(data)) {
  1683. // Array
  1684. for (var i = 0, len = data.length; i < len; i++) {
  1685. id = me._addItem(data[i]);
  1686. addedIds.push(id);
  1687. }
  1688. }
  1689. else if (util.isDataTable(data)) {
  1690. // Google DataTable
  1691. var columns = this._getColumnNames(data);
  1692. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  1693. var item = {};
  1694. for (var col = 0, cols = columns.length; col < cols; col++) {
  1695. var field = columns[col];
  1696. item[field] = data.getValue(row, col);
  1697. }
  1698. id = me._addItem(item);
  1699. addedIds.push(id);
  1700. }
  1701. }
  1702. else if (data instanceof Object) {
  1703. // Single item
  1704. id = me._addItem(data);
  1705. addedIds.push(id);
  1706. }
  1707. else {
  1708. throw new Error('Unknown dataType');
  1709. }
  1710. if (addedIds.length) {
  1711. this._trigger('add', {items: addedIds}, senderId);
  1712. }
  1713. return addedIds;
  1714. };
  1715. /**
  1716. * Update existing items. When an item does not exist, it will be created
  1717. * @param {Object | Array | DataTable} data
  1718. * @param {String} [senderId] Optional sender id
  1719. * @return {Array} updatedIds The ids of the added or updated items
  1720. */
  1721. DataSet.prototype.update = function (data, senderId) {
  1722. var addedIds = [];
  1723. var updatedIds = [];
  1724. var updatedData = [];
  1725. var me = this;
  1726. var fieldId = me._fieldId;
  1727. var addOrUpdate = function (item) {
  1728. var id = item[fieldId];
  1729. if (me._data[id]) {
  1730. // update item
  1731. id = me._updateItem(item);
  1732. updatedIds.push(id);
  1733. updatedData.push(item);
  1734. }
  1735. else {
  1736. // add new item
  1737. id = me._addItem(item);
  1738. addedIds.push(id);
  1739. }
  1740. };
  1741. if (Array.isArray(data)) {
  1742. // Array
  1743. for (var i = 0, len = data.length; i < len; i++) {
  1744. addOrUpdate(data[i]);
  1745. }
  1746. }
  1747. else if (util.isDataTable(data)) {
  1748. // Google DataTable
  1749. var columns = this._getColumnNames(data);
  1750. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  1751. var item = {};
  1752. for (var col = 0, cols = columns.length; col < cols; col++) {
  1753. var field = columns[col];
  1754. item[field] = data.getValue(row, col);
  1755. }
  1756. addOrUpdate(item);
  1757. }
  1758. }
  1759. else if (data instanceof Object) {
  1760. // Single item
  1761. addOrUpdate(data);
  1762. }
  1763. else {
  1764. throw new Error('Unknown dataType');
  1765. }
  1766. if (addedIds.length) {
  1767. this._trigger('add', {items: addedIds}, senderId);
  1768. }
  1769. if (updatedIds.length) {
  1770. this._trigger('update', {items: updatedIds, data: updatedData}, senderId);
  1771. }
  1772. return addedIds.concat(updatedIds);
  1773. };
  1774. /**
  1775. * Get a data item or multiple items.
  1776. *
  1777. * Usage:
  1778. *
  1779. * get()
  1780. * get(options: Object)
  1781. * get(options: Object, data: Array | DataTable)
  1782. *
  1783. * get(id: Number | String)
  1784. * get(id: Number | String, options: Object)
  1785. * get(id: Number | String, options: Object, data: Array | DataTable)
  1786. *
  1787. * get(ids: Number[] | String[])
  1788. * get(ids: Number[] | String[], options: Object)
  1789. * get(ids: Number[] | String[], options: Object, data: Array | DataTable)
  1790. *
  1791. * Where:
  1792. *
  1793. * {Number | String} id The id of an item
  1794. * {Number[] | String{}} ids An array with ids of items
  1795. * {Object} options An Object with options. Available options:
  1796. * {String} [returnType] Type of data to be
  1797. * returned. Can be 'DataTable' or 'Array' (default)
  1798. * {Object.<String, String>} [type]
  1799. * {String[]} [fields] field names to be returned
  1800. * {function} [filter] filter items
  1801. * {String | function} [order] Order the items by
  1802. * a field name or custom sort function.
  1803. * {Array | DataTable} [data] If provided, items will be appended to this
  1804. * array or table. Required in case of Google
  1805. * DataTable.
  1806. *
  1807. * @throws Error
  1808. */
  1809. DataSet.prototype.get = function (args) {
  1810. var me = this;
  1811. // parse the arguments
  1812. var id, ids, options, data;
  1813. var firstType = util.getType(arguments[0]);
  1814. if (firstType == 'String' || firstType == 'Number') {
  1815. // get(id [, options] [, data])
  1816. id = arguments[0];
  1817. options = arguments[1];
  1818. data = arguments[2];
  1819. }
  1820. else if (firstType == 'Array') {
  1821. // get(ids [, options] [, data])
  1822. ids = arguments[0];
  1823. options = arguments[1];
  1824. data = arguments[2];
  1825. }
  1826. else {
  1827. // get([, options] [, data])
  1828. options = arguments[0];
  1829. data = arguments[1];
  1830. }
  1831. // determine the return type
  1832. var returnType;
  1833. if (options && options.returnType) {
  1834. var allowedValues = ["DataTable", "Array", "Object"];
  1835. returnType = allowedValues.indexOf(options.returnType) == -1 ? "Array" : options.returnType;
  1836. if (data && (returnType != util.getType(data))) {
  1837. throw new Error('Type of parameter "data" (' + util.getType(data) + ') ' +
  1838. 'does not correspond with specified options.type (' + options.type + ')');
  1839. }
  1840. if (returnType == 'DataTable' && !util.isDataTable(data)) {
  1841. throw new Error('Parameter "data" must be a DataTable ' +
  1842. 'when options.type is "DataTable"');
  1843. }
  1844. }
  1845. else if (data) {
  1846. returnType = (util.getType(data) == 'DataTable') ? 'DataTable' : 'Array';
  1847. }
  1848. else {
  1849. returnType = 'Array';
  1850. }
  1851. // build options
  1852. var type = options && options.type || this._options.type;
  1853. var filter = options && options.filter;
  1854. var items = [], item, itemId, i, len;
  1855. // convert items
  1856. if (id != undefined) {
  1857. // return a single item
  1858. item = me._getItem(id, type);
  1859. if (filter && !filter(item)) {
  1860. item = null;
  1861. }
  1862. }
  1863. else if (ids != undefined) {
  1864. // return a subset of items
  1865. for (i = 0, len = ids.length; i < len; i++) {
  1866. item = me._getItem(ids[i], type);
  1867. if (!filter || filter(item)) {
  1868. items.push(item);
  1869. }
  1870. }
  1871. }
  1872. else {
  1873. // return all items
  1874. for (itemId in this._data) {
  1875. if (this._data.hasOwnProperty(itemId)) {
  1876. item = me._getItem(itemId, type);
  1877. if (!filter || filter(item)) {
  1878. items.push(item);
  1879. }
  1880. }
  1881. }
  1882. }
  1883. // order the results
  1884. if (options && options.order && id == undefined) {
  1885. this._sort(items, options.order);
  1886. }
  1887. // filter fields of the items
  1888. if (options && options.fields) {
  1889. var fields = options.fields;
  1890. if (id != undefined) {
  1891. item = this._filterFields(item, fields);
  1892. }
  1893. else {
  1894. for (i = 0, len = items.length; i < len; i++) {
  1895. items[i] = this._filterFields(items[i], fields);
  1896. }
  1897. }
  1898. }
  1899. // return the results
  1900. if (returnType == 'DataTable') {
  1901. var columns = this._getColumnNames(data);
  1902. if (id != undefined) {
  1903. // append a single item to the data table
  1904. me._appendRow(data, columns, item);
  1905. }
  1906. else {
  1907. // copy the items to the provided data table
  1908. for (i = 0; i < items.length; i++) {
  1909. me._appendRow(data, columns, items[i]);
  1910. }
  1911. }
  1912. return data;
  1913. }
  1914. else if (returnType == "Object") {
  1915. var result = {};
  1916. for (i = 0; i < items.length; i++) {
  1917. result[items[i].id] = items[i];
  1918. }
  1919. return result;
  1920. }
  1921. else {
  1922. // return an array
  1923. if (id != undefined) {
  1924. // a single item
  1925. return item;
  1926. }
  1927. else {
  1928. // multiple items
  1929. if (data) {
  1930. // copy the items to the provided array
  1931. for (i = 0, len = items.length; i < len; i++) {
  1932. data.push(items[i]);
  1933. }
  1934. return data;
  1935. }
  1936. else {
  1937. // just return our array
  1938. return items;
  1939. }
  1940. }
  1941. }
  1942. };
  1943. /**
  1944. * Get ids of all items or from a filtered set of items.
  1945. * @param {Object} [options] An Object with options. Available options:
  1946. * {function} [filter] filter items
  1947. * {String | function} [order] Order the items by
  1948. * a field name or custom sort function.
  1949. * @return {Array} ids
  1950. */
  1951. DataSet.prototype.getIds = function (options) {
  1952. var data = this._data,
  1953. filter = options && options.filter,
  1954. order = options && options.order,
  1955. type = options && options.type || this._options.type,
  1956. i,
  1957. len,
  1958. id,
  1959. item,
  1960. items,
  1961. ids = [];
  1962. if (filter) {
  1963. // get filtered items
  1964. if (order) {
  1965. // create ordered list
  1966. items = [];
  1967. for (id in data) {
  1968. if (data.hasOwnProperty(id)) {
  1969. item = this._getItem(id, type);
  1970. if (filter(item)) {
  1971. items.push(item);
  1972. }
  1973. }
  1974. }
  1975. this._sort(items, order);
  1976. for (i = 0, len = items.length; i < len; i++) {
  1977. ids[i] = items[i][this._fieldId];
  1978. }
  1979. }
  1980. else {
  1981. // create unordered list
  1982. for (id in data) {
  1983. if (data.hasOwnProperty(id)) {
  1984. item = this._getItem(id, type);
  1985. if (filter(item)) {
  1986. ids.push(item[this._fieldId]);
  1987. }
  1988. }
  1989. }
  1990. }
  1991. }
  1992. else {
  1993. // get all items
  1994. if (order) {
  1995. // create an ordered list
  1996. items = [];
  1997. for (id in data) {
  1998. if (data.hasOwnProperty(id)) {
  1999. items.push(data[id]);
  2000. }
  2001. }
  2002. this._sort(items, order);
  2003. for (i = 0, len = items.length; i < len; i++) {
  2004. ids[i] = items[i][this._fieldId];
  2005. }
  2006. }
  2007. else {
  2008. // create unordered list
  2009. for (id in data) {
  2010. if (data.hasOwnProperty(id)) {
  2011. item = data[id];
  2012. ids.push(item[this._fieldId]);
  2013. }
  2014. }
  2015. }
  2016. }
  2017. return ids;
  2018. };
  2019. /**
  2020. * Returns the DataSet itself. Is overwritten for example by the DataView,
  2021. * which returns the DataSet it is connected to instead.
  2022. */
  2023. DataSet.prototype.getDataSet = function () {
  2024. return this;
  2025. };
  2026. /**
  2027. * Execute a callback function for every item in the dataset.
  2028. * @param {function} callback
  2029. * @param {Object} [options] Available options:
  2030. * {Object.<String, String>} [type]
  2031. * {String[]} [fields] filter fields
  2032. * {function} [filter] filter items
  2033. * {String | function} [order] Order the items by
  2034. * a field name or custom sort function.
  2035. */
  2036. DataSet.prototype.forEach = function (callback, options) {
  2037. var filter = options && options.filter,
  2038. type = options && options.type || this._options.type,
  2039. data = this._data,
  2040. item,
  2041. id;
  2042. if (options && options.order) {
  2043. // execute forEach on ordered list
  2044. var items = this.get(options);
  2045. for (var i = 0, len = items.length; i < len; i++) {
  2046. item = items[i];
  2047. id = item[this._fieldId];
  2048. callback(item, id);
  2049. }
  2050. }
  2051. else {
  2052. // unordered
  2053. for (id in data) {
  2054. if (data.hasOwnProperty(id)) {
  2055. item = this._getItem(id, type);
  2056. if (!filter || filter(item)) {
  2057. callback(item, id);
  2058. }
  2059. }
  2060. }
  2061. }
  2062. };
  2063. /**
  2064. * Map every item in the dataset.
  2065. * @param {function} callback
  2066. * @param {Object} [options] Available options:
  2067. * {Object.<String, String>} [type]
  2068. * {String[]} [fields] filter fields
  2069. * {function} [filter] filter items
  2070. * {String | function} [order] Order the items by
  2071. * a field name or custom sort function.
  2072. * @return {Object[]} mappedItems
  2073. */
  2074. DataSet.prototype.map = function (callback, options) {
  2075. var filter = options && options.filter,
  2076. type = options && options.type || this._options.type,
  2077. mappedItems = [],
  2078. data = this._data,
  2079. item;
  2080. // convert and filter items
  2081. for (var id in data) {
  2082. if (data.hasOwnProperty(id)) {
  2083. item = this._getItem(id, type);
  2084. if (!filter || filter(item)) {
  2085. mappedItems.push(callback(item, id));
  2086. }
  2087. }
  2088. }
  2089. // order items
  2090. if (options && options.order) {
  2091. this._sort(mappedItems, options.order);
  2092. }
  2093. return mappedItems;
  2094. };
  2095. /**
  2096. * Filter the fields of an item
  2097. * @param {Object | null} item
  2098. * @param {String[]} fields Field names
  2099. * @return {Object | null} filteredItem or null if no item is provided
  2100. * @private
  2101. */
  2102. DataSet.prototype._filterFields = function (item, fields) {
  2103. if (!item) { // item is null
  2104. return item;
  2105. }
  2106. var filteredItem = {};
  2107. if(Array.isArray(fields)){
  2108. for (var field in item) {
  2109. if (item.hasOwnProperty(field) && (fields.indexOf(field) != -1)) {
  2110. filteredItem[field] = item[field];
  2111. }
  2112. }
  2113. }else{
  2114. for (var field in item) {
  2115. if (item.hasOwnProperty(field) && fields.hasOwnProperty(field)) {
  2116. filteredItem[fields[field]] = item[field];
  2117. }
  2118. }
  2119. }
  2120. return filteredItem;
  2121. };
  2122. /**
  2123. * Sort the provided array with items
  2124. * @param {Object[]} items
  2125. * @param {String | function} order A field name or custom sort function.
  2126. * @private
  2127. */
  2128. DataSet.prototype._sort = function (items, order) {
  2129. if (util.isString(order)) {
  2130. // order by provided field name
  2131. var name = order; // field name
  2132. items.sort(function (a, b) {
  2133. var av = a[name];
  2134. var bv = b[name];
  2135. return (av > bv) ? 1 : ((av < bv) ? -1 : 0);
  2136. });
  2137. }
  2138. else if (typeof order === 'function') {
  2139. // order by sort function
  2140. items.sort(order);
  2141. }
  2142. // TODO: extend order by an Object {field:String, direction:String}
  2143. // where direction can be 'asc' or 'desc'
  2144. else {
  2145. throw new TypeError('Order must be a function or a string');
  2146. }
  2147. };
  2148. /**
  2149. * Remove an object by pointer or by id
  2150. * @param {String | Number | Object | Array} id Object or id, or an array with
  2151. * objects or ids to be removed
  2152. * @param {String} [senderId] Optional sender id
  2153. * @return {Array} removedIds
  2154. */
  2155. DataSet.prototype.remove = function (id, senderId) {
  2156. var removedIds = [],
  2157. i, len, removedId;
  2158. if (Array.isArray(id)) {
  2159. for (i = 0, len = id.length; i < len; i++) {
  2160. removedId = this._remove(id[i]);
  2161. if (removedId != null) {
  2162. removedIds.push(removedId);
  2163. }
  2164. }
  2165. }
  2166. else {
  2167. removedId = this._remove(id);
  2168. if (removedId != null) {
  2169. removedIds.push(removedId);
  2170. }
  2171. }
  2172. if (removedIds.length) {
  2173. this._trigger('remove', {items: removedIds}, senderId);
  2174. }
  2175. return removedIds;
  2176. };
  2177. /**
  2178. * Remove an item by its id
  2179. * @param {Number | String | Object} id id or item
  2180. * @returns {Number | String | null} id
  2181. * @private
  2182. */
  2183. DataSet.prototype._remove = function (id) {
  2184. if (util.isNumber(id) || util.isString(id)) {
  2185. if (this._data[id]) {
  2186. delete this._data[id];
  2187. this.length--;
  2188. return id;
  2189. }
  2190. }
  2191. else if (id instanceof Object) {
  2192. var itemId = id[this._fieldId];
  2193. if (itemId && this._data[itemId]) {
  2194. delete this._data[itemId];
  2195. this.length--;
  2196. return itemId;
  2197. }
  2198. }
  2199. return null;
  2200. };
  2201. /**
  2202. * Clear the data
  2203. * @param {String} [senderId] Optional sender id
  2204. * @return {Array} removedIds The ids of all removed items
  2205. */
  2206. DataSet.prototype.clear = function (senderId) {
  2207. var ids = Object.keys(this._data);
  2208. this._data = {};
  2209. this.length = 0;
  2210. this._trigger('remove', {items: ids}, senderId);
  2211. return ids;
  2212. };
  2213. /**
  2214. * Find the item with maximum value of a specified field
  2215. * @param {String} field
  2216. * @return {Object | null} item Item containing max value, or null if no items
  2217. */
  2218. DataSet.prototype.max = function (field) {
  2219. var data = this._data,
  2220. max = null,
  2221. maxField = null;
  2222. for (var id in data) {
  2223. if (data.hasOwnProperty(id)) {
  2224. var item = data[id];
  2225. var itemField = item[field];
  2226. if (itemField != null && (!max || itemField > maxField)) {
  2227. max = item;
  2228. maxField = itemField;
  2229. }
  2230. }
  2231. }
  2232. return max;
  2233. };
  2234. /**
  2235. * Find the item with minimum value of a specified field
  2236. * @param {String} field
  2237. * @return {Object | null} item Item containing max value, or null if no items
  2238. */
  2239. DataSet.prototype.min = function (field) {
  2240. var data = this._data,
  2241. min = null,
  2242. minField = null;
  2243. for (var id in data) {
  2244. if (data.hasOwnProperty(id)) {
  2245. var item = data[id];
  2246. var itemField = item[field];
  2247. if (itemField != null && (!min || itemField < minField)) {
  2248. min = item;
  2249. minField = itemField;
  2250. }
  2251. }
  2252. }
  2253. return min;
  2254. };
  2255. /**
  2256. * Find all distinct values of a specified field
  2257. * @param {String} field
  2258. * @return {Array} values Array containing all distinct values. If data items
  2259. * do not contain the specified field are ignored.
  2260. * The returned array is unordered.
  2261. */
  2262. DataSet.prototype.distinct = function (field) {
  2263. var data = this._data;
  2264. var values = [];
  2265. var fieldType = this._options.type && this._options.type[field] || null;
  2266. var count = 0;
  2267. var i;
  2268. for (var prop in data) {
  2269. if (data.hasOwnProperty(prop)) {
  2270. var item = data[prop];
  2271. var value = item[field];
  2272. var exists = false;
  2273. for (i = 0; i < count; i++) {
  2274. if (values[i] == value) {
  2275. exists = true;
  2276. break;
  2277. }
  2278. }
  2279. if (!exists && (value !== undefined)) {
  2280. values[count] = value;
  2281. count++;
  2282. }
  2283. }
  2284. }
  2285. if (fieldType) {
  2286. for (i = 0; i < values.length; i++) {
  2287. values[i] = util.convert(values[i], fieldType);
  2288. }
  2289. }
  2290. return values;
  2291. };
  2292. /**
  2293. * Add a single item. Will fail when an item with the same id already exists.
  2294. * @param {Object} item
  2295. * @return {String} id
  2296. * @private
  2297. */
  2298. DataSet.prototype._addItem = function (item) {
  2299. var id = item[this._fieldId];
  2300. if (id != undefined) {
  2301. // check whether this id is already taken
  2302. if (this._data[id]) {
  2303. // item already exists
  2304. throw new Error('Cannot add item: item with id ' + id + ' already exists');
  2305. }
  2306. }
  2307. else {
  2308. // generate an id
  2309. id = util.randomUUID();
  2310. item[this._fieldId] = id;
  2311. }
  2312. var d = {};
  2313. for (var field in item) {
  2314. if (item.hasOwnProperty(field)) {
  2315. var fieldType = this._type[field]; // type may be undefined
  2316. d[field] = util.convert(item[field], fieldType);
  2317. }
  2318. }
  2319. this._data[id] = d;
  2320. this.length++;
  2321. return id;
  2322. };
  2323. /**
  2324. * Get an item. Fields can be converted to a specific type
  2325. * @param {String} id
  2326. * @param {Object.<String, String>} [types] field types to convert
  2327. * @return {Object | null} item
  2328. * @private
  2329. */
  2330. DataSet.prototype._getItem = function (id, types) {
  2331. var field, value;
  2332. // get the item from the dataset
  2333. var raw = this._data[id];
  2334. if (!raw) {
  2335. return null;
  2336. }
  2337. // convert the items field types
  2338. var converted = {};
  2339. if (types) {
  2340. for (field in raw) {
  2341. if (raw.hasOwnProperty(field)) {
  2342. value = raw[field];
  2343. converted[field] = util.convert(value, types[field]);
  2344. }
  2345. }
  2346. }
  2347. else {
  2348. // no field types specified, no converting needed
  2349. for (field in raw) {
  2350. if (raw.hasOwnProperty(field)) {
  2351. value = raw[field];
  2352. converted[field] = value;
  2353. }
  2354. }
  2355. }
  2356. return converted;
  2357. };
  2358. /**
  2359. * Update a single item: merge with existing item.
  2360. * Will fail when the item has no id, or when there does not exist an item
  2361. * with the same id.
  2362. * @param {Object} item
  2363. * @return {String} id
  2364. * @private
  2365. */
  2366. DataSet.prototype._updateItem = function (item) {
  2367. var id = item[this._fieldId];
  2368. if (id == undefined) {
  2369. throw new Error('Cannot update item: item has no id (item: ' + JSON.stringify(item) + ')');
  2370. }
  2371. var d = this._data[id];
  2372. if (!d) {
  2373. // item doesn't exist
  2374. throw new Error('Cannot update item: no item with id ' + id + ' found');
  2375. }
  2376. // merge with current item
  2377. for (var field in item) {
  2378. if (item.hasOwnProperty(field)) {
  2379. var fieldType = this._type[field]; // type may be undefined
  2380. d[field] = util.convert(item[field], fieldType);
  2381. }
  2382. }
  2383. return id;
  2384. };
  2385. /**
  2386. * Get an array with the column names of a Google DataTable
  2387. * @param {DataTable} dataTable
  2388. * @return {String[]} columnNames
  2389. * @private
  2390. */
  2391. DataSet.prototype._getColumnNames = function (dataTable) {
  2392. var columns = [];
  2393. for (var col = 0, cols = dataTable.getNumberOfColumns(); col < cols; col++) {
  2394. columns[col] = dataTable.getColumnId(col) || dataTable.getColumnLabel(col);
  2395. }
  2396. return columns;
  2397. };
  2398. /**
  2399. * Append an item as a row to the dataTable
  2400. * @param dataTable
  2401. * @param columns
  2402. * @param item
  2403. * @private
  2404. */
  2405. DataSet.prototype._appendRow = function (dataTable, columns, item) {
  2406. var row = dataTable.addRow();
  2407. for (var col = 0, cols = columns.length; col < cols; col++) {
  2408. var field = columns[col];
  2409. dataTable.setValue(row, col, item[field]);
  2410. }
  2411. };
  2412. module.exports = DataSet;
  2413. /***/ },
  2414. /* 4 */
  2415. /***/ function(module, exports, __webpack_require__) {
  2416. var util = __webpack_require__(1);
  2417. var DataSet = __webpack_require__(3);
  2418. /**
  2419. * DataView
  2420. *
  2421. * a dataview offers a filtered view on a dataset or an other dataview.
  2422. *
  2423. * @param {DataSet | DataView} data
  2424. * @param {Object} [options] Available options: see method get
  2425. *
  2426. * @constructor DataView
  2427. */
  2428. function DataView (data, options) {
  2429. this._data = null;
  2430. this._ids = {}; // ids of the items currently in memory (just contains a boolean true)
  2431. this.length = 0; // number of items in the DataView
  2432. this._options = options || {};
  2433. this._fieldId = 'id'; // name of the field containing id
  2434. this._subscribers = {}; // event subscribers
  2435. var me = this;
  2436. this.listener = function () {
  2437. me._onEvent.apply(me, arguments);
  2438. };
  2439. this.setData(data);
  2440. }
  2441. // TODO: implement a function .config() to dynamically update things like configured filter
  2442. // and trigger changes accordingly
  2443. /**
  2444. * Set a data source for the view
  2445. * @param {DataSet | DataView} data
  2446. */
  2447. DataView.prototype.setData = function (data) {
  2448. var ids, i, len;
  2449. if (this._data) {
  2450. // unsubscribe from current dataset
  2451. if (this._data.unsubscribe) {
  2452. this._data.unsubscribe('*', this.listener);
  2453. }
  2454. // trigger a remove of all items in memory
  2455. ids = [];
  2456. for (var id in this._ids) {
  2457. if (this._ids.hasOwnProperty(id)) {
  2458. ids.push(id);
  2459. }
  2460. }
  2461. this._ids = {};
  2462. this.length = 0;
  2463. this._trigger('remove', {items: ids});
  2464. }
  2465. this._data = data;
  2466. if (this._data) {
  2467. // update fieldId
  2468. this._fieldId = this._options.fieldId ||
  2469. (this._data && this._data.options && this._data.options.fieldId) ||
  2470. 'id';
  2471. // trigger an add of all added items
  2472. ids = this._data.getIds({filter: this._options && this._options.filter});
  2473. for (i = 0, len = ids.length; i < len; i++) {
  2474. id = ids[i];
  2475. this._ids[id] = true;
  2476. }
  2477. this.length = ids.length;
  2478. this._trigger('add', {items: ids});
  2479. // subscribe to new dataset
  2480. if (this._data.on) {
  2481. this._data.on('*', this.listener);
  2482. }
  2483. }
  2484. };
  2485. /**
  2486. * Refresh the DataView. Useful when the DataView has a filter function
  2487. * containing a variable parameter.
  2488. */
  2489. DataView.prototype.refresh = function () {
  2490. var id;
  2491. var ids = this._data.getIds({filter: this._options && this._options.filter});
  2492. var newIds = {};
  2493. var added = [];
  2494. var removed = [];
  2495. // check for additions
  2496. for (var i = 0; i < ids.length; i++) {
  2497. id = ids[i];
  2498. newIds[id] = true;
  2499. if (!this._ids[id]) {
  2500. added.push(id);
  2501. this._ids[id] = true;
  2502. this.length++;
  2503. }
  2504. }
  2505. // check for removals
  2506. for (id in this._ids) {
  2507. if (this._ids.hasOwnProperty(id)) {
  2508. if (!newIds[id]) {
  2509. removed.push(id);
  2510. delete this._ids[id];
  2511. this.length--;
  2512. }
  2513. }
  2514. }
  2515. // trigger events
  2516. if (added.length) {
  2517. this._trigger('add', {items: added});
  2518. }
  2519. if (removed.length) {
  2520. this._trigger('remove', {items: removed});
  2521. }
  2522. };
  2523. /**
  2524. * Get data from the data view
  2525. *
  2526. * Usage:
  2527. *
  2528. * get()
  2529. * get(options: Object)
  2530. * get(options: Object, data: Array | DataTable)
  2531. *
  2532. * get(id: Number)
  2533. * get(id: Number, options: Object)
  2534. * get(id: Number, options: Object, data: Array | DataTable)
  2535. *
  2536. * get(ids: Number[])
  2537. * get(ids: Number[], options: Object)
  2538. * get(ids: Number[], options: Object, data: Array | DataTable)
  2539. *
  2540. * Where:
  2541. *
  2542. * {Number | String} id The id of an item
  2543. * {Number[] | String{}} ids An array with ids of items
  2544. * {Object} options An Object with options. Available options:
  2545. * {String} [type] Type of data to be returned. Can
  2546. * be 'DataTable' or 'Array' (default)
  2547. * {Object.<String, String>} [convert]
  2548. * {String[]} [fields] field names to be returned
  2549. * {function} [filter] filter items
  2550. * {String | function} [order] Order the items by
  2551. * a field name or custom sort function.
  2552. * {Array | DataTable} [data] If provided, items will be appended to this
  2553. * array or table. Required in case of Google
  2554. * DataTable.
  2555. * @param args
  2556. */
  2557. DataView.prototype.get = function (args) {
  2558. var me = this;
  2559. // parse the arguments
  2560. var ids, options, data;
  2561. var firstType = util.getType(arguments[0]);
  2562. if (firstType == 'String' || firstType == 'Number' || firstType == 'Array') {
  2563. // get(id(s) [, options] [, data])
  2564. ids = arguments[0]; // can be a single id or an array with ids
  2565. options = arguments[1];
  2566. data = arguments[2];
  2567. }
  2568. else {
  2569. // get([, options] [, data])
  2570. options = arguments[0];
  2571. data = arguments[1];
  2572. }
  2573. // extend the options with the default options and provided options
  2574. var viewOptions = util.extend({}, this._options, options);
  2575. // create a combined filter method when needed
  2576. if (this._options.filter && options && options.filter) {
  2577. viewOptions.filter = function (item) {
  2578. return me._options.filter(item) && options.filter(item);
  2579. }
  2580. }
  2581. // build up the call to the linked data set
  2582. var getArguments = [];
  2583. if (ids != undefined) {
  2584. getArguments.push(ids);
  2585. }
  2586. getArguments.push(viewOptions);
  2587. getArguments.push(data);
  2588. return this._data && this._data.get.apply(this._data, getArguments);
  2589. };
  2590. /**
  2591. * Get ids of all items or from a filtered set of items.
  2592. * @param {Object} [options] An Object with options. Available options:
  2593. * {function} [filter] filter items
  2594. * {String | function} [order] Order the items by
  2595. * a field name or custom sort function.
  2596. * @return {Array} ids
  2597. */
  2598. DataView.prototype.getIds = function (options) {
  2599. var ids;
  2600. if (this._data) {
  2601. var defaultFilter = this._options.filter;
  2602. var filter;
  2603. if (options && options.filter) {
  2604. if (defaultFilter) {
  2605. filter = function (item) {
  2606. return defaultFilter(item) && options.filter(item);
  2607. }
  2608. }
  2609. else {
  2610. filter = options.filter;
  2611. }
  2612. }
  2613. else {
  2614. filter = defaultFilter;
  2615. }
  2616. ids = this._data.getIds({
  2617. filter: filter,
  2618. order: options && options.order
  2619. });
  2620. }
  2621. else {
  2622. ids = [];
  2623. }
  2624. return ids;
  2625. };
  2626. /**
  2627. * Get the DataSet to which this DataView is connected. In case there is a chain
  2628. * of multiple DataViews, the root DataSet of this chain is returned.
  2629. * @return {DataSet} dataSet
  2630. */
  2631. DataView.prototype.getDataSet = function () {
  2632. var dataSet = this;
  2633. while (dataSet instanceof DataView) {
  2634. dataSet = dataSet._data;
  2635. }
  2636. return dataSet || null;
  2637. };
  2638. /**
  2639. * Event listener. Will propagate all events from the connected data set to
  2640. * the subscribers of the DataView, but will filter the items and only trigger
  2641. * when there are changes in the filtered data set.
  2642. * @param {String} event
  2643. * @param {Object | null} params
  2644. * @param {String} senderId
  2645. * @private
  2646. */
  2647. DataView.prototype._onEvent = function (event, params, senderId) {
  2648. var i, len, id, item;
  2649. var ids = params && params.items;
  2650. var data = this._data;
  2651. var updatedData = [];
  2652. var added = [];
  2653. var updated = [];
  2654. var removed = [];
  2655. if (ids && data) {
  2656. switch (event) {
  2657. case 'add':
  2658. // filter the ids of the added items
  2659. for (i = 0, len = ids.length; i < len; i++) {
  2660. id = ids[i];
  2661. item = this.get(id);
  2662. if (item) {
  2663. this._ids[id] = true;
  2664. added.push(id);
  2665. }
  2666. }
  2667. break;
  2668. case 'update':
  2669. // determine the event from the views viewpoint: an updated
  2670. // item can be added, updated, or removed from this view.
  2671. for (i = 0, len = ids.length; i < len; i++) {
  2672. id = ids[i];
  2673. item = this.get(id);
  2674. if (item) {
  2675. if (this._ids[id]) {
  2676. updated.push(id);
  2677. updatedData.push(params.data[i]);
  2678. }
  2679. else {
  2680. this._ids[id] = true;
  2681. added.push(id);
  2682. }
  2683. }
  2684. else {
  2685. if (this._ids[id]) {
  2686. delete this._ids[id];
  2687. removed.push(id);
  2688. }
  2689. else {
  2690. // nothing interesting for me :-(
  2691. }
  2692. }
  2693. }
  2694. break;
  2695. case 'remove':
  2696. // filter the ids of the removed items
  2697. for (i = 0, len = ids.length; i < len; i++) {
  2698. id = ids[i];
  2699. if (this._ids[id]) {
  2700. delete this._ids[id];
  2701. removed.push(id);
  2702. }
  2703. }
  2704. break;
  2705. }
  2706. this.length += added.length - removed.length;
  2707. if (added.length) {
  2708. this._trigger('add', {items: added}, senderId);
  2709. }
  2710. if (updated.length) {
  2711. this._trigger('update', {items: updated, data: updatedData}, senderId);
  2712. }
  2713. if (removed.length) {
  2714. this._trigger('remove', {items: removed}, senderId);
  2715. }
  2716. }
  2717. };
  2718. // copy subscription functionality from DataSet
  2719. DataView.prototype.on = DataSet.prototype.on;
  2720. DataView.prototype.off = DataSet.prototype.off;
  2721. DataView.prototype._trigger = DataSet.prototype._trigger;
  2722. // TODO: make these functions deprecated (replaced with `on` and `off` since version 0.5)
  2723. DataView.prototype.subscribe = DataView.prototype.on;
  2724. DataView.prototype.unsubscribe = DataView.prototype.off;
  2725. module.exports = DataView;
  2726. /***/ },
  2727. /* 5 */
  2728. /***/ function(module, exports, __webpack_require__) {
  2729. /**
  2730. * A queue
  2731. * @param {Object} options
  2732. * Available options:
  2733. * - delay: number When provided, the queue will be flushed
  2734. * automatically after an inactivity of this delay
  2735. * in milliseconds.
  2736. * Default value is null.
  2737. * - max: number When the queue exceeds the given maximum number
  2738. * of entries, the queue is flushed automatically.
  2739. * Default value of max is Infinity.
  2740. * @constructor
  2741. */
  2742. function Queue(options) {
  2743. // options
  2744. this.delay = null;
  2745. this.max = Infinity;
  2746. // properties
  2747. this._queue = [];
  2748. this._timeout = null;
  2749. this._extended = null;
  2750. this.setOptions(options);
  2751. }
  2752. /**
  2753. * Update the configuration of the queue
  2754. * @param {Object} options
  2755. * Available options:
  2756. * - delay: number When provided, the queue will be flushed
  2757. * automatically after an inactivity of this delay
  2758. * in milliseconds.
  2759. * Default value is null.
  2760. * - max: number When the queue exceeds the given maximum number
  2761. * of entries, the queue is flushed automatically.
  2762. * Default value of max is Infinity.
  2763. * @param options
  2764. */
  2765. Queue.prototype.setOptions = function (options) {
  2766. if (options && typeof options.delay !== 'undefined') {
  2767. this.delay = options.delay;
  2768. }
  2769. if (options && typeof options.max !== 'undefined') {
  2770. this.max = options.max;
  2771. }
  2772. this._flushIfNeeded();
  2773. };
  2774. /**
  2775. * Extend an object with queuing functionality.
  2776. * The object will be extended with a function flush, and the methods provided
  2777. * in options.replace will be replaced with queued ones.
  2778. * @param {Object} object
  2779. * @param {Object} options
  2780. * Available options:
  2781. * - replace: Array.<string>
  2782. * A list with method names of the methods
  2783. * on the object to be replaced with queued ones.
  2784. * - delay: number When provided, the queue will be flushed
  2785. * automatically after an inactivity of this delay
  2786. * in milliseconds.
  2787. * Default value is null.
  2788. * - max: number When the queue exceeds the given maximum number
  2789. * of entries, the queue is flushed automatically.
  2790. * Default value of max is Infinity.
  2791. * @return {Queue} Returns the created queue
  2792. */
  2793. Queue.extend = function (object, options) {
  2794. var queue = new Queue(options);
  2795. if (object.flush !== undefined) {
  2796. throw new Error('Target object already has a property flush');
  2797. }
  2798. object.flush = function () {
  2799. queue.flush();
  2800. };
  2801. var methods = [{
  2802. name: 'flush',
  2803. original: undefined
  2804. }];
  2805. if (options && options.replace) {
  2806. for (var i = 0; i < options.replace.length; i++) {
  2807. var name = options.replace[i];
  2808. methods.push({
  2809. name: name,
  2810. original: object[name]
  2811. });
  2812. queue.replace(object, name);
  2813. }
  2814. }
  2815. queue._extended = {
  2816. object: object,
  2817. methods: methods
  2818. };
  2819. return queue;
  2820. };
  2821. /**
  2822. * Destroy the queue. The queue will first flush all queued actions, and in
  2823. * case it has extended an object, will restore the original object.
  2824. */
  2825. Queue.prototype.destroy = function () {
  2826. this.flush();
  2827. if (this._extended) {
  2828. var object = this._extended.object;
  2829. var methods = this._extended.methods;
  2830. for (var i = 0; i < methods.length; i++) {
  2831. var method = methods[i];
  2832. if (method.original) {
  2833. object[method.name] = method.original;
  2834. }
  2835. else {
  2836. delete object[method.name];
  2837. }
  2838. }
  2839. this._extended = null;
  2840. }
  2841. };
  2842. /**
  2843. * Replace a method on an object with a queued version
  2844. * @param {Object} object Object having the method
  2845. * @param {string} method The method name
  2846. */
  2847. Queue.prototype.replace = function(object, method) {
  2848. var me = this;
  2849. var original = object[method];
  2850. if (!original) {
  2851. throw new Error('Method ' + method + ' undefined');
  2852. }
  2853. object[method] = function () {
  2854. // create an Array with the arguments
  2855. var args = [];
  2856. for (var i = 0; i < arguments.length; i++) {
  2857. args[i] = arguments[i];
  2858. }
  2859. // add this call to the queue
  2860. me.queue({
  2861. args: args,
  2862. fn: original,
  2863. context: this
  2864. });
  2865. };
  2866. };
  2867. /**
  2868. * Queue a call
  2869. * @param {function | {fn: function, args: Array} | {fn: function, args: Array, context: Object}} entry
  2870. */
  2871. Queue.prototype.queue = function(entry) {
  2872. if (typeof entry === 'function') {
  2873. this._queue.push({fn: entry});
  2874. }
  2875. else {
  2876. this._queue.push(entry);
  2877. }
  2878. this._flushIfNeeded();
  2879. };
  2880. /**
  2881. * Check whether the queue needs to be flushed
  2882. * @private
  2883. */
  2884. Queue.prototype._flushIfNeeded = function () {
  2885. // flush when the maximum is exceeded.
  2886. if (this._queue.length > this.max) {
  2887. this.flush();
  2888. }
  2889. // flush after a period of inactivity when a delay is configured
  2890. clearTimeout(this._timeout);
  2891. if (this.queue.length > 0 && typeof this.delay === 'number') {
  2892. var me = this;
  2893. this._timeout = setTimeout(function () {
  2894. me.flush();
  2895. }, this.delay);
  2896. }
  2897. };
  2898. /**
  2899. * Flush all queued calls
  2900. */
  2901. Queue.prototype.flush = function () {
  2902. while (this._queue.length > 0) {
  2903. var entry = this._queue.shift();
  2904. entry.fn.apply(entry.context || entry.fn, entry.args || []);
  2905. }
  2906. };
  2907. module.exports = Queue;
  2908. /***/ },
  2909. /* 6 */
  2910. /***/ function(module, exports, __webpack_require__) {
  2911. var Emitter = __webpack_require__(56);
  2912. var DataSet = __webpack_require__(3);
  2913. var DataView = __webpack_require__(4);
  2914. var util = __webpack_require__(1);
  2915. var Point3d = __webpack_require__(10);
  2916. var Point2d = __webpack_require__(9);
  2917. var Camera = __webpack_require__(7);
  2918. var Filter = __webpack_require__(8);
  2919. var Slider = __webpack_require__(11);
  2920. var StepNumber = __webpack_require__(12);
  2921. /**
  2922. * @constructor Graph3d
  2923. * Graph3d displays data in 3d.
  2924. *
  2925. * Graph3d is developed in javascript as a Google Visualization Chart.
  2926. *
  2927. * @param {Element} container The DOM element in which the Graph3d will
  2928. * be created. Normally a div element.
  2929. * @param {DataSet | DataView | Array} [data]
  2930. * @param {Object} [options]
  2931. */
  2932. function Graph3d(container, data, options) {
  2933. if (!(this instanceof Graph3d)) {
  2934. throw new SyntaxError('Constructor must be called with the new operator');
  2935. }
  2936. // create variables and set default values
  2937. this.containerElement = container;
  2938. this.width = '400px';
  2939. this.height = '400px';
  2940. this.margin = 10; // px
  2941. this.defaultXCenter = '55%';
  2942. this.defaultYCenter = '50%';
  2943. this.xLabel = 'x';
  2944. this.yLabel = 'y';
  2945. this.zLabel = 'z';
  2946. var passValueFn = function(v) { return v; };
  2947. this.xValueLabel = passValueFn;
  2948. this.yValueLabel = passValueFn;
  2949. this.zValueLabel = passValueFn;
  2950. this.filterLabel = 'time';
  2951. this.legendLabel = 'value';
  2952. this.style = Graph3d.STYLE.DOT;
  2953. this.showPerspective = true;
  2954. this.showGrid = true;
  2955. this.keepAspectRatio = true;
  2956. this.showShadow = false;
  2957. this.showGrayBottom = false; // TODO: this does not work correctly
  2958. this.showTooltip = false;
  2959. this.verticalRatio = 0.5; // 0.1 to 1.0, where 1.0 results in a 'cube'
  2960. this.animationInterval = 1000; // milliseconds
  2961. this.animationPreload = false;
  2962. this.camera = new Camera();
  2963. this.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?
  2964. this.dataTable = null; // The original data table
  2965. this.dataPoints = null; // The table with point objects
  2966. // the column indexes
  2967. this.colX = undefined;
  2968. this.colY = undefined;
  2969. this.colZ = undefined;
  2970. this.colValue = undefined;
  2971. this.colFilter = undefined;
  2972. this.xMin = 0;
  2973. this.xStep = undefined; // auto by default
  2974. this.xMax = 1;
  2975. this.yMin = 0;
  2976. this.yStep = undefined; // auto by default
  2977. this.yMax = 1;
  2978. this.zMin = 0;
  2979. this.zStep = undefined; // auto by default
  2980. this.zMax = 1;
  2981. this.valueMin = 0;
  2982. this.valueMax = 1;
  2983. this.xBarWidth = 1;
  2984. this.yBarWidth = 1;
  2985. // TODO: customize axis range
  2986. // constants
  2987. this.colorAxis = '#4D4D4D';
  2988. this.colorGrid = '#D3D3D3';
  2989. this.colorDot = '#7DC1FF';
  2990. this.colorDotBorder = '#3267D2';
  2991. // create a frame and canvas
  2992. this.create();
  2993. // apply options (also when undefined)
  2994. this.setOptions(options);
  2995. // apply data
  2996. if (data) {
  2997. this.setData(data);
  2998. }
  2999. }
  3000. // Extend Graph3d with an Emitter mixin
  3001. Emitter(Graph3d.prototype);
  3002. /**
  3003. * Calculate the scaling values, dependent on the range in x, y, and z direction
  3004. */
  3005. Graph3d.prototype._setScale = function() {
  3006. this.scale = new Point3d(1 / (this.xMax - this.xMin),
  3007. 1 / (this.yMax - this.yMin),
  3008. 1 / (this.zMax - this.zMin));
  3009. // keep aspect ration between x and y scale if desired
  3010. if (this.keepAspectRatio) {
  3011. if (this.scale.x < this.scale.y) {
  3012. //noinspection JSSuspiciousNameCombination
  3013. this.scale.y = this.scale.x;
  3014. }
  3015. else {
  3016. //noinspection JSSuspiciousNameCombination
  3017. this.scale.x = this.scale.y;
  3018. }
  3019. }
  3020. // scale the vertical axis
  3021. this.scale.z *= this.verticalRatio;
  3022. // TODO: can this be automated? verticalRatio?
  3023. // determine scale for (optional) value
  3024. this.scale.value = 1 / (this.valueMax - this.valueMin);
  3025. // position the camera arm
  3026. var xCenter = (this.xMax + this.xMin) / 2 * this.scale.x;
  3027. var yCenter = (this.yMax + this.yMin) / 2 * this.scale.y;
  3028. var zCenter = (this.zMax + this.zMin) / 2 * this.scale.z;
  3029. this.camera.setArmLocation(xCenter, yCenter, zCenter);
  3030. };
  3031. /**
  3032. * Convert a 3D location to a 2D location on screen
  3033. * http://en.wikipedia.org/wiki/3D_projection
  3034. * @param {Point3d} point3d A 3D point with parameters x, y, z
  3035. * @return {Point2d} point2d A 2D point with parameters x, y
  3036. */
  3037. Graph3d.prototype._convert3Dto2D = function(point3d) {
  3038. var translation = this._convertPointToTranslation(point3d);
  3039. return this._convertTranslationToScreen(translation);
  3040. };
  3041. /**
  3042. * Convert a 3D location its translation seen from the camera
  3043. * http://en.wikipedia.org/wiki/3D_projection
  3044. * @param {Point3d} point3d A 3D point with parameters x, y, z
  3045. * @return {Point3d} translation A 3D point with parameters x, y, z This is
  3046. * the translation of the point, seen from the
  3047. * camera
  3048. */
  3049. Graph3d.prototype._convertPointToTranslation = function(point3d) {
  3050. var ax = point3d.x * this.scale.x,
  3051. ay = point3d.y * this.scale.y,
  3052. az = point3d.z * this.scale.z,
  3053. cx = this.camera.getCameraLocation().x,
  3054. cy = this.camera.getCameraLocation().y,
  3055. cz = this.camera.getCameraLocation().z,
  3056. // calculate angles
  3057. sinTx = Math.sin(this.camera.getCameraRotation().x),
  3058. cosTx = Math.cos(this.camera.getCameraRotation().x),
  3059. sinTy = Math.sin(this.camera.getCameraRotation().y),
  3060. cosTy = Math.cos(this.camera.getCameraRotation().y),
  3061. sinTz = Math.sin(this.camera.getCameraRotation().z),
  3062. cosTz = Math.cos(this.camera.getCameraRotation().z),
  3063. // calculate translation
  3064. dx = cosTy * (sinTz * (ay - cy) + cosTz * (ax - cx)) - sinTy * (az - cz),
  3065. dy = sinTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) + cosTx * (cosTz * (ay - cy) - sinTz * (ax-cx)),
  3066. dz = cosTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) - sinTx * (cosTz * (ay - cy) - sinTz * (ax-cx));
  3067. return new Point3d(dx, dy, dz);
  3068. };
  3069. /**
  3070. * Convert a translation point to a point on the screen
  3071. * @param {Point3d} translation A 3D point with parameters x, y, z This is
  3072. * the translation of the point, seen from the
  3073. * camera
  3074. * @return {Point2d} point2d A 2D point with parameters x, y
  3075. */
  3076. Graph3d.prototype._convertTranslationToScreen = function(translation) {
  3077. var ex = this.eye.x,
  3078. ey = this.eye.y,
  3079. ez = this.eye.z,
  3080. dx = translation.x,
  3081. dy = translation.y,
  3082. dz = translation.z;
  3083. // calculate position on screen from translation
  3084. var bx;
  3085. var by;
  3086. if (this.showPerspective) {
  3087. bx = (dx - ex) * (ez / dz);
  3088. by = (dy - ey) * (ez / dz);
  3089. }
  3090. else {
  3091. bx = dx * -(ez / this.camera.getArmLength());
  3092. by = dy * -(ez / this.camera.getArmLength());
  3093. }
  3094. // shift and scale the point to the center of the screen
  3095. // use the width of the graph to scale both horizontally and vertically.
  3096. return new Point2d(
  3097. this.xcenter + bx * this.frame.canvas.clientWidth,
  3098. this.ycenter - by * this.frame.canvas.clientWidth);
  3099. };
  3100. /**
  3101. * Set the background styling for the graph
  3102. * @param {string | {fill: string, stroke: string, strokeWidth: string}} backgroundColor
  3103. */
  3104. Graph3d.prototype._setBackgroundColor = function(backgroundColor) {
  3105. var fill = 'white';
  3106. var stroke = 'gray';
  3107. var strokeWidth = 1;
  3108. if (typeof(backgroundColor) === 'string') {
  3109. fill = backgroundColor;
  3110. stroke = 'none';
  3111. strokeWidth = 0;
  3112. }
  3113. else if (typeof(backgroundColor) === 'object') {
  3114. if (backgroundColor.fill !== undefined) fill = backgroundColor.fill;
  3115. if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke;
  3116. if (backgroundColor.strokeWidth !== undefined) strokeWidth = backgroundColor.strokeWidth;
  3117. }
  3118. else if (backgroundColor === undefined) {
  3119. // use use defaults
  3120. }
  3121. else {
  3122. throw 'Unsupported type of backgroundColor';
  3123. }
  3124. this.frame.style.backgroundColor = fill;
  3125. this.frame.style.borderColor = stroke;
  3126. this.frame.style.borderWidth = strokeWidth + 'px';
  3127. this.frame.style.borderStyle = 'solid';
  3128. };
  3129. /// enumerate the available styles
  3130. Graph3d.STYLE = {
  3131. BAR: 0,
  3132. BARCOLOR: 1,
  3133. BARSIZE: 2,
  3134. DOT : 3,
  3135. DOTLINE : 4,
  3136. DOTCOLOR: 5,
  3137. DOTSIZE: 6,
  3138. GRID : 7,
  3139. LINE: 8,
  3140. SURFACE : 9
  3141. };
  3142. /**
  3143. * Retrieve the style index from given styleName
  3144. * @param {string} styleName Style name such as 'dot', 'grid', 'dot-line'
  3145. * @return {Number} styleNumber Enumeration value representing the style, or -1
  3146. * when not found
  3147. */
  3148. Graph3d.prototype._getStyleNumber = function(styleName) {
  3149. switch (styleName) {
  3150. case 'dot': return Graph3d.STYLE.DOT;
  3151. case 'dot-line': return Graph3d.STYLE.DOTLINE;
  3152. case 'dot-color': return Graph3d.STYLE.DOTCOLOR;
  3153. case 'dot-size': return Graph3d.STYLE.DOTSIZE;
  3154. case 'line': return Graph3d.STYLE.LINE;
  3155. case 'grid': return Graph3d.STYLE.GRID;
  3156. case 'surface': return Graph3d.STYLE.SURFACE;
  3157. case 'bar': return Graph3d.STYLE.BAR;
  3158. case 'bar-color': return Graph3d.STYLE.BARCOLOR;
  3159. case 'bar-size': return Graph3d.STYLE.BARSIZE;
  3160. }
  3161. return -1;
  3162. };
  3163. /**
  3164. * Determine the indexes of the data columns, based on the given style and data
  3165. * @param {DataSet} data
  3166. * @param {Number} style
  3167. */
  3168. Graph3d.prototype._determineColumnIndexes = function(data, style) {
  3169. if (this.style === Graph3d.STYLE.DOT ||
  3170. this.style === Graph3d.STYLE.DOTLINE ||
  3171. this.style === Graph3d.STYLE.LINE ||
  3172. this.style === Graph3d.STYLE.GRID ||
  3173. this.style === Graph3d.STYLE.SURFACE ||
  3174. this.style === Graph3d.STYLE.BAR) {
  3175. // 3 columns expected, and optionally a 4th with filter values
  3176. this.colX = 0;
  3177. this.colY = 1;
  3178. this.colZ = 2;
  3179. this.colValue = undefined;
  3180. if (data.getNumberOfColumns() > 3) {
  3181. this.colFilter = 3;
  3182. }
  3183. }
  3184. else if (this.style === Graph3d.STYLE.DOTCOLOR ||
  3185. this.style === Graph3d.STYLE.DOTSIZE ||
  3186. this.style === Graph3d.STYLE.BARCOLOR ||
  3187. this.style === Graph3d.STYLE.BARSIZE) {
  3188. // 4 columns expected, and optionally a 5th with filter values
  3189. this.colX = 0;
  3190. this.colY = 1;
  3191. this.colZ = 2;
  3192. this.colValue = 3;
  3193. if (data.getNumberOfColumns() > 4) {
  3194. this.colFilter = 4;
  3195. }
  3196. }
  3197. else {
  3198. throw 'Unknown style "' + this.style + '"';
  3199. }
  3200. };
  3201. Graph3d.prototype.getNumberOfRows = function(data) {
  3202. return data.length;
  3203. }
  3204. Graph3d.prototype.getNumberOfColumns = function(data) {
  3205. var counter = 0;
  3206. for (var column in data[0]) {
  3207. if (data[0].hasOwnProperty(column)) {
  3208. counter++;
  3209. }
  3210. }
  3211. return counter;
  3212. }
  3213. Graph3d.prototype.getDistinctValues = function(data, column) {
  3214. var distinctValues = [];
  3215. for (var i = 0; i < data.length; i++) {
  3216. if (distinctValues.indexOf(data[i][column]) == -1) {
  3217. distinctValues.push(data[i][column]);
  3218. }
  3219. }
  3220. return distinctValues;
  3221. }
  3222. Graph3d.prototype.getColumnRange = function(data,column) {
  3223. var minMax = {min:data[0][column],max:data[0][column]};
  3224. for (var i = 0; i < data.length; i++) {
  3225. if (minMax.min > data[i][column]) { minMax.min = data[i][column]; }
  3226. if (minMax.max < data[i][column]) { minMax.max = data[i][column]; }
  3227. }
  3228. return minMax;
  3229. };
  3230. /**
  3231. * Initialize the data from the data table. Calculate minimum and maximum values
  3232. * and column index values
  3233. * @param {Array | DataSet | DataView} rawData The data containing the items for the Graph.
  3234. * @param {Number} style Style Number
  3235. */
  3236. Graph3d.prototype._dataInitialize = function (rawData, style) {
  3237. var me = this;
  3238. // unsubscribe from the dataTable
  3239. if (this.dataSet) {
  3240. this.dataSet.off('*', this._onChange);
  3241. }
  3242. if (rawData === undefined)
  3243. return;
  3244. if (Array.isArray(rawData)) {
  3245. rawData = new DataSet(rawData);
  3246. }
  3247. var data;
  3248. if (rawData instanceof DataSet || rawData instanceof DataView) {
  3249. data = rawData.get();
  3250. }
  3251. else {
  3252. throw new Error('Array, DataSet, or DataView expected');
  3253. }
  3254. if (data.length == 0)
  3255. return;
  3256. this.dataSet = rawData;
  3257. this.dataTable = data;
  3258. // subscribe to changes in the dataset
  3259. this._onChange = function () {
  3260. me.setData(me.dataSet);
  3261. };
  3262. this.dataSet.on('*', this._onChange);
  3263. // _determineColumnIndexes
  3264. // getNumberOfRows (points)
  3265. // getNumberOfColumns (x,y,z,v,t,t1,t2...)
  3266. // getDistinctValues (unique values?)
  3267. // getColumnRange
  3268. // determine the location of x,y,z,value,filter columns
  3269. this.colX = 'x';
  3270. this.colY = 'y';
  3271. this.colZ = 'z';
  3272. this.colValue = 'style';
  3273. this.colFilter = 'filter';
  3274. // check if a filter column is provided
  3275. if (data[0].hasOwnProperty('filter')) {
  3276. if (this.dataFilter === undefined) {
  3277. this.dataFilter = new Filter(rawData, this.colFilter, this);
  3278. this.dataFilter.setOnLoadCallback(function() {me.redraw();});
  3279. }
  3280. }
  3281. var withBars = this.style == Graph3d.STYLE.BAR ||
  3282. this.style == Graph3d.STYLE.BARCOLOR ||
  3283. this.style == Graph3d.STYLE.BARSIZE;
  3284. // determine barWidth from data
  3285. if (withBars) {
  3286. if (this.defaultXBarWidth !== undefined) {
  3287. this.xBarWidth = this.defaultXBarWidth;
  3288. }
  3289. else {
  3290. var dataX = this.getDistinctValues(data,this.colX);
  3291. this.xBarWidth = (dataX[1] - dataX[0]) || 1;
  3292. }
  3293. if (this.defaultYBarWidth !== undefined) {
  3294. this.yBarWidth = this.defaultYBarWidth;
  3295. }
  3296. else {
  3297. var dataY = this.getDistinctValues(data,this.colY);
  3298. this.yBarWidth = (dataY[1] - dataY[0]) || 1;
  3299. }
  3300. }
  3301. // calculate minimums and maximums
  3302. var xRange = this.getColumnRange(data,this.colX);
  3303. if (withBars) {
  3304. xRange.min -= this.xBarWidth / 2;
  3305. xRange.max += this.xBarWidth / 2;
  3306. }
  3307. this.xMin = (this.defaultXMin !== undefined) ? this.defaultXMin : xRange.min;
  3308. this.xMax = (this.defaultXMax !== undefined) ? this.defaultXMax : xRange.max;
  3309. if (this.xMax <= this.xMin) this.xMax = this.xMin + 1;
  3310. this.xStep = (this.defaultXStep !== undefined) ? this.defaultXStep : (this.xMax-this.xMin)/5;
  3311. var yRange = this.getColumnRange(data,this.colY);
  3312. if (withBars) {
  3313. yRange.min -= this.yBarWidth / 2;
  3314. yRange.max += this.yBarWidth / 2;
  3315. }
  3316. this.yMin = (this.defaultYMin !== undefined) ? this.defaultYMin : yRange.min;
  3317. this.yMax = (this.defaultYMax !== undefined) ? this.defaultYMax : yRange.max;
  3318. if (this.yMax <= this.yMin) this.yMax = this.yMin + 1;
  3319. this.yStep = (this.defaultYStep !== undefined) ? this.defaultYStep : (this.yMax-this.yMin)/5;
  3320. var zRange = this.getColumnRange(data,this.colZ);
  3321. this.zMin = (this.defaultZMin !== undefined) ? this.defaultZMin : zRange.min;
  3322. this.zMax = (this.defaultZMax !== undefined) ? this.defaultZMax : zRange.max;
  3323. if (this.zMax <= this.zMin) this.zMax = this.zMin + 1;
  3324. this.zStep = (this.defaultZStep !== undefined) ? this.defaultZStep : (this.zMax-this.zMin)/5;
  3325. if (this.colValue !== undefined) {
  3326. var valueRange = this.getColumnRange(data,this.colValue);
  3327. this.valueMin = (this.defaultValueMin !== undefined) ? this.defaultValueMin : valueRange.min;
  3328. this.valueMax = (this.defaultValueMax !== undefined) ? this.defaultValueMax : valueRange.max;
  3329. if (this.valueMax <= this.valueMin) this.valueMax = this.valueMin + 1;
  3330. }
  3331. // set the scale dependent on the ranges.
  3332. this._setScale();
  3333. };
  3334. /**
  3335. * Filter the data based on the current filter
  3336. * @param {Array} data
  3337. * @return {Array} dataPoints Array with point objects which can be drawn on screen
  3338. */
  3339. Graph3d.prototype._getDataPoints = function (data) {
  3340. // TODO: store the created matrix dataPoints in the filters instead of reloading each time
  3341. var x, y, i, z, obj, point;
  3342. var dataPoints = [];
  3343. if (this.style === Graph3d.STYLE.GRID ||
  3344. this.style === Graph3d.STYLE.SURFACE) {
  3345. // copy all values from the google data table to a matrix
  3346. // the provided values are supposed to form a grid of (x,y) positions
  3347. // create two lists with all present x and y values
  3348. var dataX = [];
  3349. var dataY = [];
  3350. for (i = 0; i < this.getNumberOfRows(data); i++) {
  3351. x = data[i][this.colX] || 0;
  3352. y = data[i][this.colY] || 0;
  3353. if (dataX.indexOf(x) === -1) {
  3354. dataX.push(x);
  3355. }
  3356. if (dataY.indexOf(y) === -1) {
  3357. dataY.push(y);
  3358. }
  3359. }
  3360. var sortNumber = function (a, b) {
  3361. return a - b;
  3362. };
  3363. dataX.sort(sortNumber);
  3364. dataY.sort(sortNumber);
  3365. // create a grid, a 2d matrix, with all values.
  3366. var dataMatrix = []; // temporary data matrix
  3367. for (i = 0; i < data.length; i++) {
  3368. x = data[i][this.colX] || 0;
  3369. y = data[i][this.colY] || 0;
  3370. z = data[i][this.colZ] || 0;
  3371. var xIndex = dataX.indexOf(x); // TODO: implement Array().indexOf() for Internet Explorer
  3372. var yIndex = dataY.indexOf(y);
  3373. if (dataMatrix[xIndex] === undefined) {
  3374. dataMatrix[xIndex] = [];
  3375. }
  3376. var point3d = new Point3d();
  3377. point3d.x = x;
  3378. point3d.y = y;
  3379. point3d.z = z;
  3380. obj = {};
  3381. obj.point = point3d;
  3382. obj.trans = undefined;
  3383. obj.screen = undefined;
  3384. obj.bottom = new Point3d(x, y, this.zMin);
  3385. dataMatrix[xIndex][yIndex] = obj;
  3386. dataPoints.push(obj);
  3387. }
  3388. // fill in the pointers to the neighbors.
  3389. for (x = 0; x < dataMatrix.length; x++) {
  3390. for (y = 0; y < dataMatrix[x].length; y++) {
  3391. if (dataMatrix[x][y]) {
  3392. dataMatrix[x][y].pointRight = (x < dataMatrix.length-1) ? dataMatrix[x+1][y] : undefined;
  3393. dataMatrix[x][y].pointTop = (y < dataMatrix[x].length-1) ? dataMatrix[x][y+1] : undefined;
  3394. dataMatrix[x][y].pointCross =
  3395. (x < dataMatrix.length-1 && y < dataMatrix[x].length-1) ?
  3396. dataMatrix[x+1][y+1] :
  3397. undefined;
  3398. }
  3399. }
  3400. }
  3401. }
  3402. else { // 'dot', 'dot-line', etc.
  3403. // copy all values from the google data table to a list with Point3d objects
  3404. for (i = 0; i < data.length; i++) {
  3405. point = new Point3d();
  3406. point.x = data[i][this.colX] || 0;
  3407. point.y = data[i][this.colY] || 0;
  3408. point.z = data[i][this.colZ] || 0;
  3409. if (this.colValue !== undefined) {
  3410. point.value = data[i][this.colValue] || 0;
  3411. }
  3412. obj = {};
  3413. obj.point = point;
  3414. obj.bottom = new Point3d(point.x, point.y, this.zMin);
  3415. obj.trans = undefined;
  3416. obj.screen = undefined;
  3417. dataPoints.push(obj);
  3418. }
  3419. }
  3420. return dataPoints;
  3421. };
  3422. /**
  3423. * Create the main frame for the Graph3d.
  3424. * This function is executed once when a Graph3d object is created. The frame
  3425. * contains a canvas, and this canvas contains all objects like the axis and
  3426. * nodes.
  3427. */
  3428. Graph3d.prototype.create = function () {
  3429. // remove all elements from the container element.
  3430. while (this.containerElement.hasChildNodes()) {
  3431. this.containerElement.removeChild(this.containerElement.firstChild);
  3432. }
  3433. this.frame = document.createElement('div');
  3434. this.frame.style.position = 'relative';
  3435. this.frame.style.overflow = 'hidden';
  3436. // create the graph canvas (HTML canvas element)
  3437. this.frame.canvas = document.createElement( 'canvas' );
  3438. this.frame.canvas.style.position = 'relative';
  3439. this.frame.appendChild(this.frame.canvas);
  3440. //if (!this.frame.canvas.getContext) {
  3441. {
  3442. var noCanvas = document.createElement( 'DIV' );
  3443. noCanvas.style.color = 'red';
  3444. noCanvas.style.fontWeight = 'bold' ;
  3445. noCanvas.style.padding = '10px';
  3446. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  3447. this.frame.canvas.appendChild(noCanvas);
  3448. }
  3449. this.frame.filter = document.createElement( 'div' );
  3450. this.frame.filter.style.position = 'absolute';
  3451. this.frame.filter.style.bottom = '0px';
  3452. this.frame.filter.style.left = '0px';
  3453. this.frame.filter.style.width = '100%';
  3454. this.frame.appendChild(this.frame.filter);
  3455. // add event listeners to handle moving and zooming the contents
  3456. var me = this;
  3457. var onmousedown = function (event) {me._onMouseDown(event);};
  3458. var ontouchstart = function (event) {me._onTouchStart(event);};
  3459. var onmousewheel = function (event) {me._onWheel(event);};
  3460. var ontooltip = function (event) {me._onTooltip(event);};
  3461. // TODO: these events are never cleaned up... can give a 'memory leakage'
  3462. util.addEventListener(this.frame.canvas, 'keydown', onkeydown);
  3463. util.addEventListener(this.frame.canvas, 'mousedown', onmousedown);
  3464. util.addEventListener(this.frame.canvas, 'touchstart', ontouchstart);
  3465. util.addEventListener(this.frame.canvas, 'mousewheel', onmousewheel);
  3466. util.addEventListener(this.frame.canvas, 'mousemove', ontooltip);
  3467. // add the new graph to the container element
  3468. this.containerElement.appendChild(this.frame);
  3469. };
  3470. /**
  3471. * Set a new size for the graph
  3472. * @param {string} width Width in pixels or percentage (for example '800px'
  3473. * or '50%')
  3474. * @param {string} height Height in pixels or percentage (for example '400px'
  3475. * or '30%')
  3476. */
  3477. Graph3d.prototype.setSize = function(width, height) {
  3478. this.frame.style.width = width;
  3479. this.frame.style.height = height;
  3480. this._resizeCanvas();
  3481. };
  3482. /**
  3483. * Resize the canvas to the current size of the frame
  3484. */
  3485. Graph3d.prototype._resizeCanvas = function() {
  3486. this.frame.canvas.style.width = '100%';
  3487. this.frame.canvas.style.height = '100%';
  3488. this.frame.canvas.width = this.frame.canvas.clientWidth;
  3489. this.frame.canvas.height = this.frame.canvas.clientHeight;
  3490. // adjust with for margin
  3491. this.frame.filter.style.width = (this.frame.canvas.clientWidth - 2 * 10) + 'px';
  3492. };
  3493. /**
  3494. * Start animation
  3495. */
  3496. Graph3d.prototype.animationStart = function() {
  3497. if (!this.frame.filter || !this.frame.filter.slider)
  3498. throw 'No animation available';
  3499. this.frame.filter.slider.play();
  3500. };
  3501. /**
  3502. * Stop animation
  3503. */
  3504. Graph3d.prototype.animationStop = function() {
  3505. if (!this.frame.filter || !this.frame.filter.slider) return;
  3506. this.frame.filter.slider.stop();
  3507. };
  3508. /**
  3509. * Resize the center position based on the current values in this.defaultXCenter
  3510. * and this.defaultYCenter (which are strings with a percentage or a value
  3511. * in pixels). The center positions are the variables this.xCenter
  3512. * and this.yCenter
  3513. */
  3514. Graph3d.prototype._resizeCenter = function() {
  3515. // calculate the horizontal center position
  3516. if (this.defaultXCenter.charAt(this.defaultXCenter.length-1) === '%') {
  3517. this.xcenter =
  3518. parseFloat(this.defaultXCenter) / 100 *
  3519. this.frame.canvas.clientWidth;
  3520. }
  3521. else {
  3522. this.xcenter = parseFloat(this.defaultXCenter); // supposed to be in px
  3523. }
  3524. // calculate the vertical center position
  3525. if (this.defaultYCenter.charAt(this.defaultYCenter.length-1) === '%') {
  3526. this.ycenter =
  3527. parseFloat(this.defaultYCenter) / 100 *
  3528. (this.frame.canvas.clientHeight - this.frame.filter.clientHeight);
  3529. }
  3530. else {
  3531. this.ycenter = parseFloat(this.defaultYCenter); // supposed to be in px
  3532. }
  3533. };
  3534. /**
  3535. * Set the rotation and distance of the camera
  3536. * @param {Object} pos An object with the camera position. The object
  3537. * contains three parameters:
  3538. * - horizontal {Number}
  3539. * The horizontal rotation, between 0 and 2*PI.
  3540. * Optional, can be left undefined.
  3541. * - vertical {Number}
  3542. * The vertical rotation, between 0 and 0.5*PI
  3543. * if vertical=0.5*PI, the graph is shown from the
  3544. * top. Optional, can be left undefined.
  3545. * - distance {Number}
  3546. * The (normalized) distance of the camera to the
  3547. * center of the graph, a value between 0.71 and 5.0.
  3548. * Optional, can be left undefined.
  3549. */
  3550. Graph3d.prototype.setCameraPosition = function(pos) {
  3551. if (pos === undefined) {
  3552. return;
  3553. }
  3554. if (pos.horizontal !== undefined && pos.vertical !== undefined) {
  3555. this.camera.setArmRotation(pos.horizontal, pos.vertical);
  3556. }
  3557. if (pos.distance !== undefined) {
  3558. this.camera.setArmLength(pos.distance);
  3559. }
  3560. this.redraw();
  3561. };
  3562. /**
  3563. * Retrieve the current camera rotation
  3564. * @return {object} An object with parameters horizontal, vertical, and
  3565. * distance
  3566. */
  3567. Graph3d.prototype.getCameraPosition = function() {
  3568. var pos = this.camera.getArmRotation();
  3569. pos.distance = this.camera.getArmLength();
  3570. return pos;
  3571. };
  3572. /**
  3573. * Load data into the 3D Graph
  3574. */
  3575. Graph3d.prototype._readData = function(data) {
  3576. // read the data
  3577. this._dataInitialize(data, this.style);
  3578. if (this.dataFilter) {
  3579. // apply filtering
  3580. this.dataPoints = this.dataFilter._getDataPoints();
  3581. }
  3582. else {
  3583. // no filtering. load all data
  3584. this.dataPoints = this._getDataPoints(this.dataTable);
  3585. }
  3586. // draw the filter
  3587. this._redrawFilter();
  3588. };
  3589. /**
  3590. * Replace the dataset of the Graph3d
  3591. * @param {Array | DataSet | DataView} data
  3592. */
  3593. Graph3d.prototype.setData = function (data) {
  3594. this._readData(data);
  3595. this.redraw();
  3596. // start animation when option is true
  3597. if (this.animationAutoStart && this.dataFilter) {
  3598. this.animationStart();
  3599. }
  3600. };
  3601. /**
  3602. * Update the options. Options will be merged with current options
  3603. * @param {Object} options
  3604. */
  3605. Graph3d.prototype.setOptions = function (options) {
  3606. var cameraPosition = undefined;
  3607. this.animationStop();
  3608. if (options !== undefined) {
  3609. // retrieve parameter values
  3610. if (options.width !== undefined) this.width = options.width;
  3611. if (options.height !== undefined) this.height = options.height;
  3612. if (options.xCenter !== undefined) this.defaultXCenter = options.xCenter;
  3613. if (options.yCenter !== undefined) this.defaultYCenter = options.yCenter;
  3614. if (options.filterLabel !== undefined) this.filterLabel = options.filterLabel;
  3615. if (options.legendLabel !== undefined) this.legendLabel = options.legendLabel;
  3616. if (options.xLabel !== undefined) this.xLabel = options.xLabel;
  3617. if (options.yLabel !== undefined) this.yLabel = options.yLabel;
  3618. if (options.zLabel !== undefined) this.zLabel = options.zLabel;
  3619. if (options.xValueLabel !== undefined) this.xValueLabel = options.xValueLabel;
  3620. if (options.yValueLabel !== undefined) this.yValueLabel = options.yValueLabel;
  3621. if (options.zValueLabel !== undefined) this.zValueLabel = options.zValueLabel;
  3622. if (options.style !== undefined) {
  3623. var styleNumber = this._getStyleNumber(options.style);
  3624. if (styleNumber !== -1) {
  3625. this.style = styleNumber;
  3626. }
  3627. }
  3628. if (options.showGrid !== undefined) this.showGrid = options.showGrid;
  3629. if (options.showPerspective !== undefined) this.showPerspective = options.showPerspective;
  3630. if (options.showShadow !== undefined) this.showShadow = options.showShadow;
  3631. if (options.tooltip !== undefined) this.showTooltip = options.tooltip;
  3632. if (options.showAnimationControls !== undefined) this.showAnimationControls = options.showAnimationControls;
  3633. if (options.keepAspectRatio !== undefined) this.keepAspectRatio = options.keepAspectRatio;
  3634. if (options.verticalRatio !== undefined) this.verticalRatio = options.verticalRatio;
  3635. if (options.animationInterval !== undefined) this.animationInterval = options.animationInterval;
  3636. if (options.animationPreload !== undefined) this.animationPreload = options.animationPreload;
  3637. if (options.animationAutoStart !== undefined)this.animationAutoStart = options.animationAutoStart;
  3638. if (options.xBarWidth !== undefined) this.defaultXBarWidth = options.xBarWidth;
  3639. if (options.yBarWidth !== undefined) this.defaultYBarWidth = options.yBarWidth;
  3640. if (options.xMin !== undefined) this.defaultXMin = options.xMin;
  3641. if (options.xStep !== undefined) this.defaultXStep = options.xStep;
  3642. if (options.xMax !== undefined) this.defaultXMax = options.xMax;
  3643. if (options.yMin !== undefined) this.defaultYMin = options.yMin;
  3644. if (options.yStep !== undefined) this.defaultYStep = options.yStep;
  3645. if (options.yMax !== undefined) this.defaultYMax = options.yMax;
  3646. if (options.zMin !== undefined) this.defaultZMin = options.zMin;
  3647. if (options.zStep !== undefined) this.defaultZStep = options.zStep;
  3648. if (options.zMax !== undefined) this.defaultZMax = options.zMax;
  3649. if (options.valueMin !== undefined) this.defaultValueMin = options.valueMin;
  3650. if (options.valueMax !== undefined) this.defaultValueMax = options.valueMax;
  3651. if (options.cameraPosition !== undefined) cameraPosition = options.cameraPosition;
  3652. if (cameraPosition !== undefined) {
  3653. this.camera.setArmRotation(cameraPosition.horizontal, cameraPosition.vertical);
  3654. this.camera.setArmLength(cameraPosition.distance);
  3655. }
  3656. else {
  3657. this.camera.setArmRotation(1.0, 0.5);
  3658. this.camera.setArmLength(1.7);
  3659. }
  3660. }
  3661. this._setBackgroundColor(options && options.backgroundColor);
  3662. this.setSize(this.width, this.height);
  3663. // re-load the data
  3664. if (this.dataTable) {
  3665. this.setData(this.dataTable);
  3666. }
  3667. // start animation when option is true
  3668. if (this.animationAutoStart && this.dataFilter) {
  3669. this.animationStart();
  3670. }
  3671. };
  3672. /**
  3673. * Redraw the Graph.
  3674. */
  3675. Graph3d.prototype.redraw = function() {
  3676. if (this.dataPoints === undefined) {
  3677. throw 'Error: graph data not initialized';
  3678. }
  3679. this._resizeCanvas();
  3680. this._resizeCenter();
  3681. this._redrawSlider();
  3682. this._redrawClear();
  3683. this._redrawAxis();
  3684. if (this.style === Graph3d.STYLE.GRID ||
  3685. this.style === Graph3d.STYLE.SURFACE) {
  3686. this._redrawDataGrid();
  3687. }
  3688. else if (this.style === Graph3d.STYLE.LINE) {
  3689. this._redrawDataLine();
  3690. }
  3691. else if (this.style === Graph3d.STYLE.BAR ||
  3692. this.style === Graph3d.STYLE.BARCOLOR ||
  3693. this.style === Graph3d.STYLE.BARSIZE) {
  3694. this._redrawDataBar();
  3695. }
  3696. else {
  3697. // style is DOT, DOTLINE, DOTCOLOR, DOTSIZE
  3698. this._redrawDataDot();
  3699. }
  3700. this._redrawInfo();
  3701. this._redrawLegend();
  3702. };
  3703. /**
  3704. * Clear the canvas before redrawing
  3705. */
  3706. Graph3d.prototype._redrawClear = function() {
  3707. var canvas = this.frame.canvas;
  3708. var ctx = canvas.getContext('2d');
  3709. ctx.clearRect(0, 0, canvas.width, canvas.height);
  3710. };
  3711. /**
  3712. * Redraw the legend showing the colors
  3713. */
  3714. Graph3d.prototype._redrawLegend = function() {
  3715. var y;
  3716. if (this.style === Graph3d.STYLE.DOTCOLOR ||
  3717. this.style === Graph3d.STYLE.DOTSIZE) {
  3718. var dotSize = this.frame.clientWidth * 0.02;
  3719. var widthMin, widthMax;
  3720. if (this.style === Graph3d.STYLE.DOTSIZE) {
  3721. widthMin = dotSize / 2; // px
  3722. widthMax = dotSize / 2 + dotSize * 2; // Todo: put this in one function
  3723. }
  3724. else {
  3725. widthMin = 20; // px
  3726. widthMax = 20; // px
  3727. }
  3728. var height = Math.max(this.frame.clientHeight * 0.25, 100);
  3729. var top = this.margin;
  3730. var right = this.frame.clientWidth - this.margin;
  3731. var left = right - widthMax;
  3732. var bottom = top + height;
  3733. }
  3734. var canvas = this.frame.canvas;
  3735. var ctx = canvas.getContext('2d');
  3736. ctx.lineWidth = 1;
  3737. ctx.font = '14px arial'; // TODO: put in options
  3738. if (this.style === Graph3d.STYLE.DOTCOLOR) {
  3739. // draw the color bar
  3740. var ymin = 0;
  3741. var ymax = height; // Todo: make height customizable
  3742. for (y = ymin; y < ymax; y++) {
  3743. var f = (y - ymin) / (ymax - ymin);
  3744. //var width = (dotSize / 2 + (1-f) * dotSize * 2); // Todo: put this in one function
  3745. var hue = f * 240;
  3746. var color = this._hsv2rgb(hue, 1, 1);
  3747. ctx.strokeStyle = color;
  3748. ctx.beginPath();
  3749. ctx.moveTo(left, top + y);
  3750. ctx.lineTo(right, top + y);
  3751. ctx.stroke();
  3752. }
  3753. ctx.strokeStyle = this.colorAxis;
  3754. ctx.strokeRect(left, top, widthMax, height);
  3755. }
  3756. if (this.style === Graph3d.STYLE.DOTSIZE) {
  3757. // draw border around color bar
  3758. ctx.strokeStyle = this.colorAxis;
  3759. ctx.fillStyle = this.colorDot;
  3760. ctx.beginPath();
  3761. ctx.moveTo(left, top);
  3762. ctx.lineTo(right, top);
  3763. ctx.lineTo(right - widthMax + widthMin, bottom);
  3764. ctx.lineTo(left, bottom);
  3765. ctx.closePath();
  3766. ctx.fill();
  3767. ctx.stroke();
  3768. }
  3769. if (this.style === Graph3d.STYLE.DOTCOLOR ||
  3770. this.style === Graph3d.STYLE.DOTSIZE) {
  3771. // print values along the color bar
  3772. var gridLineLen = 5; // px
  3773. var step = new StepNumber(this.valueMin, this.valueMax, (this.valueMax-this.valueMin)/5, true);
  3774. step.start();
  3775. if (step.getCurrent() < this.valueMin) {
  3776. step.next();
  3777. }
  3778. while (!step.end()) {
  3779. y = bottom - (step.getCurrent() - this.valueMin) / (this.valueMax - this.valueMin) * height;
  3780. ctx.beginPath();
  3781. ctx.moveTo(left - gridLineLen, y);
  3782. ctx.lineTo(left, y);
  3783. ctx.stroke();
  3784. ctx.textAlign = 'right';
  3785. ctx.textBaseline = 'middle';
  3786. ctx.fillStyle = this.colorAxis;
  3787. ctx.fillText(step.getCurrent(), left - 2 * gridLineLen, y);
  3788. step.next();
  3789. }
  3790. ctx.textAlign = 'right';
  3791. ctx.textBaseline = 'top';
  3792. var label = this.legendLabel;
  3793. ctx.fillText(label, right, bottom + this.margin);
  3794. }
  3795. };
  3796. /**
  3797. * Redraw the filter
  3798. */
  3799. Graph3d.prototype._redrawFilter = function() {
  3800. this.frame.filter.innerHTML = '';
  3801. if (this.dataFilter) {
  3802. var options = {
  3803. 'visible': this.showAnimationControls
  3804. };
  3805. var slider = new Slider(this.frame.filter, options);
  3806. this.frame.filter.slider = slider;
  3807. // TODO: css here is not nice here...
  3808. this.frame.filter.style.padding = '10px';
  3809. //this.frame.filter.style.backgroundColor = '#EFEFEF';
  3810. slider.setValues(this.dataFilter.values);
  3811. slider.setPlayInterval(this.animationInterval);
  3812. // create an event handler
  3813. var me = this;
  3814. var onchange = function () {
  3815. var index = slider.getIndex();
  3816. me.dataFilter.selectValue(index);
  3817. me.dataPoints = me.dataFilter._getDataPoints();
  3818. me.redraw();
  3819. };
  3820. slider.setOnChangeCallback(onchange);
  3821. }
  3822. else {
  3823. this.frame.filter.slider = undefined;
  3824. }
  3825. };
  3826. /**
  3827. * Redraw the slider
  3828. */
  3829. Graph3d.prototype._redrawSlider = function() {
  3830. if ( this.frame.filter.slider !== undefined) {
  3831. this.frame.filter.slider.redraw();
  3832. }
  3833. };
  3834. /**
  3835. * Redraw common information
  3836. */
  3837. Graph3d.prototype._redrawInfo = function() {
  3838. if (this.dataFilter) {
  3839. var canvas = this.frame.canvas;
  3840. var ctx = canvas.getContext('2d');
  3841. ctx.font = '14px arial'; // TODO: put in options
  3842. ctx.lineStyle = 'gray';
  3843. ctx.fillStyle = 'gray';
  3844. ctx.textAlign = 'left';
  3845. ctx.textBaseline = 'top';
  3846. var x = this.margin;
  3847. var y = this.margin;
  3848. ctx.fillText(this.dataFilter.getLabel() + ': ' + this.dataFilter.getSelectedValue(), x, y);
  3849. }
  3850. };
  3851. /**
  3852. * Redraw the axis
  3853. */
  3854. Graph3d.prototype._redrawAxis = function() {
  3855. var canvas = this.frame.canvas,
  3856. ctx = canvas.getContext('2d'),
  3857. from, to, step, prettyStep,
  3858. text, xText, yText, zText,
  3859. offset, xOffset, yOffset,
  3860. xMin2d, xMax2d;
  3861. // TODO: get the actual rendered style of the containerElement
  3862. //ctx.font = this.containerElement.style.font;
  3863. ctx.font = 24 / this.camera.getArmLength() + 'px arial';
  3864. // calculate the length for the short grid lines
  3865. var gridLenX = 0.025 / this.scale.x;
  3866. var gridLenY = 0.025 / this.scale.y;
  3867. var textMargin = 5 / this.camera.getArmLength(); // px
  3868. var armAngle = this.camera.getArmRotation().horizontal;
  3869. // draw x-grid lines
  3870. ctx.lineWidth = 1;
  3871. prettyStep = (this.defaultXStep === undefined);
  3872. step = new StepNumber(this.xMin, this.xMax, this.xStep, prettyStep);
  3873. step.start();
  3874. if (step.getCurrent() < this.xMin) {
  3875. step.next();
  3876. }
  3877. while (!step.end()) {
  3878. var x = step.getCurrent();
  3879. if (this.showGrid) {
  3880. from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin));
  3881. to = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin));
  3882. ctx.strokeStyle = this.colorGrid;
  3883. ctx.beginPath();
  3884. ctx.moveTo(from.x, from.y);
  3885. ctx.lineTo(to.x, to.y);
  3886. ctx.stroke();
  3887. }
  3888. else {
  3889. from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin));
  3890. to = this._convert3Dto2D(new Point3d(x, this.yMin+gridLenX, this.zMin));
  3891. ctx.strokeStyle = this.colorAxis;
  3892. ctx.beginPath();
  3893. ctx.moveTo(from.x, from.y);
  3894. ctx.lineTo(to.x, to.y);
  3895. ctx.stroke();
  3896. from = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin));
  3897. to = this._convert3Dto2D(new Point3d(x, this.yMax-gridLenX, this.zMin));
  3898. ctx.strokeStyle = this.colorAxis;
  3899. ctx.beginPath();
  3900. ctx.moveTo(from.x, from.y);
  3901. ctx.lineTo(to.x, to.y);
  3902. ctx.stroke();
  3903. }
  3904. yText = (Math.cos(armAngle) > 0) ? this.yMin : this.yMax;
  3905. text = this._convert3Dto2D(new Point3d(x, yText, this.zMin));
  3906. if (Math.cos(armAngle * 2) > 0) {
  3907. ctx.textAlign = 'center';
  3908. ctx.textBaseline = 'top';
  3909. text.y += textMargin;
  3910. }
  3911. else if (Math.sin(armAngle * 2) < 0){
  3912. ctx.textAlign = 'right';
  3913. ctx.textBaseline = 'middle';
  3914. }
  3915. else {
  3916. ctx.textAlign = 'left';
  3917. ctx.textBaseline = 'middle';
  3918. }
  3919. ctx.fillStyle = this.colorAxis;
  3920. ctx.fillText(' ' + this.xValueLabel(step.getCurrent()) + ' ', text.x, text.y);
  3921. step.next();
  3922. }
  3923. // draw y-grid lines
  3924. ctx.lineWidth = 1;
  3925. prettyStep = (this.defaultYStep === undefined);
  3926. step = new StepNumber(this.yMin, this.yMax, this.yStep, prettyStep);
  3927. step.start();
  3928. if (step.getCurrent() < this.yMin) {
  3929. step.next();
  3930. }
  3931. while (!step.end()) {
  3932. if (this.showGrid) {
  3933. from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin));
  3934. to = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin));
  3935. ctx.strokeStyle = this.colorGrid;
  3936. ctx.beginPath();
  3937. ctx.moveTo(from.x, from.y);
  3938. ctx.lineTo(to.x, to.y);
  3939. ctx.stroke();
  3940. }
  3941. else {
  3942. from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin));
  3943. to = this._convert3Dto2D(new Point3d(this.xMin+gridLenY, step.getCurrent(), this.zMin));
  3944. ctx.strokeStyle = this.colorAxis;
  3945. ctx.beginPath();
  3946. ctx.moveTo(from.x, from.y);
  3947. ctx.lineTo(to.x, to.y);
  3948. ctx.stroke();
  3949. from = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin));
  3950. to = this._convert3Dto2D(new Point3d(this.xMax-gridLenY, step.getCurrent(), this.zMin));
  3951. ctx.strokeStyle = this.colorAxis;
  3952. ctx.beginPath();
  3953. ctx.moveTo(from.x, from.y);
  3954. ctx.lineTo(to.x, to.y);
  3955. ctx.stroke();
  3956. }
  3957. xText = (Math.sin(armAngle ) > 0) ? this.xMin : this.xMax;
  3958. text = this._convert3Dto2D(new Point3d(xText, step.getCurrent(), this.zMin));
  3959. if (Math.cos(armAngle * 2) < 0) {
  3960. ctx.textAlign = 'center';
  3961. ctx.textBaseline = 'top';
  3962. text.y += textMargin;
  3963. }
  3964. else if (Math.sin(armAngle * 2) > 0){
  3965. ctx.textAlign = 'right';
  3966. ctx.textBaseline = 'middle';
  3967. }
  3968. else {
  3969. ctx.textAlign = 'left';
  3970. ctx.textBaseline = 'middle';
  3971. }
  3972. ctx.fillStyle = this.colorAxis;
  3973. ctx.fillText(' ' + this.yValueLabel(step.getCurrent()) + ' ', text.x, text.y);
  3974. step.next();
  3975. }
  3976. // draw z-grid lines and axis
  3977. ctx.lineWidth = 1;
  3978. prettyStep = (this.defaultZStep === undefined);
  3979. step = new StepNumber(this.zMin, this.zMax, this.zStep, prettyStep);
  3980. step.start();
  3981. if (step.getCurrent() < this.zMin) {
  3982. step.next();
  3983. }
  3984. xText = (Math.cos(armAngle ) > 0) ? this.xMin : this.xMax;
  3985. yText = (Math.sin(armAngle ) < 0) ? this.yMin : this.yMax;
  3986. while (!step.end()) {
  3987. // TODO: make z-grid lines really 3d?
  3988. from = this._convert3Dto2D(new Point3d(xText, yText, step.getCurrent()));
  3989. ctx.strokeStyle = this.colorAxis;
  3990. ctx.beginPath();
  3991. ctx.moveTo(from.x, from.y);
  3992. ctx.lineTo(from.x - textMargin, from.y);
  3993. ctx.stroke();
  3994. ctx.textAlign = 'right';
  3995. ctx.textBaseline = 'middle';
  3996. ctx.fillStyle = this.colorAxis;
  3997. ctx.fillText(this.zValueLabel(step.getCurrent()) + ' ', from.x - 5, from.y);
  3998. step.next();
  3999. }
  4000. ctx.lineWidth = 1;
  4001. from = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  4002. to = this._convert3Dto2D(new Point3d(xText, yText, this.zMax));
  4003. ctx.strokeStyle = this.colorAxis;
  4004. ctx.beginPath();
  4005. ctx.moveTo(from.x, from.y);
  4006. ctx.lineTo(to.x, to.y);
  4007. ctx.stroke();
  4008. // draw x-axis
  4009. ctx.lineWidth = 1;
  4010. // line at yMin
  4011. xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin));
  4012. xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin));
  4013. ctx.strokeStyle = this.colorAxis;
  4014. ctx.beginPath();
  4015. ctx.moveTo(xMin2d.x, xMin2d.y);
  4016. ctx.lineTo(xMax2d.x, xMax2d.y);
  4017. ctx.stroke();
  4018. // line at ymax
  4019. xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin));
  4020. xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin));
  4021. ctx.strokeStyle = this.colorAxis;
  4022. ctx.beginPath();
  4023. ctx.moveTo(xMin2d.x, xMin2d.y);
  4024. ctx.lineTo(xMax2d.x, xMax2d.y);
  4025. ctx.stroke();
  4026. // draw y-axis
  4027. ctx.lineWidth = 1;
  4028. // line at xMin
  4029. from = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin));
  4030. to = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin));
  4031. ctx.strokeStyle = this.colorAxis;
  4032. ctx.beginPath();
  4033. ctx.moveTo(from.x, from.y);
  4034. ctx.lineTo(to.x, to.y);
  4035. ctx.stroke();
  4036. // line at xMax
  4037. from = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin));
  4038. to = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin));
  4039. ctx.strokeStyle = this.colorAxis;
  4040. ctx.beginPath();
  4041. ctx.moveTo(from.x, from.y);
  4042. ctx.lineTo(to.x, to.y);
  4043. ctx.stroke();
  4044. // draw x-label
  4045. var xLabel = this.xLabel;
  4046. if (xLabel.length > 0) {
  4047. yOffset = 0.1 / this.scale.y;
  4048. xText = (this.xMin + this.xMax) / 2;
  4049. yText = (Math.cos(armAngle) > 0) ? this.yMin - yOffset: this.yMax + yOffset;
  4050. text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  4051. if (Math.cos(armAngle * 2) > 0) {
  4052. ctx.textAlign = 'center';
  4053. ctx.textBaseline = 'top';
  4054. }
  4055. else if (Math.sin(armAngle * 2) < 0){
  4056. ctx.textAlign = 'right';
  4057. ctx.textBaseline = 'middle';
  4058. }
  4059. else {
  4060. ctx.textAlign = 'left';
  4061. ctx.textBaseline = 'middle';
  4062. }
  4063. ctx.fillStyle = this.colorAxis;
  4064. ctx.fillText(xLabel, text.x, text.y);
  4065. }
  4066. // draw y-label
  4067. var yLabel = this.yLabel;
  4068. if (yLabel.length > 0) {
  4069. xOffset = 0.1 / this.scale.x;
  4070. xText = (Math.sin(armAngle ) > 0) ? this.xMin - xOffset : this.xMax + xOffset;
  4071. yText = (this.yMin + this.yMax) / 2;
  4072. text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  4073. if (Math.cos(armAngle * 2) < 0) {
  4074. ctx.textAlign = 'center';
  4075. ctx.textBaseline = 'top';
  4076. }
  4077. else if (Math.sin(armAngle * 2) > 0){
  4078. ctx.textAlign = 'right';
  4079. ctx.textBaseline = 'middle';
  4080. }
  4081. else {
  4082. ctx.textAlign = 'left';
  4083. ctx.textBaseline = 'middle';
  4084. }
  4085. ctx.fillStyle = this.colorAxis;
  4086. ctx.fillText(yLabel, text.x, text.y);
  4087. }
  4088. // draw z-label
  4089. var zLabel = this.zLabel;
  4090. if (zLabel.length > 0) {
  4091. offset = 30; // pixels. // TODO: relate to the max width of the values on the z axis?
  4092. xText = (Math.cos(armAngle ) > 0) ? this.xMin : this.xMax;
  4093. yText = (Math.sin(armAngle ) < 0) ? this.yMin : this.yMax;
  4094. zText = (this.zMin + this.zMax) / 2;
  4095. text = this._convert3Dto2D(new Point3d(xText, yText, zText));
  4096. ctx.textAlign = 'right';
  4097. ctx.textBaseline = 'middle';
  4098. ctx.fillStyle = this.colorAxis;
  4099. ctx.fillText(zLabel, text.x - offset, text.y);
  4100. }
  4101. };
  4102. /**
  4103. * Calculate the color based on the given value.
  4104. * @param {Number} H Hue, a value be between 0 and 360
  4105. * @param {Number} S Saturation, a value between 0 and 1
  4106. * @param {Number} V Value, a value between 0 and 1
  4107. */
  4108. Graph3d.prototype._hsv2rgb = function(H, S, V) {
  4109. var R, G, B, C, Hi, X;
  4110. C = V * S;
  4111. Hi = Math.floor(H/60); // hi = 0,1,2,3,4,5
  4112. X = C * (1 - Math.abs(((H/60) % 2) - 1));
  4113. switch (Hi) {
  4114. case 0: R = C; G = X; B = 0; break;
  4115. case 1: R = X; G = C; B = 0; break;
  4116. case 2: R = 0; G = C; B = X; break;
  4117. case 3: R = 0; G = X; B = C; break;
  4118. case 4: R = X; G = 0; B = C; break;
  4119. case 5: R = C; G = 0; B = X; break;
  4120. default: R = 0; G = 0; B = 0; break;
  4121. }
  4122. return 'RGB(' + parseInt(R*255) + ',' + parseInt(G*255) + ',' + parseInt(B*255) + ')';
  4123. };
  4124. /**
  4125. * Draw all datapoints as a grid
  4126. * This function can be used when the style is 'grid'
  4127. */
  4128. Graph3d.prototype._redrawDataGrid = function() {
  4129. var canvas = this.frame.canvas,
  4130. ctx = canvas.getContext('2d'),
  4131. point, right, top, cross,
  4132. i,
  4133. topSideVisible, fillStyle, strokeStyle, lineWidth,
  4134. h, s, v, zAvg;
  4135. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  4136. return; // TODO: throw exception?
  4137. // calculate the translations and screen position of all points
  4138. for (i = 0; i < this.dataPoints.length; i++) {
  4139. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  4140. var screen = this._convertTranslationToScreen(trans);
  4141. this.dataPoints[i].trans = trans;
  4142. this.dataPoints[i].screen = screen;
  4143. // calculate the translation of the point at the bottom (needed for sorting)
  4144. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  4145. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  4146. }
  4147. // sort the points on depth of their (x,y) position (not on z)
  4148. var sortDepth = function (a, b) {
  4149. return b.dist - a.dist;
  4150. };
  4151. this.dataPoints.sort(sortDepth);
  4152. if (this.style === Graph3d.STYLE.SURFACE) {
  4153. for (i = 0; i < this.dataPoints.length; i++) {
  4154. point = this.dataPoints[i];
  4155. right = this.dataPoints[i].pointRight;
  4156. top = this.dataPoints[i].pointTop;
  4157. cross = this.dataPoints[i].pointCross;
  4158. if (point !== undefined && right !== undefined && top !== undefined && cross !== undefined) {
  4159. if (this.showGrayBottom || this.showShadow) {
  4160. // calculate the cross product of the two vectors from center
  4161. // to left and right, in order to know whether we are looking at the
  4162. // bottom or at the top side. We can also use the cross product
  4163. // for calculating light intensity
  4164. var aDiff = Point3d.subtract(cross.trans, point.trans);
  4165. var bDiff = Point3d.subtract(top.trans, right.trans);
  4166. var crossproduct = Point3d.crossProduct(aDiff, bDiff);
  4167. var len = crossproduct.length();
  4168. // FIXME: there is a bug with determining the surface side (shadow or colored)
  4169. topSideVisible = (crossproduct.z > 0);
  4170. }
  4171. else {
  4172. topSideVisible = true;
  4173. }
  4174. if (topSideVisible) {
  4175. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  4176. zAvg = (point.point.z + right.point.z + top.point.z + cross.point.z) / 4;
  4177. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  4178. s = 1; // saturation
  4179. if (this.showShadow) {
  4180. v = Math.min(1 + (crossproduct.x / len) / 2, 1); // value. TODO: scale
  4181. fillStyle = this._hsv2rgb(h, s, v);
  4182. strokeStyle = fillStyle;
  4183. }
  4184. else {
  4185. v = 1;
  4186. fillStyle = this._hsv2rgb(h, s, v);
  4187. strokeStyle = this.colorAxis;
  4188. }
  4189. }
  4190. else {
  4191. fillStyle = 'gray';
  4192. strokeStyle = this.colorAxis;
  4193. }
  4194. lineWidth = 0.5;
  4195. ctx.lineWidth = lineWidth;
  4196. ctx.fillStyle = fillStyle;
  4197. ctx.strokeStyle = strokeStyle;
  4198. ctx.beginPath();
  4199. ctx.moveTo(point.screen.x, point.screen.y);
  4200. ctx.lineTo(right.screen.x, right.screen.y);
  4201. ctx.lineTo(cross.screen.x, cross.screen.y);
  4202. ctx.lineTo(top.screen.x, top.screen.y);
  4203. ctx.closePath();
  4204. ctx.fill();
  4205. ctx.stroke();
  4206. }
  4207. }
  4208. }
  4209. else { // grid style
  4210. for (i = 0; i < this.dataPoints.length; i++) {
  4211. point = this.dataPoints[i];
  4212. right = this.dataPoints[i].pointRight;
  4213. top = this.dataPoints[i].pointTop;
  4214. if (point !== undefined) {
  4215. if (this.showPerspective) {
  4216. lineWidth = 2 / -point.trans.z;
  4217. }
  4218. else {
  4219. lineWidth = 2 * -(this.eye.z / this.camera.getArmLength());
  4220. }
  4221. }
  4222. if (point !== undefined && right !== undefined) {
  4223. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  4224. zAvg = (point.point.z + right.point.z) / 2;
  4225. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  4226. ctx.lineWidth = lineWidth;
  4227. ctx.strokeStyle = this._hsv2rgb(h, 1, 1);
  4228. ctx.beginPath();
  4229. ctx.moveTo(point.screen.x, point.screen.y);
  4230. ctx.lineTo(right.screen.x, right.screen.y);
  4231. ctx.stroke();
  4232. }
  4233. if (point !== undefined && top !== undefined) {
  4234. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  4235. zAvg = (point.point.z + top.point.z) / 2;
  4236. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  4237. ctx.lineWidth = lineWidth;
  4238. ctx.strokeStyle = this._hsv2rgb(h, 1, 1);
  4239. ctx.beginPath();
  4240. ctx.moveTo(point.screen.x, point.screen.y);
  4241. ctx.lineTo(top.screen.x, top.screen.y);
  4242. ctx.stroke();
  4243. }
  4244. }
  4245. }
  4246. };
  4247. /**
  4248. * Draw all datapoints as dots.
  4249. * This function can be used when the style is 'dot' or 'dot-line'
  4250. */
  4251. Graph3d.prototype._redrawDataDot = function() {
  4252. var canvas = this.frame.canvas;
  4253. var ctx = canvas.getContext('2d');
  4254. var i;
  4255. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  4256. return; // TODO: throw exception?
  4257. // calculate the translations of all points
  4258. for (i = 0; i < this.dataPoints.length; i++) {
  4259. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  4260. var screen = this._convertTranslationToScreen(trans);
  4261. this.dataPoints[i].trans = trans;
  4262. this.dataPoints[i].screen = screen;
  4263. // calculate the distance from the point at the bottom to the camera
  4264. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  4265. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  4266. }
  4267. // order the translated points by depth
  4268. var sortDepth = function (a, b) {
  4269. return b.dist - a.dist;
  4270. };
  4271. this.dataPoints.sort(sortDepth);
  4272. // draw the datapoints as colored circles
  4273. var dotSize = this.frame.clientWidth * 0.02; // px
  4274. for (i = 0; i < this.dataPoints.length; i++) {
  4275. var point = this.dataPoints[i];
  4276. if (this.style === Graph3d.STYLE.DOTLINE) {
  4277. // draw a vertical line from the bottom to the graph value
  4278. //var from = this._convert3Dto2D(new Point3d(point.point.x, point.point.y, this.zMin));
  4279. var from = this._convert3Dto2D(point.bottom);
  4280. ctx.lineWidth = 1;
  4281. ctx.strokeStyle = this.colorGrid;
  4282. ctx.beginPath();
  4283. ctx.moveTo(from.x, from.y);
  4284. ctx.lineTo(point.screen.x, point.screen.y);
  4285. ctx.stroke();
  4286. }
  4287. // calculate radius for the circle
  4288. var size;
  4289. if (this.style === Graph3d.STYLE.DOTSIZE) {
  4290. size = dotSize/2 + 2*dotSize * (point.point.value - this.valueMin) / (this.valueMax - this.valueMin);
  4291. }
  4292. else {
  4293. size = dotSize;
  4294. }
  4295. var radius;
  4296. if (this.showPerspective) {
  4297. radius = size / -point.trans.z;
  4298. }
  4299. else {
  4300. radius = size * -(this.eye.z / this.camera.getArmLength());
  4301. }
  4302. if (radius < 0) {
  4303. radius = 0;
  4304. }
  4305. var hue, color, borderColor;
  4306. if (this.style === Graph3d.STYLE.DOTCOLOR ) {
  4307. // calculate the color based on the value
  4308. hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240;
  4309. color = this._hsv2rgb(hue, 1, 1);
  4310. borderColor = this._hsv2rgb(hue, 1, 0.8);
  4311. }
  4312. else if (this.style === Graph3d.STYLE.DOTSIZE) {
  4313. color = this.colorDot;
  4314. borderColor = this.colorDotBorder;
  4315. }
  4316. else {
  4317. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  4318. hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  4319. color = this._hsv2rgb(hue, 1, 1);
  4320. borderColor = this._hsv2rgb(hue, 1, 0.8);
  4321. }
  4322. // draw the circle
  4323. ctx.lineWidth = 1.0;
  4324. ctx.strokeStyle = borderColor;
  4325. ctx.fillStyle = color;
  4326. ctx.beginPath();
  4327. ctx.arc(point.screen.x, point.screen.y, radius, 0, Math.PI*2, true);
  4328. ctx.fill();
  4329. ctx.stroke();
  4330. }
  4331. };
  4332. /**
  4333. * Draw all datapoints as bars.
  4334. * This function can be used when the style is 'bar', 'bar-color', or 'bar-size'
  4335. */
  4336. Graph3d.prototype._redrawDataBar = function() {
  4337. var canvas = this.frame.canvas;
  4338. var ctx = canvas.getContext('2d');
  4339. var i, j, surface, corners;
  4340. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  4341. return; // TODO: throw exception?
  4342. // calculate the translations of all points
  4343. for (i = 0; i < this.dataPoints.length; i++) {
  4344. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  4345. var screen = this._convertTranslationToScreen(trans);
  4346. this.dataPoints[i].trans = trans;
  4347. this.dataPoints[i].screen = screen;
  4348. // calculate the distance from the point at the bottom to the camera
  4349. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  4350. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  4351. }
  4352. // order the translated points by depth
  4353. var sortDepth = function (a, b) {
  4354. return b.dist - a.dist;
  4355. };
  4356. this.dataPoints.sort(sortDepth);
  4357. // draw the datapoints as bars
  4358. var xWidth = this.xBarWidth / 2;
  4359. var yWidth = this.yBarWidth / 2;
  4360. for (i = 0; i < this.dataPoints.length; i++) {
  4361. var point = this.dataPoints[i];
  4362. // determine color
  4363. var hue, color, borderColor;
  4364. if (this.style === Graph3d.STYLE.BARCOLOR ) {
  4365. // calculate the color based on the value
  4366. hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240;
  4367. color = this._hsv2rgb(hue, 1, 1);
  4368. borderColor = this._hsv2rgb(hue, 1, 0.8);
  4369. }
  4370. else if (this.style === Graph3d.STYLE.BARSIZE) {
  4371. color = this.colorDot;
  4372. borderColor = this.colorDotBorder;
  4373. }
  4374. else {
  4375. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  4376. hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  4377. color = this._hsv2rgb(hue, 1, 1);
  4378. borderColor = this._hsv2rgb(hue, 1, 0.8);
  4379. }
  4380. // calculate size for the bar
  4381. if (this.style === Graph3d.STYLE.BARSIZE) {
  4382. xWidth = (this.xBarWidth / 2) * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2);
  4383. yWidth = (this.yBarWidth / 2) * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2);
  4384. }
  4385. // calculate all corner points
  4386. var me = this;
  4387. var point3d = point.point;
  4388. var top = [
  4389. {point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, point3d.z)},
  4390. {point: new Point3d(point3d.x + xWidth, point3d.y - yWidth, point3d.z)},
  4391. {point: new Point3d(point3d.x + xWidth, point3d.y + yWidth, point3d.z)},
  4392. {point: new Point3d(point3d.x - xWidth, point3d.y + yWidth, point3d.z)}
  4393. ];
  4394. var bottom = [
  4395. {point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, this.zMin)},
  4396. {point: new Point3d(point3d.x + xWidth, point3d.y - yWidth, this.zMin)},
  4397. {point: new Point3d(point3d.x + xWidth, point3d.y + yWidth, this.zMin)},
  4398. {point: new Point3d(point3d.x - xWidth, point3d.y + yWidth, this.zMin)}
  4399. ];
  4400. // calculate screen location of the points
  4401. top.forEach(function (obj) {
  4402. obj.screen = me._convert3Dto2D(obj.point);
  4403. });
  4404. bottom.forEach(function (obj) {
  4405. obj.screen = me._convert3Dto2D(obj.point);
  4406. });
  4407. // create five sides, calculate both corner points and center points
  4408. var surfaces = [
  4409. {corners: top, center: Point3d.avg(bottom[0].point, bottom[2].point)},
  4410. {corners: [top[0], top[1], bottom[1], bottom[0]], center: Point3d.avg(bottom[1].point, bottom[0].point)},
  4411. {corners: [top[1], top[2], bottom[2], bottom[1]], center: Point3d.avg(bottom[2].point, bottom[1].point)},
  4412. {corners: [top[2], top[3], bottom[3], bottom[2]], center: Point3d.avg(bottom[3].point, bottom[2].point)},
  4413. {corners: [top[3], top[0], bottom[0], bottom[3]], center: Point3d.avg(bottom[0].point, bottom[3].point)}
  4414. ];
  4415. point.surfaces = surfaces;
  4416. // calculate the distance of each of the surface centers to the camera
  4417. for (j = 0; j < surfaces.length; j++) {
  4418. surface = surfaces[j];
  4419. var transCenter = this._convertPointToTranslation(surface.center);
  4420. surface.dist = this.showPerspective ? transCenter.length() : -transCenter.z;
  4421. // TODO: this dept calculation doesn't work 100% of the cases due to perspective,
  4422. // but the current solution is fast/simple and works in 99.9% of all cases
  4423. // the issue is visible in example 14, with graph.setCameraPosition({horizontal: 2.97, vertical: 0.5, distance: 0.9})
  4424. }
  4425. // order the surfaces by their (translated) depth
  4426. surfaces.sort(function (a, b) {
  4427. var diff = b.dist - a.dist;
  4428. if (diff) return diff;
  4429. // if equal depth, sort the top surface last
  4430. if (a.corners === top) return 1;
  4431. if (b.corners === top) return -1;
  4432. // both are equal
  4433. return 0;
  4434. });
  4435. // draw the ordered surfaces
  4436. ctx.lineWidth = 1;
  4437. ctx.strokeStyle = borderColor;
  4438. ctx.fillStyle = color;
  4439. // NOTE: we start at j=2 instead of j=0 as we don't need to draw the two surfaces at the backside
  4440. for (j = 2; j < surfaces.length; j++) {
  4441. surface = surfaces[j];
  4442. corners = surface.corners;
  4443. ctx.beginPath();
  4444. ctx.moveTo(corners[3].screen.x, corners[3].screen.y);
  4445. ctx.lineTo(corners[0].screen.x, corners[0].screen.y);
  4446. ctx.lineTo(corners[1].screen.x, corners[1].screen.y);
  4447. ctx.lineTo(corners[2].screen.x, corners[2].screen.y);
  4448. ctx.lineTo(corners[3].screen.x, corners[3].screen.y);
  4449. ctx.fill();
  4450. ctx.stroke();
  4451. }
  4452. }
  4453. };
  4454. /**
  4455. * Draw a line through all datapoints.
  4456. * This function can be used when the style is 'line'
  4457. */
  4458. Graph3d.prototype._redrawDataLine = function() {
  4459. var canvas = this.frame.canvas,
  4460. ctx = canvas.getContext('2d'),
  4461. point, i;
  4462. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  4463. return; // TODO: throw exception?
  4464. // calculate the translations of all points
  4465. for (i = 0; i < this.dataPoints.length; i++) {
  4466. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  4467. var screen = this._convertTranslationToScreen(trans);
  4468. this.dataPoints[i].trans = trans;
  4469. this.dataPoints[i].screen = screen;
  4470. }
  4471. // start the line
  4472. if (this.dataPoints.length > 0) {
  4473. point = this.dataPoints[0];
  4474. ctx.lineWidth = 1; // TODO: make customizable
  4475. ctx.strokeStyle = 'blue'; // TODO: make customizable
  4476. ctx.beginPath();
  4477. ctx.moveTo(point.screen.x, point.screen.y);
  4478. }
  4479. // draw the datapoints as colored circles
  4480. for (i = 1; i < this.dataPoints.length; i++) {
  4481. point = this.dataPoints[i];
  4482. ctx.lineTo(point.screen.x, point.screen.y);
  4483. }
  4484. // finish the line
  4485. if (this.dataPoints.length > 0) {
  4486. ctx.stroke();
  4487. }
  4488. };
  4489. /**
  4490. * Start a moving operation inside the provided parent element
  4491. * @param {Event} event The event that occurred (required for
  4492. * retrieving the mouse position)
  4493. */
  4494. Graph3d.prototype._onMouseDown = function(event) {
  4495. event = event || window.event;
  4496. // check if mouse is still down (may be up when focus is lost for example
  4497. // in an iframe)
  4498. if (this.leftButtonDown) {
  4499. this._onMouseUp(event);
  4500. }
  4501. // only react on left mouse button down
  4502. this.leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
  4503. if (!this.leftButtonDown && !this.touchDown) return;
  4504. // get mouse position (different code for IE and all other browsers)
  4505. this.startMouseX = getMouseX(event);
  4506. this.startMouseY = getMouseY(event);
  4507. this.startStart = new Date(this.start);
  4508. this.startEnd = new Date(this.end);
  4509. this.startArmRotation = this.camera.getArmRotation();
  4510. this.frame.style.cursor = 'move';
  4511. // add event listeners to handle moving the contents
  4512. // we store the function onmousemove and onmouseup in the graph, so we can
  4513. // remove the eventlisteners lateron in the function mouseUp()
  4514. var me = this;
  4515. this.onmousemove = function (event) {me._onMouseMove(event);};
  4516. this.onmouseup = function (event) {me._onMouseUp(event);};
  4517. util.addEventListener(document, 'mousemove', me.onmousemove);
  4518. util.addEventListener(document, 'mouseup', me.onmouseup);
  4519. util.preventDefault(event);
  4520. };
  4521. /**
  4522. * Perform moving operating.
  4523. * This function activated from within the funcion Graph.mouseDown().
  4524. * @param {Event} event Well, eehh, the event
  4525. */
  4526. Graph3d.prototype._onMouseMove = function (event) {
  4527. event = event || window.event;
  4528. // calculate change in mouse position
  4529. var diffX = parseFloat(getMouseX(event)) - this.startMouseX;
  4530. var diffY = parseFloat(getMouseY(event)) - this.startMouseY;
  4531. var horizontalNew = this.startArmRotation.horizontal + diffX / 200;
  4532. var verticalNew = this.startArmRotation.vertical + diffY / 200;
  4533. var snapAngle = 4; // degrees
  4534. var snapValue = Math.sin(snapAngle / 360 * 2 * Math.PI);
  4535. // snap horizontally to nice angles at 0pi, 0.5pi, 1pi, 1.5pi, etc...
  4536. // the -0.001 is to take care that the vertical axis is always drawn at the left front corner
  4537. if (Math.abs(Math.sin(horizontalNew)) < snapValue) {
  4538. horizontalNew = Math.round((horizontalNew / Math.PI)) * Math.PI - 0.001;
  4539. }
  4540. if (Math.abs(Math.cos(horizontalNew)) < snapValue) {
  4541. horizontalNew = (Math.round((horizontalNew/ Math.PI - 0.5)) + 0.5) * Math.PI - 0.001;
  4542. }
  4543. // snap vertically to nice angles
  4544. if (Math.abs(Math.sin(verticalNew)) < snapValue) {
  4545. verticalNew = Math.round((verticalNew / Math.PI)) * Math.PI;
  4546. }
  4547. if (Math.abs(Math.cos(verticalNew)) < snapValue) {
  4548. verticalNew = (Math.round((verticalNew/ Math.PI - 0.5)) + 0.5) * Math.PI;
  4549. }
  4550. this.camera.setArmRotation(horizontalNew, verticalNew);
  4551. this.redraw();
  4552. // fire a cameraPositionChange event
  4553. var parameters = this.getCameraPosition();
  4554. this.emit('cameraPositionChange', parameters);
  4555. util.preventDefault(event);
  4556. };
  4557. /**
  4558. * Stop moving operating.
  4559. * This function activated from within the funcion Graph.mouseDown().
  4560. * @param {event} event The event
  4561. */
  4562. Graph3d.prototype._onMouseUp = function (event) {
  4563. this.frame.style.cursor = 'auto';
  4564. this.leftButtonDown = false;
  4565. // remove event listeners here
  4566. util.removeEventListener(document, 'mousemove', this.onmousemove);
  4567. util.removeEventListener(document, 'mouseup', this.onmouseup);
  4568. util.preventDefault(event);
  4569. };
  4570. /**
  4571. * After having moved the mouse, a tooltip should pop up when the mouse is resting on a data point
  4572. * @param {Event} event A mouse move event
  4573. */
  4574. Graph3d.prototype._onTooltip = function (event) {
  4575. var delay = 300; // ms
  4576. var boundingRect = this.frame.getBoundingClientRect();
  4577. var mouseX = getMouseX(event) - boundingRect.left;
  4578. var mouseY = getMouseY(event) - boundingRect.top;
  4579. if (!this.showTooltip) {
  4580. return;
  4581. }
  4582. if (this.tooltipTimeout) {
  4583. clearTimeout(this.tooltipTimeout);
  4584. }
  4585. // (delayed) display of a tooltip only if no mouse button is down
  4586. if (this.leftButtonDown) {
  4587. this._hideTooltip();
  4588. return;
  4589. }
  4590. if (this.tooltip && this.tooltip.dataPoint) {
  4591. // tooltip is currently visible
  4592. var dataPoint = this._dataPointFromXY(mouseX, mouseY);
  4593. if (dataPoint !== this.tooltip.dataPoint) {
  4594. // datapoint changed
  4595. if (dataPoint) {
  4596. this._showTooltip(dataPoint);
  4597. }
  4598. else {
  4599. this._hideTooltip();
  4600. }
  4601. }
  4602. }
  4603. else {
  4604. // tooltip is currently not visible
  4605. var me = this;
  4606. this.tooltipTimeout = setTimeout(function () {
  4607. me.tooltipTimeout = null;
  4608. // show a tooltip if we have a data point
  4609. var dataPoint = me._dataPointFromXY(mouseX, mouseY);
  4610. if (dataPoint) {
  4611. me._showTooltip(dataPoint);
  4612. }
  4613. }, delay);
  4614. }
  4615. };
  4616. /**
  4617. * Event handler for touchstart event on mobile devices
  4618. */
  4619. Graph3d.prototype._onTouchStart = function(event) {
  4620. this.touchDown = true;
  4621. var me = this;
  4622. this.ontouchmove = function (event) {me._onTouchMove(event);};
  4623. this.ontouchend = function (event) {me._onTouchEnd(event);};
  4624. util.addEventListener(document, 'touchmove', me.ontouchmove);
  4625. util.addEventListener(document, 'touchend', me.ontouchend);
  4626. this._onMouseDown(event);
  4627. };
  4628. /**
  4629. * Event handler for touchmove event on mobile devices
  4630. */
  4631. Graph3d.prototype._onTouchMove = function(event) {
  4632. this._onMouseMove(event);
  4633. };
  4634. /**
  4635. * Event handler for touchend event on mobile devices
  4636. */
  4637. Graph3d.prototype._onTouchEnd = function(event) {
  4638. this.touchDown = false;
  4639. util.removeEventListener(document, 'touchmove', this.ontouchmove);
  4640. util.removeEventListener(document, 'touchend', this.ontouchend);
  4641. this._onMouseUp(event);
  4642. };
  4643. /**
  4644. * Event handler for mouse wheel event, used to zoom the graph
  4645. * Code from http://adomas.org/javascript-mouse-wheel/
  4646. * @param {event} event The event
  4647. */
  4648. Graph3d.prototype._onWheel = function(event) {
  4649. if (!event) /* For IE. */
  4650. event = window.event;
  4651. // retrieve delta
  4652. var delta = 0;
  4653. if (event.wheelDelta) { /* IE/Opera. */
  4654. delta = event.wheelDelta/120;
  4655. } else if (event.detail) { /* Mozilla case. */
  4656. // In Mozilla, sign of delta is different than in IE.
  4657. // Also, delta is multiple of 3.
  4658. delta = -event.detail/3;
  4659. }
  4660. // If delta is nonzero, handle it.
  4661. // Basically, delta is now positive if wheel was scrolled up,
  4662. // and negative, if wheel was scrolled down.
  4663. if (delta) {
  4664. var oldLength = this.camera.getArmLength();
  4665. var newLength = oldLength * (1 - delta / 10);
  4666. this.camera.setArmLength(newLength);
  4667. this.redraw();
  4668. this._hideTooltip();
  4669. }
  4670. // fire a cameraPositionChange event
  4671. var parameters = this.getCameraPosition();
  4672. this.emit('cameraPositionChange', parameters);
  4673. // Prevent default actions caused by mouse wheel.
  4674. // That might be ugly, but we handle scrolls somehow
  4675. // anyway, so don't bother here..
  4676. util.preventDefault(event);
  4677. };
  4678. /**
  4679. * Test whether a point lies inside given 2D triangle
  4680. * @param {Point2d} point
  4681. * @param {Point2d[]} triangle
  4682. * @return {boolean} Returns true if given point lies inside or on the edge of the triangle
  4683. * @private
  4684. */
  4685. Graph3d.prototype._insideTriangle = function (point, triangle) {
  4686. var a = triangle[0],
  4687. b = triangle[1],
  4688. c = triangle[2];
  4689. function sign (x) {
  4690. return x > 0 ? 1 : x < 0 ? -1 : 0;
  4691. }
  4692. var as = sign((b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x));
  4693. var bs = sign((c.x - b.x) * (point.y - b.y) - (c.y - b.y) * (point.x - b.x));
  4694. var cs = sign((a.x - c.x) * (point.y - c.y) - (a.y - c.y) * (point.x - c.x));
  4695. // each of the three signs must be either equal to each other or zero
  4696. return (as == 0 || bs == 0 || as == bs) &&
  4697. (bs == 0 || cs == 0 || bs == cs) &&
  4698. (as == 0 || cs == 0 || as == cs);
  4699. };
  4700. /**
  4701. * Find a data point close to given screen position (x, y)
  4702. * @param {Number} x
  4703. * @param {Number} y
  4704. * @return {Object | null} The closest data point or null if not close to any data point
  4705. * @private
  4706. */
  4707. Graph3d.prototype._dataPointFromXY = function (x, y) {
  4708. var i,
  4709. distMax = 100, // px
  4710. dataPoint = null,
  4711. closestDataPoint = null,
  4712. closestDist = null,
  4713. center = new Point2d(x, y);
  4714. if (this.style === Graph3d.STYLE.BAR ||
  4715. this.style === Graph3d.STYLE.BARCOLOR ||
  4716. this.style === Graph3d.STYLE.BARSIZE) {
  4717. // the data points are ordered from far away to closest
  4718. for (i = this.dataPoints.length - 1; i >= 0; i--) {
  4719. dataPoint = this.dataPoints[i];
  4720. var surfaces = dataPoint.surfaces;
  4721. if (surfaces) {
  4722. for (var s = surfaces.length - 1; s >= 0; s--) {
  4723. // split each surface in two triangles, and see if the center point is inside one of these
  4724. var surface = surfaces[s];
  4725. var corners = surface.corners;
  4726. var triangle1 = [corners[0].screen, corners[1].screen, corners[2].screen];
  4727. var triangle2 = [corners[2].screen, corners[3].screen, corners[0].screen];
  4728. if (this._insideTriangle(center, triangle1) ||
  4729. this._insideTriangle(center, triangle2)) {
  4730. // return immediately at the first hit
  4731. return dataPoint;
  4732. }
  4733. }
  4734. }
  4735. }
  4736. }
  4737. else {
  4738. // find the closest data point, using distance to the center of the point on 2d screen
  4739. for (i = 0; i < this.dataPoints.length; i++) {
  4740. dataPoint = this.dataPoints[i];
  4741. var point = dataPoint.screen;
  4742. if (point) {
  4743. var distX = Math.abs(x - point.x);
  4744. var distY = Math.abs(y - point.y);
  4745. var dist = Math.sqrt(distX * distX + distY * distY);
  4746. if ((closestDist === null || dist < closestDist) && dist < distMax) {
  4747. closestDist = dist;
  4748. closestDataPoint = dataPoint;
  4749. }
  4750. }
  4751. }
  4752. }
  4753. return closestDataPoint;
  4754. };
  4755. /**
  4756. * Display a tooltip for given data point
  4757. * @param {Object} dataPoint
  4758. * @private
  4759. */
  4760. Graph3d.prototype._showTooltip = function (dataPoint) {
  4761. var content, line, dot;
  4762. if (!this.tooltip) {
  4763. content = document.createElement('div');
  4764. content.style.position = 'absolute';
  4765. content.style.padding = '10px';
  4766. content.style.border = '1px solid #4d4d4d';
  4767. content.style.color = '#1a1a1a';
  4768. content.style.background = 'rgba(255,255,255,0.7)';
  4769. content.style.borderRadius = '2px';
  4770. content.style.boxShadow = '5px 5px 10px rgba(128,128,128,0.5)';
  4771. line = document.createElement('div');
  4772. line.style.position = 'absolute';
  4773. line.style.height = '40px';
  4774. line.style.width = '0';
  4775. line.style.borderLeft = '1px solid #4d4d4d';
  4776. dot = document.createElement('div');
  4777. dot.style.position = 'absolute';
  4778. dot.style.height = '0';
  4779. dot.style.width = '0';
  4780. dot.style.border = '5px solid #4d4d4d';
  4781. dot.style.borderRadius = '5px';
  4782. this.tooltip = {
  4783. dataPoint: null,
  4784. dom: {
  4785. content: content,
  4786. line: line,
  4787. dot: dot
  4788. }
  4789. };
  4790. }
  4791. else {
  4792. content = this.tooltip.dom.content;
  4793. line = this.tooltip.dom.line;
  4794. dot = this.tooltip.dom.dot;
  4795. }
  4796. this._hideTooltip();
  4797. this.tooltip.dataPoint = dataPoint;
  4798. if (typeof this.showTooltip === 'function') {
  4799. content.innerHTML = this.showTooltip(dataPoint.point);
  4800. }
  4801. else {
  4802. content.innerHTML = '<table>' +
  4803. '<tr><td>x:</td><td>' + dataPoint.point.x + '</td></tr>' +
  4804. '<tr><td>y:</td><td>' + dataPoint.point.y + '</td></tr>' +
  4805. '<tr><td>z:</td><td>' + dataPoint.point.z + '</td></tr>' +
  4806. '</table>';
  4807. }
  4808. content.style.left = '0';
  4809. content.style.top = '0';
  4810. this.frame.appendChild(content);
  4811. this.frame.appendChild(line);
  4812. this.frame.appendChild(dot);
  4813. // calculate sizes
  4814. var contentWidth = content.offsetWidth;
  4815. var contentHeight = content.offsetHeight;
  4816. var lineHeight = line.offsetHeight;
  4817. var dotWidth = dot.offsetWidth;
  4818. var dotHeight = dot.offsetHeight;
  4819. var left = dataPoint.screen.x - contentWidth / 2;
  4820. left = Math.min(Math.max(left, 10), this.frame.clientWidth - 10 - contentWidth);
  4821. line.style.left = dataPoint.screen.x + 'px';
  4822. line.style.top = (dataPoint.screen.y - lineHeight) + 'px';
  4823. content.style.left = left + 'px';
  4824. content.style.top = (dataPoint.screen.y - lineHeight - contentHeight) + 'px';
  4825. dot.style.left = (dataPoint.screen.x - dotWidth / 2) + 'px';
  4826. dot.style.top = (dataPoint.screen.y - dotHeight / 2) + 'px';
  4827. };
  4828. /**
  4829. * Hide the tooltip when displayed
  4830. * @private
  4831. */
  4832. Graph3d.prototype._hideTooltip = function () {
  4833. if (this.tooltip) {
  4834. this.tooltip.dataPoint = null;
  4835. for (var prop in this.tooltip.dom) {
  4836. if (this.tooltip.dom.hasOwnProperty(prop)) {
  4837. var elem = this.tooltip.dom[prop];
  4838. if (elem && elem.parentNode) {
  4839. elem.parentNode.removeChild(elem);
  4840. }
  4841. }
  4842. }
  4843. }
  4844. };
  4845. /**--------------------------------------------------------------------------**/
  4846. /**
  4847. * Get the horizontal mouse position from a mouse event
  4848. * @param {Event} event
  4849. * @return {Number} mouse x
  4850. */
  4851. function getMouseX (event) {
  4852. if ('clientX' in event) return event.clientX;
  4853. return event.targetTouches[0] && event.targetTouches[0].clientX || 0;
  4854. }
  4855. /**
  4856. * Get the vertical mouse position from a mouse event
  4857. * @param {Event} event
  4858. * @return {Number} mouse y
  4859. */
  4860. function getMouseY (event) {
  4861. if ('clientY' in event) return event.clientY;
  4862. return event.targetTouches[0] && event.targetTouches[0].clientY || 0;
  4863. }
  4864. module.exports = Graph3d;
  4865. /***/ },
  4866. /* 7 */
  4867. /***/ function(module, exports, __webpack_require__) {
  4868. var Point3d = __webpack_require__(10);
  4869. /**
  4870. * @class Camera
  4871. * The camera is mounted on a (virtual) camera arm. The camera arm can rotate
  4872. * The camera is always looking in the direction of the origin of the arm.
  4873. * This way, the camera always rotates around one fixed point, the location
  4874. * of the camera arm.
  4875. *
  4876. * Documentation:
  4877. * http://en.wikipedia.org/wiki/3D_projection
  4878. */
  4879. function Camera() {
  4880. this.armLocation = new Point3d();
  4881. this.armRotation = {};
  4882. this.armRotation.horizontal = 0;
  4883. this.armRotation.vertical = 0;
  4884. this.armLength = 1.7;
  4885. this.cameraLocation = new Point3d();
  4886. this.cameraRotation = new Point3d(0.5*Math.PI, 0, 0);
  4887. this.calculateCameraOrientation();
  4888. }
  4889. /**
  4890. * Set the location (origin) of the arm
  4891. * @param {Number} x Normalized value of x
  4892. * @param {Number} y Normalized value of y
  4893. * @param {Number} z Normalized value of z
  4894. */
  4895. Camera.prototype.setArmLocation = function(x, y, z) {
  4896. this.armLocation.x = x;
  4897. this.armLocation.y = y;
  4898. this.armLocation.z = z;
  4899. this.calculateCameraOrientation();
  4900. };
  4901. /**
  4902. * Set the rotation of the camera arm
  4903. * @param {Number} horizontal The horizontal rotation, between 0 and 2*PI.
  4904. * Optional, can be left undefined.
  4905. * @param {Number} vertical The vertical rotation, between 0 and 0.5*PI
  4906. * if vertical=0.5*PI, the graph is shown from the
  4907. * top. Optional, can be left undefined.
  4908. */
  4909. Camera.prototype.setArmRotation = function(horizontal, vertical) {
  4910. if (horizontal !== undefined) {
  4911. this.armRotation.horizontal = horizontal;
  4912. }
  4913. if (vertical !== undefined) {
  4914. this.armRotation.vertical = vertical;
  4915. if (this.armRotation.vertical < 0) this.armRotation.vertical = 0;
  4916. if (this.armRotation.vertical > 0.5*Math.PI) this.armRotation.vertical = 0.5*Math.PI;
  4917. }
  4918. if (horizontal !== undefined || vertical !== undefined) {
  4919. this.calculateCameraOrientation();
  4920. }
  4921. };
  4922. /**
  4923. * Retrieve the current arm rotation
  4924. * @return {object} An object with parameters horizontal and vertical
  4925. */
  4926. Camera.prototype.getArmRotation = function() {
  4927. var rot = {};
  4928. rot.horizontal = this.armRotation.horizontal;
  4929. rot.vertical = this.armRotation.vertical;
  4930. return rot;
  4931. };
  4932. /**
  4933. * Set the (normalized) length of the camera arm.
  4934. * @param {Number} length A length between 0.71 and 5.0
  4935. */
  4936. Camera.prototype.setArmLength = function(length) {
  4937. if (length === undefined)
  4938. return;
  4939. this.armLength = length;
  4940. // Radius must be larger than the corner of the graph,
  4941. // which has a distance of sqrt(0.5^2+0.5^2) = 0.71 from the center of the
  4942. // graph
  4943. if (this.armLength < 0.71) this.armLength = 0.71;
  4944. if (this.armLength > 5.0) this.armLength = 5.0;
  4945. this.calculateCameraOrientation();
  4946. };
  4947. /**
  4948. * Retrieve the arm length
  4949. * @return {Number} length
  4950. */
  4951. Camera.prototype.getArmLength = function() {
  4952. return this.armLength;
  4953. };
  4954. /**
  4955. * Retrieve the camera location
  4956. * @return {Point3d} cameraLocation
  4957. */
  4958. Camera.prototype.getCameraLocation = function() {
  4959. return this.cameraLocation;
  4960. };
  4961. /**
  4962. * Retrieve the camera rotation
  4963. * @return {Point3d} cameraRotation
  4964. */
  4965. Camera.prototype.getCameraRotation = function() {
  4966. return this.cameraRotation;
  4967. };
  4968. /**
  4969. * Calculate the location and rotation of the camera based on the
  4970. * position and orientation of the camera arm
  4971. */
  4972. Camera.prototype.calculateCameraOrientation = function() {
  4973. // calculate location of the camera
  4974. this.cameraLocation.x = this.armLocation.x - this.armLength * Math.sin(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical);
  4975. this.cameraLocation.y = this.armLocation.y - this.armLength * Math.cos(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical);
  4976. this.cameraLocation.z = this.armLocation.z + this.armLength * Math.sin(this.armRotation.vertical);
  4977. // calculate rotation of the camera
  4978. this.cameraRotation.x = Math.PI/2 - this.armRotation.vertical;
  4979. this.cameraRotation.y = 0;
  4980. this.cameraRotation.z = -this.armRotation.horizontal;
  4981. };
  4982. module.exports = Camera;
  4983. /***/ },
  4984. /* 8 */
  4985. /***/ function(module, exports, __webpack_require__) {
  4986. var DataView = __webpack_require__(4);
  4987. /**
  4988. * @class Filter
  4989. *
  4990. * @param {DataSet} data The google data table
  4991. * @param {Number} column The index of the column to be filtered
  4992. * @param {Graph} graph The graph
  4993. */
  4994. function Filter (data, column, graph) {
  4995. this.data = data;
  4996. this.column = column;
  4997. this.graph = graph; // the parent graph
  4998. this.index = undefined;
  4999. this.value = undefined;
  5000. // read all distinct values and select the first one
  5001. this.values = graph.getDistinctValues(data.get(), this.column);
  5002. // sort both numeric and string values correctly
  5003. this.values.sort(function (a, b) {
  5004. return a > b ? 1 : a < b ? -1 : 0;
  5005. });
  5006. if (this.values.length > 0) {
  5007. this.selectValue(0);
  5008. }
  5009. // create an array with the filtered datapoints. this will be loaded afterwards
  5010. this.dataPoints = [];
  5011. this.loaded = false;
  5012. this.onLoadCallback = undefined;
  5013. if (graph.animationPreload) {
  5014. this.loaded = false;
  5015. this.loadInBackground();
  5016. }
  5017. else {
  5018. this.loaded = true;
  5019. }
  5020. };
  5021. /**
  5022. * Return the label
  5023. * @return {string} label
  5024. */
  5025. Filter.prototype.isLoaded = function() {
  5026. return this.loaded;
  5027. };
  5028. /**
  5029. * Return the loaded progress
  5030. * @return {Number} percentage between 0 and 100
  5031. */
  5032. Filter.prototype.getLoadedProgress = function() {
  5033. var len = this.values.length;
  5034. var i = 0;
  5035. while (this.dataPoints[i]) {
  5036. i++;
  5037. }
  5038. return Math.round(i / len * 100);
  5039. };
  5040. /**
  5041. * Return the label
  5042. * @return {string} label
  5043. */
  5044. Filter.prototype.getLabel = function() {
  5045. return this.graph.filterLabel;
  5046. };
  5047. /**
  5048. * Return the columnIndex of the filter
  5049. * @return {Number} columnIndex
  5050. */
  5051. Filter.prototype.getColumn = function() {
  5052. return this.column;
  5053. };
  5054. /**
  5055. * Return the currently selected value. Returns undefined if there is no selection
  5056. * @return {*} value
  5057. */
  5058. Filter.prototype.getSelectedValue = function() {
  5059. if (this.index === undefined)
  5060. return undefined;
  5061. return this.values[this.index];
  5062. };
  5063. /**
  5064. * Retrieve all values of the filter
  5065. * @return {Array} values
  5066. */
  5067. Filter.prototype.getValues = function() {
  5068. return this.values;
  5069. };
  5070. /**
  5071. * Retrieve one value of the filter
  5072. * @param {Number} index
  5073. * @return {*} value
  5074. */
  5075. Filter.prototype.getValue = function(index) {
  5076. if (index >= this.values.length)
  5077. throw 'Error: index out of range';
  5078. return this.values[index];
  5079. };
  5080. /**
  5081. * Retrieve the (filtered) dataPoints for the currently selected filter index
  5082. * @param {Number} [index] (optional)
  5083. * @return {Array} dataPoints
  5084. */
  5085. Filter.prototype._getDataPoints = function(index) {
  5086. if (index === undefined)
  5087. index = this.index;
  5088. if (index === undefined)
  5089. return [];
  5090. var dataPoints;
  5091. if (this.dataPoints[index]) {
  5092. dataPoints = this.dataPoints[index];
  5093. }
  5094. else {
  5095. var f = {};
  5096. f.column = this.column;
  5097. f.value = this.values[index];
  5098. var dataView = new DataView(this.data,{filter: function (item) {return (item[f.column] == f.value);}}).get();
  5099. dataPoints = this.graph._getDataPoints(dataView);
  5100. this.dataPoints[index] = dataPoints;
  5101. }
  5102. return dataPoints;
  5103. };
  5104. /**
  5105. * Set a callback function when the filter is fully loaded.
  5106. */
  5107. Filter.prototype.setOnLoadCallback = function(callback) {
  5108. this.onLoadCallback = callback;
  5109. };
  5110. /**
  5111. * Add a value to the list with available values for this filter
  5112. * No double entries will be created.
  5113. * @param {Number} index
  5114. */
  5115. Filter.prototype.selectValue = function(index) {
  5116. if (index >= this.values.length)
  5117. throw 'Error: index out of range';
  5118. this.index = index;
  5119. this.value = this.values[index];
  5120. };
  5121. /**
  5122. * Load all filtered rows in the background one by one
  5123. * Start this method without providing an index!
  5124. */
  5125. Filter.prototype.loadInBackground = function(index) {
  5126. if (index === undefined)
  5127. index = 0;
  5128. var frame = this.graph.frame;
  5129. if (index < this.values.length) {
  5130. var dataPointsTemp = this._getDataPoints(index);
  5131. //this.graph.redrawInfo(); // TODO: not neat
  5132. // create a progress box
  5133. if (frame.progress === undefined) {
  5134. frame.progress = document.createElement('DIV');
  5135. frame.progress.style.position = 'absolute';
  5136. frame.progress.style.color = 'gray';
  5137. frame.appendChild(frame.progress);
  5138. }
  5139. var progress = this.getLoadedProgress();
  5140. frame.progress.innerHTML = 'Loading animation... ' + progress + '%';
  5141. // TODO: this is no nice solution...
  5142. frame.progress.style.bottom = 60 + 'px'; // TODO: use height of slider
  5143. frame.progress.style.left = 10 + 'px';
  5144. var me = this;
  5145. setTimeout(function() {me.loadInBackground(index+1);}, 10);
  5146. this.loaded = false;
  5147. }
  5148. else {
  5149. this.loaded = true;
  5150. // remove the progress box
  5151. if (frame.progress !== undefined) {
  5152. frame.removeChild(frame.progress);
  5153. frame.progress = undefined;
  5154. }
  5155. if (this.onLoadCallback)
  5156. this.onLoadCallback();
  5157. }
  5158. };
  5159. module.exports = Filter;
  5160. /***/ },
  5161. /* 9 */
  5162. /***/ function(module, exports, __webpack_require__) {
  5163. /**
  5164. * @prototype Point2d
  5165. * @param {Number} [x]
  5166. * @param {Number} [y]
  5167. */
  5168. function Point2d (x, y) {
  5169. this.x = x !== undefined ? x : 0;
  5170. this.y = y !== undefined ? y : 0;
  5171. }
  5172. module.exports = Point2d;
  5173. /***/ },
  5174. /* 10 */
  5175. /***/ function(module, exports, __webpack_require__) {
  5176. /**
  5177. * @prototype Point3d
  5178. * @param {Number} [x]
  5179. * @param {Number} [y]
  5180. * @param {Number} [z]
  5181. */
  5182. function Point3d(x, y, z) {
  5183. this.x = x !== undefined ? x : 0;
  5184. this.y = y !== undefined ? y : 0;
  5185. this.z = z !== undefined ? z : 0;
  5186. };
  5187. /**
  5188. * Subtract the two provided points, returns a-b
  5189. * @param {Point3d} a
  5190. * @param {Point3d} b
  5191. * @return {Point3d} a-b
  5192. */
  5193. Point3d.subtract = function(a, b) {
  5194. var sub = new Point3d();
  5195. sub.x = a.x - b.x;
  5196. sub.y = a.y - b.y;
  5197. sub.z = a.z - b.z;
  5198. return sub;
  5199. };
  5200. /**
  5201. * Add the two provided points, returns a+b
  5202. * @param {Point3d} a
  5203. * @param {Point3d} b
  5204. * @return {Point3d} a+b
  5205. */
  5206. Point3d.add = function(a, b) {
  5207. var sum = new Point3d();
  5208. sum.x = a.x + b.x;
  5209. sum.y = a.y + b.y;
  5210. sum.z = a.z + b.z;
  5211. return sum;
  5212. };
  5213. /**
  5214. * Calculate the average of two 3d points
  5215. * @param {Point3d} a
  5216. * @param {Point3d} b
  5217. * @return {Point3d} The average, (a+b)/2
  5218. */
  5219. Point3d.avg = function(a, b) {
  5220. return new Point3d(
  5221. (a.x + b.x) / 2,
  5222. (a.y + b.y) / 2,
  5223. (a.z + b.z) / 2
  5224. );
  5225. };
  5226. /**
  5227. * Calculate the cross product of the two provided points, returns axb
  5228. * Documentation: http://en.wikipedia.org/wiki/Cross_product
  5229. * @param {Point3d} a
  5230. * @param {Point3d} b
  5231. * @return {Point3d} cross product axb
  5232. */
  5233. Point3d.crossProduct = function(a, b) {
  5234. var crossproduct = new Point3d();
  5235. crossproduct.x = a.y * b.z - a.z * b.y;
  5236. crossproduct.y = a.z * b.x - a.x * b.z;
  5237. crossproduct.z = a.x * b.y - a.y * b.x;
  5238. return crossproduct;
  5239. };
  5240. /**
  5241. * Rtrieve the length of the vector (or the distance from this point to the origin
  5242. * @return {Number} length
  5243. */
  5244. Point3d.prototype.length = function() {
  5245. return Math.sqrt(
  5246. this.x * this.x +
  5247. this.y * this.y +
  5248. this.z * this.z
  5249. );
  5250. };
  5251. module.exports = Point3d;
  5252. /***/ },
  5253. /* 11 */
  5254. /***/ function(module, exports, __webpack_require__) {
  5255. var util = __webpack_require__(1);
  5256. /**
  5257. * @constructor Slider
  5258. *
  5259. * An html slider control with start/stop/prev/next buttons
  5260. * @param {Element} container The element where the slider will be created
  5261. * @param {Object} options Available options:
  5262. * {boolean} visible If true (default) the
  5263. * slider is visible.
  5264. */
  5265. function Slider(container, options) {
  5266. if (container === undefined) {
  5267. throw 'Error: No container element defined';
  5268. }
  5269. this.container = container;
  5270. this.visible = (options && options.visible != undefined) ? options.visible : true;
  5271. if (this.visible) {
  5272. this.frame = document.createElement('DIV');
  5273. //this.frame.style.backgroundColor = '#E5E5E5';
  5274. this.frame.style.width = '100%';
  5275. this.frame.style.position = 'relative';
  5276. this.container.appendChild(this.frame);
  5277. this.frame.prev = document.createElement('INPUT');
  5278. this.frame.prev.type = 'BUTTON';
  5279. this.frame.prev.value = 'Prev';
  5280. this.frame.appendChild(this.frame.prev);
  5281. this.frame.play = document.createElement('INPUT');
  5282. this.frame.play.type = 'BUTTON';
  5283. this.frame.play.value = 'Play';
  5284. this.frame.appendChild(this.frame.play);
  5285. this.frame.next = document.createElement('INPUT');
  5286. this.frame.next.type = 'BUTTON';
  5287. this.frame.next.value = 'Next';
  5288. this.frame.appendChild(this.frame.next);
  5289. this.frame.bar = document.createElement('INPUT');
  5290. this.frame.bar.type = 'BUTTON';
  5291. this.frame.bar.style.position = 'absolute';
  5292. this.frame.bar.style.border = '1px solid red';
  5293. this.frame.bar.style.width = '100px';
  5294. this.frame.bar.style.height = '6px';
  5295. this.frame.bar.style.borderRadius = '2px';
  5296. this.frame.bar.style.MozBorderRadius = '2px';
  5297. this.frame.bar.style.border = '1px solid #7F7F7F';
  5298. this.frame.bar.style.backgroundColor = '#E5E5E5';
  5299. this.frame.appendChild(this.frame.bar);
  5300. this.frame.slide = document.createElement('INPUT');
  5301. this.frame.slide.type = 'BUTTON';
  5302. this.frame.slide.style.margin = '0px';
  5303. this.frame.slide.value = ' ';
  5304. this.frame.slide.style.position = 'relative';
  5305. this.frame.slide.style.left = '-100px';
  5306. this.frame.appendChild(this.frame.slide);
  5307. // create events
  5308. var me = this;
  5309. this.frame.slide.onmousedown = function (event) {me._onMouseDown(event);};
  5310. this.frame.prev.onclick = function (event) {me.prev(event);};
  5311. this.frame.play.onclick = function (event) {me.togglePlay(event);};
  5312. this.frame.next.onclick = function (event) {me.next(event);};
  5313. }
  5314. this.onChangeCallback = undefined;
  5315. this.values = [];
  5316. this.index = undefined;
  5317. this.playTimeout = undefined;
  5318. this.playInterval = 1000; // milliseconds
  5319. this.playLoop = true;
  5320. }
  5321. /**
  5322. * Select the previous index
  5323. */
  5324. Slider.prototype.prev = function() {
  5325. var index = this.getIndex();
  5326. if (index > 0) {
  5327. index--;
  5328. this.setIndex(index);
  5329. }
  5330. };
  5331. /**
  5332. * Select the next index
  5333. */
  5334. Slider.prototype.next = function() {
  5335. var index = this.getIndex();
  5336. if (index < this.values.length - 1) {
  5337. index++;
  5338. this.setIndex(index);
  5339. }
  5340. };
  5341. /**
  5342. * Select the next index
  5343. */
  5344. Slider.prototype.playNext = function() {
  5345. var start = new Date();
  5346. var index = this.getIndex();
  5347. if (index < this.values.length - 1) {
  5348. index++;
  5349. this.setIndex(index);
  5350. }
  5351. else if (this.playLoop) {
  5352. // jump to the start
  5353. index = 0;
  5354. this.setIndex(index);
  5355. }
  5356. var end = new Date();
  5357. var diff = (end - start);
  5358. // calculate how much time it to to set the index and to execute the callback
  5359. // function.
  5360. var interval = Math.max(this.playInterval - diff, 0);
  5361. // document.title = diff // TODO: cleanup
  5362. var me = this;
  5363. this.playTimeout = setTimeout(function() {me.playNext();}, interval);
  5364. };
  5365. /**
  5366. * Toggle start or stop playing
  5367. */
  5368. Slider.prototype.togglePlay = function() {
  5369. if (this.playTimeout === undefined) {
  5370. this.play();
  5371. } else {
  5372. this.stop();
  5373. }
  5374. };
  5375. /**
  5376. * Start playing
  5377. */
  5378. Slider.prototype.play = function() {
  5379. // Test whether already playing
  5380. if (this.playTimeout) return;
  5381. this.playNext();
  5382. if (this.frame) {
  5383. this.frame.play.value = 'Stop';
  5384. }
  5385. };
  5386. /**
  5387. * Stop playing
  5388. */
  5389. Slider.prototype.stop = function() {
  5390. clearInterval(this.playTimeout);
  5391. this.playTimeout = undefined;
  5392. if (this.frame) {
  5393. this.frame.play.value = 'Play';
  5394. }
  5395. };
  5396. /**
  5397. * Set a callback function which will be triggered when the value of the
  5398. * slider bar has changed.
  5399. */
  5400. Slider.prototype.setOnChangeCallback = function(callback) {
  5401. this.onChangeCallback = callback;
  5402. };
  5403. /**
  5404. * Set the interval for playing the list
  5405. * @param {Number} interval The interval in milliseconds
  5406. */
  5407. Slider.prototype.setPlayInterval = function(interval) {
  5408. this.playInterval = interval;
  5409. };
  5410. /**
  5411. * Retrieve the current play interval
  5412. * @return {Number} interval The interval in milliseconds
  5413. */
  5414. Slider.prototype.getPlayInterval = function(interval) {
  5415. return this.playInterval;
  5416. };
  5417. /**
  5418. * Set looping on or off
  5419. * @pararm {boolean} doLoop If true, the slider will jump to the start when
  5420. * the end is passed, and will jump to the end
  5421. * when the start is passed.
  5422. */
  5423. Slider.prototype.setPlayLoop = function(doLoop) {
  5424. this.playLoop = doLoop;
  5425. };
  5426. /**
  5427. * Execute the onchange callback function
  5428. */
  5429. Slider.prototype.onChange = function() {
  5430. if (this.onChangeCallback !== undefined) {
  5431. this.onChangeCallback();
  5432. }
  5433. };
  5434. /**
  5435. * redraw the slider on the correct place
  5436. */
  5437. Slider.prototype.redraw = function() {
  5438. if (this.frame) {
  5439. // resize the bar
  5440. this.frame.bar.style.top = (this.frame.clientHeight/2 -
  5441. this.frame.bar.offsetHeight/2) + 'px';
  5442. this.frame.bar.style.width = (this.frame.clientWidth -
  5443. this.frame.prev.clientWidth -
  5444. this.frame.play.clientWidth -
  5445. this.frame.next.clientWidth - 30) + 'px';
  5446. // position the slider button
  5447. var left = this.indexToLeft(this.index);
  5448. this.frame.slide.style.left = (left) + 'px';
  5449. }
  5450. };
  5451. /**
  5452. * Set the list with values for the slider
  5453. * @param {Array} values A javascript array with values (any type)
  5454. */
  5455. Slider.prototype.setValues = function(values) {
  5456. this.values = values;
  5457. if (this.values.length > 0)
  5458. this.setIndex(0);
  5459. else
  5460. this.index = undefined;
  5461. };
  5462. /**
  5463. * Select a value by its index
  5464. * @param {Number} index
  5465. */
  5466. Slider.prototype.setIndex = function(index) {
  5467. if (index < this.values.length) {
  5468. this.index = index;
  5469. this.redraw();
  5470. this.onChange();
  5471. }
  5472. else {
  5473. throw 'Error: index out of range';
  5474. }
  5475. };
  5476. /**
  5477. * retrieve the index of the currently selected vaue
  5478. * @return {Number} index
  5479. */
  5480. Slider.prototype.getIndex = function() {
  5481. return this.index;
  5482. };
  5483. /**
  5484. * retrieve the currently selected value
  5485. * @return {*} value
  5486. */
  5487. Slider.prototype.get = function() {
  5488. return this.values[this.index];
  5489. };
  5490. Slider.prototype._onMouseDown = function(event) {
  5491. // only react on left mouse button down
  5492. var leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
  5493. if (!leftButtonDown) return;
  5494. this.startClientX = event.clientX;
  5495. this.startSlideX = parseFloat(this.frame.slide.style.left);
  5496. this.frame.style.cursor = 'move';
  5497. // add event listeners to handle moving the contents
  5498. // we store the function onmousemove and onmouseup in the graph, so we can
  5499. // remove the eventlisteners lateron in the function mouseUp()
  5500. var me = this;
  5501. this.onmousemove = function (event) {me._onMouseMove(event);};
  5502. this.onmouseup = function (event) {me._onMouseUp(event);};
  5503. util.addEventListener(document, 'mousemove', this.onmousemove);
  5504. util.addEventListener(document, 'mouseup', this.onmouseup);
  5505. util.preventDefault(event);
  5506. };
  5507. Slider.prototype.leftToIndex = function (left) {
  5508. var width = parseFloat(this.frame.bar.style.width) -
  5509. this.frame.slide.clientWidth - 10;
  5510. var x = left - 3;
  5511. var index = Math.round(x / width * (this.values.length-1));
  5512. if (index < 0) index = 0;
  5513. if (index > this.values.length-1) index = this.values.length-1;
  5514. return index;
  5515. };
  5516. Slider.prototype.indexToLeft = function (index) {
  5517. var width = parseFloat(this.frame.bar.style.width) -
  5518. this.frame.slide.clientWidth - 10;
  5519. var x = index / (this.values.length-1) * width;
  5520. var left = x + 3;
  5521. return left;
  5522. };
  5523. Slider.prototype._onMouseMove = function (event) {
  5524. var diff = event.clientX - this.startClientX;
  5525. var x = this.startSlideX + diff;
  5526. var index = this.leftToIndex(x);
  5527. this.setIndex(index);
  5528. util.preventDefault();
  5529. };
  5530. Slider.prototype._onMouseUp = function (event) {
  5531. this.frame.style.cursor = 'auto';
  5532. // remove event listeners
  5533. util.removeEventListener(document, 'mousemove', this.onmousemove);
  5534. util.removeEventListener(document, 'mouseup', this.onmouseup);
  5535. util.preventDefault();
  5536. };
  5537. module.exports = Slider;
  5538. /***/ },
  5539. /* 12 */
  5540. /***/ function(module, exports, __webpack_require__) {
  5541. /**
  5542. * @prototype StepNumber
  5543. * The class StepNumber is an iterator for Numbers. You provide a start and end
  5544. * value, and a best step size. StepNumber itself rounds to fixed values and
  5545. * a finds the step that best fits the provided step.
  5546. *
  5547. * If prettyStep is true, the step size is chosen as close as possible to the
  5548. * provided step, but being a round value like 1, 2, 5, 10, 20, 50, ....
  5549. *
  5550. * Example usage:
  5551. * var step = new StepNumber(0, 10, 2.5, true);
  5552. * step.start();
  5553. * while (!step.end()) {
  5554. * alert(step.getCurrent());
  5555. * step.next();
  5556. * }
  5557. *
  5558. * Version: 1.0
  5559. *
  5560. * @param {Number} start The start value
  5561. * @param {Number} end The end value
  5562. * @param {Number} step Optional. Step size. Must be a positive value.
  5563. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  5564. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  5565. */
  5566. function StepNumber(start, end, step, prettyStep) {
  5567. // set default values
  5568. this._start = 0;
  5569. this._end = 0;
  5570. this._step = 1;
  5571. this.prettyStep = true;
  5572. this.precision = 5;
  5573. this._current = 0;
  5574. this.setRange(start, end, step, prettyStep);
  5575. };
  5576. /**
  5577. * Set a new range: start, end and step.
  5578. *
  5579. * @param {Number} start The start value
  5580. * @param {Number} end The end value
  5581. * @param {Number} step Optional. Step size. Must be a positive value.
  5582. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  5583. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  5584. */
  5585. StepNumber.prototype.setRange = function(start, end, step, prettyStep) {
  5586. this._start = start ? start : 0;
  5587. this._end = end ? end : 0;
  5588. this.setStep(step, prettyStep);
  5589. };
  5590. /**
  5591. * Set a new step size
  5592. * @param {Number} step New step size. Must be a positive value
  5593. * @param {boolean} prettyStep Optional. If true, the provided step is rounded
  5594. * to a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  5595. */
  5596. StepNumber.prototype.setStep = function(step, prettyStep) {
  5597. if (step === undefined || step <= 0)
  5598. return;
  5599. if (prettyStep !== undefined)
  5600. this.prettyStep = prettyStep;
  5601. if (this.prettyStep === true)
  5602. this._step = StepNumber.calculatePrettyStep(step);
  5603. else
  5604. this._step = step;
  5605. };
  5606. /**
  5607. * Calculate a nice step size, closest to the desired step size.
  5608. * Returns a value in one of the ranges 1*10^n, 2*10^n, or 5*10^n, where n is an
  5609. * integer Number. For example 1, 2, 5, 10, 20, 50, etc...
  5610. * @param {Number} step Desired step size
  5611. * @return {Number} Nice step size
  5612. */
  5613. StepNumber.calculatePrettyStep = function (step) {
  5614. var log10 = function (x) {return Math.log(x) / Math.LN10;};
  5615. // try three steps (multiple of 1, 2, or 5
  5616. var step1 = Math.pow(10, Math.round(log10(step))),
  5617. step2 = 2 * Math.pow(10, Math.round(log10(step / 2))),
  5618. step5 = 5 * Math.pow(10, Math.round(log10(step / 5)));
  5619. // choose the best step (closest to minimum step)
  5620. var prettyStep = step1;
  5621. if (Math.abs(step2 - step) <= Math.abs(prettyStep - step)) prettyStep = step2;
  5622. if (Math.abs(step5 - step) <= Math.abs(prettyStep - step)) prettyStep = step5;
  5623. // for safety
  5624. if (prettyStep <= 0) {
  5625. prettyStep = 1;
  5626. }
  5627. return prettyStep;
  5628. };
  5629. /**
  5630. * returns the current value of the step
  5631. * @return {Number} current value
  5632. */
  5633. StepNumber.prototype.getCurrent = function () {
  5634. return parseFloat(this._current.toPrecision(this.precision));
  5635. };
  5636. /**
  5637. * returns the current step size
  5638. * @return {Number} current step size
  5639. */
  5640. StepNumber.prototype.getStep = function () {
  5641. return this._step;
  5642. };
  5643. /**
  5644. * Set the current value to the largest value smaller than start, which
  5645. * is a multiple of the step size
  5646. */
  5647. StepNumber.prototype.start = function() {
  5648. this._current = this._start - this._start % this._step;
  5649. };
  5650. /**
  5651. * Do a step, add the step size to the current value
  5652. */
  5653. StepNumber.prototype.next = function () {
  5654. this._current += this._step;
  5655. };
  5656. /**
  5657. * Returns true whether the end is reached
  5658. * @return {boolean} True if the current value has passed the end value.
  5659. */
  5660. StepNumber.prototype.end = function () {
  5661. return (this._current > this._end);
  5662. };
  5663. module.exports = StepNumber;
  5664. /***/ },
  5665. /* 13 */
  5666. /***/ function(module, exports, __webpack_require__) {
  5667. var Emitter = __webpack_require__(56);
  5668. var Hammer = __webpack_require__(45);
  5669. var util = __webpack_require__(1);
  5670. var DataSet = __webpack_require__(3);
  5671. var DataView = __webpack_require__(4);
  5672. var Range = __webpack_require__(17);
  5673. var Core = __webpack_require__(46);
  5674. var TimeAxis = __webpack_require__(35);
  5675. var CurrentTime = __webpack_require__(26);
  5676. var CustomTime = __webpack_require__(27);
  5677. var ItemSet = __webpack_require__(32);
  5678. /**
  5679. * Create a timeline visualization
  5680. * @param {HTMLElement} container
  5681. * @param {vis.DataSet | vis.DataView | Array | google.visualization.DataTable} [items]
  5682. * @param {vis.DataSet | vis.DataView | Array | google.visualization.DataTable} [groups]
  5683. * @param {Object} [options] See Timeline.setOptions for the available options.
  5684. * @constructor
  5685. * @extends Core
  5686. */
  5687. function Timeline (container, items, groups, options) {
  5688. if (!(this instanceof Timeline)) {
  5689. throw new SyntaxError('Constructor must be called with the new operator');
  5690. }
  5691. // if the third element is options, the forth is groups (optionally);
  5692. if (!(Array.isArray(groups) || groups instanceof DataSet || groups instanceof DataView) && groups instanceof Object) {
  5693. var forthArgument = options;
  5694. options = groups;
  5695. groups = forthArgument;
  5696. }
  5697. var me = this;
  5698. this.defaultOptions = {
  5699. start: null,
  5700. end: null,
  5701. autoResize: true,
  5702. orientation: 'bottom', // axis orientation: 'bottom', 'top', or 'both'
  5703. width: null,
  5704. height: null,
  5705. maxHeight: null,
  5706. minHeight: null
  5707. };
  5708. this.options = util.deepExtend({}, this.defaultOptions);
  5709. // Create the DOM, props, and emitter
  5710. this._create(container);
  5711. // all components listed here will be repainted automatically
  5712. this.components = [];
  5713. this.body = {
  5714. dom: this.dom,
  5715. domProps: this.props,
  5716. emitter: {
  5717. on: this.on.bind(this),
  5718. off: this.off.bind(this),
  5719. emit: this.emit.bind(this)
  5720. },
  5721. hiddenDates: [],
  5722. util: {
  5723. getScale: function () {
  5724. return me.timeAxis.step.scale;
  5725. },
  5726. getStep: function () {
  5727. return me.timeAxis.step.step;
  5728. },
  5729. toScreen: me._toScreen.bind(me),
  5730. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  5731. toTime: me._toTime.bind(me),
  5732. toGlobalTime : me._toGlobalTime.bind(me)
  5733. }
  5734. };
  5735. // range
  5736. this.range = new Range(this.body);
  5737. this.components.push(this.range);
  5738. this.body.range = this.range;
  5739. // time axis
  5740. this.timeAxis = new TimeAxis(this.body);
  5741. this.timeAxis2 = null; // used in case of orientation option 'both'
  5742. this.components.push(this.timeAxis);
  5743. // current time bar
  5744. this.currentTime = new CurrentTime(this.body);
  5745. this.components.push(this.currentTime);
  5746. // custom time bar
  5747. // Note: time bar will be attached in this.setOptions when selected
  5748. this.customTime = new CustomTime(this.body);
  5749. this.components.push(this.customTime);
  5750. // item set
  5751. // this.itemSet = new ItemSet(this.body);
  5752. this.itemSet = new ItemSet(this.body, this.options);
  5753. this.components.push(this.itemSet);
  5754. this.itemsData = null; // DataSet
  5755. this.groupsData = null; // DataSet
  5756. this.on('tap', function (event) {
  5757. me.emit('click', me.getEventProperties(event))
  5758. });
  5759. this.on('doubletap', function (event) {
  5760. me.emit('doubleClick', me.getEventProperties(event))
  5761. });
  5762. this.dom.root.oncontextmenu = function (event) {
  5763. me.emit('contextmenu', me.getEventProperties(event))
  5764. };
  5765. // apply options
  5766. if (options) {
  5767. this.setOptions(options);
  5768. }
  5769. // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
  5770. if (groups) {
  5771. this.setGroups(groups);
  5772. }
  5773. // create itemset
  5774. if (items) {
  5775. this.setItems(items);
  5776. }
  5777. else {
  5778. this._redraw();
  5779. }
  5780. }
  5781. // Extend the functionality from Core
  5782. Timeline.prototype = new Core();
  5783. /**
  5784. * Force a redraw. The size of all items will be recalculated.
  5785. * Can be useful to manually redraw when option autoResize=false and the window
  5786. * has been resized, or when the items CSS has been changed.
  5787. */
  5788. Timeline.prototype.redraw = function() {
  5789. this.itemSet && this.itemSet.markDirty({refreshItems: true});
  5790. this._redraw();
  5791. };
  5792. /**
  5793. * Set items
  5794. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  5795. */
  5796. Timeline.prototype.setItems = function(items) {
  5797. var initialLoad = (this.itemsData == null);
  5798. // convert to type DataSet when needed
  5799. var newDataSet;
  5800. if (!items) {
  5801. newDataSet = null;
  5802. }
  5803. else if (items instanceof DataSet || items instanceof DataView) {
  5804. newDataSet = items;
  5805. }
  5806. else {
  5807. // turn an array into a dataset
  5808. newDataSet = new DataSet(items, {
  5809. type: {
  5810. start: 'Date',
  5811. end: 'Date'
  5812. }
  5813. });
  5814. }
  5815. // set items
  5816. this.itemsData = newDataSet;
  5817. this.itemSet && this.itemSet.setItems(newDataSet);
  5818. if (initialLoad) {
  5819. if (this.options.start != undefined || this.options.end != undefined) {
  5820. if (this.options.start == undefined || this.options.end == undefined) {
  5821. var dataRange = this._getDataRange();
  5822. }
  5823. var start = this.options.start != undefined ? this.options.start : dataRange.start;
  5824. var end = this.options.end != undefined ? this.options.end : dataRange.end;
  5825. this.setWindow(start, end, {animate: false});
  5826. }
  5827. else {
  5828. this.fit({animate: false});
  5829. }
  5830. }
  5831. };
  5832. /**
  5833. * Set groups
  5834. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  5835. */
  5836. Timeline.prototype.setGroups = function(groups) {
  5837. // convert to type DataSet when needed
  5838. var newDataSet;
  5839. if (!groups) {
  5840. newDataSet = null;
  5841. }
  5842. else if (groups instanceof DataSet || groups instanceof DataView) {
  5843. newDataSet = groups;
  5844. }
  5845. else {
  5846. // turn an array into a dataset
  5847. newDataSet = new DataSet(groups);
  5848. }
  5849. this.groupsData = newDataSet;
  5850. this.itemSet.setGroups(newDataSet);
  5851. };
  5852. /**
  5853. * Set selected items by their id. Replaces the current selection
  5854. * Unknown id's are silently ignored.
  5855. * @param {string[] | string} [ids] An array with zero or more id's of the items to be
  5856. * selected. If ids is an empty array, all items will be
  5857. * unselected.
  5858. * @param {Object} [options] Available options:
  5859. * `focus: boolean`
  5860. * If true, focus will be set to the selected item(s)
  5861. * `animate: boolean | number`
  5862. * If true (default), the range is animated
  5863. * smoothly to the new window.
  5864. * If a number, the number is taken as duration
  5865. * for the animation. Default duration is 500 ms.
  5866. * Only applicable when option focus is true.
  5867. */
  5868. Timeline.prototype.setSelection = function(ids, options) {
  5869. this.itemSet && this.itemSet.setSelection(ids);
  5870. if (options && options.focus) {
  5871. this.focus(ids, options);
  5872. }
  5873. };
  5874. /**
  5875. * Get the selected items by their id
  5876. * @return {Array} ids The ids of the selected items
  5877. */
  5878. Timeline.prototype.getSelection = function() {
  5879. return this.itemSet && this.itemSet.getSelection() || [];
  5880. };
  5881. /**
  5882. * Adjust the visible window such that the selected item (or multiple items)
  5883. * are centered on screen.
  5884. * @param {String | String[]} id An item id or array with item ids
  5885. * @param {Object} [options] Available options:
  5886. * `animate: boolean | number`
  5887. * If true (default), the range is animated
  5888. * smoothly to the new window.
  5889. * If a number, the number is taken as duration
  5890. * for the animation. Default duration is 500 ms.
  5891. * Only applicable when option focus is true
  5892. */
  5893. Timeline.prototype.focus = function(id, options) {
  5894. if (!this.itemsData || id == undefined) return;
  5895. var ids = Array.isArray(id) ? id : [id];
  5896. // get the specified item(s)
  5897. var itemsData = this.itemsData.getDataSet().get(ids, {
  5898. type: {
  5899. start: 'Date',
  5900. end: 'Date'
  5901. }
  5902. });
  5903. // calculate minimum start and maximum end of specified items
  5904. var start = null;
  5905. var end = null;
  5906. itemsData.forEach(function (itemData) {
  5907. var s = itemData.start.valueOf();
  5908. var e = 'end' in itemData ? itemData.end.valueOf() : itemData.start.valueOf();
  5909. if (start === null || s < start) {
  5910. start = s;
  5911. }
  5912. if (end === null || e > end) {
  5913. end = e;
  5914. }
  5915. });
  5916. if (start !== null && end !== null) {
  5917. // calculate the new middle and interval for the window
  5918. var middle = (start + end) / 2;
  5919. var interval = Math.max((this.range.end - this.range.start), (end - start) * 1.1);
  5920. var animate = (options && options.animate !== undefined) ? options.animate : true;
  5921. this.range.setRange(middle - interval / 2, middle + interval / 2, animate);
  5922. }
  5923. };
  5924. /**
  5925. * Get the data range of the item set.
  5926. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  5927. * When no minimum is found, min==null
  5928. * When no maximum is found, max==null
  5929. */
  5930. Timeline.prototype.getItemRange = function() {
  5931. // calculate min from start filed
  5932. var dataset = this.itemsData.getDataSet(),
  5933. min = null,
  5934. max = null;
  5935. if (dataset) {
  5936. // calculate the minimum value of the field 'start'
  5937. var minItem = dataset.min('start');
  5938. min = minItem ? util.convert(minItem.start, 'Date').valueOf() : null;
  5939. // Note: we convert first to Date and then to number because else
  5940. // a conversion from ISODate to Number will fail
  5941. // calculate maximum value of fields 'start' and 'end'
  5942. var maxStartItem = dataset.max('start');
  5943. if (maxStartItem) {
  5944. max = util.convert(maxStartItem.start, 'Date').valueOf();
  5945. }
  5946. var maxEndItem = dataset.max('end');
  5947. if (maxEndItem) {
  5948. if (max == null) {
  5949. max = util.convert(maxEndItem.end, 'Date').valueOf();
  5950. }
  5951. else {
  5952. max = Math.max(max, util.convert(maxEndItem.end, 'Date').valueOf());
  5953. }
  5954. }
  5955. }
  5956. return {
  5957. min: (min != null) ? new Date(min) : null,
  5958. max: (max != null) ? new Date(max) : null
  5959. };
  5960. };
  5961. /**
  5962. * Generate Timeline related information from an event
  5963. * @param {Event} event
  5964. * @return {Object} An object with related information, like on which area
  5965. * The event happened, whether clicked on an item, etc.
  5966. */
  5967. Timeline.prototype.getEventProperties = function (event) {
  5968. var item = this.itemSet.itemFromTarget(event);
  5969. var group = this.itemSet.groupFromTarget(event);
  5970. var pageX = event.gesture ? event.gesture.center.pageX : event.pageX;
  5971. var pageY = event.gesture ? event.gesture.center.pageY : event.pageY;
  5972. var x = pageX - util.getAbsoluteLeft(this.dom.centerContainer);
  5973. var y = pageY - util.getAbsoluteTop(this.dom.centerContainer);
  5974. var snap = this.itemSet.options.snap || null;
  5975. var scale = this.body.util.getScale();
  5976. var step = this.body.util.getStep();
  5977. var time = this._toTime(x);
  5978. var snappedTime = snap ? snap(time, scale, step) : time;
  5979. var element = util.getTarget(event);
  5980. var what = null;
  5981. if (item != null) {what = 'item';}
  5982. else if (util.hasParent(element, this.timeAxis.dom.foreground)) {what = 'axis';}
  5983. else if (this.timeAxis2 && util.hasParent(element, this.timeAxis2.dom.foreground)) {what = 'axis';}
  5984. else if (util.hasParent(element, this.itemSet.dom.labelSet)) {what = 'group-label';}
  5985. else if (util.hasParent(element, this.customTime.bar)) {what = 'custom-time';} // TODO: fix for multiple custom time bars
  5986. else if (util.hasParent(element, this.currentTime.bar)) {what = 'current-time';}
  5987. else if (util.hasParent(element, this.dom.center)) {what = 'background';}
  5988. return {
  5989. event: event,
  5990. item: item ? item.id : null,
  5991. group: group ? group.groupId : null,
  5992. what: what,
  5993. pageX: pageX,
  5994. pageY: pageY,
  5995. x: x,
  5996. y: y,
  5997. time: time,
  5998. snappedTime: snappedTime
  5999. }
  6000. };
  6001. module.exports = Timeline;
  6002. /***/ },
  6003. /* 14 */
  6004. /***/ function(module, exports, __webpack_require__) {
  6005. var Emitter = __webpack_require__(56);
  6006. var Hammer = __webpack_require__(45);
  6007. var util = __webpack_require__(1);
  6008. var DataSet = __webpack_require__(3);
  6009. var DataView = __webpack_require__(4);
  6010. var Range = __webpack_require__(17);
  6011. var Core = __webpack_require__(46);
  6012. var TimeAxis = __webpack_require__(35);
  6013. var CurrentTime = __webpack_require__(26);
  6014. var CustomTime = __webpack_require__(27);
  6015. var LineGraph = __webpack_require__(34);
  6016. /**
  6017. * Create a timeline visualization
  6018. * @param {HTMLElement} container
  6019. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  6020. * @param {Object} [options] See Graph2d.setOptions for the available options.
  6021. * @constructor
  6022. * @extends Core
  6023. */
  6024. function Graph2d (container, items, groups, options) {
  6025. // if the third element is options, the forth is groups (optionally);
  6026. if (!(Array.isArray(groups) || groups instanceof DataSet) && groups instanceof Object) {
  6027. var forthArgument = options;
  6028. options = groups;
  6029. groups = forthArgument;
  6030. }
  6031. var me = this;
  6032. this.defaultOptions = {
  6033. start: null,
  6034. end: null,
  6035. autoResize: true,
  6036. orientation: 'bottom',
  6037. width: null,
  6038. height: null,
  6039. maxHeight: null,
  6040. minHeight: null
  6041. };
  6042. this.options = util.deepExtend({}, this.defaultOptions);
  6043. // Create the DOM, props, and emitter
  6044. this._create(container);
  6045. // all components listed here will be repainted automatically
  6046. this.components = [];
  6047. this.body = {
  6048. dom: this.dom,
  6049. domProps: this.props,
  6050. emitter: {
  6051. on: this.on.bind(this),
  6052. off: this.off.bind(this),
  6053. emit: this.emit.bind(this)
  6054. },
  6055. hiddenDates: [],
  6056. util: {
  6057. toScreen: me._toScreen.bind(me),
  6058. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  6059. toTime: me._toTime.bind(me),
  6060. toGlobalTime : me._toGlobalTime.bind(me)
  6061. }
  6062. };
  6063. // range
  6064. this.range = new Range(this.body);
  6065. this.components.push(this.range);
  6066. this.body.range = this.range;
  6067. // time axis
  6068. this.timeAxis = new TimeAxis(this.body);
  6069. this.components.push(this.timeAxis);
  6070. //this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
  6071. // current time bar
  6072. this.currentTime = new CurrentTime(this.body);
  6073. this.components.push(this.currentTime);
  6074. // custom time bar
  6075. // Note: time bar will be attached in this.setOptions when selected
  6076. this.customTime = new CustomTime(this.body);
  6077. this.components.push(this.customTime);
  6078. // item set
  6079. this.linegraph = new LineGraph(this.body);
  6080. this.components.push(this.linegraph);
  6081. this.itemsData = null; // DataSet
  6082. this.groupsData = null; // DataSet
  6083. this.on('tap', function (event) {
  6084. me.emit('click', me.getEventProperties(event))
  6085. });
  6086. this.on('doubletap', function (event) {
  6087. me.emit('doubleClick', me.getEventProperties(event))
  6088. });
  6089. this.dom.root.oncontextmenu = function (event) {
  6090. me.emit('contextmenu', me.getEventProperties(event))
  6091. };
  6092. // apply options
  6093. if (options) {
  6094. this.setOptions(options);
  6095. }
  6096. // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
  6097. if (groups) {
  6098. this.setGroups(groups);
  6099. }
  6100. // create itemset
  6101. if (items) {
  6102. this.setItems(items);
  6103. }
  6104. else {
  6105. this._redraw();
  6106. }
  6107. }
  6108. // Extend the functionality from Core
  6109. Graph2d.prototype = new Core();
  6110. /**
  6111. * Set items
  6112. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  6113. */
  6114. Graph2d.prototype.setItems = function(items) {
  6115. var initialLoad = (this.itemsData == null);
  6116. // convert to type DataSet when needed
  6117. var newDataSet;
  6118. if (!items) {
  6119. newDataSet = null;
  6120. }
  6121. else if (items instanceof DataSet || items instanceof DataView) {
  6122. newDataSet = items;
  6123. }
  6124. else {
  6125. // turn an array into a dataset
  6126. newDataSet = new DataSet(items, {
  6127. type: {
  6128. start: 'Date',
  6129. end: 'Date'
  6130. }
  6131. });
  6132. }
  6133. // set items
  6134. this.itemsData = newDataSet;
  6135. this.linegraph && this.linegraph.setItems(newDataSet);
  6136. if (initialLoad) {
  6137. if (this.options.start != undefined || this.options.end != undefined) {
  6138. var start = this.options.start != undefined ? this.options.start : null;
  6139. var end = this.options.end != undefined ? this.options.end : null;
  6140. this.setWindow(start, end, {animate: false});
  6141. }
  6142. else {
  6143. this.fit({animate: false});
  6144. }
  6145. }
  6146. };
  6147. /**
  6148. * Set groups
  6149. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  6150. */
  6151. Graph2d.prototype.setGroups = function(groups) {
  6152. // convert to type DataSet when needed
  6153. var newDataSet;
  6154. if (!groups) {
  6155. newDataSet = null;
  6156. }
  6157. else if (groups instanceof DataSet || groups instanceof DataView) {
  6158. newDataSet = groups;
  6159. }
  6160. else {
  6161. // turn an array into a dataset
  6162. newDataSet = new DataSet(groups);
  6163. }
  6164. this.groupsData = newDataSet;
  6165. this.linegraph.setGroups(newDataSet);
  6166. };
  6167. /**
  6168. * 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).
  6169. * @param groupId
  6170. * @param width
  6171. * @param height
  6172. */
  6173. Graph2d.prototype.getLegend = function(groupId, width, height) {
  6174. if (width === undefined) {width = 15;}
  6175. if (height === undefined) {height = 15;}
  6176. if (this.linegraph.groups[groupId] !== undefined) {
  6177. return this.linegraph.groups[groupId].getLegend(width,height);
  6178. }
  6179. else {
  6180. return "cannot find group:" + groupId;
  6181. }
  6182. };
  6183. /**
  6184. * This checks if the visible option of the supplied group (by ID) is true or false.
  6185. * @param groupId
  6186. * @returns {*}
  6187. */
  6188. Graph2d.prototype.isGroupVisible = function(groupId) {
  6189. if (this.linegraph.groups[groupId] !== undefined) {
  6190. return (this.linegraph.groups[groupId].visible && (this.linegraph.options.groups.visibility[groupId] === undefined || this.linegraph.options.groups.visibility[groupId] == true));
  6191. }
  6192. else {
  6193. return false;
  6194. }
  6195. };
  6196. /**
  6197. * Get the data range of the item set.
  6198. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  6199. * When no minimum is found, min==null
  6200. * When no maximum is found, max==null
  6201. */
  6202. Graph2d.prototype.getItemRange = function() {
  6203. var min = null;
  6204. var max = null;
  6205. // calculate min from start filed
  6206. for (var groupId in this.linegraph.groups) {
  6207. if (this.linegraph.groups.hasOwnProperty(groupId)) {
  6208. if (this.linegraph.groups[groupId].visible == true) {
  6209. for (var i = 0; i < this.linegraph.groups[groupId].itemsData.length; i++) {
  6210. var item = this.linegraph.groups[groupId].itemsData[i];
  6211. var value = util.convert(item.x, 'Date').valueOf();
  6212. min = min == null ? value : min > value ? value : min;
  6213. max = max == null ? value : max < value ? value : max;
  6214. }
  6215. }
  6216. }
  6217. }
  6218. return {
  6219. min: (min != null) ? new Date(min) : null,
  6220. max: (max != null) ? new Date(max) : null
  6221. };
  6222. };
  6223. /**
  6224. * Generate Timeline related information from an event
  6225. * @param {Event} event
  6226. * @return {Object} An object with related information, like on which area
  6227. * The event happened, whether clicked on an item, etc.
  6228. */
  6229. Graph2d.prototype.getEventProperties = function (event) {
  6230. var pageX = event.gesture ? event.gesture.center.pageX : event.pageX;
  6231. var pageY = event.gesture ? event.gesture.center.pageY : event.pageY;
  6232. var x = pageX - util.getAbsoluteLeft(this.dom.centerContainer);
  6233. var y = pageY - util.getAbsoluteTop(this.dom.centerContainer);
  6234. var time = this._toTime(x);
  6235. var element = util.getTarget(event);
  6236. var what = null;
  6237. if (util.hasParent(element, this.timeAxis.dom.foreground)) {what = 'axis';}
  6238. else if (this.timeAxis2 && util.hasParent(element, this.timeAxis2.dom.foreground)) {what = 'axis';}
  6239. else if (util.hasParent(element, this.linegraph.yAxisLeft.dom.frame)) {what = 'data-axis';}
  6240. else if (util.hasParent(element, this.linegraph.yAxisRight.dom.frame)) {what = 'data-axis';}
  6241. else if (util.hasParent(element, this.linegraph.legendLeft.dom.frame)) {what = 'legend';}
  6242. else if (util.hasParent(element, this.linegraph.legendRight.dom.frame)) {what = 'legend';}
  6243. else if (util.hasParent(element, this.customTime.bar)) {what = 'custom-time';} // TODO: fix for multiple custom time bars
  6244. else if (util.hasParent(element, this.currentTime.bar)) {what = 'current-time';}
  6245. else if (util.hasParent(element, this.dom.center)) {what = 'background';}
  6246. var value = [];
  6247. var yAxisLeft = this.linegraph.yAxisLeft;
  6248. var yAxisRight = this.linegraph.yAxisRight;
  6249. if (!yAxisLeft.hidden) {
  6250. value.push(yAxisLeft.screenToValue(y));
  6251. }
  6252. if (!yAxisRight.hidden) {
  6253. value.push(yAxisRight.screenToValue(y));
  6254. }
  6255. return {
  6256. event: event,
  6257. what: what,
  6258. pageX: pageX,
  6259. pageY: pageY,
  6260. x: x,
  6261. y: y,
  6262. time: time,
  6263. value: value
  6264. }
  6265. };
  6266. module.exports = Graph2d;
  6267. /***/ },
  6268. /* 15 */
  6269. /***/ function(module, exports, __webpack_require__) {
  6270. /**
  6271. * Created by Alex on 10/3/2014.
  6272. */
  6273. var moment = __webpack_require__(44);
  6274. /**
  6275. * used in Core to convert the options into a volatile variable
  6276. *
  6277. * @param Core
  6278. */
  6279. exports.convertHiddenOptions = function(body, hiddenDates) {
  6280. body.hiddenDates = [];
  6281. if (hiddenDates) {
  6282. if (Array.isArray(hiddenDates) == true) {
  6283. for (var i = 0; i < hiddenDates.length; i++) {
  6284. if (hiddenDates[i].repeat === undefined) {
  6285. var dateItem = {};
  6286. dateItem.start = moment(hiddenDates[i].start).toDate().valueOf();
  6287. dateItem.end = moment(hiddenDates[i].end).toDate().valueOf();
  6288. body.hiddenDates.push(dateItem);
  6289. }
  6290. }
  6291. body.hiddenDates.sort(function (a, b) {
  6292. return a.start - b.start;
  6293. }); // sort by start time
  6294. }
  6295. }
  6296. };
  6297. /**
  6298. * create new entrees for the repeating hidden dates
  6299. * @param body
  6300. * @param hiddenDates
  6301. */
  6302. exports.updateHiddenDates = function (body, hiddenDates) {
  6303. if (hiddenDates && body.domProps.centerContainer.width !== undefined) {
  6304. exports.convertHiddenOptions(body, hiddenDates);
  6305. var start = moment(body.range.start);
  6306. var end = moment(body.range.end);
  6307. var totalRange = (body.range.end - body.range.start);
  6308. var pixelTime = totalRange / body.domProps.centerContainer.width;
  6309. for (var i = 0; i < hiddenDates.length; i++) {
  6310. if (hiddenDates[i].repeat !== undefined) {
  6311. var startDate = moment(hiddenDates[i].start);
  6312. var endDate = moment(hiddenDates[i].end);
  6313. if (startDate._d == "Invalid Date") {
  6314. throw new Error("Supplied start date is not valid: " + hiddenDates[i].start);
  6315. }
  6316. if (endDate._d == "Invalid Date") {
  6317. throw new Error("Supplied end date is not valid: " + hiddenDates[i].end);
  6318. }
  6319. var duration = endDate - startDate;
  6320. if (duration >= 4 * pixelTime) {
  6321. var offset = 0;
  6322. var runUntil = end.clone();
  6323. switch (hiddenDates[i].repeat) {
  6324. case "daily": // case of time
  6325. if (startDate.day() != endDate.day()) {
  6326. offset = 1;
  6327. }
  6328. startDate.dayOfYear(start.dayOfYear());
  6329. startDate.year(start.year());
  6330. startDate.subtract(7,'days');
  6331. endDate.dayOfYear(start.dayOfYear());
  6332. endDate.year(start.year());
  6333. endDate.subtract(7 - offset,'days');
  6334. runUntil.add(1, 'weeks');
  6335. break;
  6336. case "weekly":
  6337. var dayOffset = endDate.diff(startDate,'days')
  6338. var day = startDate.day();
  6339. // set the start date to the range.start
  6340. startDate.date(start.date());
  6341. startDate.month(start.month());
  6342. startDate.year(start.year());
  6343. endDate = startDate.clone();
  6344. // force
  6345. startDate.day(day);
  6346. endDate.day(day);
  6347. endDate.add(dayOffset,'days');
  6348. startDate.subtract(1,'weeks');
  6349. endDate.subtract(1,'weeks');
  6350. runUntil.add(1, 'weeks');
  6351. break
  6352. case "monthly":
  6353. if (startDate.month() != endDate.month()) {
  6354. offset = 1;
  6355. }
  6356. startDate.month(start.month());
  6357. startDate.year(start.year());
  6358. startDate.subtract(1,'months');
  6359. endDate.month(start.month());
  6360. endDate.year(start.year());
  6361. endDate.subtract(1,'months');
  6362. endDate.add(offset,'months');
  6363. runUntil.add(1, 'months');
  6364. break;
  6365. case "yearly":
  6366. if (startDate.year() != endDate.year()) {
  6367. offset = 1;
  6368. }
  6369. startDate.year(start.year());
  6370. startDate.subtract(1,'years');
  6371. endDate.year(start.year());
  6372. endDate.subtract(1,'years');
  6373. endDate.add(offset,'years');
  6374. runUntil.add(1, 'years');
  6375. break;
  6376. default:
  6377. console.log("Wrong repeat format, allowed are: daily, weekly, monthly, yearly. Given:", hiddenDates[i].repeat);
  6378. return;
  6379. }
  6380. while (startDate < runUntil) {
  6381. body.hiddenDates.push({start: startDate.valueOf(), end: endDate.valueOf()});
  6382. switch (hiddenDates[i].repeat) {
  6383. case "daily":
  6384. startDate.add(1, 'days');
  6385. endDate.add(1, 'days');
  6386. break;
  6387. case "weekly":
  6388. startDate.add(1, 'weeks');
  6389. endDate.add(1, 'weeks');
  6390. break
  6391. case "monthly":
  6392. startDate.add(1, 'months');
  6393. endDate.add(1, 'months');
  6394. break;
  6395. case "yearly":
  6396. startDate.add(1, 'y');
  6397. endDate.add(1, 'y');
  6398. break;
  6399. default:
  6400. console.log("Wrong repeat format, allowed are: daily, weekly, monthly, yearly. Given:", hiddenDates[i].repeat);
  6401. return;
  6402. }
  6403. }
  6404. body.hiddenDates.push({start: startDate.valueOf(), end: endDate.valueOf()});
  6405. }
  6406. }
  6407. }
  6408. // remove duplicates, merge where possible
  6409. exports.removeDuplicates(body);
  6410. // ensure the new positions are not on hidden dates
  6411. var startHidden = exports.isHidden(body.range.start, body.hiddenDates);
  6412. var endHidden = exports.isHidden(body.range.end,body.hiddenDates);
  6413. var rangeStart = body.range.start;
  6414. var rangeEnd = body.range.end;
  6415. if (startHidden.hidden == true) {rangeStart = body.range.startToFront == true ? startHidden.startDate - 1 : startHidden.endDate + 1;}
  6416. if (endHidden.hidden == true) {rangeEnd = body.range.endToFront == true ? endHidden.startDate - 1 : endHidden.endDate + 1;}
  6417. if (startHidden.hidden == true || endHidden.hidden == true) {
  6418. body.range._applyRange(rangeStart, rangeEnd);
  6419. }
  6420. }
  6421. }
  6422. /**
  6423. * remove duplicates from the hidden dates list. Duplicates are evil. They mess everything up.
  6424. * Scales with N^2
  6425. * @param body
  6426. */
  6427. exports.removeDuplicates = function(body) {
  6428. var hiddenDates = body.hiddenDates;
  6429. var safeDates = [];
  6430. for (var i = 0; i < hiddenDates.length; i++) {
  6431. for (var j = 0; j < hiddenDates.length; j++) {
  6432. if (i != j && hiddenDates[j].remove != true && hiddenDates[i].remove != true) {
  6433. // j inside i
  6434. if (hiddenDates[j].start >= hiddenDates[i].start && hiddenDates[j].end <= hiddenDates[i].end) {
  6435. hiddenDates[j].remove = true;
  6436. }
  6437. // j start inside i
  6438. else if (hiddenDates[j].start >= hiddenDates[i].start && hiddenDates[j].start <= hiddenDates[i].end) {
  6439. hiddenDates[i].end = hiddenDates[j].end;
  6440. hiddenDates[j].remove = true;
  6441. }
  6442. // j end inside i
  6443. else if (hiddenDates[j].end >= hiddenDates[i].start && hiddenDates[j].end <= hiddenDates[i].end) {
  6444. hiddenDates[i].start = hiddenDates[j].start;
  6445. hiddenDates[j].remove = true;
  6446. }
  6447. }
  6448. }
  6449. }
  6450. for (var i = 0; i < hiddenDates.length; i++) {
  6451. if (hiddenDates[i].remove !== true) {
  6452. safeDates.push(hiddenDates[i]);
  6453. }
  6454. }
  6455. body.hiddenDates = safeDates;
  6456. body.hiddenDates.sort(function (a, b) {
  6457. return a.start - b.start;
  6458. }); // sort by start time
  6459. }
  6460. exports.printDates = function(dates) {
  6461. for (var i =0; i < dates.length; i++) {
  6462. console.log(i, new Date(dates[i].start),new Date(dates[i].end), dates[i].start, dates[i].end, dates[i].remove);
  6463. }
  6464. }
  6465. /**
  6466. * Used in TimeStep to avoid the hidden times.
  6467. * @param timeStep
  6468. * @param previousTime
  6469. */
  6470. exports.stepOverHiddenDates = function(timeStep, previousTime) {
  6471. var stepInHidden = false;
  6472. var currentValue = timeStep.current.valueOf();
  6473. for (var i = 0; i < timeStep.hiddenDates.length; i++) {
  6474. var startDate = timeStep.hiddenDates[i].start;
  6475. var endDate = timeStep.hiddenDates[i].end;
  6476. if (currentValue >= startDate && currentValue < endDate) {
  6477. stepInHidden = true;
  6478. break;
  6479. }
  6480. }
  6481. if (stepInHidden == true && currentValue < timeStep._end.valueOf() && currentValue != previousTime) {
  6482. var prevValue = moment(previousTime);
  6483. var newValue = moment(endDate);
  6484. //check if the next step should be major
  6485. if (prevValue.year() != newValue.year()) {timeStep.switchedYear = true;}
  6486. else if (prevValue.month() != newValue.month()) {timeStep.switchedMonth = true;}
  6487. else if (prevValue.dayOfYear() != newValue.dayOfYear()) {timeStep.switchedDay = true;}
  6488. timeStep.current = newValue.toDate();
  6489. }
  6490. };
  6491. ///**
  6492. // * Used in TimeStep to avoid the hidden times.
  6493. // * @param timeStep
  6494. // * @param previousTime
  6495. // */
  6496. //exports.checkFirstStep = function(timeStep) {
  6497. // var stepInHidden = false;
  6498. // var currentValue = timeStep.current.valueOf();
  6499. // for (var i = 0; i < timeStep.hiddenDates.length; i++) {
  6500. // var startDate = timeStep.hiddenDates[i].start;
  6501. // var endDate = timeStep.hiddenDates[i].end;
  6502. // if (currentValue >= startDate && currentValue < endDate) {
  6503. // stepInHidden = true;
  6504. // break;
  6505. // }
  6506. // }
  6507. //
  6508. // if (stepInHidden == true && currentValue <= timeStep._end.valueOf()) {
  6509. // var newValue = moment(endDate);
  6510. // timeStep.current = newValue.toDate();
  6511. // }
  6512. //};
  6513. /**
  6514. * replaces the Core toScreen methods
  6515. * @param Core
  6516. * @param time
  6517. * @param width
  6518. * @returns {number}
  6519. */
  6520. exports.toScreen = function(Core, time, width) {
  6521. if (Core.body.hiddenDates.length == 0) {
  6522. var conversion = Core.range.conversion(width);
  6523. return (time.valueOf() - conversion.offset) * conversion.scale;
  6524. }
  6525. else {
  6526. var hidden = exports.isHidden(time, Core.body.hiddenDates)
  6527. if (hidden.hidden == true) {
  6528. time = hidden.startDate;
  6529. }
  6530. var duration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
  6531. time = exports.correctTimeForHidden(Core.body.hiddenDates, Core.range, time);
  6532. var conversion = Core.range.conversion(width, duration);
  6533. return (time.valueOf() - conversion.offset) * conversion.scale;
  6534. }
  6535. };
  6536. /**
  6537. * Replaces the core toTime methods
  6538. * @param body
  6539. * @param range
  6540. * @param x
  6541. * @param width
  6542. * @returns {Date}
  6543. */
  6544. exports.toTime = function(Core, x, width) {
  6545. if (Core.body.hiddenDates.length == 0) {
  6546. var conversion = Core.range.conversion(width);
  6547. return new Date(x / conversion.scale + conversion.offset);
  6548. }
  6549. else {
  6550. var hiddenDuration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
  6551. var totalDuration = Core.range.end - Core.range.start - hiddenDuration;
  6552. var partialDuration = totalDuration * x / width;
  6553. var accumulatedHiddenDuration = exports.getAccumulatedHiddenDuration(Core.body.hiddenDates, Core.range, partialDuration);
  6554. var newTime = new Date(accumulatedHiddenDuration + partialDuration + Core.range.start);
  6555. return newTime;
  6556. }
  6557. };
  6558. /**
  6559. * Support function
  6560. *
  6561. * @param hiddenDates
  6562. * @param range
  6563. * @returns {number}
  6564. */
  6565. exports.getHiddenDurationBetween = function(hiddenDates, start, end) {
  6566. var duration = 0;
  6567. for (var i = 0; i < hiddenDates.length; i++) {
  6568. var startDate = hiddenDates[i].start;
  6569. var endDate = hiddenDates[i].end;
  6570. // if time after the cutout, and the
  6571. if (startDate >= start && endDate < end) {
  6572. duration += endDate - startDate;
  6573. }
  6574. }
  6575. return duration;
  6576. };
  6577. /**
  6578. * Support function
  6579. * @param hiddenDates
  6580. * @param range
  6581. * @param time
  6582. * @returns {{duration: number, time: *, offset: number}}
  6583. */
  6584. exports.correctTimeForHidden = function(hiddenDates, range, time) {
  6585. time = moment(time).toDate().valueOf();
  6586. time -= exports.getHiddenDurationBefore(hiddenDates,range,time);
  6587. return time;
  6588. };
  6589. exports.getHiddenDurationBefore = function(hiddenDates, range, time) {
  6590. var timeOffset = 0;
  6591. time = moment(time).toDate().valueOf();
  6592. for (var i = 0; i < hiddenDates.length; i++) {
  6593. var startDate = hiddenDates[i].start;
  6594. var endDate = hiddenDates[i].end;
  6595. // if time after the cutout, and the
  6596. if (startDate >= range.start && endDate < range.end) {
  6597. if (time >= endDate) {
  6598. timeOffset += (endDate - startDate);
  6599. }
  6600. }
  6601. }
  6602. return timeOffset;
  6603. }
  6604. /**
  6605. * sum the duration from start to finish, including the hidden duration,
  6606. * until the required amount has been reached, return the accumulated hidden duration
  6607. * @param hiddenDates
  6608. * @param range
  6609. * @param time
  6610. * @returns {{duration: number, time: *, offset: number}}
  6611. */
  6612. exports.getAccumulatedHiddenDuration = function(hiddenDates, range, requiredDuration) {
  6613. var hiddenDuration = 0;
  6614. var duration = 0;
  6615. var previousPoint = range.start;
  6616. //exports.printDates(hiddenDates)
  6617. for (var i = 0; i < hiddenDates.length; i++) {
  6618. var startDate = hiddenDates[i].start;
  6619. var endDate = hiddenDates[i].end;
  6620. // if time after the cutout, and the
  6621. if (startDate >= range.start && endDate < range.end) {
  6622. duration += startDate - previousPoint;
  6623. previousPoint = endDate;
  6624. if (duration >= requiredDuration) {
  6625. break;
  6626. }
  6627. else {
  6628. hiddenDuration += endDate - startDate;
  6629. }
  6630. }
  6631. }
  6632. return hiddenDuration;
  6633. };
  6634. /**
  6635. * used to step over to either side of a hidden block. Correction is disabled on tablets, might be set to true
  6636. * @param hiddenDates
  6637. * @param time
  6638. * @param direction
  6639. * @param correctionEnabled
  6640. * @returns {*}
  6641. */
  6642. exports.snapAwayFromHidden = function(hiddenDates, time, direction, correctionEnabled) {
  6643. var isHidden = exports.isHidden(time, hiddenDates);
  6644. if (isHidden.hidden == true) {
  6645. if (direction < 0) {
  6646. if (correctionEnabled == true) {
  6647. return isHidden.startDate - (isHidden.endDate - time) - 1;
  6648. }
  6649. else {
  6650. return isHidden.startDate - 1;
  6651. }
  6652. }
  6653. else {
  6654. if (correctionEnabled == true) {
  6655. return isHidden.endDate + (time - isHidden.startDate) + 1;
  6656. }
  6657. else {
  6658. return isHidden.endDate + 1;
  6659. }
  6660. }
  6661. }
  6662. else {
  6663. return time;
  6664. }
  6665. }
  6666. /**
  6667. * Check if a time is hidden
  6668. *
  6669. * @param time
  6670. * @param hiddenDates
  6671. * @returns {{hidden: boolean, startDate: Window.start, endDate: *}}
  6672. */
  6673. exports.isHidden = function(time, hiddenDates) {
  6674. for (var i = 0; i < hiddenDates.length; i++) {
  6675. var startDate = hiddenDates[i].start;
  6676. var endDate = hiddenDates[i].end;
  6677. if (time >= startDate && time < endDate) { // if the start is entering a hidden zone
  6678. return {hidden: true, startDate: startDate, endDate: endDate};
  6679. break;
  6680. }
  6681. }
  6682. return {hidden: false, startDate: startDate, endDate: endDate};
  6683. }
  6684. /***/ },
  6685. /* 16 */
  6686. /***/ function(module, exports, __webpack_require__) {
  6687. /**
  6688. * @constructor DataStep
  6689. * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an
  6690. * end data point. The class itself determines the best scale (step size) based on the
  6691. * provided start Date, end Date, and minimumStep.
  6692. *
  6693. * If minimumStep is provided, the step size is chosen as close as possible
  6694. * to the minimumStep but larger than minimumStep. If minimumStep is not
  6695. * provided, the scale is set to 1 DAY.
  6696. * The minimumStep should correspond with the onscreen size of about 6 characters
  6697. *
  6698. * Alternatively, you can set a scale by hand.
  6699. * After creation, you can initialize the class by executing first(). Then you
  6700. * can iterate from the start date to the end date via next(). You can check if
  6701. * the end date is reached with the function hasNext(). After each step, you can
  6702. * retrieve the current date via getCurrent().
  6703. * The DataStep has scales ranging from milliseconds, seconds, minutes, hours,
  6704. * days, to years.
  6705. *
  6706. * Version: 1.2
  6707. *
  6708. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  6709. * or new Date(2010, 9, 21, 23, 45, 00)
  6710. * @param {Date} [end] The end date
  6711. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  6712. */
  6713. function DataStep(start, end, minimumStep, containerHeight, customRange, alignZeros) {
  6714. // variables
  6715. this.current = 0;
  6716. this.autoScale = true;
  6717. this.stepIndex = 0;
  6718. this.step = 1;
  6719. this.scale = 1;
  6720. this.marginStart;
  6721. this.marginEnd;
  6722. this.deadSpace = 0;
  6723. this.majorSteps = [1, 2, 5, 10];
  6724. this.minorSteps = [0.25, 0.5, 1, 2];
  6725. this.alignZeros = alignZeros;
  6726. this.setRange(start, end, minimumStep, containerHeight, customRange);
  6727. }
  6728. /**
  6729. * Set a new range
  6730. * If minimumStep is provided, the step size is chosen as close as possible
  6731. * to the minimumStep but larger than minimumStep. If minimumStep is not
  6732. * provided, the scale is set to 1 DAY.
  6733. * The minimumStep should correspond with the onscreen size of about 6 characters
  6734. * @param {Number} [start] The start date and time.
  6735. * @param {Number} [end] The end date and time.
  6736. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  6737. */
  6738. DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, customRange) {
  6739. this._start = customRange.min === undefined ? start : customRange.min;
  6740. this._end = customRange.max === undefined ? end : customRange.max;
  6741. if (this._start == this._end) {
  6742. this._start -= 0.75;
  6743. this._end += 1;
  6744. }
  6745. if (this.autoScale == true) {
  6746. this.setMinimumStep(minimumStep, containerHeight);
  6747. }
  6748. this.setFirst(customRange);
  6749. };
  6750. /**
  6751. * Automatically determine the scale that bests fits the provided minimum step
  6752. * @param {Number} [minimumStep] The minimum step size in milliseconds
  6753. */
  6754. DataStep.prototype.setMinimumStep = function(minimumStep, containerHeight) {
  6755. // round to floor
  6756. var size = this._end - this._start;
  6757. var safeSize = size * 1.2;
  6758. var minimumStepValue = minimumStep * (safeSize / containerHeight);
  6759. var orderOfMagnitude = Math.round(Math.log(safeSize)/Math.LN10);
  6760. var minorStepIdx = -1;
  6761. var magnitudefactor = Math.pow(10,orderOfMagnitude);
  6762. var start = 0;
  6763. if (orderOfMagnitude < 0) {
  6764. start = orderOfMagnitude;
  6765. }
  6766. var solutionFound = false;
  6767. for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) {
  6768. magnitudefactor = Math.pow(10,i);
  6769. for (var j = 0; j < this.minorSteps.length; j++) {
  6770. var stepSize = magnitudefactor * this.minorSteps[j];
  6771. if (stepSize >= minimumStepValue) {
  6772. solutionFound = true;
  6773. minorStepIdx = j;
  6774. break;
  6775. }
  6776. }
  6777. if (solutionFound == true) {
  6778. break;
  6779. }
  6780. }
  6781. this.stepIndex = minorStepIdx;
  6782. this.scale = magnitudefactor;
  6783. this.step = magnitudefactor * this.minorSteps[minorStepIdx];
  6784. };
  6785. /**
  6786. * Round the current date to the first minor date value
  6787. * This must be executed once when the current date is set to start Date
  6788. */
  6789. DataStep.prototype.setFirst = function(customRange) {
  6790. if (customRange === undefined) {
  6791. customRange = {};
  6792. }
  6793. var niceStart = customRange.min === undefined ? this._start - (this.scale * 2 * this.minorSteps[this.stepIndex]) : customRange.min;
  6794. var niceEnd = customRange.max === undefined ? this._end + (this.scale * this.minorSteps[this.stepIndex]) : customRange.max;
  6795. this.marginEnd = customRange.max === undefined ? this.roundToMinor(niceEnd) : customRange.max;
  6796. this.marginStart = customRange.min === undefined ? this.roundToMinor(niceStart) : customRange.min;
  6797. // if we need to align the zero's we need to make sure that there is a zero to use.
  6798. if (this.alignZeros == true && (this.marginEnd - this.marginStart) % this.step != 0) {
  6799. this.marginEnd += this.marginEnd % this.step;
  6800. }
  6801. this.deadSpace = this.roundToMinor(niceEnd) - niceEnd + this.roundToMinor(niceStart) - niceStart;
  6802. this.marginRange = this.marginEnd - this.marginStart;
  6803. this.current = this.marginEnd;
  6804. };
  6805. DataStep.prototype.roundToMinor = function(value) {
  6806. var rounded = value - (value % (this.scale * this.minorSteps[this.stepIndex]));
  6807. if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) {
  6808. return rounded + (this.scale * this.minorSteps[this.stepIndex]);
  6809. }
  6810. else {
  6811. return rounded;
  6812. }
  6813. }
  6814. /**
  6815. * Check if the there is a next step
  6816. * @return {boolean} true if the current date has not passed the end date
  6817. */
  6818. DataStep.prototype.hasNext = function () {
  6819. return (this.current >= this.marginStart);
  6820. };
  6821. /**
  6822. * Do the next step
  6823. */
  6824. DataStep.prototype.next = function() {
  6825. var prev = this.current;
  6826. this.current -= this.step;
  6827. // safety mechanism: if current time is still unchanged, move to the end
  6828. if (this.current == prev) {
  6829. this.current = this._end;
  6830. }
  6831. };
  6832. /**
  6833. * Do the next step
  6834. */
  6835. DataStep.prototype.previous = function() {
  6836. this.current += this.step;
  6837. this.marginEnd += this.step;
  6838. this.marginRange = this.marginEnd - this.marginStart;
  6839. };
  6840. /**
  6841. * Get the current datetime
  6842. * @return {String} current The current date
  6843. */
  6844. DataStep.prototype.getCurrent = function(decimals) {
  6845. // prevent round-off errors when close to zero
  6846. var current = (Math.abs(this.current) < this.step / 2) ? 0 : this.current;
  6847. var toPrecision = '' + Number(current).toPrecision(5);
  6848. // If decimals is specified, then limit or extend the string as required
  6849. if(decimals !== undefined && !isNaN(Number(decimals))) {
  6850. // If string includes exponent, then we need to add it to the end
  6851. var exp = "";
  6852. var index = toPrecision.indexOf("e");
  6853. if(index != -1) {
  6854. // Get the exponent
  6855. exp = toPrecision.slice(index);
  6856. // Remove the exponent in case we need to zero-extend
  6857. toPrecision = toPrecision.slice(0, index);
  6858. }
  6859. index = Math.max(toPrecision.indexOf(","), toPrecision.indexOf("."));
  6860. if(index === -1) {
  6861. // No decimal found - if we want decimals, then we need to add it
  6862. if(decimals !== 0) {
  6863. toPrecision += '.';
  6864. }
  6865. // Calculate how long the string should be
  6866. index = toPrecision.length + decimals;
  6867. }
  6868. else if(decimals !== 0) {
  6869. // Calculate how long the string should be - accounting for the decimal place
  6870. index += decimals + 1;
  6871. }
  6872. if(index > toPrecision.length) {
  6873. // We need to add zeros!
  6874. for(var cnt = index - toPrecision.length; cnt > 0; cnt--) {
  6875. toPrecision += '0';
  6876. }
  6877. }
  6878. else {
  6879. // we need to remove characters
  6880. toPrecision = toPrecision.slice(0, index);
  6881. }
  6882. // Add the exponent if there is one
  6883. toPrecision += exp;
  6884. }
  6885. else {
  6886. if (toPrecision.indexOf(",") != -1 || toPrecision.indexOf(".") != -1) {
  6887. // If no decimal is specified, and there are decimal places, remove trailing zeros
  6888. for (var i = toPrecision.length - 1; i > 0; i--) {
  6889. if (toPrecision[i] == "0") {
  6890. toPrecision = toPrecision.slice(0, i);
  6891. }
  6892. else if (toPrecision[i] == "." || toPrecision[i] == ",") {
  6893. toPrecision = toPrecision.slice(0, i);
  6894. break;
  6895. }
  6896. else {
  6897. break;
  6898. }
  6899. }
  6900. }
  6901. }
  6902. return toPrecision;
  6903. };
  6904. /**
  6905. * Check if the current value is a major value (for example when the step
  6906. * is DAY, a major value is each first day of the MONTH)
  6907. * @return {boolean} true if current date is major, else false.
  6908. */
  6909. DataStep.prototype.isMajor = function() {
  6910. return (this.current % (this.scale * this.majorSteps[this.stepIndex]) == 0);
  6911. };
  6912. module.exports = DataStep;
  6913. /***/ },
  6914. /* 17 */
  6915. /***/ function(module, exports, __webpack_require__) {
  6916. var util = __webpack_require__(1);
  6917. var hammerUtil = __webpack_require__(47);
  6918. var moment = __webpack_require__(44);
  6919. var Component = __webpack_require__(25);
  6920. var DateUtil = __webpack_require__(15);
  6921. /**
  6922. * @constructor Range
  6923. * A Range controls a numeric range with a start and end value.
  6924. * The Range adjusts the range based on mouse events or programmatic changes,
  6925. * and triggers events when the range is changing or has been changed.
  6926. * @param {{dom: Object, domProps: Object, emitter: Emitter}} body
  6927. * @param {Object} [options] See description at Range.setOptions
  6928. */
  6929. function Range(body, options) {
  6930. var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
  6931. this.start = now.clone().add(-3, 'days').valueOf(); // Number
  6932. this.end = now.clone().add(4, 'days').valueOf(); // Number
  6933. this.body = body;
  6934. this.deltaDifference = 0;
  6935. this.scaleOffset = 0;
  6936. this.startToFront = false;
  6937. this.endToFront = true;
  6938. // default options
  6939. this.defaultOptions = {
  6940. start: null,
  6941. end: null,
  6942. direction: 'horizontal', // 'horizontal' or 'vertical'
  6943. moveable: true,
  6944. zoomable: true,
  6945. min: null,
  6946. max: null,
  6947. zoomMin: 10, // milliseconds
  6948. zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000 // milliseconds
  6949. };
  6950. this.options = util.extend({}, this.defaultOptions);
  6951. this.props = {
  6952. touch: {}
  6953. };
  6954. this.animateTimer = null;
  6955. // drag listeners for dragging
  6956. this.body.emitter.on('dragstart', this._onDragStart.bind(this));
  6957. this.body.emitter.on('drag', this._onDrag.bind(this));
  6958. this.body.emitter.on('dragend', this._onDragEnd.bind(this));
  6959. // ignore dragging when holding
  6960. this.body.emitter.on('hold', this._onHold.bind(this));
  6961. // mouse wheel for zooming
  6962. this.body.emitter.on('mousewheel', this._onMouseWheel.bind(this));
  6963. this.body.emitter.on('DOMMouseScroll', this._onMouseWheel.bind(this)); // For FF
  6964. // pinch to zoom
  6965. this.body.emitter.on('touch', this._onTouch.bind(this));
  6966. this.body.emitter.on('pinch', this._onPinch.bind(this));
  6967. this.setOptions(options);
  6968. }
  6969. Range.prototype = new Component();
  6970. /**
  6971. * Set options for the range controller
  6972. * @param {Object} options Available options:
  6973. * {Number | Date | String} start Start date for the range
  6974. * {Number | Date | String} end End date for the range
  6975. * {Number} min Minimum value for start
  6976. * {Number} max Maximum value for end
  6977. * {Number} zoomMin Set a minimum value for
  6978. * (end - start).
  6979. * {Number} zoomMax Set a maximum value for
  6980. * (end - start).
  6981. * {Boolean} moveable Enable moving of the range
  6982. * by dragging. True by default
  6983. * {Boolean} zoomable Enable zooming of the range
  6984. * by pinching/scrolling. True by default
  6985. */
  6986. Range.prototype.setOptions = function (options) {
  6987. if (options) {
  6988. // copy the options that we know
  6989. var fields = ['direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable', 'activate', 'hiddenDates'];
  6990. util.selectiveExtend(fields, this.options, options);
  6991. if ('start' in options || 'end' in options) {
  6992. // apply a new range. both start and end are optional
  6993. this.setRange(options.start, options.end);
  6994. }
  6995. }
  6996. };
  6997. /**
  6998. * Test whether direction has a valid value
  6999. * @param {String} direction 'horizontal' or 'vertical'
  7000. */
  7001. function validateDirection (direction) {
  7002. if (direction != 'horizontal' && direction != 'vertical') {
  7003. throw new TypeError('Unknown direction "' + direction + '". ' +
  7004. 'Choose "horizontal" or "vertical".');
  7005. }
  7006. }
  7007. /**
  7008. * Set a new start and end range
  7009. * @param {Date | Number | String} [start]
  7010. * @param {Date | Number | String} [end]
  7011. * @param {boolean | number} [animate=false] If true, the range is animated
  7012. * smoothly to the new window.
  7013. * If animate is a number, the
  7014. * number is taken as duration
  7015. * Default duration is 500 ms.
  7016. * @param {Boolean} [byUser=false]
  7017. *
  7018. */
  7019. Range.prototype.setRange = function(start, end, animate, byUser) {
  7020. if (byUser !== true) {
  7021. byUser = false;
  7022. }
  7023. var _start = start != undefined ? util.convert(start, 'Date').valueOf() : null;
  7024. var _end = end != undefined ? util.convert(end, 'Date').valueOf() : null;
  7025. this._cancelAnimation();
  7026. if (animate) {
  7027. var me = this;
  7028. var initStart = this.start;
  7029. var initEnd = this.end;
  7030. var duration = typeof animate === 'number' ? animate : 500;
  7031. var initTime = new Date().valueOf();
  7032. var anyChanged = false;
  7033. var next = function () {
  7034. if (!me.props.touch.dragging) {
  7035. var now = new Date().valueOf();
  7036. var time = now - initTime;
  7037. var done = time > duration;
  7038. var s = (done || _start === null) ? _start : util.easeInOutQuad(time, initStart, _start, duration);
  7039. var e = (done || _end === null) ? _end : util.easeInOutQuad(time, initEnd, _end, duration);
  7040. changed = me._applyRange(s, e);
  7041. DateUtil.updateHiddenDates(me.body, me.options.hiddenDates);
  7042. anyChanged = anyChanged || changed;
  7043. if (changed) {
  7044. me.body.emitter.emit('rangechange', {start: new Date(me.start), end: new Date(me.end), byUser:byUser});
  7045. }
  7046. if (done) {
  7047. if (anyChanged) {
  7048. me.body.emitter.emit('rangechanged', {start: new Date(me.start), end: new Date(me.end), byUser:byUser});
  7049. }
  7050. }
  7051. else {
  7052. // animate with as high as possible frame rate, leave 20 ms in between
  7053. // each to prevent the browser from blocking
  7054. me.animateTimer = setTimeout(next, 20);
  7055. }
  7056. }
  7057. };
  7058. return next();
  7059. }
  7060. else {
  7061. var changed = this._applyRange(_start, _end);
  7062. DateUtil.updateHiddenDates(this.body, this.options.hiddenDates);
  7063. if (changed) {
  7064. var params = {start: new Date(this.start), end: new Date(this.end), byUser:byUser};
  7065. this.body.emitter.emit('rangechange', params);
  7066. this.body.emitter.emit('rangechanged', params);
  7067. }
  7068. }
  7069. };
  7070. /**
  7071. * Stop an animation
  7072. * @private
  7073. */
  7074. Range.prototype._cancelAnimation = function () {
  7075. if (this.animateTimer) {
  7076. clearTimeout(this.animateTimer);
  7077. this.animateTimer = null;
  7078. }
  7079. };
  7080. /**
  7081. * Set a new start and end range. This method is the same as setRange, but
  7082. * does not trigger a range change and range changed event, and it returns
  7083. * true when the range is changed
  7084. * @param {Number} [start]
  7085. * @param {Number} [end]
  7086. * @return {Boolean} changed
  7087. * @private
  7088. */
  7089. Range.prototype._applyRange = function(start, end) {
  7090. var newStart = (start != null) ? util.convert(start, 'Date').valueOf() : this.start,
  7091. newEnd = (end != null) ? util.convert(end, 'Date').valueOf() : this.end,
  7092. max = (this.options.max != null) ? util.convert(this.options.max, 'Date').valueOf() : null,
  7093. min = (this.options.min != null) ? util.convert(this.options.min, 'Date').valueOf() : null,
  7094. diff;
  7095. // check for valid number
  7096. if (isNaN(newStart) || newStart === null) {
  7097. throw new Error('Invalid start "' + start + '"');
  7098. }
  7099. if (isNaN(newEnd) || newEnd === null) {
  7100. throw new Error('Invalid end "' + end + '"');
  7101. }
  7102. // prevent start < end
  7103. if (newEnd < newStart) {
  7104. newEnd = newStart;
  7105. }
  7106. // prevent start < min
  7107. if (min !== null) {
  7108. if (newStart < min) {
  7109. diff = (min - newStart);
  7110. newStart += diff;
  7111. newEnd += diff;
  7112. // prevent end > max
  7113. if (max != null) {
  7114. if (newEnd > max) {
  7115. newEnd = max;
  7116. }
  7117. }
  7118. }
  7119. }
  7120. // prevent end > max
  7121. if (max !== null) {
  7122. if (newEnd > max) {
  7123. diff = (newEnd - max);
  7124. newStart -= diff;
  7125. newEnd -= diff;
  7126. // prevent start < min
  7127. if (min != null) {
  7128. if (newStart < min) {
  7129. newStart = min;
  7130. }
  7131. }
  7132. }
  7133. }
  7134. // prevent (end-start) < zoomMin
  7135. if (this.options.zoomMin !== null) {
  7136. var zoomMin = parseFloat(this.options.zoomMin);
  7137. if (zoomMin < 0) {
  7138. zoomMin = 0;
  7139. }
  7140. if ((newEnd - newStart) < zoomMin) {
  7141. if ((this.end - this.start) === zoomMin && newStart > this.start && newEnd < this.end) {
  7142. // ignore this action, we are already zoomed to the minimum
  7143. newStart = this.start;
  7144. newEnd = this.end;
  7145. }
  7146. else {
  7147. // zoom to the minimum
  7148. diff = (zoomMin - (newEnd - newStart));
  7149. newStart -= diff / 2;
  7150. newEnd += diff / 2;
  7151. }
  7152. }
  7153. }
  7154. // prevent (end-start) > zoomMax
  7155. if (this.options.zoomMax !== null) {
  7156. var zoomMax = parseFloat(this.options.zoomMax);
  7157. if (zoomMax < 0) {
  7158. zoomMax = 0;
  7159. }
  7160. if ((newEnd - newStart) > zoomMax) {
  7161. if ((this.end - this.start) === zoomMax && newStart < this.start && newEnd > this.end) {
  7162. // ignore this action, we are already zoomed to the maximum
  7163. newStart = this.start;
  7164. newEnd = this.end;
  7165. }
  7166. else {
  7167. // zoom to the maximum
  7168. diff = ((newEnd - newStart) - zoomMax);
  7169. newStart += diff / 2;
  7170. newEnd -= diff / 2;
  7171. }
  7172. }
  7173. }
  7174. var changed = (this.start != newStart || this.end != newEnd);
  7175. // if the new range does NOT overlap with the old range, emit checkRangedItems to avoid not showing ranged items (ranged meaning has end time, not necessarily of type Range)
  7176. if (!((newStart >= this.start && newStart <= this.end) || (newEnd >= this.start && newEnd <= this.end)) &&
  7177. !((this.start >= newStart && this.start <= newEnd) || (this.end >= newStart && this.end <= newEnd) )) {
  7178. this.body.emitter.emit('checkRangedItems');
  7179. }
  7180. this.start = newStart;
  7181. this.end = newEnd;
  7182. return changed;
  7183. };
  7184. /**
  7185. * Retrieve the current range.
  7186. * @return {Object} An object with start and end properties
  7187. */
  7188. Range.prototype.getRange = function() {
  7189. return {
  7190. start: this.start,
  7191. end: this.end
  7192. };
  7193. };
  7194. /**
  7195. * Calculate the conversion offset and scale for current range, based on
  7196. * the provided width
  7197. * @param {Number} width
  7198. * @returns {{offset: number, scale: number}} conversion
  7199. */
  7200. Range.prototype.conversion = function (width, totalHidden) {
  7201. return Range.conversion(this.start, this.end, width, totalHidden);
  7202. };
  7203. /**
  7204. * Static method to calculate the conversion offset and scale for a range,
  7205. * based on the provided start, end, and width
  7206. * @param {Number} start
  7207. * @param {Number} end
  7208. * @param {Number} width
  7209. * @returns {{offset: number, scale: number}} conversion
  7210. */
  7211. Range.conversion = function (start, end, width, totalHidden) {
  7212. if (totalHidden === undefined) {
  7213. totalHidden = 0;
  7214. }
  7215. if (width != 0 && (end - start != 0)) {
  7216. return {
  7217. offset: start,
  7218. scale: width / (end - start - totalHidden)
  7219. }
  7220. }
  7221. else {
  7222. return {
  7223. offset: 0,
  7224. scale: 1
  7225. };
  7226. }
  7227. };
  7228. /**
  7229. * Start dragging horizontally or vertically
  7230. * @param {Event} event
  7231. * @private
  7232. */
  7233. Range.prototype._onDragStart = function(event) {
  7234. this.deltaDifference = 0;
  7235. this.previousDelta = 0;
  7236. // only allow dragging when configured as movable
  7237. if (!this.options.moveable) return;
  7238. // refuse to drag when we where pinching to prevent the timeline make a jump
  7239. // when releasing the fingers in opposite order from the touch screen
  7240. if (!this.props.touch.allowDragging) return;
  7241. this.props.touch.start = this.start;
  7242. this.props.touch.end = this.end;
  7243. this.props.touch.dragging = true;
  7244. if (this.body.dom.root) {
  7245. this.body.dom.root.style.cursor = 'move';
  7246. }
  7247. };
  7248. /**
  7249. * Perform dragging operation
  7250. * @param {Event} event
  7251. * @private
  7252. */
  7253. Range.prototype._onDrag = function (event) {
  7254. // only allow dragging when configured as movable
  7255. if (!this.options.moveable) return;
  7256. // refuse to drag when we where pinching to prevent the timeline make a jump
  7257. // when releasing the fingers in opposite order from the touch screen
  7258. if (!this.props.touch.allowDragging) return;
  7259. var direction = this.options.direction;
  7260. validateDirection(direction);
  7261. var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY;
  7262. delta -= this.deltaDifference;
  7263. var interval = (this.props.touch.end - this.props.touch.start);
  7264. // normalize dragging speed if cutout is in between.
  7265. var duration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  7266. interval -= duration;
  7267. var width = (direction == 'horizontal') ? this.body.domProps.center.width : this.body.domProps.center.height;
  7268. var diffRange = -delta / width * interval;
  7269. var newStart = this.props.touch.start + diffRange;
  7270. var newEnd = this.props.touch.end + diffRange;
  7271. // snapping times away from hidden zones
  7272. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, this.previousDelta-delta, true);
  7273. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, this.previousDelta-delta, true);
  7274. if (safeStart != newStart || safeEnd != newEnd) {
  7275. this.deltaDifference += delta;
  7276. this.props.touch.start = safeStart;
  7277. this.props.touch.end = safeEnd;
  7278. this._onDrag(event);
  7279. return;
  7280. }
  7281. this.previousDelta = delta;
  7282. this._applyRange(newStart, newEnd);
  7283. // fire a rangechange event
  7284. this.body.emitter.emit('rangechange', {
  7285. start: new Date(this.start),
  7286. end: new Date(this.end),
  7287. byUser: true
  7288. });
  7289. };
  7290. /**
  7291. * Stop dragging operation
  7292. * @param {event} event
  7293. * @private
  7294. */
  7295. Range.prototype._onDragEnd = function (event) {
  7296. // only allow dragging when configured as movable
  7297. if (!this.options.moveable) return;
  7298. // refuse to drag when we where pinching to prevent the timeline make a jump
  7299. // when releasing the fingers in opposite order from the touch screen
  7300. if (!this.props.touch.allowDragging) return;
  7301. this.props.touch.dragging = false;
  7302. if (this.body.dom.root) {
  7303. this.body.dom.root.style.cursor = 'auto';
  7304. }
  7305. // fire a rangechanged event
  7306. this.body.emitter.emit('rangechanged', {
  7307. start: new Date(this.start),
  7308. end: new Date(this.end),
  7309. byUser: true
  7310. });
  7311. };
  7312. /**
  7313. * Event handler for mouse wheel event, used to zoom
  7314. * Code from http://adomas.org/javascript-mouse-wheel/
  7315. * @param {Event} event
  7316. * @private
  7317. */
  7318. Range.prototype._onMouseWheel = function(event) {
  7319. // only allow zooming when configured as zoomable and moveable
  7320. if (!(this.options.zoomable && this.options.moveable)) return;
  7321. // retrieve delta
  7322. var delta = 0;
  7323. if (event.wheelDelta) { /* IE/Opera. */
  7324. delta = event.wheelDelta / 120;
  7325. } else if (event.detail) { /* Mozilla case. */
  7326. // In Mozilla, sign of delta is different than in IE.
  7327. // Also, delta is multiple of 3.
  7328. delta = -event.detail / 3;
  7329. }
  7330. // If delta is nonzero, handle it.
  7331. // Basically, delta is now positive if wheel was scrolled up,
  7332. // and negative, if wheel was scrolled down.
  7333. if (delta) {
  7334. // perform the zoom action. Delta is normally 1 or -1
  7335. // adjust a negative delta such that zooming in with delta 0.1
  7336. // equals zooming out with a delta -0.1
  7337. var scale;
  7338. if (delta < 0) {
  7339. scale = 1 - (delta / 5);
  7340. }
  7341. else {
  7342. scale = 1 / (1 + (delta / 5)) ;
  7343. }
  7344. // calculate center, the date to zoom around
  7345. var gesture = hammerUtil.fakeGesture(this, event),
  7346. pointer = getPointer(gesture.center, this.body.dom.center),
  7347. pointerDate = this._pointerToDate(pointer);
  7348. this.zoom(scale, pointerDate, delta);
  7349. }
  7350. // Prevent default actions caused by mouse wheel
  7351. // (else the page and timeline both zoom and scroll)
  7352. event.preventDefault();
  7353. };
  7354. /**
  7355. * Start of a touch gesture
  7356. * @private
  7357. */
  7358. Range.prototype._onTouch = function (event) {
  7359. this.props.touch.start = this.start;
  7360. this.props.touch.end = this.end;
  7361. this.props.touch.allowDragging = true;
  7362. this.props.touch.center = null;
  7363. this.scaleOffset = 0;
  7364. this.deltaDifference = 0;
  7365. };
  7366. /**
  7367. * On start of a hold gesture
  7368. * @private
  7369. */
  7370. Range.prototype._onHold = function () {
  7371. this.props.touch.allowDragging = false;
  7372. };
  7373. /**
  7374. * Handle pinch event
  7375. * @param {Event} event
  7376. * @private
  7377. */
  7378. Range.prototype._onPinch = function (event) {
  7379. // only allow zooming when configured as zoomable and moveable
  7380. if (!(this.options.zoomable && this.options.moveable)) return;
  7381. this.props.touch.allowDragging = false;
  7382. if (event.gesture.touches.length > 1) {
  7383. if (!this.props.touch.center) {
  7384. this.props.touch.center = getPointer(event.gesture.center, this.body.dom.center);
  7385. }
  7386. var scale = 1 / (event.gesture.scale + this.scaleOffset);
  7387. var centerDate = this._pointerToDate(this.props.touch.center);
  7388. var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  7389. var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this, centerDate);
  7390. var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore;
  7391. // calculate new start and end
  7392. var newStart = (centerDate - hiddenDurationBefore) + (this.props.touch.start - (centerDate - hiddenDurationBefore)) * scale;
  7393. var newEnd = (centerDate + hiddenDurationAfter) + (this.props.touch.end - (centerDate + hiddenDurationAfter)) * scale;
  7394. // snapping times away from hidden zones
  7395. this.startToFront = 1 - scale > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  7396. this.endToFront = scale - 1 > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  7397. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, 1 - scale, true);
  7398. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, scale - 1, true);
  7399. if (safeStart != newStart || safeEnd != newEnd) {
  7400. this.props.touch.start = safeStart;
  7401. this.props.touch.end = safeEnd;
  7402. this.scaleOffset = 1 - event.gesture.scale;
  7403. newStart = safeStart;
  7404. newEnd = safeEnd;
  7405. }
  7406. this.setRange(newStart, newEnd, false, true);
  7407. this.startToFront = false; // revert to default
  7408. this.endToFront = true; // revert to default
  7409. }
  7410. };
  7411. /**
  7412. * Helper function to calculate the center date for zooming
  7413. * @param {{x: Number, y: Number}} pointer
  7414. * @return {number} date
  7415. * @private
  7416. */
  7417. Range.prototype._pointerToDate = function (pointer) {
  7418. var conversion;
  7419. var direction = this.options.direction;
  7420. validateDirection(direction);
  7421. if (direction == 'horizontal') {
  7422. return this.body.util.toTime(pointer.x).valueOf();
  7423. }
  7424. else {
  7425. var height = this.body.domProps.center.height;
  7426. conversion = this.conversion(height);
  7427. return pointer.y / conversion.scale + conversion.offset;
  7428. }
  7429. };
  7430. /**
  7431. * Get the pointer location relative to the location of the dom element
  7432. * @param {{pageX: Number, pageY: Number}} touch
  7433. * @param {Element} element HTML DOM element
  7434. * @return {{x: Number, y: Number}} pointer
  7435. * @private
  7436. */
  7437. function getPointer (touch, element) {
  7438. return {
  7439. x: touch.pageX - util.getAbsoluteLeft(element),
  7440. y: touch.pageY - util.getAbsoluteTop(element)
  7441. };
  7442. }
  7443. /**
  7444. * Zoom the range the given scale in or out. Start and end date will
  7445. * be adjusted, and the timeline will be redrawn. You can optionally give a
  7446. * date around which to zoom.
  7447. * For example, try scale = 0.9 or 1.1
  7448. * @param {Number} scale Scaling factor. Values above 1 will zoom out,
  7449. * values below 1 will zoom in.
  7450. * @param {Number} [center] Value representing a date around which will
  7451. * be zoomed.
  7452. */
  7453. Range.prototype.zoom = function(scale, center, delta) {
  7454. // if centerDate is not provided, take it half between start Date and end Date
  7455. if (center == null) {
  7456. center = (this.start + this.end) / 2;
  7457. }
  7458. var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  7459. var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this, center);
  7460. var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore;
  7461. // calculate new start and end
  7462. var newStart = (center-hiddenDurationBefore) + (this.start - (center-hiddenDurationBefore)) * scale;
  7463. var newEnd = (center+hiddenDurationAfter) + (this.end - (center+hiddenDurationAfter)) * scale;
  7464. // snapping times away from hidden zones
  7465. this.startToFront = delta > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  7466. this.endToFront = -delta > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  7467. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, delta, true);
  7468. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, -delta, true);
  7469. if (safeStart != newStart || safeEnd != newEnd) {
  7470. newStart = safeStart;
  7471. newEnd = safeEnd;
  7472. }
  7473. this.setRange(newStart, newEnd, false, true);
  7474. this.startToFront = false; // revert to default
  7475. this.endToFront = true; // revert to default
  7476. };
  7477. /**
  7478. * Move the range with a given delta to the left or right. Start and end
  7479. * value will be adjusted. For example, try delta = 0.1 or -0.1
  7480. * @param {Number} delta Moving amount. Positive value will move right,
  7481. * negative value will move left
  7482. */
  7483. Range.prototype.move = function(delta) {
  7484. // zoom start Date and end Date relative to the centerDate
  7485. var diff = (this.end - this.start);
  7486. // apply new values
  7487. var newStart = this.start + diff * delta;
  7488. var newEnd = this.end + diff * delta;
  7489. // TODO: reckon with min and max range
  7490. this.start = newStart;
  7491. this.end = newEnd;
  7492. };
  7493. /**
  7494. * Move the range to a new center point
  7495. * @param {Number} moveTo New center point of the range
  7496. */
  7497. Range.prototype.moveTo = function(moveTo) {
  7498. var center = (this.start + this.end) / 2;
  7499. var diff = center - moveTo;
  7500. // calculate new start and end
  7501. var newStart = this.start - diff;
  7502. var newEnd = this.end - diff;
  7503. this.setRange(newStart, newEnd);
  7504. };
  7505. module.exports = Range;
  7506. /***/ },
  7507. /* 18 */
  7508. /***/ function(module, exports, __webpack_require__) {
  7509. // Utility functions for ordering and stacking of items
  7510. var EPSILON = 0.001; // used when checking collisions, to prevent round-off errors
  7511. /**
  7512. * Order items by their start data
  7513. * @param {Item[]} items
  7514. */
  7515. exports.orderByStart = function(items) {
  7516. items.sort(function (a, b) {
  7517. return a.data.start - b.data.start;
  7518. });
  7519. };
  7520. /**
  7521. * Order items by their end date. If they have no end date, their start date
  7522. * is used.
  7523. * @param {Item[]} items
  7524. */
  7525. exports.orderByEnd = function(items) {
  7526. items.sort(function (a, b) {
  7527. var aTime = ('end' in a.data) ? a.data.end : a.data.start,
  7528. bTime = ('end' in b.data) ? b.data.end : b.data.start;
  7529. return aTime - bTime;
  7530. });
  7531. };
  7532. /**
  7533. * Adjust vertical positions of the items such that they don't overlap each
  7534. * other.
  7535. * @param {Item[]} items
  7536. * All visible items
  7537. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  7538. * Margins between items and between items and the axis.
  7539. * @param {boolean} [force=false]
  7540. * If true, all items will be repositioned. If false (default), only
  7541. * items having a top===null will be re-stacked
  7542. */
  7543. exports.stack = function(items, margin, force) {
  7544. var i, iMax;
  7545. if (force) {
  7546. // reset top position of all items
  7547. for (i = 0, iMax = items.length; i < iMax; i++) {
  7548. items[i].top = null;
  7549. }
  7550. }
  7551. // calculate new, non-overlapping positions
  7552. for (i = 0, iMax = items.length; i < iMax; i++) {
  7553. var item = items[i];
  7554. if (item.stack && item.top === null) {
  7555. // initialize top position
  7556. item.top = margin.axis;
  7557. do {
  7558. // TODO: optimize checking for overlap. when there is a gap without items,
  7559. // you only need to check for items from the next item on, not from zero
  7560. var collidingItem = null;
  7561. for (var j = 0, jj = items.length; j < jj; j++) {
  7562. var other = items[j];
  7563. if (other.top !== null && other !== item && other.stack && exports.collision(item, other, margin.item)) {
  7564. collidingItem = other;
  7565. break;
  7566. }
  7567. }
  7568. if (collidingItem != null) {
  7569. // There is a collision. Reposition the items above the colliding element
  7570. item.top = collidingItem.top + collidingItem.height + margin.item.vertical;
  7571. }
  7572. } while (collidingItem);
  7573. }
  7574. }
  7575. };
  7576. /**
  7577. * Adjust vertical positions of the items without stacking them
  7578. * @param {Item[]} items
  7579. * All visible items
  7580. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  7581. * Margins between items and between items and the axis.
  7582. */
  7583. exports.nostack = function(items, margin, subgroups) {
  7584. var i, iMax, newTop;
  7585. // reset top position of all items
  7586. for (i = 0, iMax = items.length; i < iMax; i++) {
  7587. if (items[i].data.subgroup !== undefined) {
  7588. newTop = margin.axis;
  7589. for (var subgroup in subgroups) {
  7590. if (subgroups.hasOwnProperty(subgroup)) {
  7591. if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroups[items[i].data.subgroup].index) {
  7592. newTop += subgroups[subgroup].height + margin.item.vertical;
  7593. }
  7594. }
  7595. }
  7596. items[i].top = newTop;
  7597. }
  7598. else {
  7599. items[i].top = margin.axis;
  7600. }
  7601. }
  7602. };
  7603. /**
  7604. * Test if the two provided items collide
  7605. * The items must have parameters left, width, top, and height.
  7606. * @param {Item} a The first item
  7607. * @param {Item} b The second item
  7608. * @param {{horizontal: number, vertical: number}} margin
  7609. * An object containing a horizontal and vertical
  7610. * minimum required margin.
  7611. * @return {boolean} true if a and b collide, else false
  7612. */
  7613. exports.collision = function(a, b, margin) {
  7614. return ((a.left - margin.horizontal + EPSILON) < (b.left + b.width) &&
  7615. (a.left + a.width + margin.horizontal - EPSILON) > b.left &&
  7616. (a.top - margin.vertical + EPSILON) < (b.top + b.height) &&
  7617. (a.top + a.height + margin.vertical - EPSILON) > b.top);
  7618. };
  7619. /***/ },
  7620. /* 19 */
  7621. /***/ function(module, exports, __webpack_require__) {
  7622. var moment = __webpack_require__(44);
  7623. var DateUtil = __webpack_require__(15);
  7624. var util = __webpack_require__(1);
  7625. /**
  7626. * @constructor TimeStep
  7627. * The class TimeStep is an iterator for dates. You provide a start date and an
  7628. * end date. The class itself determines the best scale (step size) based on the
  7629. * provided start Date, end Date, and minimumStep.
  7630. *
  7631. * If minimumStep is provided, the step size is chosen as close as possible
  7632. * to the minimumStep but larger than minimumStep. If minimumStep is not
  7633. * provided, the scale is set to 1 DAY.
  7634. * The minimumStep should correspond with the onscreen size of about 6 characters
  7635. *
  7636. * Alternatively, you can set a scale by hand.
  7637. * After creation, you can initialize the class by executing first(). Then you
  7638. * can iterate from the start date to the end date via next(). You can check if
  7639. * the end date is reached with the function hasNext(). After each step, you can
  7640. * retrieve the current date via getCurrent().
  7641. * The TimeStep has scales ranging from milliseconds, seconds, minutes, hours,
  7642. * days, to years.
  7643. *
  7644. * Version: 1.2
  7645. *
  7646. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  7647. * or new Date(2010, 9, 21, 23, 45, 00)
  7648. * @param {Date} [end] The end date
  7649. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  7650. */
  7651. function TimeStep(start, end, minimumStep, hiddenDates) {
  7652. // variables
  7653. this.current = new Date();
  7654. this._start = new Date();
  7655. this._end = new Date();
  7656. this.autoScale = true;
  7657. this.scale = 'day';
  7658. this.step = 1;
  7659. // initialize the range
  7660. this.setRange(start, end, minimumStep);
  7661. // hidden Dates options
  7662. this.switchedDay = false;
  7663. this.switchedMonth = false;
  7664. this.switchedYear = false;
  7665. this.hiddenDates = hiddenDates;
  7666. if (hiddenDates === undefined) {
  7667. this.hiddenDates = [];
  7668. }
  7669. this.format = TimeStep.FORMAT; // default formatting
  7670. }
  7671. // Time formatting
  7672. TimeStep.FORMAT = {
  7673. minorLabels: {
  7674. millisecond:'SSS',
  7675. second: 's',
  7676. minute: 'HH:mm',
  7677. hour: 'HH:mm',
  7678. weekday: 'ddd D',
  7679. day: 'D',
  7680. month: 'MMM',
  7681. year: 'YYYY'
  7682. },
  7683. majorLabels: {
  7684. millisecond:'HH:mm:ss',
  7685. second: 'D MMMM HH:mm',
  7686. minute: 'ddd D MMMM',
  7687. hour: 'ddd D MMMM',
  7688. weekday: 'MMMM YYYY',
  7689. day: 'MMMM YYYY',
  7690. month: 'YYYY',
  7691. year: ''
  7692. }
  7693. };
  7694. /**
  7695. * Set custom formatting for the minor an major labels of the TimeStep.
  7696. * Both `minorLabels` and `majorLabels` are an Object with properties:
  7697. * 'millisecond, 'second, 'minute', 'hour', 'weekday, 'day, 'month, 'year'.
  7698. * @param {{minorLabels: Object, majorLabels: Object}} format
  7699. */
  7700. TimeStep.prototype.setFormat = function (format) {
  7701. var defaultFormat = util.deepExtend({}, TimeStep.FORMAT);
  7702. this.format = util.deepExtend(defaultFormat, format);
  7703. };
  7704. /**
  7705. * Set a new range
  7706. * If minimumStep is provided, the step size is chosen as close as possible
  7707. * to the minimumStep but larger than minimumStep. If minimumStep is not
  7708. * provided, the scale is set to 1 DAY.
  7709. * The minimumStep should correspond with the onscreen size of about 6 characters
  7710. * @param {Date} [start] The start date and time.
  7711. * @param {Date} [end] The end date and time.
  7712. * @param {int} [minimumStep] Optional. Minimum step size in milliseconds
  7713. */
  7714. TimeStep.prototype.setRange = function(start, end, minimumStep) {
  7715. if (!(start instanceof Date) || !(end instanceof Date)) {
  7716. throw "No legal start or end date in method setRange";
  7717. }
  7718. this._start = (start != undefined) ? new Date(start.valueOf()) : new Date();
  7719. this._end = (end != undefined) ? new Date(end.valueOf()) : new Date();
  7720. if (this.autoScale) {
  7721. this.setMinimumStep(minimumStep);
  7722. }
  7723. };
  7724. /**
  7725. * Set the range iterator to the start date.
  7726. */
  7727. TimeStep.prototype.first = function() {
  7728. this.current = new Date(this._start.valueOf());
  7729. this.roundToMinor();
  7730. };
  7731. /**
  7732. * Round the current date to the first minor date value
  7733. * This must be executed once when the current date is set to start Date
  7734. */
  7735. TimeStep.prototype.roundToMinor = function() {
  7736. // round to floor
  7737. // IMPORTANT: we have no breaks in this switch! (this is no bug)
  7738. // noinspection FallThroughInSwitchStatementJS
  7739. switch (this.scale) {
  7740. case 'year':
  7741. this.current.setFullYear(this.step * Math.floor(this.current.getFullYear() / this.step));
  7742. this.current.setMonth(0);
  7743. case 'month': this.current.setDate(1);
  7744. case 'day': // intentional fall through
  7745. case 'weekday': this.current.setHours(0);
  7746. case 'hour': this.current.setMinutes(0);
  7747. case 'minute': this.current.setSeconds(0);
  7748. case 'second': this.current.setMilliseconds(0);
  7749. //case 'millisecond': // nothing to do for milliseconds
  7750. }
  7751. if (this.step != 1) {
  7752. // round down to the first minor value that is a multiple of the current step size
  7753. switch (this.scale) {
  7754. case 'millisecond': this.current.setMilliseconds(this.current.getMilliseconds() - this.current.getMilliseconds() % this.step); break;
  7755. case 'second': this.current.setSeconds(this.current.getSeconds() - this.current.getSeconds() % this.step); break;
  7756. case 'minute': this.current.setMinutes(this.current.getMinutes() - this.current.getMinutes() % this.step); break;
  7757. case 'hour': this.current.setHours(this.current.getHours() - this.current.getHours() % this.step); break;
  7758. case 'weekday': // intentional fall through
  7759. case 'day': this.current.setDate((this.current.getDate()-1) - (this.current.getDate()-1) % this.step + 1); break;
  7760. case 'month': this.current.setMonth(this.current.getMonth() - this.current.getMonth() % this.step); break;
  7761. case 'year': this.current.setFullYear(this.current.getFullYear() - this.current.getFullYear() % this.step); break;
  7762. default: break;
  7763. }
  7764. }
  7765. };
  7766. /**
  7767. * Check if the there is a next step
  7768. * @return {boolean} true if the current date has not passed the end date
  7769. */
  7770. TimeStep.prototype.hasNext = function () {
  7771. return (this.current.valueOf() <= this._end.valueOf());
  7772. };
  7773. /**
  7774. * Do the next step
  7775. */
  7776. TimeStep.prototype.next = function() {
  7777. var prev = this.current.valueOf();
  7778. // Two cases, needed to prevent issues with switching daylight savings
  7779. // (end of March and end of October)
  7780. if (this.current.getMonth() < 6) {
  7781. switch (this.scale) {
  7782. case 'millisecond':
  7783. this.current = new Date(this.current.valueOf() + this.step); break;
  7784. case 'second': this.current = new Date(this.current.valueOf() + this.step * 1000); break;
  7785. case 'minute': this.current = new Date(this.current.valueOf() + this.step * 1000 * 60); break;
  7786. case 'hour':
  7787. this.current = new Date(this.current.valueOf() + this.step * 1000 * 60 * 60);
  7788. // in case of skipping an hour for daylight savings, adjust the hour again (else you get: 0h 5h 9h ... instead of 0h 4h 8h ...)
  7789. var h = this.current.getHours();
  7790. this.current.setHours(h - (h % this.step));
  7791. break;
  7792. case 'weekday': // intentional fall through
  7793. case 'day': this.current.setDate(this.current.getDate() + this.step); break;
  7794. case 'month': this.current.setMonth(this.current.getMonth() + this.step); break;
  7795. case 'year': this.current.setFullYear(this.current.getFullYear() + this.step); break;
  7796. default: break;
  7797. }
  7798. }
  7799. else {
  7800. switch (this.scale) {
  7801. case 'millisecond': this.current = new Date(this.current.valueOf() + this.step); break;
  7802. case 'second': this.current.setSeconds(this.current.getSeconds() + this.step); break;
  7803. case 'minute': this.current.setMinutes(this.current.getMinutes() + this.step); break;
  7804. case 'hour': this.current.setHours(this.current.getHours() + this.step); break;
  7805. case 'weekday': // intentional fall through
  7806. case 'day': this.current.setDate(this.current.getDate() + this.step); break;
  7807. case 'month': this.current.setMonth(this.current.getMonth() + this.step); break;
  7808. case 'year': this.current.setFullYear(this.current.getFullYear() + this.step); break;
  7809. default: break;
  7810. }
  7811. }
  7812. if (this.step != 1) {
  7813. // round down to the correct major value
  7814. switch (this.scale) {
  7815. case 'millisecond': if(this.current.getMilliseconds() < this.step) this.current.setMilliseconds(0); break;
  7816. case 'second': if(this.current.getSeconds() < this.step) this.current.setSeconds(0); break;
  7817. case 'minute': if(this.current.getMinutes() < this.step) this.current.setMinutes(0); break;
  7818. case 'hour': if(this.current.getHours() < this.step) this.current.setHours(0); break;
  7819. case 'weekday': // intentional fall through
  7820. case 'day': if(this.current.getDate() < this.step+1) this.current.setDate(1); break;
  7821. case 'month': if(this.current.getMonth() < this.step) this.current.setMonth(0); break;
  7822. case 'year': break; // nothing to do for year
  7823. default: break;
  7824. }
  7825. }
  7826. // safety mechanism: if current time is still unchanged, move to the end
  7827. if (this.current.valueOf() == prev) {
  7828. this.current = new Date(this._end.valueOf());
  7829. }
  7830. DateUtil.stepOverHiddenDates(this, prev);
  7831. };
  7832. /**
  7833. * Get the current datetime
  7834. * @return {Date} current The current date
  7835. */
  7836. TimeStep.prototype.getCurrent = function() {
  7837. return this.current;
  7838. };
  7839. /**
  7840. * Set a custom scale. Autoscaling will be disabled.
  7841. * For example setScale('minute', 5) will result
  7842. * in minor steps of 5 minutes, and major steps of an hour.
  7843. *
  7844. * @param {{scale: string, step: number}} params
  7845. * An object containing two properties:
  7846. * - A string 'scale'. Choose from 'millisecond', 'second',
  7847. * 'minute', 'hour', 'weekday, 'day, 'month, 'year'.
  7848. * - A number 'step'. A step size, by default 1.
  7849. * Choose for example 1, 2, 5, or 10.
  7850. */
  7851. TimeStep.prototype.setScale = function(params) {
  7852. if (params && typeof params.scale == 'string') {
  7853. this.scale = params.scale;
  7854. this.step = params.step > 0 ? params.step : 1;
  7855. this.autoScale = false;
  7856. }
  7857. };
  7858. /**
  7859. * Enable or disable autoscaling
  7860. * @param {boolean} enable If true, autoascaling is set true
  7861. */
  7862. TimeStep.prototype.setAutoScale = function (enable) {
  7863. this.autoScale = enable;
  7864. };
  7865. /**
  7866. * Automatically determine the scale that bests fits the provided minimum step
  7867. * @param {Number} [minimumStep] The minimum step size in milliseconds
  7868. */
  7869. TimeStep.prototype.setMinimumStep = function(minimumStep) {
  7870. if (minimumStep == undefined) {
  7871. return;
  7872. }
  7873. //var b = asc + ds;
  7874. var stepYear = (1000 * 60 * 60 * 24 * 30 * 12);
  7875. var stepMonth = (1000 * 60 * 60 * 24 * 30);
  7876. var stepDay = (1000 * 60 * 60 * 24);
  7877. var stepHour = (1000 * 60 * 60);
  7878. var stepMinute = (1000 * 60);
  7879. var stepSecond = (1000);
  7880. var stepMillisecond= (1);
  7881. // find the smallest step that is larger than the provided minimumStep
  7882. if (stepYear*1000 > minimumStep) {this.scale = 'year'; this.step = 1000;}
  7883. if (stepYear*500 > minimumStep) {this.scale = 'year'; this.step = 500;}
  7884. if (stepYear*100 > minimumStep) {this.scale = 'year'; this.step = 100;}
  7885. if (stepYear*50 > minimumStep) {this.scale = 'year'; this.step = 50;}
  7886. if (stepYear*10 > minimumStep) {this.scale = 'year'; this.step = 10;}
  7887. if (stepYear*5 > minimumStep) {this.scale = 'year'; this.step = 5;}
  7888. if (stepYear > minimumStep) {this.scale = 'year'; this.step = 1;}
  7889. if (stepMonth*3 > minimumStep) {this.scale = 'month'; this.step = 3;}
  7890. if (stepMonth > minimumStep) {this.scale = 'month'; this.step = 1;}
  7891. if (stepDay*5 > minimumStep) {this.scale = 'day'; this.step = 5;}
  7892. if (stepDay*2 > minimumStep) {this.scale = 'day'; this.step = 2;}
  7893. if (stepDay > minimumStep) {this.scale = 'day'; this.step = 1;}
  7894. if (stepDay/2 > minimumStep) {this.scale = 'weekday'; this.step = 1;}
  7895. if (stepHour*4 > minimumStep) {this.scale = 'hour'; this.step = 4;}
  7896. if (stepHour > minimumStep) {this.scale = 'hour'; this.step = 1;}
  7897. if (stepMinute*15 > minimumStep) {this.scale = 'minute'; this.step = 15;}
  7898. if (stepMinute*10 > minimumStep) {this.scale = 'minute'; this.step = 10;}
  7899. if (stepMinute*5 > minimumStep) {this.scale = 'minute'; this.step = 5;}
  7900. if (stepMinute > minimumStep) {this.scale = 'minute'; this.step = 1;}
  7901. if (stepSecond*15 > minimumStep) {this.scale = 'second'; this.step = 15;}
  7902. if (stepSecond*10 > minimumStep) {this.scale = 'second'; this.step = 10;}
  7903. if (stepSecond*5 > minimumStep) {this.scale = 'second'; this.step = 5;}
  7904. if (stepSecond > minimumStep) {this.scale = 'second'; this.step = 1;}
  7905. if (stepMillisecond*200 > minimumStep) {this.scale = 'millisecond'; this.step = 200;}
  7906. if (stepMillisecond*100 > minimumStep) {this.scale = 'millisecond'; this.step = 100;}
  7907. if (stepMillisecond*50 > minimumStep) {this.scale = 'millisecond'; this.step = 50;}
  7908. if (stepMillisecond*10 > minimumStep) {this.scale = 'millisecond'; this.step = 10;}
  7909. if (stepMillisecond*5 > minimumStep) {this.scale = 'millisecond'; this.step = 5;}
  7910. if (stepMillisecond > minimumStep) {this.scale = 'millisecond'; this.step = 1;}
  7911. };
  7912. /**
  7913. * Snap a date to a rounded value.
  7914. * The snap intervals are dependent on the current scale and step.
  7915. * Static function
  7916. * @param {Date} date the date to be snapped.
  7917. * @param {string} scale Current scale, can be 'millisecond', 'second',
  7918. * 'minute', 'hour', 'weekday, 'day, 'month, 'year'.
  7919. * @param {number} step Current step (1, 2, 4, 5, ...
  7920. * @return {Date} snappedDate
  7921. */
  7922. TimeStep.snap = function(date, scale, step) {
  7923. var clone = new Date(date.valueOf());
  7924. if (scale == 'year') {
  7925. var year = clone.getFullYear() + Math.round(clone.getMonth() / 12);
  7926. clone.setFullYear(Math.round(year / step) * step);
  7927. clone.setMonth(0);
  7928. clone.setDate(0);
  7929. clone.setHours(0);
  7930. clone.setMinutes(0);
  7931. clone.setSeconds(0);
  7932. clone.setMilliseconds(0);
  7933. }
  7934. else if (scale == 'month') {
  7935. if (clone.getDate() > 15) {
  7936. clone.setDate(1);
  7937. clone.setMonth(clone.getMonth() + 1);
  7938. // important: first set Date to 1, after that change the month.
  7939. }
  7940. else {
  7941. clone.setDate(1);
  7942. }
  7943. clone.setHours(0);
  7944. clone.setMinutes(0);
  7945. clone.setSeconds(0);
  7946. clone.setMilliseconds(0);
  7947. }
  7948. else if (scale == 'day') {
  7949. //noinspection FallthroughInSwitchStatementJS
  7950. switch (step) {
  7951. case 5:
  7952. case 2:
  7953. clone.setHours(Math.round(clone.getHours() / 24) * 24); break;
  7954. default:
  7955. clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
  7956. }
  7957. clone.setMinutes(0);
  7958. clone.setSeconds(0);
  7959. clone.setMilliseconds(0);
  7960. }
  7961. else if (scale == 'weekday') {
  7962. //noinspection FallthroughInSwitchStatementJS
  7963. switch (step) {
  7964. case 5:
  7965. case 2:
  7966. clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
  7967. default:
  7968. clone.setHours(Math.round(clone.getHours() / 6) * 6); break;
  7969. }
  7970. clone.setMinutes(0);
  7971. clone.setSeconds(0);
  7972. clone.setMilliseconds(0);
  7973. }
  7974. else if (scale == 'hour') {
  7975. switch (step) {
  7976. case 4:
  7977. clone.setMinutes(Math.round(clone.getMinutes() / 60) * 60); break;
  7978. default:
  7979. clone.setMinutes(Math.round(clone.getMinutes() / 30) * 30); break;
  7980. }
  7981. clone.setSeconds(0);
  7982. clone.setMilliseconds(0);
  7983. } else if (scale == 'minute') {
  7984. //noinspection FallthroughInSwitchStatementJS
  7985. switch (step) {
  7986. case 15:
  7987. case 10:
  7988. clone.setMinutes(Math.round(clone.getMinutes() / 5) * 5);
  7989. clone.setSeconds(0);
  7990. break;
  7991. case 5:
  7992. clone.setSeconds(Math.round(clone.getSeconds() / 60) * 60); break;
  7993. default:
  7994. clone.setSeconds(Math.round(clone.getSeconds() / 30) * 30); break;
  7995. }
  7996. clone.setMilliseconds(0);
  7997. }
  7998. else if (scale == 'second') {
  7999. //noinspection FallthroughInSwitchStatementJS
  8000. switch (step) {
  8001. case 15:
  8002. case 10:
  8003. clone.setSeconds(Math.round(clone.getSeconds() / 5) * 5);
  8004. clone.setMilliseconds(0);
  8005. break;
  8006. case 5:
  8007. clone.setMilliseconds(Math.round(clone.getMilliseconds() / 1000) * 1000); break;
  8008. default:
  8009. clone.setMilliseconds(Math.round(clone.getMilliseconds() / 500) * 500); break;
  8010. }
  8011. }
  8012. else if (scale == 'millisecond') {
  8013. var _step = step > 5 ? step / 2 : 1;
  8014. clone.setMilliseconds(Math.round(clone.getMilliseconds() / _step) * _step);
  8015. }
  8016. return clone;
  8017. };
  8018. /**
  8019. * Check if the current value is a major value (for example when the step
  8020. * is DAY, a major value is each first day of the MONTH)
  8021. * @return {boolean} true if current date is major, else false.
  8022. */
  8023. TimeStep.prototype.isMajor = function() {
  8024. if (this.switchedYear == true) {
  8025. this.switchedYear = false;
  8026. switch (this.scale) {
  8027. case 'year':
  8028. case 'month':
  8029. case 'weekday':
  8030. case 'day':
  8031. case 'hour':
  8032. case 'minute':
  8033. case 'second':
  8034. case 'millisecond':
  8035. return true;
  8036. default:
  8037. return false;
  8038. }
  8039. }
  8040. else if (this.switchedMonth == true) {
  8041. this.switchedMonth = false;
  8042. switch (this.scale) {
  8043. case 'weekday':
  8044. case 'day':
  8045. case 'hour':
  8046. case 'minute':
  8047. case 'second':
  8048. case 'millisecond':
  8049. return true;
  8050. default:
  8051. return false;
  8052. }
  8053. }
  8054. else if (this.switchedDay == true) {
  8055. this.switchedDay = false;
  8056. switch (this.scale) {
  8057. case 'millisecond':
  8058. case 'second':
  8059. case 'minute':
  8060. case 'hour':
  8061. return true;
  8062. default:
  8063. return false;
  8064. }
  8065. }
  8066. switch (this.scale) {
  8067. case 'millisecond':
  8068. return (this.current.getMilliseconds() == 0);
  8069. case 'second':
  8070. return (this.current.getSeconds() == 0);
  8071. case 'minute':
  8072. return (this.current.getHours() == 0) && (this.current.getMinutes() == 0);
  8073. case 'hour':
  8074. return (this.current.getHours() == 0);
  8075. case 'weekday': // intentional fall through
  8076. case 'day':
  8077. return (this.current.getDate() == 1);
  8078. case 'month':
  8079. return (this.current.getMonth() == 0);
  8080. case 'year':
  8081. return false;
  8082. default:
  8083. return false;
  8084. }
  8085. };
  8086. /**
  8087. * Returns formatted text for the minor axislabel, depending on the current
  8088. * date and the scale. For example when scale is MINUTE, the current time is
  8089. * formatted as "hh:mm".
  8090. * @param {Date} [date] custom date. if not provided, current date is taken
  8091. */
  8092. TimeStep.prototype.getLabelMinor = function (date) {
  8093. if (date == undefined) {
  8094. date = this.current;
  8095. }
  8096. if (date instanceof Date) {
  8097. date = moment(date);
  8098. }
  8099. if (typeof this.format.minorLabels === "function") {
  8100. return this.format.minorLabels(date, this.scale, this.step);
  8101. }
  8102. var format = this.format.minorLabels[this.scale];
  8103. // noinspection FallThroughInSwitchStatementJS
  8104. switch (this.scale) {
  8105. case 'week':
  8106. if (this.isMajor() && date.weekday() !== 0) {
  8107. return "";
  8108. }
  8109. default:
  8110. // eslint-disable-line no-fallthrough
  8111. return format && format.length > 0 ? moment(date).format(format) : '';
  8112. }
  8113. };
  8114. /**
  8115. * Returns formatted text for the major axis label, depending on the current
  8116. * date and the scale. For example when scale is MINUTE, the major scale is
  8117. * hours, and the hour will be formatted as "hh".
  8118. * @param {Date} [date] custom date. if not provided, current date is taken
  8119. */
  8120. TimeStep.prototype.getLabelMajor = function (date) {
  8121. if (date == undefined) {
  8122. date = this.current;
  8123. }
  8124. if (date instanceof Date) {
  8125. date = moment(date);
  8126. }
  8127. if (typeof this.format.majorLabels === "function") {
  8128. return this.format.majorLabels(date, this.scale, this.step);
  8129. }
  8130. var format = this.format.majorLabels[this.scale];
  8131. return format && format.length > 0 ? moment(date).format(format) : '';
  8132. };
  8133. TimeStep.prototype.getClassName = function() {
  8134. var m = moment(this.current);
  8135. var date = m.locale ? m.locale('en') : m.lang('en'); // old versions of moment have .lang() function
  8136. var step = this.step;
  8137. function even(value) {
  8138. return (value / step % 2 == 0) ? ' even' : ' odd';
  8139. }
  8140. function today(date) {
  8141. if (date.isSame(new Date(), 'day')) {
  8142. return ' today';
  8143. }
  8144. if (date.isSame(moment().add(1, 'day'), 'day')) {
  8145. return ' tomorrow';
  8146. }
  8147. if (date.isSame(moment().add(-1, 'day'), 'day')) {
  8148. return ' yesterday';
  8149. }
  8150. return '';
  8151. }
  8152. function currentWeek(date) {
  8153. return date.isSame(new Date(), 'week') ? ' current-week' : '';
  8154. }
  8155. function currentMonth(date) {
  8156. return date.isSame(new Date(), 'month') ? ' current-month' : '';
  8157. }
  8158. function currentYear(date) {
  8159. return date.isSame(new Date(), 'year') ? ' current-year' : '';
  8160. }
  8161. switch (this.scale) {
  8162. case 'millisecond':
  8163. return even(date.milliseconds()).trim();
  8164. case 'second':
  8165. return even(date.seconds()).trim();
  8166. case 'minute':
  8167. return even(date.minutes()).trim();
  8168. case 'hour':
  8169. var hours = date.hours();
  8170. if (this.step == 4) {
  8171. hours = hours + '-' + (hours + 4);
  8172. }
  8173. return hours + 'h' + today(date) + even(date.hours());
  8174. case 'weekday':
  8175. return date.format('dddd').toLowerCase() +
  8176. today(date) + currentWeek(date) + even(date.date());
  8177. case 'day':
  8178. var day = date.date();
  8179. var month = date.format('MMMM').toLowerCase();
  8180. return 'day' + day + ' ' + month + currentMonth(date) + even(day - 1);
  8181. case 'month':
  8182. return date.format('MMMM').toLowerCase() +
  8183. currentMonth(date) + even(date.month());
  8184. case 'year':
  8185. var year = date.year();
  8186. return 'year' + year + currentYear(date)+ even(year);
  8187. default:
  8188. return '';
  8189. }
  8190. };
  8191. module.exports = TimeStep;
  8192. /***/ },
  8193. /* 20 */
  8194. /***/ function(module, exports, __webpack_require__) {
  8195. var Hammer = __webpack_require__(45);
  8196. var util = __webpack_require__(1);
  8197. /**
  8198. * @constructor Item
  8199. * @param {Object} data Object containing (optional) parameters type,
  8200. * start, end, content, group, className.
  8201. * @param {{toScreen: function, toTime: function}} conversion
  8202. * Conversion functions from time to screen and vice versa
  8203. * @param {Object} options Configuration options
  8204. * // TODO: describe available options
  8205. */
  8206. function Item (data, conversion, options) {
  8207. this.id = null;
  8208. this.parent = null;
  8209. this.data = data;
  8210. this.dom = null;
  8211. this.conversion = conversion || {};
  8212. this.options = options || {};
  8213. this.selected = false;
  8214. this.displayed = false;
  8215. this.dirty = true;
  8216. this.top = null;
  8217. this.left = null;
  8218. this.width = null;
  8219. this.height = null;
  8220. }
  8221. Item.prototype.stack = true;
  8222. /**
  8223. * Select current item
  8224. */
  8225. Item.prototype.select = function() {
  8226. this.selected = true;
  8227. this.dirty = true;
  8228. if (this.displayed) this.redraw();
  8229. };
  8230. /**
  8231. * Unselect current item
  8232. */
  8233. Item.prototype.unselect = function() {
  8234. this.selected = false;
  8235. this.dirty = true;
  8236. if (this.displayed) this.redraw();
  8237. };
  8238. /**
  8239. * Set data for the item. Existing data will be updated. The id should not
  8240. * be changed. When the item is displayed, it will be redrawn immediately.
  8241. * @param {Object} data
  8242. */
  8243. Item.prototype.setData = function(data) {
  8244. var groupChanged = data.group != undefined && this.data.group != data.group;
  8245. if (groupChanged) {
  8246. this.parent.itemSet._moveToGroup(this, data.group);
  8247. }
  8248. this.data = data;
  8249. this.dirty = true;
  8250. if (this.displayed) this.redraw();
  8251. };
  8252. /**
  8253. * Set a parent for the item
  8254. * @param {ItemSet | Group} parent
  8255. */
  8256. Item.prototype.setParent = function(parent) {
  8257. if (this.displayed) {
  8258. this.hide();
  8259. this.parent = parent;
  8260. if (this.parent) {
  8261. this.show();
  8262. }
  8263. }
  8264. else {
  8265. this.parent = parent;
  8266. }
  8267. };
  8268. /**
  8269. * Check whether this item is visible inside given range
  8270. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  8271. * @returns {boolean} True if visible
  8272. */
  8273. Item.prototype.isVisible = function(range) {
  8274. // Should be implemented by Item implementations
  8275. return false;
  8276. };
  8277. /**
  8278. * Show the Item in the DOM (when not already visible)
  8279. * @return {Boolean} changed
  8280. */
  8281. Item.prototype.show = function() {
  8282. return false;
  8283. };
  8284. /**
  8285. * Hide the Item from the DOM (when visible)
  8286. * @return {Boolean} changed
  8287. */
  8288. Item.prototype.hide = function() {
  8289. return false;
  8290. };
  8291. /**
  8292. * Repaint the item
  8293. */
  8294. Item.prototype.redraw = function() {
  8295. // should be implemented by the item
  8296. };
  8297. /**
  8298. * Reposition the Item horizontally
  8299. */
  8300. Item.prototype.repositionX = function() {
  8301. // should be implemented by the item
  8302. };
  8303. /**
  8304. * Reposition the Item vertically
  8305. */
  8306. Item.prototype.repositionY = function() {
  8307. // should be implemented by the item
  8308. };
  8309. /**
  8310. * Repaint a delete button on the top right of the item when the item is selected
  8311. * @param {HTMLElement} anchor
  8312. * @protected
  8313. */
  8314. Item.prototype._repaintDeleteButton = function (anchor) {
  8315. if (this.selected && this.options.editable.remove && !this.dom.deleteButton) {
  8316. // create and show button
  8317. var me = this;
  8318. var deleteButton = document.createElement('div');
  8319. deleteButton.className = 'delete';
  8320. deleteButton.title = 'Delete this item';
  8321. Hammer(deleteButton, {
  8322. preventDefault: true
  8323. }).on('tap', function (event) {
  8324. event.preventDefault();
  8325. event.stopPropagation();
  8326. me.parent.removeFromDataSet(me);
  8327. });
  8328. anchor.appendChild(deleteButton);
  8329. this.dom.deleteButton = deleteButton;
  8330. }
  8331. else if (!this.selected && this.dom.deleteButton) {
  8332. // remove button
  8333. if (this.dom.deleteButton.parentNode) {
  8334. this.dom.deleteButton.parentNode.removeChild(this.dom.deleteButton);
  8335. }
  8336. this.dom.deleteButton = null;
  8337. }
  8338. };
  8339. /**
  8340. * Set HTML contents for the item
  8341. * @param {Element} element HTML element to fill with the contents
  8342. * @private
  8343. */
  8344. Item.prototype._updateContents = function (element) {
  8345. var content;
  8346. if (this.options.template) {
  8347. var itemData = this.parent.itemSet.itemsData.get(this.id); // get a clone of the data from the dataset
  8348. content = this.options.template(itemData);
  8349. }
  8350. else {
  8351. content = this.data.content;
  8352. }
  8353. if(content !== this.content) {
  8354. // only replace the content when changed
  8355. if (content instanceof Element) {
  8356. element.innerHTML = '';
  8357. element.appendChild(content);
  8358. }
  8359. else if (content != undefined) {
  8360. element.innerHTML = content;
  8361. }
  8362. else {
  8363. if (!(this.data.type == 'background' && this.data.content === undefined)) {
  8364. throw new Error('Property "content" missing in item ' + this.id);
  8365. }
  8366. }
  8367. this.content = content;
  8368. }
  8369. };
  8370. /**
  8371. * Set HTML contents for the item
  8372. * @param {Element} element HTML element to fill with the contents
  8373. * @private
  8374. */
  8375. Item.prototype._updateTitle = function (element) {
  8376. if (this.data.title != null) {
  8377. element.title = this.data.title || '';
  8378. }
  8379. else {
  8380. element.removeAttribute('title');
  8381. }
  8382. };
  8383. /**
  8384. * Process dataAttributes timeline option and set as data- attributes on dom.content
  8385. * @param {Element} element HTML element to which the attributes will be attached
  8386. * @private
  8387. */
  8388. Item.prototype._updateDataAttributes = function(element) {
  8389. if (this.options.dataAttributes && this.options.dataAttributes.length > 0) {
  8390. var attributes = [];
  8391. if (Array.isArray(this.options.dataAttributes)) {
  8392. attributes = this.options.dataAttributes;
  8393. }
  8394. else if (this.options.dataAttributes == 'all') {
  8395. attributes = Object.keys(this.data);
  8396. }
  8397. else {
  8398. return;
  8399. }
  8400. for (var i = 0; i < attributes.length; i++) {
  8401. var name = attributes[i];
  8402. var value = this.data[name];
  8403. if (value != null) {
  8404. element.setAttribute('data-' + name, value);
  8405. }
  8406. else {
  8407. element.removeAttribute('data-' + name);
  8408. }
  8409. }
  8410. }
  8411. };
  8412. /**
  8413. * Update custom styles of the element
  8414. * @param element
  8415. * @private
  8416. */
  8417. Item.prototype._updateStyle = function(element) {
  8418. // remove old styles
  8419. if (this.style) {
  8420. util.removeCssText(element, this.style);
  8421. this.style = null;
  8422. }
  8423. // append new styles
  8424. if (this.data.style) {
  8425. util.addCssText(element, this.data.style);
  8426. this.style = this.data.style;
  8427. }
  8428. };
  8429. module.exports = Item;
  8430. /***/ },
  8431. /* 21 */
  8432. /***/ function(module, exports, __webpack_require__) {
  8433. var Hammer = __webpack_require__(45);
  8434. var Item = __webpack_require__(20);
  8435. var BackgroundGroup = __webpack_require__(31);
  8436. var RangeItem = __webpack_require__(24);
  8437. /**
  8438. * @constructor BackgroundItem
  8439. * @extends Item
  8440. * @param {Object} data Object containing parameters start, end
  8441. * content, className.
  8442. * @param {{toScreen: function, toTime: function}} conversion
  8443. * Conversion functions from time to screen and vice versa
  8444. * @param {Object} [options] Configuration options
  8445. * // TODO: describe options
  8446. */
  8447. // TODO: implement support for the BackgroundItem just having a start, then being displayed as a sort of an annotation
  8448. function BackgroundItem (data, conversion, options) {
  8449. this.props = {
  8450. content: {
  8451. width: 0
  8452. }
  8453. };
  8454. this.overflow = false; // if contents can overflow (css styling), this flag is set to true
  8455. // validate data
  8456. if (data) {
  8457. if (data.start == undefined) {
  8458. throw new Error('Property "start" missing in item ' + data.id);
  8459. }
  8460. if (data.end == undefined) {
  8461. throw new Error('Property "end" missing in item ' + data.id);
  8462. }
  8463. }
  8464. Item.call(this, data, conversion, options);
  8465. this.emptyContent = false;
  8466. }
  8467. BackgroundItem.prototype = new Item (null, null, null);
  8468. BackgroundItem.prototype.baseClassName = 'item background';
  8469. BackgroundItem.prototype.stack = false;
  8470. /**
  8471. * Check whether this item is visible inside given range
  8472. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  8473. * @returns {boolean} True if visible
  8474. */
  8475. BackgroundItem.prototype.isVisible = function(range) {
  8476. // determine visibility
  8477. return (this.data.start < range.end) && (this.data.end > range.start);
  8478. };
  8479. /**
  8480. * Repaint the item
  8481. */
  8482. BackgroundItem.prototype.redraw = function() {
  8483. var dom = this.dom;
  8484. if (!dom) {
  8485. // create DOM
  8486. this.dom = {};
  8487. dom = this.dom;
  8488. // background box
  8489. dom.box = document.createElement('div');
  8490. // className is updated in redraw()
  8491. // contents box
  8492. dom.content = document.createElement('div');
  8493. dom.content.className = 'content';
  8494. dom.box.appendChild(dom.content);
  8495. // Note: we do NOT attach this item as attribute to the DOM,
  8496. // such that background items cannot be selected
  8497. //dom.box['timeline-item'] = this;
  8498. this.dirty = true;
  8499. }
  8500. // append DOM to parent DOM
  8501. if (!this.parent) {
  8502. throw new Error('Cannot redraw item: no parent attached');
  8503. }
  8504. if (!dom.box.parentNode) {
  8505. var background = this.parent.dom.background;
  8506. if (!background) {
  8507. throw new Error('Cannot redraw item: parent has no background container element');
  8508. }
  8509. background.appendChild(dom.box);
  8510. }
  8511. this.displayed = true;
  8512. // Update DOM when item is marked dirty. An item is marked dirty when:
  8513. // - the item is not yet rendered
  8514. // - the item's data is changed
  8515. // - the item is selected/deselected
  8516. if (this.dirty) {
  8517. this._updateContents(this.dom.content);
  8518. this._updateTitle(this.dom.content);
  8519. this._updateDataAttributes(this.dom.content);
  8520. this._updateStyle(this.dom.box);
  8521. // update class
  8522. var className = (this.data.className ? (' ' + this.data.className) : '') +
  8523. (this.selected ? ' selected' : '');
  8524. dom.box.className = this.baseClassName + className;
  8525. // determine from css whether this box has overflow
  8526. this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden';
  8527. // recalculate size
  8528. this.props.content.width = this.dom.content.offsetWidth;
  8529. this.height = 0; // set height zero, so this item will be ignored when stacking items
  8530. this.dirty = false;
  8531. }
  8532. };
  8533. /**
  8534. * Show the item in the DOM (when not already visible). The items DOM will
  8535. * be created when needed.
  8536. */
  8537. BackgroundItem.prototype.show = RangeItem.prototype.show;
  8538. /**
  8539. * Hide the item from the DOM (when visible)
  8540. * @return {Boolean} changed
  8541. */
  8542. BackgroundItem.prototype.hide = RangeItem.prototype.hide;
  8543. /**
  8544. * Reposition the item horizontally
  8545. * @Override
  8546. */
  8547. BackgroundItem.prototype.repositionX = RangeItem.prototype.repositionX;
  8548. /**
  8549. * Reposition the item vertically
  8550. * @Override
  8551. */
  8552. BackgroundItem.prototype.repositionY = function(margin) {
  8553. var onTop = this.options.orientation === 'top';
  8554. this.dom.content.style.top = onTop ? '' : '0';
  8555. this.dom.content.style.bottom = onTop ? '0' : '';
  8556. var height;
  8557. // special positioning for subgroups
  8558. if (this.data.subgroup !== undefined) {
  8559. // TODO: instead of calculating the top position of the subgroups here for every BackgroundItem, calculate the top of the subgroup once in Itemset
  8560. var itemSubgroup = this.data.subgroup;
  8561. var subgroups = this.parent.subgroups;
  8562. var subgroupIndex = subgroups[itemSubgroup].index;
  8563. // if the orientation is top, we need to take the difference in height into account.
  8564. if (onTop == true) {
  8565. // the first subgroup will have to account for the distance from the top to the first item.
  8566. height = this.parent.subgroups[itemSubgroup].height + margin.item.vertical;
  8567. height += subgroupIndex == 0 ? margin.axis - 0.5*margin.item.vertical : 0;
  8568. var newTop = this.parent.top;
  8569. for (var subgroup in subgroups) {
  8570. if (subgroups.hasOwnProperty(subgroup)) {
  8571. if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroupIndex) {
  8572. newTop += subgroups[subgroup].height + margin.item.vertical;
  8573. }
  8574. }
  8575. }
  8576. // the others will have to be offset downwards with this same distance.
  8577. newTop += subgroupIndex != 0 ? margin.axis - 0.5 * margin.item.vertical : 0;
  8578. this.dom.box.style.top = newTop + 'px';
  8579. this.dom.box.style.bottom = '';
  8580. }
  8581. // and when the orientation is bottom:
  8582. else {
  8583. var newTop = this.parent.top;
  8584. var totalHeight = 0;
  8585. for (var subgroup in subgroups) {
  8586. if (subgroups.hasOwnProperty(subgroup)) {
  8587. if (subgroups[subgroup].visible == true) {
  8588. var newHeight = subgroups[subgroup].height + margin.item.vertical;
  8589. totalHeight += newHeight;
  8590. if (subgroups[subgroup].index > subgroupIndex) {
  8591. newTop += newHeight;
  8592. }
  8593. }
  8594. }
  8595. }
  8596. height = this.parent.subgroups[itemSubgroup].height + margin.item.vertical;
  8597. this.dom.box.style.top = (this.parent.height - totalHeight + newTop) + 'px';
  8598. this.dom.box.style.bottom = '';
  8599. }
  8600. }
  8601. // and in the case of no subgroups:
  8602. else {
  8603. // we want backgrounds with groups to only show in groups.
  8604. if (this.parent instanceof BackgroundGroup) {
  8605. // if the item is not in a group:
  8606. height = Math.max(this.parent.height,
  8607. this.parent.itemSet.body.domProps.center.height,
  8608. this.parent.itemSet.body.domProps.centerContainer.height);
  8609. this.dom.box.style.top = onTop ? '0' : '';
  8610. this.dom.box.style.bottom = onTop ? '' : '0';
  8611. }
  8612. else {
  8613. height = this.parent.height;
  8614. // same alignment for items when orientation is top or bottom
  8615. this.dom.box.style.top = this.parent.top + 'px';
  8616. this.dom.box.style.bottom = '';
  8617. }
  8618. }
  8619. this.dom.box.style.height = height + 'px';
  8620. };
  8621. module.exports = BackgroundItem;
  8622. /***/ },
  8623. /* 22 */
  8624. /***/ function(module, exports, __webpack_require__) {
  8625. var Item = __webpack_require__(20);
  8626. var util = __webpack_require__(1);
  8627. /**
  8628. * @constructor BoxItem
  8629. * @extends Item
  8630. * @param {Object} data Object containing parameters start
  8631. * content, className.
  8632. * @param {{toScreen: function, toTime: function}} conversion
  8633. * Conversion functions from time to screen and vice versa
  8634. * @param {Object} [options] Configuration options
  8635. * // TODO: describe available options
  8636. */
  8637. function BoxItem (data, conversion, options) {
  8638. this.props = {
  8639. dot: {
  8640. width: 0,
  8641. height: 0
  8642. },
  8643. line: {
  8644. width: 0,
  8645. height: 0
  8646. }
  8647. };
  8648. // validate data
  8649. if (data) {
  8650. if (data.start == undefined) {
  8651. throw new Error('Property "start" missing in item ' + data);
  8652. }
  8653. }
  8654. Item.call(this, data, conversion, options);
  8655. }
  8656. BoxItem.prototype = new Item (null, null, null);
  8657. /**
  8658. * Check whether this item is visible inside given range
  8659. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  8660. * @returns {boolean} True if visible
  8661. */
  8662. BoxItem.prototype.isVisible = function(range) {
  8663. // determine visibility
  8664. // TODO: account for the real width of the item. Right now we just add 1/4 to the window
  8665. var interval = (range.end - range.start) / 4;
  8666. return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
  8667. };
  8668. /**
  8669. * Repaint the item
  8670. */
  8671. BoxItem.prototype.redraw = function() {
  8672. var dom = this.dom;
  8673. if (!dom) {
  8674. // create DOM
  8675. this.dom = {};
  8676. dom = this.dom;
  8677. // create main box
  8678. dom.box = document.createElement('DIV');
  8679. // contents box (inside the background box). used for making margins
  8680. dom.content = document.createElement('DIV');
  8681. dom.content.className = 'content';
  8682. dom.box.appendChild(dom.content);
  8683. // line to axis
  8684. dom.line = document.createElement('DIV');
  8685. dom.line.className = 'line';
  8686. // dot on axis
  8687. dom.dot = document.createElement('DIV');
  8688. dom.dot.className = 'dot';
  8689. // attach this item as attribute
  8690. dom.box['timeline-item'] = this;
  8691. this.dirty = true;
  8692. }
  8693. // append DOM to parent DOM
  8694. if (!this.parent) {
  8695. throw new Error('Cannot redraw item: no parent attached');
  8696. }
  8697. if (!dom.box.parentNode) {
  8698. var foreground = this.parent.dom.foreground;
  8699. if (!foreground) throw new Error('Cannot redraw item: parent has no foreground container element');
  8700. foreground.appendChild(dom.box);
  8701. }
  8702. if (!dom.line.parentNode) {
  8703. var background = this.parent.dom.background;
  8704. if (!background) throw new Error('Cannot redraw item: parent has no background container element');
  8705. background.appendChild(dom.line);
  8706. }
  8707. if (!dom.dot.parentNode) {
  8708. var axis = this.parent.dom.axis;
  8709. if (!background) throw new Error('Cannot redraw item: parent has no axis container element');
  8710. axis.appendChild(dom.dot);
  8711. }
  8712. this.displayed = true;
  8713. // Update DOM when item is marked dirty. An item is marked dirty when:
  8714. // - the item is not yet rendered
  8715. // - the item's data is changed
  8716. // - the item is selected/deselected
  8717. if (this.dirty) {
  8718. this._updateContents(this.dom.content);
  8719. this._updateTitle(this.dom.box);
  8720. this._updateDataAttributes(this.dom.box);
  8721. this._updateStyle(this.dom.box);
  8722. // update class
  8723. var className = (this.data.className? ' ' + this.data.className : '') +
  8724. (this.selected ? ' selected' : '');
  8725. dom.box.className = 'item box' + className;
  8726. dom.line.className = 'item line' + className;
  8727. dom.dot.className = 'item dot' + className;
  8728. // recalculate size
  8729. this.props.dot.height = dom.dot.offsetHeight;
  8730. this.props.dot.width = dom.dot.offsetWidth;
  8731. this.props.line.width = dom.line.offsetWidth;
  8732. this.width = dom.box.offsetWidth;
  8733. this.height = dom.box.offsetHeight;
  8734. this.dirty = false;
  8735. }
  8736. this._repaintDeleteButton(dom.box);
  8737. };
  8738. /**
  8739. * Show the item in the DOM (when not already displayed). The items DOM will
  8740. * be created when needed.
  8741. */
  8742. BoxItem.prototype.show = function() {
  8743. if (!this.displayed) {
  8744. this.redraw();
  8745. }
  8746. };
  8747. /**
  8748. * Hide the item from the DOM (when visible)
  8749. */
  8750. BoxItem.prototype.hide = function() {
  8751. if (this.displayed) {
  8752. var dom = this.dom;
  8753. if (dom.box.parentNode) dom.box.parentNode.removeChild(dom.box);
  8754. if (dom.line.parentNode) dom.line.parentNode.removeChild(dom.line);
  8755. if (dom.dot.parentNode) dom.dot.parentNode.removeChild(dom.dot);
  8756. this.displayed = false;
  8757. }
  8758. };
  8759. /**
  8760. * Reposition the item horizontally
  8761. * @Override
  8762. */
  8763. BoxItem.prototype.repositionX = function() {
  8764. var start = this.conversion.toScreen(this.data.start);
  8765. var align = this.options.align;
  8766. var left;
  8767. // calculate left position of the box
  8768. if (align == 'right') {
  8769. this.left = start - this.width;
  8770. }
  8771. else if (align == 'left') {
  8772. this.left = start;
  8773. }
  8774. else {
  8775. // default or 'center'
  8776. this.left = start - this.width / 2;
  8777. }
  8778. // reposition box
  8779. this.dom.box.style.left = this.left + 'px';
  8780. // reposition line
  8781. this.dom.line.style.left = (start - this.props.line.width / 2) + 'px';
  8782. // reposition dot
  8783. this.dom.dot.style.left = (start - this.props.dot.width / 2) + 'px';
  8784. };
  8785. /**
  8786. * Reposition the item vertically
  8787. * @Override
  8788. */
  8789. BoxItem.prototype.repositionY = function() {
  8790. var orientation = this.options.orientation;
  8791. var box = this.dom.box;
  8792. var line = this.dom.line;
  8793. var dot = this.dom.dot;
  8794. if (orientation == 'top') {
  8795. box.style.top = (this.top || 0) + 'px';
  8796. line.style.top = '0';
  8797. line.style.height = (this.parent.top + this.top + 1) + 'px';
  8798. line.style.bottom = '';
  8799. }
  8800. else { // orientation 'bottom'
  8801. var itemSetHeight = this.parent.itemSet.props.height; // TODO: this is nasty
  8802. var lineHeight = itemSetHeight - this.parent.top - this.parent.height + this.top;
  8803. box.style.top = (this.parent.height - this.top - this.height || 0) + 'px';
  8804. line.style.top = (itemSetHeight - lineHeight) + 'px';
  8805. line.style.bottom = '0';
  8806. }
  8807. dot.style.top = (-this.props.dot.height / 2) + 'px';
  8808. };
  8809. module.exports = BoxItem;
  8810. /***/ },
  8811. /* 23 */
  8812. /***/ function(module, exports, __webpack_require__) {
  8813. var Item = __webpack_require__(20);
  8814. /**
  8815. * @constructor PointItem
  8816. * @extends Item
  8817. * @param {Object} data Object containing parameters start
  8818. * content, className.
  8819. * @param {{toScreen: function, toTime: function}} conversion
  8820. * Conversion functions from time to screen and vice versa
  8821. * @param {Object} [options] Configuration options
  8822. * // TODO: describe available options
  8823. */
  8824. function PointItem (data, conversion, options) {
  8825. this.props = {
  8826. dot: {
  8827. top: 0,
  8828. width: 0,
  8829. height: 0
  8830. },
  8831. content: {
  8832. height: 0,
  8833. marginLeft: 0
  8834. }
  8835. };
  8836. // validate data
  8837. if (data) {
  8838. if (data.start == undefined) {
  8839. throw new Error('Property "start" missing in item ' + data);
  8840. }
  8841. }
  8842. Item.call(this, data, conversion, options);
  8843. }
  8844. PointItem.prototype = new Item (null, null, null);
  8845. /**
  8846. * Check whether this item is visible inside given range
  8847. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  8848. * @returns {boolean} True if visible
  8849. */
  8850. PointItem.prototype.isVisible = function(range) {
  8851. // determine visibility
  8852. // TODO: account for the real width of the item. Right now we just add 1/4 to the window
  8853. var interval = (range.end - range.start) / 4;
  8854. return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
  8855. };
  8856. /**
  8857. * Repaint the item
  8858. */
  8859. PointItem.prototype.redraw = function() {
  8860. var dom = this.dom;
  8861. if (!dom) {
  8862. // create DOM
  8863. this.dom = {};
  8864. dom = this.dom;
  8865. // background box
  8866. dom.point = document.createElement('div');
  8867. // className is updated in redraw()
  8868. // contents box, right from the dot
  8869. dom.content = document.createElement('div');
  8870. dom.content.className = 'content';
  8871. dom.point.appendChild(dom.content);
  8872. // dot at start
  8873. dom.dot = document.createElement('div');
  8874. dom.point.appendChild(dom.dot);
  8875. // attach this item as attribute
  8876. dom.point['timeline-item'] = this;
  8877. this.dirty = true;
  8878. }
  8879. // append DOM to parent DOM
  8880. if (!this.parent) {
  8881. throw new Error('Cannot redraw item: no parent attached');
  8882. }
  8883. if (!dom.point.parentNode) {
  8884. var foreground = this.parent.dom.foreground;
  8885. if (!foreground) {
  8886. throw new Error('Cannot redraw item: parent has no foreground container element');
  8887. }
  8888. foreground.appendChild(dom.point);
  8889. }
  8890. this.displayed = true;
  8891. // Update DOM when item is marked dirty. An item is marked dirty when:
  8892. // - the item is not yet rendered
  8893. // - the item's data is changed
  8894. // - the item is selected/deselected
  8895. if (this.dirty) {
  8896. this._updateContents(this.dom.content);
  8897. this._updateTitle(this.dom.point);
  8898. this._updateDataAttributes(this.dom.point);
  8899. this._updateStyle(this.dom.point);
  8900. // update class
  8901. var className = (this.data.className? ' ' + this.data.className : '') +
  8902. (this.selected ? ' selected' : '');
  8903. dom.point.className = 'item point' + className;
  8904. dom.dot.className = 'item dot' + className;
  8905. // recalculate size
  8906. this.width = dom.point.offsetWidth;
  8907. this.height = dom.point.offsetHeight;
  8908. this.props.dot.width = dom.dot.offsetWidth;
  8909. this.props.dot.height = dom.dot.offsetHeight;
  8910. this.props.content.height = dom.content.offsetHeight;
  8911. // resize contents
  8912. dom.content.style.marginLeft = 2 * this.props.dot.width + 'px';
  8913. //dom.content.style.marginRight = ... + 'px'; // TODO: margin right
  8914. dom.dot.style.top = ((this.height - this.props.dot.height) / 2) + 'px';
  8915. dom.dot.style.left = (this.props.dot.width / 2) + 'px';
  8916. this.dirty = false;
  8917. }
  8918. this._repaintDeleteButton(dom.point);
  8919. };
  8920. /**
  8921. * Show the item in the DOM (when not already visible). The items DOM will
  8922. * be created when needed.
  8923. */
  8924. PointItem.prototype.show = function() {
  8925. if (!this.displayed) {
  8926. this.redraw();
  8927. }
  8928. };
  8929. /**
  8930. * Hide the item from the DOM (when visible)
  8931. */
  8932. PointItem.prototype.hide = function() {
  8933. if (this.displayed) {
  8934. if (this.dom.point.parentNode) {
  8935. this.dom.point.parentNode.removeChild(this.dom.point);
  8936. }
  8937. this.displayed = false;
  8938. }
  8939. };
  8940. /**
  8941. * Reposition the item horizontally
  8942. * @Override
  8943. */
  8944. PointItem.prototype.repositionX = function() {
  8945. var start = this.conversion.toScreen(this.data.start);
  8946. this.left = start - this.props.dot.width;
  8947. // reposition point
  8948. this.dom.point.style.left = this.left + 'px';
  8949. };
  8950. /**
  8951. * Reposition the item vertically
  8952. * @Override
  8953. */
  8954. PointItem.prototype.repositionY = function() {
  8955. var orientation = this.options.orientation,
  8956. point = this.dom.point;
  8957. if (orientation == 'top') {
  8958. point.style.top = this.top + 'px';
  8959. }
  8960. else {
  8961. point.style.top = (this.parent.height - this.top - this.height) + 'px';
  8962. }
  8963. };
  8964. module.exports = PointItem;
  8965. /***/ },
  8966. /* 24 */
  8967. /***/ function(module, exports, __webpack_require__) {
  8968. var Hammer = __webpack_require__(45);
  8969. var Item = __webpack_require__(20);
  8970. /**
  8971. * @constructor RangeItem
  8972. * @extends Item
  8973. * @param {Object} data Object containing parameters start, end
  8974. * content, className.
  8975. * @param {{toScreen: function, toTime: function}} conversion
  8976. * Conversion functions from time to screen and vice versa
  8977. * @param {Object} [options] Configuration options
  8978. * // TODO: describe options
  8979. */
  8980. function RangeItem (data, conversion, options) {
  8981. this.props = {
  8982. content: {
  8983. width: 0
  8984. }
  8985. };
  8986. this.overflow = false; // if contents can overflow (css styling), this flag is set to true
  8987. // validate data
  8988. if (data) {
  8989. if (data.start == undefined) {
  8990. throw new Error('Property "start" missing in item ' + data.id);
  8991. }
  8992. if (data.end == undefined) {
  8993. throw new Error('Property "end" missing in item ' + data.id);
  8994. }
  8995. }
  8996. Item.call(this, data, conversion, options);
  8997. }
  8998. RangeItem.prototype = new Item (null, null, null);
  8999. RangeItem.prototype.baseClassName = 'item range';
  9000. /**
  9001. * Check whether this item is visible inside given range
  9002. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  9003. * @returns {boolean} True if visible
  9004. */
  9005. RangeItem.prototype.isVisible = function(range) {
  9006. // determine visibility
  9007. return (this.data.start < range.end) && (this.data.end > range.start);
  9008. };
  9009. /**
  9010. * Repaint the item
  9011. */
  9012. RangeItem.prototype.redraw = function() {
  9013. var dom = this.dom;
  9014. if (!dom) {
  9015. // create DOM
  9016. this.dom = {};
  9017. dom = this.dom;
  9018. // background box
  9019. dom.box = document.createElement('div');
  9020. // className is updated in redraw()
  9021. // contents box
  9022. dom.content = document.createElement('div');
  9023. dom.content.className = 'content';
  9024. dom.box.appendChild(dom.content);
  9025. // attach this item as attribute
  9026. dom.box['timeline-item'] = this;
  9027. this.dirty = true;
  9028. }
  9029. // append DOM to parent DOM
  9030. if (!this.parent) {
  9031. throw new Error('Cannot redraw item: no parent attached');
  9032. }
  9033. if (!dom.box.parentNode) {
  9034. var foreground = this.parent.dom.foreground;
  9035. if (!foreground) {
  9036. throw new Error('Cannot redraw item: parent has no foreground container element');
  9037. }
  9038. foreground.appendChild(dom.box);
  9039. }
  9040. this.displayed = true;
  9041. // Update DOM when item is marked dirty. An item is marked dirty when:
  9042. // - the item is not yet rendered
  9043. // - the item's data is changed
  9044. // - the item is selected/deselected
  9045. if (this.dirty) {
  9046. this._updateContents(this.dom.content);
  9047. this._updateTitle(this.dom.box);
  9048. this._updateDataAttributes(this.dom.box);
  9049. this._updateStyle(this.dom.box);
  9050. // update class
  9051. var className = (this.data.className ? (' ' + this.data.className) : '') +
  9052. (this.selected ? ' selected' : '');
  9053. dom.box.className = this.baseClassName + className;
  9054. // determine from css whether this box has overflow
  9055. this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden';
  9056. // recalculate size
  9057. // turn off max-width to be able to calculate the real width
  9058. // this causes an extra browser repaint/reflow, but so be it
  9059. this.dom.content.style.maxWidth = 'none';
  9060. this.props.content.width = this.dom.content.offsetWidth;
  9061. this.height = this.dom.box.offsetHeight;
  9062. this.dom.content.style.maxWidth = '';
  9063. this.dirty = false;
  9064. }
  9065. this._repaintDeleteButton(dom.box);
  9066. this._repaintDragLeft();
  9067. this._repaintDragRight();
  9068. };
  9069. /**
  9070. * Show the item in the DOM (when not already visible). The items DOM will
  9071. * be created when needed.
  9072. */
  9073. RangeItem.prototype.show = function() {
  9074. if (!this.displayed) {
  9075. this.redraw();
  9076. }
  9077. };
  9078. /**
  9079. * Hide the item from the DOM (when visible)
  9080. * @return {Boolean} changed
  9081. */
  9082. RangeItem.prototype.hide = function() {
  9083. if (this.displayed) {
  9084. var box = this.dom.box;
  9085. if (box.parentNode) {
  9086. box.parentNode.removeChild(box);
  9087. }
  9088. this.displayed = false;
  9089. }
  9090. };
  9091. /**
  9092. * Reposition the item horizontally
  9093. * @param {boolean} [limitSize=true] If true (default), the width of the range
  9094. * item will be limited, as the browser cannot
  9095. * display very wide divs. This means though
  9096. * that the applied left and width may
  9097. * not correspond to the ranges start and end
  9098. * @Override
  9099. */
  9100. RangeItem.prototype.repositionX = function(limitSize) {
  9101. var parentWidth = this.parent.width;
  9102. var start = this.conversion.toScreen(this.data.start);
  9103. var end = this.conversion.toScreen(this.data.end);
  9104. var contentLeft;
  9105. var contentWidth;
  9106. // limit the width of the range, as browsers cannot draw very wide divs
  9107. if (limitSize === undefined || limitSize === true) {
  9108. if (start < -parentWidth) {
  9109. start = -parentWidth;
  9110. }
  9111. if (end > 2 * parentWidth) {
  9112. end = 2 * parentWidth;
  9113. }
  9114. }
  9115. var boxWidth = Math.max(end - start, 1);
  9116. if (this.overflow) {
  9117. this.left = start;
  9118. this.width = boxWidth + this.props.content.width;
  9119. contentWidth = this.props.content.width;
  9120. // Note: The calculation of width is an optimistic calculation, giving
  9121. // a width which will not change when moving the Timeline
  9122. // So no re-stacking needed, which is nicer for the eye;
  9123. }
  9124. else {
  9125. this.left = start;
  9126. this.width = boxWidth;
  9127. contentWidth = Math.min(end - start - 2 * this.options.padding, this.props.content.width);
  9128. }
  9129. this.dom.box.style.left = this.left + 'px';
  9130. this.dom.box.style.width = boxWidth + 'px';
  9131. switch (this.options.align) {
  9132. case 'left':
  9133. this.dom.content.style.left = '0';
  9134. break;
  9135. case 'right':
  9136. this.dom.content.style.left = Math.max((boxWidth - contentWidth - 2 * this.options.padding), 0) + 'px';
  9137. break;
  9138. case 'center':
  9139. this.dom.content.style.left = Math.max((boxWidth - contentWidth - 2 * this.options.padding) / 2, 0) + 'px';
  9140. break;
  9141. default: // 'auto'
  9142. // when range exceeds left of the window, position the contents at the left of the visible area
  9143. if (this.overflow) {
  9144. if (end > 0) {
  9145. contentLeft = Math.max(-start, 0);
  9146. }
  9147. else {
  9148. contentLeft = -contentWidth; // ensure it's not visible anymore
  9149. }
  9150. }
  9151. else {
  9152. if (start < 0) {
  9153. contentLeft = Math.min(-start,
  9154. (end - start - contentWidth - 2 * this.options.padding));
  9155. // TODO: remove the need for options.padding. it's terrible.
  9156. }
  9157. else {
  9158. contentLeft = 0;
  9159. }
  9160. }
  9161. this.dom.content.style.left = contentLeft + 'px';
  9162. }
  9163. };
  9164. /**
  9165. * Reposition the item vertically
  9166. * @Override
  9167. */
  9168. RangeItem.prototype.repositionY = function() {
  9169. var orientation = this.options.orientation,
  9170. box = this.dom.box;
  9171. if (orientation == 'top') {
  9172. box.style.top = this.top + 'px';
  9173. }
  9174. else {
  9175. box.style.top = (this.parent.height - this.top - this.height) + 'px';
  9176. }
  9177. };
  9178. /**
  9179. * Repaint a drag area on the left side of the range when the range is selected
  9180. * @protected
  9181. */
  9182. RangeItem.prototype._repaintDragLeft = function () {
  9183. if (this.selected && this.options.editable.updateTime && !this.dom.dragLeft) {
  9184. // create and show drag area
  9185. var dragLeft = document.createElement('div');
  9186. dragLeft.className = 'drag-left';
  9187. dragLeft.dragLeftItem = this;
  9188. // TODO: this should be redundant?
  9189. Hammer(dragLeft, {
  9190. preventDefault: true
  9191. }).on('drag', function () {
  9192. //console.log('drag left')
  9193. });
  9194. this.dom.box.appendChild(dragLeft);
  9195. this.dom.dragLeft = dragLeft;
  9196. }
  9197. else if (!this.selected && this.dom.dragLeft) {
  9198. // delete drag area
  9199. if (this.dom.dragLeft.parentNode) {
  9200. this.dom.dragLeft.parentNode.removeChild(this.dom.dragLeft);
  9201. }
  9202. this.dom.dragLeft = null;
  9203. }
  9204. };
  9205. /**
  9206. * Repaint a drag area on the right side of the range when the range is selected
  9207. * @protected
  9208. */
  9209. RangeItem.prototype._repaintDragRight = function () {
  9210. if (this.selected && this.options.editable.updateTime && !this.dom.dragRight) {
  9211. // create and show drag area
  9212. var dragRight = document.createElement('div');
  9213. dragRight.className = 'drag-right';
  9214. dragRight.dragRightItem = this;
  9215. // TODO: this should be redundant?
  9216. Hammer(dragRight, {
  9217. preventDefault: true
  9218. }).on('drag', function () {
  9219. //console.log('drag right')
  9220. });
  9221. this.dom.box.appendChild(dragRight);
  9222. this.dom.dragRight = dragRight;
  9223. }
  9224. else if (!this.selected && this.dom.dragRight) {
  9225. // delete drag area
  9226. if (this.dom.dragRight.parentNode) {
  9227. this.dom.dragRight.parentNode.removeChild(this.dom.dragRight);
  9228. }
  9229. this.dom.dragRight = null;
  9230. }
  9231. };
  9232. module.exports = RangeItem;
  9233. /***/ },
  9234. /* 25 */
  9235. /***/ function(module, exports, __webpack_require__) {
  9236. /**
  9237. * Prototype for visual components
  9238. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} [body]
  9239. * @param {Object} [options]
  9240. */
  9241. function Component (body, options) {
  9242. this.options = null;
  9243. this.props = null;
  9244. }
  9245. /**
  9246. * Set options for the component. The new options will be merged into the
  9247. * current options.
  9248. * @param {Object} options
  9249. */
  9250. Component.prototype.setOptions = function(options) {
  9251. if (options) {
  9252. util.extend(this.options, options);
  9253. }
  9254. };
  9255. /**
  9256. * Repaint the component
  9257. * @return {boolean} Returns true if the component is resized
  9258. */
  9259. Component.prototype.redraw = function() {
  9260. // should be implemented by the component
  9261. return false;
  9262. };
  9263. /**
  9264. * Destroy the component. Cleanup DOM and event listeners
  9265. */
  9266. Component.prototype.destroy = function() {
  9267. // should be implemented by the component
  9268. };
  9269. /**
  9270. * Test whether the component is resized since the last time _isResized() was
  9271. * called.
  9272. * @return {Boolean} Returns true if the component is resized
  9273. * @protected
  9274. */
  9275. Component.prototype._isResized = function() {
  9276. var resized = (this.props._previousWidth !== this.props.width ||
  9277. this.props._previousHeight !== this.props.height);
  9278. this.props._previousWidth = this.props.width;
  9279. this.props._previousHeight = this.props.height;
  9280. return resized;
  9281. };
  9282. module.exports = Component;
  9283. /***/ },
  9284. /* 26 */
  9285. /***/ function(module, exports, __webpack_require__) {
  9286. var util = __webpack_require__(1);
  9287. var Component = __webpack_require__(25);
  9288. var moment = __webpack_require__(44);
  9289. var locales = __webpack_require__(48);
  9290. /**
  9291. * A current time bar
  9292. * @param {{range: Range, dom: Object, domProps: Object}} body
  9293. * @param {Object} [options] Available parameters:
  9294. * {Boolean} [showCurrentTime]
  9295. * @constructor CurrentTime
  9296. * @extends Component
  9297. */
  9298. function CurrentTime (body, options) {
  9299. this.body = body;
  9300. // default options
  9301. this.defaultOptions = {
  9302. showCurrentTime: true,
  9303. locales: locales,
  9304. locale: 'en'
  9305. };
  9306. this.options = util.extend({}, this.defaultOptions);
  9307. this.offset = 0;
  9308. this._create();
  9309. this.setOptions(options);
  9310. }
  9311. CurrentTime.prototype = new Component();
  9312. /**
  9313. * Create the HTML DOM for the current time bar
  9314. * @private
  9315. */
  9316. CurrentTime.prototype._create = function() {
  9317. var bar = document.createElement('div');
  9318. bar.className = 'currenttime';
  9319. bar.style.position = 'absolute';
  9320. bar.style.top = '0px';
  9321. bar.style.height = '100%';
  9322. this.bar = bar;
  9323. };
  9324. /**
  9325. * Destroy the CurrentTime bar
  9326. */
  9327. CurrentTime.prototype.destroy = function () {
  9328. this.options.showCurrentTime = false;
  9329. this.redraw(); // will remove the bar from the DOM and stop refreshing
  9330. this.body = null;
  9331. };
  9332. /**
  9333. * Set options for the component. Options will be merged in current options.
  9334. * @param {Object} options Available parameters:
  9335. * {boolean} [showCurrentTime]
  9336. */
  9337. CurrentTime.prototype.setOptions = function(options) {
  9338. if (options) {
  9339. // copy all options that we know
  9340. util.selectiveExtend(['showCurrentTime', 'locale', 'locales'], this.options, options);
  9341. }
  9342. };
  9343. /**
  9344. * Repaint the component
  9345. * @return {boolean} Returns true if the component is resized
  9346. */
  9347. CurrentTime.prototype.redraw = function() {
  9348. if (this.options.showCurrentTime) {
  9349. var parent = this.body.dom.backgroundVertical;
  9350. if (this.bar.parentNode != parent) {
  9351. // attach to the dom
  9352. if (this.bar.parentNode) {
  9353. this.bar.parentNode.removeChild(this.bar);
  9354. }
  9355. parent.appendChild(this.bar);
  9356. this.start();
  9357. }
  9358. var now = new Date(new Date().valueOf() + this.offset);
  9359. var x = this.body.util.toScreen(now);
  9360. var locale = this.options.locales[this.options.locale];
  9361. var title = locale.current + ' ' + locale.time + ': ' + moment(now).format('dddd, MMMM Do YYYY, H:mm:ss');
  9362. title = title.charAt(0).toUpperCase() + title.substring(1);
  9363. this.bar.style.left = x + 'px';
  9364. this.bar.title = title;
  9365. }
  9366. else {
  9367. // remove the line from the DOM
  9368. if (this.bar.parentNode) {
  9369. this.bar.parentNode.removeChild(this.bar);
  9370. }
  9371. this.stop();
  9372. }
  9373. return false;
  9374. };
  9375. /**
  9376. * Start auto refreshing the current time bar
  9377. */
  9378. CurrentTime.prototype.start = function() {
  9379. var me = this;
  9380. function update () {
  9381. me.stop();
  9382. // determine interval to refresh
  9383. var scale = me.body.range.conversion(me.body.domProps.center.width).scale;
  9384. var interval = 1 / scale / 10;
  9385. if (interval < 30) interval = 30;
  9386. if (interval > 1000) interval = 1000;
  9387. me.redraw();
  9388. // start a timer to adjust for the new time
  9389. me.currentTimeTimer = setTimeout(update, interval);
  9390. }
  9391. update();
  9392. };
  9393. /**
  9394. * Stop auto refreshing the current time bar
  9395. */
  9396. CurrentTime.prototype.stop = function() {
  9397. if (this.currentTimeTimer !== undefined) {
  9398. clearTimeout(this.currentTimeTimer);
  9399. delete this.currentTimeTimer;
  9400. }
  9401. };
  9402. /**
  9403. * Set a current time. This can be used for example to ensure that a client's
  9404. * time is synchronized with a shared server time.
  9405. * @param {Date | String | Number} time A Date, unix timestamp, or
  9406. * ISO date string.
  9407. */
  9408. CurrentTime.prototype.setCurrentTime = function(time) {
  9409. var t = util.convert(time, 'Date').valueOf();
  9410. var now = new Date().valueOf();
  9411. this.offset = t - now;
  9412. this.redraw();
  9413. };
  9414. /**
  9415. * Get the current time.
  9416. * @return {Date} Returns the current time.
  9417. */
  9418. CurrentTime.prototype.getCurrentTime = function() {
  9419. return new Date(new Date().valueOf() + this.offset);
  9420. };
  9421. module.exports = CurrentTime;
  9422. /***/ },
  9423. /* 27 */
  9424. /***/ function(module, exports, __webpack_require__) {
  9425. var Hammer = __webpack_require__(45);
  9426. var util = __webpack_require__(1);
  9427. var Component = __webpack_require__(25);
  9428. var moment = __webpack_require__(44);
  9429. var locales = __webpack_require__(48);
  9430. /**
  9431. * A custom time bar
  9432. * @param {{range: Range, dom: Object}} body
  9433. * @param {Object} [options] Available parameters:
  9434. * {Boolean} [showCustomTime]
  9435. * @constructor CustomTime
  9436. * @extends Component
  9437. */
  9438. function CustomTime (body, options) {
  9439. this.body = body;
  9440. // default options
  9441. this.defaultOptions = {
  9442. showCustomTime: false,
  9443. locales: locales,
  9444. locale: 'en',
  9445. id: 0
  9446. };
  9447. this.options = util.extend({}, this.defaultOptions);
  9448. if (options && options.time) {
  9449. this.customTime = options.time;
  9450. } else {
  9451. this.customTime = new Date();
  9452. }
  9453. this.eventParams = {}; // stores state parameters while dragging the bar
  9454. // create the DOM
  9455. this._create();
  9456. this.setOptions(options);
  9457. }
  9458. CustomTime.prototype = new Component();
  9459. /**
  9460. * Set options for the component. Options will be merged in current options.
  9461. * @param {Object} options Available parameters:
  9462. * {boolean} [showCustomTime]
  9463. */
  9464. CustomTime.prototype.setOptions = function(options) {
  9465. if (options) {
  9466. // copy all options that we know
  9467. util.selectiveExtend(['showCustomTime', 'locale', 'locales', 'id'], this.options, options);
  9468. // Triggered by addCustomTimeBar, redraw to add new bar
  9469. if (this.options.id) {
  9470. this.redraw();
  9471. }
  9472. }
  9473. };
  9474. /**
  9475. * Create the DOM for the custom time
  9476. * @private
  9477. */
  9478. CustomTime.prototype._create = function() {
  9479. var bar = document.createElement('div');
  9480. bar.className = 'customtime';
  9481. bar.style.position = 'absolute';
  9482. bar.style.top = '0px';
  9483. bar.style.height = '100%';
  9484. this.bar = bar;
  9485. var drag = document.createElement('div');
  9486. drag.style.position = 'relative';
  9487. drag.style.top = '0px';
  9488. drag.style.left = '-10px';
  9489. drag.style.height = '100%';
  9490. drag.style.width = '20px';
  9491. bar.appendChild(drag);
  9492. // attach event listeners
  9493. this.hammer = Hammer(bar, {
  9494. prevent_default: true
  9495. });
  9496. this.hammer.on('dragstart', this._onDragStart.bind(this));
  9497. this.hammer.on('drag', this._onDrag.bind(this));
  9498. this.hammer.on('dragend', this._onDragEnd.bind(this));
  9499. };
  9500. /**
  9501. * Destroy the CustomTime bar
  9502. */
  9503. CustomTime.prototype.destroy = function () {
  9504. this.options.showCustomTime = false;
  9505. this.redraw(); // will remove the bar from the DOM
  9506. this.hammer.enable(false);
  9507. this.hammer = null;
  9508. this.body = null;
  9509. };
  9510. /**
  9511. * Repaint the component
  9512. * @return {boolean} Returns true if the component is resized
  9513. */
  9514. CustomTime.prototype.redraw = function () {
  9515. if (this.options.showCustomTime) {
  9516. var parent = this.body.dom.backgroundVertical;
  9517. if (this.bar.parentNode != parent) {
  9518. // attach to the dom
  9519. if (this.bar.parentNode) {
  9520. this.bar.parentNode.removeChild(this.bar);
  9521. }
  9522. parent.appendChild(this.bar);
  9523. }
  9524. var x = this.body.util.toScreen(this.customTime);
  9525. var locale = this.options.locales[this.options.locale];
  9526. var title = locale.time + ': ' + moment(this.customTime).format('dddd, MMMM Do YYYY, H:mm:ss');
  9527. title = title.charAt(0).toUpperCase() + title.substring(1);
  9528. this.bar.style.left = x + 'px';
  9529. this.bar.title = title;
  9530. }
  9531. else {
  9532. // remove the line from the DOM
  9533. if (this.bar.parentNode) {
  9534. this.bar.parentNode.removeChild(this.bar);
  9535. }
  9536. }
  9537. return false;
  9538. };
  9539. /**
  9540. * Set custom time.
  9541. * @param {Date | number | string} time
  9542. */
  9543. CustomTime.prototype.setCustomTime = function(time) {
  9544. this.customTime = util.convert(time, 'Date');
  9545. this.redraw();
  9546. };
  9547. /**
  9548. * Retrieve the current custom time.
  9549. * @return {Date} customTime
  9550. */
  9551. CustomTime.prototype.getCustomTime = function() {
  9552. return new Date(this.customTime.valueOf());
  9553. };
  9554. /**
  9555. * Start moving horizontally
  9556. * @param {Event} event
  9557. * @private
  9558. */
  9559. CustomTime.prototype._onDragStart = function(event) {
  9560. this.eventParams.dragging = true;
  9561. this.eventParams.customTime = this.customTime;
  9562. event.stopPropagation();
  9563. event.preventDefault();
  9564. };
  9565. /**
  9566. * Perform moving operating.
  9567. * @param {Event} event
  9568. * @private
  9569. */
  9570. CustomTime.prototype._onDrag = function (event) {
  9571. if (!this.eventParams.dragging) return;
  9572. var deltaX = event.gesture.deltaX,
  9573. x = this.body.util.toScreen(this.eventParams.customTime) + deltaX,
  9574. time = this.body.util.toTime(x);
  9575. this.setCustomTime(time);
  9576. // fire a timechange event
  9577. this.body.emitter.emit('timechange', {
  9578. id: this.options.id,
  9579. time: new Date(this.customTime.valueOf())
  9580. });
  9581. event.stopPropagation();
  9582. event.preventDefault();
  9583. };
  9584. /**
  9585. * Stop moving operating.
  9586. * @param {event} event
  9587. * @private
  9588. */
  9589. CustomTime.prototype._onDragEnd = function (event) {
  9590. if (!this.eventParams.dragging) return;
  9591. // fire a timechanged event
  9592. this.body.emitter.emit('timechanged', {
  9593. id: this.options.id,
  9594. time: new Date(this.customTime.valueOf())
  9595. });
  9596. event.stopPropagation();
  9597. event.preventDefault();
  9598. };
  9599. module.exports = CustomTime;
  9600. /***/ },
  9601. /* 28 */
  9602. /***/ function(module, exports, __webpack_require__) {
  9603. var util = __webpack_require__(1);
  9604. var DOMutil = __webpack_require__(2);
  9605. var Component = __webpack_require__(25);
  9606. var DataStep = __webpack_require__(16);
  9607. /**
  9608. * A horizontal time axis
  9609. * @param {Object} [options] See DataAxis.setOptions for the available
  9610. * options.
  9611. * @constructor DataAxis
  9612. * @extends Component
  9613. * @param body
  9614. */
  9615. function DataAxis (body, options, svg, linegraphOptions) {
  9616. this.id = util.randomUUID();
  9617. this.body = body;
  9618. this.defaultOptions = {
  9619. orientation: 'left', // supported: 'left', 'right'
  9620. showMinorLabels: true,
  9621. showMajorLabels: true,
  9622. icons: true,
  9623. majorLinesOffset: 7,
  9624. minorLinesOffset: 4,
  9625. labelOffsetX: 10,
  9626. labelOffsetY: 2,
  9627. iconWidth: 20,
  9628. width: '40px',
  9629. visible: true,
  9630. alignZeros: true,
  9631. customRange: {
  9632. left: {min:undefined, max:undefined},
  9633. right: {min:undefined, max:undefined}
  9634. },
  9635. title: {
  9636. left: {text:undefined},
  9637. right: {text:undefined}
  9638. },
  9639. format: {
  9640. left: {decimals: undefined},
  9641. right: {decimals: undefined}
  9642. }
  9643. };
  9644. this.linegraphOptions = linegraphOptions;
  9645. this.linegraphSVG = svg;
  9646. this.props = {};
  9647. this.DOMelements = { // dynamic elements
  9648. lines: {},
  9649. labels: {},
  9650. title: {}
  9651. };
  9652. this.dom = {};
  9653. this.range = {start:0, end:0};
  9654. this.options = util.extend({}, this.defaultOptions);
  9655. this.conversionFactor = 1;
  9656. this.setOptions(options);
  9657. this.width = Number(('' + this.options.width).replace("px",""));
  9658. this.minWidth = this.width;
  9659. this.height = this.linegraphSVG.offsetHeight;
  9660. this.hidden = false;
  9661. this.stepPixels = 25;
  9662. this.stepPixelsForced = 25;
  9663. this.zeroCrossing = -1;
  9664. this.lineOffset = 0;
  9665. this.master = true;
  9666. this.svgElements = {};
  9667. this.iconsRemoved = false;
  9668. this.groups = {};
  9669. this.amountOfGroups = 0;
  9670. // create the HTML DOM
  9671. this._create();
  9672. var me = this;
  9673. this.body.emitter.on("verticalDrag", function() {
  9674. me.dom.lineContainer.style.top = me.body.domProps.scrollTop + 'px';
  9675. });
  9676. }
  9677. DataAxis.prototype = new Component();
  9678. DataAxis.prototype.addGroup = function(label, graphOptions) {
  9679. if (!this.groups.hasOwnProperty(label)) {
  9680. this.groups[label] = graphOptions;
  9681. }
  9682. this.amountOfGroups += 1;
  9683. };
  9684. DataAxis.prototype.updateGroup = function(label, graphOptions) {
  9685. this.groups[label] = graphOptions;
  9686. };
  9687. DataAxis.prototype.removeGroup = function(label) {
  9688. if (this.groups.hasOwnProperty(label)) {
  9689. delete this.groups[label];
  9690. this.amountOfGroups -= 1;
  9691. }
  9692. };
  9693. DataAxis.prototype.setOptions = function (options) {
  9694. if (options) {
  9695. var redraw = false;
  9696. if (this.options.orientation != options.orientation && options.orientation !== undefined) {
  9697. redraw = true;
  9698. }
  9699. var fields = [
  9700. 'orientation',
  9701. 'showMinorLabels',
  9702. 'showMajorLabels',
  9703. 'icons',
  9704. 'majorLinesOffset',
  9705. 'minorLinesOffset',
  9706. 'labelOffsetX',
  9707. 'labelOffsetY',
  9708. 'iconWidth',
  9709. 'width',
  9710. 'visible',
  9711. 'customRange',
  9712. 'title',
  9713. 'format',
  9714. 'alignZeros'
  9715. ];
  9716. util.selectiveExtend(fields, this.options, options);
  9717. this.minWidth = Number(('' + this.options.width).replace("px",""));
  9718. if (redraw == true && this.dom.frame) {
  9719. this.hide();
  9720. this.show();
  9721. }
  9722. }
  9723. };
  9724. /**
  9725. * Create the HTML DOM for the DataAxis
  9726. */
  9727. DataAxis.prototype._create = function() {
  9728. this.dom.frame = document.createElement('div');
  9729. this.dom.frame.style.width = this.options.width;
  9730. this.dom.frame.style.height = this.height;
  9731. this.dom.lineContainer = document.createElement('div');
  9732. this.dom.lineContainer.style.width = '100%';
  9733. this.dom.lineContainer.style.height = this.height;
  9734. this.dom.lineContainer.style.position = 'relative';
  9735. // create svg element for graph drawing.
  9736. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  9737. this.svg.style.position = "absolute";
  9738. this.svg.style.top = '0px';
  9739. this.svg.style.height = '100%';
  9740. this.svg.style.width = '100%';
  9741. this.svg.style.display = "block";
  9742. this.dom.frame.appendChild(this.svg);
  9743. };
  9744. DataAxis.prototype._redrawGroupIcons = function () {
  9745. DOMutil.prepareElements(this.svgElements);
  9746. var x;
  9747. var iconWidth = this.options.iconWidth;
  9748. var iconHeight = 15;
  9749. var iconOffset = 4;
  9750. var y = iconOffset + 0.5 * iconHeight;
  9751. if (this.options.orientation == 'left') {
  9752. x = iconOffset;
  9753. }
  9754. else {
  9755. x = this.width - iconWidth - iconOffset;
  9756. }
  9757. for (var groupId in this.groups) {
  9758. if (this.groups.hasOwnProperty(groupId)) {
  9759. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  9760. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  9761. y += iconHeight + iconOffset;
  9762. }
  9763. }
  9764. }
  9765. DOMutil.cleanupElements(this.svgElements);
  9766. this.iconsRemoved = false;
  9767. };
  9768. DataAxis.prototype._cleanupIcons = function() {
  9769. if (this.iconsRemoved == false) {
  9770. DOMutil.prepareElements(this.svgElements);
  9771. DOMutil.cleanupElements(this.svgElements);
  9772. this.iconsRemoved = true;
  9773. }
  9774. }
  9775. /**
  9776. * Create the HTML DOM for the DataAxis
  9777. */
  9778. DataAxis.prototype.show = function() {
  9779. this.hidden = false;
  9780. if (!this.dom.frame.parentNode) {
  9781. if (this.options.orientation == 'left') {
  9782. this.body.dom.left.appendChild(this.dom.frame);
  9783. }
  9784. else {
  9785. this.body.dom.right.appendChild(this.dom.frame);
  9786. }
  9787. }
  9788. if (!this.dom.lineContainer.parentNode) {
  9789. this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer);
  9790. }
  9791. };
  9792. /**
  9793. * Create the HTML DOM for the DataAxis
  9794. */
  9795. DataAxis.prototype.hide = function() {
  9796. this.hidden = true;
  9797. if (this.dom.frame.parentNode) {
  9798. this.dom.frame.parentNode.removeChild(this.dom.frame);
  9799. }
  9800. if (this.dom.lineContainer.parentNode) {
  9801. this.dom.lineContainer.parentNode.removeChild(this.dom.lineContainer);
  9802. }
  9803. };
  9804. /**
  9805. * Set a range (start and end)
  9806. * @param end
  9807. * @param start
  9808. * @param end
  9809. */
  9810. DataAxis.prototype.setRange = function (start, end) {
  9811. if (this.master == false && this.options.alignZeros == true && this.zeroCrossing != -1) {
  9812. if (start > 0) {
  9813. start = 0;
  9814. }
  9815. }
  9816. this.range.start = start;
  9817. this.range.end = end;
  9818. };
  9819. /**
  9820. * Repaint the component
  9821. * @return {boolean} Returns true if the component is resized
  9822. */
  9823. DataAxis.prototype.redraw = function () {
  9824. var resized = false;
  9825. var activeGroups = 0;
  9826. // Make sure the line container adheres to the vertical scrolling.
  9827. this.dom.lineContainer.style.top = this.body.domProps.scrollTop + 'px';
  9828. for (var groupId in this.groups) {
  9829. if (this.groups.hasOwnProperty(groupId)) {
  9830. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  9831. activeGroups++;
  9832. }
  9833. }
  9834. }
  9835. if (this.amountOfGroups == 0 || activeGroups == 0) {
  9836. this.hide();
  9837. }
  9838. else {
  9839. this.show();
  9840. this.height = Number(this.linegraphSVG.style.height.replace("px",""));
  9841. // svg offsetheight did not work in firefox and explorer...
  9842. this.dom.lineContainer.style.height = this.height + 'px';
  9843. this.width = this.options.visible == true ? Number(('' + this.options.width).replace("px","")) : 0;
  9844. var props = this.props;
  9845. var frame = this.dom.frame;
  9846. // update classname
  9847. frame.className = 'dataaxis';
  9848. // calculate character width and height
  9849. this._calculateCharSize();
  9850. var orientation = this.options.orientation;
  9851. var showMinorLabels = this.options.showMinorLabels;
  9852. var showMajorLabels = this.options.showMajorLabels;
  9853. // determine the width and height of the elements for the axis
  9854. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  9855. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  9856. props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset;
  9857. props.minorLineHeight = 1;
  9858. props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset;
  9859. props.majorLineHeight = 1;
  9860. // take frame offline while updating (is almost twice as fast)
  9861. if (orientation == 'left') {
  9862. frame.style.top = '0';
  9863. frame.style.left = '0';
  9864. frame.style.bottom = '';
  9865. frame.style.width = this.width + 'px';
  9866. frame.style.height = this.height + "px";
  9867. this.props.width = this.body.domProps.left.width;
  9868. this.props.height = this.body.domProps.left.height;
  9869. }
  9870. else { // right
  9871. frame.style.top = '';
  9872. frame.style.bottom = '0';
  9873. frame.style.left = '0';
  9874. frame.style.width = this.width + 'px';
  9875. frame.style.height = this.height + "px";
  9876. this.props.width = this.body.domProps.right.width;
  9877. this.props.height = this.body.domProps.right.height;
  9878. }
  9879. resized = this._redrawLabels();
  9880. resized = this._isResized() || resized;
  9881. if (this.options.icons == true) {
  9882. this._redrawGroupIcons();
  9883. }
  9884. else {
  9885. this._cleanupIcons();
  9886. }
  9887. this._redrawTitle(orientation);
  9888. }
  9889. return resized;
  9890. };
  9891. /**
  9892. * Repaint major and minor text labels and vertical grid lines
  9893. * @private
  9894. */
  9895. DataAxis.prototype._redrawLabels = function () {
  9896. var resized = false;
  9897. DOMutil.prepareElements(this.DOMelements.lines);
  9898. DOMutil.prepareElements(this.DOMelements.labels);
  9899. var orientation = this.options['orientation'];
  9900. // calculate range and step (step such that we have space for 7 characters per label)
  9901. var minimumStep = this.master ? this.props.majorCharHeight || 10 : this.stepPixelsForced;
  9902. var step = new DataStep(
  9903. this.range.start,
  9904. this.range.end,
  9905. minimumStep,
  9906. this.dom.frame.offsetHeight,
  9907. this.options.customRange[this.options.orientation],
  9908. this.master == false && this.options.alignZeros // doess the step have to align zeros? only if not master and the options is on
  9909. );
  9910. this.step = step;
  9911. // get the distance in pixels for a step
  9912. // dead space is space that is "left over" after a step
  9913. var stepPixels = (this.dom.frame.offsetHeight - (step.deadSpace * (this.dom.frame.offsetHeight / step.marginRange))) / (((step.marginRange - step.deadSpace) / step.step));
  9914. this.stepPixels = stepPixels;
  9915. var amountOfSteps = this.height / stepPixels;
  9916. var stepDifference = 0;
  9917. // the slave axis needs to use the same horizontal lines as the master axis.
  9918. if (this.master == false) {
  9919. stepPixels = this.stepPixelsForced;
  9920. stepDifference = Math.round((this.dom.frame.offsetHeight / stepPixels) - amountOfSteps);
  9921. for (var i = 0; i < 0.5 * stepDifference; i++) {
  9922. step.previous();
  9923. }
  9924. amountOfSteps = this.height / stepPixels;
  9925. if (this.zeroCrossing != -1 && this.options.alignZeros == true) {
  9926. var zeroStepDifference = (step.marginEnd / step.step) - this.zeroCrossing;
  9927. if (zeroStepDifference > 0) {
  9928. for (var i = 0; i < zeroStepDifference; i++) {step.next();}
  9929. }
  9930. else if (zeroStepDifference < 0) {
  9931. for (var i = 0; i < -zeroStepDifference; i++) {step.previous();}
  9932. }
  9933. }
  9934. }
  9935. else {
  9936. amountOfSteps += 0.25;
  9937. }
  9938. this.valueAtZero = step.marginEnd;
  9939. var marginStartPos = 0;
  9940. // do not draw the first label
  9941. var max = 1;
  9942. // Get the number of decimal places
  9943. var decimals;
  9944. if(this.options.format[orientation] !== undefined) {
  9945. decimals = this.options.format[orientation].decimals;
  9946. }
  9947. this.maxLabelSize = 0;
  9948. var y = 0;
  9949. while (max < Math.round(amountOfSteps)) {
  9950. step.next();
  9951. y = Math.round(max * stepPixels);
  9952. marginStartPos = max * stepPixels;
  9953. var isMajor = step.isMajor();
  9954. if (this.options['showMinorLabels'] && isMajor == false || this.master == false && this.options['showMinorLabels'] == true) {
  9955. this._redrawLabel(y - 2, step.getCurrent(decimals), orientation, 'yAxis minor', this.props.minorCharHeight);
  9956. }
  9957. if (isMajor && this.options['showMajorLabels'] && this.master == true ||
  9958. this.options['showMinorLabels'] == false && this.master == false && isMajor == true) {
  9959. if (y >= 0) {
  9960. this._redrawLabel(y - 2, step.getCurrent(decimals), orientation, 'yAxis major', this.props.majorCharHeight);
  9961. }
  9962. this._redrawLine(y, orientation, 'grid horizontal major', this.options.majorLinesOffset, this.props.majorLineWidth);
  9963. }
  9964. else {
  9965. this._redrawLine(y, orientation, 'grid horizontal minor', this.options.minorLinesOffset, this.props.minorLineWidth);
  9966. }
  9967. if (this.master == true && step.current == 0) {
  9968. this.zeroCrossing = max;
  9969. }
  9970. max++;
  9971. }
  9972. if (this.master == false) {
  9973. this.conversionFactor = y / (this.valueAtZero - step.current);
  9974. }
  9975. else {
  9976. this.conversionFactor = this.dom.frame.offsetHeight / step.marginRange;
  9977. }
  9978. // Note that title is rotated, so we're using the height, not width!
  9979. var titleWidth = 0;
  9980. if (this.options.title[orientation] !== undefined && this.options.title[orientation].text !== undefined) {
  9981. titleWidth = this.props.titleCharHeight;
  9982. }
  9983. var offset = this.options.icons == true ? Math.max(this.options.iconWidth, titleWidth) + this.options.labelOffsetX + 15 : titleWidth + this.options.labelOffsetX + 15;
  9984. // this will resize the yAxis to accommodate the labels.
  9985. if (this.maxLabelSize > (this.width - offset) && this.options.visible == true) {
  9986. this.width = this.maxLabelSize + offset;
  9987. this.options.width = this.width + "px";
  9988. DOMutil.cleanupElements(this.DOMelements.lines);
  9989. DOMutil.cleanupElements(this.DOMelements.labels);
  9990. this.redraw();
  9991. resized = true;
  9992. }
  9993. // this will resize the yAxis if it is too big for the labels.
  9994. else if (this.maxLabelSize < (this.width - offset) && this.options.visible == true && this.width > this.minWidth) {
  9995. this.width = Math.max(this.minWidth,this.maxLabelSize + offset);
  9996. this.options.width = this.width + "px";
  9997. DOMutil.cleanupElements(this.DOMelements.lines);
  9998. DOMutil.cleanupElements(this.DOMelements.labels);
  9999. this.redraw();
  10000. resized = true;
  10001. }
  10002. else {
  10003. DOMutil.cleanupElements(this.DOMelements.lines);
  10004. DOMutil.cleanupElements(this.DOMelements.labels);
  10005. resized = false;
  10006. }
  10007. return resized;
  10008. };
  10009. DataAxis.prototype.convertValue = function (value) {
  10010. var invertedValue = this.valueAtZero - value;
  10011. var convertedValue = invertedValue * this.conversionFactor;
  10012. return convertedValue;
  10013. };
  10014. DataAxis.prototype.screenToValue = function (x) {
  10015. return this.valueAtZero - (x / this.conversionFactor);
  10016. };
  10017. /**
  10018. * Create a label for the axis at position x
  10019. * @private
  10020. * @param y
  10021. * @param text
  10022. * @param orientation
  10023. * @param className
  10024. * @param characterHeight
  10025. */
  10026. DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) {
  10027. // reuse redundant label
  10028. var label = DOMutil.getDOMElement('div',this.DOMelements.labels, this.dom.frame); //this.dom.redundant.labels.shift();
  10029. label.className = className;
  10030. label.innerHTML = text;
  10031. if (orientation == 'left') {
  10032. label.style.left = '-' + this.options.labelOffsetX + 'px';
  10033. label.style.textAlign = "right";
  10034. }
  10035. else {
  10036. label.style.right = '-' + this.options.labelOffsetX + 'px';
  10037. label.style.textAlign = "left";
  10038. }
  10039. label.style.top = y - 0.5 * characterHeight + this.options.labelOffsetY + 'px';
  10040. text += '';
  10041. var largestWidth = Math.max(this.props.majorCharWidth,this.props.minorCharWidth);
  10042. if (this.maxLabelSize < text.length * largestWidth) {
  10043. this.maxLabelSize = text.length * largestWidth;
  10044. }
  10045. };
  10046. /**
  10047. * Create a minor line for the axis at position y
  10048. * @param y
  10049. * @param orientation
  10050. * @param className
  10051. * @param offset
  10052. * @param width
  10053. */
  10054. DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) {
  10055. if (this.master == true) {
  10056. var line = DOMutil.getDOMElement('div',this.DOMelements.lines, this.dom.lineContainer);//this.dom.redundant.lines.shift();
  10057. line.className = className;
  10058. line.innerHTML = '';
  10059. if (orientation == 'left') {
  10060. line.style.left = (this.width - offset) + 'px';
  10061. }
  10062. else {
  10063. line.style.right = (this.width - offset) + 'px';
  10064. }
  10065. line.style.width = width + 'px';
  10066. line.style.top = y + 'px';
  10067. }
  10068. };
  10069. /**
  10070. * Create a title for the axis
  10071. * @private
  10072. * @param orientation
  10073. */
  10074. DataAxis.prototype._redrawTitle = function (orientation) {
  10075. DOMutil.prepareElements(this.DOMelements.title);
  10076. // Check if the title is defined for this axes
  10077. if (this.options.title[orientation] !== undefined && this.options.title[orientation].text !== undefined) {
  10078. var title = DOMutil.getDOMElement('div', this.DOMelements.title, this.dom.frame);
  10079. title.className = 'yAxis title ' + orientation;
  10080. title.innerHTML = this.options.title[orientation].text;
  10081. // Add style - if provided
  10082. if (this.options.title[orientation].style !== undefined) {
  10083. util.addCssText(title, this.options.title[orientation].style);
  10084. }
  10085. if (orientation == 'left') {
  10086. title.style.left = this.props.titleCharHeight + 'px';
  10087. }
  10088. else {
  10089. title.style.right = this.props.titleCharHeight + 'px';
  10090. }
  10091. title.style.width = this.height + 'px';
  10092. }
  10093. // we need to clean up in case we did not use all elements.
  10094. DOMutil.cleanupElements(this.DOMelements.title);
  10095. };
  10096. /**
  10097. * Determine the size of text on the axis (both major and minor axis).
  10098. * The size is calculated only once and then cached in this.props.
  10099. * @private
  10100. */
  10101. DataAxis.prototype._calculateCharSize = function () {
  10102. // determine the char width and height on the minor axis
  10103. if (!('minorCharHeight' in this.props)) {
  10104. var textMinor = document.createTextNode('0');
  10105. var measureCharMinor = document.createElement('div');
  10106. measureCharMinor.className = 'yAxis minor measure';
  10107. measureCharMinor.appendChild(textMinor);
  10108. this.dom.frame.appendChild(measureCharMinor);
  10109. this.props.minorCharHeight = measureCharMinor.clientHeight;
  10110. this.props.minorCharWidth = measureCharMinor.clientWidth;
  10111. this.dom.frame.removeChild(measureCharMinor);
  10112. }
  10113. if (!('majorCharHeight' in this.props)) {
  10114. var textMajor = document.createTextNode('0');
  10115. var measureCharMajor = document.createElement('div');
  10116. measureCharMajor.className = 'yAxis major measure';
  10117. measureCharMajor.appendChild(textMajor);
  10118. this.dom.frame.appendChild(measureCharMajor);
  10119. this.props.majorCharHeight = measureCharMajor.clientHeight;
  10120. this.props.majorCharWidth = measureCharMajor.clientWidth;
  10121. this.dom.frame.removeChild(measureCharMajor);
  10122. }
  10123. if (!('titleCharHeight' in this.props)) {
  10124. var textTitle = document.createTextNode('0');
  10125. var measureCharTitle = document.createElement('div');
  10126. measureCharTitle.className = 'yAxis title measure';
  10127. measureCharTitle.appendChild(textTitle);
  10128. this.dom.frame.appendChild(measureCharTitle);
  10129. this.props.titleCharHeight = measureCharTitle.clientHeight;
  10130. this.props.titleCharWidth = measureCharTitle.clientWidth;
  10131. this.dom.frame.removeChild(measureCharTitle);
  10132. }
  10133. };
  10134. module.exports = DataAxis;
  10135. /***/ },
  10136. /* 29 */
  10137. /***/ function(module, exports, __webpack_require__) {
  10138. var util = __webpack_require__(1);
  10139. var DOMutil = __webpack_require__(2);
  10140. var Line = __webpack_require__(49);
  10141. var Bar = __webpack_require__(50);
  10142. var Points = __webpack_require__(51);
  10143. /**
  10144. * /**
  10145. * @param {object} group | the object of the group from the dataset
  10146. * @param {string} groupId | ID of the group
  10147. * @param {object} options | the default options
  10148. * @param {array} groupsUsingDefaultStyles | this array has one entree.
  10149. * It is passed as an array so it is passed by reference.
  10150. * It enumerates through the default styles
  10151. * @constructor
  10152. */
  10153. function GraphGroup (group, groupId, options, groupsUsingDefaultStyles) {
  10154. this.id = groupId;
  10155. var fields = ['sampling','style','sort','yAxisOrientation','barChart','drawPoints','shaded','catmullRom']
  10156. this.options = util.selectiveBridgeObject(fields,options);
  10157. this.usingDefaultStyle = group.className === undefined;
  10158. this.groupsUsingDefaultStyles = groupsUsingDefaultStyles;
  10159. this.zeroPosition = 0;
  10160. this.update(group);
  10161. if (this.usingDefaultStyle == true) {
  10162. this.groupsUsingDefaultStyles[0] += 1;
  10163. }
  10164. this.itemsData = [];
  10165. this.visible = group.visible === undefined ? true : group.visible;
  10166. }
  10167. /**
  10168. * this loads a reference to all items in this group into this group.
  10169. * @param {array} items
  10170. */
  10171. GraphGroup.prototype.setItems = function(items) {
  10172. if (items != null) {
  10173. this.itemsData = items;
  10174. if (this.options.sort == true) {
  10175. this.itemsData.sort(function (a,b) {return a.x - b.x;})
  10176. }
  10177. }
  10178. else {
  10179. this.itemsData = [];
  10180. }
  10181. };
  10182. /**
  10183. * this is used for plotting barcharts, this way, we only have to calculate it once.
  10184. * @param pos
  10185. */
  10186. GraphGroup.prototype.setZeroPosition = function(pos) {
  10187. this.zeroPosition = pos;
  10188. };
  10189. /**
  10190. * set the options of the graph group over the default options.
  10191. * @param options
  10192. */
  10193. GraphGroup.prototype.setOptions = function(options) {
  10194. if (options !== undefined) {
  10195. var fields = ['sampling','style','sort','yAxisOrientation','barChart'];
  10196. util.selectiveDeepExtend(fields, this.options, options);
  10197. util.mergeOptions(this.options, options,'catmullRom');
  10198. util.mergeOptions(this.options, options,'drawPoints');
  10199. util.mergeOptions(this.options, options,'shaded');
  10200. if (options.catmullRom) {
  10201. if (typeof options.catmullRom == 'object') {
  10202. if (options.catmullRom.parametrization) {
  10203. if (options.catmullRom.parametrization == 'uniform') {
  10204. this.options.catmullRom.alpha = 0;
  10205. }
  10206. else if (options.catmullRom.parametrization == 'chordal') {
  10207. this.options.catmullRom.alpha = 1.0;
  10208. }
  10209. else {
  10210. this.options.catmullRom.parametrization = 'centripetal';
  10211. this.options.catmullRom.alpha = 0.5;
  10212. }
  10213. }
  10214. }
  10215. }
  10216. }
  10217. if (this.options.style == 'line') {
  10218. this.type = new Line(this.id, this.options);
  10219. }
  10220. else if (this.options.style == 'bar') {
  10221. this.type = new Bar(this.id, this.options);
  10222. }
  10223. else if (this.options.style == 'points') {
  10224. this.type = new Points(this.id, this.options);
  10225. }
  10226. };
  10227. /**
  10228. * this updates the current group class with the latest group dataset entree, used in _updateGroup in linegraph
  10229. * @param group
  10230. */
  10231. GraphGroup.prototype.update = function(group) {
  10232. this.group = group;
  10233. this.content = group.content || 'graph';
  10234. this.className = group.className || this.className || "graphGroup" + this.groupsUsingDefaultStyles[0] % 10;
  10235. this.visible = group.visible === undefined ? true : group.visible;
  10236. this.style = group.style;
  10237. this.setOptions(group.options);
  10238. };
  10239. /**
  10240. * draw the icon for the legend.
  10241. *
  10242. * @param x
  10243. * @param y
  10244. * @param JSONcontainer
  10245. * @param SVGcontainer
  10246. * @param iconWidth
  10247. * @param iconHeight
  10248. */
  10249. GraphGroup.prototype.drawIcon = function(x, y, JSONcontainer, SVGcontainer, iconWidth, iconHeight) {
  10250. var fillHeight = iconHeight * 0.5;
  10251. var path, fillPath;
  10252. var outline = DOMutil.getSVGElement("rect", JSONcontainer, SVGcontainer);
  10253. outline.setAttributeNS(null, "x", x);
  10254. outline.setAttributeNS(null, "y", y - fillHeight);
  10255. outline.setAttributeNS(null, "width", iconWidth);
  10256. outline.setAttributeNS(null, "height", 2*fillHeight);
  10257. outline.setAttributeNS(null, "class", "outline");
  10258. if (this.options.style == 'line') {
  10259. path = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
  10260. path.setAttributeNS(null, "class", this.className);
  10261. if(this.style !== undefined) {
  10262. path.setAttributeNS(null, "style", this.style);
  10263. }
  10264. path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+"");
  10265. if (this.options.shaded.enabled == true) {
  10266. fillPath = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
  10267. if (this.options.shaded.orientation == 'top') {
  10268. fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) +
  10269. "L"+x+","+y+" L"+ (x + iconWidth) + ","+y+" L"+ (x + iconWidth) + "," + (y - fillHeight));
  10270. }
  10271. else {
  10272. fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " +
  10273. "L"+x+"," + (y + fillHeight) + " " +
  10274. "L"+ (x + iconWidth) + "," + (y + fillHeight) +
  10275. "L"+ (x + iconWidth) + ","+y);
  10276. }
  10277. fillPath.setAttributeNS(null, "class", this.className + " iconFill");
  10278. }
  10279. if (this.options.drawPoints.enabled == true) {
  10280. DOMutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer);
  10281. }
  10282. }
  10283. else {
  10284. var barWidth = Math.round(0.3 * iconWidth);
  10285. var bar1Height = Math.round(0.4 * iconHeight);
  10286. var bar2Height = Math.round(0.75 * iconHeight);
  10287. var offset = Math.round((iconWidth - (2 * barWidth))/3);
  10288. DOMutil.drawBar(x + 0.5*barWidth + offset , y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' bar', JSONcontainer, SVGcontainer);
  10289. DOMutil.drawBar(x + 1.5*barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' bar', JSONcontainer, SVGcontainer);
  10290. }
  10291. };
  10292. /**
  10293. * return the legend entree for this group.
  10294. *
  10295. * @param iconWidth
  10296. * @param iconHeight
  10297. * @returns {{icon: HTMLElement, label: (group.content|*|string), orientation: (.options.yAxisOrientation|*)}}
  10298. */
  10299. GraphGroup.prototype.getLegend = function(iconWidth, iconHeight) {
  10300. var svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  10301. this.drawIcon(0,0.5*iconHeight,[],svg,iconWidth,iconHeight);
  10302. return {icon: svg, label: this.content, orientation:this.options.yAxisOrientation};
  10303. }
  10304. GraphGroup.prototype.getYRange = function(groupData) {
  10305. return this.type.getYRange(groupData);
  10306. }
  10307. GraphGroup.prototype.draw = function(dataset, group, framework) {
  10308. this.type.draw(dataset, group, framework);
  10309. }
  10310. module.exports = GraphGroup;
  10311. /***/ },
  10312. /* 30 */
  10313. /***/ function(module, exports, __webpack_require__) {
  10314. var util = __webpack_require__(1);
  10315. var stack = __webpack_require__(18);
  10316. var RangeItem = __webpack_require__(24);
  10317. /**
  10318. * @param {number | string} groupId
  10319. * @param {Object} data
  10320. * @param {ItemSet} itemSet
  10321. * @constructor Group
  10322. */
  10323. function Group(groupId, data, itemSet) {
  10324. this.groupId = groupId;
  10325. this.subgroups = {};
  10326. this.subgroupStack = {};
  10327. this.subgroupStackAll = false;
  10328. this.doInnerStack = false;
  10329. this.subgroupIndex = 0;
  10330. this.subgroupOrderer = data && data.subgroupOrder;
  10331. this.itemSet = itemSet;
  10332. this.isVisible = null;
  10333. this.stackDirty = true; // if true, items will be restacked on next redraw
  10334. if (data && data.nestedGroups) {
  10335. this.nestedGroups = data.nestedGroups;
  10336. if (data.showNested == false) {
  10337. this.showNested = false;
  10338. } else {
  10339. this.showNested = true;
  10340. }
  10341. }
  10342. if (data && data.subgroupStack) {
  10343. if (typeof data.subgroupStack === "boolean") {
  10344. this.doInnerStack = data.subgroupStack;
  10345. this.subgroupStackAll = data.subgroupStack;
  10346. } else {
  10347. // We might be doing stacking on specific sub groups, but only
  10348. // if at least one is set to do stacking
  10349. for (var key in data.subgroupStack) {
  10350. this.subgroupStack[key] = data.subgroupStack[key];
  10351. this.doInnerStack = this.doInnerStack || data.subgroupStack[key];
  10352. }
  10353. }
  10354. }
  10355. this.nestedInGroup = null;
  10356. this.dom = {};
  10357. this.props = {
  10358. label: {
  10359. width: 0,
  10360. height: 0
  10361. }
  10362. };
  10363. this.className = null;
  10364. this.items = {}; // items filtered by groupId of this group
  10365. this.visibleItems = []; // items currently visible in window
  10366. this.itemsInRange = []; // items currently in range
  10367. this.orderedItems = {
  10368. byStart: [],
  10369. byEnd: []
  10370. };
  10371. this.checkRangedItems = false; // needed to refresh the ranged items if the window is programatically changed with NO overlap.
  10372. var me = this;
  10373. this.itemSet.body.emitter.on("checkRangedItems", function () {
  10374. me.checkRangedItems = true;
  10375. });
  10376. this._create();
  10377. this.setData(data);
  10378. }
  10379. /**
  10380. * Create DOM elements for the group
  10381. * @private
  10382. */
  10383. Group.prototype._create = function() {
  10384. var label = document.createElement('div');
  10385. label.className = 'vlabel';
  10386. this.dom.label = label;
  10387. var inner = document.createElement('div');
  10388. inner.className = 'inner';
  10389. label.appendChild(inner);
  10390. this.dom.inner = inner;
  10391. var foreground = document.createElement('div');
  10392. foreground.className = 'group';
  10393. //changes
  10394. if (this.groupId){
  10395. foreground.id = this.groupId;
  10396. }
  10397. //END CHANGES
  10398. foreground['timeline-group'] = this;
  10399. this.dom.foreground = foreground;
  10400. this.dom.background = document.createElement('div');
  10401. this.dom.background.className = 'group';
  10402. this.dom.axis = document.createElement('div');
  10403. this.dom.axis.className = 'group';
  10404. // create a hidden marker to detect when the Timelines container is attached
  10405. // to the DOM, or the style of a parent of the Timeline is changed from
  10406. // display:none is changed to visible.
  10407. this.dom.marker = document.createElement('div');
  10408. this.dom.marker.style.visibility = 'hidden'; // TODO: ask jos why this is not none?
  10409. this.dom.marker.innerHTML = '?';
  10410. this.dom.background.appendChild(this.dom.marker);
  10411. };
  10412. /**
  10413. * Set the group data for this group
  10414. * @param {Object} data Group data, can contain properties content and className
  10415. */
  10416. Group.prototype.setData = function (data) {
  10417. // update contents
  10418. var content;
  10419. var templateFunction;
  10420. if (this.itemSet.options && this.itemSet.options.groupTemplate) {
  10421. templateFunction = this.itemSet.options.groupTemplate.bind(this);
  10422. content = templateFunction(data, this.dom.inner);
  10423. } else {
  10424. content = data && data.content;
  10425. }
  10426. if (content instanceof Element) {
  10427. this.dom.inner.appendChild(content);
  10428. while (this.dom.inner.firstChild) {
  10429. this.dom.inner.removeChild(this.dom.inner.firstChild);
  10430. }
  10431. this.dom.inner.appendChild(content);
  10432. } else if (content instanceof Object) {
  10433. templateFunction(data, this.dom.inner);
  10434. } else if (content !== undefined && content !== null) {
  10435. this.dom.inner.innerHTML = content;
  10436. } else {
  10437. this.dom.inner.innerHTML = this.groupId || ''; // groupId can be null
  10438. }
  10439. // update title
  10440. this.dom.label.title = data && data.title || '';
  10441. if (!this.dom.inner.firstChild) {
  10442. util.addClassName(this.dom.inner, 'vis-hidden');
  10443. } else {
  10444. util.removeClassName(this.dom.inner, 'vis-hidden');
  10445. }
  10446. if (data && data.nestedGroups) {
  10447. if (!this.nestedGroups || this.nestedGroups != data.nestedGroups) {
  10448. this.nestedGroups = data.nestedGroups;
  10449. }
  10450. if (data.showNested !== undefined || this.showNested === undefined) {
  10451. if (data.showNested == false) {
  10452. this.showNested = false;
  10453. } else {
  10454. this.showNested = true;
  10455. }
  10456. }
  10457. util.addClassName(this.dom.label, 'vis-nesting-group');
  10458. var collapsedDirClassName = this.itemSet.options.rtl ? 'collapsed-rtl' : 'collapsed';
  10459. if (this.showNested) {
  10460. util.removeClassName(this.dom.label, collapsedDirClassName);
  10461. util.addClassName(this.dom.label, 'expanded');
  10462. } else {
  10463. util.removeClassName(this.dom.label, 'expanded');
  10464. util.addClassName(this.dom.label, collapsedDirClassName);
  10465. }
  10466. } else if (this.nestedGroups) {
  10467. this.nestedGroups = null;
  10468. collapsedDirClassName = this.itemSet.options.rtl ? 'collapsed-rtl' : 'collapsed';
  10469. util.removeClassName(this.dom.label, collapsedDirClassName);
  10470. util.removeClassName(this.dom.label, 'expanded');
  10471. util.removeClassName(this.dom.label, 'vis-nesting-group');
  10472. }
  10473. if (data && data.nestedInGroup) {
  10474. util.addClassName(this.dom.label, 'vis-nested-group');
  10475. if (this.itemSet.options && this.itemSet.options.rtl) {
  10476. this.dom.inner.style.paddingRight = '30px';
  10477. } else {
  10478. this.dom.inner.style.paddingLeft = '30px';
  10479. }
  10480. }
  10481. // update className
  10482. var className = data && data.className || null;
  10483. if (className != this.className) {
  10484. if (this.className) {
  10485. util.removeClassName(this.dom.label, this.className);
  10486. util.removeClassName(this.dom.foreground, this.className);
  10487. util.removeClassName(this.dom.background, this.className);
  10488. util.removeClassName(this.dom.axis, this.className);
  10489. }
  10490. util.addClassName(this.dom.label, className);
  10491. util.addClassName(this.dom.foreground, className);
  10492. util.addClassName(this.dom.background, className);
  10493. util.addClassName(this.dom.axis, className);
  10494. this.className = className;
  10495. }
  10496. // update style
  10497. if (this.style) {
  10498. util.removeCssText(this.dom.label, this.style);
  10499. this.style = null;
  10500. }
  10501. if (data && data.style) {
  10502. util.addCssText(this.dom.label, data.style);
  10503. this.style = data.style;
  10504. }
  10505. };
  10506. /**
  10507. * Get the width of the group label
  10508. * @return {number} width
  10509. */
  10510. Group.prototype.getLabelWidth = function() {
  10511. return this.props.label.width;
  10512. };
  10513. /**
  10514. * Repaint this group
  10515. * @param {{start: number, end: number}} range
  10516. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  10517. * @param {boolean} [restack=false] Force restacking of all items
  10518. * @return {boolean} Returns true if the group is resized
  10519. */
  10520. Group.prototype.redraw = function(range, margin, restack) {
  10521. var resized = false;
  10522. // force recalculation of the height of the items when the marker height changed
  10523. // (due to the Timeline being attached to the DOM or changed from display:none to visible)
  10524. var markerHeight = this.dom.marker.clientHeight;
  10525. if (markerHeight != this.lastMarkerHeight) {
  10526. this.lastMarkerHeight = markerHeight;
  10527. util.forEach(this.items, function (item) {
  10528. item.dirty = true;
  10529. if (item.displayed) item.redraw();
  10530. });
  10531. restack = true;
  10532. }
  10533. // reposition visible items vertically
  10534. if (typeof this.itemSet.options.order === 'function') {
  10535. // a custom order function
  10536. if (restack) {
  10537. // brute force restack of all items
  10538. // show all items
  10539. var me = this;
  10540. var limitSize = false;
  10541. util.forEach(this.items, function (item) {
  10542. if (!item.displayed) {
  10543. item.redraw();
  10544. me.visibleItems.push(item);
  10545. }
  10546. item.repositionX(limitSize);
  10547. });
  10548. // order all items and force a restacking
  10549. var customOrderedItems = this.orderedItems.byStart.slice().sort(function (a, b) {
  10550. return me.itemSet.options.order(a.data, b.data);
  10551. });
  10552. stack.stack(customOrderedItems, margin, true /* restack=true */);
  10553. }
  10554. this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
  10555. }
  10556. else {
  10557. // no custom order function, lazy stacking
  10558. this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
  10559. if (this.itemSet.options.stack) { // TODO: ugly way to access options...
  10560. stack.stack(this.visibleItems, margin, restack);
  10561. }
  10562. else { // no stacking
  10563. stack.nostack(this.visibleItems, margin, this.subgroups);
  10564. }
  10565. }
  10566. // recalculate the height of the group
  10567. var height = this._calculateHeight(margin);
  10568. // calculate actual size and position
  10569. var foreground = this.dom.foreground;
  10570. this.top = foreground.offsetTop;
  10571. this.left = foreground.offsetLeft;
  10572. this.width = foreground.offsetWidth;
  10573. resized = util.updateProperty(this, 'height', height) || resized;
  10574. // recalculate size of label
  10575. resized = util.updateProperty(this.props.label, 'width', this.dom.inner.clientWidth) || resized;
  10576. resized = util.updateProperty(this.props.label, 'height', this.dom.inner.clientHeight) || resized;
  10577. // apply new height
  10578. this.dom.background.style.height = height + 'px';
  10579. this.dom.foreground.style.height = height + 'px';
  10580. this.dom.label.style.height = height + 'px';
  10581. // update vertical position of items after they are re-stacked and the height of the group is calculated
  10582. for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
  10583. var item = this.visibleItems[i];
  10584. item.repositionY(margin);
  10585. }
  10586. return resized;
  10587. };
  10588. /**
  10589. * recalculate the height of the group
  10590. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  10591. * @returns {number} Returns the height
  10592. * @private
  10593. */
  10594. Group.prototype._calculateHeight = function (margin) {
  10595. // recalculate the height of the group
  10596. var height;
  10597. var visibleItems = this.visibleItems;
  10598. //var visibleSubgroups = [];
  10599. //this.visibleSubgroups = 0;
  10600. this.resetSubgroups();
  10601. var me = this;
  10602. if (visibleItems.length) {
  10603. var min = visibleItems[0].top;
  10604. var max = visibleItems[0].top + visibleItems[0].height;
  10605. util.forEach(visibleItems, function (item) {
  10606. min = Math.min(min, item.top);
  10607. max = Math.max(max, (item.top + item.height));
  10608. if (item.data.subgroup !== undefined) {
  10609. me.subgroups[item.data.subgroup].height = Math.max(me.subgroups[item.data.subgroup].height,item.height);
  10610. me.subgroups[item.data.subgroup].visible = true;
  10611. //if (visibleSubgroups.indexOf(item.data.subgroup) == -1){
  10612. // visibleSubgroups.push(item.data.subgroup);
  10613. // me.visibleSubgroups += 1;
  10614. //}
  10615. }
  10616. });
  10617. if (min > margin.axis) {
  10618. // there is an empty gap between the lowest item and the axis
  10619. var offset = min - margin.axis;
  10620. max -= offset;
  10621. util.forEach(visibleItems, function (item) {
  10622. item.top -= offset;
  10623. });
  10624. }
  10625. height = max + margin.item.vertical / 2;
  10626. }
  10627. else {
  10628. height = 0;
  10629. }
  10630. height = Math.max(height, this.props.label.height);
  10631. return height;
  10632. };
  10633. /**
  10634. * Show this group: attach to the DOM
  10635. */
  10636. Group.prototype.show = function() {
  10637. if (!this.dom.label.parentNode) {
  10638. this.itemSet.dom.labelSet.appendChild(this.dom.label);
  10639. }
  10640. if (!this.dom.foreground.parentNode) {
  10641. this.itemSet.dom.foreground.appendChild(this.dom.foreground);
  10642. }
  10643. if (!this.dom.background.parentNode) {
  10644. this.itemSet.dom.background.appendChild(this.dom.background);
  10645. }
  10646. if (!this.dom.axis.parentNode) {
  10647. this.itemSet.dom.axis.appendChild(this.dom.axis);
  10648. }
  10649. };
  10650. /**
  10651. * Hide this group: remove from the DOM
  10652. */
  10653. Group.prototype.hide = function() {
  10654. var label = this.dom.label;
  10655. if (label.parentNode) {
  10656. label.parentNode.removeChild(label);
  10657. }
  10658. var foreground = this.dom.foreground;
  10659. if (foreground.parentNode) {
  10660. foreground.parentNode.removeChild(foreground);
  10661. }
  10662. var background = this.dom.background;
  10663. if (background.parentNode) {
  10664. background.parentNode.removeChild(background);
  10665. }
  10666. var axis = this.dom.axis;
  10667. if (axis.parentNode) {
  10668. axis.parentNode.removeChild(axis);
  10669. }
  10670. };
  10671. /**
  10672. * Add an item to the group
  10673. * @param {Item} item
  10674. */
  10675. Group.prototype.add = function(item) {
  10676. this.items[item.id] = item;
  10677. item.setParent(this);
  10678. // add to
  10679. if (item.data.subgroup !== undefined) {
  10680. if (this.subgroups[item.data.subgroup] === undefined) {
  10681. this.subgroups[item.data.subgroup] = {height:0, visible: false, index:this.subgroupIndex, items: []};
  10682. this.subgroupIndex++;
  10683. }
  10684. this.subgroups[item.data.subgroup].items.push(item);
  10685. }
  10686. this.orderSubgroups();
  10687. if (this.visibleItems.indexOf(item) == -1) {
  10688. var range = this.itemSet.body.range; // TODO: not nice accessing the range like this
  10689. this._checkIfVisible(item, this.visibleItems, range);
  10690. }
  10691. };
  10692. Group.prototype.orderSubgroups = function() {
  10693. if (this.subgroupOrderer !== undefined) {
  10694. var sortArray = [];
  10695. if (typeof this.subgroupOrderer == 'string') {
  10696. for (var subgroup in this.subgroups) {
  10697. sortArray.push({subgroup: subgroup, sortField: this.subgroups[subgroup].items[0].data[this.subgroupOrderer]})
  10698. }
  10699. sortArray.sort(function (a, b) {
  10700. return a.sortField - b.sortField;
  10701. })
  10702. }
  10703. else if (typeof this.subgroupOrderer == 'function') {
  10704. for (var subgroup in this.subgroups) {
  10705. sortArray.push(this.subgroups[subgroup].items[0].data);
  10706. }
  10707. sortArray.sort(this.subgroupOrderer);
  10708. }
  10709. if (sortArray.length > 0) {
  10710. for (var i = 0; i < sortArray.length; i++) {
  10711. this.subgroups[sortArray[i].subgroup].index = i;
  10712. }
  10713. }
  10714. }
  10715. };
  10716. Group.prototype.resetSubgroups = function() {
  10717. for (var subgroup in this.subgroups) {
  10718. if (this.subgroups.hasOwnProperty(subgroup)) {
  10719. this.subgroups[subgroup].visible = false;
  10720. }
  10721. }
  10722. };
  10723. /**
  10724. * Remove an item from the group
  10725. * @param {Item} item
  10726. */
  10727. Group.prototype.remove = function(item) {
  10728. delete this.items[item.id];
  10729. item.setParent(null);
  10730. // remove from visible items
  10731. var index = this.visibleItems.indexOf(item);
  10732. if (index != -1) this.visibleItems.splice(index, 1);
  10733. // TODO: also remove from ordered items?
  10734. };
  10735. /**
  10736. * Remove an item from the corresponding DataSet
  10737. * @param {Item} item
  10738. */
  10739. Group.prototype.removeFromDataSet = function(item) {
  10740. this.itemSet.removeItem(item.id);
  10741. };
  10742. /**
  10743. * Reorder the items
  10744. */
  10745. Group.prototype.order = function() {
  10746. var array = util.toArray(this.items);
  10747. var startArray = [];
  10748. var endArray = [];
  10749. for (var i = 0; i < array.length; i++) {
  10750. if (array[i].data.end !== undefined) {
  10751. endArray.push(array[i]);
  10752. }
  10753. startArray.push(array[i]);
  10754. }
  10755. this.orderedItems = {
  10756. byStart: startArray,
  10757. byEnd: endArray
  10758. };
  10759. stack.orderByStart(this.orderedItems.byStart);
  10760. stack.orderByEnd(this.orderedItems.byEnd);
  10761. };
  10762. /**
  10763. * Update the visible items
  10764. * @param {{byStart: Item[], byEnd: Item[]}} orderedItems All items ordered by start date and by end date
  10765. * @param {Item[]} visibleItems The previously visible items.
  10766. * @param {{start: number, end: number}} range Visible range
  10767. * @return {Item[]} visibleItems The new visible items.
  10768. * @private
  10769. */
  10770. Group.prototype._updateVisibleItems = function(orderedItems, oldVisibleItems, range) {
  10771. var visibleItems = [];
  10772. var visibleItemsLookup = {}; // we keep this to quickly look up if an item already exists in the list without using indexOf on visibleItems
  10773. var interval = (range.end - range.start) / 4;
  10774. var lowerBound = range.start - interval;
  10775. var upperBound = range.end + interval;
  10776. var item, i;
  10777. // this function is used to do the binary search.
  10778. var searchFunction = function (value) {
  10779. if (value < lowerBound) {return -1;}
  10780. else if (value <= upperBound) {return 0;}
  10781. else {return 1;}
  10782. }
  10783. // first check if the items that were in view previously are still in view.
  10784. // IMPORTANT: this handles the case for the items with startdate before the window and enddate after the window!
  10785. // also cleans up invisible items.
  10786. if (oldVisibleItems.length > 0) {
  10787. for (i = 0; i < oldVisibleItems.length; i++) {
  10788. this._checkIfVisibleWithReference(oldVisibleItems[i], visibleItems, visibleItemsLookup, range);
  10789. }
  10790. }
  10791. // we do a binary search for the items that have only start values.
  10792. var initialPosByStart = util.binarySearchCustom(orderedItems.byStart, searchFunction, 'data','start');
  10793. // trace the visible items from the inital start pos both ways until an invisible item is found, we only look at the start values.
  10794. this._traceVisible(initialPosByStart, orderedItems.byStart, visibleItems, visibleItemsLookup, function (item) {
  10795. return (item.data.start < lowerBound || item.data.start > upperBound);
  10796. });
  10797. // if the window has changed programmatically without overlapping the old window, the ranged items with start < lowerBound and end > upperbound are not shown.
  10798. // We therefore have to brute force check all items in the byEnd list
  10799. if (this.checkRangedItems == true) {
  10800. this.checkRangedItems = false;
  10801. for (i = 0; i < orderedItems.byEnd.length; i++) {
  10802. this._checkIfVisibleWithReference(orderedItems.byEnd[i], visibleItems, visibleItemsLookup, range);
  10803. }
  10804. }
  10805. else {
  10806. // we do a binary search for the items that have defined end times.
  10807. var initialPosByEnd = util.binarySearchCustom(orderedItems.byEnd, searchFunction, 'data','end');
  10808. // trace the visible items from the inital start pos both ways until an invisible item is found, we only look at the end values.
  10809. this._traceVisible(initialPosByEnd, orderedItems.byEnd, visibleItems, visibleItemsLookup, function (item) {
  10810. return (item.data.end < lowerBound || item.data.end > upperBound);
  10811. });
  10812. }
  10813. // finally, we reposition all the visible items.
  10814. for (i = 0; i < visibleItems.length; i++) {
  10815. item = visibleItems[i];
  10816. if (!item.displayed) item.show();
  10817. // reposition item horizontally
  10818. item.repositionX();
  10819. }
  10820. // debug
  10821. //console.log("new line")
  10822. //if (this.groupId == null) {
  10823. // for (i = 0; i < orderedItems.byStart.length; i++) {
  10824. // item = orderedItems.byStart[i].data;
  10825. // console.log('start',i,initialPosByStart, item.start.valueOf(), item.content, item.start >= lowerBound && item.start <= upperBound,i == initialPosByStart ? "<------------------- HEREEEE" : "")
  10826. // }
  10827. // for (i = 0; i < orderedItems.byEnd.length; i++) {
  10828. // item = orderedItems.byEnd[i].data;
  10829. // console.log('rangeEnd',i,initialPosByEnd, item.end.valueOf(), item.content, item.end >= range.start && item.end <= range.end,i == initialPosByEnd ? "<------------------- HEREEEE" : "")
  10830. // }
  10831. //}
  10832. return visibleItems;
  10833. };
  10834. Group.prototype._traceVisible = function (initialPos, items, visibleItems, visibleItemsLookup, breakCondition) {
  10835. var item;
  10836. var i;
  10837. if (initialPos != -1) {
  10838. for (i = initialPos; i >= 0; i--) {
  10839. item = items[i];
  10840. if (breakCondition(item)) {
  10841. break;
  10842. }
  10843. else {
  10844. if (visibleItemsLookup[item.id] === undefined) {
  10845. visibleItemsLookup[item.id] = true;
  10846. visibleItems.push(item);
  10847. }
  10848. }
  10849. }
  10850. for (i = initialPos + 1; i < items.length; i++) {
  10851. item = items[i];
  10852. if (breakCondition(item)) {
  10853. break;
  10854. }
  10855. else {
  10856. if (visibleItemsLookup[item.id] === undefined) {
  10857. visibleItemsLookup[item.id] = true;
  10858. visibleItems.push(item);
  10859. }
  10860. }
  10861. }
  10862. }
  10863. }
  10864. /**
  10865. * this function is very similar to the _checkIfInvisible() but it does not
  10866. * return booleans, hides the item if it should not be seen and always adds to
  10867. * the visibleItems.
  10868. * this one is for brute forcing and hiding.
  10869. *
  10870. * @param {Item} item
  10871. * @param {Array} visibleItems
  10872. * @param {{start:number, end:number}} range
  10873. * @private
  10874. */
  10875. Group.prototype._checkIfVisible = function(item, visibleItems, range) {
  10876. if (item.isVisible(range)) {
  10877. if (!item.displayed) item.show();
  10878. // reposition item horizontally
  10879. item.repositionX();
  10880. visibleItems.push(item);
  10881. }
  10882. else {
  10883. if (item.displayed) item.hide();
  10884. }
  10885. };
  10886. /**
  10887. * this function is very similar to the _checkIfInvisible() but it does not
  10888. * return booleans, hides the item if it should not be seen and always adds to
  10889. * the visibleItems.
  10890. * this one is for brute forcing and hiding.
  10891. *
  10892. * @param {Item} item
  10893. * @param {Array} visibleItems
  10894. * @param {{start:number, end:number}} range
  10895. * @private
  10896. */
  10897. Group.prototype._checkIfVisibleWithReference = function(item, visibleItems, visibleItemsLookup, range) {
  10898. if (item.isVisible(range)) {
  10899. if (visibleItemsLookup[item.id] === undefined) {
  10900. visibleItemsLookup[item.id] = true;
  10901. visibleItems.push(item);
  10902. }
  10903. }
  10904. else {
  10905. if (item.displayed) item.hide();
  10906. }
  10907. };
  10908. module.exports = Group;
  10909. /***/ },
  10910. /* 31 */
  10911. /***/ function(module, exports, __webpack_require__) {
  10912. var util = __webpack_require__(1);
  10913. var Group = __webpack_require__(30);
  10914. /**
  10915. * @constructor BackgroundGroup
  10916. * @param {Number | String} groupId
  10917. * @param {Object} data
  10918. * @param {ItemSet} itemSet
  10919. */
  10920. function BackgroundGroup (groupId, data, itemSet) {
  10921. Group.call(this, groupId, data, itemSet);
  10922. this.width = 0;
  10923. this.height = 0;
  10924. this.top = 0;
  10925. this.left = 0;
  10926. }
  10927. BackgroundGroup.prototype = Object.create(Group.prototype);
  10928. /**
  10929. * Repaint this group
  10930. * @param {{start: number, end: number}} range
  10931. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  10932. * @param {boolean} [restack=false] Force restacking of all items
  10933. * @return {boolean} Returns true if the group is resized
  10934. */
  10935. BackgroundGroup.prototype.redraw = function(range, margin, restack) {
  10936. var resized = false;
  10937. this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
  10938. // calculate actual size
  10939. this.width = this.dom.background.offsetWidth;
  10940. // apply new height (just always zero for BackgroundGroup
  10941. this.dom.background.style.height = '0';
  10942. // update vertical position of items after they are re-stacked and the height of the group is calculated
  10943. for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
  10944. var item = this.visibleItems[i];
  10945. item.repositionY(margin);
  10946. }
  10947. return resized;
  10948. };
  10949. /**
  10950. * Show this group: attach to the DOM
  10951. */
  10952. BackgroundGroup.prototype.show = function() {
  10953. if (!this.dom.background.parentNode) {
  10954. this.itemSet.dom.background.appendChild(this.dom.background);
  10955. }
  10956. };
  10957. module.exports = BackgroundGroup;
  10958. /***/ },
  10959. /* 32 */
  10960. /***/ function(module, exports, __webpack_require__) {
  10961. var Hammer = __webpack_require__(45);
  10962. var util = __webpack_require__(1);
  10963. var DataSet = __webpack_require__(3);
  10964. var DataView = __webpack_require__(4);
  10965. var TimeStep = __webpack_require__(19);
  10966. var Component = __webpack_require__(25);
  10967. var Group = __webpack_require__(30);
  10968. var BackgroundGroup = __webpack_require__(31);
  10969. var BoxItem = __webpack_require__(22);
  10970. var PointItem = __webpack_require__(23);
  10971. var RangeItem = __webpack_require__(24);
  10972. var BackgroundItem = __webpack_require__(21);
  10973. var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
  10974. var BACKGROUND = '__background__'; // reserved group id for background items without group
  10975. /**
  10976. * An ItemSet holds a set of items and ranges which can be displayed in a
  10977. * range. The width is determined by the parent of the ItemSet, and the height
  10978. * is determined by the size of the items.
  10979. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
  10980. * @param {Object} [options] See ItemSet.setOptions for the available options.
  10981. * @constructor ItemSet
  10982. * @extends Component
  10983. */
  10984. function ItemSet(body, options) {
  10985. this.body = body;
  10986. this.defaultOptions = {
  10987. type: null, // 'box', 'point', 'range', 'background'
  10988. orientation: {
  10989. item: 'bottom' // item orientation: 'top' or 'bottom'
  10990. },
  10991. align: 'auto', // alignment of box items
  10992. stack: true,
  10993. stackSubgroups: true,
  10994. groupOrderSwap: function groupOrderSwap(fromGroup, toGroup, groups) {
  10995. // eslint-disable-line no-unused-vars
  10996. var targetOrder = toGroup.order;
  10997. toGroup.order = fromGroup.order;
  10998. fromGroup.order = targetOrder;
  10999. },
  11000. groupOrder: 'order',
  11001. selectable: true,
  11002. multiselect: false,
  11003. itemsAlwaysDraggable: {
  11004. item: false,
  11005. range: false
  11006. },
  11007. editable: {
  11008. updateTime: false,
  11009. updateGroup: false,
  11010. add: false,
  11011. remove: false,
  11012. overrideItems: false
  11013. },
  11014. groupEditable: {
  11015. order: false,
  11016. add: false,
  11017. remove: false
  11018. },
  11019. snap: TimeStep.snap,
  11020. // Only called when `objectData.target === 'item'.
  11021. onDropObjectOnItem: function onDropObjectOnItem(objectData, item, callback) {
  11022. callback(item);
  11023. },
  11024. onAdd: function onAdd(item, callback) {
  11025. callback(item);
  11026. },
  11027. onUpdate: function onUpdate(item, callback) {
  11028. callback(item);
  11029. },
  11030. onMove: function onMove(item, callback) {
  11031. callback(item);
  11032. },
  11033. onRemove: function onRemove(item, callback) {
  11034. callback(item);
  11035. },
  11036. onMoving: function onMoving(item, callback) {
  11037. callback(item);
  11038. },
  11039. onAddGroup: function onAddGroup(item, callback) {
  11040. callback(item);
  11041. },
  11042. onMoveGroup: function onMoveGroup(item, callback) {
  11043. callback(item);
  11044. },
  11045. onRemoveGroup: function onRemoveGroup(item, callback) {
  11046. callback(item);
  11047. },
  11048. margin: {
  11049. item: {
  11050. horizontal: 10,
  11051. vertical: 10
  11052. },
  11053. axis: 20
  11054. },
  11055. showTooltips: true,
  11056. tooltip: {
  11057. followMouse: false,
  11058. overflowMethod: 'flip'
  11059. },
  11060. tooltipOnItemUpdateTime: false
  11061. };
  11062. // options is shared by this ItemSet and all its items
  11063. this.options = util.extend({}, this.defaultOptions);
  11064. this.options.rtl = options.rtl;
  11065. // options for getting items from the DataSet with the correct type
  11066. this.itemOptions = {
  11067. type: { start: 'Date', end: 'Date' }
  11068. };
  11069. this.conversion = {
  11070. toScreen: body.util.toScreen,
  11071. toTime: body.util.toTime
  11072. };
  11073. this.dom = {};
  11074. this.props = {};
  11075. this.hammer = null;
  11076. var me = this;
  11077. this.itemsData = null; // DataSet
  11078. this.groupsData = null; // DataSet
  11079. // listeners for the DataSet of the items
  11080. this.itemListeners = {
  11081. 'add': function add(event, params, senderId) {
  11082. // eslint-disable-line no-unused-vars
  11083. me._onAdd(params.items);
  11084. },
  11085. 'update': function update(event, params, senderId) {
  11086. // eslint-disable-line no-unused-vars
  11087. me._onUpdate(params.items);
  11088. },
  11089. 'remove': function remove(event, params, senderId) {
  11090. // eslint-disable-line no-unused-vars
  11091. me._onRemove(params.items);
  11092. }
  11093. };
  11094. // listeners for the DataSet of the groups
  11095. this.groupListeners = {
  11096. 'add': function add(event, params, senderId) {
  11097. // eslint-disable-line no-unused-vars
  11098. me._onAddGroups(params.items);
  11099. if (me.groupsData && me.groupsData.length > 0) {
  11100. var groupsData = me.groupsData.getDataSet();
  11101. groupsData.get().forEach(function (groupData) {
  11102. if (groupData.nestedGroups) {
  11103. if (groupData.showNested != false) {
  11104. groupData.showNested = true;
  11105. }
  11106. var updatedGroups = [];
  11107. groupData.nestedGroups.forEach(function (nestedGroupId) {
  11108. var updatedNestedGroup = groupsData.get(nestedGroupId);
  11109. if (!updatedNestedGroup) {
  11110. return;
  11111. }
  11112. updatedNestedGroup.nestedInGroup = groupData.id;
  11113. if (groupData.showNested == false) {
  11114. updatedNestedGroup.visible = false;
  11115. }
  11116. updatedGroups = updatedGroups.concat(updatedNestedGroup);
  11117. });
  11118. groupsData.update(updatedGroups, senderId);
  11119. }
  11120. });
  11121. }
  11122. },
  11123. 'update': function update(event, params, senderId) {
  11124. // eslint-disable-line no-unused-vars
  11125. me._onUpdateGroups(params.items);
  11126. },
  11127. 'remove': function remove(event, params, senderId) {
  11128. // eslint-disable-line no-unused-vars
  11129. me._onRemoveGroups(params.items);
  11130. }
  11131. };
  11132. this.items = {}; // object with an Item for every data item
  11133. this.groups = {}; // Group object for every group
  11134. this.groupIds = [];
  11135. this.selection = []; // list with the ids of all selected nodes
  11136. this.popup = null;
  11137. this.touchParams = {}; // stores properties while dragging
  11138. this.groupTouchParams = {};
  11139. // create the HTML DOM
  11140. this._create();
  11141. this.setOptions(options);
  11142. }
  11143. ItemSet.prototype = new Component();
  11144. // available item types will be registered here
  11145. ItemSet.types = {
  11146. background: BackgroundItem,
  11147. box: BoxItem,
  11148. range: RangeItem,
  11149. point: PointItem
  11150. };
  11151. /**
  11152. * Create the HTML DOM for the ItemSet
  11153. */
  11154. ItemSet.prototype._create = function(){
  11155. var frame = document.createElement('div');
  11156. frame.className = 'itemset';
  11157. frame['timeline-itemset'] = this;
  11158. this.dom.frame = frame;
  11159. // create background panel
  11160. var background = document.createElement('div');
  11161. background.className = 'background';
  11162. frame.appendChild(background);
  11163. this.dom.background = background;
  11164. // create foreground panel
  11165. var foreground = document.createElement('div');
  11166. foreground.className = 'foreground';
  11167. frame.appendChild(foreground);
  11168. this.dom.foreground = foreground;
  11169. // create axis panel
  11170. var axis = document.createElement('div');
  11171. axis.className = 'axis';
  11172. this.dom.axis = axis;
  11173. // create labelset
  11174. var labelSet = document.createElement('div');
  11175. labelSet.className = 'labelset';
  11176. this.dom.labelSet = labelSet;
  11177. // create ungrouped Group
  11178. this._updateUngrouped();
  11179. // create background Group
  11180. var backgroundGroup = new BackgroundGroup(BACKGROUND, null, this);
  11181. backgroundGroup.show();
  11182. this.groups[BACKGROUND] = backgroundGroup;
  11183. // attach event listeners
  11184. // Note: we bind to the centerContainer for the case where the height
  11185. // of the center container is larger than of the ItemSet, so we
  11186. // can click in the empty area to create a new item or deselect an item.
  11187. this.hammer = Hammer(this.body.dom.centerContainer, {
  11188. preventDefault: true
  11189. });
  11190. // drag items when selected
  11191. this.hammer.on('touch', this._onTouch.bind(this));
  11192. this.hammer.on('dragstart', this._onDragStart.bind(this));
  11193. this.hammer.on('drag', this._onDrag.bind(this));
  11194. this.hammer.on('dragend', this._onDragEnd.bind(this));
  11195. // single select (or unselect) when tapping an item
  11196. this.hammer.on('tap', this._onSelectItem.bind(this));
  11197. // multi select when holding mouse/touch, or on ctrl+click
  11198. this.hammer.on('hold', this._onMultiSelectItem.bind(this));
  11199. // add item on doubletap
  11200. this.hammer.on('doubletap', this._onAddItem.bind(this));
  11201. // attach to the DOM
  11202. this.show();
  11203. };
  11204. /**
  11205. * Set options for the ItemSet. Existing options will be extended/overwritten.
  11206. * @param {Object} [options] The following options are available:
  11207. * {String} type
  11208. * Default type for the items. Choose from 'box'
  11209. * (default), 'point', 'range', or 'background'.
  11210. * The default style can be overwritten by
  11211. * individual items.
  11212. * {String} align
  11213. * Alignment for the items, only applicable for
  11214. * BoxItem. Choose 'center' (default), 'left', or
  11215. * 'right'.
  11216. * {String} orientation
  11217. * Orientation of the item set. Choose 'top' or
  11218. * 'bottom' (default).
  11219. * {Function} groupOrder
  11220. * A sorting function for ordering groups
  11221. * {Boolean} stack
  11222. * If true (deafult), items will be stacked on
  11223. * top of each other.
  11224. * {Number} margin.axis
  11225. * Margin between the axis and the items in pixels.
  11226. * Default is 20.
  11227. * {Number} margin.item.horizontal
  11228. * Horizontal margin between items in pixels.
  11229. * Default is 10.
  11230. * {Number} margin.item.vertical
  11231. * Vertical Margin between items in pixels.
  11232. * Default is 10.
  11233. * {Number} margin.item
  11234. * Margin between items in pixels in both horizontal
  11235. * and vertical direction. Default is 10.
  11236. * {Number} margin
  11237. * Set margin for both axis and items in pixels.
  11238. * {Number} padding
  11239. * Padding of the contents of an item in pixels.
  11240. * Must correspond with the items css. Default is 5.
  11241. * {Boolean} selectable
  11242. * If true (default), items can be selected.
  11243. * {Boolean} editable
  11244. * Set all editable options to true or false
  11245. * {Boolean} editable.updateTime
  11246. * Allow dragging an item to an other moment in time
  11247. * {Boolean} editable.updateGroup
  11248. * Allow dragging an item to an other group
  11249. * {Boolean} editable.add
  11250. * Allow creating new items on double tap
  11251. * {Boolean} editable.remove
  11252. * Allow removing items by clicking the delete button
  11253. * top right of a selected item.
  11254. * {Function(item: Item, callback: Function)} onAdd
  11255. * Callback function triggered when an item is about to be added:
  11256. * when the user double taps an empty space in the Timeline.
  11257. * {Function(item: Item, callback: Function)} onUpdate
  11258. * Callback function fired when an item is about to be updated.
  11259. * This function typically has to show a dialog where the user
  11260. * change the item. If not implemented, nothing happens.
  11261. * {Function(item: Item, callback: Function)} onMove
  11262. * Fired when an item has been moved. If not implemented,
  11263. * the move action will be accepted.
  11264. * {Function(item: Item, callback: Function)} onRemove
  11265. * Fired when an item is about to be deleted.
  11266. * If not implemented, the item will be always removed.
  11267. */
  11268. ItemSet.prototype.setOptions = function(options) {
  11269. if (options) {
  11270. // copy all options that we know
  11271. var fields = ['type', 'align', 'order', 'padding', 'stack', 'selectable', 'groupOrder', 'dataAttributes', 'template','hide', 'snap'];
  11272. util.selectiveExtend(fields, this.options, options);
  11273. if ('orientation' in options) {
  11274. if (typeof options.orientation === 'string') {
  11275. this.options.orientation = options.orientation;
  11276. }
  11277. else if (typeof options.orientation === 'object' && 'item' in options.orientation) {
  11278. this.options.orientation = options.orientation.item;
  11279. }
  11280. }
  11281. if ('margin' in options) {
  11282. if (typeof options.margin === 'number') {
  11283. this.options.margin.axis = options.margin;
  11284. this.options.margin.item.horizontal = options.margin;
  11285. this.options.margin.item.vertical = options.margin;
  11286. }
  11287. else if (typeof options.margin === 'object') {
  11288. util.selectiveExtend(['axis'], this.options.margin, options.margin);
  11289. if ('item' in options.margin) {
  11290. if (typeof options.margin.item === 'number') {
  11291. this.options.margin.item.horizontal = options.margin.item;
  11292. this.options.margin.item.vertical = options.margin.item;
  11293. }
  11294. else if (typeof options.margin.item === 'object') {
  11295. util.selectiveExtend(['horizontal', 'vertical'], this.options.margin.item, options.margin.item);
  11296. }
  11297. }
  11298. }
  11299. }
  11300. if ('editable' in options) {
  11301. if (typeof options.editable === 'boolean') {
  11302. this.options.editable.updateTime = options.editable;
  11303. this.options.editable.updateGroup = options.editable;
  11304. this.options.editable.add = options.editable;
  11305. this.options.editable.remove = options.editable;
  11306. }
  11307. else if (typeof options.editable === 'object') {
  11308. util.selectiveExtend(['updateTime', 'updateGroup', 'add', 'remove'], this.options.editable, options.editable);
  11309. }
  11310. }
  11311. // callback functions
  11312. var addCallback = (function (name) {
  11313. var fn = options[name];
  11314. if (fn) {
  11315. if (!(fn instanceof Function)) {
  11316. throw new Error('option ' + name + ' must be a function ' + name + '(item, callback)');
  11317. }
  11318. this.options[name] = fn;
  11319. }
  11320. }).bind(this);
  11321. ['onAdd', 'onUpdate', 'onRemove', 'onMove', 'onMoving'].forEach(addCallback);
  11322. // force the itemSet to refresh: options like orientation and margins may be changed
  11323. this.markDirty();
  11324. }
  11325. };
  11326. /**
  11327. * Mark the ItemSet dirty so it will refresh everything with next redraw.
  11328. * Optionally, all items can be marked as dirty and be refreshed.
  11329. * @param {{refreshItems: boolean}} [options]
  11330. */
  11331. ItemSet.prototype.markDirty = function(options) {
  11332. this.groupIds = [];
  11333. this.stackDirty = true;
  11334. if (options && options.refreshItems) {
  11335. util.forEach(this.items, function (item) {
  11336. item.dirty = true;
  11337. if (item.displayed) item.redraw();
  11338. });
  11339. }
  11340. };
  11341. /**
  11342. * Destroy the ItemSet
  11343. */
  11344. ItemSet.prototype.destroy = function() {
  11345. this.hide();
  11346. this.setItems(null);
  11347. this.setGroups(null);
  11348. this.hammer = null;
  11349. this.body = null;
  11350. this.conversion = null;
  11351. };
  11352. /**
  11353. * Hide the component from the DOM
  11354. */
  11355. ItemSet.prototype.hide = function() {
  11356. // remove the frame containing the items
  11357. if (this.dom.frame.parentNode) {
  11358. this.dom.frame.parentNode.removeChild(this.dom.frame);
  11359. }
  11360. // remove the axis with dots
  11361. if (this.dom.axis.parentNode) {
  11362. this.dom.axis.parentNode.removeChild(this.dom.axis);
  11363. }
  11364. // remove the labelset containing all group labels
  11365. if (this.dom.labelSet.parentNode) {
  11366. this.dom.labelSet.parentNode.removeChild(this.dom.labelSet);
  11367. }
  11368. };
  11369. /**
  11370. * Show the component in the DOM (when not already visible).
  11371. * @return {Boolean} changed
  11372. */
  11373. ItemSet.prototype.show = function() {
  11374. // show frame containing the items
  11375. if (!this.dom.frame.parentNode) {
  11376. this.body.dom.center.appendChild(this.dom.frame);
  11377. }
  11378. // show axis with dots
  11379. if (!this.dom.axis.parentNode) {
  11380. this.body.dom.backgroundVertical.appendChild(this.dom.axis);
  11381. }
  11382. // show labelset containing labels
  11383. if (!this.dom.labelSet.parentNode) {
  11384. this.body.dom.left.appendChild(this.dom.labelSet);
  11385. }
  11386. };
  11387. /**
  11388. * Set selected items by their id. Replaces the current selection
  11389. * Unknown id's are silently ignored.
  11390. * @param {string[] | string} [ids] An array with zero or more id's of the items to be
  11391. * selected, or a single item id. If ids is undefined
  11392. * or an empty array, all items will be unselected.
  11393. */
  11394. ItemSet.prototype.setSelection = function(ids) {
  11395. var i, ii, id, item;
  11396. if (ids == undefined) ids = [];
  11397. if (!Array.isArray(ids)) ids = [ids];
  11398. // unselect currently selected items
  11399. for (i = 0, ii = this.selection.length; i < ii; i++) {
  11400. id = this.selection[i];
  11401. item = this.items[id];
  11402. if (item) item.unselect();
  11403. }
  11404. // select items
  11405. this.selection = [];
  11406. for (i = 0, ii = ids.length; i < ii; i++) {
  11407. id = ids[i];
  11408. item = this.items[id];
  11409. if (item) {
  11410. this.selection.push(id);
  11411. item.select();
  11412. }
  11413. }
  11414. };
  11415. /**
  11416. * Get the selected items by their id
  11417. * @return {Array} ids The ids of the selected items
  11418. */
  11419. ItemSet.prototype.getSelection = function() {
  11420. return this.selection.concat([]);
  11421. };
  11422. /**
  11423. * Get the id's of the currently visible items.
  11424. * @returns {Array} The ids of the visible items
  11425. */
  11426. ItemSet.prototype.getVisibleItems = function() {
  11427. var range = this.body.range.getRange();
  11428. var left = this.body.util.toScreen(range.start);
  11429. var right = this.body.util.toScreen(range.end);
  11430. var ids = [];
  11431. for (var groupId in this.groups) {
  11432. if (this.groups.hasOwnProperty(groupId)) {
  11433. var group = this.groups[groupId];
  11434. var rawVisibleItems = group.visibleItems;
  11435. // filter the "raw" set with visibleItems into a set which is really
  11436. // visible by pixels
  11437. for (var i = 0; i < rawVisibleItems.length; i++) {
  11438. var item = rawVisibleItems[i];
  11439. // TODO: also check whether visible vertically
  11440. if ((item.left < right) && (item.left + item.width > left)) {
  11441. ids.push(item.id);
  11442. }
  11443. }
  11444. }
  11445. }
  11446. return ids;
  11447. };
  11448. /**
  11449. * Deselect a selected item
  11450. * @param {String | Number} id
  11451. * @private
  11452. */
  11453. ItemSet.prototype._deselect = function(id) {
  11454. var selection = this.selection;
  11455. for (var i = 0, ii = selection.length; i < ii; i++) {
  11456. if (selection[i] == id) { // non-strict comparison!
  11457. selection.splice(i, 1);
  11458. break;
  11459. }
  11460. }
  11461. };
  11462. /**
  11463. * Repaint the component
  11464. * @return {boolean} Returns true if the component is resized
  11465. */
  11466. ItemSet.prototype.redraw = function() {
  11467. var margin = this.options.margin,
  11468. range = this.body.range,
  11469. asSize = util.option.asSize,
  11470. options = this.options,
  11471. orientation = options.orientation,
  11472. resized = false,
  11473. frame = this.dom.frame,
  11474. editable = options.editable.updateTime || options.editable.updateGroup;
  11475. // recalculate absolute position (before redrawing groups)
  11476. this.props.top = this.body.domProps.top.height + this.body.domProps.border.top;
  11477. this.props.left = this.body.domProps.left.width + this.body.domProps.border.left;
  11478. // update class name
  11479. frame.className = 'itemset' + (editable ? ' editable' : '');
  11480. // reorder the groups (if needed)
  11481. resized = this._orderGroups() || resized;
  11482. // check whether zoomed (in that case we need to re-stack everything)
  11483. // TODO: would be nicer to get this as a trigger from Range
  11484. var visibleInterval = range.end - range.start;
  11485. var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.props.width != this.props.lastWidth);
  11486. if (zoomed) this.stackDirty = true;
  11487. this.lastVisibleInterval = visibleInterval;
  11488. this.props.lastWidth = this.props.width;
  11489. var restack = this.stackDirty;
  11490. var firstGroup = this._firstGroup();
  11491. var firstMargin = {
  11492. item: margin.item,
  11493. axis: margin.axis
  11494. };
  11495. var nonFirstMargin = {
  11496. item: margin.item,
  11497. axis: margin.item.vertical / 2
  11498. };
  11499. var height = 0;
  11500. var minHeight = margin.axis + margin.item.vertical;
  11501. // redraw the background group
  11502. this.groups[BACKGROUND].redraw(range, nonFirstMargin, restack);
  11503. // redraw all regular groups
  11504. util.forEach(this.groups, function (group) {
  11505. var groupMargin = (group == firstGroup) ? firstMargin : nonFirstMargin;
  11506. var groupResized = group.redraw(range, groupMargin, restack);
  11507. resized = groupResized || resized;
  11508. height += group.height;
  11509. });
  11510. height = Math.max(height, minHeight);
  11511. this.stackDirty = false;
  11512. // update frame height
  11513. frame.style.height = asSize(height);
  11514. // calculate actual size
  11515. this.props.width = frame.offsetWidth;
  11516. this.props.height = height;
  11517. // reposition axis
  11518. this.dom.axis.style.top = asSize((orientation == 'top') ?
  11519. (this.body.domProps.top.height + this.body.domProps.border.top) :
  11520. (this.body.domProps.top.height + this.body.domProps.centerContainer.height));
  11521. this.dom.axis.style.left = '0';
  11522. // check if this component is resized
  11523. resized = this._isResized() || resized;
  11524. return resized;
  11525. };
  11526. /**
  11527. * Get the first group, aligned with the axis
  11528. * @return {Group | null} firstGroup
  11529. * @private
  11530. */
  11531. ItemSet.prototype._firstGroup = function() {
  11532. var firstGroupIndex = (this.options.orientation == 'top') ? 0 : (this.groupIds.length - 1);
  11533. var firstGroupId = this.groupIds[firstGroupIndex];
  11534. var firstGroup = this.groups[firstGroupId] || this.groups[UNGROUPED];
  11535. return firstGroup || null;
  11536. };
  11537. /**
  11538. * Create or delete the group holding all ungrouped items. This group is used when
  11539. * there are no groups specified.
  11540. * @protected
  11541. */
  11542. ItemSet.prototype._updateUngrouped = function() {
  11543. var ungrouped = this.groups[UNGROUPED];
  11544. var background = this.groups[BACKGROUND];
  11545. var item, itemId;
  11546. if (this.groupsData) {
  11547. // remove the group holding all ungrouped items
  11548. if (ungrouped) {
  11549. ungrouped.hide();
  11550. delete this.groups[UNGROUPED];
  11551. for (itemId in this.items) {
  11552. if (this.items.hasOwnProperty(itemId)) {
  11553. item = this.items[itemId];
  11554. item.parent && item.parent.remove(item);
  11555. var groupId = this._getGroupId(item.data);
  11556. var group = this.groups[groupId];
  11557. group && group.add(item) || item.hide();
  11558. }
  11559. }
  11560. }
  11561. }
  11562. else {
  11563. // create a group holding all (unfiltered) items
  11564. if (!ungrouped) {
  11565. var id = null;
  11566. var data = null;
  11567. ungrouped = new Group(id, data, this);
  11568. this.groups[UNGROUPED] = ungrouped;
  11569. for (itemId in this.items) {
  11570. if (this.items.hasOwnProperty(itemId)) {
  11571. item = this.items[itemId];
  11572. ungrouped.add(item);
  11573. }
  11574. }
  11575. ungrouped.show();
  11576. }
  11577. }
  11578. };
  11579. /**
  11580. * Get the element for the labelset
  11581. * @return {HTMLElement} labelSet
  11582. */
  11583. ItemSet.prototype.getLabelSet = function() {
  11584. return this.dom.labelSet;
  11585. };
  11586. /**
  11587. * Set items
  11588. * @param {vis.DataSet | null} items
  11589. */
  11590. ItemSet.prototype.setItems = function(items) {
  11591. var me = this,
  11592. ids,
  11593. oldItemsData = this.itemsData;
  11594. // replace the dataset
  11595. if (!items) {
  11596. this.itemsData = null;
  11597. }
  11598. else if (items instanceof DataSet || items instanceof DataView) {
  11599. this.itemsData = items;
  11600. }
  11601. else {
  11602. throw new TypeError('Data must be an instance of DataSet or DataView');
  11603. }
  11604. if (oldItemsData) {
  11605. // unsubscribe from old dataset
  11606. util.forEach(this.itemListeners, function (callback, event) {
  11607. oldItemsData.off(event, callback);
  11608. });
  11609. // remove all drawn items
  11610. ids = oldItemsData.getIds();
  11611. this._onRemove(ids);
  11612. }
  11613. if (this.itemsData) {
  11614. // subscribe to new dataset
  11615. var id = this.id;
  11616. util.forEach(this.itemListeners, function (callback, event) {
  11617. me.itemsData.on(event, callback, id);
  11618. });
  11619. // add all new items
  11620. ids = this.itemsData.getIds();
  11621. this._onAdd(ids);
  11622. // update the group holding all ungrouped items
  11623. this._updateUngrouped();
  11624. }
  11625. };
  11626. /**
  11627. * Get the current items
  11628. * @returns {vis.DataSet | null}
  11629. */
  11630. ItemSet.prototype.getItems = function() {
  11631. return this.itemsData;
  11632. };
  11633. /**
  11634. * Set groups
  11635. * @param {vis.DataSet} groups
  11636. */
  11637. ItemSet.prototype.setGroups = function (groups) {
  11638. var me = this,
  11639. ids;
  11640. // unsubscribe from current dataset
  11641. if (this.groupsData) {
  11642. util.forEach(this.groupListeners, function (callback, event) {
  11643. me.groupsData.off(event, callback);
  11644. });
  11645. // remove all drawn groups
  11646. ids = this.groupsData.getIds();
  11647. this.groupsData = null;
  11648. this._onRemoveGroups(ids); // note: this will cause a redraw
  11649. }
  11650. // replace the dataset
  11651. if (!groups) {
  11652. this.groupsData = null;
  11653. } else if (groups instanceof DataSet || groups instanceof DataView) {
  11654. this.groupsData = groups;
  11655. } else {
  11656. throw new TypeError('Data must be an instance of DataSet or DataView');
  11657. }
  11658. if (this.groupsData) {
  11659. // go over all groups nesting
  11660. var groupsData = this.groupsData;
  11661. if (this.groupsData instanceof DataView) {
  11662. groupsData = this.groupsData.getDataSet();
  11663. }
  11664. groupsData.get().forEach(function (group) {
  11665. if (group.nestedGroups) {
  11666. group.nestedGroups.forEach(function (nestedGroupId) {
  11667. var updatedNestedGroup = groupsData.get(nestedGroupId);
  11668. updatedNestedGroup.nestedInGroup = group.id;
  11669. if (group.showNested == false) {
  11670. updatedNestedGroup.visible = false;
  11671. }
  11672. groupsData.update(updatedNestedGroup);
  11673. });
  11674. }
  11675. });
  11676. // subscribe to new dataset
  11677. var id = this.id;
  11678. util.forEach(this.groupListeners, function (callback, event) {
  11679. me.groupsData.on(event, callback, id);
  11680. });
  11681. // draw all ms
  11682. ids = this.groupsData.getIds();
  11683. this._onAddGroups(ids);
  11684. }
  11685. // update the group holding all ungrouped items
  11686. this._updateUngrouped();
  11687. // update the order of all items in each group
  11688. this._order();
  11689. this.body.emitter.emit('_change', { queue: true });
  11690. };
  11691. /**
  11692. * Get the current groups
  11693. * @returns {vis.DataSet | null} groups
  11694. */
  11695. ItemSet.prototype.getGroups = function() {
  11696. return this.groupsData;
  11697. };
  11698. /**
  11699. * Remove an item by its id
  11700. * @param {String | Number} id
  11701. */
  11702. ItemSet.prototype.removeItem = function(id) {
  11703. var item = this.itemsData.get(id),
  11704. dataset = this.itemsData.getDataSet();
  11705. if (item) {
  11706. // confirm deletion
  11707. this.options.onRemove(item, function (item) {
  11708. if (item) {
  11709. // remove by id here, it is possible that an item has no id defined
  11710. // itself, so better not delete by the item itself
  11711. dataset.remove(id);
  11712. }
  11713. });
  11714. }
  11715. };
  11716. /**
  11717. * Get the time of an item based on it's data and options.type
  11718. * @param {Object} itemData
  11719. * @returns {string} Returns the type
  11720. * @private
  11721. */
  11722. ItemSet.prototype._getType = function (itemData) {
  11723. return itemData.type || this.options.type || (itemData.end ? 'range' : 'box');
  11724. };
  11725. /**
  11726. * Get the group id for an item
  11727. * @param {Object} itemData
  11728. * @returns {string} Returns the groupId
  11729. * @private
  11730. */
  11731. ItemSet.prototype._getGroupId = function (itemData) {
  11732. var type = this._getType(itemData);
  11733. if (type == 'background' && itemData.group == undefined) {
  11734. return BACKGROUND;
  11735. }
  11736. else {
  11737. return this.groupsData ? itemData.group : UNGROUPED;
  11738. }
  11739. };
  11740. /**
  11741. * Handle updated items
  11742. * @param {Number[]} ids
  11743. * @protected
  11744. */
  11745. ItemSet.prototype._onUpdate = function(ids) {
  11746. var me = this;
  11747. ids.forEach(function (id) {
  11748. var itemData = me.itemsData.get(id, me.itemOptions);
  11749. var item = me.items[id];
  11750. var type = me._getType(itemData);
  11751. var constructor = ItemSet.types[type];
  11752. if (item) {
  11753. // update item
  11754. if (!constructor || !(item instanceof constructor)) {
  11755. // item type has changed, delete the item and recreate it
  11756. me._removeItem(item);
  11757. item = null;
  11758. }
  11759. else {
  11760. me._updateItem(item, itemData);
  11761. }
  11762. }
  11763. if (!item) {
  11764. // create item
  11765. if (constructor) {
  11766. item = new constructor(itemData, me.conversion, me.options);
  11767. item.id = id; // TODO: not so nice setting id afterwards
  11768. me._addItem(item);
  11769. }
  11770. else if (type == 'rangeoverflow') {
  11771. // TODO: deprecated since version 2.1.0 (or 3.0.0?). cleanup some day
  11772. throw new TypeError('Item type "rangeoverflow" is deprecated. Use css styling instead: ' +
  11773. '.vis.timeline .item.range .content {overflow: visible;}');
  11774. }
  11775. else {
  11776. throw new TypeError('Unknown item type "' + type + '"');
  11777. }
  11778. }
  11779. });
  11780. this._order();
  11781. this.stackDirty = true; // force re-stacking of all items next redraw
  11782. this.body.emitter.emit('change', {queue: true});
  11783. };
  11784. /**
  11785. * Handle added items
  11786. * @param {Number[]} ids
  11787. * @protected
  11788. */
  11789. ItemSet.prototype._onAdd = ItemSet.prototype._onUpdate;
  11790. /**
  11791. * Handle removed items
  11792. * @param {Number[]} ids
  11793. * @protected
  11794. */
  11795. ItemSet.prototype._onRemove = function(ids) {
  11796. var count = 0;
  11797. var me = this;
  11798. ids.forEach(function (id) {
  11799. var item = me.items[id];
  11800. if (item) {
  11801. count++;
  11802. me._removeItem(item);
  11803. }
  11804. });
  11805. if (count) {
  11806. // update order
  11807. this._order();
  11808. this.stackDirty = true; // force re-stacking of all items next redraw
  11809. this.body.emitter.emit('change', {queue: true});
  11810. }
  11811. };
  11812. /**
  11813. * Update the order of item in all groups
  11814. * @private
  11815. */
  11816. ItemSet.prototype._order = function() {
  11817. // reorder the items in all groups
  11818. // TODO: optimization: only reorder groups affected by the changed items
  11819. util.forEach(this.groups, function (group) {
  11820. group.order();
  11821. });
  11822. };
  11823. /**
  11824. * Handle updated groups
  11825. * @param {Number[]} ids
  11826. * @private
  11827. */
  11828. ItemSet.prototype._onUpdateGroups = function(ids) {
  11829. this._onAddGroups(ids);
  11830. };
  11831. /**
  11832. * Handle changed groups (added or updated)
  11833. * @param {Number[]} ids
  11834. * @private
  11835. */
  11836. ItemSet.prototype._onAddGroups = function(ids) {
  11837. var me = this;
  11838. ids.forEach(function (id) {
  11839. var groupData = me.groupsData.get(id);
  11840. var group = me.groups[id];
  11841. if (!group) {
  11842. // check for reserved ids
  11843. if (id == UNGROUPED || id == BACKGROUND) {
  11844. throw new Error('Illegal group id. ' + id + ' is a reserved id.');
  11845. }
  11846. var groupOptions = Object.create(me.options);
  11847. util.extend(groupOptions, {
  11848. height: null
  11849. });
  11850. group = new Group(id, groupData, me);
  11851. me.groups[id] = group;
  11852. // add items with this groupId to the new group
  11853. for (var itemId in me.items) {
  11854. if (me.items.hasOwnProperty(itemId)) {
  11855. var item = me.items[itemId];
  11856. if (item.data.group == id) {
  11857. group.add(item);
  11858. }
  11859. }
  11860. }
  11861. group.order();
  11862. group.show();
  11863. }
  11864. else {
  11865. // update group
  11866. group.setData(groupData);
  11867. }
  11868. });
  11869. this.body.emitter.emit('change', {queue: true});
  11870. };
  11871. /**
  11872. * Handle removed groups
  11873. * @param {Number[]} ids
  11874. * @private
  11875. */
  11876. ItemSet.prototype._onRemoveGroups = function(ids) {
  11877. var groups = this.groups;
  11878. ids.forEach(function (id) {
  11879. var group = groups[id];
  11880. if (group) {
  11881. group.hide();
  11882. delete groups[id];
  11883. }
  11884. });
  11885. this.markDirty();
  11886. this.body.emitter.emit('change', {queue: true});
  11887. };
  11888. /**
  11889. * Reorder the groups if needed
  11890. * @return {boolean} changed
  11891. * @private
  11892. */
  11893. ItemSet.prototype._orderGroups = function () {
  11894. if (this.groupsData) {
  11895. // reorder the groups
  11896. var groupIds = this.groupsData.getIds({
  11897. order: this.options.groupOrder
  11898. });
  11899. groupIds = this._orderNestedGroups(groupIds);
  11900. var changed = !util.equalArray(groupIds, this.groupIds);
  11901. if (changed) {
  11902. // hide all groups, removes them from the DOM
  11903. var groups = this.groups;
  11904. groupIds.forEach(function (groupId) {
  11905. groups[groupId].hide();
  11906. });
  11907. // show the groups again, attach them to the DOM in correct order
  11908. groupIds.forEach(function (groupId) {
  11909. groups[groupId].show();
  11910. });
  11911. this.groupIds = groupIds;
  11912. }
  11913. return changed;
  11914. } else {
  11915. return false;
  11916. }
  11917. };
  11918. /**
  11919. * Reorder the nested groups
  11920. *
  11921. * @param {Array.<number>} groupIds
  11922. * @returns {Array.<number>}
  11923. * @private
  11924. */
  11925. ItemSet.prototype._orderNestedGroups = function (groupIds) {
  11926. var newGroupIdsOrder = [];
  11927. groupIds.forEach(function (groupId) {
  11928. var groupData = this.groupsData.get(groupId);
  11929. if (!groupData.nestedInGroup) {
  11930. newGroupIdsOrder.push(groupId);
  11931. }
  11932. if (groupData.nestedGroups) {
  11933. var nestedGroups = this.groupsData.get({
  11934. filter: function filter(nestedGroup) {
  11935. return nestedGroup.nestedInGroup == groupId;
  11936. },
  11937. order: this.options.groupOrder
  11938. });
  11939. var nestedGroupIds = nestedGroups.map(function (nestedGroup) {
  11940. return nestedGroup.id;
  11941. });
  11942. newGroupIdsOrder = newGroupIdsOrder.concat(nestedGroupIds);
  11943. }
  11944. }, this);
  11945. return newGroupIdsOrder;
  11946. };
  11947. /**
  11948. * Add a new item
  11949. * @param {Item} item
  11950. * @private
  11951. */
  11952. ItemSet.prototype._addItem = function(item) {
  11953. this.items[item.id] = item;
  11954. // add to group
  11955. var groupId = this._getGroupId(item.data);
  11956. var group = this.groups[groupId];
  11957. if (group) group.add(item);
  11958. };
  11959. /**
  11960. * Update an existing item
  11961. * @param {Item} item
  11962. * @param {Object} itemData
  11963. * @private
  11964. */
  11965. ItemSet.prototype._updateItem = function(item, itemData) {
  11966. var oldGroupId = item.data.group;
  11967. // update the items data (will redraw the item when displayed)
  11968. item.setData(itemData);
  11969. // update group
  11970. if (oldGroupId != item.data.group) {
  11971. var oldGroup = this.groups[oldGroupId];
  11972. if (oldGroup) oldGroup.remove(item);
  11973. var groupId = this._getGroupId(item.data);
  11974. var group = this.groups[groupId];
  11975. if (group) group.add(item);
  11976. }
  11977. };
  11978. /**
  11979. * Delete an item from the ItemSet: remove it from the DOM, from the map
  11980. * with items, and from the map with visible items, and from the selection
  11981. * @param {Item} item
  11982. * @private
  11983. */
  11984. ItemSet.prototype._removeItem = function(item) {
  11985. // remove from DOM
  11986. item.hide();
  11987. // remove from items
  11988. delete this.items[item.id];
  11989. // remove from selection
  11990. var index = this.selection.indexOf(item.id);
  11991. if (index != -1) this.selection.splice(index, 1);
  11992. // remove from group
  11993. item.parent && item.parent.remove(item);
  11994. };
  11995. /**
  11996. * Create an array containing all items being a range (having an end date)
  11997. * @param array
  11998. * @returns {Array}
  11999. * @private
  12000. */
  12001. ItemSet.prototype._constructByEndArray = function(array) {
  12002. var endArray = [];
  12003. for (var i = 0; i < array.length; i++) {
  12004. if (array[i] instanceof RangeItem) {
  12005. endArray.push(array[i]);
  12006. }
  12007. }
  12008. return endArray;
  12009. };
  12010. /**
  12011. * Register the clicked item on touch, before dragStart is initiated.
  12012. *
  12013. * dragStart is initiated from a mousemove event, which can have left the item
  12014. * already resulting in an item == null
  12015. *
  12016. * @param {Event} event
  12017. * @private
  12018. */
  12019. ItemSet.prototype._onTouch = function (event) {
  12020. // store the touched item, used in _onDragStart
  12021. this.touchParams.item = this.itemFromTarget(event);
  12022. };
  12023. /**
  12024. * Start dragging the selected events
  12025. * @param {Event} event
  12026. * @private
  12027. */
  12028. ItemSet.prototype._onDragStart = function (event) {
  12029. if (!this.options.editable.updateTime && !this.options.editable.updateGroup) {
  12030. return;
  12031. }
  12032. var item = this.touchParams.item || null;
  12033. var me = this;
  12034. var props;
  12035. if (item && item.selected) {
  12036. var dragLeftItem = event.target.dragLeftItem;
  12037. var dragRightItem = event.target.dragRightItem;
  12038. if (dragLeftItem) {
  12039. props = {
  12040. item: dragLeftItem,
  12041. initialX: event.gesture.center.pageX,
  12042. dragLeft: true,
  12043. data: util.extend({}, item.data) // clone the items data
  12044. };
  12045. this.touchParams.itemProps = [props];
  12046. }
  12047. else if (dragRightItem) {
  12048. props = {
  12049. item: dragRightItem,
  12050. initialX: event.gesture.center.pageX,
  12051. dragRight: true,
  12052. data: util.extend({}, item.data) // clone the items data
  12053. };
  12054. this.touchParams.itemProps = [props];
  12055. }
  12056. else {
  12057. this.touchParams.itemProps = this.getSelection().map(function (id) {
  12058. var item = me.items[id];
  12059. var props = {
  12060. item: item,
  12061. initialX: event.gesture.center.pageX,
  12062. data: util.extend({}, item.data) // clone the items data
  12063. };
  12064. return props;
  12065. });
  12066. }
  12067. event.stopPropagation();
  12068. }
  12069. else if (this.options.editable.add && event.gesture.srcEvent.ctrlKey) {
  12070. // create a new range item when dragging with ctrl key down
  12071. this._onDragStartAddItem(event);
  12072. }
  12073. };
  12074. /**
  12075. * Start creating a new range item by dragging.
  12076. * @param {Event} event
  12077. * @private
  12078. */
  12079. ItemSet.prototype._onDragStartAddItem = function (event) {
  12080. var snap = this.options.snap || null;
  12081. var xAbs = util.getAbsoluteLeft(this.dom.frame);
  12082. var x = event.gesture.center.pageX - xAbs - 10; // minus 10 to compensate for the drag starting as soon as you've moved 10px
  12083. var time = this.body.util.toTime(x);
  12084. var scale = this.body.util.getScale();
  12085. var step = this.body.util.getStep();
  12086. var start = snap ? snap(time, scale, step) : start;
  12087. var end = start;
  12088. var itemData = {
  12089. type: 'range',
  12090. start: start,
  12091. end: end,
  12092. content: 'new item'
  12093. };
  12094. var id = util.randomUUID();
  12095. itemData[this.itemsData._fieldId] = id;
  12096. var group = this.groupFromTarget(event);
  12097. if (group) {
  12098. itemData.group = group.groupId;
  12099. }
  12100. var newItem = new RangeItem(itemData, this.conversion, this.options);
  12101. newItem.id = id; // TODO: not so nice setting id afterwards
  12102. newItem.data = itemData;
  12103. this._addItem(newItem);
  12104. var props = {
  12105. item: newItem,
  12106. dragRight: true,
  12107. initialX: event.gesture.center.pageX,
  12108. data: util.extend({}, itemData)
  12109. };
  12110. this.touchParams.itemProps = [props];
  12111. event.stopPropagation();
  12112. };
  12113. /**
  12114. * Drag selected items
  12115. * @param {Event} event
  12116. * @private
  12117. */
  12118. ItemSet.prototype._onDrag = function (event) {
  12119. event.preventDefault();
  12120. if (this.touchParams.itemProps) {
  12121. event.stopPropagation();
  12122. var me = this;
  12123. var snap = this.options.snap || null;
  12124. var xOffset = this.body.dom.root.offsetLeft + this.body.domProps.left.width;
  12125. var scale = this.body.util.getScale();
  12126. var step = this.body.util.getStep();
  12127. // move
  12128. this.touchParams.itemProps.forEach(function (props) {
  12129. var current = me.body.util.toTime(event.gesture.center.pageX - xOffset);
  12130. var initial = me.body.util.toTime(props.initialX - xOffset);
  12131. var offset = current - initial;
  12132. var itemData = util.extend({}, props.item.data); // clone the data
  12133. if (me.options.editable.updateTime) {
  12134. if (props.dragLeft) {
  12135. // drag left side of a range item
  12136. if (itemData.start != undefined) {
  12137. var initialStart = util.convert(props.data.start, 'Date');
  12138. var start = new Date(initialStart.valueOf() + offset);
  12139. itemData.start = snap ? snap(start, scale, step) : start;
  12140. }
  12141. }
  12142. else if (props.dragRight) {
  12143. // drag right side of a range item
  12144. if (itemData.end != undefined) {
  12145. var initialEnd = util.convert(props.data.end, 'Date');
  12146. var end = new Date(initialEnd.valueOf() + offset);
  12147. itemData.end = snap ? snap(end, scale, step) : end;
  12148. }
  12149. }
  12150. else {
  12151. // drag both start and end
  12152. if (itemData.start != undefined) {
  12153. var initialStart = util.convert(props.data.start, 'Date').valueOf();
  12154. var start = new Date(initialStart + offset);
  12155. if (itemData.end != undefined) {
  12156. var initialEnd = util.convert(props.data.end, 'Date');
  12157. var duration = initialEnd.valueOf() - initialStart.valueOf();
  12158. itemData.start = snap ? snap(start, scale, step) : start;
  12159. itemData.end = new Date(itemData.start.valueOf() + duration);
  12160. }
  12161. else {
  12162. itemData.start = snap ? snap(start, scale, step) : start;
  12163. }
  12164. }
  12165. }
  12166. }
  12167. if (me.options.editable.updateGroup && (!props.dragLeft && !props.dragRight)) {
  12168. if (itemData.group != undefined) {
  12169. // drag from one group to another
  12170. var group = me.groupFromTarget(event);
  12171. if (group) {
  12172. itemData.group = group.groupId;
  12173. }
  12174. }
  12175. }
  12176. // confirm moving the item
  12177. me.options.onMoving(itemData, function (itemData) {
  12178. if (itemData) {
  12179. props.item.setData(itemData);
  12180. }
  12181. });
  12182. });
  12183. this.stackDirty = true; // force re-stacking of all items next redraw
  12184. this.body.emitter.emit('change');
  12185. }
  12186. };
  12187. /**
  12188. * Move an item to another group
  12189. * @param {Item} item
  12190. * @param {String | Number} groupId
  12191. * @private
  12192. */
  12193. ItemSet.prototype._moveToGroup = function(item, groupId) {
  12194. var group = this.groups[groupId];
  12195. if (group && group.groupId != item.data.group) {
  12196. var oldGroup = item.parent;
  12197. oldGroup.remove(item);
  12198. oldGroup.order();
  12199. group.add(item);
  12200. group.order();
  12201. item.data.group = group.groupId;
  12202. }
  12203. };
  12204. /**
  12205. * End of dragging selected items
  12206. * @param {Event} event
  12207. * @private
  12208. */
  12209. ItemSet.prototype._onDragEnd = function (event) {
  12210. event.preventDefault();
  12211. if (this.touchParams.itemProps) {
  12212. event.stopPropagation();
  12213. // prepare a change set for the changed items
  12214. var changes = [];
  12215. var me = this;
  12216. var dataset = this.itemsData.getDataSet();
  12217. var itemProps = this.touchParams.itemProps ;
  12218. this.touchParams.itemProps = null;
  12219. itemProps.forEach(function (props) {
  12220. var id = props.item.id;
  12221. var exists = me.itemsData.get(id, me.itemOptions) != null;
  12222. if (!exists) {
  12223. // add a new item
  12224. me.options.onAdd(props.item.data, function (itemData) {
  12225. me._removeItem(props.item); // remove temporary item
  12226. if (itemData) {
  12227. me.itemsData.getDataSet().add(itemData);
  12228. }
  12229. // force re-stacking of all items next redraw
  12230. me.stackDirty = true;
  12231. me.body.emitter.emit('change');
  12232. });
  12233. }
  12234. else {
  12235. // update existing item
  12236. var itemData = util.extend({}, props.item.data); // clone the data
  12237. me.options.onMove(itemData, function (itemData) {
  12238. if (itemData) {
  12239. // apply changes
  12240. itemData[dataset._fieldId] = id; // ensure the item contains its id (can be undefined)
  12241. changes.push(itemData);
  12242. }
  12243. else {
  12244. // restore original values
  12245. props.item.setData(props.data);
  12246. me.stackDirty = true; // force re-stacking of all items next redraw
  12247. me.body.emitter.emit('change');
  12248. }
  12249. });
  12250. }
  12251. });
  12252. // apply the changes to the data (if there are changes)
  12253. if (changes.length) {
  12254. dataset.update(changes);
  12255. }
  12256. }
  12257. };
  12258. ItemSet.prototype._onGroupClick = function (event) {
  12259. var group = this.groupFromTarget(event);
  12260. if (!group || !group.nestedGroups) return;
  12261. var groupsData = this.groupsData.getDataSet();
  12262. var nestingGroup = groupsData.get(group.groupId);
  12263. if (nestingGroup.showNested == undefined) {
  12264. nestingGroup.showNested = true;
  12265. }
  12266. nestingGroup.showNested = !nestingGroup.showNested;
  12267. var nestedGroups = groupsData.get(group.nestedGroups).map(function (nestedGroup) {
  12268. nestedGroup.visible = nestingGroup.showNested;
  12269. return nestedGroup;
  12270. });
  12271. groupsData.update(nestedGroups.concat(nestingGroup));
  12272. if (nestingGroup.showNested) {
  12273. util.removeClassName(group.dom.label, 'collapsed');
  12274. util.addClassName(group.dom.label, 'expanded');
  12275. } else {
  12276. util.removeClassName(group.dom.label, 'expanded');
  12277. var collapsedDirClassName = this.options.rtl ? 'collapsed-rtl' : 'collapsed';
  12278. util.addClassName(group.dom.label, collapsedDirClassName);
  12279. }
  12280. };
  12281. ItemSet.prototype._onGroupDragStart = function (event) {
  12282. if (this.options.groupEditable.order) {
  12283. this.groupTouchParams.group = this.groupFromTarget(event);
  12284. if (this.groupTouchParams.group) {
  12285. event.stopPropagation();
  12286. this.groupTouchParams.originalOrder = this.groupsData.getIds({
  12287. order: this.options.groupOrder
  12288. });
  12289. }
  12290. }
  12291. };
  12292. ItemSet.prototype._onGroupDrag = function (event) {
  12293. if (this.options.groupEditable.order && this.groupTouchParams.group) {
  12294. event.stopPropagation();
  12295. var groupsData = this.groupsData;
  12296. if (this.groupsData instanceof DataView) {
  12297. groupsData = this.groupsData.getDataSet();
  12298. }
  12299. // drag from one group to another
  12300. var group = this.groupFromTarget(event);
  12301. // try to avoid toggling when groups differ in height
  12302. if (group && group.height != this.groupTouchParams.group.height) {
  12303. var movingUp = group.top < this.groupTouchParams.group.top;
  12304. var clientY = event.center ? event.center.y : event.clientY;
  12305. var targetGroupTop = util.getAbsoluteTop(group.dom.foreground);
  12306. var draggedGroupHeight = this.groupTouchParams.group.height;
  12307. if (movingUp) {
  12308. // skip swapping the groups when the dragged group is not below clientY afterwards
  12309. if (targetGroupTop + draggedGroupHeight < clientY) {
  12310. return;
  12311. }
  12312. } else {
  12313. var targetGroupHeight = group.height;
  12314. // skip swapping the groups when the dragged group is not below clientY afterwards
  12315. if (targetGroupTop + targetGroupHeight - draggedGroupHeight > clientY) {
  12316. return;
  12317. }
  12318. }
  12319. }
  12320. if (group && group != this.groupTouchParams.group) {
  12321. var targetGroup = groupsData.get(group.groupId);
  12322. var draggedGroup = groupsData.get(this.groupTouchParams.group.groupId);
  12323. // switch groups
  12324. if (draggedGroup && targetGroup) {
  12325. this.options.groupOrderSwap(draggedGroup, targetGroup, groupsData);
  12326. groupsData.update(draggedGroup);
  12327. groupsData.update(targetGroup);
  12328. }
  12329. // fetch current order of groups
  12330. var newOrder = groupsData.getIds({
  12331. order: this.options.groupOrder
  12332. });
  12333. // in case of changes since _onGroupDragStart
  12334. if (!util.equalArray(newOrder, this.groupTouchParams.originalOrder)) {
  12335. var origOrder = this.groupTouchParams.originalOrder;
  12336. var draggedId = this.groupTouchParams.group.groupId;
  12337. var numGroups = Math.min(origOrder.length, newOrder.length);
  12338. var curPos = 0;
  12339. var newOffset = 0;
  12340. var orgOffset = 0;
  12341. while (curPos < numGroups) {
  12342. // as long as the groups are where they should be step down along the groups order
  12343. while (curPos + newOffset < numGroups && curPos + orgOffset < numGroups && newOrder[curPos + newOffset] == origOrder[curPos + orgOffset]) {
  12344. curPos++;
  12345. }
  12346. // all ok
  12347. if (curPos + newOffset >= numGroups) {
  12348. break;
  12349. }
  12350. // not all ok
  12351. // if dragged group was move upwards everything below should have an offset
  12352. if (newOrder[curPos + newOffset] == draggedId) {
  12353. newOffset = 1;
  12354. }
  12355. // if dragged group was move downwards everything above should have an offset
  12356. else if (origOrder[curPos + orgOffset] == draggedId) {
  12357. orgOffset = 1;
  12358. }
  12359. // found a group (apart from dragged group) that has the wrong position -> switch with the
  12360. // group at the position where other one should be, fix index arrays and continue
  12361. else {
  12362. var slippedPosition = newOrder.indexOf(origOrder[curPos + orgOffset]);
  12363. var switchGroup = groupsData.get(newOrder[curPos + newOffset]);
  12364. var shouldBeGroup = groupsData.get(origOrder[curPos + orgOffset]);
  12365. this.options.groupOrderSwap(switchGroup, shouldBeGroup, groupsData);
  12366. groupsData.update(switchGroup);
  12367. groupsData.update(shouldBeGroup);
  12368. var switchGroupId = newOrder[curPos + newOffset];
  12369. newOrder[curPos + newOffset] = origOrder[curPos + orgOffset];
  12370. newOrder[slippedPosition] = switchGroupId;
  12371. curPos++;
  12372. }
  12373. }
  12374. }
  12375. }
  12376. }
  12377. };
  12378. ItemSet.prototype._onGroupDragEnd = function (event) {
  12379. if (this.options.groupEditable.order && this.groupTouchParams.group) {
  12380. event.stopPropagation();
  12381. // update existing group
  12382. var me = this;
  12383. var id = me.groupTouchParams.group.groupId;
  12384. var dataset = me.groupsData.getDataSet();
  12385. var groupData = util.extend({}, dataset.get(id)); // clone the data
  12386. me.options.onMoveGroup(groupData, function (groupData) {
  12387. if (groupData) {
  12388. // apply changes
  12389. groupData[dataset._fieldId] = id; // ensure the group contains its id (can be undefined)
  12390. dataset.update(groupData);
  12391. } else {
  12392. // fetch current order of groups
  12393. var newOrder = dataset.getIds({
  12394. order: me.options.groupOrder
  12395. });
  12396. // restore original order
  12397. if (!util.equalArray(newOrder, me.groupTouchParams.originalOrder)) {
  12398. var origOrder = me.groupTouchParams.originalOrder;
  12399. var numGroups = Math.min(origOrder.length, newOrder.length);
  12400. var curPos = 0;
  12401. while (curPos < numGroups) {
  12402. // as long as the groups are where they should be step down along the groups order
  12403. while (curPos < numGroups && newOrder[curPos] == origOrder[curPos]) {
  12404. curPos++;
  12405. }
  12406. // all ok
  12407. if (curPos >= numGroups) {
  12408. break;
  12409. }
  12410. // found a group that has the wrong position -> switch with the
  12411. // group at the position where other one should be, fix index arrays and continue
  12412. var slippedPosition = newOrder.indexOf(origOrder[curPos]);
  12413. var switchGroup = dataset.get(newOrder[curPos]);
  12414. var shouldBeGroup = dataset.get(origOrder[curPos]);
  12415. me.options.groupOrderSwap(switchGroup, shouldBeGroup, dataset);
  12416. dataset.update(switchGroup);
  12417. dataset.update(shouldBeGroup);
  12418. var switchGroupId = newOrder[curPos];
  12419. newOrder[curPos] = origOrder[curPos];
  12420. newOrder[slippedPosition] = switchGroupId;
  12421. curPos++;
  12422. }
  12423. }
  12424. }
  12425. });
  12426. me.body.emitter.emit('groupDragged', { groupId: id });
  12427. }
  12428. };
  12429. /**
  12430. * Handle selecting/deselecting an item when tapping it
  12431. * @param {Event} event
  12432. * @private
  12433. */
  12434. ItemSet.prototype._onSelectItem = function (event) {
  12435. if (!this.options.selectable) return;
  12436. var ctrlKey = event.gesture.srcEvent && event.gesture.srcEvent.ctrlKey;
  12437. var shiftKey = event.gesture.srcEvent && event.gesture.srcEvent.shiftKey;
  12438. if (ctrlKey || shiftKey) {
  12439. this._onMultiSelectItem(event);
  12440. return;
  12441. }
  12442. var oldSelection = this.getSelection();
  12443. var item = this.itemFromTarget(event);
  12444. var selection = item ? [item.id] : [];
  12445. this.setSelection(selection);
  12446. var newSelection = this.getSelection();
  12447. // emit a select event,
  12448. // except when old selection is empty and new selection is still empty
  12449. if (newSelection.length > 0 || oldSelection.length > 0) {
  12450. this.body.emitter.emit('select', {
  12451. items: newSelection
  12452. });
  12453. }
  12454. };
  12455. /**
  12456. * Handle creation and updates of an item on double tap
  12457. * @param event
  12458. * @private
  12459. */
  12460. ItemSet.prototype._onAddItem = function (event) {
  12461. if (!this.options.selectable) return;
  12462. if (!this.options.editable.add) return;
  12463. var me = this,
  12464. snap = this.options.snap || null,
  12465. item = this.itemFromTarget(event);
  12466. if (item) {
  12467. // update item
  12468. // execute async handler to update the item (or cancel it)
  12469. var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset
  12470. this.options.onUpdate(itemData, function (itemData) {
  12471. if (itemData) {
  12472. me.itemsData.getDataSet().update(itemData);
  12473. }
  12474. });
  12475. }
  12476. else {
  12477. // add item
  12478. var xAbs = util.getAbsoluteLeft(this.dom.frame);
  12479. var x = event.gesture.center.pageX - xAbs;
  12480. var start = this.body.util.toTime(x);
  12481. var scale = this.body.util.getScale();
  12482. var step = this.body.util.getStep();
  12483. var newItem = {
  12484. start: snap ? snap(start, scale, step) : start,
  12485. content: 'new item'
  12486. };
  12487. // when default type is a range, add a default end date to the new item
  12488. if (this.options.type === 'range') {
  12489. var end = this.body.util.toTime(x + this.props.width / 5);
  12490. newItem.end = snap ? snap(end, scale, step) : end;
  12491. }
  12492. newItem[this.itemsData._fieldId] = util.randomUUID();
  12493. var group = this.groupFromTarget(event);
  12494. if (group) {
  12495. newItem.group = group.groupId;
  12496. }
  12497. // execute async handler to customize (or cancel) adding an item
  12498. this.options.onAdd(newItem, function (item) {
  12499. if (item) {
  12500. me.itemsData.getDataSet().add(item);
  12501. // TODO: need to trigger a redraw?
  12502. }
  12503. });
  12504. }
  12505. };
  12506. /**
  12507. * Handle selecting/deselecting multiple items when holding an item
  12508. * @param {Event} event
  12509. * @private
  12510. */
  12511. ItemSet.prototype._onMultiSelectItem = function (event) {
  12512. if (!this.options.selectable) return;
  12513. var selection,
  12514. item = this.itemFromTarget(event);
  12515. if (item) {
  12516. // multi select items
  12517. selection = this.getSelection(); // current selection
  12518. var shiftKey = event.gesture.touches[0] && event.gesture.touches[0].shiftKey || false;
  12519. if (shiftKey) {
  12520. // select all items between the old selection and the tapped item
  12521. // determine the selection range
  12522. selection.push(item.id);
  12523. var range = ItemSet._getItemRange(this.itemsData.get(selection, this.itemOptions));
  12524. // select all items within the selection range
  12525. selection = [];
  12526. for (var id in this.items) {
  12527. if (this.items.hasOwnProperty(id)) {
  12528. var _item = this.items[id];
  12529. var start = _item.data.start;
  12530. var end = (_item.data.end !== undefined) ? _item.data.end : start;
  12531. if (start >= range.min &&
  12532. end <= range.max &&
  12533. !(_item instanceof BackgroundItem)) {
  12534. selection.push(_item.id); // do not use id but item.id, id itself is stringified
  12535. }
  12536. }
  12537. }
  12538. }
  12539. else {
  12540. // add/remove this item from the current selection
  12541. var index = selection.indexOf(item.id);
  12542. if (index == -1) {
  12543. // item is not yet selected -> select it
  12544. selection.push(item.id);
  12545. }
  12546. else {
  12547. // item is already selected -> deselect it
  12548. selection.splice(index, 1);
  12549. }
  12550. }
  12551. this.setSelection(selection);
  12552. this.body.emitter.emit('select', {
  12553. items: this.getSelection()
  12554. });
  12555. }
  12556. };
  12557. /**
  12558. * Calculate the time range of a list of items
  12559. * @param {Array.<Object>} itemsData
  12560. * @return {{min: Date, max: Date}} Returns the range of the provided items
  12561. * @private
  12562. */
  12563. ItemSet._getItemRange = function(itemsData) {
  12564. var max = null;
  12565. var min = null;
  12566. itemsData.forEach(function (data) {
  12567. if (min == null || data.start < min) {
  12568. min = data.start;
  12569. }
  12570. if (data.end != undefined) {
  12571. if (max == null || data.end > max) {
  12572. max = data.end;
  12573. }
  12574. }
  12575. else {
  12576. if (max == null || data.start > max) {
  12577. max = data.start;
  12578. }
  12579. }
  12580. });
  12581. return {
  12582. min: min,
  12583. max: max
  12584. }
  12585. };
  12586. /**
  12587. * Find an item from an event target:
  12588. * searches for the attribute 'timeline-item' in the event target's element tree
  12589. * @param {Event} event
  12590. * @return {Item | null} item
  12591. */
  12592. ItemSet.prototype.itemFromTarget = function(event) {
  12593. var target = event.target;
  12594. while (target) {
  12595. if (target.hasOwnProperty('timeline-item')) {
  12596. return target['timeline-item'];
  12597. }
  12598. target = target.parentNode;
  12599. }
  12600. return null;
  12601. };
  12602. /**
  12603. * Find the Group from an event target:
  12604. * searches for the attribute 'timeline-group' in the event target's element tree
  12605. * @param {Event} event
  12606. * @return {Group | null} group
  12607. */
  12608. ItemSet.prototype.groupFromTarget = function(event) {
  12609. var pageY = event.gesture ? event.gesture.center.pageY : event.pageY;
  12610. for (var i = 0; i < this.groupIds.length; i++) {
  12611. var groupId = this.groupIds[i];
  12612. var group = this.groups[groupId];
  12613. var foreground = group.dom.foreground;
  12614. var top = util.getAbsoluteTop(foreground);
  12615. if (pageY > top && pageY < top + foreground.offsetHeight) {
  12616. return group;
  12617. }
  12618. if (this.options.orientation === 'top') {
  12619. if (i === this.groupIds.length - 1 && pageY > top) {
  12620. return group;
  12621. }
  12622. }
  12623. else {
  12624. if (i === 0 && pageY < top + foreground.offset) {
  12625. return group;
  12626. }
  12627. }
  12628. }
  12629. return null;
  12630. };
  12631. /**
  12632. * Find the ItemSet from an event target:
  12633. * searches for the attribute 'timeline-itemset' in the event target's element tree
  12634. * @param {Event} event
  12635. * @return {ItemSet | null} item
  12636. */
  12637. ItemSet.itemSetFromTarget = function(event) {
  12638. var target = event.target;
  12639. while (target) {
  12640. if (target.hasOwnProperty('timeline-itemset')) {
  12641. return target['timeline-itemset'];
  12642. }
  12643. target = target.parentNode;
  12644. }
  12645. return null;
  12646. };
  12647. module.exports = ItemSet;
  12648. /***/ },
  12649. /* 33 */
  12650. /***/ function(module, exports, __webpack_require__) {
  12651. var util = __webpack_require__(1);
  12652. var DOMutil = __webpack_require__(2);
  12653. var Component = __webpack_require__(25);
  12654. /**
  12655. * Legend for Graph2d
  12656. */
  12657. function Legend(body, options, side, linegraphOptions) {
  12658. this.body = body;
  12659. this.defaultOptions = {
  12660. enabled: true,
  12661. icons: true,
  12662. iconSize: 20,
  12663. iconSpacing: 6,
  12664. left: {
  12665. visible: true,
  12666. position: 'top-left' // top/bottom - left,center,right
  12667. },
  12668. right: {
  12669. visible: true,
  12670. position: 'top-left' // top/bottom - left,center,right
  12671. }
  12672. }
  12673. this.side = side;
  12674. this.options = util.extend({},this.defaultOptions);
  12675. this.linegraphOptions = linegraphOptions;
  12676. this.svgElements = {};
  12677. this.dom = {};
  12678. this.groups = {};
  12679. this.amountOfGroups = 0;
  12680. this._create();
  12681. this.setOptions(options);
  12682. }
  12683. Legend.prototype = new Component();
  12684. Legend.prototype.clear = function() {
  12685. this.groups = {};
  12686. this.amountOfGroups = 0;
  12687. }
  12688. Legend.prototype.addGroup = function(label, graphOptions) {
  12689. if (!this.groups.hasOwnProperty(label)) {
  12690. this.groups[label] = graphOptions;
  12691. }
  12692. this.amountOfGroups += 1;
  12693. };
  12694. Legend.prototype.updateGroup = function(label, graphOptions) {
  12695. this.groups[label] = graphOptions;
  12696. };
  12697. Legend.prototype.removeGroup = function(label) {
  12698. if (this.groups.hasOwnProperty(label)) {
  12699. delete this.groups[label];
  12700. this.amountOfGroups -= 1;
  12701. }
  12702. };
  12703. Legend.prototype._create = function() {
  12704. this.dom.frame = document.createElement('div');
  12705. this.dom.frame.className = 'legend';
  12706. this.dom.frame.style.position = "absolute";
  12707. this.dom.frame.style.top = "10px";
  12708. this.dom.frame.style.display = "block";
  12709. this.dom.textArea = document.createElement('div');
  12710. this.dom.textArea.className = 'legendText';
  12711. this.dom.textArea.style.position = "relative";
  12712. this.dom.textArea.style.top = "0px";
  12713. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  12714. this.svg.style.position = 'absolute';
  12715. this.svg.style.top = 0 +'px';
  12716. this.svg.style.width = this.options.iconSize + 5 + 'px';
  12717. this.svg.style.height = '100%';
  12718. this.dom.frame.appendChild(this.svg);
  12719. this.dom.frame.appendChild(this.dom.textArea);
  12720. };
  12721. /**
  12722. * Hide the component from the DOM
  12723. */
  12724. Legend.prototype.hide = function() {
  12725. // remove the frame containing the items
  12726. if (this.dom.frame.parentNode) {
  12727. this.dom.frame.parentNode.removeChild(this.dom.frame);
  12728. }
  12729. };
  12730. /**
  12731. * Show the component in the DOM (when not already visible).
  12732. * @return {Boolean} changed
  12733. */
  12734. Legend.prototype.show = function() {
  12735. // show frame containing the items
  12736. if (!this.dom.frame.parentNode) {
  12737. this.body.dom.center.appendChild(this.dom.frame);
  12738. }
  12739. };
  12740. Legend.prototype.setOptions = function(options) {
  12741. var fields = ['enabled','orientation','icons','left','right'];
  12742. util.selectiveDeepExtend(fields, this.options, options);
  12743. };
  12744. Legend.prototype.redraw = function() {
  12745. var activeGroups = 0;
  12746. for (var groupId in this.groups) {
  12747. if (this.groups.hasOwnProperty(groupId)) {
  12748. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  12749. activeGroups++;
  12750. }
  12751. }
  12752. }
  12753. if (this.options[this.side].visible == false || this.amountOfGroups == 0 || this.options.enabled == false || activeGroups == 0) {
  12754. this.hide();
  12755. }
  12756. else {
  12757. this.show();
  12758. if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'bottom-left') {
  12759. this.dom.frame.style.left = '4px';
  12760. this.dom.frame.style.textAlign = "left";
  12761. this.dom.textArea.style.textAlign = "left";
  12762. this.dom.textArea.style.left = (this.options.iconSize + 15) + 'px';
  12763. this.dom.textArea.style.right = '';
  12764. this.svg.style.left = 0 +'px';
  12765. this.svg.style.right = '';
  12766. }
  12767. else {
  12768. this.dom.frame.style.right = '4px';
  12769. this.dom.frame.style.textAlign = "right";
  12770. this.dom.textArea.style.textAlign = "right";
  12771. this.dom.textArea.style.right = (this.options.iconSize + 15) + 'px';
  12772. this.dom.textArea.style.left = '';
  12773. this.svg.style.right = 0 +'px';
  12774. this.svg.style.left = '';
  12775. }
  12776. if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'top-right') {
  12777. this.dom.frame.style.top = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
  12778. this.dom.frame.style.bottom = '';
  12779. }
  12780. else {
  12781. var scrollableHeight = this.body.domProps.center.height - this.body.domProps.centerContainer.height;
  12782. this.dom.frame.style.bottom = 4 + scrollableHeight + Number(this.body.dom.center.style.top.replace("px","")) + 'px';
  12783. this.dom.frame.style.top = '';
  12784. }
  12785. if (this.options.icons == false) {
  12786. this.dom.frame.style.width = this.dom.textArea.offsetWidth + 10 + 'px';
  12787. this.dom.textArea.style.right = '';
  12788. this.dom.textArea.style.left = '';
  12789. this.svg.style.width = '0px';
  12790. }
  12791. else {
  12792. this.dom.frame.style.width = this.options.iconSize + 15 + this.dom.textArea.offsetWidth + 10 + 'px'
  12793. this.drawLegendIcons();
  12794. }
  12795. var content = '';
  12796. for (var groupId in this.groups) {
  12797. if (this.groups.hasOwnProperty(groupId)) {
  12798. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  12799. content += this.groups[groupId].content + '<br />';
  12800. }
  12801. }
  12802. }
  12803. this.dom.textArea.innerHTML = content;
  12804. this.dom.textArea.style.lineHeight = ((0.75 * this.options.iconSize) + this.options.iconSpacing) + 'px';
  12805. }
  12806. };
  12807. Legend.prototype.drawLegendIcons = function() {
  12808. if (this.dom.frame.parentNode) {
  12809. DOMutil.prepareElements(this.svgElements);
  12810. var padding = window.getComputedStyle(this.dom.frame).paddingTop;
  12811. var iconOffset = Number(padding.replace('px',''));
  12812. var x = iconOffset;
  12813. var iconWidth = this.options.iconSize;
  12814. var iconHeight = 0.75 * this.options.iconSize;
  12815. var y = iconOffset + 0.5 * iconHeight + 3;
  12816. this.svg.style.width = iconWidth + 5 + iconOffset + 'px';
  12817. for (var groupId in this.groups) {
  12818. if (this.groups.hasOwnProperty(groupId)) {
  12819. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  12820. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  12821. y += iconHeight + this.options.iconSpacing;
  12822. }
  12823. }
  12824. }
  12825. DOMutil.cleanupElements(this.svgElements);
  12826. }
  12827. };
  12828. module.exports = Legend;
  12829. /***/ },
  12830. /* 34 */
  12831. /***/ function(module, exports, __webpack_require__) {
  12832. var util = __webpack_require__(1);
  12833. var DOMutil = __webpack_require__(2);
  12834. var DataSet = __webpack_require__(3);
  12835. var DataView = __webpack_require__(4);
  12836. var Component = __webpack_require__(25);
  12837. var DataAxis = __webpack_require__(28);
  12838. var GraphGroup = __webpack_require__(29);
  12839. var Legend = __webpack_require__(33);
  12840. var BarGraphFunctions = __webpack_require__(50);
  12841. var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
  12842. /**
  12843. * This is the constructor of the LineGraph. It requires a Timeline body and options.
  12844. *
  12845. * @param body
  12846. * @param options
  12847. * @constructor
  12848. */
  12849. function LineGraph(body, options) {
  12850. this.id = util.randomUUID();
  12851. this.body = body;
  12852. this.defaultOptions = {
  12853. yAxisOrientation: 'left',
  12854. defaultGroup: 'default',
  12855. sort: true,
  12856. sampling: true,
  12857. graphHeight: '400px',
  12858. shaded: {
  12859. enabled: false,
  12860. orientation: 'bottom' // top, bottom
  12861. },
  12862. style: 'line', // line, bar
  12863. barChart: {
  12864. width: 50,
  12865. handleOverlap: 'overlap',
  12866. align: 'center' // left, center, right
  12867. },
  12868. catmullRom: {
  12869. enabled: true,
  12870. parametrization: 'centripetal', // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5)
  12871. alpha: 0.5
  12872. },
  12873. drawPoints: {
  12874. enabled: true,
  12875. size: 6,
  12876. style: 'square' // square, circle
  12877. },
  12878. dataAxis: {
  12879. showMinorLabels: true,
  12880. showMajorLabels: true,
  12881. icons: false,
  12882. width: '40px',
  12883. visible: true,
  12884. alignZeros: true,
  12885. customRange: {
  12886. left: {min:undefined, max:undefined},
  12887. right: {min:undefined, max:undefined}
  12888. }
  12889. //, these options are not set by default, but this shows the format they will be in
  12890. //format: {
  12891. // left: {decimals: 2},
  12892. // right: {decimals: 2}
  12893. //},
  12894. //title: {
  12895. // left: {
  12896. // text: 'left',
  12897. // style: 'color:black;'
  12898. // },
  12899. // right: {
  12900. // text: 'right',
  12901. // style: 'color:black;'
  12902. // }
  12903. //}
  12904. },
  12905. legend: {
  12906. enabled: false,
  12907. icons: true,
  12908. left: {
  12909. visible: true,
  12910. position: 'top-left' // top/bottom - left,right
  12911. },
  12912. right: {
  12913. visible: true,
  12914. position: 'top-right' // top/bottom - left,right
  12915. }
  12916. },
  12917. groups: {
  12918. visibility: {}
  12919. }
  12920. };
  12921. // options is shared by this ItemSet and all its items
  12922. this.options = util.extend({}, this.defaultOptions);
  12923. this.dom = {};
  12924. this.props = {};
  12925. this.hammer = null;
  12926. this.groups = {};
  12927. this.abortedGraphUpdate = false;
  12928. this.updateSVGheight = false;
  12929. this.updateSVGheightOnResize = false;
  12930. var me = this;
  12931. this.itemsData = null; // DataSet
  12932. this.groupsData = null; // DataSet
  12933. // listeners for the DataSet of the items
  12934. this.itemListeners = {
  12935. 'add': function (event, params, senderId) {
  12936. me._onAdd(params.items);
  12937. },
  12938. 'update': function (event, params, senderId) {
  12939. me._onUpdate(params.items);
  12940. },
  12941. 'remove': function (event, params, senderId) {
  12942. me._onRemove(params.items);
  12943. }
  12944. };
  12945. // listeners for the DataSet of the groups
  12946. this.groupListeners = {
  12947. 'add': function (event, params, senderId) {
  12948. me._onAddGroups(params.items);
  12949. },
  12950. 'update': function (event, params, senderId) {
  12951. me._onUpdateGroups(params.items);
  12952. },
  12953. 'remove': function (event, params, senderId) {
  12954. me._onRemoveGroups(params.items);
  12955. }
  12956. };
  12957. this.items = {}; // object with an Item for every data item
  12958. this.selection = []; // list with the ids of all selected nodes
  12959. this.lastStart = this.body.range.start;
  12960. this.touchParams = {}; // stores properties while dragging
  12961. this.svgElements = {};
  12962. this.setOptions(options);
  12963. this.groupsUsingDefaultStyles = [0];
  12964. this.COUNTER = 0;
  12965. this.body.emitter.on('rangechanged', function() {
  12966. me.lastStart = me.body.range.start;
  12967. me.svg.style.left = util.option.asSize(-me.props.width);
  12968. me.redraw.call(me,true);
  12969. });
  12970. // create the HTML DOM
  12971. this._create();
  12972. this.framework = {svg: this.svg, svgElements: this.svgElements, options: this.options, groups: this.groups};
  12973. this.body.emitter.emit('change');
  12974. }
  12975. LineGraph.prototype = new Component();
  12976. /**
  12977. * Create the HTML DOM for the ItemSet
  12978. */
  12979. LineGraph.prototype._create = function(){
  12980. var frame = document.createElement('div');
  12981. frame.className = 'LineGraph';
  12982. this.dom.frame = frame;
  12983. // create svg element for graph drawing.
  12984. this.svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
  12985. this.svg.style.position = 'relative';
  12986. this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px';
  12987. this.svg.style.display = 'block';
  12988. frame.appendChild(this.svg);
  12989. // data axis
  12990. this.options.dataAxis.orientation = 'left';
  12991. this.yAxisLeft = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
  12992. this.options.dataAxis.orientation = 'right';
  12993. this.yAxisRight = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
  12994. delete this.options.dataAxis.orientation;
  12995. // legends
  12996. this.legendLeft = new Legend(this.body, this.options.legend, 'left', this.options.groups);
  12997. this.legendRight = new Legend(this.body, this.options.legend, 'right', this.options.groups);
  12998. this.show();
  12999. };
  13000. /**
  13001. * set the options of the LineGraph. the mergeOptions is used for subObjects that have an enabled element.
  13002. * @param {object} options
  13003. */
  13004. LineGraph.prototype.setOptions = function(options) {
  13005. if (options) {
  13006. var fields = ['sampling','defaultGroup','height','graphHeight','yAxisOrientation','style','barChart','dataAxis','sort','groups'];
  13007. if (options.graphHeight === undefined && options.height !== undefined && this.body.domProps.centerContainer.height !== undefined) {
  13008. this.updateSVGheight = true;
  13009. this.updateSVGheightOnResize = true;
  13010. }
  13011. else if (this.body.domProps.centerContainer.height !== undefined && options.graphHeight !== undefined) {
  13012. if (parseInt((options.graphHeight + '').replace("px",'')) < this.body.domProps.centerContainer.height) {
  13013. this.updateSVGheight = true;
  13014. }
  13015. }
  13016. util.selectiveDeepExtend(fields, this.options, options);
  13017. util.mergeOptions(this.options, options,'catmullRom');
  13018. util.mergeOptions(this.options, options,'drawPoints');
  13019. util.mergeOptions(this.options, options,'shaded');
  13020. util.mergeOptions(this.options, options,'legend');
  13021. if (options.catmullRom) {
  13022. if (typeof options.catmullRom == 'object') {
  13023. if (options.catmullRom.parametrization) {
  13024. if (options.catmullRom.parametrization == 'uniform') {
  13025. this.options.catmullRom.alpha = 0;
  13026. }
  13027. else if (options.catmullRom.parametrization == 'chordal') {
  13028. this.options.catmullRom.alpha = 1.0;
  13029. }
  13030. else {
  13031. this.options.catmullRom.parametrization = 'centripetal';
  13032. this.options.catmullRom.alpha = 0.5;
  13033. }
  13034. }
  13035. }
  13036. }
  13037. if (this.yAxisLeft) {
  13038. if (options.dataAxis !== undefined) {
  13039. this.yAxisLeft.setOptions(this.options.dataAxis);
  13040. this.yAxisRight.setOptions(this.options.dataAxis);
  13041. }
  13042. }
  13043. if (this.legendLeft) {
  13044. if (options.legend !== undefined) {
  13045. this.legendLeft.setOptions(this.options.legend);
  13046. this.legendRight.setOptions(this.options.legend);
  13047. }
  13048. }
  13049. if (this.groups.hasOwnProperty(UNGROUPED)) {
  13050. this.groups[UNGROUPED].setOptions(options);
  13051. }
  13052. }
  13053. // this is used to redraw the graph if the visibility of the groups is changed.
  13054. if (this.dom.frame) {
  13055. this.redraw(true);
  13056. }
  13057. };
  13058. /**
  13059. * Hide the component from the DOM
  13060. */
  13061. LineGraph.prototype.hide = function() {
  13062. // remove the frame containing the items
  13063. if (this.dom.frame.parentNode) {
  13064. this.dom.frame.parentNode.removeChild(this.dom.frame);
  13065. }
  13066. };
  13067. /**
  13068. * Show the component in the DOM (when not already visible).
  13069. * @return {Boolean} changed
  13070. */
  13071. LineGraph.prototype.show = function() {
  13072. // show frame containing the items
  13073. if (!this.dom.frame.parentNode) {
  13074. this.body.dom.center.appendChild(this.dom.frame);
  13075. }
  13076. };
  13077. /**
  13078. * Set items
  13079. * @param {vis.DataSet | null} items
  13080. */
  13081. LineGraph.prototype.setItems = function(items) {
  13082. var me = this,
  13083. ids,
  13084. oldItemsData = this.itemsData;
  13085. // replace the dataset
  13086. if (!items) {
  13087. this.itemsData = null;
  13088. }
  13089. else if (items instanceof DataSet || items instanceof DataView) {
  13090. this.itemsData = items;
  13091. }
  13092. else {
  13093. throw new TypeError('Data must be an instance of DataSet or DataView');
  13094. }
  13095. if (oldItemsData) {
  13096. // unsubscribe from old dataset
  13097. util.forEach(this.itemListeners, function (callback, event) {
  13098. oldItemsData.off(event, callback);
  13099. });
  13100. // remove all drawn items
  13101. ids = oldItemsData.getIds();
  13102. this._onRemove(ids);
  13103. }
  13104. if (this.itemsData) {
  13105. // subscribe to new dataset
  13106. var id = this.id;
  13107. util.forEach(this.itemListeners, function (callback, event) {
  13108. me.itemsData.on(event, callback, id);
  13109. });
  13110. // add all new items
  13111. ids = this.itemsData.getIds();
  13112. this._onAdd(ids);
  13113. }
  13114. this._updateUngrouped();
  13115. //this._updateGraph();
  13116. this.redraw(true);
  13117. };
  13118. /**
  13119. * Set groups
  13120. * @param {vis.DataSet} groups
  13121. */
  13122. LineGraph.prototype.setGroups = function(groups) {
  13123. var me = this;
  13124. var ids;
  13125. // unsubscribe from current dataset
  13126. if (this.groupsData) {
  13127. util.forEach(this.groupListeners, function (callback, event) {
  13128. me.groupsData.unsubscribe(event, callback);
  13129. });
  13130. // remove all drawn groups
  13131. ids = this.groupsData.getIds();
  13132. this.groupsData = null;
  13133. this._onRemoveGroups(ids); // note: this will cause a redraw
  13134. }
  13135. // replace the dataset
  13136. if (!groups) {
  13137. this.groupsData = null;
  13138. }
  13139. else if (groups instanceof DataSet || groups instanceof DataView) {
  13140. this.groupsData = groups;
  13141. }
  13142. else {
  13143. throw new TypeError('Data must be an instance of DataSet or DataView');
  13144. }
  13145. if (this.groupsData) {
  13146. // subscribe to new dataset
  13147. var id = this.id;
  13148. util.forEach(this.groupListeners, function (callback, event) {
  13149. me.groupsData.on(event, callback, id);
  13150. });
  13151. // draw all ms
  13152. ids = this.groupsData.getIds();
  13153. this._onAddGroups(ids);
  13154. }
  13155. this._onUpdate();
  13156. };
  13157. /**
  13158. * Update the data
  13159. * @param [ids]
  13160. * @private
  13161. */
  13162. LineGraph.prototype._onUpdate = function(ids) {
  13163. this._updateUngrouped();
  13164. this._updateAllGroupData();
  13165. //this._updateGraph();
  13166. this.redraw(true);
  13167. };
  13168. LineGraph.prototype._onAdd = function (ids) {this._onUpdate(ids);};
  13169. LineGraph.prototype._onRemove = function (ids) {this._onUpdate(ids);};
  13170. LineGraph.prototype._onUpdateGroups = function (groupIds) {
  13171. for (var i = 0; i < groupIds.length; i++) {
  13172. var group = this.groupsData.get(groupIds[i]);
  13173. this._updateGroup(group, groupIds[i]);
  13174. }
  13175. //this._updateGraph();
  13176. this.redraw(true);
  13177. };
  13178. LineGraph.prototype._onAddGroups = function (groupIds) {this._onUpdateGroups(groupIds);};
  13179. /**
  13180. * this cleans the group out off the legends and the dataaxis, updates the ungrouped and updates the graph
  13181. * @param {Array} groupIds
  13182. * @private
  13183. */
  13184. LineGraph.prototype._onRemoveGroups = function (groupIds) {
  13185. for (var i = 0; i < groupIds.length; i++) {
  13186. if (this.groups.hasOwnProperty(groupIds[i])) {
  13187. if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') {
  13188. this.yAxisRight.removeGroup(groupIds[i]);
  13189. this.legendRight.removeGroup(groupIds[i]);
  13190. this.legendRight.redraw();
  13191. }
  13192. else {
  13193. this.yAxisLeft.removeGroup(groupIds[i]);
  13194. this.legendLeft.removeGroup(groupIds[i]);
  13195. this.legendLeft.redraw();
  13196. }
  13197. delete this.groups[groupIds[i]];
  13198. }
  13199. }
  13200. this._updateUngrouped();
  13201. //this._updateGraph();
  13202. this.redraw(true);
  13203. };
  13204. /**
  13205. * update a group object with the group dataset entree
  13206. *
  13207. * @param group
  13208. * @param groupId
  13209. * @private
  13210. */
  13211. LineGraph.prototype._updateGroup = function (group, groupId) {
  13212. if (!this.groups.hasOwnProperty(groupId)) {
  13213. this.groups[groupId] = new GraphGroup(group, groupId, this.options, this.groupsUsingDefaultStyles);
  13214. if (this.groups[groupId].options.yAxisOrientation == 'right') {
  13215. this.yAxisRight.addGroup(groupId, this.groups[groupId]);
  13216. this.legendRight.addGroup(groupId, this.groups[groupId]);
  13217. }
  13218. else {
  13219. this.yAxisLeft.addGroup(groupId, this.groups[groupId]);
  13220. this.legendLeft.addGroup(groupId, this.groups[groupId]);
  13221. }
  13222. }
  13223. else {
  13224. this.groups[groupId].update(group);
  13225. if (this.groups[groupId].options.yAxisOrientation == 'right') {
  13226. this.yAxisRight.updateGroup(groupId, this.groups[groupId]);
  13227. this.legendRight.updateGroup(groupId, this.groups[groupId]);
  13228. }
  13229. else {
  13230. this.yAxisLeft.updateGroup(groupId, this.groups[groupId]);
  13231. this.legendLeft.updateGroup(groupId, this.groups[groupId]);
  13232. }
  13233. }
  13234. this.legendLeft.redraw();
  13235. this.legendRight.redraw();
  13236. };
  13237. /**
  13238. * this updates all groups, it is used when there is an update the the itemset.
  13239. *
  13240. * @private
  13241. */
  13242. LineGraph.prototype._updateAllGroupData = function () {
  13243. if (this.itemsData != null) {
  13244. var groupsContent = {};
  13245. var groupId;
  13246. for (groupId in this.groups) {
  13247. if (this.groups.hasOwnProperty(groupId)) {
  13248. groupsContent[groupId] = [];
  13249. }
  13250. }
  13251. for (var itemId in this.itemsData._data) {
  13252. if (this.itemsData._data.hasOwnProperty(itemId)) {
  13253. var item = this.itemsData._data[itemId];
  13254. if (groupsContent[item.group] === undefined) {
  13255. throw new Error('Cannot find referenced group. Possible reason: items added before groups? Groups need to be added before items, as items refer to groups.')
  13256. }
  13257. item.x = util.convert(item.x,'Date');
  13258. groupsContent[item.group].push(item);
  13259. }
  13260. }
  13261. for (groupId in this.groups) {
  13262. if (this.groups.hasOwnProperty(groupId)) {
  13263. this.groups[groupId].setItems(groupsContent[groupId]);
  13264. }
  13265. }
  13266. }
  13267. };
  13268. /**
  13269. * Create or delete the group holding all ungrouped items. This group is used when
  13270. * there are no groups specified. This anonymous group is called 'graph'.
  13271. * @protected
  13272. */
  13273. LineGraph.prototype._updateUngrouped = function() {
  13274. if (this.itemsData && this.itemsData != null) {
  13275. var ungroupedCounter = 0;
  13276. for (var itemId in this.itemsData._data) {
  13277. if (this.itemsData._data.hasOwnProperty(itemId)) {
  13278. var item = this.itemsData._data[itemId];
  13279. if (item != undefined) {
  13280. if (item.hasOwnProperty('group')) {
  13281. if (item.group === undefined) {
  13282. item.group = UNGROUPED;
  13283. }
  13284. }
  13285. else {
  13286. item.group = UNGROUPED;
  13287. }
  13288. ungroupedCounter = item.group == UNGROUPED ? ungroupedCounter + 1 : ungroupedCounter;
  13289. }
  13290. }
  13291. }
  13292. if (ungroupedCounter == 0) {
  13293. delete this.groups[UNGROUPED];
  13294. this.legendLeft.removeGroup(UNGROUPED);
  13295. this.legendRight.removeGroup(UNGROUPED);
  13296. this.yAxisLeft.removeGroup(UNGROUPED);
  13297. this.yAxisRight.removeGroup(UNGROUPED);
  13298. }
  13299. else {
  13300. var group = {id: UNGROUPED, content: this.options.defaultGroup};
  13301. this._updateGroup(group, UNGROUPED);
  13302. }
  13303. }
  13304. else {
  13305. delete this.groups[UNGROUPED];
  13306. this.legendLeft.removeGroup(UNGROUPED);
  13307. this.legendRight.removeGroup(UNGROUPED);
  13308. this.yAxisLeft.removeGroup(UNGROUPED);
  13309. this.yAxisRight.removeGroup(UNGROUPED);
  13310. }
  13311. this.legendLeft.redraw();
  13312. this.legendRight.redraw();
  13313. };
  13314. /**
  13315. * Redraw the component, mandatory function
  13316. * @return {boolean} Returns true if the component is resized
  13317. */
  13318. LineGraph.prototype.redraw = function(forceGraphUpdate) {
  13319. var resized = false;
  13320. // calculate actual size and position
  13321. this.props.width = this.dom.frame.offsetWidth;
  13322. this.props.height = this.body.domProps.centerContainer.height;
  13323. // update the graph if there is no lastWidth or with, used for the initial draw
  13324. if (this.lastWidth === undefined && this.props.width) {
  13325. forceGraphUpdate = true;
  13326. }
  13327. // check if this component is resized
  13328. resized = this._isResized() || resized;
  13329. // check whether zoomed (in that case we need to re-stack everything)
  13330. var visibleInterval = this.body.range.end - this.body.range.start;
  13331. var zoomed = (visibleInterval != this.lastVisibleInterval);
  13332. this.lastVisibleInterval = visibleInterval;
  13333. // the svg element is three times as big as the width, this allows for fully dragging left and right
  13334. // without reloading the graph. the controls for this are bound to events in the constructor
  13335. if (resized == true) {
  13336. this.svg.style.width = util.option.asSize(3*this.props.width);
  13337. this.svg.style.left = util.option.asSize(-this.props.width);
  13338. // if the height of the graph is set as proportional, change the height of the svg
  13339. if ((this.options.height + '').indexOf("%") != -1 || this.updateSVGheightOnResize == true) {
  13340. this.updateSVGheight = true;
  13341. }
  13342. }
  13343. // update the height of the graph on each redraw of the graph.
  13344. if (this.updateSVGheight == true) {
  13345. if (this.options.graphHeight != this.body.domProps.centerContainer.height + 'px') {
  13346. this.options.graphHeight = this.body.domProps.centerContainer.height + 'px';
  13347. this.svg.style.height = this.body.domProps.centerContainer.height + 'px';
  13348. }
  13349. this.updateSVGheight = false;
  13350. }
  13351. else {
  13352. this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px';
  13353. }
  13354. // zoomed is here to ensure that animations are shown correctly.
  13355. if (resized == true || zoomed == true || this.abortedGraphUpdate == true || forceGraphUpdate == true) {
  13356. resized = this._updateGraph() || resized;
  13357. }
  13358. else {
  13359. // move the whole svg while dragging
  13360. if (this.lastStart != 0) {
  13361. var offset = this.body.range.start - this.lastStart;
  13362. var range = this.body.range.end - this.body.range.start;
  13363. if (this.props.width != 0) {
  13364. var rangePerPixelInv = this.props.width/range;
  13365. var xOffset = offset * rangePerPixelInv;
  13366. this.svg.style.left = (-this.props.width - xOffset) + 'px';
  13367. }
  13368. }
  13369. }
  13370. this.legendLeft.redraw();
  13371. this.legendRight.redraw();
  13372. return resized;
  13373. };
  13374. /**
  13375. * Update and redraw the graph.
  13376. *
  13377. */
  13378. LineGraph.prototype._updateGraph = function () {
  13379. // reset the svg elements
  13380. DOMutil.prepareElements(this.svgElements);
  13381. if (this.props.width != 0 && this.itemsData != null) {
  13382. var group, i;
  13383. var preprocessedGroupData = {};
  13384. var processedGroupData = {};
  13385. var groupRanges = {};
  13386. var changeCalled = false;
  13387. // getting group Ids
  13388. var groupIds = [];
  13389. for (var groupId in this.groups) {
  13390. if (this.groups.hasOwnProperty(groupId)) {
  13391. group = this.groups[groupId];
  13392. if (group.visible == true && (this.options.groups.visibility[groupId] === undefined || this.options.groups.visibility[groupId] == true)) {
  13393. groupIds.push(groupId);
  13394. }
  13395. }
  13396. }
  13397. if (groupIds.length > 0) {
  13398. // this is the range of the SVG canvas
  13399. var minDate = this.body.util.toGlobalTime(-this.body.domProps.root.width);
  13400. var maxDate = this.body.util.toGlobalTime(2 * this.body.domProps.root.width);
  13401. var groupsData = {};
  13402. // fill groups data, this only loads the data we require based on the timewindow
  13403. this._getRelevantData(groupIds, groupsData, minDate, maxDate);
  13404. // apply sampling, if disabled, it will pass through this function.
  13405. this._applySampling(groupIds, groupsData);
  13406. // we transform the X coordinates to detect collisions
  13407. for (i = 0; i < groupIds.length; i++) {
  13408. preprocessedGroupData[groupIds[i]] = this._convertXcoordinates(groupsData[groupIds[i]]);
  13409. }
  13410. // now all needed data has been collected we start the processing.
  13411. this._getYRanges(groupIds, preprocessedGroupData, groupRanges);
  13412. // update the Y axis first, we use this data to draw at the correct Y points
  13413. // changeCalled is required to clean the SVG on a change emit.
  13414. changeCalled = this._updateYAxis(groupIds, groupRanges);
  13415. var MAX_CYCLES = 5;
  13416. if (changeCalled == true && this.COUNTER < MAX_CYCLES) {
  13417. DOMutil.cleanupElements(this.svgElements);
  13418. this.abortedGraphUpdate = true;
  13419. this.COUNTER++;
  13420. this.body.emitter.emit('change');
  13421. return true;
  13422. }
  13423. else {
  13424. if (this.COUNTER > MAX_CYCLES) {
  13425. console.log("WARNING: there may be an infinite loop in the _updateGraph emitter cycle.")
  13426. }
  13427. this.COUNTER = 0;
  13428. this.abortedGraphUpdate = false;
  13429. // With the yAxis scaled correctly, use this to get the Y values of the points.
  13430. for (i = 0; i < groupIds.length; i++) {
  13431. group = this.groups[groupIds[i]];
  13432. processedGroupData[groupIds[i]] = this._convertYcoordinates(groupsData[groupIds[i]], group);
  13433. }
  13434. // draw the groups
  13435. for (i = 0; i < groupIds.length; i++) {
  13436. group = this.groups[groupIds[i]];
  13437. if (group.options.style != 'bar') { // bar needs to be drawn enmasse
  13438. group.draw(processedGroupData[groupIds[i]], group, this.framework);
  13439. }
  13440. }
  13441. BarGraphFunctions.draw(groupIds, processedGroupData, this.framework);
  13442. }
  13443. }
  13444. }
  13445. // cleanup unused svg elements
  13446. DOMutil.cleanupElements(this.svgElements);
  13447. return false;
  13448. };
  13449. /**
  13450. * first select and preprocess the data from the datasets.
  13451. * the groups have their preselection of data, we now loop over this data to see
  13452. * what data we need to draw. Sorted data is much faster.
  13453. * more optimization is possible by doing the sampling before and using the binary search
  13454. * to find the end date to determine the increment.
  13455. *
  13456. * @param {array} groupIds
  13457. * @param {object} groupsData
  13458. * @param {date} minDate
  13459. * @param {date} maxDate
  13460. * @private
  13461. */
  13462. LineGraph.prototype._getRelevantData = function (groupIds, groupsData, minDate, maxDate) {
  13463. var group, i, j, item;
  13464. if (groupIds.length > 0) {
  13465. for (i = 0; i < groupIds.length; i++) {
  13466. group = this.groups[groupIds[i]];
  13467. groupsData[groupIds[i]] = [];
  13468. var dataContainer = groupsData[groupIds[i]];
  13469. // optimization for sorted data
  13470. if (group.options.sort == true) {
  13471. var guess = Math.max(0, util.binarySearchValue(group.itemsData, minDate, 'x', 'before'));
  13472. for (j = guess; j < group.itemsData.length; j++) {
  13473. item = group.itemsData[j];
  13474. if (item !== undefined) {
  13475. if (item.x > maxDate) {
  13476. dataContainer.push(item);
  13477. break;
  13478. }
  13479. else {
  13480. dataContainer.push(item);
  13481. }
  13482. }
  13483. }
  13484. }
  13485. else {
  13486. for (j = 0; j < group.itemsData.length; j++) {
  13487. item = group.itemsData[j];
  13488. if (item !== undefined) {
  13489. if (item.x > minDate && item.x < maxDate) {
  13490. dataContainer.push(item);
  13491. }
  13492. }
  13493. }
  13494. }
  13495. }
  13496. }
  13497. };
  13498. /**
  13499. *
  13500. * @param groupIds
  13501. * @param groupsData
  13502. * @private
  13503. */
  13504. LineGraph.prototype._applySampling = function (groupIds, groupsData) {
  13505. var group;
  13506. if (groupIds.length > 0) {
  13507. for (var i = 0; i < groupIds.length; i++) {
  13508. group = this.groups[groupIds[i]];
  13509. if (group.options.sampling == true) {
  13510. var dataContainer = groupsData[groupIds[i]];
  13511. if (dataContainer.length > 0) {
  13512. var increment = 1;
  13513. var amountOfPoints = dataContainer.length;
  13514. // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop
  13515. // of width changing of the yAxis.
  13516. var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x);
  13517. var pointsPerPixel = amountOfPoints / xDistance;
  13518. increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel)));
  13519. var sampledData = [];
  13520. for (var j = 0; j < amountOfPoints; j += increment) {
  13521. sampledData.push(dataContainer[j]);
  13522. }
  13523. groupsData[groupIds[i]] = sampledData;
  13524. }
  13525. }
  13526. }
  13527. }
  13528. };
  13529. /**
  13530. *
  13531. *
  13532. * @param {array} groupIds
  13533. * @param {object} groupsData
  13534. * @param {object} groupRanges | this is being filled here
  13535. * @private
  13536. */
  13537. LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) {
  13538. var groupData, group, i;
  13539. var barCombinedDataLeft = [];
  13540. var barCombinedDataRight = [];
  13541. var options;
  13542. if (groupIds.length > 0) {
  13543. for (i = 0; i < groupIds.length; i++) {
  13544. groupData = groupsData[groupIds[i]];
  13545. options = this.groups[groupIds[i]].options;
  13546. if (groupData.length > 0) {
  13547. group = this.groups[groupIds[i]];
  13548. // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups.
  13549. if (options.barChart.handleOverlap == 'stack' && options.style == 'bar') {
  13550. if (options.yAxisOrientation == 'left') {barCombinedDataLeft = barCombinedDataLeft.concat(group.getYRange(groupData)) ;}
  13551. else {barCombinedDataRight = barCombinedDataRight.concat(group.getYRange(groupData));}
  13552. }
  13553. else {
  13554. groupRanges[groupIds[i]] = group.getYRange(groupData,groupIds[i]);
  13555. }
  13556. }
  13557. }
  13558. // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups.
  13559. BarGraphFunctions.getStackedBarYRange(barCombinedDataLeft , groupRanges, groupIds, '__barchartLeft' , 'left' );
  13560. BarGraphFunctions.getStackedBarYRange(barCombinedDataRight, groupRanges, groupIds, '__barchartRight', 'right');
  13561. }
  13562. };
  13563. /**
  13564. * this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden.
  13565. * @param {Array} groupIds
  13566. * @param {Object} groupRanges
  13567. * @private
  13568. */
  13569. LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
  13570. var resized = false;
  13571. var yAxisLeftUsed = false;
  13572. var yAxisRightUsed = false;
  13573. var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal;
  13574. // if groups are present
  13575. if (groupIds.length > 0) {
  13576. // this is here to make sure that if there are no items in the axis but there are groups, that there is no infinite draw/redraw loop.
  13577. for (var i = 0; i < groupIds.length; i++) {
  13578. var group = this.groups[groupIds[i]];
  13579. if (group && group.options.yAxisOrientation != 'right') {
  13580. yAxisLeftUsed = true;
  13581. minLeft = 0;
  13582. maxLeft = 0;
  13583. }
  13584. else if (group && group.options.yAxisOrientation) {
  13585. yAxisRightUsed = true;
  13586. minRight = 0;
  13587. maxRight = 0;
  13588. }
  13589. }
  13590. // if there are items:
  13591. for (var i = 0; i < groupIds.length; i++) {
  13592. if (groupRanges.hasOwnProperty(groupIds[i])) {
  13593. if (groupRanges[groupIds[i]].ignore !== true) {
  13594. minVal = groupRanges[groupIds[i]].min;
  13595. maxVal = groupRanges[groupIds[i]].max;
  13596. if (groupRanges[groupIds[i]].yAxisOrientation != 'right') {
  13597. yAxisLeftUsed = true;
  13598. minLeft = minLeft > minVal ? minVal : minLeft;
  13599. maxLeft = maxLeft < maxVal ? maxVal : maxLeft;
  13600. }
  13601. else {
  13602. yAxisRightUsed = true;
  13603. minRight = minRight > minVal ? minVal : minRight;
  13604. maxRight = maxRight < maxVal ? maxVal : maxRight;
  13605. }
  13606. }
  13607. }
  13608. }
  13609. if (yAxisLeftUsed == true) {
  13610. this.yAxisLeft.setRange(minLeft, maxLeft);
  13611. }
  13612. if (yAxisRightUsed == true) {
  13613. this.yAxisRight.setRange(minRight, maxRight);
  13614. }
  13615. }
  13616. resized = this._toggleAxisVisiblity(yAxisLeftUsed , this.yAxisLeft) || resized;
  13617. resized = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || resized;
  13618. if (yAxisRightUsed == true && yAxisLeftUsed == true) {
  13619. this.yAxisLeft.drawIcons = true;
  13620. this.yAxisRight.drawIcons = true;
  13621. }
  13622. else {
  13623. this.yAxisLeft.drawIcons = false;
  13624. this.yAxisRight.drawIcons = false;
  13625. }
  13626. this.yAxisRight.master = !yAxisLeftUsed;
  13627. if (this.yAxisRight.master == false) {
  13628. if (yAxisRightUsed == true) {this.yAxisLeft.lineOffset = this.yAxisRight.width;}
  13629. else {this.yAxisLeft.lineOffset = 0;}
  13630. resized = this.yAxisLeft.redraw() || resized;
  13631. this.yAxisRight.stepPixelsForced = this.yAxisLeft.stepPixels;
  13632. this.yAxisRight.zeroCrossing = this.yAxisLeft.zeroCrossing;
  13633. resized = this.yAxisRight.redraw() || resized;
  13634. }
  13635. else {
  13636. resized = this.yAxisRight.redraw() || resized;
  13637. }
  13638. // clean the accumulated lists
  13639. if (groupIds.indexOf('__barchartLeft') != -1) {
  13640. groupIds.splice(groupIds.indexOf('__barchartLeft'),1);
  13641. }
  13642. if (groupIds.indexOf('__barchartRight') != -1) {
  13643. groupIds.splice(groupIds.indexOf('__barchartRight'),1);
  13644. }
  13645. return resized;
  13646. };
  13647. /**
  13648. * This shows or hides the Y axis if needed. If there is a change, the changed event is emitted by the updateYAxis function
  13649. *
  13650. * @param {boolean} axisUsed
  13651. * @returns {boolean}
  13652. * @private
  13653. * @param axis
  13654. */
  13655. LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) {
  13656. var changed = false;
  13657. if (axisUsed == false) {
  13658. if (axis.dom.frame.parentNode && axis.hidden == false) {
  13659. axis.hide()
  13660. changed = true;
  13661. }
  13662. }
  13663. else {
  13664. if (!axis.dom.frame.parentNode && axis.hidden == true) {
  13665. axis.show();
  13666. changed = true;
  13667. }
  13668. }
  13669. return changed;
  13670. };
  13671. /**
  13672. * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
  13673. * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
  13674. * the yAxis.
  13675. *
  13676. * @param datapoints
  13677. * @returns {Array}
  13678. * @private
  13679. */
  13680. LineGraph.prototype._convertXcoordinates = function (datapoints) {
  13681. var extractedData = [];
  13682. var xValue, yValue;
  13683. var toScreen = this.body.util.toScreen;
  13684. for (var i = 0; i < datapoints.length; i++) {
  13685. xValue = toScreen(datapoints[i].x) + this.props.width;
  13686. yValue = datapoints[i].y;
  13687. extractedData.push({x: xValue, y: yValue});
  13688. }
  13689. return extractedData;
  13690. };
  13691. /**
  13692. * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
  13693. * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
  13694. * the yAxis.
  13695. *
  13696. * @param datapoints
  13697. * @param group
  13698. * @returns {Array}
  13699. * @private
  13700. */
  13701. LineGraph.prototype._convertYcoordinates = function (datapoints, group) {
  13702. var extractedData = [];
  13703. var xValue, yValue;
  13704. var toScreen = this.body.util.toScreen;
  13705. var axis = this.yAxisLeft;
  13706. var svgHeight = Number(this.svg.style.height.replace('px',''));
  13707. if (group.options.yAxisOrientation == 'right') {
  13708. axis = this.yAxisRight;
  13709. }
  13710. for (var i = 0; i < datapoints.length; i++) {
  13711. var labelValue;
  13712. //if (datapoints[i].label) {
  13713. // labelValue = datapoints[i].label;
  13714. //}
  13715. //else {
  13716. // labelValue = null;
  13717. //}
  13718. labelValue = datapoints[i].label ? datapoints[i].label : null;
  13719. xValue = toScreen(datapoints[i].x) + this.props.width;
  13720. yValue = Math.round(axis.convertValue(datapoints[i].y));
  13721. extractedData.push({x: xValue, y: yValue, label:labelValue});
  13722. }
  13723. group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0)));
  13724. return extractedData;
  13725. };
  13726. module.exports = LineGraph;
  13727. /***/ },
  13728. /* 35 */
  13729. /***/ function(module, exports, __webpack_require__) {
  13730. var util = __webpack_require__(1);
  13731. var Component = __webpack_require__(25);
  13732. var TimeStep = __webpack_require__(19);
  13733. var DateUtil = __webpack_require__(15);
  13734. var moment = __webpack_require__(44);
  13735. /**
  13736. * A horizontal time axis
  13737. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
  13738. * @param {Object} [options] See TimeAxis.setOptions for the available
  13739. * options.
  13740. * @constructor TimeAxis
  13741. * @extends Component
  13742. */
  13743. function TimeAxis (body, options) {
  13744. this.dom = {
  13745. foreground: null,
  13746. lines: [],
  13747. majorTexts: [],
  13748. minorTexts: [],
  13749. redundant: {
  13750. lines: [],
  13751. majorTexts: [],
  13752. minorTexts: []
  13753. }
  13754. };
  13755. this.props = {
  13756. range: {
  13757. start: 0,
  13758. end: 0,
  13759. minimumStep: 0
  13760. },
  13761. lineTop: 0
  13762. };
  13763. this.defaultOptions = {
  13764. orientation: 'bottom', // axis orientation: 'top' or 'bottom'
  13765. showMinorLabels: true,
  13766. showMajorLabels: true,
  13767. format: null,
  13768. timeAxis: null
  13769. };
  13770. this.options = util.extend({}, this.defaultOptions);
  13771. this.body = body;
  13772. // create the HTML DOM
  13773. this._create();
  13774. this.setOptions(options);
  13775. }
  13776. TimeAxis.prototype = new Component();
  13777. /**
  13778. * Set options for the TimeAxis.
  13779. * Parameters will be merged in current options.
  13780. * @param {Object} options Available options:
  13781. * {string} [orientation]
  13782. * {boolean} [showMinorLabels]
  13783. * {boolean} [showMajorLabels]
  13784. */
  13785. TimeAxis.prototype.setOptions = function(options) {
  13786. if (options) {
  13787. // copy all options that we know
  13788. util.selectiveExtend([
  13789. 'showMinorLabels',
  13790. 'showMajorLabels',
  13791. 'hiddenDates',
  13792. 'format',
  13793. 'timeAxis'
  13794. ], this.options, options);
  13795. if ('orientation' in options) {
  13796. if (typeof options.orientation === 'string') {
  13797. this.options.orientation = options.orientation;
  13798. }
  13799. else if (typeof options.orientation === 'object' && 'axis' in options.orientation) {
  13800. this.options.orientation = options.orientation.axis;
  13801. }
  13802. }
  13803. // apply locale to moment.js
  13804. // TODO: not so nice, this is applied globally to moment.js
  13805. if ('locale' in options) {
  13806. if (typeof moment.locale === 'function') {
  13807. // moment.js 2.8.1+
  13808. moment.locale(options.locale);
  13809. }
  13810. else {
  13811. moment.lang(options.locale);
  13812. }
  13813. }
  13814. }
  13815. };
  13816. /**
  13817. * Create the HTML DOM for the TimeAxis
  13818. */
  13819. TimeAxis.prototype._create = function() {
  13820. this.dom.foreground = document.createElement('div');
  13821. this.dom.background = document.createElement('div');
  13822. this.dom.foreground.className = 'timeaxis foreground';
  13823. this.dom.background.className = 'timeaxis background';
  13824. };
  13825. /**
  13826. * Destroy the TimeAxis
  13827. */
  13828. TimeAxis.prototype.destroy = function() {
  13829. // remove from DOM
  13830. if (this.dom.foreground.parentNode) {
  13831. this.dom.foreground.parentNode.removeChild(this.dom.foreground);
  13832. }
  13833. if (this.dom.background.parentNode) {
  13834. this.dom.background.parentNode.removeChild(this.dom.background);
  13835. }
  13836. this.body = null;
  13837. };
  13838. /**
  13839. * Repaint the component
  13840. * @return {boolean} Returns true if the component is resized
  13841. */
  13842. TimeAxis.prototype.redraw = function () {
  13843. var options = this.options;
  13844. var props = this.props;
  13845. var foreground = this.dom.foreground;
  13846. var background = this.dom.background;
  13847. // determine the correct parent DOM element (depending on option orientation)
  13848. var parent = (options.orientation == 'top') ? this.body.dom.top : this.body.dom.bottom;
  13849. var parentChanged = (foreground.parentNode !== parent);
  13850. // calculate character width and height
  13851. this._calculateCharSize();
  13852. // TODO: recalculate sizes only needed when parent is resized or options is changed
  13853. var showMinorLabels = this.options.showMinorLabels;
  13854. var showMajorLabels = this.options.showMajorLabels;
  13855. // determine the width and height of the elemens for the axis
  13856. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  13857. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  13858. props.height = props.minorLabelHeight + props.majorLabelHeight;
  13859. props.width = foreground.offsetWidth;
  13860. props.minorLineHeight = this.body.domProps.root.height - props.majorLabelHeight -
  13861. (options.orientation == 'top' ? this.body.domProps.bottom.height : this.body.domProps.top.height);
  13862. props.minorLineWidth = 1; // TODO: really calculate width
  13863. props.majorLineHeight = props.minorLineHeight + props.majorLabelHeight;
  13864. props.majorLineWidth = 1; // TODO: really calculate width
  13865. // take foreground and background offline while updating (is almost twice as fast)
  13866. var foregroundNextSibling = foreground.nextSibling;
  13867. var backgroundNextSibling = background.nextSibling;
  13868. foreground.parentNode && foreground.parentNode.removeChild(foreground);
  13869. background.parentNode && background.parentNode.removeChild(background);
  13870. foreground.style.height = this.props.height + 'px';
  13871. this._repaintLabels();
  13872. // put DOM online again (at the same place)
  13873. if (foregroundNextSibling) {
  13874. parent.insertBefore(foreground, foregroundNextSibling);
  13875. }
  13876. else {
  13877. parent.appendChild(foreground)
  13878. }
  13879. if (backgroundNextSibling) {
  13880. this.body.dom.backgroundVertical.insertBefore(background, backgroundNextSibling);
  13881. }
  13882. else {
  13883. this.body.dom.backgroundVertical.appendChild(background)
  13884. }
  13885. return this._isResized() || parentChanged;
  13886. };
  13887. /**
  13888. * Repaint major and minor text labels and vertical grid lines
  13889. * @private
  13890. */
  13891. TimeAxis.prototype._repaintLabels = function () {
  13892. var orientation = this.options.orientation;
  13893. // calculate range and step (step such that we have space for 7 characters per label)
  13894. var start = util.convert(this.body.range.start, 'Number');
  13895. var end = util.convert(this.body.range.end, 'Number');
  13896. var timeLabelsize = this.body.util.toTime((this.props.minorCharWidth || 10) * 7).valueOf();
  13897. var minimumStep = timeLabelsize - DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this.body.range, timeLabelsize);
  13898. minimumStep -= this.body.util.toTime(0).valueOf();
  13899. var step = new TimeStep(new Date(start), new Date(end), minimumStep, this.body.hiddenDates);
  13900. if (this.options.format) {
  13901. step.setFormat(this.options.format);
  13902. }
  13903. if (this.options.timeAxis) {
  13904. step.setScale(this.options.timeAxis);
  13905. }
  13906. this.step = step;
  13907. // Move all DOM elements to a "redundant" list, where they
  13908. // can be picked for re-use, and clear the lists with lines and texts.
  13909. // At the end of the function _repaintLabels, left over elements will be cleaned up
  13910. var dom = this.dom;
  13911. dom.redundant.lines = dom.lines;
  13912. dom.redundant.majorTexts = dom.majorTexts;
  13913. dom.redundant.minorTexts = dom.minorTexts;
  13914. dom.lines = [];
  13915. dom.majorTexts = [];
  13916. dom.minorTexts = [];
  13917. var cur;
  13918. var x = 0;
  13919. var isMajor;
  13920. var xPrev = 0;
  13921. var width = 0;
  13922. var prevLine;
  13923. var xFirstMajorLabel = undefined;
  13924. var max = 0;
  13925. var className;
  13926. step.first();
  13927. while (step.hasNext() && max < 1000) {
  13928. max++;
  13929. cur = step.getCurrent();
  13930. isMajor = step.isMajor();
  13931. className = step.getClassName();
  13932. xPrev = x;
  13933. x = this.body.util.toScreen(cur);
  13934. width = x - xPrev;
  13935. if (prevLine) {
  13936. prevLine.style.width = width + 'px';
  13937. }
  13938. if (this.options.showMinorLabels) {
  13939. this._repaintMinorText(x, step.getLabelMinor(), orientation, className);
  13940. }
  13941. if (isMajor && this.options.showMajorLabels) {
  13942. if (x > 0) {
  13943. if (xFirstMajorLabel == undefined) {
  13944. xFirstMajorLabel = x;
  13945. }
  13946. this._repaintMajorText(x, step.getLabelMajor(), orientation, className);
  13947. }
  13948. prevLine = this._repaintMajorLine(x, orientation, className);
  13949. }
  13950. else {
  13951. prevLine = this._repaintMinorLine(x, orientation, className);
  13952. }
  13953. step.next();
  13954. }
  13955. // create a major label on the left when needed
  13956. if (this.options.showMajorLabels) {
  13957. var leftTime = this.body.util.toTime(0),
  13958. leftText = step.getLabelMajor(leftTime),
  13959. widthText = leftText.length * (this.props.majorCharWidth || 10) + 10; // upper bound estimation
  13960. if (xFirstMajorLabel == undefined || widthText < xFirstMajorLabel) {
  13961. this._repaintMajorText(0, leftText, orientation, className);
  13962. }
  13963. }
  13964. // Cleanup leftover DOM elements from the redundant list
  13965. util.forEach(this.dom.redundant, function (arr) {
  13966. while (arr.length) {
  13967. var elem = arr.pop();
  13968. if (elem && elem.parentNode) {
  13969. elem.parentNode.removeChild(elem);
  13970. }
  13971. }
  13972. });
  13973. };
  13974. /**
  13975. * Create a minor label for the axis at position x
  13976. * @param {Number} x
  13977. * @param {String} text
  13978. * @param {String} orientation "top" or "bottom" (default)
  13979. * @param {String} className
  13980. * @private
  13981. */
  13982. TimeAxis.prototype._repaintMinorText = function (x, text, orientation, className) {
  13983. // reuse redundant label
  13984. var label = this.dom.redundant.minorTexts.shift();
  13985. if (!label) {
  13986. // create new label
  13987. var content = document.createTextNode('');
  13988. label = document.createElement('div');
  13989. label.appendChild(content);
  13990. this.dom.foreground.appendChild(label);
  13991. }
  13992. this.dom.minorTexts.push(label);
  13993. label.childNodes[0].nodeValue = text;
  13994. label.style.top = (orientation == 'top') ? (this.props.majorLabelHeight + 'px') : '0';
  13995. label.style.left = x + 'px';
  13996. label.className = 'text minor ' + className;
  13997. //label.title = title; // TODO: this is a heavy operation
  13998. };
  13999. /**
  14000. * Create a Major label for the axis at position x
  14001. * @param {Number} x
  14002. * @param {String} text
  14003. * @param {String} orientation "top" or "bottom" (default)
  14004. * @param {String} className
  14005. * @private
  14006. */
  14007. TimeAxis.prototype._repaintMajorText = function (x, text, orientation, className) {
  14008. // reuse redundant label
  14009. var label = this.dom.redundant.majorTexts.shift();
  14010. if (!label) {
  14011. // create label
  14012. var content = document.createTextNode(text);
  14013. label = document.createElement('div');
  14014. label.appendChild(content);
  14015. this.dom.foreground.appendChild(label);
  14016. }
  14017. this.dom.majorTexts.push(label);
  14018. label.childNodes[0].nodeValue = text;
  14019. label.className = 'text major ' + className;
  14020. //label.title = title; // TODO: this is a heavy operation
  14021. label.style.top = (orientation == 'top') ? '0' : (this.props.minorLabelHeight + 'px');
  14022. label.style.left = x + 'px';
  14023. };
  14024. /**
  14025. * Create a minor line for the axis at position x
  14026. * @param {Number} x
  14027. * @param {String} orientation "top" or "bottom" (default)
  14028. * @param {String} className
  14029. * @return {Element} Returns the created line
  14030. * @private
  14031. */
  14032. TimeAxis.prototype._repaintMinorLine = function (x, orientation, className) {
  14033. // reuse redundant line
  14034. var line = this.dom.redundant.lines.shift();
  14035. if (!line) {
  14036. // create vertical line
  14037. line = document.createElement('div');
  14038. this.dom.background.appendChild(line);
  14039. }
  14040. this.dom.lines.push(line);
  14041. var props = this.props;
  14042. if (orientation == 'top') {
  14043. line.style.top = props.majorLabelHeight + 'px';
  14044. }
  14045. else {
  14046. line.style.top = this.body.domProps.top.height + 'px';
  14047. }
  14048. line.style.height = props.minorLineHeight + 'px';
  14049. line.style.left = (x - props.minorLineWidth / 2) + 'px';
  14050. line.className = 'grid vertical minor ' + className;
  14051. return line;
  14052. };
  14053. /**
  14054. * Create a Major line for the axis at position x
  14055. * @param {Number} x
  14056. * @param {String} orientation "top" or "bottom" (default)
  14057. * @param {String} className
  14058. * @return {Element} Returns the created line
  14059. * @private
  14060. */
  14061. TimeAxis.prototype._repaintMajorLine = function (x, orientation, className) {
  14062. // reuse redundant line
  14063. var line = this.dom.redundant.lines.shift();
  14064. if (!line) {
  14065. // create vertical line
  14066. line = document.createElement('div');
  14067. this.dom.background.appendChild(line);
  14068. }
  14069. this.dom.lines.push(line);
  14070. var props = this.props;
  14071. if (orientation == 'top') {
  14072. line.style.top = '0';
  14073. }
  14074. else {
  14075. line.style.top = this.body.domProps.top.height + 'px';
  14076. }
  14077. line.style.left = (x - props.majorLineWidth / 2) + 'px';
  14078. line.style.height = props.majorLineHeight + 'px';
  14079. line.className = 'grid vertical major ' + className;
  14080. return line;
  14081. };
  14082. /**
  14083. * Determine the size of text on the axis (both major and minor axis).
  14084. * The size is calculated only once and then cached in this.props.
  14085. * @private
  14086. */
  14087. TimeAxis.prototype._calculateCharSize = function () {
  14088. // Note: We calculate char size with every redraw. Size may change, for
  14089. // example when any of the timelines parents had display:none for example.
  14090. // determine the char width and height on the minor axis
  14091. if (!this.dom.measureCharMinor) {
  14092. this.dom.measureCharMinor = document.createElement('DIV');
  14093. this.dom.measureCharMinor.className = 'text minor measure';
  14094. this.dom.measureCharMinor.style.position = 'absolute';
  14095. this.dom.measureCharMinor.appendChild(document.createTextNode('0'));
  14096. this.dom.foreground.appendChild(this.dom.measureCharMinor);
  14097. }
  14098. this.props.minorCharHeight = this.dom.measureCharMinor.clientHeight;
  14099. this.props.minorCharWidth = this.dom.measureCharMinor.clientWidth;
  14100. // determine the char width and height on the major axis
  14101. if (!this.dom.measureCharMajor) {
  14102. this.dom.measureCharMajor = document.createElement('DIV');
  14103. this.dom.measureCharMajor.className = 'text major measure';
  14104. this.dom.measureCharMajor.style.position = 'absolute';
  14105. this.dom.measureCharMajor.appendChild(document.createTextNode('0'));
  14106. this.dom.foreground.appendChild(this.dom.measureCharMajor);
  14107. }
  14108. this.props.majorCharHeight = this.dom.measureCharMajor.clientHeight;
  14109. this.props.majorCharWidth = this.dom.measureCharMajor.clientWidth;
  14110. };
  14111. module.exports = TimeAxis;
  14112. /***/ },
  14113. /* 36 */
  14114. /***/ function(module, exports, __webpack_require__) {
  14115. var Emitter = __webpack_require__(56);
  14116. var Hammer = __webpack_require__(45);
  14117. var keycharm = __webpack_require__(57);
  14118. var util = __webpack_require__(1);
  14119. var hammerUtil = __webpack_require__(47);
  14120. var DataSet = __webpack_require__(3);
  14121. var DataView = __webpack_require__(4);
  14122. var dotparser = __webpack_require__(42);
  14123. var gephiParser = __webpack_require__(43);
  14124. var Groups = __webpack_require__(38);
  14125. var Images = __webpack_require__(39);
  14126. var Node = __webpack_require__(40);
  14127. var Edge = __webpack_require__(37);
  14128. var Popup = __webpack_require__(41);
  14129. var MixinLoader = __webpack_require__(52);
  14130. var Activator = __webpack_require__(53);
  14131. var locales = __webpack_require__(54);
  14132. // Load custom shapes into CanvasRenderingContext2D
  14133. __webpack_require__(55);
  14134. /**
  14135. * @constructor Network
  14136. * Create a network visualization, displaying nodes and edges.
  14137. *
  14138. * @param {Element} container The DOM element in which the Network will
  14139. * be created. Normally a div element.
  14140. * @param {Object} data An object containing parameters
  14141. * {Array} nodes
  14142. * {Array} edges
  14143. * @param {Object} options Options
  14144. */
  14145. function Network (container, data, options) {
  14146. if (!(this instanceof Network)) {
  14147. throw new SyntaxError('Constructor must be called with the new operator');
  14148. }
  14149. this._determineBrowserMethod();
  14150. this._initializeMixinLoaders();
  14151. // create variables and set default values
  14152. this.containerElement = container;
  14153. // render and calculation settings
  14154. this.renderRefreshRate = 60; // hz (fps)
  14155. this.renderTimestep = 1000 / this.renderRefreshRate; // ms -- saves calculation later on
  14156. this.renderTime = 0; // measured time it takes to render a frame
  14157. this.physicsTime = 0; // measured time it takes to render a frame
  14158. this.runDoubleSpeed = false;
  14159. this.physicsDiscreteStepsize = 0.50; // discrete stepsize of the simulation
  14160. this.initializing = true;
  14161. this.triggerFunctions = {add:null,edit:null,editEdge:null,connect:null,del:null};
  14162. var customScalingFunction = function (min,max,total,value) {
  14163. if (max == min) {
  14164. return 0.5;
  14165. }
  14166. else {
  14167. var scale = 1 / (max - min);
  14168. return Math.max(0,(value - min)*scale);
  14169. }
  14170. };
  14171. // set constant values
  14172. this.defaultOptions = {
  14173. nodes: {
  14174. customScalingFunction: customScalingFunction,
  14175. mass: 1,
  14176. radiusMin: 10,
  14177. radiusMax: 30,
  14178. radius: 10,
  14179. shape: 'ellipse',
  14180. image: undefined,
  14181. widthMin: 16, // px
  14182. widthMax: 64, // px
  14183. fontColor: 'black',
  14184. fontSize: 14, // px
  14185. fontFace: 'verdana',
  14186. fontFill: undefined,
  14187. fontStrokeWidth: 0, // px
  14188. fontStrokeColor: '#ffffff',
  14189. fontDrawThreshold: 3,
  14190. scaleFontWithValue: false,
  14191. fontSizeMin: 14,
  14192. fontSizeMax: 30,
  14193. fontSizeMaxVisible: 30,
  14194. level: -1,
  14195. color: {
  14196. border: '#2B7CE9',
  14197. background: '#97C2FC',
  14198. highlight: {
  14199. border: '#2B7CE9',
  14200. background: '#D2E5FF'
  14201. },
  14202. hover: {
  14203. border: '#2B7CE9',
  14204. background: '#D2E5FF'
  14205. }
  14206. },
  14207. group: undefined,
  14208. borderWidth: 1,
  14209. borderWidthSelected: undefined
  14210. },
  14211. edges: {
  14212. customScalingFunction: customScalingFunction,
  14213. widthMin: 1, //
  14214. widthMax: 15,//
  14215. width: 1,
  14216. widthSelectionMultiplier: 2,
  14217. hoverWidth: 1.5,
  14218. style: 'line',
  14219. color: {
  14220. color:'#848484',
  14221. highlight:'#848484',
  14222. hover: '#848484'
  14223. },
  14224. opacity:1.0,
  14225. fontColor: '#343434',
  14226. fontSize: 14, // px
  14227. fontFace: 'arial',
  14228. fontFill: 'white',
  14229. fontStrokeWidth: 0, // px
  14230. fontStrokeColor: 'white',
  14231. labelAlignment:'horizontal',
  14232. arrowScaleFactor: 1,
  14233. dash: {
  14234. length: 10,
  14235. gap: 5,
  14236. altLength: undefined
  14237. },
  14238. inheritColor: "from", // to, from, false, true (== from)
  14239. useGradients: false // release in 4.0
  14240. },
  14241. configurePhysics:false,
  14242. physics: {
  14243. barnesHut: {
  14244. enabled: true,
  14245. thetaInverted: 1 / 0.5, // inverted to save time during calculation
  14246. gravitationalConstant: -2000,
  14247. centralGravity: 0.3,
  14248. springLength: 95,
  14249. springConstant: 0.04,
  14250. damping: 0.09
  14251. },
  14252. repulsion: {
  14253. centralGravity: 0.0,
  14254. springLength: 200,
  14255. springConstant: 0.05,
  14256. nodeDistance: 100,
  14257. damping: 0.09
  14258. },
  14259. hierarchicalRepulsion: {
  14260. enabled: false,
  14261. centralGravity: 0.0,
  14262. springLength: 100,
  14263. springConstant: 0.01,
  14264. nodeDistance: 150,
  14265. damping: 0.09
  14266. },
  14267. damping: null,
  14268. centralGravity: null,
  14269. springLength: null,
  14270. springConstant: null
  14271. },
  14272. clustering: { // Per Node in Cluster = PNiC
  14273. enabled: false, // (Boolean) | global on/off switch for clustering.
  14274. initialMaxNodes: 100, // (# nodes) | if the initial amount of nodes is larger than this, we cluster until the total number is less than this threshold.
  14275. 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
  14276. 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
  14277. chainThreshold: 0.4, // (% of all drawn nodes)| maximum percentage of allowed chainnodes (long strings of connected nodes) within all nodes. (lower means less chains).
  14278. clusterEdgeThreshold: 20, // (px) | edge length threshold. if smaller, this node is clustered.
  14279. sectorThreshold: 100, // (# nodes in cluster) | cluster size threshold. If larger, expanding in own sector.
  14280. 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.
  14281. fontSizeMultiplier: 4.0, // (px PNiC) | how much the cluster font size grows per node in cluster (in px).
  14282. maxFontSize: 1000,
  14283. forceAmplification: 0.1, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster).
  14284. distanceAmplification: 0.1, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster).
  14285. edgeGrowth: 20, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
  14286. nodeScaling: {width: 1, // (px PNiC) | growth of the width per node in cluster.
  14287. height: 1, // (px PNiC) | growth of the height per node in cluster.
  14288. radius: 1}, // (px PNiC) | growth of the radius per node in cluster.
  14289. maxNodeSizeIncrements: 600, // (# increments) | max growth of the width per node in cluster.
  14290. activeAreaBoxSize: 80, // (px) | box area around the curser where clusters are popped open.
  14291. clusterLevelDifference: 2, // used for normalization of the cluster levels
  14292. clusterByZoom: true // enable clustering through zooming in and out
  14293. },
  14294. navigation: {
  14295. enabled: false
  14296. },
  14297. keyboard: {
  14298. enabled: false,
  14299. speed: {x: 10, y: 10, zoom: 0.02},
  14300. bindToWindow: true
  14301. },
  14302. dataManipulation: {
  14303. enabled: false,
  14304. initiallyVisible: false
  14305. },
  14306. hierarchicalLayout: {
  14307. enabled:false,
  14308. levelSeparation: 150,
  14309. nodeSpacing: 100,
  14310. direction: "UD", // UD, DU, LR, RL
  14311. layout: "hubsize" // hubsize, directed
  14312. },
  14313. freezeForStabilization: false,
  14314. smoothCurves: {
  14315. enabled: true,
  14316. dynamic: true,
  14317. type: "continuous",
  14318. roundness: 0.5
  14319. },
  14320. maxVelocity: 50,
  14321. minVelocity: 0.1, // px/s
  14322. stabilize: true, // stabilize before displaying the network
  14323. stabilizationIterations: 1000, // maximum number of iteration to stabilize
  14324. zoomExtentOnStabilize: true,
  14325. locale: 'en',
  14326. locales: locales,
  14327. tooltip: {
  14328. delay: 300,
  14329. fontColor: 'black',
  14330. fontSize: 14, // px
  14331. fontFace: 'verdana',
  14332. color: {
  14333. border: '#666',
  14334. background: '#FFFFC6'
  14335. }
  14336. },
  14337. dragNetwork: true,
  14338. dragNodes: true,
  14339. zoomable: true,
  14340. hover: false,
  14341. hideEdgesOnDrag: false,
  14342. hideNodesOnDrag: false,
  14343. width : '100%',
  14344. height : '100%',
  14345. selectable: true,
  14346. useDefaultGroups: true
  14347. };
  14348. this.constants = util.extend({}, this.defaultOptions);
  14349. this.pixelRatio = 1;
  14350. this.hoverObj = {nodes:{},edges:{}};
  14351. this.controlNodesActive = false;
  14352. this.navigationHammers = [];
  14353. this.manipulationHammers = [];
  14354. // animation properties
  14355. this.animationSpeed = 1/this.renderRefreshRate;
  14356. this.animationEasingFunction = "easeInOutQuint";
  14357. this.animating = false;
  14358. this.easingTime = 0;
  14359. this.sourceScale = 0;
  14360. this.targetScale = 0;
  14361. this.sourceTranslation = 0;
  14362. this.targetTranslation = 0;
  14363. this.lockedOnNodeId = null;
  14364. this.lockedOnNodeOffset = null;
  14365. this.touchTime = 0;
  14366. this.redrawRequested = false;
  14367. // Node variables
  14368. var network = this;
  14369. this.groups = new Groups(); // object with groups
  14370. this.images = new Images(); // object with images
  14371. this.images.setOnloadCallback(function (status) {
  14372. network._requestRedraw();
  14373. });
  14374. // keyboard navigation variables
  14375. this.xIncrement = 0;
  14376. this.yIncrement = 0;
  14377. this.zoomIncrement = 0;
  14378. // loading all the mixins:
  14379. // load the force calculation functions, grouped under the physics system.
  14380. this._loadPhysicsSystem();
  14381. // create a frame and canvas
  14382. this._create();
  14383. // load the sector system. (mandatory, fully integrated with Network)
  14384. this._loadSectorSystem();
  14385. // load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it)
  14386. this._loadClusterSystem();
  14387. // load the selection system. (mandatory, required by Network)
  14388. this._loadSelectionSystem();
  14389. // load the selection system. (mandatory, required by Network)
  14390. this._loadHierarchySystem();
  14391. // apply options
  14392. this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
  14393. this._setScale(1);
  14394. this.setOptions(options);
  14395. // other vars
  14396. this.freezeSimulationEnabled = false;// freeze the simulation
  14397. this.cachedFunctions = {};
  14398. this.startedStabilization = false;
  14399. this.stabilized = false;
  14400. this.stabilizationIterations = null;
  14401. this.draggingNodes = false;
  14402. // containers for nodes and edges
  14403. this.calculationNodes = {};
  14404. this.calculationNodeIndices = [];
  14405. this.nodeIndices = []; // array with all the indices of the nodes. Used to speed up forces calculation
  14406. this.nodes = {}; // object with Node objects
  14407. this.edges = {}; // object with Edge objects
  14408. // position and scale variables and objects
  14409. this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw.
  14410. this.canvasBottomRight = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  14411. this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  14412. this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action
  14413. this.scale = 1; // defining the global scale variable in the constructor
  14414. this.previousScale = this.scale; // this is used to check if the zoom operation is zooming in or out
  14415. // datasets or dataviews
  14416. this.nodesData = null; // A DataSet or DataView
  14417. this.edgesData = null; // A DataSet or DataView
  14418. // create event listeners used to subscribe on the DataSets of the nodes and edges
  14419. this.nodesListeners = {
  14420. 'add': function (event, params) {
  14421. network._addNodes(params.items);
  14422. network.start();
  14423. },
  14424. 'update': function (event, params) {
  14425. network._updateNodes(params.items);
  14426. network.start();
  14427. },
  14428. 'remove': function (event, params) {
  14429. network._removeNodes(params.items);
  14430. network.start();
  14431. }
  14432. };
  14433. this.edgesListeners = {
  14434. 'add': function (event, params) {
  14435. network._addEdges(params.items);
  14436. network.start();
  14437. },
  14438. 'update': function (event, params) {
  14439. network._updateEdges(params.items);
  14440. network.start();
  14441. },
  14442. 'remove': function (event, params) {
  14443. network._removeEdges(params.items);
  14444. network.start();
  14445. }
  14446. };
  14447. // properties for the animation
  14448. this.moving = true;
  14449. this.timer = undefined; // Scheduling function. Is definded in this.start();
  14450. // load data (the disable start variable will be the same as the enabled clustering)
  14451. this.setData(data,this.constants.clustering.enabled || this.constants.hierarchicalLayout.enabled);
  14452. // hierarchical layout
  14453. this.initializing = false;
  14454. if (this.constants.hierarchicalLayout.enabled == true) {
  14455. this._setupHierarchicalLayout();
  14456. }
  14457. else {
  14458. // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
  14459. if (this.constants.stabilize == false) {
  14460. this.zoomExtent({duration:0}, true, this.constants.clustering.enabled);
  14461. }
  14462. }
  14463. // if clustering is disabled, the simulation will have started in the setData function
  14464. if (this.constants.clustering.enabled) {
  14465. this.startWithClustering();
  14466. }
  14467. }
  14468. // Extend Network with an Emitter mixin
  14469. Emitter(Network.prototype);
  14470. /**
  14471. * Determine if the browser requires a setTimeout or a requestAnimationFrame. This was required because
  14472. * some implementations (safari and IE9) did not support requestAnimationFrame
  14473. * @private
  14474. */
  14475. Network.prototype._determineBrowserMethod = function() {
  14476. var browserType = navigator.userAgent.toLowerCase();
  14477. this.requiresTimeout = false;
  14478. if (browserType.indexOf('msie 9.0') != -1) { // IE 9
  14479. this.requiresTimeout = true;
  14480. }
  14481. else if (browserType.indexOf('safari') != -1) { // safari
  14482. if (browserType.indexOf('chrome') <= -1) {
  14483. this.requiresTimeout = true;
  14484. }
  14485. }
  14486. }
  14487. /**
  14488. * Get the script path where the vis.js library is located
  14489. *
  14490. * @returns {string | null} path Path or null when not found. Path does not
  14491. * end with a slash.
  14492. * @private
  14493. */
  14494. Network.prototype._getScriptPath = function() {
  14495. var scripts = document.getElementsByTagName( 'script' );
  14496. // find script named vis.js or vis.min.js
  14497. for (var i = 0; i < scripts.length; i++) {
  14498. var src = scripts[i].src;
  14499. var match = src && /\/?vis(.min)?\.js$/.exec(src);
  14500. if (match) {
  14501. // return path without the script name
  14502. return src.substring(0, src.length - match[0].length);
  14503. }
  14504. }
  14505. return null;
  14506. };
  14507. /**
  14508. * Find the center position of the network
  14509. * @private
  14510. */
  14511. Network.prototype._getRange = function(specificNodes) {
  14512. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  14513. if (specificNodes.length > 0) {
  14514. for (var i = 0; i < specificNodes.length; i++) {
  14515. node = this.nodes[specificNodes[i]];
  14516. if (minX > (node.boundingBox.left)) {
  14517. minX = node.boundingBox.left;
  14518. }
  14519. if (maxX < (node.boundingBox.right)) {
  14520. maxX = node.boundingBox.right;
  14521. }
  14522. if (minY > (node.boundingBox.bottom)) {
  14523. minY = node.boundingBox.top;
  14524. } // top is negative, bottom is positive
  14525. if (maxY < (node.boundingBox.top)) {
  14526. maxY = node.boundingBox.bottom;
  14527. } // top is negative, bottom is positive
  14528. }
  14529. }
  14530. else {
  14531. for (var nodeId in this.nodes) {
  14532. if (this.nodes.hasOwnProperty(nodeId)) {
  14533. node = this.nodes[nodeId];
  14534. if (minX > (node.boundingBox.left)) {
  14535. minX = node.boundingBox.left;
  14536. }
  14537. if (maxX < (node.boundingBox.right)) {
  14538. maxX = node.boundingBox.right;
  14539. }
  14540. if (minY > (node.boundingBox.bottom)) {
  14541. minY = node.boundingBox.top;
  14542. } // top is negative, bottom is positive
  14543. if (maxY < (node.boundingBox.top)) {
  14544. maxY = node.boundingBox.bottom;
  14545. } // top is negative, bottom is positive
  14546. }
  14547. }
  14548. }
  14549. if (minX == 1e9 && maxX == -1e9 && minY == 1e9 && maxY == -1e9) {
  14550. minY = 0, maxY = 0, minX = 0, maxX = 0;
  14551. }
  14552. return {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  14553. };
  14554. /**
  14555. * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  14556. * @returns {{x: number, y: number}}
  14557. * @private
  14558. */
  14559. Network.prototype._findCenter = function(range) {
  14560. return {x: (0.5 * (range.maxX + range.minX)),
  14561. y: (0.5 * (range.maxY + range.minY))};
  14562. };
  14563. /**
  14564. * This function zooms out to fit all data on screen based on amount of nodes
  14565. *
  14566. * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
  14567. * @param {Boolean} [disableStart] | If true, start is not called.
  14568. */
  14569. Network.prototype.zoomExtent = function(options, initialZoom, disableStart) {
  14570. this._redraw(true);
  14571. if (initialZoom === undefined) {initialZoom = false;}
  14572. if (disableStart === undefined) {disableStart = false;}
  14573. if (options === undefined) {options = {nodes:[]};}
  14574. if (options.nodes === undefined) {
  14575. options.nodes = [];
  14576. }
  14577. var range;
  14578. var zoomLevel;
  14579. if (initialZoom == true) {
  14580. // check if more than half of the nodes have a predefined position. If so, we use the range, not the approximation.
  14581. var positionDefined = 0;
  14582. for (var nodeId in this.nodes) {
  14583. if (this.nodes.hasOwnProperty(nodeId)) {
  14584. var node = this.nodes[nodeId];
  14585. if (node.predefinedPosition == true) {
  14586. positionDefined += 1;
  14587. }
  14588. }
  14589. }
  14590. if (positionDefined > 0.5 * this.nodeIndices.length) {
  14591. this.zoomExtent(options,false,disableStart);
  14592. return;
  14593. }
  14594. range = this._getRange(options.nodes);
  14595. var numberOfNodes = this.nodeIndices.length;
  14596. if (this.constants.smoothCurves == true) {
  14597. if (this.constants.clustering.enabled == true &&
  14598. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  14599. 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.
  14600. }
  14601. else {
  14602. zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  14603. }
  14604. }
  14605. else {
  14606. if (this.constants.clustering.enabled == true &&
  14607. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  14608. 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.
  14609. }
  14610. else {
  14611. zoomLevel = 30.5062972 / (numberOfNodes + 19.93597763) + 0.08413486; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  14612. }
  14613. }
  14614. // correct for larger canvasses.
  14615. var factor = Math.min(this.frame.canvas.clientWidth / 600, this.frame.canvas.clientHeight / 600);
  14616. zoomLevel *= factor;
  14617. }
  14618. else {
  14619. range = this._getRange(options.nodes);
  14620. var xDistance = Math.abs(range.maxX - range.minX) * 1.1;
  14621. var yDistance = Math.abs(range.maxY - range.minY) * 1.1;
  14622. var xZoomLevel = this.frame.canvas.clientWidth / xDistance;
  14623. var yZoomLevel = this.frame.canvas.clientHeight / yDistance;
  14624. zoomLevel = (xZoomLevel <= yZoomLevel) ? xZoomLevel : yZoomLevel;
  14625. }
  14626. if (zoomLevel > 1.0) {
  14627. zoomLevel = 1.0;
  14628. }
  14629. var center = this._findCenter(range);
  14630. if (disableStart == false) {
  14631. var options = {position: center, scale: zoomLevel, animation: options};
  14632. this.moveTo(options);
  14633. this.moving = true;
  14634. this.start();
  14635. }
  14636. else {
  14637. center.x *= zoomLevel;
  14638. center.y *= zoomLevel;
  14639. center.x -= 0.5 * this.frame.canvas.clientWidth;
  14640. center.y -= 0.5 * this.frame.canvas.clientHeight;
  14641. this._setScale(zoomLevel);
  14642. this._setTranslation(-center.x,-center.y);
  14643. }
  14644. };
  14645. /**
  14646. * Update the this.nodeIndices with the most recent node index list
  14647. * @private
  14648. */
  14649. Network.prototype._updateNodeIndexList = function() {
  14650. this._clearNodeIndexList();
  14651. for (var idx in this.nodes) {
  14652. if (this.nodes.hasOwnProperty(idx)) {
  14653. this.nodeIndices.push(idx);
  14654. }
  14655. }
  14656. };
  14657. /**
  14658. * Set nodes and edges, and optionally options as well.
  14659. *
  14660. * @param {Object} data Object containing parameters:
  14661. * {Array | DataSet | DataView} [nodes] Array with nodes
  14662. * {Array | DataSet | DataView} [edges] Array with edges
  14663. * {String} [dot] String containing data in DOT format
  14664. * {String} [gephi] String containing data in gephi JSON format
  14665. * {Options} [options] Object with options
  14666. * @param {Boolean} [disableStart] | optional: disable the calling of the start function.
  14667. */
  14668. Network.prototype.setData = function(data, disableStart) {
  14669. if (disableStart === undefined) {
  14670. disableStart = false;
  14671. }
  14672. // unselect all to ensure no selections from old data are carried over.
  14673. this._unselectAll(true);
  14674. // we set initializing to true to ensure that the hierarchical layout is not performed until both nodes and edges are added.
  14675. this.initializing = true;
  14676. if (data && data.dot && (data.nodes || data.edges)) {
  14677. throw new SyntaxError('Data must contain either parameter "dot" or ' +
  14678. ' parameter pair "nodes" and "edges", but not both.');
  14679. }
  14680. // clean up in case there is anyone in an active mode of the manipulation. This is the same option as bound to the escape button.
  14681. if (this.constants.dataManipulation.enabled == true) {
  14682. this._createManipulatorBar();
  14683. }
  14684. // set options
  14685. this.setOptions(data && data.options);
  14686. // set all data
  14687. if (data && data.dot) {
  14688. // parse DOT file
  14689. if(data && data.dot) {
  14690. var dotData = dotparser.DOTToGraph(data.dot);
  14691. this.setData(dotData);
  14692. return;
  14693. }
  14694. }
  14695. else if (data && data.gephi) {
  14696. // parse DOT file
  14697. if(data && data.gephi) {
  14698. var gephiData = gephiParser.parseGephi(data.gephi);
  14699. this.setData(gephiData);
  14700. return;
  14701. }
  14702. }
  14703. else {
  14704. this._setNodes(data && data.nodes);
  14705. this._setEdges(data && data.edges);
  14706. }
  14707. this._putDataInSector();
  14708. if (disableStart == false) {
  14709. if (this.constants.hierarchicalLayout.enabled == true) {
  14710. this._resetLevels();
  14711. this._setupHierarchicalLayout();
  14712. }
  14713. else {
  14714. // find a stable position or start animating to a stable position
  14715. if (this.constants.stabilize == true) {
  14716. this._stabilize();
  14717. }
  14718. }
  14719. this.start();
  14720. }
  14721. this.initializing = false;
  14722. };
  14723. /**
  14724. * Set options
  14725. * @param {Object} options
  14726. */
  14727. Network.prototype.setOptions = function (options) {
  14728. if (options) {
  14729. var prop;
  14730. var fields = ['nodes','edges','smoothCurves','hierarchicalLayout','clustering','navigation',
  14731. 'keyboard','dataManipulation','onAdd','onEdit','onEditEdge','onConnect','onDelete','clickToUse'
  14732. ];
  14733. // extend all but the values in fields
  14734. util.selectiveNotDeepExtend(fields,this.constants, options);
  14735. util.selectiveNotDeepExtend(['color'],this.constants.nodes, options.nodes);
  14736. util.selectiveNotDeepExtend(['color','length'],this.constants.edges, options.edges);
  14737. this.groups.useDefaultGroups = this.constants.useDefaultGroups;
  14738. if (options.physics) {
  14739. util.mergeOptions(this.constants.physics, options.physics,'barnesHut');
  14740. util.mergeOptions(this.constants.physics, options.physics,'repulsion');
  14741. if (options.physics.hierarchicalRepulsion) {
  14742. this.constants.hierarchicalLayout.enabled = true;
  14743. this.constants.physics.hierarchicalRepulsion.enabled = true;
  14744. this.constants.physics.barnesHut.enabled = false;
  14745. for (prop in options.physics.hierarchicalRepulsion) {
  14746. if (options.physics.hierarchicalRepulsion.hasOwnProperty(prop)) {
  14747. this.constants.physics.hierarchicalRepulsion[prop] = options.physics.hierarchicalRepulsion[prop];
  14748. }
  14749. }
  14750. }
  14751. }
  14752. if (options.onAdd) {this.triggerFunctions.add = options.onAdd;}
  14753. if (options.onEdit) {this.triggerFunctions.edit = options.onEdit;}
  14754. if (options.onEditEdge) {this.triggerFunctions.editEdge = options.onEditEdge;}
  14755. if (options.onConnect) {this.triggerFunctions.connect = options.onConnect;}
  14756. if (options.onDelete) {this.triggerFunctions.del = options.onDelete;}
  14757. util.mergeOptions(this.constants, options,'smoothCurves');
  14758. util.mergeOptions(this.constants, options,'hierarchicalLayout');
  14759. util.mergeOptions(this.constants, options,'clustering');
  14760. util.mergeOptions(this.constants, options,'navigation');
  14761. util.mergeOptions(this.constants, options,'keyboard');
  14762. util.mergeOptions(this.constants, options,'dataManipulation');
  14763. if (options.dataManipulation) {
  14764. this.editMode = this.constants.dataManipulation.initiallyVisible;
  14765. }
  14766. // TODO: work out these options and document them
  14767. if (options.edges) {
  14768. if (options.edges.color !== undefined) {
  14769. if (util.isString(options.edges.color)) {
  14770. this.constants.edges.color = {};
  14771. this.constants.edges.color.color = options.edges.color;
  14772. this.constants.edges.color.highlight = options.edges.color;
  14773. this.constants.edges.color.hover = options.edges.color;
  14774. }
  14775. else {
  14776. if (options.edges.color.color !== undefined) {this.constants.edges.color.color = options.edges.color.color;}
  14777. if (options.edges.color.highlight !== undefined) {this.constants.edges.color.highlight = options.edges.color.highlight;}
  14778. if (options.edges.color.hover !== undefined) {this.constants.edges.color.hover = options.edges.color.hover;}
  14779. }
  14780. this.constants.edges.inheritColor = false;
  14781. }
  14782. if (!options.edges.fontColor) {
  14783. if (options.edges.color !== undefined) {
  14784. if (util.isString(options.edges.color)) {this.constants.edges.fontColor = options.edges.color;}
  14785. else if (options.edges.color.color !== undefined) {this.constants.edges.fontColor = options.edges.color.color;}
  14786. }
  14787. }
  14788. }
  14789. if (options.nodes) {
  14790. if (options.nodes.color) {
  14791. var newColorObj = util.parseColor(options.nodes.color);
  14792. this.constants.nodes.color.background = newColorObj.background;
  14793. this.constants.nodes.color.border = newColorObj.border;
  14794. this.constants.nodes.color.highlight.background = newColorObj.highlight.background;
  14795. this.constants.nodes.color.highlight.border = newColorObj.highlight.border;
  14796. this.constants.nodes.color.hover.background = newColorObj.hover.background;
  14797. this.constants.nodes.color.hover.border = newColorObj.hover.border;
  14798. }
  14799. }
  14800. if (options.groups) {
  14801. for (var groupname in options.groups) {
  14802. if (options.groups.hasOwnProperty(groupname)) {
  14803. var group = options.groups[groupname];
  14804. this.groups.add(groupname, group);
  14805. }
  14806. }
  14807. }
  14808. if (options.tooltip) {
  14809. for (prop in options.tooltip) {
  14810. if (options.tooltip.hasOwnProperty(prop)) {
  14811. this.constants.tooltip[prop] = options.tooltip[prop];
  14812. }
  14813. }
  14814. if (options.tooltip.color) {
  14815. this.constants.tooltip.color = util.parseColor(options.tooltip.color);
  14816. }
  14817. }
  14818. if ('clickToUse' in options) {
  14819. if (options.clickToUse) {
  14820. if (!this.activator) {
  14821. this.activator = new Activator(this.frame);
  14822. this.activator.on('change', this._createKeyBinds.bind(this));
  14823. }
  14824. }
  14825. else {
  14826. if (this.activator) {
  14827. this.activator.destroy();
  14828. delete this.activator;
  14829. }
  14830. }
  14831. }
  14832. if (options.labels) {
  14833. throw new Error('Option "labels" is deprecated. Use options "locale" and "locales" instead.');
  14834. }
  14835. // (Re)loading the mixins that can be enabled or disabled in the options.
  14836. // load the force calculation functions, grouped under the physics system.
  14837. this._loadPhysicsSystem();
  14838. // load the navigation system.
  14839. this._loadNavigationControls();
  14840. // load the data manipulation system
  14841. this._loadManipulationSystem();
  14842. // configure the smooth curves
  14843. this._configureSmoothCurves();
  14844. // bind hammer
  14845. this._bindHammer();
  14846. // bind keys. If disabled, this will not do anything;
  14847. this._createKeyBinds();
  14848. this._markAllEdgesAsDirty();
  14849. this.setSize(this.constants.width, this.constants.height);
  14850. this.moving = true;
  14851. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  14852. this._resetLevels();
  14853. this._setupHierarchicalLayout();
  14854. }
  14855. this.start();
  14856. }
  14857. };
  14858. /**
  14859. * Create the main frame for the Network.
  14860. * This function is executed once when a Network object is created. The frame
  14861. * contains a canvas, and this canvas contains all objects like the axis and
  14862. * nodes.
  14863. * @private
  14864. */
  14865. Network.prototype._create = function () {
  14866. // remove all elements from the container element.
  14867. while (this.containerElement.hasChildNodes()) {
  14868. this.containerElement.removeChild(this.containerElement.firstChild);
  14869. }
  14870. this.frame = document.createElement('div');
  14871. this.frame.className = 'vis network-frame';
  14872. this.frame.style.position = 'relative';
  14873. this.frame.style.overflow = 'hidden';
  14874. this.frame.tabIndex = 900;
  14875. //////////////////////////////////////////////////////////////////
  14876. this.frame.canvas = document.createElement("canvas");
  14877. this.frame.canvas.style.position = 'relative';
  14878. this.frame.appendChild(this.frame.canvas);
  14879. if (!this.frame.canvas.getContext) {
  14880. var noCanvas = document.createElement( 'DIV' );
  14881. noCanvas.style.color = 'red';
  14882. noCanvas.style.fontWeight = 'bold' ;
  14883. noCanvas.style.padding = '10px';
  14884. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  14885. this.frame.canvas.appendChild(noCanvas);
  14886. }
  14887. else {
  14888. var ctx = this.frame.canvas.getContext("2d");
  14889. this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio ||
  14890. ctx.mozBackingStorePixelRatio ||
  14891. ctx.msBackingStorePixelRatio ||
  14892. ctx.oBackingStorePixelRatio ||
  14893. ctx.backingStorePixelRatio || 1);
  14894. //this.pixelRatio = Math.max(1,this.pixelRatio); // this is to account for browser zooming out. The pixel ratio is ment to switch between 1 and 2 for HD screens.
  14895. this.frame.canvas.getContext("2d").setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
  14896. }
  14897. this._bindHammer();
  14898. };
  14899. /**
  14900. * This function binds hammer, it can be repeated over and over due to the uniqueness check.
  14901. * @private
  14902. */
  14903. Network.prototype._bindHammer = function() {
  14904. var me = this;
  14905. if (this.hammer !== undefined) {
  14906. this.hammer.dispose();
  14907. }
  14908. this.drag = {};
  14909. this.pinch = {};
  14910. this.hammer = Hammer(this.frame.canvas, {
  14911. prevent_default: true
  14912. });
  14913. this.hammer.on('tap', me._onTap.bind(me) );
  14914. this.hammer.on('doubletap', me._onDoubleTap.bind(me) );
  14915. this.hammer.on('hold', me._onHold.bind(me) );
  14916. this.hammer.on('touch', me._onTouch.bind(me) );
  14917. this.hammer.on('dragstart', me._onDragStart.bind(me) );
  14918. this.hammer.on('drag', me._onDrag.bind(me) );
  14919. this.hammer.on('dragend', me._onDragEnd.bind(me) );
  14920. if (this.constants.zoomable == true) {
  14921. this.hammer.on('mousewheel', me._onMouseWheel.bind(me));
  14922. this.hammer.on('DOMMouseScroll', me._onMouseWheel.bind(me)); // for FF
  14923. this.hammer.on('pinch', me._onPinch.bind(me) );
  14924. }
  14925. this.hammer.on('mousemove', me._onMouseMoveTitle.bind(me) );
  14926. this.hammerFrame = Hammer(this.frame, {
  14927. prevent_default: true
  14928. });
  14929. this.hammerFrame.on('release', me._onRelease.bind(me) );
  14930. // add the frame to the container element
  14931. this.containerElement.appendChild(this.frame);
  14932. }
  14933. /**
  14934. * Binding the keys for keyboard navigation. These functions are defined in the NavigationMixin
  14935. * @private
  14936. */
  14937. Network.prototype._createKeyBinds = function() {
  14938. var me = this;
  14939. if (this.keycharm !== undefined) {
  14940. this.keycharm.destroy();
  14941. }
  14942. if (this.constants.keyboard.bindToWindow == true) {
  14943. this.keycharm = keycharm({container: window, preventDefault: false});
  14944. }
  14945. else {
  14946. this.keycharm = keycharm({container: this.frame, preventDefault: false});
  14947. }
  14948. this.keycharm.reset();
  14949. if (this.constants.keyboard.enabled && this.isActive()) {
  14950. this.keycharm.bind("up", this._moveUp.bind(me) , "keydown");
  14951. this.keycharm.bind("up", this._yStopMoving.bind(me), "keyup");
  14952. this.keycharm.bind("down", this._moveDown.bind(me) , "keydown");
  14953. this.keycharm.bind("down", this._yStopMoving.bind(me), "keyup");
  14954. this.keycharm.bind("left", this._moveLeft.bind(me) , "keydown");
  14955. this.keycharm.bind("left", this._xStopMoving.bind(me), "keyup");
  14956. this.keycharm.bind("right",this._moveRight.bind(me), "keydown");
  14957. this.keycharm.bind("right",this._xStopMoving.bind(me), "keyup");
  14958. this.keycharm.bind("=", this._zoomIn.bind(me), "keydown");
  14959. this.keycharm.bind("=", this._stopZoom.bind(me), "keyup");
  14960. this.keycharm.bind("num+", this._zoomIn.bind(me), "keydown");
  14961. this.keycharm.bind("num+", this._stopZoom.bind(me), "keyup");
  14962. this.keycharm.bind("num-", this._zoomOut.bind(me), "keydown");
  14963. this.keycharm.bind("num-", this._stopZoom.bind(me), "keyup");
  14964. this.keycharm.bind("-", this._zoomOut.bind(me), "keydown");
  14965. this.keycharm.bind("-", this._stopZoom.bind(me), "keyup");
  14966. this.keycharm.bind("[", this._zoomIn.bind(me), "keydown");
  14967. this.keycharm.bind("[", this._stopZoom.bind(me), "keyup");
  14968. this.keycharm.bind("]", this._zoomOut.bind(me), "keydown");
  14969. this.keycharm.bind("]", this._stopZoom.bind(me), "keyup");
  14970. this.keycharm.bind("pageup",this._zoomIn.bind(me), "keydown");
  14971. this.keycharm.bind("pageup",this._stopZoom.bind(me), "keyup");
  14972. this.keycharm.bind("pagedown",this._zoomOut.bind(me),"keydown");
  14973. this.keycharm.bind("pagedown",this._stopZoom.bind(me), "keyup");
  14974. }
  14975. if (this.constants.dataManipulation.enabled == true) {
  14976. this.keycharm.bind("esc",this._createManipulatorBar.bind(me));
  14977. this.keycharm.bind("delete",this._deleteSelected.bind(me));
  14978. }
  14979. };
  14980. /**
  14981. * Cleans up all bindings of the network, removing it fully from the memory IF the variable is set to null after calling this function.
  14982. * var network = new vis.Network(..);
  14983. * network.destroy();
  14984. * network = null;
  14985. */
  14986. Network.prototype.destroy = function() {
  14987. this.start = function () {};
  14988. this.redraw = function () {};
  14989. this.timer = false;
  14990. // cleanup physicsConfiguration if it exists
  14991. this._cleanupPhysicsConfiguration();
  14992. // remove keybindings
  14993. this.keycharm.reset();
  14994. // clear hammer bindings
  14995. this.hammer.dispose();
  14996. // clear events
  14997. this.off();
  14998. this._recursiveDOMDelete(this.containerElement);
  14999. }
  15000. Network.prototype._recursiveDOMDelete = function(DOMobject) {
  15001. while (DOMobject.hasChildNodes() == true) {
  15002. this._recursiveDOMDelete(DOMobject.firstChild);
  15003. DOMobject.removeChild(DOMobject.firstChild);
  15004. }
  15005. }
  15006. /**
  15007. * Get the pointer location from a touch location
  15008. * @param {{pageX: Number, pageY: Number}} touch
  15009. * @return {{x: Number, y: Number}} pointer
  15010. * @private
  15011. */
  15012. Network.prototype._getPointer = function (touch) {
  15013. return {
  15014. x: touch.pageX - util.getAbsoluteLeft(this.frame.canvas),
  15015. y: touch.pageY - util.getAbsoluteTop(this.frame.canvas)
  15016. };
  15017. };
  15018. /**
  15019. * On start of a touch gesture, store the pointer
  15020. * @param event
  15021. * @private
  15022. */
  15023. Network.prototype._onTouch = function (event) {
  15024. if (new Date().valueOf() - this.touchTime > 100) {
  15025. this.drag.pointer = this._getPointer(event.gesture.center);
  15026. this.drag.pinched = false;
  15027. this.pinch.scale = this._getScale();
  15028. // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame)
  15029. this.touchTime = new Date().valueOf();
  15030. this._handleTouch(this.drag.pointer);
  15031. }
  15032. };
  15033. /**
  15034. * handle drag start event
  15035. * @private
  15036. */
  15037. Network.prototype._onDragStart = function (event) {
  15038. this._handleDragStart(event);
  15039. };
  15040. /**
  15041. * This function is called by _onDragStart.
  15042. * It is separated out because we can then overload it for the datamanipulation system.
  15043. *
  15044. * @private
  15045. */
  15046. Network.prototype._handleDragStart = function(event) {
  15047. // in case the touch event was triggered on an external div, do the initial touch now.
  15048. if (this.drag.pointer === undefined) {
  15049. this._onTouch(event);
  15050. }
  15051. var node = this._getNodeAt(this.drag.pointer);
  15052. // note: drag.pointer is set in _onTouch to get the initial touch location
  15053. this.drag.dragging = true;
  15054. this.drag.selection = [];
  15055. this.drag.translation = this._getTranslation();
  15056. this.drag.nodeId = null;
  15057. this.draggingNodes = false;
  15058. if (node != null && this.constants.dragNodes == true) {
  15059. this.draggingNodes = true;
  15060. this.drag.nodeId = node.id;
  15061. // select the clicked node if not yet selected
  15062. if (!node.isSelected()) {
  15063. this._selectObject(node,false);
  15064. }
  15065. this.emit("dragStart",{nodeIds:this.getSelection().nodes});
  15066. // create an array with the selected nodes and their original location and status
  15067. for (var objectId in this.selectionObj.nodes) {
  15068. if (this.selectionObj.nodes.hasOwnProperty(objectId)) {
  15069. var object = this.selectionObj.nodes[objectId];
  15070. var s = {
  15071. id: object.id,
  15072. node: object,
  15073. // store original x, y, xFixed and yFixed, make the node temporarily Fixed
  15074. x: object.x,
  15075. y: object.y,
  15076. xFixed: object.xFixed,
  15077. yFixed: object.yFixed
  15078. };
  15079. object.xFixed = true;
  15080. object.yFixed = true;
  15081. this.drag.selection.push(s);
  15082. }
  15083. }
  15084. }
  15085. };
  15086. /**
  15087. * handle drag event
  15088. * @private
  15089. */
  15090. Network.prototype._onDrag = function (event) {
  15091. this._handleOnDrag(event)
  15092. };
  15093. /**
  15094. * This function is called by _onDrag.
  15095. * It is separated out because we can then overload it for the datamanipulation system.
  15096. *
  15097. * @private
  15098. */
  15099. Network.prototype._handleOnDrag = function(event) {
  15100. if (this.drag.pinched) {
  15101. return;
  15102. }
  15103. // remove the focus on node if it is focussed on by the focusOnNode
  15104. this.releaseNode();
  15105. var pointer = this._getPointer(event.gesture.center);
  15106. var me = this;
  15107. var drag = this.drag;
  15108. var selection = drag.selection;
  15109. if (selection && selection.length && this.constants.dragNodes == true) {
  15110. // calculate delta's and new location
  15111. var deltaX = pointer.x - drag.pointer.x;
  15112. var deltaY = pointer.y - drag.pointer.y;
  15113. // update position of all selected nodes
  15114. selection.forEach(function (s) {
  15115. var node = s.node;
  15116. if (!s.xFixed) {
  15117. node.x = me._XconvertDOMtoCanvas(me._XconvertCanvasToDOM(s.x) + deltaX);
  15118. }
  15119. if (!s.yFixed) {
  15120. node.y = me._YconvertDOMtoCanvas(me._YconvertCanvasToDOM(s.y) + deltaY);
  15121. }
  15122. });
  15123. // start _animationStep if not yet running
  15124. if (!this.moving) {
  15125. this.moving = true;
  15126. this.start();
  15127. }
  15128. }
  15129. else {
  15130. // move the network
  15131. if (this.constants.dragNetwork == true) {
  15132. // if the drag was not started properly because the click started outside the network div, start it now.
  15133. if (this.drag.pointer === undefined) {
  15134. this._handleDragStart(event);
  15135. return;
  15136. }
  15137. var diffX = pointer.x - this.drag.pointer.x;
  15138. var diffY = pointer.y - this.drag.pointer.y;
  15139. this._setTranslation(
  15140. this.drag.translation.x + diffX,
  15141. this.drag.translation.y + diffY
  15142. );
  15143. this._redraw();
  15144. }
  15145. }
  15146. };
  15147. /**
  15148. * handle drag start event
  15149. * @private
  15150. */
  15151. Network.prototype._onDragEnd = function (event) {
  15152. this._handleDragEnd(event);
  15153. };
  15154. Network.prototype._handleDragEnd = function(event) {
  15155. this.drag.dragging = false;
  15156. var selection = this.drag.selection;
  15157. if (selection && selection.length) {
  15158. selection.forEach(function (s) {
  15159. // restore original xFixed and yFixed
  15160. s.node.xFixed = s.xFixed;
  15161. s.node.yFixed = s.yFixed;
  15162. });
  15163. this.moving = true;
  15164. this.start();
  15165. }
  15166. else {
  15167. this._redraw();
  15168. }
  15169. if (this.draggingNodes == false) {
  15170. this.emit("dragEnd",{nodeIds:[]});
  15171. }
  15172. else {
  15173. this.emit("dragEnd",{nodeIds:this.getSelection().nodes});
  15174. }
  15175. }
  15176. /**
  15177. * handle tap/click event: select/unselect a node
  15178. * @private
  15179. */
  15180. Network.prototype._onTap = function (event) {
  15181. var pointer = this._getPointer(event.gesture.center);
  15182. this.pointerPosition = pointer;
  15183. this._handleTap(pointer);
  15184. };
  15185. /**
  15186. * handle doubletap event
  15187. * @private
  15188. */
  15189. Network.prototype._onDoubleTap = function (event) {
  15190. var pointer = this._getPointer(event.gesture.center);
  15191. this._handleDoubleTap(pointer);
  15192. };
  15193. /**
  15194. * handle long tap event: multi select nodes
  15195. * @private
  15196. */
  15197. Network.prototype._onHold = function (event) {
  15198. var pointer = this._getPointer(event.gesture.center);
  15199. this.pointerPosition = pointer;
  15200. this._handleOnHold(pointer);
  15201. };
  15202. /**
  15203. * handle the release of the screen
  15204. *
  15205. * @private
  15206. */
  15207. Network.prototype._onRelease = function (event) {
  15208. var pointer = this._getPointer(event.gesture.center);
  15209. this._handleOnRelease(pointer);
  15210. };
  15211. /**
  15212. * Handle pinch event
  15213. * @param event
  15214. * @private
  15215. */
  15216. Network.prototype._onPinch = function (event) {
  15217. var pointer = this._getPointer(event.gesture.center);
  15218. this.drag.pinched = true;
  15219. if (!('scale' in this.pinch)) {
  15220. this.pinch.scale = 1;
  15221. }
  15222. // TODO: enabled moving while pinching?
  15223. var scale = this.pinch.scale * event.gesture.scale;
  15224. this._zoom(scale, pointer)
  15225. };
  15226. /**
  15227. * Zoom the network in or out
  15228. * @param {Number} scale a number around 1, and between 0.01 and 10
  15229. * @param {{x: Number, y: Number}} pointer Position on screen
  15230. * @return {Number} appliedScale scale is limited within the boundaries
  15231. * @private
  15232. */
  15233. Network.prototype._zoom = function(scale, pointer) {
  15234. if (this.constants.zoomable == true) {
  15235. var scaleOld = this._getScale();
  15236. if (scale < 0.00001) {
  15237. scale = 0.00001;
  15238. }
  15239. if (scale > 10) {
  15240. scale = 10;
  15241. }
  15242. var preScaleDragPointer = null;
  15243. if (this.drag !== undefined) {
  15244. if (this.drag.dragging == true) {
  15245. preScaleDragPointer = this.DOMtoCanvas(this.drag.pointer);
  15246. }
  15247. }
  15248. // + this.frame.canvas.clientHeight / 2
  15249. var translation = this._getTranslation();
  15250. var scaleFrac = scale / scaleOld;
  15251. var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
  15252. var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
  15253. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  15254. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  15255. this._setScale(scale);
  15256. this._setTranslation(tx, ty);
  15257. this.updateClustersDefault();
  15258. if (preScaleDragPointer != null) {
  15259. var postScaleDragPointer = this.canvasToDOM(preScaleDragPointer);
  15260. this.drag.pointer.x = postScaleDragPointer.x;
  15261. this.drag.pointer.y = postScaleDragPointer.y;
  15262. }
  15263. this._redraw();
  15264. if (scaleOld < scale) {
  15265. this.emit("zoom", {direction:"+"});
  15266. }
  15267. else {
  15268. this.emit("zoom", {direction:"-"});
  15269. }
  15270. return scale;
  15271. }
  15272. };
  15273. /**
  15274. * Event handler for mouse wheel event, used to zoom the timeline
  15275. * See http://adomas.org/javascript-mouse-wheel/
  15276. * https://github.com/EightMedia/hammer.js/issues/256
  15277. * @param {MouseEvent} event
  15278. * @private
  15279. */
  15280. Network.prototype._onMouseWheel = function(event) {
  15281. // retrieve delta
  15282. var delta = 0;
  15283. if (event.wheelDelta) { /* IE/Opera. */
  15284. delta = event.wheelDelta/120;
  15285. } else if (event.detail) { /* Mozilla case. */
  15286. // In Mozilla, sign of delta is different than in IE.
  15287. // Also, delta is multiple of 3.
  15288. delta = -event.detail/3;
  15289. }
  15290. // If delta is nonzero, handle it.
  15291. // Basically, delta is now positive if wheel was scrolled up,
  15292. // and negative, if wheel was scrolled down.
  15293. if (delta) {
  15294. // calculate the new scale
  15295. var scale = this._getScale();
  15296. var zoom = delta / 10;
  15297. if (delta < 0) {
  15298. zoom = zoom / (1 - zoom);
  15299. }
  15300. scale *= (1 + zoom);
  15301. // calculate the pointer location
  15302. var gesture = hammerUtil.fakeGesture(this, event);
  15303. var pointer = this._getPointer(gesture.center);
  15304. // apply the new scale
  15305. this._zoom(scale, pointer);
  15306. }
  15307. // Prevent default actions caused by mouse wheel.
  15308. event.preventDefault();
  15309. };
  15310. /**
  15311. * Mouse move handler for checking whether the title moves over a node with a title.
  15312. * @param {Event} event
  15313. * @private
  15314. */
  15315. Network.prototype._onMouseMoveTitle = function (event) {
  15316. var gesture = hammerUtil.fakeGesture(this, event);
  15317. var pointer = this._getPointer(gesture.center);
  15318. var popupVisible = false;
  15319. // check if the previously selected node is still selected
  15320. if (this.popup !== undefined) {
  15321. if (this.popup.hidden === false) {
  15322. this._checkHidePopup(pointer);
  15323. }
  15324. // if the popup was not hidden above
  15325. if (this.popup.hidden === false) {
  15326. popupVisible = true;
  15327. this.popup.setPosition(pointer.x + 3,pointer.y - 5)
  15328. this.popup.show();
  15329. }
  15330. }
  15331. // if we bind the keyboard to the div, we have to highlight it to use it. This highlights it on mouse over
  15332. if (this.constants.keyboard.bindToWindow == false && this.constants.keyboard.enabled == true) {
  15333. this.frame.focus();
  15334. }
  15335. // start a timeout that will check if the mouse is positioned above an element
  15336. if (popupVisible === false) {
  15337. var me = this;
  15338. var checkShow = function () {
  15339. me._checkShowPopup(pointer);
  15340. };
  15341. if (this.popupTimer) {
  15342. clearInterval(this.popupTimer); // stop any running calculationTimer
  15343. }
  15344. if (!this.drag.dragging) {
  15345. this.popupTimer = setTimeout(checkShow, this.constants.tooltip.delay);
  15346. }
  15347. }
  15348. /**
  15349. * Adding hover highlights
  15350. */
  15351. if (this.constants.hover == true) {
  15352. // removing all hover highlights
  15353. for (var edgeId in this.hoverObj.edges) {
  15354. if (this.hoverObj.edges.hasOwnProperty(edgeId)) {
  15355. this.hoverObj.edges[edgeId].hover = false;
  15356. delete this.hoverObj.edges[edgeId];
  15357. }
  15358. }
  15359. // adding hover highlights
  15360. var obj = this._getNodeAt(pointer);
  15361. if (obj == null) {
  15362. obj = this._getEdgeAt(pointer);
  15363. }
  15364. if (obj != null) {
  15365. this._hoverObject(obj);
  15366. }
  15367. // removing all node hover highlights except for the selected one.
  15368. for (var nodeId in this.hoverObj.nodes) {
  15369. if (this.hoverObj.nodes.hasOwnProperty(nodeId)) {
  15370. if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj == null) {
  15371. this._blurObject(this.hoverObj.nodes[nodeId]);
  15372. delete this.hoverObj.nodes[nodeId];
  15373. }
  15374. }
  15375. }
  15376. this.redraw();
  15377. }
  15378. };
  15379. /**
  15380. * Check if there is an element on the given position in the network
  15381. * (a node or edge). If so, and if this element has a title,
  15382. * show a popup window with its title.
  15383. *
  15384. * @param {{x:Number, y:Number}} pointer
  15385. * @private
  15386. */
  15387. Network.prototype._checkShowPopup = function (pointer) {
  15388. var obj = {
  15389. left: this._XconvertDOMtoCanvas(pointer.x),
  15390. top: this._YconvertDOMtoCanvas(pointer.y),
  15391. right: this._XconvertDOMtoCanvas(pointer.x),
  15392. bottom: this._YconvertDOMtoCanvas(pointer.y)
  15393. };
  15394. var id;
  15395. var previousPopupObjId = this.popupObj === undefined ? "" : this.popupObj.id;
  15396. var nodeUnderCursor = false;
  15397. var popupType = "node";
  15398. if (this.popupObj == undefined) {
  15399. // search the nodes for overlap, select the top one in case of multiple nodes
  15400. var nodes = this.nodes;
  15401. var overlappingNodes = [];
  15402. for (id in nodes) {
  15403. if (nodes.hasOwnProperty(id)) {
  15404. var node = nodes[id];
  15405. if (node.isOverlappingWith(obj)) {
  15406. if (node.getTitle() !== undefined) {
  15407. overlappingNodes.push(id);
  15408. }
  15409. }
  15410. }
  15411. }
  15412. if (overlappingNodes.length > 0) {
  15413. // if there are overlapping nodes, select the last one, this is the
  15414. // one which is drawn on top of the others
  15415. this.popupObj = this.nodes[overlappingNodes[overlappingNodes.length - 1]];
  15416. // if you hover over a node, the title of the edge is not supposed to be shown.
  15417. nodeUnderCursor = true;
  15418. }
  15419. }
  15420. if (this.popupObj === undefined && nodeUnderCursor == false) {
  15421. // search the edges for overlap
  15422. var edges = this.edges;
  15423. var overlappingEdges = [];
  15424. for (id in edges) {
  15425. if (edges.hasOwnProperty(id)) {
  15426. var edge = edges[id];
  15427. if (edge.connected && (edge.getTitle() !== undefined) &&
  15428. edge.isOverlappingWith(obj)) {
  15429. overlappingEdges.push(id);
  15430. }
  15431. }
  15432. }
  15433. if (overlappingEdges.length > 0) {
  15434. this.popupObj = this.edges[overlappingEdges[overlappingEdges.length - 1]];
  15435. popupType = "edge";
  15436. }
  15437. }
  15438. if (this.popupObj) {
  15439. // show popup message window
  15440. if (this.popupObj.id != previousPopupObjId) {
  15441. if (this.popup === undefined) {
  15442. this.popup = new Popup(this.frame, this.constants.tooltip);
  15443. }
  15444. this.popup.popupTargetType = popupType;
  15445. this.popup.popupTargetId = this.popupObj.id;
  15446. // adjust a small offset such that the mouse cursor is located in the
  15447. // bottom left location of the popup, and you can easily move over the
  15448. // popup area
  15449. this.popup.setPosition(pointer.x + 3, pointer.y - 5);
  15450. this.popup.setText(this.popupObj.getTitle());
  15451. this.popup.show();
  15452. }
  15453. }
  15454. else {
  15455. if (this.popup) {
  15456. this.popup.hide();
  15457. }
  15458. }
  15459. };
  15460. /**
  15461. * Check if the popup must be hidden, which is the case when the mouse is no
  15462. * longer hovering on the object
  15463. * @param {{x:Number, y:Number}} pointer
  15464. * @private
  15465. */
  15466. Network.prototype._checkHidePopup = function (pointer) {
  15467. var pointerObj = {
  15468. left: this._XconvertDOMtoCanvas(pointer.x),
  15469. top: this._YconvertDOMtoCanvas(pointer.y),
  15470. right: this._XconvertDOMtoCanvas(pointer.x),
  15471. bottom: this._YconvertDOMtoCanvas(pointer.y)
  15472. };
  15473. var stillOnObj = false;
  15474. if (this.popup.popupTargetType == 'node') {
  15475. stillOnObj = this.nodes[this.popup.popupTargetId].isOverlappingWith(pointerObj);
  15476. if (stillOnObj === true) {
  15477. var overNode = this._getNodeAt(pointer);
  15478. stillOnObj = overNode.id == this.popup.popupTargetId;
  15479. }
  15480. }
  15481. else {
  15482. if (this._getNodeAt(pointer) === null) {
  15483. stillOnObj = this.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj);
  15484. }
  15485. }
  15486. if (stillOnObj === false) {
  15487. this.popupObj = undefined;
  15488. this.popup.hide();
  15489. }
  15490. };
  15491. /**
  15492. * Set a new size for the network
  15493. * @param {string} width Width in pixels or percentage (for example '800px'
  15494. * or '50%')
  15495. * @param {string} height Height in pixels or percentage (for example '400px'
  15496. * or '30%')
  15497. */
  15498. Network.prototype.setSize = function(width, height) {
  15499. var emitEvent = false;
  15500. var oldWidth = this.frame.canvas.width;
  15501. var oldHeight = this.frame.canvas.height;
  15502. if (width != this.constants.width || height != this.constants.height || this.frame.style.width != width || this.frame.style.height != height) {
  15503. this.frame.style.width = width;
  15504. this.frame.style.height = height;
  15505. this.frame.canvas.style.width = '100%';
  15506. this.frame.canvas.style.height = '100%';
  15507. this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio;
  15508. this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio;
  15509. this.constants.width = width;
  15510. this.constants.height = height;
  15511. emitEvent = true;
  15512. }
  15513. else {
  15514. // this would adapt the width of the canvas to the width from 100% if and only if
  15515. // there is a change.
  15516. if (this.frame.canvas.width != this.frame.canvas.clientWidth * this.pixelRatio) {
  15517. this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio;
  15518. emitEvent = true;
  15519. }
  15520. if (this.frame.canvas.height != this.frame.canvas.clientHeight * this.pixelRatio) {
  15521. this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio;
  15522. emitEvent = true;
  15523. }
  15524. }
  15525. if (emitEvent == true) {
  15526. this.emit('resize', {width:this.frame.canvas.width * this.pixelRatio,height:this.frame.canvas.height * this.pixelRatio, oldWidth: oldWidth * this.pixelRatio, oldHeight: oldHeight * this.pixelRatio});
  15527. }
  15528. };
  15529. /**
  15530. * Set a data set with nodes for the network
  15531. * @param {Array | DataSet | DataView} nodes The data containing the nodes.
  15532. * @private
  15533. */
  15534. Network.prototype._setNodes = function(nodes) {
  15535. var oldNodesData = this.nodesData;
  15536. if (nodes instanceof DataSet || nodes instanceof DataView) {
  15537. this.nodesData = nodes;
  15538. }
  15539. else if (Array.isArray(nodes)) {
  15540. this.nodesData = new DataSet();
  15541. this.nodesData.add(nodes);
  15542. }
  15543. else if (!nodes) {
  15544. this.nodesData = new DataSet();
  15545. }
  15546. else {
  15547. throw new TypeError('Array or DataSet expected');
  15548. }
  15549. if (oldNodesData) {
  15550. // unsubscribe from old dataset
  15551. util.forEach(this.nodesListeners, function (callback, event) {
  15552. oldNodesData.off(event, callback);
  15553. });
  15554. }
  15555. // remove drawn nodes
  15556. this.nodes = {};
  15557. if (this.nodesData) {
  15558. // subscribe to new dataset
  15559. var me = this;
  15560. util.forEach(this.nodesListeners, function (callback, event) {
  15561. me.nodesData.on(event, callback);
  15562. });
  15563. // draw all new nodes
  15564. var ids = this.nodesData.getIds();
  15565. this._addNodes(ids);
  15566. }
  15567. this._updateSelection();
  15568. };
  15569. /**
  15570. * Add nodes
  15571. * @param {Number[] | String[]} ids
  15572. * @private
  15573. */
  15574. Network.prototype._addNodes = function(ids) {
  15575. var id;
  15576. var fieldId = this.nodesData._fieldId || null;
  15577. for (var i = 0, len = ids.length; i < len; i++) {
  15578. id = ids[i];
  15579. var data = this.nodesData.get(id);
  15580. if (fieldId) {
  15581. data.id = data[fieldId];
  15582. }
  15583. var node = new Node(data, this.images, this.groups, this.constants);
  15584. this.nodes[id] = node; // note: this may replace an existing node
  15585. if ((node.xFixed == false || node.yFixed == false) && (node.x === null || node.y === null)) {
  15586. var radius = 10 * 0.1*ids.length + 10;
  15587. var angle = 2 * Math.PI * Math.random();
  15588. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  15589. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  15590. }
  15591. this.moving = true;
  15592. }
  15593. this._updateNodeIndexList();
  15594. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  15595. this._resetLevels();
  15596. this._setupHierarchicalLayout();
  15597. }
  15598. this._updateCalculationNodes();
  15599. this._reconnectEdges();
  15600. this._updateValueRange(this.nodes);
  15601. this.updateLabels();
  15602. };
  15603. /**
  15604. * Update existing nodes, or create them when not yet existing
  15605. * @param {Number[] | String[]} ids
  15606. * @private
  15607. */
  15608. Network.prototype._updateNodes = function(ids) {
  15609. var nodesData = this.nodesData.get(ids);
  15610. var nodes = this.nodes;
  15611. for (var i = 0, len = ids.length; i < len; i++) {
  15612. var id = ids[i];
  15613. var node = nodes[id];
  15614. var data = nodesData[i];
  15615. if (node) {
  15616. // update node
  15617. node.setProperties(data, this.constants);
  15618. }
  15619. else {
  15620. // create node
  15621. node = new Node(properties, this.images, this.groups, this.constants);
  15622. nodes[id] = node;
  15623. }
  15624. }
  15625. this.moving = true;
  15626. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  15627. this._resetLevels();
  15628. this._setupHierarchicalLayout();
  15629. }
  15630. this._updateNodeIndexList();
  15631. this._updateValueRange(nodes);
  15632. this._markAllEdgesAsDirty();
  15633. };
  15634. Network.prototype._markAllEdgesAsDirty = function() {
  15635. for (var edgeId in this.edges) {
  15636. this.edges[edgeId].colorDirty = true;
  15637. }
  15638. }
  15639. /**
  15640. * Remove existing nodes. If nodes do not exist, the method will just ignore it.
  15641. * @param {Number[] | String[]} ids
  15642. * @private
  15643. */
  15644. Network.prototype._removeNodes = function(ids) {
  15645. var nodes = this.nodes;
  15646. // remove from selection
  15647. for (var i = 0, len = ids.length; i < len; i++) {
  15648. if (this.selectionObj.nodes[ids[i]] !== undefined) {
  15649. this.nodes[ids[i]].unselect();
  15650. this._removeFromSelection(this.nodes[ids[i]]);
  15651. }
  15652. }
  15653. for (var i = 0, len = ids.length; i < len; i++) {
  15654. var id = ids[i];
  15655. delete nodes[id];
  15656. }
  15657. this._updateNodeIndexList();
  15658. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  15659. this._resetLevels();
  15660. this._setupHierarchicalLayout();
  15661. }
  15662. this._updateCalculationNodes();
  15663. this._reconnectEdges();
  15664. this._updateSelection();
  15665. this._updateValueRange(nodes);
  15666. };
  15667. /**
  15668. * Load edges by reading the data table
  15669. * @param {Array | DataSet | DataView} edges The data containing the edges.
  15670. * @private
  15671. * @private
  15672. */
  15673. Network.prototype._setEdges = function(edges) {
  15674. var oldEdgesData = this.edgesData;
  15675. if (edges instanceof DataSet || edges instanceof DataView) {
  15676. this.edgesData = edges;
  15677. }
  15678. else if (Array.isArray(edges)) {
  15679. this.edgesData = new DataSet();
  15680. this.edgesData.add(edges);
  15681. }
  15682. else if (!edges) {
  15683. this.edgesData = new DataSet();
  15684. }
  15685. else {
  15686. throw new TypeError('Array or DataSet expected');
  15687. }
  15688. if (oldEdgesData) {
  15689. // unsubscribe from old dataset
  15690. util.forEach(this.edgesListeners, function (callback, event) {
  15691. oldEdgesData.off(event, callback);
  15692. });
  15693. }
  15694. // remove drawn edges
  15695. this.edges = {};
  15696. if (this.edgesData) {
  15697. // subscribe to new dataset
  15698. var me = this;
  15699. util.forEach(this.edgesListeners, function (callback, event) {
  15700. me.edgesData.on(event, callback);
  15701. });
  15702. // draw all new nodes
  15703. var ids = this.edgesData.getIds();
  15704. this._addEdges(ids);
  15705. }
  15706. this._reconnectEdges();
  15707. };
  15708. /**
  15709. * Add edges
  15710. * @param {Number[] | String[]} ids
  15711. * @private
  15712. */
  15713. Network.prototype._addEdges = function (ids) {
  15714. var edges = this.edges;
  15715. var edgesData = this.edgesData;
  15716. var fieldId = this.edgesData._fieldId;
  15717. for (var i = 0, len = ids.length; i < len; i++) {
  15718. var id = ids[i];
  15719. var oldEdge = edges[id];
  15720. if (oldEdge) {
  15721. oldEdge.disconnect();
  15722. }
  15723. var data = edgesData.get(id, {"showInternalIds" : true});
  15724. if (fieldId) {
  15725. data.id = data[fieldId];
  15726. }
  15727. edges[id] = new Edge(data, this, this.constants);
  15728. }
  15729. this.moving = true;
  15730. this._updateValueRange(edges);
  15731. this._createBezierNodes();
  15732. this._updateCalculationNodes();
  15733. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  15734. this._resetLevels();
  15735. this._setupHierarchicalLayout();
  15736. }
  15737. };
  15738. /**
  15739. * Update existing edges, or create them when not yet existing
  15740. * @param {Number[] | String[]} ids
  15741. * @private
  15742. */
  15743. Network.prototype._updateEdges = function (ids) {
  15744. var edges = this.edges,
  15745. edgesData = this.edgesData;
  15746. for (var i = 0, len = ids.length; i < len; i++) {
  15747. var id = ids[i];
  15748. var data = edgesData.get(id);
  15749. var edge = edges[id];
  15750. if (edge) {
  15751. // update edge
  15752. edge.disconnect();
  15753. edge.setProperties(data, this.constants);
  15754. edge.connect();
  15755. }
  15756. else {
  15757. // create edge
  15758. edge = new Edge(data, this, this.constants);
  15759. this.edges[id] = edge;
  15760. }
  15761. }
  15762. this._createBezierNodes();
  15763. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  15764. this._resetLevels();
  15765. this._setupHierarchicalLayout();
  15766. }
  15767. this.moving = true;
  15768. this._updateValueRange(edges);
  15769. };
  15770. /**
  15771. * Remove existing edges. Non existing ids will be ignored
  15772. * @param {Number[] | String[]} ids
  15773. * @private
  15774. */
  15775. Network.prototype._removeEdges = function (ids) {
  15776. var edges = this.edges;
  15777. // remove from selection
  15778. for (var i = 0, len = ids.length; i < len; i++) {
  15779. if (this.selectionObj.edges[ids[i]] !== undefined) {
  15780. edges[ids[i]].unselect();
  15781. this._removeFromSelection(edges[ids[i]]);
  15782. }
  15783. }
  15784. for (var i = 0, len = ids.length; i < len; i++) {
  15785. var id = ids[i];
  15786. var edge = edges[id];
  15787. if (edge) {
  15788. if (edge.via != null) {
  15789. delete this.sectors['support']['nodes'][edge.via.id];
  15790. }
  15791. edge.disconnect();
  15792. delete edges[id];
  15793. }
  15794. }
  15795. this.moving = true;
  15796. this._updateValueRange(edges);
  15797. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  15798. this._resetLevels();
  15799. this._setupHierarchicalLayout();
  15800. }
  15801. this._updateCalculationNodes();
  15802. };
  15803. /**
  15804. * Reconnect all edges
  15805. * @private
  15806. */
  15807. Network.prototype._reconnectEdges = function() {
  15808. var id,
  15809. nodes = this.nodes,
  15810. edges = this.edges;
  15811. for (id in nodes) {
  15812. if (nodes.hasOwnProperty(id)) {
  15813. nodes[id].edges = [];
  15814. nodes[id].dynamicEdges = [];
  15815. }
  15816. }
  15817. for (id in edges) {
  15818. if (edges.hasOwnProperty(id)) {
  15819. var edge = edges[id];
  15820. edge.from = null;
  15821. edge.to = null;
  15822. edge.connect();
  15823. }
  15824. }
  15825. };
  15826. /**
  15827. * Update the values of all object in the given array according to the current
  15828. * value range of the objects in the array.
  15829. * @param {Object} obj An object containing a set of Edges or Nodes
  15830. * The objects must have a method getValue() and
  15831. * setValueRange(min, max).
  15832. * @private
  15833. */
  15834. Network.prototype._updateValueRange = function(obj) {
  15835. var id;
  15836. // determine the range of the objects
  15837. var valueMin = undefined;
  15838. var valueMax = undefined;
  15839. var valueTotal = 0;
  15840. for (id in obj) {
  15841. if (obj.hasOwnProperty(id)) {
  15842. var value = obj[id].getValue();
  15843. if (value !== undefined) {
  15844. valueMin = (valueMin === undefined) ? value : Math.min(value, valueMin);
  15845. valueMax = (valueMax === undefined) ? value : Math.max(value, valueMax);
  15846. valueTotal += value;
  15847. }
  15848. }
  15849. }
  15850. // adjust the range of all objects
  15851. if (valueMin !== undefined && valueMax !== undefined) {
  15852. for (id in obj) {
  15853. if (obj.hasOwnProperty(id)) {
  15854. obj[id].setValueRange(valueMin, valueMax, valueTotal);
  15855. }
  15856. }
  15857. }
  15858. };
  15859. /**
  15860. * Redraw the network with the current data
  15861. * chart will be resized too.
  15862. */
  15863. Network.prototype.redraw = function() {
  15864. this.setSize(this.constants.width, this.constants.height);
  15865. this._redraw();
  15866. };
  15867. /**
  15868. * Redraw the network with the current data
  15869. * @param hidden | used to get the first estimate of the node sizes. only the nodes are drawn after which they are quickly drawn over.
  15870. * @private
  15871. */
  15872. Network.prototype._requestRedraw = function(hidden) {
  15873. if (this.redrawRequested !== true) {
  15874. this.redrawRequested = true;
  15875. if (this.requiresTimeout === true) {
  15876. window.setTimeout(this._redraw.bind(this, hidden),0);
  15877. }
  15878. else {
  15879. window.requestAnimationFrame(this._redraw.bind(this, hidden, true));
  15880. }
  15881. }
  15882. };
  15883. Network.prototype._redraw = function(hidden, requested) {
  15884. if (hidden === undefined) {
  15885. hidden = false;
  15886. }
  15887. this.redrawRequested = false;
  15888. var ctx = this.frame.canvas.getContext('2d');
  15889. ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
  15890. // clear the canvas
  15891. var w = this.frame.canvas.clientWidth;
  15892. var h = this.frame.canvas.clientHeight;
  15893. ctx.clearRect(0, 0, w, h);
  15894. // set scaling and translation
  15895. ctx.save();
  15896. ctx.translate(this.translation.x, this.translation.y);
  15897. ctx.scale(this.scale, this.scale);
  15898. this.canvasTopLeft = {
  15899. "x": this._XconvertDOMtoCanvas(0),
  15900. "y": this._YconvertDOMtoCanvas(0)
  15901. };
  15902. this.canvasBottomRight = {
  15903. "x": this._XconvertDOMtoCanvas(this.frame.canvas.clientWidth),
  15904. "y": this._YconvertDOMtoCanvas(this.frame.canvas.clientHeight)
  15905. };
  15906. if (hidden === false) {
  15907. this._doInAllSectors("_drawAllSectorNodes", ctx);
  15908. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideEdgesOnDrag == false) {
  15909. this._doInAllSectors("_drawEdges", ctx);
  15910. }
  15911. }
  15912. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideNodesOnDrag == false) {
  15913. this._doInAllSectors("_drawNodes",ctx,false);
  15914. }
  15915. if (hidden === false) {
  15916. if (this.controlNodesActive == true) {
  15917. this._doInAllSectors("_drawControlNodes", ctx);
  15918. }
  15919. }
  15920. // this._doInSupportSector("_drawNodes",ctx,true);
  15921. // this._drawTree(ctx,"#F00F0F");
  15922. // restore original scaling and translation
  15923. ctx.restore();
  15924. if (hidden === true) {
  15925. ctx.clearRect(0, 0, w, h);
  15926. }
  15927. }
  15928. /**
  15929. * Set the translation of the network
  15930. * @param {Number} offsetX Horizontal offset
  15931. * @param {Number} offsetY Vertical offset
  15932. * @private
  15933. */
  15934. Network.prototype._setTranslation = function(offsetX, offsetY) {
  15935. if (this.translation === undefined) {
  15936. this.translation = {
  15937. x: 0,
  15938. y: 0
  15939. };
  15940. }
  15941. if (offsetX !== undefined) {
  15942. this.translation.x = offsetX;
  15943. }
  15944. if (offsetY !== undefined) {
  15945. this.translation.y = offsetY;
  15946. }
  15947. this.emit('viewChanged');
  15948. };
  15949. /**
  15950. * Get the translation of the network
  15951. * @return {Object} translation An object with parameters x and y, both a number
  15952. * @private
  15953. */
  15954. Network.prototype._getTranslation = function() {
  15955. return {
  15956. x: this.translation.x,
  15957. y: this.translation.y
  15958. };
  15959. };
  15960. /**
  15961. * Scale the network
  15962. * @param {Number} scale Scaling factor 1.0 is unscaled
  15963. * @private
  15964. */
  15965. Network.prototype._setScale = function(scale) {
  15966. this.scale = scale;
  15967. };
  15968. /**
  15969. * Get the current scale of the network
  15970. * @return {Number} scale Scaling factor 1.0 is unscaled
  15971. * @private
  15972. */
  15973. Network.prototype._getScale = function() {
  15974. return this.scale;
  15975. };
  15976. /**
  15977. * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to
  15978. * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  15979. * @param {number} x
  15980. * @returns {number}
  15981. * @private
  15982. */
  15983. Network.prototype._XconvertDOMtoCanvas = function(x) {
  15984. return (x - this.translation.x) / this.scale;
  15985. };
  15986. /**
  15987. * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  15988. * the X coordinate in DOM-space (coordinate point in browser relative to the container div)
  15989. * @param {number} x
  15990. * @returns {number}
  15991. * @private
  15992. */
  15993. Network.prototype._XconvertCanvasToDOM = function(x) {
  15994. return x * this.scale + this.translation.x;
  15995. };
  15996. /**
  15997. * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to
  15998. * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  15999. * @param {number} y
  16000. * @returns {number}
  16001. * @private
  16002. */
  16003. Network.prototype._YconvertDOMtoCanvas = function(y) {
  16004. return (y - this.translation.y) / this.scale;
  16005. };
  16006. /**
  16007. * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  16008. * the Y coordinate in DOM-space (coordinate point in browser relative to the container div)
  16009. * @param {number} y
  16010. * @returns {number}
  16011. * @private
  16012. */
  16013. Network.prototype._YconvertCanvasToDOM = function(y) {
  16014. return y * this.scale + this.translation.y ;
  16015. };
  16016. /**
  16017. *
  16018. * @param {object} pos = {x: number, y: number}
  16019. * @returns {{x: number, y: number}}
  16020. * @constructor
  16021. */
  16022. Network.prototype.canvasToDOM = function (pos) {
  16023. return {x: this._XconvertCanvasToDOM(pos.x), y: this._YconvertCanvasToDOM(pos.y)};
  16024. };
  16025. /**
  16026. *
  16027. * @param {object} pos = {x: number, y: number}
  16028. * @returns {{x: number, y: number}}
  16029. * @constructor
  16030. */
  16031. Network.prototype.DOMtoCanvas = function (pos) {
  16032. return {x: this._XconvertDOMtoCanvas(pos.x), y: this._YconvertDOMtoCanvas(pos.y)};
  16033. };
  16034. /**
  16035. * Redraw all nodes
  16036. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  16037. * @param {CanvasRenderingContext2D} ctx
  16038. * @param {Boolean} [alwaysShow]
  16039. * @private
  16040. */
  16041. Network.prototype._drawNodes = function(ctx,alwaysShow) {
  16042. if (alwaysShow === undefined) {
  16043. alwaysShow = false;
  16044. }
  16045. // first draw the unselected nodes
  16046. var nodes = this.nodes;
  16047. var selected = [];
  16048. for (var id in nodes) {
  16049. if (nodes.hasOwnProperty(id)) {
  16050. nodes[id].setScaleAndPos(this.scale,this.canvasTopLeft,this.canvasBottomRight);
  16051. if (nodes[id].isSelected()) {
  16052. selected.push(id);
  16053. }
  16054. else {
  16055. if (nodes[id].inArea() || alwaysShow) {
  16056. nodes[id].draw(ctx);
  16057. }
  16058. }
  16059. }
  16060. }
  16061. // draw the selected nodes on top
  16062. for (var s = 0, sMax = selected.length; s < sMax; s++) {
  16063. if (nodes[selected[s]].inArea() || alwaysShow) {
  16064. nodes[selected[s]].draw(ctx);
  16065. }
  16066. }
  16067. };
  16068. /**
  16069. * Redraw all edges
  16070. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  16071. * @param {CanvasRenderingContext2D} ctx
  16072. * @private
  16073. */
  16074. Network.prototype._drawEdges = function(ctx) {
  16075. var edges = this.edges;
  16076. for (var id in edges) {
  16077. if (edges.hasOwnProperty(id)) {
  16078. var edge = edges[id];
  16079. edge.setScale(this.scale);
  16080. if (edge.connected) {
  16081. edges[id].draw(ctx);
  16082. }
  16083. }
  16084. }
  16085. };
  16086. /**
  16087. * Redraw all edges
  16088. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  16089. * @param {CanvasRenderingContext2D} ctx
  16090. * @private
  16091. */
  16092. Network.prototype._drawControlNodes = function(ctx) {
  16093. var edges = this.edges;
  16094. for (var id in edges) {
  16095. if (edges.hasOwnProperty(id)) {
  16096. edges[id]._drawControlNodes(ctx);
  16097. }
  16098. }
  16099. };
  16100. /**
  16101. * Find a stable position for all nodes
  16102. * @private
  16103. */
  16104. Network.prototype._stabilize = function() {
  16105. if (this.constants.freezeForStabilization == true) {
  16106. this._freezeDefinedNodes();
  16107. }
  16108. // find stable position
  16109. var count = 0;
  16110. while (this.moving && count < this.constants.stabilizationIterations) {
  16111. this._physicsTick();
  16112. count++;
  16113. }
  16114. if (this.constants.zoomExtentOnStabilize == true) {
  16115. this.zoomExtent({duration:0}, false, true);
  16116. }
  16117. if (this.constants.freezeForStabilization == true) {
  16118. this._restoreFrozenNodes();
  16119. }
  16120. this.emit("stabilizationIterationsDone");
  16121. };
  16122. /**
  16123. * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization
  16124. * because only the supportnodes for the smoothCurves have to settle.
  16125. *
  16126. * @private
  16127. */
  16128. Network.prototype._freezeDefinedNodes = function() {
  16129. var nodes = this.nodes;
  16130. for (var id in nodes) {
  16131. if (nodes.hasOwnProperty(id)) {
  16132. if (nodes[id].x != null && nodes[id].y != null) {
  16133. nodes[id].fixedData.x = nodes[id].xFixed;
  16134. nodes[id].fixedData.y = nodes[id].yFixed;
  16135. nodes[id].xFixed = true;
  16136. nodes[id].yFixed = true;
  16137. }
  16138. }
  16139. }
  16140. };
  16141. /**
  16142. * Unfreezes the nodes that have been frozen by _freezeDefinedNodes.
  16143. *
  16144. * @private
  16145. */
  16146. Network.prototype._restoreFrozenNodes = function() {
  16147. var nodes = this.nodes;
  16148. for (var id in nodes) {
  16149. if (nodes.hasOwnProperty(id)) {
  16150. if (nodes[id].fixedData.x != null) {
  16151. nodes[id].xFixed = nodes[id].fixedData.x;
  16152. nodes[id].yFixed = nodes[id].fixedData.y;
  16153. }
  16154. }
  16155. }
  16156. };
  16157. /**
  16158. * Check if any of the nodes is still moving
  16159. * @param {number} vmin the minimum velocity considered as 'moving'
  16160. * @return {boolean} true if moving, false if non of the nodes is moving
  16161. * @private
  16162. */
  16163. Network.prototype._isMoving = function(vmin) {
  16164. var nodes = this.nodes;
  16165. for (var id in nodes) {
  16166. if (nodes[id] !== undefined) {
  16167. if (nodes[id].isMoving(vmin) == true) {
  16168. return true;
  16169. }
  16170. }
  16171. }
  16172. return false;
  16173. };
  16174. /**
  16175. * /**
  16176. * Perform one discrete step for all nodes
  16177. *
  16178. * @private
  16179. */
  16180. Network.prototype._discreteStepNodes = function() {
  16181. var interval = this.physicsDiscreteStepsize;
  16182. var nodes = this.nodes;
  16183. var nodeId;
  16184. var nodesPresent = false;
  16185. if (this.constants.maxVelocity > 0) {
  16186. for (nodeId in nodes) {
  16187. if (nodes.hasOwnProperty(nodeId)) {
  16188. nodes[nodeId].discreteStepLimited(interval, this.constants.maxVelocity);
  16189. nodesPresent = true;
  16190. }
  16191. }
  16192. }
  16193. else {
  16194. for (nodeId in nodes) {
  16195. if (nodes.hasOwnProperty(nodeId)) {
  16196. nodes[nodeId].discreteStep(interval);
  16197. nodesPresent = true;
  16198. }
  16199. }
  16200. }
  16201. if (nodesPresent == true) {
  16202. var vminCorrected = this.constants.minVelocity / Math.max(this.scale,0.05);
  16203. if (vminCorrected > 0.5*this.constants.maxVelocity) {
  16204. return true;
  16205. }
  16206. else {
  16207. return this._isMoving(vminCorrected);
  16208. }
  16209. }
  16210. return false;
  16211. };
  16212. Network.prototype._revertPhysicsState = function() {
  16213. var nodes = this.nodes;
  16214. for (var nodeId in nodes) {
  16215. if (nodes.hasOwnProperty(nodeId)) {
  16216. nodes[nodeId].revertPosition();
  16217. }
  16218. }
  16219. }
  16220. Network.prototype._revertPhysicsTick = function() {
  16221. this._doInAllActiveSectors("_revertPhysicsState");
  16222. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  16223. this._doInSupportSector("_revertPhysicsState");
  16224. }
  16225. }
  16226. /**
  16227. * A single simulation step (or "tick") in the physics simulation
  16228. *
  16229. * @private
  16230. */
  16231. Network.prototype._physicsTick = function() {
  16232. if (!this.freezeSimulationEnabled) {
  16233. if (this.moving == true) {
  16234. var mainMovingStatus = false;
  16235. var supportMovingStatus = false;
  16236. this._doInAllActiveSectors("_initializeForceCalculation");
  16237. var mainMoving = this._doInAllActiveSectors("_discreteStepNodes");
  16238. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  16239. supportMovingStatus = this._doInSupportSector("_discreteStepNodes");
  16240. }
  16241. // gather movement data from all sectors, if one moves, we are NOT stabilzied
  16242. for (var i = 0; i < mainMoving.length; i++) {
  16243. mainMovingStatus = mainMoving[i] || mainMovingStatus;
  16244. }
  16245. // determine if the network has stabilzied
  16246. this.moving = mainMovingStatus || supportMovingStatus;
  16247. if (this.moving == false) {
  16248. this._revertPhysicsTick();
  16249. }
  16250. else {
  16251. // this is here to ensure that there is no start event when the network is already stable.
  16252. if (this.startedStabilization == false) {
  16253. this.emit("startStabilization");
  16254. this.startedStabilization = true;
  16255. }
  16256. }
  16257. this.stabilizationIterations++;
  16258. }
  16259. }
  16260. };
  16261. /**
  16262. * This function runs one step of the animation. It calls an x amount of physics ticks and one render tick.
  16263. * It reschedules itself at the beginning of the function
  16264. *
  16265. * @private
  16266. */
  16267. Network.prototype._animationStep = function() {
  16268. // reset the timer so a new scheduled animation step can be set
  16269. this.timer = undefined;
  16270. if (this.requiresTimeout == true) {
  16271. // this schedules a new animation step
  16272. this.start();
  16273. }
  16274. // handle the keyboad movement
  16275. this._handleNavigation();
  16276. // check if the physics have settled
  16277. if (this.moving == true) {
  16278. var startTime = Date.now();
  16279. this._physicsTick();
  16280. var physicsTime = Date.now() - startTime;
  16281. // run double speed if it is a little graph
  16282. if ((this.renderTimestep - this.renderTime > 2 * physicsTime || this.runDoubleSpeed == true) && this.moving == true) {
  16283. this._physicsTick();
  16284. // this makes sure there is no jitter. The decision is taken once to run it at double speed.
  16285. if (this.renderTime != 0) {
  16286. this.runDoubleSpeed = true
  16287. }
  16288. }
  16289. }
  16290. var renderStartTime = Date.now();
  16291. this._redraw();
  16292. this.renderTime = Date.now() - renderStartTime;
  16293. if (this.requiresTimeout == false) {
  16294. // this schedules a new animation step
  16295. this.start();
  16296. }
  16297. };
  16298. if (typeof window !== 'undefined') {
  16299. window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
  16300. window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  16301. }
  16302. /**
  16303. * Schedule a animation step with the refreshrate interval.
  16304. */
  16305. Network.prototype.start = function() {
  16306. if (this.freezeSimulationEnabled == true) {
  16307. this.moving = false;
  16308. }
  16309. if (this.moving == true || this.xIncrement != 0 || this.yIncrement != 0 || this.zoomIncrement != 0 || this.animating == true) {
  16310. if (!this.timer) {
  16311. if (this.requiresTimeout == true) {
  16312. this.timer = window.setTimeout(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
  16313. }
  16314. else {
  16315. this.timer = window.requestAnimationFrame(this._animationStep.bind(this)); // wait this.renderTimeStep milliseconds and perform the animation step function
  16316. }
  16317. }
  16318. }
  16319. else {
  16320. this._requestRedraw();
  16321. // this check is to ensure that the network does not emit these events if it was already stabilized and setOptions is called (setting moving to true and calling start())
  16322. if (this.stabilizationIterations > 1) {
  16323. // trigger the "stabilized" event.
  16324. // The event is triggered on the next tick, to prevent the case that
  16325. // it is fired while initializing the Network, in which case you would not
  16326. // be able to catch it
  16327. var me = this;
  16328. var params = {
  16329. iterations: me.stabilizationIterations
  16330. };
  16331. this.stabilizationIterations = 0;
  16332. this.startedStabilization = false;
  16333. setTimeout(function () {
  16334. me.emit("stabilized", params);
  16335. }, 0);
  16336. }
  16337. else {
  16338. this.stabilizationIterations = 0;
  16339. }
  16340. }
  16341. };
  16342. /**
  16343. * Move the network according to the keyboard presses.
  16344. *
  16345. * @private
  16346. */
  16347. Network.prototype._handleNavigation = function() {
  16348. if (this.xIncrement != 0 || this.yIncrement != 0) {
  16349. var translation = this._getTranslation();
  16350. this._setTranslation(translation.x+this.xIncrement, translation.y+this.yIncrement);
  16351. }
  16352. if (this.zoomIncrement != 0) {
  16353. var center = {
  16354. x: this.frame.canvas.clientWidth / 2,
  16355. y: this.frame.canvas.clientHeight / 2
  16356. };
  16357. this._zoom(this.scale*(1 + this.zoomIncrement), center);
  16358. }
  16359. };
  16360. /**
  16361. * Freeze the _animationStep
  16362. */
  16363. Network.prototype.freezeSimulation = function(freeze) {
  16364. if (freeze == true) {
  16365. this.freezeSimulationEnabled = true;
  16366. this.moving = false;
  16367. }
  16368. else {
  16369. this.freezeSimulationEnabled = false;
  16370. this.moving = true;
  16371. this.start();
  16372. }
  16373. };
  16374. /**
  16375. * This function cleans the support nodes if they are not needed and adds them when they are.
  16376. *
  16377. * @param {boolean} [disableStart]
  16378. * @private
  16379. */
  16380. Network.prototype._configureSmoothCurves = function(disableStart) {
  16381. if (disableStart === undefined) {
  16382. disableStart = true;
  16383. }
  16384. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  16385. this._createBezierNodes();
  16386. // cleanup unused support nodes
  16387. for (var nodeId in this.sectors['support']['nodes']) {
  16388. if (this.sectors['support']['nodes'].hasOwnProperty(nodeId)) {
  16389. if (this.edges[this.sectors['support']['nodes'][nodeId].parentEdgeId] === undefined) {
  16390. delete this.sectors['support']['nodes'][nodeId];
  16391. }
  16392. }
  16393. }
  16394. }
  16395. else {
  16396. // delete the support nodes
  16397. this.sectors['support']['nodes'] = {};
  16398. for (var edgeId in this.edges) {
  16399. if (this.edges.hasOwnProperty(edgeId)) {
  16400. this.edges[edgeId].via = null;
  16401. }
  16402. }
  16403. }
  16404. this._updateCalculationNodes();
  16405. if (!disableStart) {
  16406. this.moving = true;
  16407. this.start();
  16408. }
  16409. };
  16410. /**
  16411. * Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
  16412. * are used for the force calculation.
  16413. *
  16414. * @private
  16415. */
  16416. Network.prototype._createBezierNodes = function() {
  16417. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  16418. for (var edgeId in this.edges) {
  16419. if (this.edges.hasOwnProperty(edgeId)) {
  16420. var edge = this.edges[edgeId];
  16421. if (edge.via == null) {
  16422. var nodeId = "edgeId:".concat(edge.id);
  16423. this.sectors['support']['nodes'][nodeId] = new Node(
  16424. {id:nodeId,
  16425. mass:1,
  16426. shape:'circle',
  16427. image:"",
  16428. internalMultiplier:1
  16429. },{},{},this.constants);
  16430. edge.via = this.sectors['support']['nodes'][nodeId];
  16431. edge.via.parentEdgeId = edge.id;
  16432. edge.positionBezierNode();
  16433. }
  16434. }
  16435. }
  16436. }
  16437. };
  16438. /**
  16439. * load the functions that load the mixins into the prototype.
  16440. *
  16441. * @private
  16442. */
  16443. Network.prototype._initializeMixinLoaders = function () {
  16444. for (var mixin in MixinLoader) {
  16445. if (MixinLoader.hasOwnProperty(mixin)) {
  16446. Network.prototype[mixin] = MixinLoader[mixin];
  16447. }
  16448. }
  16449. };
  16450. /**
  16451. * Load the XY positions of the nodes into the dataset.
  16452. */
  16453. Network.prototype.storePosition = function() {
  16454. console.log("storePosition is depricated: use .storePositions() from now on.")
  16455. this.storePositions();
  16456. };
  16457. /**
  16458. * Load the XY positions of the nodes into the dataset.
  16459. */
  16460. Network.prototype.storePositions = function() {
  16461. var dataArray = [];
  16462. for (var nodeId in this.nodes) {
  16463. if (this.nodes.hasOwnProperty(nodeId)) {
  16464. var node = this.nodes[nodeId];
  16465. var allowedToMoveX = !this.nodes.xFixed;
  16466. var allowedToMoveY = !this.nodes.yFixed;
  16467. if (this.nodesData._data[nodeId].x != Math.round(node.x) || this.nodesData._data[nodeId].y != Math.round(node.y)) {
  16468. dataArray.push({id:nodeId,x:Math.round(node.x),y:Math.round(node.y),allowedToMoveX:allowedToMoveX,allowedToMoveY:allowedToMoveY});
  16469. }
  16470. }
  16471. }
  16472. this.nodesData.update(dataArray);
  16473. };
  16474. /**
  16475. * Return the positions of the nodes.
  16476. */
  16477. Network.prototype.getPositions = function(ids) {
  16478. var dataArray = {};
  16479. if (ids !== undefined) {
  16480. if (Array.isArray(ids) == true) {
  16481. for (var i = 0; i < ids.length; i++) {
  16482. if (this.nodes[ids[i]] !== undefined) {
  16483. var node = this.nodes[ids[i]];
  16484. dataArray[ids[i]] = {x: Math.round(node.x), y: Math.round(node.y)};
  16485. }
  16486. }
  16487. }
  16488. else {
  16489. if (this.nodes[ids] !== undefined) {
  16490. var node = this.nodes[ids];
  16491. dataArray[ids] = {x: Math.round(node.x), y: Math.round(node.y)};
  16492. }
  16493. }
  16494. }
  16495. else {
  16496. for (var nodeId in this.nodes) {
  16497. if (this.nodes.hasOwnProperty(nodeId)) {
  16498. var node = this.nodes[nodeId];
  16499. dataArray[nodeId] = {x: Math.round(node.x), y: Math.round(node.y)};
  16500. }
  16501. }
  16502. }
  16503. return dataArray;
  16504. };
  16505. /**
  16506. * Center a node in view.
  16507. *
  16508. * @param {Number} nodeId
  16509. * @param {Number} [options]
  16510. */
  16511. Network.prototype.focusOnNode = function (nodeId, options) {
  16512. if (this.nodes.hasOwnProperty(nodeId)) {
  16513. if (options === undefined) {
  16514. options = {};
  16515. }
  16516. var nodePosition = {x: this.nodes[nodeId].x, y: this.nodes[nodeId].y};
  16517. options.position = nodePosition;
  16518. options.lockedOnNode = nodeId;
  16519. this.moveTo(options)
  16520. }
  16521. else {
  16522. console.log("This nodeId cannot be found.");
  16523. }
  16524. };
  16525. /**
  16526. *
  16527. * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels
  16528. * | options.scale = Number // scale to move to
  16529. * | options.position = {x:Number, y:Number} // position to move to
  16530. * | options.animation = {duration:Number, easingFunction:String} || Boolean // position to move to
  16531. */
  16532. Network.prototype.moveTo = function (options) {
  16533. if (options === undefined) {
  16534. options = {};
  16535. return;
  16536. }
  16537. if (options.offset === undefined) {options.offset = {x: 0, y: 0}; }
  16538. if (options.offset.x === undefined) {options.offset.x = 0; }
  16539. if (options.offset.y === undefined) {options.offset.y = 0; }
  16540. if (options.scale === undefined) {options.scale = this._getScale(); }
  16541. if (options.position === undefined) {options.position = this._getTranslation();}
  16542. if (options.animation === undefined) {options.animation = {duration:0}; }
  16543. if (options.animation === false ) {options.animation = {duration:0}; }
  16544. if (options.animation === true ) {options.animation = {}; }
  16545. if (options.animation.duration === undefined) {options.animation.duration = 1000; } // default duration
  16546. if (options.animation.easingFunction === undefined) {options.animation.easingFunction = "easeInOutQuad"; } // default easing function
  16547. this.animateView(options);
  16548. };
  16549. /**
  16550. *
  16551. * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels
  16552. * | options.time = Number // animation time in milliseconds
  16553. * | options.scale = Number // scale to animate to
  16554. * | options.position = {x:Number, y:Number} // position to animate to
  16555. * | options.easingFunction = String // linear, easeInQuad, easeOutQuad, easeInOutQuad,
  16556. * // easeInCubic, easeOutCubic, easeInOutCubic,
  16557. * // easeInQuart, easeOutQuart, easeInOutQuart,
  16558. * // easeInQuint, easeOutQuint, easeInOutQuint
  16559. */
  16560. Network.prototype.animateView = function (options) {
  16561. if (options === undefined) {
  16562. options = {};
  16563. return;
  16564. }
  16565. // release if something focussed on the node
  16566. this.releaseNode();
  16567. if (options.locked == true) {
  16568. this.lockedOnNodeId = options.lockedOnNode;
  16569. this.lockedOnNodeOffset = options.offset;
  16570. }
  16571. // forcefully complete the old animation if it was still running
  16572. if (this.easingTime != 0) {
  16573. this._transitionRedraw(1); // by setting easingtime to 1, we finish the animation.
  16574. }
  16575. this.sourceScale = this._getScale();
  16576. this.sourceTranslation = this._getTranslation();
  16577. this.targetScale = options.scale;
  16578. // set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw
  16579. // but at least then we'll have the target transition
  16580. this._setScale(this.targetScale);
  16581. var viewCenter = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  16582. var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
  16583. x: viewCenter.x - options.position.x,
  16584. y: viewCenter.y - options.position.y
  16585. };
  16586. this.targetTranslation = {
  16587. x: this.sourceTranslation.x + distanceFromCenter.x * this.targetScale + options.offset.x,
  16588. y: this.sourceTranslation.y + distanceFromCenter.y * this.targetScale + options.offset.y
  16589. };
  16590. // if the time is set to 0, don't do an animation
  16591. if (options.animation.duration == 0) {
  16592. if (this.lockedOnNodeId != null) {
  16593. this._classicRedraw = this._redraw;
  16594. this._redraw = this._lockedRedraw;
  16595. }
  16596. else {
  16597. this._setScale(this.targetScale);
  16598. this._setTranslation(this.targetTranslation.x, this.targetTranslation.y);
  16599. this._redraw();
  16600. }
  16601. }
  16602. else {
  16603. this.animating = true;
  16604. this.animationSpeed = 1 / (this.renderRefreshRate * options.animation.duration * 0.001) || 1 / this.renderRefreshRate;
  16605. this.animationEasingFunction = options.animation.easingFunction;
  16606. this._classicRedraw = this._redraw;
  16607. this._redraw = this._transitionRedraw;
  16608. this._redraw();
  16609. this.start();
  16610. }
  16611. };
  16612. /**
  16613. * used to animate smoothly by hijacking the redraw function.
  16614. * @private
  16615. */
  16616. Network.prototype._lockedRedraw = function () {
  16617. var nodePosition = {x: this.nodes[this.lockedOnNodeId].x, y: this.nodes[this.lockedOnNodeId].y};
  16618. var viewCenter = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  16619. var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
  16620. x: viewCenter.x - nodePosition.x,
  16621. y: viewCenter.y - nodePosition.y
  16622. };
  16623. var sourceTranslation = this._getTranslation();
  16624. var targetTranslation = {
  16625. x: sourceTranslation.x + distanceFromCenter.x * this.scale + this.lockedOnNodeOffset.x,
  16626. y: sourceTranslation.y + distanceFromCenter.y * this.scale + this.lockedOnNodeOffset.y
  16627. };
  16628. this._setTranslation(targetTranslation.x,targetTranslation.y);
  16629. this._classicRedraw();
  16630. }
  16631. Network.prototype.releaseNode = function () {
  16632. if (this.lockedOnNodeId != null) {
  16633. this._redraw = this._classicRedraw;
  16634. this.lockedOnNodeId = null;
  16635. this.lockedOnNodeOffset = null;
  16636. }
  16637. }
  16638. /**
  16639. *
  16640. * @param easingTime
  16641. * @private
  16642. */
  16643. Network.prototype._transitionRedraw = function (easingTime) {
  16644. this.easingTime = easingTime || this.easingTime + this.animationSpeed;
  16645. this.easingTime += this.animationSpeed;
  16646. var progress = util.easingFunctions[this.animationEasingFunction](this.easingTime);
  16647. this._setScale(this.sourceScale + (this.targetScale - this.sourceScale) * progress);
  16648. this._setTranslation(
  16649. this.sourceTranslation.x + (this.targetTranslation.x - this.sourceTranslation.x) * progress,
  16650. this.sourceTranslation.y + (this.targetTranslation.y - this.sourceTranslation.y) * progress
  16651. );
  16652. this._classicRedraw();
  16653. // cleanup
  16654. if (this.easingTime >= 1.0) {
  16655. this.animating = false;
  16656. this.easingTime = 0;
  16657. if (this.lockedOnNodeId != null) {
  16658. this._redraw = this._lockedRedraw;
  16659. }
  16660. else {
  16661. this._redraw = this._classicRedraw;
  16662. }
  16663. this.emit("animationFinished");
  16664. }
  16665. };
  16666. Network.prototype._classicRedraw = function () {
  16667. // placeholder function to be overloaded by animations;
  16668. };
  16669. /**
  16670. * Returns true when the Network is active.
  16671. * @returns {boolean}
  16672. */
  16673. Network.prototype.isActive = function () {
  16674. return !this.activator || this.activator.active;
  16675. };
  16676. /**
  16677. * Sets the scale
  16678. * @returns {Number}
  16679. */
  16680. Network.prototype.setScale = function () {
  16681. return this._setScale();
  16682. };
  16683. /**
  16684. * Returns the scale
  16685. * @returns {Number}
  16686. */
  16687. Network.prototype.getScale = function () {
  16688. return this._getScale();
  16689. };
  16690. /**
  16691. * Returns the scale
  16692. * @returns {Number}
  16693. */
  16694. Network.prototype.getCenterCoordinates = function () {
  16695. return this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  16696. };
  16697. Network.prototype.getBoundingBox = function(nodeId) {
  16698. if (this.nodes[nodeId] !== undefined) {
  16699. return this.nodes[nodeId].boundingBox;
  16700. }
  16701. }
  16702. Network.prototype.getConnectedNodes = function(nodeId) {
  16703. var nodeList = [];
  16704. if (this.nodes[nodeId] !== undefined) {
  16705. var node = this.nodes[nodeId];
  16706. var nodeObj = {nodeId : true}; // used to quickly check if node already exists
  16707. for (var i = 0; i < node.edges.length; i++) {
  16708. var edge = node.edges[i];
  16709. if (edge.toId == nodeId) {
  16710. if (nodeObj[edge.fromId] === undefined) {
  16711. nodeList.push(edge.fromId);
  16712. nodeObj[edge.fromId] = true;
  16713. }
  16714. }
  16715. else if (edge.fromId == nodeId) {
  16716. if (nodeObj[edge.toId] === undefined) {
  16717. nodeList.push(edge.toId)
  16718. nodeObj[edge.toId] = true;
  16719. }
  16720. }
  16721. }
  16722. }
  16723. return nodeList;
  16724. }
  16725. Network.prototype.getEdgesFromNode = function(nodeId) {
  16726. var edgesList = [];
  16727. if (this.nodes[nodeId] !== undefined) {
  16728. var node = this.nodes[nodeId];
  16729. for (var i = 0; i < node.edges.length; i++) {
  16730. edgesList.push(node.edges[i].id);
  16731. }
  16732. }
  16733. return edgesList;
  16734. }
  16735. Network.prototype.generateColorObject = function(color) {
  16736. return util.parseColor(color);
  16737. }
  16738. module.exports = Network;
  16739. /***/ },
  16740. /* 37 */
  16741. /***/ function(module, exports, __webpack_require__) {
  16742. var util = __webpack_require__(1);
  16743. var Node = __webpack_require__(40);
  16744. /**
  16745. * @class Edge
  16746. *
  16747. * A edge connects two nodes
  16748. * @param {Object} properties Object with properties. Must contain
  16749. * At least properties from and to.
  16750. * Available properties: from (number),
  16751. * to (number), label (string, color (string),
  16752. * width (number), style (string),
  16753. * length (number), title (string)
  16754. * @param {Network} network A Network object, used to find and edge to
  16755. * nodes.
  16756. * @param {Object} constants An object with default values for
  16757. * example for the color
  16758. */
  16759. function Edge (properties, network, networkConstants) {
  16760. if (!network) {
  16761. throw "No network provided";
  16762. }
  16763. var fields = ['edges','physics'];
  16764. var constants = util.selectiveBridgeObject(fields,networkConstants);
  16765. this.options = constants.edges;
  16766. this.physics = constants.physics;
  16767. this.options['smoothCurves'] = networkConstants['smoothCurves'];
  16768. this.network = network;
  16769. // initialize variables
  16770. this.id = undefined;
  16771. this.fromId = undefined;
  16772. this.toId = undefined;
  16773. this.title = undefined;
  16774. this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
  16775. this.value = undefined;
  16776. this.selected = false;
  16777. this.hover = false;
  16778. this.labelDimensions = {top:0,left:0,width:0,height:0,yLine:0}; // could be cached
  16779. this.dirtyLabel = true;
  16780. this.colorDirty = true;
  16781. this.from = null; // a node
  16782. this.to = null; // a node
  16783. this.via = null; // a temp node
  16784. this.fromBackup = null; // used to clean up after reconnect
  16785. this.toBackup = null;; // used to clean up after reconnect
  16786. // we use this to be able to reconnect the edge to a cluster if its node is put into a cluster
  16787. // by storing the original information we can revert to the original connection when the cluser is opened.
  16788. this.originalFromId = [];
  16789. this.originalToId = [];
  16790. this.connected = false;
  16791. this.widthFixed = false;
  16792. this.lengthFixed = false;
  16793. this.setProperties(properties);
  16794. this.controlNodesEnabled = false;
  16795. this.controlNodes = {from:null, to:null, positions:{}};
  16796. this.connectedNode = null;
  16797. }
  16798. /**
  16799. * Set or overwrite properties for the edge
  16800. * @param {Object} properties an object with properties
  16801. * @param {Object} constants and object with default, global properties
  16802. */
  16803. Edge.prototype.setProperties = function(properties) {
  16804. this.colorDirty = true;
  16805. if (!properties) {
  16806. return;
  16807. }
  16808. var fields = ['style','fontSize','fontFace','fontColor','fontFill','fontStrokeWidth','fontStrokeColor','width',
  16809. 'widthSelectionMultiplier','hoverWidth','arrowScaleFactor','dash','inheritColor','labelAlignment', 'opacity',
  16810. 'customScalingFunction','useGradients'
  16811. ];
  16812. util.selectiveDeepExtend(fields, this.options, properties);
  16813. if (properties.from !== undefined) {this.fromId = properties.from;}
  16814. if (properties.to !== undefined) {this.toId = properties.to;}
  16815. if (properties.id !== undefined) {this.id = properties.id;}
  16816. if (properties.label !== undefined) {this.label = properties.label; this.dirtyLabel = true;}
  16817. if (properties.title !== undefined) {this.title = properties.title;}
  16818. if (properties.value !== undefined) {this.value = properties.value;}
  16819. if (properties.length !== undefined) {this.physics.springLength = properties.length;}
  16820. if (properties.color !== undefined) {
  16821. this.options.inheritColor = false;
  16822. if (util.isString(properties.color)) {
  16823. this.options.color.color = properties.color;
  16824. this.options.color.highlight = properties.color;
  16825. }
  16826. else {
  16827. if (properties.color.color !== undefined) {this.options.color.color = properties.color.color;}
  16828. if (properties.color.highlight !== undefined) {this.options.color.highlight = properties.color.highlight;}
  16829. if (properties.color.hover !== undefined) {this.options.color.hover = properties.color.hover;}
  16830. }
  16831. }
  16832. // A node is connected when it has a from and to node.
  16833. this.connect();
  16834. this.widthFixed = this.widthFixed || (properties.width !== undefined);
  16835. this.lengthFixed = this.lengthFixed || (properties.length !== undefined);
  16836. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  16837. // set draw method based on style
  16838. switch (this.options.style) {
  16839. case 'line': this.draw = this._drawLine; break;
  16840. case 'arrow': this.draw = this._drawArrow; break;
  16841. case 'arrow-center': this.draw = this._drawArrowCenter; break;
  16842. case 'dash-line': this.draw = this._drawDashLine; break;
  16843. default: this.draw = this._drawLine; break;
  16844. }
  16845. };
  16846. /**
  16847. * Connect an edge to its nodes
  16848. */
  16849. Edge.prototype.connect = function () {
  16850. this.disconnect();
  16851. this.from = this.network.nodes[this.fromId] || null;
  16852. this.to = this.network.nodes[this.toId] || null;
  16853. this.connected = (this.from && this.to);
  16854. if (this.connected) {
  16855. this.from.attachEdge(this);
  16856. this.to.attachEdge(this);
  16857. }
  16858. else {
  16859. if (this.from) {
  16860. this.from.detachEdge(this);
  16861. }
  16862. if (this.to) {
  16863. this.to.detachEdge(this);
  16864. }
  16865. }
  16866. };
  16867. /**
  16868. * Disconnect an edge from its nodes
  16869. */
  16870. Edge.prototype.disconnect = function () {
  16871. if (this.from) {
  16872. this.from.detachEdge(this);
  16873. this.from = null;
  16874. }
  16875. if (this.to) {
  16876. this.to.detachEdge(this);
  16877. this.to = null;
  16878. }
  16879. this.connected = false;
  16880. };
  16881. /**
  16882. * get the title of this edge.
  16883. * @return {string} title The title of the edge, or undefined when no title
  16884. * has been set.
  16885. */
  16886. Edge.prototype.getTitle = function() {
  16887. return typeof this.title === "function" ? this.title() : this.title;
  16888. };
  16889. /**
  16890. * Retrieve the value of the edge. Can be undefined
  16891. * @return {Number} value
  16892. */
  16893. Edge.prototype.getValue = function() {
  16894. return this.value;
  16895. };
  16896. /**
  16897. * Adjust the value range of the edge. The edge will adjust it's width
  16898. * based on its value.
  16899. * @param {Number} min
  16900. * @param {Number} max
  16901. */
  16902. Edge.prototype.setValueRange = function(min, max, total) {
  16903. if (!this.widthFixed && this.value !== undefined) {
  16904. var scale = this.options.customScalingFunction(min, max, total, this.value);
  16905. var widthDiff = this.options.widthMax - this.options.widthMin;
  16906. this.options.width = this.options.widthMin + scale * widthDiff;
  16907. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  16908. }
  16909. };
  16910. /**
  16911. * Redraw a edge
  16912. * Draw this edge in the given canvas
  16913. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  16914. * @param {CanvasRenderingContext2D} ctx
  16915. */
  16916. Edge.prototype.draw = function(ctx) {
  16917. throw "Method draw not initialized in edge";
  16918. };
  16919. /**
  16920. * Check if this object is overlapping with the provided object
  16921. * @param {Object} obj an object with parameters left, top
  16922. * @return {boolean} True if location is located on the edge
  16923. */
  16924. Edge.prototype.isOverlappingWith = function(obj) {
  16925. if (this.connected) {
  16926. var distMax = 10;
  16927. var xFrom = this.from.x;
  16928. var yFrom = this.from.y;
  16929. var xTo = this.to.x;
  16930. var yTo = this.to.y;
  16931. var xObj = obj.left;
  16932. var yObj = obj.top;
  16933. var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  16934. return (dist < distMax);
  16935. }
  16936. else {
  16937. return false
  16938. }
  16939. };
  16940. Edge.prototype._getColor = function(ctx) {
  16941. var colorObj = this.options.color;
  16942. if (this.options.useGradients == true) {
  16943. var grd = ctx.createLinearGradient(this.from.x, this.from.y, this.to.x, this.to.y);
  16944. var fromColor, toColor;
  16945. fromColor = this.from.options.color.highlight.border;
  16946. toColor = this.to.options.color.highlight.border;
  16947. if (this.from.selected == false && this.to.selected == false) {
  16948. fromColor = util.overrideOpacity(this.from.options.color.border, this.options.opacity);
  16949. toColor = util.overrideOpacity(this.to.options.color.border, this.options.opacity);
  16950. }
  16951. else if (this.from.selected == true && this.to.selected == false) {
  16952. toColor = this.to.options.color.border;
  16953. }
  16954. else if (this.from.selected == false && this.to.selected == true) {
  16955. fromColor = this.from.options.color.border;
  16956. }
  16957. grd.addColorStop(0, fromColor);
  16958. grd.addColorStop(1, toColor);
  16959. return grd;
  16960. }
  16961. if (this.colorDirty === true) {
  16962. if (this.options.inheritColor == "to") {
  16963. colorObj = {
  16964. highlight: this.to.options.color.highlight.border,
  16965. hover: this.to.options.color.hover.border,
  16966. color: util.overrideOpacity(this.from.options.color.border, this.options.opacity)
  16967. };
  16968. }
  16969. else if (this.options.inheritColor == "from" || this.options.inheritColor == true) {
  16970. colorObj = {
  16971. highlight: this.from.options.color.highlight.border,
  16972. hover: this.from.options.color.hover.border,
  16973. color: util.overrideOpacity(this.from.options.color.border, this.options.opacity)
  16974. };
  16975. }
  16976. this.options.color = colorObj;
  16977. this.colorDirty = false;
  16978. }
  16979. if (this.selected == true) {return colorObj.highlight;}
  16980. else if (this.hover == true) {return colorObj.hover;}
  16981. else {return colorObj.color;}
  16982. };
  16983. /**
  16984. * Redraw a edge as a line
  16985. * Draw this edge in the given canvas
  16986. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  16987. * @param {CanvasRenderingContext2D} ctx
  16988. * @private
  16989. */
  16990. Edge.prototype._drawLine = function(ctx) {
  16991. // set style
  16992. ctx.strokeStyle = this._getColor(ctx);
  16993. ctx.lineWidth = this._getLineWidth();
  16994. if (this.from != this.to) {
  16995. // draw line
  16996. var via = this._line(ctx);
  16997. // draw label
  16998. var point;
  16999. if (this.label) {
  17000. if (this.options.smoothCurves.enabled == true && via != null) {
  17001. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  17002. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  17003. point = {x:midpointX, y:midpointY};
  17004. }
  17005. else {
  17006. point = this._pointOnLine(0.5);
  17007. }
  17008. this._label(ctx, this.label, point.x, point.y);
  17009. }
  17010. }
  17011. else {
  17012. var x, y;
  17013. var radius = this.physics.springLength / 4;
  17014. var node = this.from;
  17015. if (!node.width) {
  17016. node.resize(ctx);
  17017. }
  17018. if (node.width > node.height) {
  17019. x = node.x + node.width / 2;
  17020. y = node.y - radius;
  17021. }
  17022. else {
  17023. x = node.x + radius;
  17024. y = node.y - node.height / 2;
  17025. }
  17026. this._circle(ctx, x, y, radius);
  17027. point = this._pointOnCircle(x, y, radius, 0.5);
  17028. this._label(ctx, this.label, point.x, point.y);
  17029. }
  17030. };
  17031. /**
  17032. * Get the line width of the edge. Depends on width and whether one of the
  17033. * connected nodes is selected.
  17034. * @return {Number} width
  17035. * @private
  17036. */
  17037. Edge.prototype._getLineWidth = function() {
  17038. if (this.selected == true) {
  17039. return Math.max(Math.min(this.widthSelected, this.options.widthMax), 0.3*this.networkScaleInv);
  17040. }
  17041. else {
  17042. if (this.hover == true) {
  17043. return Math.max(Math.min(this.options.hoverWidth, this.options.widthMax), 0.3*this.networkScaleInv);
  17044. }
  17045. else {
  17046. return Math.max(this.options.width, 0.3*this.networkScaleInv);
  17047. }
  17048. }
  17049. };
  17050. Edge.prototype._getViaCoordinates = function () {
  17051. if (this.options.smoothCurves.dynamic == true && this.options.smoothCurves.enabled == true ) {
  17052. return this.via;
  17053. }
  17054. else if (this.options.smoothCurves.enabled == false) {
  17055. return {x:0,y:0};
  17056. }
  17057. else {
  17058. var xVia = null;
  17059. var yVia = null;
  17060. var factor = this.options.smoothCurves.roundness;
  17061. var type = this.options.smoothCurves.type;
  17062. var dx = Math.abs(this.from.x - this.to.x);
  17063. var dy = Math.abs(this.from.y - this.to.y);
  17064. if (type == 'discrete' || type == 'diagonalCross') {
  17065. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  17066. if (this.from.y > this.to.y) {
  17067. if (this.from.x < this.to.x) {
  17068. xVia = this.from.x + factor * dy;
  17069. yVia = this.from.y - factor * dy;
  17070. }
  17071. else if (this.from.x > this.to.x) {
  17072. xVia = this.from.x - factor * dy;
  17073. yVia = this.from.y - factor * dy;
  17074. }
  17075. }
  17076. else if (this.from.y < this.to.y) {
  17077. if (this.from.x < this.to.x) {
  17078. xVia = this.from.x + factor * dy;
  17079. yVia = this.from.y + factor * dy;
  17080. }
  17081. else if (this.from.x > this.to.x) {
  17082. xVia = this.from.x - factor * dy;
  17083. yVia = this.from.y + factor * dy;
  17084. }
  17085. }
  17086. if (type == "discrete") {
  17087. xVia = dx < factor * dy ? this.from.x : xVia;
  17088. }
  17089. }
  17090. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  17091. if (this.from.y > this.to.y) {
  17092. if (this.from.x < this.to.x) {
  17093. xVia = this.from.x + factor * dx;
  17094. yVia = this.from.y - factor * dx;
  17095. }
  17096. else if (this.from.x > this.to.x) {
  17097. xVia = this.from.x - factor * dx;
  17098. yVia = this.from.y - factor * dx;
  17099. }
  17100. }
  17101. else if (this.from.y < this.to.y) {
  17102. if (this.from.x < this.to.x) {
  17103. xVia = this.from.x + factor * dx;
  17104. yVia = this.from.y + factor * dx;
  17105. }
  17106. else if (this.from.x > this.to.x) {
  17107. xVia = this.from.x - factor * dx;
  17108. yVia = this.from.y + factor * dx;
  17109. }
  17110. }
  17111. if (type == "discrete") {
  17112. yVia = dy < factor * dx ? this.from.y : yVia;
  17113. }
  17114. }
  17115. }
  17116. else if (type == "straightCross") {
  17117. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { // up - down
  17118. xVia = this.from.x;
  17119. if (this.from.y < this.to.y) {
  17120. yVia = this.to.y - (1 - factor) * dy;
  17121. }
  17122. else {
  17123. yVia = this.to.y + (1 - factor) * dy;
  17124. }
  17125. }
  17126. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { // left - right
  17127. if (this.from.x < this.to.x) {
  17128. xVia = this.to.x - (1 - factor) * dx;
  17129. }
  17130. else {
  17131. xVia = this.to.x + (1 - factor) * dx;
  17132. }
  17133. yVia = this.from.y;
  17134. }
  17135. }
  17136. else if (type == 'horizontal') {
  17137. if (this.from.x < this.to.x) {
  17138. xVia = this.to.x - (1 - factor) * dx;
  17139. }
  17140. else {
  17141. xVia = this.to.x + (1 - factor) * dx;
  17142. }
  17143. yVia = this.from.y;
  17144. }
  17145. else if (type == 'vertical') {
  17146. xVia = this.from.x;
  17147. if (this.from.y < this.to.y) {
  17148. yVia = this.to.y - (1 - factor) * dy;
  17149. }
  17150. else {
  17151. yVia = this.to.y + (1 - factor) * dy;
  17152. }
  17153. }
  17154. else if (type == 'curvedCW') {
  17155. var dx = this.to.x - this.from.x;
  17156. var dy = this.from.y - this.to.y;
  17157. var radius = Math.sqrt(dx*dx + dy*dy);
  17158. var pi = Math.PI;
  17159. var originalAngle = Math.atan2(dy,dx);
  17160. var myAngle = (originalAngle + ((factor * 0.5) + 0.5) * pi) % (2 * pi);
  17161. xVia = this.from.x + (factor*0.5 + 0.5)*radius*Math.sin(myAngle);
  17162. yVia = this.from.y + (factor*0.5 + 0.5)*radius*Math.cos(myAngle);
  17163. }
  17164. else if (type == 'curvedCCW') {
  17165. var dx = this.to.x - this.from.x;
  17166. var dy = this.from.y - this.to.y;
  17167. var radius = Math.sqrt(dx*dx + dy*dy);
  17168. var pi = Math.PI;
  17169. var originalAngle = Math.atan2(dy,dx);
  17170. var myAngle = (originalAngle + ((-factor * 0.5) + 0.5) * pi) % (2 * pi);
  17171. xVia = this.from.x + (factor*0.5 + 0.5)*radius*Math.sin(myAngle);
  17172. yVia = this.from.y + (factor*0.5 + 0.5)*radius*Math.cos(myAngle);
  17173. }
  17174. else { // continuous
  17175. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  17176. if (this.from.y > this.to.y) {
  17177. if (this.from.x < this.to.x) {
  17178. xVia = this.from.x + factor * dy;
  17179. yVia = this.from.y - factor * dy;
  17180. xVia = this.to.x < xVia ? this.to.x : xVia;
  17181. }
  17182. else if (this.from.x > this.to.x) {
  17183. xVia = this.from.x - factor * dy;
  17184. yVia = this.from.y - factor * dy;
  17185. xVia = this.to.x > xVia ? this.to.x : xVia;
  17186. }
  17187. }
  17188. else if (this.from.y < this.to.y) {
  17189. if (this.from.x < this.to.x) {
  17190. xVia = this.from.x + factor * dy;
  17191. yVia = this.from.y + factor * dy;
  17192. xVia = this.to.x < xVia ? this.to.x : xVia;
  17193. }
  17194. else if (this.from.x > this.to.x) {
  17195. xVia = this.from.x - factor * dy;
  17196. yVia = this.from.y + factor * dy;
  17197. xVia = this.to.x > xVia ? this.to.x : xVia;
  17198. }
  17199. }
  17200. }
  17201. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  17202. if (this.from.y > this.to.y) {
  17203. if (this.from.x < this.to.x) {
  17204. xVia = this.from.x + factor * dx;
  17205. yVia = this.from.y - factor * dx;
  17206. yVia = this.to.y > yVia ? this.to.y : yVia;
  17207. }
  17208. else if (this.from.x > this.to.x) {
  17209. xVia = this.from.x - factor * dx;
  17210. yVia = this.from.y - factor * dx;
  17211. yVia = this.to.y > yVia ? this.to.y : yVia;
  17212. }
  17213. }
  17214. else if (this.from.y < this.to.y) {
  17215. if (this.from.x < this.to.x) {
  17216. xVia = this.from.x + factor * dx;
  17217. yVia = this.from.y + factor * dx;
  17218. yVia = this.to.y < yVia ? this.to.y : yVia;
  17219. }
  17220. else if (this.from.x > this.to.x) {
  17221. xVia = this.from.x - factor * dx;
  17222. yVia = this.from.y + factor * dx;
  17223. yVia = this.to.y < yVia ? this.to.y : yVia;
  17224. }
  17225. }
  17226. }
  17227. }
  17228. return {x: xVia, y: yVia};
  17229. }
  17230. };
  17231. /**
  17232. * Draw a line between two nodes
  17233. * @param {CanvasRenderingContext2D} ctx
  17234. * @private
  17235. */
  17236. Edge.prototype._line = function (ctx) {
  17237. // draw a straight line
  17238. ctx.beginPath();
  17239. ctx.moveTo(this.from.x, this.from.y);
  17240. if (this.options.smoothCurves.enabled == true) {
  17241. if (this.options.smoothCurves.dynamic == false) {
  17242. var via = this._getViaCoordinates();
  17243. if (via.x == null) {
  17244. ctx.lineTo(this.to.x, this.to.y);
  17245. ctx.stroke();
  17246. return null;
  17247. }
  17248. else {
  17249. // this.via.x = via.x;
  17250. // this.via.y = via.y;
  17251. ctx.quadraticCurveTo(via.x,via.y,this.to.x, this.to.y);
  17252. ctx.stroke();
  17253. //ctx.circle(via.x,via.y,2)
  17254. //ctx.stroke();
  17255. return via;
  17256. }
  17257. }
  17258. else {
  17259. ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
  17260. ctx.stroke();
  17261. return this.via;
  17262. }
  17263. }
  17264. else {
  17265. ctx.lineTo(this.to.x, this.to.y);
  17266. ctx.stroke();
  17267. return null;
  17268. }
  17269. };
  17270. /**
  17271. * Draw a line from a node to itself, a circle
  17272. * @param {CanvasRenderingContext2D} ctx
  17273. * @param {Number} x
  17274. * @param {Number} y
  17275. * @param {Number} radius
  17276. * @private
  17277. */
  17278. Edge.prototype._circle = function (ctx, x, y, radius) {
  17279. // draw a circle
  17280. ctx.beginPath();
  17281. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  17282. ctx.stroke();
  17283. };
  17284. /**
  17285. * Draw label with white background and with the middle at (x, y)
  17286. * @param {CanvasRenderingContext2D} ctx
  17287. * @param {String} text
  17288. * @param {Number} x
  17289. * @param {Number} y
  17290. * @private
  17291. */
  17292. Edge.prototype._label = function (ctx, text, x, y) {
  17293. if (text) {
  17294. ctx.font = ((this.from.selected || this.to.selected) ? "bold " : "") +
  17295. this.options.fontSize + "px " + this.options.fontFace;
  17296. var yLine;
  17297. if (this.dirtyLabel == true) {
  17298. var lines = String(text).split('\n');
  17299. var lineCount = lines.length;
  17300. var fontSize = Number(this.options.fontSize);
  17301. yLine = y + (1 - lineCount) / 2 * fontSize;
  17302. var width = ctx.measureText(lines[0]).width;
  17303. for (var i = 1; i < lineCount; i++) {
  17304. var lineWidth = ctx.measureText(lines[i]).width;
  17305. width = lineWidth > width ? lineWidth : width;
  17306. }
  17307. var height = this.options.fontSize * lineCount;
  17308. var left = x - width / 2;
  17309. var top = y - height / 2;
  17310. // cache
  17311. this.labelDimensions = {top:top,left:left,width:width,height:height,yLine:yLine};
  17312. }
  17313. var yLine = this.labelDimensions.yLine;
  17314. ctx.save();
  17315. if (this.options.labelAlignment != "horizontal"){
  17316. ctx.translate(x, yLine);
  17317. this._rotateForLabelAlignment(ctx);
  17318. x = 0;
  17319. yLine = 0;
  17320. }
  17321. this._drawLabelRect(ctx);
  17322. this._drawLabelText(ctx,x,yLine, lines, lineCount, fontSize);
  17323. ctx.restore();
  17324. }
  17325. };
  17326. /**
  17327. * Rotates the canvas so the text is most readable
  17328. * @param {CanvasRenderingContext2D} ctx
  17329. * @private
  17330. */
  17331. Edge.prototype._rotateForLabelAlignment = function(ctx) {
  17332. var dy = this.from.y - this.to.y;
  17333. var dx = this.from.x - this.to.x;
  17334. var angleInDegrees = Math.atan2(dy, dx);
  17335. // rotate so label it is readable
  17336. if((angleInDegrees < -1 && dx < 0) || (angleInDegrees > 0 && dx < 0)){
  17337. angleInDegrees = angleInDegrees + Math.PI;
  17338. }
  17339. ctx.rotate(angleInDegrees);
  17340. };
  17341. /**
  17342. * Draws the label rectangle
  17343. * @param {CanvasRenderingContext2D} ctx
  17344. * @param {String} labelAlignment
  17345. * @private
  17346. */
  17347. Edge.prototype._drawLabelRect = function(ctx) {
  17348. if (this.options.fontFill !== undefined && this.options.fontFill !== null && this.options.fontFill !== "none") {
  17349. ctx.fillStyle = this.options.fontFill;
  17350. var lineMargin = 2;
  17351. if (this.options.labelAlignment == 'line-center') {
  17352. ctx.fillRect(-this.labelDimensions.width * 0.5, -this.labelDimensions.height * 0.5, this.labelDimensions.width, this.labelDimensions.height);
  17353. }
  17354. else if (this.options.labelAlignment == 'line-above') {
  17355. ctx.fillRect(-this.labelDimensions.width * 0.5, -(this.labelDimensions.height + lineMargin), this.labelDimensions.width, this.labelDimensions.height);
  17356. }
  17357. else if (this.options.labelAlignment == 'line-below') {
  17358. ctx.fillRect(-this.labelDimensions.width * 0.5, lineMargin, this.labelDimensions.width, this.labelDimensions.height);
  17359. }
  17360. else {
  17361. ctx.fillRect(this.labelDimensions.left, this.labelDimensions.top, this.labelDimensions.width, this.labelDimensions.height);
  17362. }
  17363. }
  17364. };
  17365. /**
  17366. * Draws the label text
  17367. * @param {CanvasRenderingContext2D} ctx
  17368. * @param {Number} x
  17369. * @param {Number} yLine
  17370. * @param {Array} lines
  17371. * @param {Number} lineCount
  17372. * @param {Number} fontSize
  17373. * @private
  17374. */
  17375. Edge.prototype._drawLabelText = function(ctx, x, yLine, lines, lineCount, fontSize) {
  17376. // draw text
  17377. ctx.fillStyle = this.options.fontColor || "black";
  17378. ctx.textAlign = "center";
  17379. // check for label alignment
  17380. if (this.options.labelAlignment != 'horizontal') {
  17381. var lineMargin = 2;
  17382. if (this.options.labelAlignment == 'line-above') {
  17383. ctx.textBaseline = "alphabetic";
  17384. yLine -= 2 * lineMargin; // distance from edge, required because we use alphabetic. Alphabetic has less difference between browsers
  17385. }
  17386. else if (this.options.labelAlignment == 'line-below') {
  17387. ctx.textBaseline = "hanging";
  17388. yLine += 2 * lineMargin;// distance from edge, required because we use hanging. Hanging has less difference between browsers
  17389. }
  17390. else {
  17391. ctx.textBaseline = "middle";
  17392. }
  17393. }
  17394. else {
  17395. ctx.textBaseline = "middle";
  17396. }
  17397. // check for strokeWidth
  17398. if (this.options.fontStrokeWidth > 0){
  17399. ctx.lineWidth = this.options.fontStrokeWidth;
  17400. ctx.strokeStyle = this.options.fontStrokeColor;
  17401. ctx.lineJoin = 'round';
  17402. }
  17403. for (var i = 0; i < lineCount; i++) {
  17404. if(this.options.fontStrokeWidth > 0){
  17405. ctx.strokeText(lines[i], x, yLine);
  17406. }
  17407. ctx.fillText(lines[i], x, yLine);
  17408. yLine += fontSize;
  17409. }
  17410. };
  17411. /**
  17412. * Redraw a edge as a dashed line
  17413. * Draw this edge in the given canvas
  17414. * @author David Jordan
  17415. * @date 2012-08-08
  17416. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  17417. * @param {CanvasRenderingContext2D} ctx
  17418. * @private
  17419. */
  17420. Edge.prototype._drawDashLine = function(ctx) {
  17421. // set style
  17422. ctx.strokeStyle = this._getColor(ctx);
  17423. ctx.lineWidth = this._getLineWidth();
  17424. var via = null;
  17425. // only firefox and chrome support this method, else we use the legacy one.
  17426. if (ctx.setLineDash !== undefined) {
  17427. ctx.save();
  17428. // configure the dash pattern
  17429. var pattern = [0];
  17430. if (this.options.dash.length !== undefined && this.options.dash.gap !== undefined) {
  17431. pattern = [this.options.dash.length,this.options.dash.gap];
  17432. }
  17433. else {
  17434. pattern = [5,5];
  17435. }
  17436. // set dash settings for chrome or firefox
  17437. ctx.setLineDash(pattern);
  17438. ctx.lineDashOffset = 0;
  17439. // draw the line
  17440. via = this._line(ctx);
  17441. // restore the dash settings.
  17442. ctx.setLineDash([0]);
  17443. ctx.lineDashOffset = 0;
  17444. ctx.restore();
  17445. }
  17446. else { // unsupporting smooth lines
  17447. // draw dashed line
  17448. ctx.beginPath();
  17449. ctx.lineCap = 'round';
  17450. if (this.options.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
  17451. {
  17452. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  17453. [this.options.dash.length,this.options.dash.gap,this.options.dash.altLength,this.options.dash.gap]);
  17454. }
  17455. 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
  17456. {
  17457. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  17458. [this.options.dash.length,this.options.dash.gap]);
  17459. }
  17460. else //If all else fails draw a line
  17461. {
  17462. ctx.moveTo(this.from.x, this.from.y);
  17463. ctx.lineTo(this.to.x, this.to.y);
  17464. }
  17465. ctx.stroke();
  17466. }
  17467. // draw label
  17468. if (this.label) {
  17469. var point;
  17470. if (this.options.smoothCurves.enabled == true && via != null) {
  17471. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  17472. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  17473. point = {x:midpointX, y:midpointY};
  17474. }
  17475. else {
  17476. point = this._pointOnLine(0.5);
  17477. }
  17478. this._label(ctx, this.label, point.x, point.y);
  17479. }
  17480. };
  17481. /**
  17482. * Get a point on a line
  17483. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  17484. * @return {Object} point
  17485. * @private
  17486. */
  17487. Edge.prototype._pointOnLine = function (percentage) {
  17488. return {
  17489. x: (1 - percentage) * this.from.x + percentage * this.to.x,
  17490. y: (1 - percentage) * this.from.y + percentage * this.to.y
  17491. }
  17492. };
  17493. /**
  17494. * Get a point on a circle
  17495. * @param {Number} x
  17496. * @param {Number} y
  17497. * @param {Number} radius
  17498. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  17499. * @return {Object} point
  17500. * @private
  17501. */
  17502. Edge.prototype._pointOnCircle = function (x, y, radius, percentage) {
  17503. var angle = (percentage - 3/8) * 2 * Math.PI;
  17504. return {
  17505. x: x + radius * Math.cos(angle),
  17506. y: y - radius * Math.sin(angle)
  17507. }
  17508. };
  17509. /**
  17510. * Redraw a edge as a line with an arrow halfway the line
  17511. * Draw this edge in the given canvas
  17512. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  17513. * @param {CanvasRenderingContext2D} ctx
  17514. * @private
  17515. */
  17516. Edge.prototype._drawArrowCenter = function(ctx) {
  17517. var point;
  17518. // set style
  17519. ctx.strokeStyle = this._getColor(ctx);
  17520. ctx.fillStyle = ctx.strokeStyle;
  17521. ctx.lineWidth = this._getLineWidth();
  17522. if (this.from != this.to) {
  17523. // draw line
  17524. var via = this._line(ctx);
  17525. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  17526. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  17527. // draw an arrow halfway the line
  17528. if (this.options.smoothCurves.enabled == true && via != null) {
  17529. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  17530. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  17531. point = {x:midpointX, y:midpointY};
  17532. }
  17533. else {
  17534. point = this._pointOnLine(0.5);
  17535. }
  17536. ctx.arrow(point.x, point.y, angle, length);
  17537. ctx.fill();
  17538. ctx.stroke();
  17539. // draw label
  17540. if (this.label) {
  17541. this._label(ctx, this.label, point.x, point.y);
  17542. }
  17543. }
  17544. else {
  17545. // draw circle
  17546. var x, y;
  17547. var radius = 0.25 * Math.max(100,this.physics.springLength);
  17548. var node = this.from;
  17549. if (!node.width) {
  17550. node.resize(ctx);
  17551. }
  17552. if (node.width > node.height) {
  17553. x = node.x + node.width * 0.5;
  17554. y = node.y - radius;
  17555. }
  17556. else {
  17557. x = node.x + radius;
  17558. y = node.y - node.height * 0.5;
  17559. }
  17560. this._circle(ctx, x, y, radius);
  17561. // draw all arrows
  17562. var angle = 0.2 * Math.PI;
  17563. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  17564. point = this._pointOnCircle(x, y, radius, 0.5);
  17565. ctx.arrow(point.x, point.y, angle, length);
  17566. ctx.fill();
  17567. ctx.stroke();
  17568. // draw label
  17569. if (this.label) {
  17570. point = this._pointOnCircle(x, y, radius, 0.5);
  17571. this._label(ctx, this.label, point.x, point.y);
  17572. }
  17573. }
  17574. };
  17575. Edge.prototype._pointOnBezier = function(t) {
  17576. var via = this._getViaCoordinates();
  17577. var x = Math.pow(1-t,2)*this.from.x + (2*t*(1 - t))*via.x + Math.pow(t,2)*this.to.x;
  17578. var y = Math.pow(1-t,2)*this.from.y + (2*t*(1 - t))*via.y + Math.pow(t,2)*this.to.y;
  17579. return {x:x,y:y};
  17580. }
  17581. /**
  17582. * This function uses binary search to look for the point where the bezier curve crosses the border of the node.
  17583. *
  17584. * @param from
  17585. * @param ctx
  17586. * @returns {*}
  17587. * @private
  17588. */
  17589. Edge.prototype._findBorderPosition = function(from,ctx) {
  17590. var maxIterations = 10;
  17591. var iteration = 0;
  17592. var low = 0;
  17593. var high = 1;
  17594. var pos,angle,distanceToBorder, distanceToNodes, difference;
  17595. var threshold = 0.2;
  17596. var node = this.to;
  17597. if (from == true) {
  17598. node = this.from;
  17599. }
  17600. while (low <= high && iteration < maxIterations) {
  17601. var middle = (low + high) * 0.5;
  17602. pos = this._pointOnBezier(middle);
  17603. angle = Math.atan2((node.y - pos.y), (node.x - pos.x));
  17604. distanceToBorder = node.distanceToBorder(ctx,angle);
  17605. distanceToNodes = Math.sqrt(Math.pow(pos.x-node.x,2) + Math.pow(pos.y-node.y,2));
  17606. difference = distanceToBorder - distanceToNodes;
  17607. if (Math.abs(difference) < threshold) {
  17608. break; // found
  17609. }
  17610. else if (difference < 0) { // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node.
  17611. if (from == false) {
  17612. low = middle;
  17613. }
  17614. else {
  17615. high = middle;
  17616. }
  17617. }
  17618. else {
  17619. if (from == false) {
  17620. high = middle;
  17621. }
  17622. else {
  17623. low = middle;
  17624. }
  17625. }
  17626. iteration++;
  17627. }
  17628. pos.t = middle;
  17629. return pos;
  17630. };
  17631. /**
  17632. * Redraw a edge as a line with an arrow
  17633. * Draw this edge in the given canvas
  17634. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  17635. * @param {CanvasRenderingContext2D} ctx
  17636. * @private
  17637. */
  17638. Edge.prototype._drawArrow = function(ctx) {
  17639. // set style
  17640. ctx.strokeStyle = this._getColor(ctx);
  17641. ctx.fillStyle = ctx.strokeStyle;
  17642. ctx.lineWidth = this._getLineWidth();
  17643. // set vars
  17644. var angle, length, arrowPos;
  17645. // if not connected to itself
  17646. if (this.from != this.to) {
  17647. // draw line
  17648. this._line(ctx);
  17649. // draw arrow head
  17650. if (this.options.smoothCurves.enabled == true) {
  17651. var via = this._getViaCoordinates();
  17652. arrowPos = this._findBorderPosition(false, ctx);
  17653. var guidePos = this._pointOnBezier(Math.max(0.0, arrowPos.t - 0.1))
  17654. angle = Math.atan2((arrowPos.y - guidePos.y), (arrowPos.x - guidePos.x));
  17655. }
  17656. else {
  17657. angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  17658. var dx = (this.to.x - this.from.x);
  17659. var dy = (this.to.y - this.from.y);
  17660. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  17661. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  17662. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  17663. arrowPos = {};
  17664. arrowPos.x = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  17665. arrowPos.y = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  17666. }
  17667. // draw arrow at the end of the line
  17668. length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  17669. ctx.arrow(arrowPos.x,arrowPos.y, angle, length);
  17670. ctx.fill();
  17671. ctx.stroke();
  17672. // draw label
  17673. if (this.label) {
  17674. var point;
  17675. if (this.options.smoothCurves.enabled == true && via != null) {
  17676. point = this._pointOnBezier(0.5);
  17677. }
  17678. else {
  17679. point = this._pointOnLine(0.5);
  17680. }
  17681. this._label(ctx, this.label, point.x, point.y);
  17682. }
  17683. }
  17684. else {
  17685. // draw circle
  17686. var node = this.from;
  17687. var x, y, arrow;
  17688. var radius = 0.25 * Math.max(100,this.physics.springLength);
  17689. if (!node.width) {
  17690. node.resize(ctx);
  17691. }
  17692. if (node.width > node.height) {
  17693. x = node.x + node.width * 0.5;
  17694. y = node.y - radius;
  17695. arrow = {
  17696. x: x,
  17697. y: node.y,
  17698. angle: 0.9 * Math.PI
  17699. };
  17700. }
  17701. else {
  17702. x = node.x + radius;
  17703. y = node.y - node.height * 0.5;
  17704. arrow = {
  17705. x: node.x,
  17706. y: y,
  17707. angle: 0.6 * Math.PI
  17708. };
  17709. }
  17710. ctx.beginPath();
  17711. // TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
  17712. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  17713. ctx.stroke();
  17714. // draw all arrows
  17715. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  17716. ctx.arrow(arrow.x, arrow.y, arrow.angle, length);
  17717. ctx.fill();
  17718. ctx.stroke();
  17719. // draw label
  17720. if (this.label) {
  17721. point = this._pointOnCircle(x, y, radius, 0.5);
  17722. this._label(ctx, this.label, point.x, point.y);
  17723. }
  17724. }
  17725. };
  17726. /**
  17727. * Calculate the distance between a point (x3,y3) and a line segment from
  17728. * (x1,y1) to (x2,y2).
  17729. * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
  17730. * @param {number} x1
  17731. * @param {number} y1
  17732. * @param {number} x2
  17733. * @param {number} y2
  17734. * @param {number} x3
  17735. * @param {number} y3
  17736. * @private
  17737. */
  17738. Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
  17739. var returnValue = 0;
  17740. if (this.from != this.to) {
  17741. if (this.options.smoothCurves.enabled == true) {
  17742. var xVia, yVia;
  17743. if (this.options.smoothCurves.enabled == true && this.options.smoothCurves.dynamic == true) {
  17744. xVia = this.via.x;
  17745. yVia = this.via.y;
  17746. }
  17747. else {
  17748. var via = this._getViaCoordinates();
  17749. xVia = via.x;
  17750. yVia = via.y;
  17751. }
  17752. var minDistance = 1e9;
  17753. var distance;
  17754. var i,t,x,y, lastX, lastY;
  17755. for (i = 0; i < 10; i++) {
  17756. t = 0.1*i;
  17757. x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*xVia + Math.pow(t,2)*x2;
  17758. y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*yVia + Math.pow(t,2)*y2;
  17759. if (i > 0) {
  17760. distance = this._getDistanceToLine(lastX,lastY,x,y, x3,y3);
  17761. minDistance = distance < minDistance ? distance : minDistance;
  17762. }
  17763. lastX = x; lastY = y;
  17764. }
  17765. returnValue = minDistance;
  17766. }
  17767. else {
  17768. returnValue = this._getDistanceToLine(x1,y1,x2,y2,x3,y3);
  17769. }
  17770. }
  17771. else {
  17772. var x, y, dx, dy;
  17773. var radius = 0.25 * this.physics.springLength;
  17774. var node = this.from;
  17775. if (node.width > node.height) {
  17776. x = node.x + 0.5 * node.width;
  17777. y = node.y - radius;
  17778. }
  17779. else {
  17780. x = node.x + radius;
  17781. y = node.y - 0.5 * node.height;
  17782. }
  17783. dx = x - x3;
  17784. dy = y - y3;
  17785. returnValue = Math.abs(Math.sqrt(dx*dx + dy*dy) - radius);
  17786. }
  17787. if (this.labelDimensions.left < x3 &&
  17788. this.labelDimensions.left + this.labelDimensions.width > x3 &&
  17789. this.labelDimensions.top < y3 &&
  17790. this.labelDimensions.top + this.labelDimensions.height > y3) {
  17791. return 0;
  17792. }
  17793. else {
  17794. return returnValue;
  17795. }
  17796. };
  17797. Edge.prototype._getDistanceToLine = function(x1,y1,x2,y2,x3,y3) {
  17798. var px = x2-x1,
  17799. py = y2-y1,
  17800. something = px*px + py*py,
  17801. u = ((x3 - x1) * px + (y3 - y1) * py) / something;
  17802. if (u > 1) {
  17803. u = 1;
  17804. }
  17805. else if (u < 0) {
  17806. u = 0;
  17807. }
  17808. var x = x1 + u * px,
  17809. y = y1 + u * py,
  17810. dx = x - x3,
  17811. dy = y - y3;
  17812. //# Note: If the actual distance does not matter,
  17813. //# if you only want to compare what this function
  17814. //# returns to other results of this function, you
  17815. //# can just return the squared distance instead
  17816. //# (i.e. remove the sqrt) to gain a little performance
  17817. return Math.sqrt(dx*dx + dy*dy);
  17818. };
  17819. /**
  17820. * This allows the zoom level of the network to influence the rendering
  17821. *
  17822. * @param scale
  17823. */
  17824. Edge.prototype.setScale = function(scale) {
  17825. this.networkScaleInv = 1.0/scale;
  17826. };
  17827. Edge.prototype.select = function() {
  17828. this.selected = true;
  17829. };
  17830. Edge.prototype.unselect = function() {
  17831. this.selected = false;
  17832. };
  17833. Edge.prototype.positionBezierNode = function() {
  17834. if (this.via !== null && this.from !== null && this.to !== null) {
  17835. this.via.x = 0.5 * (this.from.x + this.to.x);
  17836. this.via.y = 0.5 * (this.from.y + this.to.y);
  17837. }
  17838. else if (this.via !== null) {
  17839. this.via.x = 0;
  17840. this.via.y = 0;
  17841. }
  17842. };
  17843. /**
  17844. * This function draws the control nodes for the manipulator.
  17845. * In order to enable this, only set the this.controlNodesEnabled to true.
  17846. * @param ctx
  17847. */
  17848. Edge.prototype._drawControlNodes = function(ctx) {
  17849. if (this.controlNodesEnabled == true) {
  17850. if (this.controlNodes.from === null && this.controlNodes.to === null) {
  17851. var nodeIdFrom = "edgeIdFrom:".concat(this.id);
  17852. var nodeIdTo = "edgeIdTo:".concat(this.id);
  17853. var constants = {
  17854. nodes:{group:'', radius:7, borderWidth:2, borderWidthSelected: 2},
  17855. physics:{damping:0},
  17856. clustering: {maxNodeSizeIncrements: 0 ,nodeScaling: {width:0, height: 0, radius:0}}
  17857. };
  17858. this.controlNodes.from = new Node(
  17859. {id:nodeIdFrom,
  17860. shape:'dot',
  17861. color:{background:'#ff0000', border:'#3c3c3c', highlight: {background:'#07f968'}}
  17862. },{},{},constants);
  17863. this.controlNodes.to = new Node(
  17864. {id:nodeIdTo,
  17865. shape:'dot',
  17866. color:{background:'#ff0000', border:'#3c3c3c', highlight: {background:'#07f968'}}
  17867. },{},{},constants);
  17868. }
  17869. this.controlNodes.positions = {};
  17870. if (this.controlNodes.from.selected == false) {
  17871. this.controlNodes.positions.from = this.getControlNodeFromPosition(ctx);
  17872. this.controlNodes.from.x = this.controlNodes.positions.from.x;
  17873. this.controlNodes.from.y = this.controlNodes.positions.from.y;
  17874. }
  17875. if (this.controlNodes.to.selected == false) {
  17876. this.controlNodes.positions.to = this.getControlNodeToPosition(ctx);
  17877. this.controlNodes.to.x = this.controlNodes.positions.to.x;
  17878. this.controlNodes.to.y = this.controlNodes.positions.to.y;
  17879. }
  17880. this.controlNodes.from.draw(ctx);
  17881. this.controlNodes.to.draw(ctx);
  17882. }
  17883. else {
  17884. this.controlNodes = {from:null, to:null, positions:{}};
  17885. }
  17886. };
  17887. /**
  17888. * Enable control nodes.
  17889. * @private
  17890. */
  17891. Edge.prototype._enableControlNodes = function() {
  17892. this.fromBackup = this.from;
  17893. this.toBackup = this.to;
  17894. this.controlNodesEnabled = true;
  17895. };
  17896. /**
  17897. * disable control nodes and remove from dynamicEdges from old node
  17898. * @private
  17899. */
  17900. Edge.prototype._disableControlNodes = function() {
  17901. this.fromId = this.from.id;
  17902. this.toId = this.to.id;
  17903. if (this.fromId != this.fromBackup.id) { // from was changed, remove edge from old 'from' node dynamic edges
  17904. this.fromBackup.detachEdge(this);
  17905. }
  17906. else if (this.toId != this.toBackup.id) { // to was changed, remove edge from old 'to' node dynamic edges
  17907. this.toBackup.detachEdge(this);
  17908. }
  17909. this.fromBackup = null;
  17910. this.toBackup = null;
  17911. this.controlNodesEnabled = false;
  17912. };
  17913. /**
  17914. * This checks if one of the control nodes is selected and if so, returns the control node object. Else it returns null.
  17915. * @param x
  17916. * @param y
  17917. * @returns {null}
  17918. * @private
  17919. */
  17920. Edge.prototype._getSelectedControlNode = function(x,y) {
  17921. var positions = this.controlNodes.positions;
  17922. var fromDistance = Math.sqrt(Math.pow(x - positions.from.x,2) + Math.pow(y - positions.from.y,2));
  17923. var toDistance = Math.sqrt(Math.pow(x - positions.to.x ,2) + Math.pow(y - positions.to.y ,2));
  17924. if (fromDistance < 15) {
  17925. this.connectedNode = this.from;
  17926. this.from = this.controlNodes.from;
  17927. return this.controlNodes.from;
  17928. }
  17929. else if (toDistance < 15) {
  17930. this.connectedNode = this.to;
  17931. this.to = this.controlNodes.to;
  17932. return this.controlNodes.to;
  17933. }
  17934. else {
  17935. return null;
  17936. }
  17937. };
  17938. /**
  17939. * this resets the control nodes to their original position.
  17940. * @private
  17941. */
  17942. Edge.prototype._restoreControlNodes = function() {
  17943. if (this.controlNodes.from.selected == true) {
  17944. this.from = this.connectedNode;
  17945. this.connectedNode = null;
  17946. this.controlNodes.from.unselect();
  17947. }
  17948. else if (this.controlNodes.to.selected == true) {
  17949. this.to = this.connectedNode;
  17950. this.connectedNode = null;
  17951. this.controlNodes.to.unselect();
  17952. }
  17953. };
  17954. /**
  17955. * this calculates the position of the control nodes on the edges of the parent nodes.
  17956. *
  17957. * @param ctx
  17958. * @returns {x: *, y: *}
  17959. */
  17960. Edge.prototype.getControlNodeFromPosition = function(ctx) {
  17961. // draw arrow head
  17962. var controlnodeFromPos;
  17963. if (this.options.smoothCurves.enabled == true) {
  17964. controlnodeFromPos = this._findBorderPosition(true, ctx);
  17965. }
  17966. else {
  17967. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  17968. var dx = (this.to.x - this.from.x);
  17969. var dy = (this.to.y - this.from.y);
  17970. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  17971. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  17972. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  17973. controlnodeFromPos = {};
  17974. controlnodeFromPos.x = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  17975. controlnodeFromPos.y = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  17976. }
  17977. return controlnodeFromPos;
  17978. };
  17979. /**
  17980. * this calculates the position of the control nodes on the edges of the parent nodes.
  17981. *
  17982. * @param ctx
  17983. * @returns {{from: {x: number, y: number}, to: {x: *, y: *}}}
  17984. */
  17985. Edge.prototype.getControlNodeToPosition = function(ctx) {
  17986. // draw arrow head
  17987. var controlnodeFromPos,controlnodeToPos;
  17988. if (this.options.smoothCurves.enabled == true) {
  17989. controlnodeToPos = this._findBorderPosition(false, ctx);
  17990. }
  17991. else {
  17992. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  17993. var dx = (this.to.x - this.from.x);
  17994. var dy = (this.to.y - this.from.y);
  17995. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  17996. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  17997. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  17998. controlnodeToPos = {};
  17999. controlnodeToPos.x = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  18000. controlnodeToPos.y = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  18001. }
  18002. return controlnodeToPos;
  18003. };
  18004. module.exports = Edge;
  18005. /***/ },
  18006. /* 38 */
  18007. /***/ function(module, exports, __webpack_require__) {
  18008. var util = __webpack_require__(1);
  18009. /**
  18010. * @class Groups
  18011. * This class can store groups and properties specific for groups.
  18012. */
  18013. function Groups() {
  18014. this.clear();
  18015. this.defaultIndex = 0;
  18016. this.groupsArray = [];
  18017. this.groupIndex = 0;
  18018. this.useDefaultGroups = true;
  18019. }
  18020. /**
  18021. * default constants for group colors
  18022. */
  18023. Groups.DEFAULT = [
  18024. {border: "#2B7CE9", background: "#97C2FC", highlight: {border: "#2B7CE9", background: "#D2E5FF"}, hover: {border: "#2B7CE9", background: "#D2E5FF"}}, // 0: blue
  18025. {border: "#FFA500", background: "#FFFF00", highlight: {border: "#FFA500", background: "#FFFFA3"}, hover: {border: "#FFA500", background: "#FFFFA3"}}, // 1: yellow
  18026. {border: "#FA0A10", background: "#FB7E81", highlight: {border: "#FA0A10", background: "#FFAFB1"}, hover: {border: "#FA0A10", background: "#FFAFB1"}}, // 2: red
  18027. {border: "#41A906", background: "#7BE141", highlight: {border: "#41A906", background: "#A1EC76"}, hover: {border: "#41A906", background: "#A1EC76"}}, // 3: green
  18028. {border: "#E129F0", background: "#EB7DF4", highlight: {border: "#E129F0", background: "#F0B3F5"}, hover: {border: "#E129F0", background: "#F0B3F5"}}, // 4: magenta
  18029. {border: "#7C29F0", background: "#AD85E4", highlight: {border: "#7C29F0", background: "#D3BDF0"}, hover: {border: "#7C29F0", background: "#D3BDF0"}}, // 5: purple
  18030. {border: "#C37F00", background: "#FFA807", highlight: {border: "#C37F00", background: "#FFCA66"}, hover: {border: "#C37F00", background: "#FFCA66"}}, // 6: orange
  18031. {border: "#4220FB", background: "#6E6EFD", highlight: {border: "#4220FB", background: "#9B9BFD"}, hover: {border: "#4220FB", background: "#9B9BFD"}}, // 7: darkblue
  18032. {border: "#FD5A77", background: "#FFC0CB", highlight: {border: "#FD5A77", background: "#FFD1D9"}, hover: {border: "#FD5A77", background: "#FFD1D9"}}, // 8: pink
  18033. {border: "#4AD63A", background: "#C2FABC", highlight: {border: "#4AD63A", background: "#E6FFE3"}, hover: {border: "#4AD63A", background: "#E6FFE3"}}, // 9: mint
  18034. {border: "#990000", background: "#EE0000", highlight: {border: "#BB0000", background: "#FF3333"}, hover: {border: "#BB0000", background: "#FF3333"}}, // 10:bright red
  18035. {border: "#FF6000", background: "#FF6000", highlight: {border: "#FF6000", background: "#FF6000"}, hover: {border: "#FF6000", background: "#FF6000"}}, // 12: real orange
  18036. {border: "#97C2FC", background: "#2B7CE9", highlight: {border: "#D2E5FF", background: "#2B7CE9"}, hover: {border: "#D2E5FF", background: "#2B7CE9"}}, // 13: blue
  18037. {border: "#399605", background: "#255C03", highlight: {border: "#399605", background: "#255C03"}, hover: {border: "#399605", background: "#255C03"}}, // 14: green
  18038. {border: "#B70054", background: "#FF007E", highlight: {border: "#B70054", background: "#FF007E"}, hover: {border: "#B70054", background: "#FF007E"}}, // 15: magenta
  18039. {border: "#AD85E4", background: "#7C29F0", highlight: {border: "#D3BDF0", background: "#7C29F0"}, hover: {border: "#D3BDF0", background: "#7C29F0"}}, // 16: purple
  18040. {border: "#4557FA", background: "#000EA1", highlight: {border: "#6E6EFD", background: "#000EA1"}, hover: {border: "#6E6EFD", background: "#000EA1"}}, // 17: darkblue
  18041. {border: "#FFC0CB", background: "#FD5A77", highlight: {border: "#FFD1D9", background: "#FD5A77"}, hover: {border: "#FFD1D9", background: "#FD5A77"}}, // 18: pink
  18042. {border: "#C2FABC", background: "#74D66A", highlight: {border: "#E6FFE3", background: "#74D66A"}, hover: {border: "#E6FFE3", background: "#74D66A"}}, // 19: mint
  18043. {border: "#EE0000", background: "#990000", highlight: {border: "#FF3333", background: "#BB0000"}, hover: {border: "#FF3333", background: "#BB0000"}}, // 20:bright red
  18044. ];
  18045. /**
  18046. * Clear all groups
  18047. */
  18048. Groups.prototype.clear = function () {
  18049. this.groups = {};
  18050. this.groups.length = function()
  18051. {
  18052. var i = 0;
  18053. for ( var p in this ) {
  18054. if (this.hasOwnProperty(p)) {
  18055. i++;
  18056. }
  18057. }
  18058. return i;
  18059. }
  18060. };
  18061. /**
  18062. * get group properties of a groupname. If groupname is not found, a new group
  18063. * is added.
  18064. * @param {*} groupname Can be a number, string, Date, etc.
  18065. * @return {Object} group The created group, containing all group properties
  18066. */
  18067. Groups.prototype.get = function (groupname) {
  18068. var group = this.groups[groupname];
  18069. if (group == undefined) {
  18070. if (this.useDefaultGroups === false && this.groupsArray.length > 0) {
  18071. // create new group
  18072. var index = this.groupIndex % this.groupsArray.length;
  18073. this.groupIndex++;
  18074. group = {};
  18075. group.color = this.groups[this.groupsArray[index]];
  18076. this.groups[groupname] = group;
  18077. }
  18078. else {
  18079. // create new group
  18080. var index = this.defaultIndex % Groups.DEFAULT.length;
  18081. this.defaultIndex++;
  18082. group = {};
  18083. group.color = Groups.DEFAULT[index];
  18084. this.groups[groupname] = group;
  18085. }
  18086. }
  18087. return group;
  18088. };
  18089. /**
  18090. * Add a custom group style
  18091. * @param {String} groupName
  18092. * @param {Object} style An object containing borderColor,
  18093. * backgroundColor, etc.
  18094. * @return {Object} group The created group object
  18095. */
  18096. Groups.prototype.add = function (groupName, style) {
  18097. this.groups[groupName] = style;
  18098. this.groupsArray.push(groupName);
  18099. return style;
  18100. };
  18101. module.exports = Groups;
  18102. /***/ },
  18103. /* 39 */
  18104. /***/ function(module, exports, __webpack_require__) {
  18105. /**
  18106. * @class Images
  18107. * This class loads images and keeps them stored.
  18108. */
  18109. function Images() {
  18110. this.images = {};
  18111. this.imageBroken = {};
  18112. this.callback = undefined;
  18113. }
  18114. /**
  18115. * Set an onload callback function. This will be called each time an image
  18116. * is loaded
  18117. * @param {function} callback
  18118. */
  18119. Images.prototype.setOnloadCallback = function(callback) {
  18120. this.callback = callback;
  18121. };
  18122. /**
  18123. *
  18124. * @param {string} url Url of the image
  18125. * @param {string} url Url of an image to use if the url image is not found
  18126. * @return {Image} img The image object
  18127. */
  18128. Images.prototype.load = function(url, brokenUrl) {
  18129. var img = this.images[url]; // make a pointer
  18130. if (img === undefined) {
  18131. // create the image
  18132. var me = this;
  18133. img = new Image();
  18134. img.onload = function () {
  18135. // IE11 fix -- thanks dponch!
  18136. if (this.width == 0) {
  18137. document.body.appendChild(this);
  18138. this.width = this.offsetWidth;
  18139. this.height = this.offsetHeight;
  18140. document.body.removeChild(this);
  18141. }
  18142. if (me.callback) {
  18143. me.images[url] = img;
  18144. me.callback(this);
  18145. }
  18146. };
  18147. img.onerror = function () {
  18148. if (brokenUrl === undefined) {
  18149. console.error("Could not load image:", url);
  18150. delete this.src;
  18151. if (me.callback) {
  18152. me.callback(this);
  18153. }
  18154. }
  18155. else {
  18156. if (me.imageBroken[url] === true) {
  18157. if (this.src == brokenUrl) {
  18158. console.error("Could not load brokenImage:", brokenUrl);
  18159. delete this.src;
  18160. if (me.callback) {
  18161. me.callback(this);
  18162. }
  18163. }
  18164. else {
  18165. console.error("Could not load image:", url);
  18166. this.src = brokenUrl;
  18167. }
  18168. }
  18169. else {
  18170. console.error("Could not load image:", url);
  18171. this.src = brokenUrl;
  18172. me.imageBroken[url] = true;
  18173. }
  18174. }
  18175. };
  18176. img.src = url;
  18177. }
  18178. return img;
  18179. };
  18180. module.exports = Images;
  18181. /***/ },
  18182. /* 40 */
  18183. /***/ function(module, exports, __webpack_require__) {
  18184. var util = __webpack_require__(1);
  18185. /**
  18186. * @class Node
  18187. * A node. A node can be connected to other nodes via one or multiple edges.
  18188. * @param {object} properties An object containing properties for the node. All
  18189. * properties are optional, except for the id.
  18190. * {number} id Id of the node. Required
  18191. * {string} label Text label for the node
  18192. * {number} x Horizontal position of the node
  18193. * {number} y Vertical position of the node
  18194. * {string} shape Node shape, available:
  18195. * "database", "circle", "ellipse",
  18196. * "box", "image", "text", "dot",
  18197. * "star", "triangle", "triangleDown",
  18198. * "square", "icon"
  18199. * {string} image An image url
  18200. * {string} title An title text, can be HTML
  18201. * {anytype} group A group name or number
  18202. * @param {Network.Images} imagelist A list with images. Only needed
  18203. * when the node has an image
  18204. * @param {Network.Groups} grouplist A list with groups. Needed for
  18205. * retrieving group properties
  18206. * @param {Object} constants An object with default values for
  18207. * example for the color
  18208. *
  18209. */
  18210. function Node(properties, imagelist, grouplist, networkConstants) {
  18211. var constants = util.selectiveBridgeObject(['nodes'],networkConstants);
  18212. this.options = constants.nodes;
  18213. this.selected = false;
  18214. this.hover = false;
  18215. this.edges = []; // all edges connected to this node
  18216. this.dynamicEdges = [];
  18217. this.reroutedEdges = {};
  18218. // set defaults for the properties
  18219. this.id = undefined;
  18220. this.allowedToMoveX = false;
  18221. this.allowedToMoveY = false;
  18222. this.xFixed = false;
  18223. this.yFixed = false;
  18224. this.horizontalAlignLeft = true; // these are for the navigation controls
  18225. this.verticalAlignTop = true; // these are for the navigation controls
  18226. this.baseRadiusValue = networkConstants.nodes.radius;
  18227. this.radiusFixed = false;
  18228. this.level = -1;
  18229. this.preassignedLevel = false;
  18230. this.hierarchyEnumerated = false;
  18231. this.labelDimensions = {top:0, left:0, width:0, height:0, yLine:0}; // could be cached
  18232. this.boundingBox = {top:0, left:0, right:0, bottom:0};
  18233. this.imagelist = imagelist;
  18234. this.grouplist = grouplist;
  18235. // physics properties
  18236. this.fx = 0.0; // external force x
  18237. this.fy = 0.0; // external force y
  18238. this.vx = 0.0; // velocity x
  18239. this.vy = 0.0; // velocity y
  18240. this.x = null;
  18241. this.y = null;
  18242. this.predefinedPosition = false; // used to check if initial zoomExtent should just take the range or approximate
  18243. // used for reverting to previous position on stabilization
  18244. this.previousState = {vx:0,vy:0,x:0,y:0};
  18245. this.damping = networkConstants.physics.damping; // written every time gravity is calculated
  18246. this.fixedData = {x:null,y:null};
  18247. this.setProperties(properties, constants);
  18248. // creating the variables for clustering
  18249. this.resetCluster();
  18250. this.clusterSession = 0;
  18251. this.clusterSizeWidthFactor = networkConstants.clustering.nodeScaling.width;
  18252. this.clusterSizeHeightFactor = networkConstants.clustering.nodeScaling.height;
  18253. this.clusterSizeRadiusFactor = networkConstants.clustering.nodeScaling.radius;
  18254. this.maxNodeSizeIncrements = networkConstants.clustering.maxNodeSizeIncrements;
  18255. this.growthIndicator = 0;
  18256. // variables to tell the node about the network.
  18257. this.networkScaleInv = 1;
  18258. this.networkScale = 1;
  18259. this.canvasTopLeft = {"x": -300, "y": -300};
  18260. this.canvasBottomRight = {"x": 300, "y": 300};
  18261. this.parentEdgeId = null;
  18262. }
  18263. /**
  18264. * Revert the position and velocity of the previous step.
  18265. */
  18266. Node.prototype.revertPosition = function() {
  18267. this.x = this.previousState.x;
  18268. this.y = this.previousState.y;
  18269. this.vx = this.previousState.vx;
  18270. this.vy = this.previousState.vy;
  18271. }
  18272. /**
  18273. * (re)setting the clustering variables and objects
  18274. */
  18275. Node.prototype.resetCluster = function() {
  18276. // clustering variables
  18277. this.formationScale = undefined; // this is used to determine when to open the cluster
  18278. this.clusterSize = 1; // this signifies the total amount of nodes in this cluster
  18279. this.containedNodes = {};
  18280. this.containedEdges = {};
  18281. this.clusterSessions = [];
  18282. };
  18283. /**
  18284. * Attach a edge to the node
  18285. * @param {Edge} edge
  18286. */
  18287. Node.prototype.attachEdge = function(edge) {
  18288. if (this.edges.indexOf(edge) == -1) {
  18289. this.edges.push(edge);
  18290. }
  18291. if (this.dynamicEdges.indexOf(edge) == -1) {
  18292. this.dynamicEdges.push(edge);
  18293. }
  18294. };
  18295. /**
  18296. * Detach a edge from the node
  18297. * @param {Edge} edge
  18298. */
  18299. Node.prototype.detachEdge = function(edge) {
  18300. var index = this.edges.indexOf(edge);
  18301. if (index != -1) {
  18302. this.edges.splice(index, 1);
  18303. }
  18304. index = this.dynamicEdges.indexOf(edge);
  18305. if (index != -1) {
  18306. this.dynamicEdges.splice(index, 1);
  18307. }
  18308. };
  18309. /**
  18310. * Set or overwrite properties for the node
  18311. * @param {Object} properties an object with properties
  18312. * @param {Object} constants and object with default, global properties
  18313. */
  18314. Node.prototype.setProperties = function(properties, constants) {
  18315. if (!properties) {
  18316. return;
  18317. }
  18318. var fields = ['borderWidth','borderWidthSelected','shape','image','brokenImage','radius','fontColor',
  18319. 'fontSize','fontFace','fontFill','fontStrokeWidth','fontStrokeColor','group','mass','fontDrawThreshold',
  18320. 'scaleFontWithValue','fontSizeMaxVisible','customScalingFunction','iconFontFace', 'icon', 'iconColor', 'iconSize'
  18321. ];
  18322. util.selectiveDeepExtend(fields, this.options, properties);
  18323. // basic properties
  18324. if (properties.id !== undefined) {this.id = properties.id;}
  18325. if (properties.label !== undefined) {this.label = properties.label; this.originalLabel = properties.label;}
  18326. if (properties.title !== undefined) {this.title = properties.title;}
  18327. if (properties.x !== undefined) {this.x = properties.x; this.predefinedPosition = true;}
  18328. if (properties.y !== undefined) {this.y = properties.y; this.predefinedPosition = true;}
  18329. if (properties.value !== undefined) {this.value = properties.value;}
  18330. if (properties.level !== undefined) {this.level = properties.level; this.preassignedLevel = true;}
  18331. // navigation controls properties
  18332. if (properties.horizontalAlignLeft !== undefined) {this.horizontalAlignLeft = properties.horizontalAlignLeft;}
  18333. if (properties.verticalAlignTop !== undefined) {this.verticalAlignTop = properties.verticalAlignTop;}
  18334. if (properties.triggerFunction !== undefined) {this.triggerFunction = properties.triggerFunction;}
  18335. if (this.id === undefined) {
  18336. throw "Node must have an id";
  18337. }
  18338. // copy group properties
  18339. if (typeof properties.group === 'number' || (typeof properties.group === 'string' && properties.group != '')) {
  18340. var groupObj = this.grouplist.get(properties.group);
  18341. util.deepExtend(this.options, groupObj);
  18342. // the color object needs to be completely defined. Since groups can partially overwrite the colors, we parse it again, just in case.
  18343. this.options.color = util.parseColor(this.options.color);
  18344. }
  18345. // individual shape properties
  18346. if (properties.radius !== undefined) {this.baseRadiusValue = this.options.radius;}
  18347. if (properties.color !== undefined) {this.options.color = util.parseColor(properties.color);}
  18348. if (this.options.image !== undefined && this.options.image!= "") {
  18349. if (this.imagelist) {
  18350. this.imageObj = this.imagelist.load(this.options.image, this.options.brokenImage);
  18351. }
  18352. else {
  18353. throw "No imagelist provided";
  18354. }
  18355. }
  18356. if (properties.allowedToMoveX !== undefined) {
  18357. this.xFixed = !properties.allowedToMoveX;
  18358. this.allowedToMoveX = properties.allowedToMoveX;
  18359. }
  18360. else if (properties.x !== undefined && this.allowedToMoveX == false) {
  18361. this.xFixed = true;
  18362. }
  18363. if (properties.allowedToMoveY !== undefined) {
  18364. this.yFixed = !properties.allowedToMoveY;
  18365. this.allowedToMoveY = properties.allowedToMoveY;
  18366. }
  18367. else if (properties.y !== undefined && this.allowedToMoveY == false) {
  18368. this.yFixed = true;
  18369. }
  18370. this.radiusFixed = this.radiusFixed || (properties.radius !== undefined);
  18371. if (this.options.shape === 'image' || this.options.shape === 'circularImage') {
  18372. this.options.radiusMin = constants.nodes.widthMin;
  18373. this.options.radiusMax = constants.nodes.widthMax;
  18374. }
  18375. // choose draw method depending on the shape
  18376. switch (this.options.shape) {
  18377. case 'database': this.draw = this._drawDatabase; this.resize = this._resizeDatabase; break;
  18378. case 'box': this.draw = this._drawBox; this.resize = this._resizeBox; break;
  18379. case 'circle': this.draw = this._drawCircle; this.resize = this._resizeCircle; break;
  18380. case 'ellipse': this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
  18381. // TODO: add diamond shape
  18382. case 'image': this.draw = this._drawImage; this.resize = this._resizeImage; break;
  18383. case 'circularImage': this.draw = this._drawCircularImage; this.resize = this._resizeCircularImage; break;
  18384. case 'text': this.draw = this._drawText; this.resize = this._resizeText; break;
  18385. case 'dot': this.draw = this._drawDot; this.resize = this._resizeShape; break;
  18386. case 'square': this.draw = this._drawSquare; this.resize = this._resizeShape; break;
  18387. case 'triangle': this.draw = this._drawTriangle; this.resize = this._resizeShape; break;
  18388. case 'triangleDown': this.draw = this._drawTriangleDown; this.resize = this._resizeShape; break;
  18389. case 'star': this.draw = this._drawStar; this.resize = this._resizeShape; break;
  18390. case 'icon': this.draw = this._drawIcon; this.resize = this._resizeIcon; break;
  18391. default: this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
  18392. }
  18393. // reset the size of the node, this can be changed
  18394. this._reset();
  18395. };
  18396. /**
  18397. * select this node
  18398. */
  18399. Node.prototype.select = function() {
  18400. this.selected = true;
  18401. this._reset();
  18402. };
  18403. /**
  18404. * unselect this node
  18405. */
  18406. Node.prototype.unselect = function() {
  18407. this.selected = false;
  18408. this._reset();
  18409. };
  18410. /**
  18411. * Reset the calculated size of the node, forces it to recalculate its size
  18412. */
  18413. Node.prototype.clearSizeCache = function() {
  18414. this._reset();
  18415. };
  18416. /**
  18417. * Reset the calculated size of the node, forces it to recalculate its size
  18418. * @private
  18419. */
  18420. Node.prototype._reset = function() {
  18421. this.width = undefined;
  18422. this.height = undefined;
  18423. };
  18424. /**
  18425. * get the title of this node.
  18426. * @return {string} title The title of the node, or undefined when no title
  18427. * has been set.
  18428. */
  18429. Node.prototype.getTitle = function() {
  18430. return typeof this.title === "function" ? this.title() : this.title;
  18431. };
  18432. /**
  18433. * Calculate the distance to the border of the Node
  18434. * @param {CanvasRenderingContext2D} ctx
  18435. * @param {Number} angle Angle in radians
  18436. * @returns {number} distance Distance to the border in pixels
  18437. */
  18438. Node.prototype.distanceToBorder = function (ctx, angle) {
  18439. var borderWidth = 1;
  18440. if (!this.width) {
  18441. this.resize(ctx);
  18442. }
  18443. switch (this.options.shape) {
  18444. case 'circle':
  18445. case 'dot':
  18446. return this.options.radius+ borderWidth;
  18447. case 'ellipse':
  18448. var a = this.width / 2;
  18449. var b = this.height / 2;
  18450. var w = (Math.sin(angle) * a);
  18451. var h = (Math.cos(angle) * b);
  18452. return a * b / Math.sqrt(w * w + h * h);
  18453. // TODO: implement distanceToBorder for database
  18454. // TODO: implement distanceToBorder for triangle
  18455. // TODO: implement distanceToBorder for triangleDown
  18456. case 'box':
  18457. case 'image':
  18458. case 'text':
  18459. default:
  18460. if (this.width) {
  18461. return Math.min(
  18462. Math.abs(this.width / 2 / Math.cos(angle)),
  18463. Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth;
  18464. // TODO: reckon with border radius too in case of box
  18465. }
  18466. else {
  18467. return 0;
  18468. }
  18469. }
  18470. // TODO: implement calculation of distance to border for all shapes
  18471. };
  18472. /**
  18473. * Set forces acting on the node
  18474. * @param {number} fx Force in horizontal direction
  18475. * @param {number} fy Force in vertical direction
  18476. */
  18477. Node.prototype._setForce = function(fx, fy) {
  18478. this.fx = fx;
  18479. this.fy = fy;
  18480. };
  18481. /**
  18482. * Add forces acting on the node
  18483. * @param {number} fx Force in horizontal direction
  18484. * @param {number} fy Force in vertical direction
  18485. * @private
  18486. */
  18487. Node.prototype._addForce = function(fx, fy) {
  18488. this.fx += fx;
  18489. this.fy += fy;
  18490. };
  18491. /**
  18492. * Store the state before the next step
  18493. */
  18494. Node.prototype.storeState = function() {
  18495. this.previousState.x = this.x;
  18496. this.previousState.y = this.y;
  18497. this.previousState.vx = this.vx;
  18498. this.previousState.vy = this.vy;
  18499. }
  18500. /**
  18501. * Perform one discrete step for the node
  18502. * @param {number} interval Time interval in seconds
  18503. */
  18504. Node.prototype.discreteStep = function(interval) {
  18505. this.storeState();
  18506. if (!this.xFixed) {
  18507. var dx = this.damping * this.vx; // damping force
  18508. var ax = (this.fx - dx) / this.options.mass; // acceleration
  18509. this.vx += ax * interval; // velocity
  18510. this.x += this.vx * interval; // position
  18511. }
  18512. else {
  18513. this.fx = 0;
  18514. this.vx = 0;
  18515. }
  18516. if (!this.yFixed) {
  18517. var dy = this.damping * this.vy; // damping force
  18518. var ay = (this.fy - dy) / this.options.mass; // acceleration
  18519. this.vy += ay * interval; // velocity
  18520. this.y += this.vy * interval; // position
  18521. }
  18522. else {
  18523. this.fy = 0;
  18524. this.vy = 0;
  18525. }
  18526. };
  18527. /**
  18528. * Perform one discrete step for the node
  18529. * @param {number} interval Time interval in seconds
  18530. * @param {number} maxVelocity The speed limit imposed on the velocity
  18531. */
  18532. Node.prototype.discreteStepLimited = function(interval, maxVelocity) {
  18533. this.storeState();
  18534. if (!this.xFixed) {
  18535. var dx = this.damping * this.vx; // damping force
  18536. var ax = (this.fx - dx) / this.options.mass; // acceleration
  18537. this.vx += ax * interval; // velocity
  18538. this.vx = (Math.abs(this.vx) > maxVelocity) ? ((this.vx > 0) ? maxVelocity : -maxVelocity) : this.vx;
  18539. this.x += this.vx * interval; // position
  18540. }
  18541. else {
  18542. this.fx = 0;
  18543. this.vx = 0;
  18544. }
  18545. if (!this.yFixed) {
  18546. var dy = this.damping * this.vy; // damping force
  18547. var ay = (this.fy - dy) / this.options.mass; // acceleration
  18548. this.vy += ay * interval; // velocity
  18549. this.vy = (Math.abs(this.vy) > maxVelocity) ? ((this.vy > 0) ? maxVelocity : -maxVelocity) : this.vy;
  18550. this.y += this.vy * interval; // position
  18551. }
  18552. else {
  18553. this.fy = 0;
  18554. this.vy = 0;
  18555. }
  18556. };
  18557. /**
  18558. * Check if this node has a fixed x and y position
  18559. * @return {boolean} true if fixed, false if not
  18560. */
  18561. Node.prototype.isFixed = function() {
  18562. return (this.xFixed && this.yFixed);
  18563. };
  18564. /**
  18565. * Check if this node is moving
  18566. * @param {number} vmin the minimum velocity considered as "moving"
  18567. * @return {boolean} true if moving, false if it has no velocity
  18568. */
  18569. Node.prototype.isMoving = function(vmin) {
  18570. var velocity = Math.sqrt(Math.pow(this.vx,2) + Math.pow(this.vy,2));
  18571. // this.velocity = Math.sqrt(Math.pow(this.vx,2) + Math.pow(this.vy,2))
  18572. return (velocity > vmin);
  18573. };
  18574. /**
  18575. * check if this node is selecte
  18576. * @return {boolean} selected True if node is selected, else false
  18577. */
  18578. Node.prototype.isSelected = function() {
  18579. return this.selected;
  18580. };
  18581. /**
  18582. * Retrieve the value of the node. Can be undefined
  18583. * @return {Number} value
  18584. */
  18585. Node.prototype.getValue = function() {
  18586. return this.value;
  18587. };
  18588. /**
  18589. * Calculate the distance from the nodes location to the given location (x,y)
  18590. * @param {Number} x
  18591. * @param {Number} y
  18592. * @return {Number} value
  18593. */
  18594. Node.prototype.getDistance = function(x, y) {
  18595. var dx = this.x - x,
  18596. dy = this.y - y;
  18597. return Math.sqrt(dx * dx + dy * dy);
  18598. };
  18599. /**
  18600. * Adjust the value range of the node. The node will adjust it's radius
  18601. * based on its value.
  18602. * @param {Number} min
  18603. * @param {Number} max
  18604. */
  18605. Node.prototype.setValueRange = function(min, max, total) {
  18606. if (!this.radiusFixed && this.value !== undefined) {
  18607. var scale = this.options.customScalingFunction(min, max, total, this.value);
  18608. var radiusDiff = this.options.radiusMax - this.options.radiusMin;
  18609. if (this.options.scaleFontWithValue == true) {
  18610. var fontDiff = this.options.fontSizeMax - this.options.fontSizeMin;
  18611. this.options.fontSize = this.options.fontSizeMin + scale * fontDiff;
  18612. }
  18613. this.options.radius = this.options.radiusMin + scale * radiusDiff;
  18614. }
  18615. this.baseRadiusValue = this.options.radius;
  18616. };
  18617. /**
  18618. * Draw this node in the given canvas
  18619. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  18620. * @param {CanvasRenderingContext2D} ctx
  18621. */
  18622. Node.prototype.draw = function(ctx) {
  18623. throw "Draw method not initialized for node";
  18624. };
  18625. /**
  18626. * Recalculate the size of this node in the given canvas
  18627. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  18628. * @param {CanvasRenderingContext2D} ctx
  18629. */
  18630. Node.prototype.resize = function(ctx) {
  18631. throw "Resize method not initialized for node";
  18632. };
  18633. /**
  18634. * Check if this object is overlapping with the provided object
  18635. * @param {Object} obj an object with parameters left, top, right, bottom
  18636. * @return {boolean} True if location is located on node
  18637. */
  18638. Node.prototype.isOverlappingWith = function(obj) {
  18639. return (this.left < obj.right &&
  18640. this.left + this.width > obj.left &&
  18641. this.top < obj.bottom &&
  18642. this.top + this.height > obj.top);
  18643. };
  18644. Node.prototype._resizeImage = function (ctx) {
  18645. // TODO: pre calculate the image size
  18646. if (!this.width || !this.height) { // undefined or 0
  18647. var width, height;
  18648. if (this.value) {
  18649. this.options.radius= this.baseRadiusValue;
  18650. var scale = this.imageObj.height / this.imageObj.width;
  18651. if (scale !== undefined) {
  18652. width = this.options.radius|| this.imageObj.width;
  18653. height = this.options.radius* scale || this.imageObj.height;
  18654. }
  18655. else {
  18656. width = 0;
  18657. height = 0;
  18658. }
  18659. }
  18660. else {
  18661. width = this.imageObj.width;
  18662. height = this.imageObj.height;
  18663. }
  18664. this.width = width;
  18665. this.height = height;
  18666. this.growthIndicator = 0;
  18667. if (this.width > 0 && this.height > 0) {
  18668. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  18669. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  18670. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  18671. this.growthIndicator = this.width - width;
  18672. }
  18673. }
  18674. };
  18675. Node.prototype._drawImageAtPosition = function (ctx) {
  18676. if (this.imageObj.width != 0 ) {
  18677. // draw the shade
  18678. if (this.clusterSize > 1) {
  18679. var lineWidth = ((this.clusterSize > 1) ? 10 : 0.0);
  18680. lineWidth *= this.networkScaleInv;
  18681. lineWidth = Math.min(0.2 * this.width,lineWidth);
  18682. ctx.globalAlpha = 0.5;
  18683. ctx.drawImage(this.imageObj, this.left - lineWidth, this.top - lineWidth, this.width + 2*lineWidth, this.height + 2*lineWidth);
  18684. }
  18685. // draw the image
  18686. ctx.globalAlpha = 1.0;
  18687. ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height);
  18688. }
  18689. };
  18690. Node.prototype._drawImageLabel = function (ctx) {
  18691. var yLabel;
  18692. var offset = 0;
  18693. if (this.height){
  18694. offset = this.height / 2;
  18695. var labelDimensions = this.getTextSize(ctx);
  18696. if (labelDimensions.lineCount >= 1){
  18697. offset += labelDimensions.height / 2;
  18698. offset += 3;
  18699. }
  18700. }
  18701. yLabel = this.y + offset;
  18702. this._label(ctx, this.label, this.x, yLabel, undefined);
  18703. };
  18704. Node.prototype._drawImage = function (ctx) {
  18705. this._resizeImage(ctx);
  18706. this.left = this.x - this.width / 2;
  18707. this.top = this.y - this.height / 2;
  18708. this._drawImageAtPosition(ctx);
  18709. this.boundingBox.top = this.top;
  18710. this.boundingBox.left = this.left;
  18711. this.boundingBox.right = this.left + this.width;
  18712. this.boundingBox.bottom = this.top + this.height;
  18713. this._drawImageLabel(ctx);
  18714. this.boundingBox.left = Math.min(this.boundingBox.left, this.labelDimensions.left);
  18715. this.boundingBox.right = Math.max(this.boundingBox.right, this.labelDimensions.left + this.labelDimensions.width);
  18716. this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelDimensions.height);
  18717. };
  18718. Node.prototype._resizeCircularImage = function (ctx) {
  18719. if(!this.imageObj.src || !this.imageObj.width || !this.imageObj.height){
  18720. if (!this.width) {
  18721. var diameter = this.options.radius * 2;
  18722. this.width = diameter;
  18723. this.height = diameter;
  18724. // scaling used for clustering
  18725. //this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  18726. //this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  18727. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  18728. this.growthIndicator = this.options.radius- 0.5*diameter;
  18729. this._swapToImageResizeWhenImageLoaded = true;
  18730. }
  18731. }
  18732. else {
  18733. if (this._swapToImageResizeWhenImageLoaded) {
  18734. this.width = 0;
  18735. this.height = 0;
  18736. delete this._swapToImageResizeWhenImageLoaded;
  18737. }
  18738. this._resizeImage(ctx);
  18739. }
  18740. };
  18741. Node.prototype._drawCircularImage = function (ctx) {
  18742. this._resizeCircularImage(ctx);
  18743. this.left = this.x - this.width / 2;
  18744. this.top = this.y - this.height / 2;
  18745. var centerX = this.left + (this.width / 2);
  18746. var centerY = this.top + (this.height / 2);
  18747. var radius = Math.abs(this.height / 2);
  18748. this._drawRawCircle(ctx, centerX, centerY, radius);
  18749. ctx.save();
  18750. ctx.circle(this.x, this.y, radius);
  18751. ctx.stroke();
  18752. ctx.clip();
  18753. this._drawImageAtPosition(ctx);
  18754. ctx.restore();
  18755. this.boundingBox.top = this.y - this.options.radius;
  18756. this.boundingBox.left = this.x - this.options.radius;
  18757. this.boundingBox.right = this.x + this.options.radius;
  18758. this.boundingBox.bottom = this.y + this.options.radius;
  18759. this._drawImageLabel(ctx);
  18760. this.boundingBox.left = Math.min(this.boundingBox.left, this.labelDimensions.left);
  18761. this.boundingBox.right = Math.max(this.boundingBox.right, this.labelDimensions.left + this.labelDimensions.width);
  18762. this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelDimensions.height);
  18763. };
  18764. Node.prototype._resizeBox = function (ctx) {
  18765. if (!this.width) {
  18766. var margin = 5;
  18767. var textSize = this.getTextSize(ctx);
  18768. this.width = textSize.width + 2 * margin;
  18769. this.height = textSize.height + 2 * margin;
  18770. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  18771. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  18772. this.growthIndicator = this.width - (textSize.width + 2 * margin);
  18773. // this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  18774. }
  18775. };
  18776. Node.prototype._drawBox = function (ctx) {
  18777. this._resizeBox(ctx);
  18778. this.left = this.x - this.width / 2;
  18779. this.top = this.y - this.height / 2;
  18780. var clusterLineWidth = 2.5;
  18781. var borderWidth = this.options.borderWidth;
  18782. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  18783. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  18784. // draw the outer border
  18785. if (this.clusterSize > 1) {
  18786. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18787. ctx.lineWidth *= this.networkScaleInv;
  18788. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18789. 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);
  18790. ctx.stroke();
  18791. }
  18792. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18793. ctx.lineWidth *= this.networkScaleInv;
  18794. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18795. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  18796. ctx.roundRect(this.left, this.top, this.width, this.height, this.options.radius);
  18797. ctx.fill();
  18798. ctx.stroke();
  18799. this.boundingBox.top = this.top;
  18800. this.boundingBox.left = this.left;
  18801. this.boundingBox.right = this.left + this.width;
  18802. this.boundingBox.bottom = this.top + this.height;
  18803. this._label(ctx, this.label, this.x, this.y);
  18804. };
  18805. Node.prototype._resizeDatabase = function (ctx) {
  18806. if (!this.width) {
  18807. var margin = 5;
  18808. var textSize = this.getTextSize(ctx);
  18809. var size = textSize.width + 2 * margin;
  18810. this.width = size;
  18811. this.height = size;
  18812. // scaling used for clustering
  18813. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  18814. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  18815. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  18816. this.growthIndicator = this.width - size;
  18817. }
  18818. };
  18819. Node.prototype._drawDatabase = function (ctx) {
  18820. this._resizeDatabase(ctx);
  18821. this.left = this.x - this.width / 2;
  18822. this.top = this.y - this.height / 2;
  18823. var clusterLineWidth = 2.5;
  18824. var borderWidth = this.options.borderWidth;
  18825. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  18826. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  18827. // draw the outer border
  18828. if (this.clusterSize > 1) {
  18829. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18830. ctx.lineWidth *= this.networkScaleInv;
  18831. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18832. 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);
  18833. ctx.stroke();
  18834. }
  18835. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18836. ctx.lineWidth *= this.networkScaleInv;
  18837. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18838. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  18839. ctx.database(this.x - this.width/2, this.y - this.height*0.5, this.width, this.height);
  18840. ctx.fill();
  18841. ctx.stroke();
  18842. this.boundingBox.top = this.top;
  18843. this.boundingBox.left = this.left;
  18844. this.boundingBox.right = this.left + this.width;
  18845. this.boundingBox.bottom = this.top + this.height;
  18846. this._label(ctx, this.label, this.x, this.y);
  18847. };
  18848. Node.prototype._resizeCircle = function (ctx) {
  18849. if (!this.width) {
  18850. var margin = 5;
  18851. var textSize = this.getTextSize(ctx);
  18852. var diameter = Math.max(textSize.width, textSize.height) + 2 * margin;
  18853. this.options.radius = diameter / 2;
  18854. this.width = diameter;
  18855. this.height = diameter;
  18856. // scaling used for clustering
  18857. // this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  18858. // this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  18859. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  18860. this.growthIndicator = this.options.radius- 0.5*diameter;
  18861. }
  18862. };
  18863. Node.prototype._drawRawCircle = function (ctx, x, y, radius) {
  18864. var clusterLineWidth = 2.5;
  18865. var borderWidth = this.options.borderWidth;
  18866. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  18867. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  18868. // draw the outer border
  18869. if (this.clusterSize > 1) {
  18870. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18871. ctx.lineWidth *= this.networkScaleInv;
  18872. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18873. ctx.circle(x, y, radius+2*ctx.lineWidth);
  18874. ctx.stroke();
  18875. }
  18876. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18877. ctx.lineWidth *= this.networkScaleInv;
  18878. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18879. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  18880. ctx.circle(this.x, this.y, radius);
  18881. ctx.fill();
  18882. ctx.stroke();
  18883. };
  18884. Node.prototype._drawCircle = function (ctx) {
  18885. this._resizeCircle(ctx);
  18886. this.left = this.x - this.width / 2;
  18887. this.top = this.y - this.height / 2;
  18888. this._drawRawCircle(ctx, this.x, this.y, this.options.radius);
  18889. this.boundingBox.top = this.y - this.options.radius;
  18890. this.boundingBox.left = this.x - this.options.radius;
  18891. this.boundingBox.right = this.x + this.options.radius;
  18892. this.boundingBox.bottom = this.y + this.options.radius;
  18893. this._label(ctx, this.label, this.x, this.y);
  18894. };
  18895. Node.prototype._resizeEllipse = function (ctx) {
  18896. if (!this.width) {
  18897. var textSize = this.getTextSize(ctx);
  18898. this.width = textSize.width * 1.5;
  18899. this.height = textSize.height * 2;
  18900. if (this.width < this.height) {
  18901. this.width = this.height;
  18902. }
  18903. var defaultSize = this.width;
  18904. // scaling used for clustering
  18905. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  18906. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  18907. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  18908. this.growthIndicator = this.width - defaultSize;
  18909. }
  18910. };
  18911. Node.prototype._drawEllipse = function (ctx) {
  18912. this._resizeEllipse(ctx);
  18913. this.left = this.x - this.width / 2;
  18914. this.top = this.y - this.height / 2;
  18915. var clusterLineWidth = 2.5;
  18916. var borderWidth = this.options.borderWidth;
  18917. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  18918. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  18919. // draw the outer border
  18920. if (this.clusterSize > 1) {
  18921. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18922. ctx.lineWidth *= this.networkScaleInv;
  18923. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18924. ctx.ellipse(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth);
  18925. ctx.stroke();
  18926. }
  18927. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18928. ctx.lineWidth *= this.networkScaleInv;
  18929. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18930. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  18931. ctx.ellipse(this.left, this.top, this.width, this.height);
  18932. ctx.fill();
  18933. ctx.stroke();
  18934. this.boundingBox.top = this.top;
  18935. this.boundingBox.left = this.left;
  18936. this.boundingBox.right = this.left + this.width;
  18937. this.boundingBox.bottom = this.top + this.height;
  18938. this._label(ctx, this.label, this.x, this.y);
  18939. };
  18940. Node.prototype._drawDot = function (ctx) {
  18941. this._drawShape(ctx, 'circle');
  18942. };
  18943. Node.prototype._drawTriangle = function (ctx) {
  18944. this._drawShape(ctx, 'triangle');
  18945. };
  18946. Node.prototype._drawTriangleDown = function (ctx) {
  18947. this._drawShape(ctx, 'triangleDown');
  18948. };
  18949. Node.prototype._drawSquare = function (ctx) {
  18950. this._drawShape(ctx, 'square');
  18951. };
  18952. Node.prototype._drawStar = function (ctx) {
  18953. this._drawShape(ctx, 'star');
  18954. };
  18955. Node.prototype._resizeShape = function (ctx) {
  18956. if (!this.width) {
  18957. this.options.radius= this.baseRadiusValue;
  18958. var size = 2 * this.options.radius;
  18959. this.width = size;
  18960. this.height = size;
  18961. // scaling used for clustering
  18962. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  18963. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  18964. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  18965. this.growthIndicator = this.width - size;
  18966. }
  18967. };
  18968. Node.prototype._drawShape = function (ctx, shape) {
  18969. this._resizeShape(ctx);
  18970. this.left = this.x - this.width / 2;
  18971. this.top = this.y - this.height / 2;
  18972. var clusterLineWidth = 2.5;
  18973. var borderWidth = this.options.borderWidth;
  18974. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  18975. var radiusMultiplier = 2;
  18976. // choose draw method depending on the shape
  18977. switch (shape) {
  18978. case 'dot': radiusMultiplier = 2; break;
  18979. case 'square': radiusMultiplier = 2; break;
  18980. case 'triangle': radiusMultiplier = 3; break;
  18981. case 'triangleDown': radiusMultiplier = 3; break;
  18982. case 'star': radiusMultiplier = 4; break;
  18983. }
  18984. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  18985. // draw the outer border
  18986. if (this.clusterSize > 1) {
  18987. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18988. ctx.lineWidth *= this.networkScaleInv;
  18989. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18990. ctx[shape](this.x, this.y, this.options.radius+ radiusMultiplier * ctx.lineWidth);
  18991. ctx.stroke();
  18992. }
  18993. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18994. ctx.lineWidth *= this.networkScaleInv;
  18995. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18996. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  18997. ctx[shape](this.x, this.y, this.options.radius);
  18998. ctx.fill();
  18999. ctx.stroke();
  19000. this.boundingBox.top = this.y - this.options.radius;
  19001. this.boundingBox.left = this.x - this.options.radius;
  19002. this.boundingBox.right = this.x + this.options.radius;
  19003. this.boundingBox.bottom = this.y + this.options.radius;
  19004. if (this.label) {
  19005. this._label(ctx, this.label, this.x, this.y + this.height / 2, undefined, 'hanging',true);
  19006. this.boundingBox.left = Math.min(this.boundingBox.left, this.labelDimensions.left);
  19007. this.boundingBox.right = Math.max(this.boundingBox.right, this.labelDimensions.left + this.labelDimensions.width);
  19008. this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelDimensions.height);
  19009. }
  19010. };
  19011. Node.prototype._resizeText = function (ctx) {
  19012. if (!this.width) {
  19013. var margin = 5;
  19014. var textSize = this.getTextSize(ctx);
  19015. this.width = textSize.width + 2 * margin;
  19016. this.height = textSize.height + 2 * margin;
  19017. // scaling used for clustering
  19018. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  19019. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  19020. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  19021. this.growthIndicator = this.width - (textSize.width + 2 * margin);
  19022. }
  19023. };
  19024. Node.prototype._drawText = function (ctx) {
  19025. this._resizeText(ctx);
  19026. this.left = this.x - this.width / 2;
  19027. this.top = this.y - this.height / 2;
  19028. this._label(ctx, this.label, this.x, this.y);
  19029. this.boundingBox.top = this.top;
  19030. this.boundingBox.left = this.left;
  19031. this.boundingBox.right = this.left + this.width;
  19032. this.boundingBox.bottom = this.top + this.height;
  19033. };
  19034. Node.prototype._resizeIcon = function (ctx) {
  19035. if (!this.width) {
  19036. var margin = 5;
  19037. var iconSize =
  19038. {
  19039. width: Number(this.options.iconSize),
  19040. height: Number(this.options.iconSize)
  19041. };
  19042. this.width = iconSize.width + 2 * margin;
  19043. this.height = iconSize.height + 2 * margin;
  19044. // scaling used for clustering
  19045. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  19046. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  19047. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  19048. this.growthIndicator = this.width - (iconSize.width + 2 * margin);
  19049. }
  19050. };
  19051. Node.prototype._drawIcon = function (ctx) {
  19052. this._resizeIcon(ctx);
  19053. this.options.iconSize = this.options.iconSize || 50;
  19054. this.left = this.x - this.width / 2;
  19055. this.top = this.y - this.height / 2;
  19056. this._icon(ctx);
  19057. this.boundingBox.top = this.y - this.options.iconSize/2;
  19058. this.boundingBox.left = this.x - this.options.iconSize/2;
  19059. this.boundingBox.right = this.x + this.options.iconSize/2;
  19060. this.boundingBox.bottom = this.y + this.options.iconSize/2;
  19061. if (this.label) {
  19062. var iconTextSpacing = 5;
  19063. this._label(ctx, this.label, this.x, this.y + this.height / 2 + iconTextSpacing, 'top', true);
  19064. this.boundingBox.left = Math.min(this.boundingBox.left, this.labelDimensions.left);
  19065. this.boundingBox.right = Math.max(this.boundingBox.right, this.labelDimensions.left + this.labelDimensions.width);
  19066. this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelDimensions.height);
  19067. }
  19068. };
  19069. Node.prototype._icon = function (ctx) {
  19070. var relativeIconSize = Number(this.options.iconSize) * this.networkScale;
  19071. if (this.options.icon && relativeIconSize > this.options.fontDrawThreshold - 1) {
  19072. var iconSize = Number(this.options.iconSize);
  19073. ctx.font = (this.selected ? "bold " : "") + iconSize + "px " + this.options.iconFontFace;
  19074. // draw icon
  19075. ctx.fillStyle = this.options.iconColor || "black";
  19076. ctx.textAlign = "center";
  19077. ctx.textBaseline = "middle";
  19078. ctx.fillText(this.options.icon, this.x, this.y);
  19079. }
  19080. };
  19081. Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNode) {
  19082. var relativeFontSize = Number(this.options.fontSize) * this.networkScale;
  19083. if (text && relativeFontSize >= this.options.fontDrawThreshold - 1) {
  19084. var fontSize = Number(this.options.fontSize);
  19085. // this ensures that there will not be HUGE letters on screen by setting an upper limit on the visible text size (regardless of zoomLevel)
  19086. if (relativeFontSize >= this.options.fontSizeMaxVisible) {
  19087. fontSize = Number(this.options.fontSizeMaxVisible) * this.networkScaleInv;
  19088. }
  19089. // fade in when relative scale is between threshold and threshold - 1
  19090. var fontColor = this.options.fontColor || "#000000";
  19091. var strokecolor = this.options.fontStrokeColor;
  19092. if (relativeFontSize <= this.options.fontDrawThreshold) {
  19093. var opacity = Math.max(0,Math.min(1,1 - (this.options.fontDrawThreshold - relativeFontSize)));
  19094. fontColor = util.overrideOpacity(fontColor, opacity);
  19095. strokecolor = util.overrideOpacity(strokecolor, opacity);
  19096. }
  19097. ctx.font = (this.selected ? "bold " : "") + fontSize + "px " + this.options.fontFace;
  19098. var lines = text.split('\n');
  19099. var lineCount = lines.length;
  19100. var yLine = y + (1 - lineCount) / 2 * fontSize;
  19101. if (labelUnderNode == true) {
  19102. yLine = y + (1 - lineCount) / (2 * fontSize);
  19103. }
  19104. // font fill from edges now for nodes!
  19105. var width = ctx.measureText(lines[0]).width;
  19106. for (var i = 1; i < lineCount; i++) {
  19107. var lineWidth = ctx.measureText(lines[i]).width;
  19108. width = lineWidth > width ? lineWidth : width;
  19109. }
  19110. var height = fontSize * lineCount;
  19111. var left = x - width / 2;
  19112. var top = y - height / 2;
  19113. if (baseline == "hanging") {
  19114. top += 0.5 * fontSize;
  19115. top += 4; // distance from node, required because we use hanging. Hanging has less difference between browsers
  19116. yLine += 4; // distance from node
  19117. }
  19118. this.labelDimensions = {top:top,left:left,width:width,height:height,yLine:yLine};
  19119. // create the fontfill background
  19120. if (this.options.fontFill !== undefined && this.options.fontFill !== null && this.options.fontFill !== "none") {
  19121. ctx.fillStyle = this.options.fontFill;
  19122. ctx.fillRect(left, top, width, height);
  19123. }
  19124. // draw text
  19125. ctx.fillStyle = fontColor;
  19126. ctx.textAlign = align || "center";
  19127. ctx.textBaseline = baseline || "middle";
  19128. if (this.options.fontStrokeWidth > 0){
  19129. ctx.lineWidth = this.options.fontStrokeWidth;
  19130. ctx.strokeStyle = strokecolor;
  19131. ctx.lineJoin = 'round';
  19132. }
  19133. for (var i = 0; i < lineCount; i++) {
  19134. if(this.options.fontStrokeWidth){
  19135. ctx.strokeText(lines[i], x, yLine);
  19136. }
  19137. ctx.fillText(lines[i], x, yLine);
  19138. yLine += fontSize;
  19139. }
  19140. }
  19141. };
  19142. Node.prototype.getTextSize = function(ctx) {
  19143. if (this.label !== undefined) {
  19144. var fontSize = Number(this.options.fontSize);
  19145. if (fontSize * this.networkScale > this.options.fontSizeMaxVisible) {
  19146. fontSize = Number(this.options.fontSizeMaxVisible) * this.networkScaleInv;
  19147. }
  19148. ctx.font = (this.selected ? "bold " : "") + fontSize + "px " + this.options.fontFace;
  19149. var lines = this.label.split('\n'),
  19150. height = (fontSize + 4) * lines.length,
  19151. width = 0;
  19152. for (var i = 0, iMax = lines.length; i < iMax; i++) {
  19153. width = Math.max(width, ctx.measureText(lines[i]).width);
  19154. }
  19155. return {"width": width, "height": height, lineCount: lines.length};
  19156. }
  19157. else {
  19158. return {"width": 0, "height": 0, lineCount: 0};
  19159. }
  19160. };
  19161. /**
  19162. * this is used to determine if a node is visible at all. this is used to determine when it needs to be drawn.
  19163. * there is a safety margin of 0.3 * width;
  19164. *
  19165. * @returns {boolean}
  19166. */
  19167. Node.prototype.inArea = function() {
  19168. if (this.width !== undefined) {
  19169. return (this.x + this.width *this.networkScaleInv >= this.canvasTopLeft.x &&
  19170. this.x - this.width *this.networkScaleInv < this.canvasBottomRight.x &&
  19171. this.y + this.height*this.networkScaleInv >= this.canvasTopLeft.y &&
  19172. this.y - this.height*this.networkScaleInv < this.canvasBottomRight.y);
  19173. }
  19174. else {
  19175. return true;
  19176. }
  19177. };
  19178. /**
  19179. * checks if the core of the node is in the display area, this is used for opening clusters around zoom
  19180. * @returns {boolean}
  19181. */
  19182. Node.prototype.inView = function() {
  19183. return (this.x >= this.canvasTopLeft.x &&
  19184. this.x < this.canvasBottomRight.x &&
  19185. this.y >= this.canvasTopLeft.y &&
  19186. this.y < this.canvasBottomRight.y);
  19187. };
  19188. /**
  19189. * This allows the zoom level of the network to influence the rendering
  19190. * We store the inverted scale and the coordinates of the top left, and bottom right points of the canvas
  19191. *
  19192. * @param scale
  19193. * @param canvasTopLeft
  19194. * @param canvasBottomRight
  19195. */
  19196. Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight) {
  19197. this.networkScaleInv = 1.0/scale;
  19198. this.networkScale = scale;
  19199. this.canvasTopLeft = canvasTopLeft;
  19200. this.canvasBottomRight = canvasBottomRight;
  19201. };
  19202. /**
  19203. * This allows the zoom level of the network to influence the rendering
  19204. *
  19205. * @param scale
  19206. */
  19207. Node.prototype.setScale = function(scale) {
  19208. this.networkScaleInv = 1.0/scale;
  19209. this.networkScale = scale;
  19210. };
  19211. /**
  19212. * set the velocity at 0. Is called when this node is contained in another during clustering
  19213. */
  19214. Node.prototype.clearVelocity = function() {
  19215. this.vx = 0;
  19216. this.vy = 0;
  19217. };
  19218. /**
  19219. * Basic preservation of (kinectic) energy
  19220. *
  19221. * @param massBeforeClustering
  19222. */
  19223. Node.prototype.updateVelocity = function(massBeforeClustering) {
  19224. var energyBefore = this.vx * this.vx * massBeforeClustering;
  19225. //this.vx = (this.vx < 0) ? -Math.sqrt(energyBefore/this.options.mass) : Math.sqrt(energyBefore/this.options.mass);
  19226. this.vx = Math.sqrt(energyBefore/this.options.mass);
  19227. energyBefore = this.vy * this.vy * massBeforeClustering;
  19228. //this.vy = (this.vy < 0) ? -Math.sqrt(energyBefore/this.options.mass) : Math.sqrt(energyBefore/this.options.mass);
  19229. this.vy = Math.sqrt(energyBefore/this.options.mass);
  19230. };
  19231. module.exports = Node;
  19232. /***/ },
  19233. /* 41 */
  19234. /***/ function(module, exports, __webpack_require__) {
  19235. /**
  19236. * Popup is a class to create a popup window with some text
  19237. * @param {Element} container The container object.
  19238. * @param {Number} [x]
  19239. * @param {Number} [y]
  19240. * @param {String} [text]
  19241. * @param {Object} [style] An object containing borderColor,
  19242. * backgroundColor, etc.
  19243. */
  19244. function Popup(container, x, y, text, style) {
  19245. if (container) {
  19246. this.container = container;
  19247. }
  19248. else {
  19249. this.container = document.body;
  19250. }
  19251. // x, y and text are optional, see if a style object was passed in their place
  19252. if (style === undefined) {
  19253. if (typeof x === "object") {
  19254. style = x;
  19255. x = undefined;
  19256. } else if (typeof text === "object") {
  19257. style = text;
  19258. text = undefined;
  19259. } else {
  19260. // for backwards compatibility, in case clients other than Network are creating Popup directly
  19261. style = {
  19262. fontColor: 'black',
  19263. fontSize: 14, // px
  19264. fontFace: 'verdana',
  19265. color: {
  19266. border: '#666',
  19267. background: '#FFFFC6'
  19268. }
  19269. }
  19270. }
  19271. }
  19272. this.x = 0;
  19273. this.y = 0;
  19274. this.padding = 5;
  19275. this.hidden = false;
  19276. if (x !== undefined && y !== undefined) {
  19277. this.setPosition(x, y);
  19278. }
  19279. if (text !== undefined) {
  19280. this.setText(text);
  19281. }
  19282. // create the frame
  19283. this.frame = document.createElement('div');
  19284. this.frame.className = 'network-tooltip';
  19285. this.frame.style.color = style.fontColor;
  19286. this.frame.style.backgroundColor = style.color.background;
  19287. this.frame.style.borderColor = style.color.border;
  19288. this.frame.style.fontSize = style.fontSize + 'px';
  19289. this.frame.style.fontFamily = style.fontFace;
  19290. this.container.appendChild(this.frame);
  19291. }
  19292. /**
  19293. * @param {number} x Horizontal position of the popup window
  19294. * @param {number} y Vertical position of the popup window
  19295. */
  19296. Popup.prototype.setPosition = function(x, y) {
  19297. this.x = parseInt(x);
  19298. this.y = parseInt(y);
  19299. };
  19300. /**
  19301. * Set the content for the popup window. This can be HTML code or text.
  19302. * @param {string | Element} content
  19303. */
  19304. Popup.prototype.setText = function(content) {
  19305. if (content instanceof Element) {
  19306. this.frame.innerHTML = '';
  19307. this.frame.appendChild(content);
  19308. }
  19309. else {
  19310. this.frame.innerHTML = content; // string containing text or HTML
  19311. }
  19312. };
  19313. /**
  19314. * Show the popup window
  19315. * @param {boolean} show Optional. Show or hide the window
  19316. */
  19317. Popup.prototype.show = function (show) {
  19318. if (show === undefined) {
  19319. show = true;
  19320. }
  19321. if (show) {
  19322. var height = this.frame.clientHeight;
  19323. var width = this.frame.clientWidth;
  19324. var maxHeight = this.frame.parentNode.clientHeight;
  19325. var maxWidth = this.frame.parentNode.clientWidth;
  19326. var top = (this.y - height);
  19327. if (top + height + this.padding > maxHeight) {
  19328. top = maxHeight - height - this.padding;
  19329. }
  19330. if (top < this.padding) {
  19331. top = this.padding;
  19332. }
  19333. var left = this.x;
  19334. if (left + width + this.padding > maxWidth) {
  19335. left = maxWidth - width - this.padding;
  19336. }
  19337. if (left < this.padding) {
  19338. left = this.padding;
  19339. }
  19340. this.frame.style.left = left + "px";
  19341. this.frame.style.top = top + "px";
  19342. this.frame.style.visibility = "visible";
  19343. this.hidden = false;
  19344. }
  19345. else {
  19346. this.hide();
  19347. }
  19348. };
  19349. /**
  19350. * Hide the popup window
  19351. */
  19352. Popup.prototype.hide = function () {
  19353. this.hidden = true;
  19354. this.frame.style.visibility = "hidden";
  19355. };
  19356. module.exports = Popup;
  19357. /***/ },
  19358. /* 42 */
  19359. /***/ function(module, exports, __webpack_require__) {
  19360. /**
  19361. * Parse a text source containing data in DOT language into a JSON object.
  19362. * The object contains two lists: one with nodes and one with edges.
  19363. *
  19364. * DOT language reference: http://www.graphviz.org/doc/info/lang.html
  19365. *
  19366. * @param {String} data Text containing a graph in DOT-notation
  19367. * @return {Object} graph An object containing two parameters:
  19368. * {Object[]} nodes
  19369. * {Object[]} edges
  19370. */
  19371. function parseDOT (data) {
  19372. dot = data;
  19373. return parseGraph();
  19374. }
  19375. // token types enumeration
  19376. var TOKENTYPE = {
  19377. NULL : 0,
  19378. DELIMITER : 1,
  19379. IDENTIFIER: 2,
  19380. UNKNOWN : 3
  19381. };
  19382. // map with all delimiters
  19383. var DELIMITERS = {
  19384. '{': true,
  19385. '}': true,
  19386. '[': true,
  19387. ']': true,
  19388. ';': true,
  19389. '=': true,
  19390. ',': true,
  19391. '->': true,
  19392. '--': true
  19393. };
  19394. var dot = ''; // current dot file
  19395. var index = 0; // current index in dot file
  19396. var c = ''; // current token character in expr
  19397. var token = ''; // current token
  19398. var tokenType = TOKENTYPE.NULL; // type of the token
  19399. /**
  19400. * Get the first character from the dot file.
  19401. * The character is stored into the char c. If the end of the dot file is
  19402. * reached, the function puts an empty string in c.
  19403. */
  19404. function first() {
  19405. index = 0;
  19406. c = dot.charAt(0);
  19407. }
  19408. /**
  19409. * Get the next character from the dot file.
  19410. * The character is stored into the char c. If the end of the dot file is
  19411. * reached, the function puts an empty string in c.
  19412. */
  19413. function next() {
  19414. index++;
  19415. c = dot.charAt(index);
  19416. }
  19417. /**
  19418. * Preview the next character from the dot file.
  19419. * @return {String} cNext
  19420. */
  19421. function nextPreview() {
  19422. return dot.charAt(index + 1);
  19423. }
  19424. /**
  19425. * Test whether given character is alphabetic or numeric
  19426. * @param {String} c
  19427. * @return {Boolean} isAlphaNumeric
  19428. */
  19429. var regexAlphaNumeric = /[a-zA-Z_0-9.:#]/;
  19430. function isAlphaNumeric(c) {
  19431. return regexAlphaNumeric.test(c);
  19432. }
  19433. /**
  19434. * Merge all properties of object b into object b
  19435. * @param {Object} a
  19436. * @param {Object} b
  19437. * @return {Object} a
  19438. */
  19439. function merge (a, b) {
  19440. if (!a) {
  19441. a = {};
  19442. }
  19443. if (b) {
  19444. for (var name in b) {
  19445. if (b.hasOwnProperty(name)) {
  19446. a[name] = b[name];
  19447. }
  19448. }
  19449. }
  19450. return a;
  19451. }
  19452. /**
  19453. * Set a value in an object, where the provided parameter name can be a
  19454. * path with nested parameters. For example:
  19455. *
  19456. * var obj = {a: 2};
  19457. * setValue(obj, 'b.c', 3); // obj = {a: 2, b: {c: 3}}
  19458. *
  19459. * @param {Object} obj
  19460. * @param {String} path A parameter name or dot-separated parameter path,
  19461. * like "color.highlight.border".
  19462. * @param {*} value
  19463. */
  19464. function setValue(obj, path, value) {
  19465. var keys = path.split('.');
  19466. var o = obj;
  19467. while (keys.length) {
  19468. var key = keys.shift();
  19469. if (keys.length) {
  19470. // this isn't the end point
  19471. if (!o[key]) {
  19472. o[key] = {};
  19473. }
  19474. o = o[key];
  19475. }
  19476. else {
  19477. // this is the end point
  19478. o[key] = value;
  19479. }
  19480. }
  19481. }
  19482. /**
  19483. * Add a node to a graph object. If there is already a node with
  19484. * the same id, their attributes will be merged.
  19485. * @param {Object} graph
  19486. * @param {Object} node
  19487. */
  19488. function addNode(graph, node) {
  19489. var i, len;
  19490. var current = null;
  19491. // find root graph (in case of subgraph)
  19492. var graphs = [graph]; // list with all graphs from current graph to root graph
  19493. var root = graph;
  19494. while (root.parent) {
  19495. graphs.push(root.parent);
  19496. root = root.parent;
  19497. }
  19498. // find existing node (at root level) by its id
  19499. if (root.nodes) {
  19500. for (i = 0, len = root.nodes.length; i < len; i++) {
  19501. if (node.id === root.nodes[i].id) {
  19502. current = root.nodes[i];
  19503. break;
  19504. }
  19505. }
  19506. }
  19507. if (!current) {
  19508. // this is a new node
  19509. current = {
  19510. id: node.id
  19511. };
  19512. if (graph.node) {
  19513. // clone default attributes
  19514. current.attr = merge(current.attr, graph.node);
  19515. }
  19516. }
  19517. // add node to this (sub)graph and all its parent graphs
  19518. for (i = graphs.length - 1; i >= 0; i--) {
  19519. var g = graphs[i];
  19520. if (!g.nodes) {
  19521. g.nodes = [];
  19522. }
  19523. if (g.nodes.indexOf(current) == -1) {
  19524. g.nodes.push(current);
  19525. }
  19526. }
  19527. // merge attributes
  19528. if (node.attr) {
  19529. current.attr = merge(current.attr, node.attr);
  19530. }
  19531. }
  19532. /**
  19533. * Add an edge to a graph object
  19534. * @param {Object} graph
  19535. * @param {Object} edge
  19536. */
  19537. function addEdge(graph, edge) {
  19538. if (!graph.edges) {
  19539. graph.edges = [];
  19540. }
  19541. graph.edges.push(edge);
  19542. if (graph.edge) {
  19543. var attr = merge({}, graph.edge); // clone default attributes
  19544. edge.attr = merge(attr, edge.attr); // merge attributes
  19545. }
  19546. }
  19547. /**
  19548. * Create an edge to a graph object
  19549. * @param {Object} graph
  19550. * @param {String | Number | Object} from
  19551. * @param {String | Number | Object} to
  19552. * @param {String} type
  19553. * @param {Object | null} attr
  19554. * @return {Object} edge
  19555. */
  19556. function createEdge(graph, from, to, type, attr) {
  19557. var edge = {
  19558. from: from,
  19559. to: to,
  19560. type: type
  19561. };
  19562. if (graph.edge) {
  19563. edge.attr = merge({}, graph.edge); // clone default attributes
  19564. }
  19565. edge.attr = merge(edge.attr || {}, attr); // merge attributes
  19566. return edge;
  19567. }
  19568. /**
  19569. * Get next token in the current dot file.
  19570. * The token and token type are available as token and tokenType
  19571. */
  19572. function getToken() {
  19573. tokenType = TOKENTYPE.NULL;
  19574. token = '';
  19575. // skip over whitespaces
  19576. while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter
  19577. next();
  19578. }
  19579. do {
  19580. var isComment = false;
  19581. // skip comment
  19582. if (c == '#') {
  19583. // find the previous non-space character
  19584. var i = index - 1;
  19585. while (dot.charAt(i) == ' ' || dot.charAt(i) == '\t') {
  19586. i--;
  19587. }
  19588. if (dot.charAt(i) == '\n' || dot.charAt(i) == '') {
  19589. // the # is at the start of a line, this is indeed a line comment
  19590. while (c != '' && c != '\n') {
  19591. next();
  19592. }
  19593. isComment = true;
  19594. }
  19595. }
  19596. if (c == '/' && nextPreview() == '/') {
  19597. // skip line comment
  19598. while (c != '' && c != '\n') {
  19599. next();
  19600. }
  19601. isComment = true;
  19602. }
  19603. if (c == '/' && nextPreview() == '*') {
  19604. // skip block comment
  19605. while (c != '') {
  19606. if (c == '*' && nextPreview() == '/') {
  19607. // end of block comment found. skip these last two characters
  19608. next();
  19609. next();
  19610. break;
  19611. }
  19612. else {
  19613. next();
  19614. }
  19615. }
  19616. isComment = true;
  19617. }
  19618. // skip over whitespaces
  19619. while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter
  19620. next();
  19621. }
  19622. }
  19623. while (isComment);
  19624. // check for end of dot file
  19625. if (c == '') {
  19626. // token is still empty
  19627. tokenType = TOKENTYPE.DELIMITER;
  19628. return;
  19629. }
  19630. // check for delimiters consisting of 2 characters
  19631. var c2 = c + nextPreview();
  19632. if (DELIMITERS[c2]) {
  19633. tokenType = TOKENTYPE.DELIMITER;
  19634. token = c2;
  19635. next();
  19636. next();
  19637. return;
  19638. }
  19639. // check for delimiters consisting of 1 character
  19640. if (DELIMITERS[c]) {
  19641. tokenType = TOKENTYPE.DELIMITER;
  19642. token = c;
  19643. next();
  19644. return;
  19645. }
  19646. // check for an identifier (number or string)
  19647. // TODO: more precise parsing of numbers/strings (and the port separator ':')
  19648. if (isAlphaNumeric(c) || c == '-') {
  19649. token += c;
  19650. next();
  19651. while (isAlphaNumeric(c)) {
  19652. token += c;
  19653. next();
  19654. }
  19655. if (token == 'false') {
  19656. token = false; // convert to boolean
  19657. }
  19658. else if (token == 'true') {
  19659. token = true; // convert to boolean
  19660. }
  19661. else if (!isNaN(Number(token))) {
  19662. token = Number(token); // convert to number
  19663. }
  19664. tokenType = TOKENTYPE.IDENTIFIER;
  19665. return;
  19666. }
  19667. // check for a string enclosed by double quotes
  19668. if (c == '"') {
  19669. next();
  19670. while (c != '' && (c != '"' || (c == '"' && nextPreview() == '"'))) {
  19671. token += c;
  19672. if (c == '"') { // skip the escape character
  19673. next();
  19674. }
  19675. next();
  19676. }
  19677. if (c != '"') {
  19678. throw newSyntaxError('End of string " expected');
  19679. }
  19680. next();
  19681. tokenType = TOKENTYPE.IDENTIFIER;
  19682. return;
  19683. }
  19684. // something unknown is found, wrong characters, a syntax error
  19685. tokenType = TOKENTYPE.UNKNOWN;
  19686. while (c != '') {
  19687. token += c;
  19688. next();
  19689. }
  19690. throw new SyntaxError('Syntax error in part "' + chop(token, 30) + '"');
  19691. }
  19692. /**
  19693. * Parse a graph.
  19694. * @returns {Object} graph
  19695. */
  19696. function parseGraph() {
  19697. var graph = {};
  19698. first();
  19699. getToken();
  19700. // optional strict keyword
  19701. if (token == 'strict') {
  19702. graph.strict = true;
  19703. getToken();
  19704. }
  19705. // graph or digraph keyword
  19706. if (token == 'graph' || token == 'digraph') {
  19707. graph.type = token;
  19708. getToken();
  19709. }
  19710. // optional graph id
  19711. if (tokenType == TOKENTYPE.IDENTIFIER) {
  19712. graph.id = token;
  19713. getToken();
  19714. }
  19715. // open angle bracket
  19716. if (token != '{') {
  19717. throw newSyntaxError('Angle bracket { expected');
  19718. }
  19719. getToken();
  19720. // statements
  19721. parseStatements(graph);
  19722. // close angle bracket
  19723. if (token != '}') {
  19724. throw newSyntaxError('Angle bracket } expected');
  19725. }
  19726. getToken();
  19727. // end of file
  19728. if (token !== '') {
  19729. throw newSyntaxError('End of file expected');
  19730. }
  19731. getToken();
  19732. // remove temporary default properties
  19733. delete graph.node;
  19734. delete graph.edge;
  19735. delete graph.graph;
  19736. return graph;
  19737. }
  19738. /**
  19739. * Parse a list with statements.
  19740. * @param {Object} graph
  19741. */
  19742. function parseStatements (graph) {
  19743. while (token !== '' && token != '}') {
  19744. parseStatement(graph);
  19745. if (token == ';') {
  19746. getToken();
  19747. }
  19748. }
  19749. }
  19750. /**
  19751. * Parse a single statement. Can be a an attribute statement, node
  19752. * statement, a series of node statements and edge statements, or a
  19753. * parameter.
  19754. * @param {Object} graph
  19755. */
  19756. function parseStatement(graph) {
  19757. // parse subgraph
  19758. var subgraph = parseSubgraph(graph);
  19759. if (subgraph) {
  19760. // edge statements
  19761. parseEdge(graph, subgraph);
  19762. return;
  19763. }
  19764. // parse an attribute statement
  19765. var attr = parseAttributeStatement(graph);
  19766. if (attr) {
  19767. return;
  19768. }
  19769. // parse node
  19770. if (tokenType != TOKENTYPE.IDENTIFIER) {
  19771. throw newSyntaxError('Identifier expected');
  19772. }
  19773. var id = token; // id can be a string or a number
  19774. getToken();
  19775. if (token == '=') {
  19776. // id statement
  19777. getToken();
  19778. if (tokenType != TOKENTYPE.IDENTIFIER) {
  19779. throw newSyntaxError('Identifier expected');
  19780. }
  19781. graph[id] = token;
  19782. getToken();
  19783. // TODO: implement comma separated list with "a_list: ID=ID [','] [a_list] "
  19784. }
  19785. else {
  19786. parseNodeStatement(graph, id);
  19787. }
  19788. }
  19789. /**
  19790. * Parse a subgraph
  19791. * @param {Object} graph parent graph object
  19792. * @return {Object | null} subgraph
  19793. */
  19794. function parseSubgraph (graph) {
  19795. var subgraph = null;
  19796. // optional subgraph keyword
  19797. if (token == 'subgraph') {
  19798. subgraph = {};
  19799. subgraph.type = 'subgraph';
  19800. getToken();
  19801. // optional graph id
  19802. if (tokenType == TOKENTYPE.IDENTIFIER) {
  19803. subgraph.id = token;
  19804. getToken();
  19805. }
  19806. }
  19807. // open angle bracket
  19808. if (token == '{') {
  19809. getToken();
  19810. if (!subgraph) {
  19811. subgraph = {};
  19812. }
  19813. subgraph.parent = graph;
  19814. subgraph.node = graph.node;
  19815. subgraph.edge = graph.edge;
  19816. subgraph.graph = graph.graph;
  19817. // statements
  19818. parseStatements(subgraph);
  19819. // close angle bracket
  19820. if (token != '}') {
  19821. throw newSyntaxError('Angle bracket } expected');
  19822. }
  19823. getToken();
  19824. // remove temporary default properties
  19825. delete subgraph.node;
  19826. delete subgraph.edge;
  19827. delete subgraph.graph;
  19828. delete subgraph.parent;
  19829. // register at the parent graph
  19830. if (!graph.subgraphs) {
  19831. graph.subgraphs = [];
  19832. }
  19833. graph.subgraphs.push(subgraph);
  19834. }
  19835. return subgraph;
  19836. }
  19837. /**
  19838. * parse an attribute statement like "node [shape=circle fontSize=16]".
  19839. * Available keywords are 'node', 'edge', 'graph'.
  19840. * The previous list with default attributes will be replaced
  19841. * @param {Object} graph
  19842. * @returns {String | null} keyword Returns the name of the parsed attribute
  19843. * (node, edge, graph), or null if nothing
  19844. * is parsed.
  19845. */
  19846. function parseAttributeStatement (graph) {
  19847. // attribute statements
  19848. if (token == 'node') {
  19849. getToken();
  19850. // node attributes
  19851. graph.node = parseAttributeList();
  19852. return 'node';
  19853. }
  19854. else if (token == 'edge') {
  19855. getToken();
  19856. // edge attributes
  19857. graph.edge = parseAttributeList();
  19858. return 'edge';
  19859. }
  19860. else if (token == 'graph') {
  19861. getToken();
  19862. // graph attributes
  19863. graph.graph = parseAttributeList();
  19864. return 'graph';
  19865. }
  19866. return null;
  19867. }
  19868. /**
  19869. * parse a node statement
  19870. * @param {Object} graph
  19871. * @param {String | Number} id
  19872. */
  19873. function parseNodeStatement(graph, id) {
  19874. // node statement
  19875. var node = {
  19876. id: id
  19877. };
  19878. var attr = parseAttributeList();
  19879. if (attr) {
  19880. node.attr = attr;
  19881. }
  19882. addNode(graph, node);
  19883. // edge statements
  19884. parseEdge(graph, id);
  19885. }
  19886. /**
  19887. * Parse an edge or a series of edges
  19888. * @param {Object} graph
  19889. * @param {String | Number} from Id of the from node
  19890. */
  19891. function parseEdge(graph, from) {
  19892. while (token == '->' || token == '--') {
  19893. var to;
  19894. var type = token;
  19895. getToken();
  19896. var subgraph = parseSubgraph(graph);
  19897. if (subgraph) {
  19898. to = subgraph;
  19899. }
  19900. else {
  19901. if (tokenType != TOKENTYPE.IDENTIFIER) {
  19902. throw newSyntaxError('Identifier or subgraph expected');
  19903. }
  19904. to = token;
  19905. addNode(graph, {
  19906. id: to
  19907. });
  19908. getToken();
  19909. }
  19910. // parse edge attributes
  19911. var attr = parseAttributeList();
  19912. // create edge
  19913. var edge = createEdge(graph, from, to, type, attr);
  19914. addEdge(graph, edge);
  19915. from = to;
  19916. }
  19917. }
  19918. /**
  19919. * Parse a set with attributes,
  19920. * for example [label="1.000", shape=solid]
  19921. * @return {Object | null} attr
  19922. */
  19923. function parseAttributeList() {
  19924. var attr = null;
  19925. while (token == '[') {
  19926. getToken();
  19927. attr = {};
  19928. while (token !== '' && token != ']') {
  19929. if (tokenType != TOKENTYPE.IDENTIFIER) {
  19930. throw newSyntaxError('Attribute name expected');
  19931. }
  19932. var name = token;
  19933. getToken();
  19934. if (token != '=') {
  19935. throw newSyntaxError('Equal sign = expected');
  19936. }
  19937. getToken();
  19938. if (tokenType != TOKENTYPE.IDENTIFIER) {
  19939. throw newSyntaxError('Attribute value expected');
  19940. }
  19941. var value = token;
  19942. setValue(attr, name, value); // name can be a path
  19943. getToken();
  19944. if (token ==',') {
  19945. getToken();
  19946. }
  19947. }
  19948. if (token != ']') {
  19949. throw newSyntaxError('Bracket ] expected');
  19950. }
  19951. getToken();
  19952. }
  19953. return attr;
  19954. }
  19955. /**
  19956. * Create a syntax error with extra information on current token and index.
  19957. * @param {String} message
  19958. * @returns {SyntaxError} err
  19959. */
  19960. function newSyntaxError(message) {
  19961. return new SyntaxError(message + ', got "' + chop(token, 30) + '" (char ' + index + ')');
  19962. }
  19963. /**
  19964. * Chop off text after a maximum length
  19965. * @param {String} text
  19966. * @param {Number} maxLength
  19967. * @returns {String}
  19968. */
  19969. function chop (text, maxLength) {
  19970. return (text.length <= maxLength) ? text : (text.substr(0, 27) + '...');
  19971. }
  19972. /**
  19973. * Execute a function fn for each pair of elements in two arrays
  19974. * @param {Array | *} array1
  19975. * @param {Array | *} array2
  19976. * @param {function} fn
  19977. */
  19978. function forEach2(array1, array2, fn) {
  19979. if (Array.isArray(array1)) {
  19980. array1.forEach(function (elem1) {
  19981. if (Array.isArray(array2)) {
  19982. array2.forEach(function (elem2) {
  19983. fn(elem1, elem2);
  19984. });
  19985. }
  19986. else {
  19987. fn(elem1, array2);
  19988. }
  19989. });
  19990. }
  19991. else {
  19992. if (Array.isArray(array2)) {
  19993. array2.forEach(function (elem2) {
  19994. fn(array1, elem2);
  19995. });
  19996. }
  19997. else {
  19998. fn(array1, array2);
  19999. }
  20000. }
  20001. }
  20002. /**
  20003. * Convert a string containing a graph in DOT language into a map containing
  20004. * with nodes and edges in the format of graph.
  20005. * @param {String} data Text containing a graph in DOT-notation
  20006. * @return {Object} graphData
  20007. */
  20008. function DOTToGraph (data) {
  20009. // parse the DOT file
  20010. var dotData = parseDOT(data);
  20011. var graphData = {
  20012. nodes: [],
  20013. edges: [],
  20014. options: {}
  20015. };
  20016. // copy the nodes
  20017. if (dotData.nodes) {
  20018. dotData.nodes.forEach(function (dotNode) {
  20019. var graphNode = {
  20020. id: dotNode.id,
  20021. label: String(dotNode.label || dotNode.id)
  20022. };
  20023. merge(graphNode, dotNode.attr);
  20024. if (graphNode.image) {
  20025. graphNode.shape = 'image';
  20026. }
  20027. graphData.nodes.push(graphNode);
  20028. });
  20029. }
  20030. // copy the edges
  20031. if (dotData.edges) {
  20032. /**
  20033. * Convert an edge in DOT format to an edge with VisGraph format
  20034. * @param {Object} dotEdge
  20035. * @returns {Object} graphEdge
  20036. */
  20037. var convertEdge = function (dotEdge) {
  20038. var graphEdge = {
  20039. from: dotEdge.from,
  20040. to: dotEdge.to
  20041. };
  20042. merge(graphEdge, dotEdge.attr);
  20043. graphEdge.style = (dotEdge.type == '->') ? 'arrow' : 'line';
  20044. return graphEdge;
  20045. }
  20046. dotData.edges.forEach(function (dotEdge) {
  20047. var from, to;
  20048. if (dotEdge.from instanceof Object) {
  20049. from = dotEdge.from.nodes;
  20050. }
  20051. else {
  20052. from = {
  20053. id: dotEdge.from
  20054. }
  20055. }
  20056. if (dotEdge.to instanceof Object) {
  20057. to = dotEdge.to.nodes;
  20058. }
  20059. else {
  20060. to = {
  20061. id: dotEdge.to
  20062. }
  20063. }
  20064. if (dotEdge.from instanceof Object && dotEdge.from.edges) {
  20065. dotEdge.from.edges.forEach(function (subEdge) {
  20066. var graphEdge = convertEdge(subEdge);
  20067. graphData.edges.push(graphEdge);
  20068. });
  20069. }
  20070. forEach2(from, to, function (from, to) {
  20071. var subEdge = createEdge(graphData, from.id, to.id, dotEdge.type, dotEdge.attr);
  20072. var graphEdge = convertEdge(subEdge);
  20073. graphData.edges.push(graphEdge);
  20074. });
  20075. if (dotEdge.to instanceof Object && dotEdge.to.edges) {
  20076. dotEdge.to.edges.forEach(function (subEdge) {
  20077. var graphEdge = convertEdge(subEdge);
  20078. graphData.edges.push(graphEdge);
  20079. });
  20080. }
  20081. });
  20082. }
  20083. // copy the options
  20084. if (dotData.attr) {
  20085. graphData.options = dotData.attr;
  20086. }
  20087. return graphData;
  20088. }
  20089. // exports
  20090. exports.parseDOT = parseDOT;
  20091. exports.DOTToGraph = DOTToGraph;
  20092. /***/ },
  20093. /* 43 */
  20094. /***/ function(module, exports, __webpack_require__) {
  20095. function parseGephi(gephiJSON, options) {
  20096. var edges = [];
  20097. var nodes = [];
  20098. this.options = {
  20099. edges: {
  20100. inheritColor: true
  20101. },
  20102. nodes: {
  20103. allowedToMove: false,
  20104. parseColor: false
  20105. }
  20106. };
  20107. if (options !== undefined) {
  20108. this.options.nodes['allowedToMove'] = options.allowedToMove | false;
  20109. this.options.nodes['parseColor'] = options.parseColor | false;
  20110. this.options.edges['inheritColor'] = options.inheritColor | true;
  20111. }
  20112. var gEdges = gephiJSON.edges;
  20113. var gNodes = gephiJSON.nodes;
  20114. for (var i = 0; i < gEdges.length; i++) {
  20115. var edge = {};
  20116. var gEdge = gEdges[i];
  20117. edge['id'] = gEdge.id;
  20118. edge['from'] = gEdge.source;
  20119. edge['to'] = gEdge.target;
  20120. edge['attributes'] = gEdge.attributes;
  20121. // edge['value'] = gEdge.attributes !== undefined ? gEdge.attributes.Weight : undefined;
  20122. // edge['width'] = edge['value'] !== undefined ? undefined : edgegEdge.size;
  20123. edge['color'] = gEdge.color;
  20124. edge['inheritColor'] = edge['color'] !== undefined ? false : this.options.inheritColor;
  20125. edges.push(edge);
  20126. }
  20127. for (var i = 0; i < gNodes.length; i++) {
  20128. var node = {};
  20129. var gNode = gNodes[i];
  20130. node['id'] = gNode.id;
  20131. node['attributes'] = gNode.attributes;
  20132. node['x'] = gNode.x;
  20133. node['y'] = gNode.y;
  20134. node['label'] = gNode.label;
  20135. if (this.options.nodes.parseColor == true) {
  20136. node['color'] = gNode.color;
  20137. }
  20138. else {
  20139. node['color'] = gNode.color !== undefined ? {background:gNode.color, border:gNode.color} : undefined;
  20140. }
  20141. node['radius'] = gNode.size;
  20142. node['allowedToMoveX'] = this.options.nodes.allowedToMove;
  20143. node['allowedToMoveY'] = this.options.nodes.allowedToMove;
  20144. nodes.push(node);
  20145. }
  20146. return {nodes:nodes, edges:edges};
  20147. }
  20148. exports.parseGephi = parseGephi;
  20149. /***/ },
  20150. /* 44 */
  20151. /***/ function(module, exports, __webpack_require__) {
  20152. // first check if moment.js is already loaded in the browser window, if so,
  20153. // use this instance. Else, load via commonjs.
  20154. module.exports = (typeof window !== 'undefined') && window['moment'] || __webpack_require__(58);
  20155. /***/ },
  20156. /* 45 */
  20157. /***/ function(module, exports, __webpack_require__) {
  20158. // Only load hammer.js when in a browser environment
  20159. // (loading hammer.js in a node.js environment gives errors)
  20160. if (typeof window !== 'undefined') {
  20161. module.exports = window['Hammer'] || __webpack_require__(59);
  20162. }
  20163. else {
  20164. module.exports = function () {
  20165. throw Error('hammer.js is only available in a browser, not in node.js.');
  20166. }
  20167. }
  20168. /***/ },
  20169. /* 46 */
  20170. /***/ function(module, exports, __webpack_require__) {
  20171. var Emitter = __webpack_require__(56);
  20172. var Hammer = __webpack_require__(45);
  20173. var util = __webpack_require__(1);
  20174. var DataSet = __webpack_require__(3);
  20175. var DataView = __webpack_require__(4);
  20176. var Range = __webpack_require__(17);
  20177. var ItemSet = __webpack_require__(32);
  20178. var TimeAxis = __webpack_require__(35);
  20179. var Activator = __webpack_require__(53);
  20180. var DateUtil = __webpack_require__(15);
  20181. var CustomTime = __webpack_require__(27);
  20182. /**
  20183. * Create a timeline visualization
  20184. * @param {HTMLElement} container
  20185. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  20186. * @param {Object} [options] See Core.setOptions for the available options.
  20187. * @constructor
  20188. */
  20189. function Core () {}
  20190. // turn Core into an event emitter
  20191. Emitter(Core.prototype);
  20192. /**
  20193. * Create the main DOM for the Core: a root panel containing left, right,
  20194. * top, bottom, content, and background panel.
  20195. * @param {Element} container The container element where the Core will
  20196. * be attached.
  20197. * @protected
  20198. */
  20199. Core.prototype._create = function (container) {
  20200. this.dom = {};
  20201. this.dom.root = document.createElement('div');
  20202. this.dom.background = document.createElement('div');
  20203. this.dom.backgroundVertical = document.createElement('div');
  20204. this.dom.backgroundHorizontal = document.createElement('div');
  20205. this.dom.centerContainer = document.createElement('div');
  20206. this.dom.leftContainer = document.createElement('div');
  20207. this.dom.rightContainer = document.createElement('div');
  20208. this.dom.center = document.createElement('div');
  20209. this.dom.left = document.createElement('div');
  20210. this.dom.right = document.createElement('div');
  20211. this.dom.top = document.createElement('div');
  20212. this.dom.bottom = document.createElement('div');
  20213. this.dom.shadowTop = document.createElement('div');
  20214. this.dom.shadowBottom = document.createElement('div');
  20215. this.dom.shadowTopLeft = document.createElement('div');
  20216. this.dom.shadowBottomLeft = document.createElement('div');
  20217. this.dom.shadowTopRight = document.createElement('div');
  20218. this.dom.shadowBottomRight = document.createElement('div');
  20219. this.dom.root.className = 'vis timeline root';
  20220. this.dom.background.className = 'vispanel background';
  20221. this.dom.backgroundVertical.className = 'vispanel background vertical';
  20222. this.dom.backgroundHorizontal.className = 'vispanel background horizontal';
  20223. this.dom.centerContainer.className = 'vispanel center';
  20224. this.dom.leftContainer.className = 'vispanel left';
  20225. this.dom.rightContainer.className = 'vispanel right';
  20226. this.dom.top.className = 'vispanel top';
  20227. this.dom.bottom.className = 'vispanel bottom';
  20228. this.dom.left.className = 'content';
  20229. this.dom.center.className = 'content';
  20230. this.dom.right.className = 'content';
  20231. this.dom.shadowTop.className = 'shadow top';
  20232. this.dom.shadowBottom.className = 'shadow bottom';
  20233. this.dom.shadowTopLeft.className = 'shadow top';
  20234. this.dom.shadowBottomLeft.className = 'shadow bottom';
  20235. this.dom.shadowTopRight.className = 'shadow top';
  20236. this.dom.shadowBottomRight.className = 'shadow bottom';
  20237. this.dom.root.appendChild(this.dom.background);
  20238. this.dom.root.appendChild(this.dom.backgroundVertical);
  20239. this.dom.root.appendChild(this.dom.backgroundHorizontal);
  20240. this.dom.root.appendChild(this.dom.centerContainer);
  20241. this.dom.root.appendChild(this.dom.leftContainer);
  20242. this.dom.root.appendChild(this.dom.rightContainer);
  20243. this.dom.root.appendChild(this.dom.top);
  20244. this.dom.root.appendChild(this.dom.bottom);
  20245. this.dom.centerContainer.appendChild(this.dom.center);
  20246. this.dom.leftContainer.appendChild(this.dom.left);
  20247. this.dom.rightContainer.appendChild(this.dom.right);
  20248. this.dom.centerContainer.appendChild(this.dom.shadowTop);
  20249. this.dom.centerContainer.appendChild(this.dom.shadowBottom);
  20250. this.dom.leftContainer.appendChild(this.dom.shadowTopLeft);
  20251. this.dom.leftContainer.appendChild(this.dom.shadowBottomLeft);
  20252. this.dom.rightContainer.appendChild(this.dom.shadowTopRight);
  20253. this.dom.rightContainer.appendChild(this.dom.shadowBottomRight);
  20254. this.on('rangechange', this._redraw.bind(this));
  20255. this.on('touch', this._onTouch.bind(this));
  20256. this.on('pinch', this._onPinch.bind(this));
  20257. this.on('dragstart', this._onDragStart.bind(this));
  20258. this.on('drag', this._onDrag.bind(this));
  20259. var me = this;
  20260. this.on('change', function (properties) {
  20261. if (properties && properties.queue == true) {
  20262. // redraw once on next tick
  20263. if (!me._redrawTimer) {
  20264. me._redrawTimer = setTimeout(function () {
  20265. me._redrawTimer = null;
  20266. me._redraw();
  20267. }, 0)
  20268. }
  20269. }
  20270. else {
  20271. // redraw immediately
  20272. me._redraw();
  20273. }
  20274. });
  20275. // create event listeners for all interesting events, these events will be
  20276. // emitted via emitter
  20277. this.hammer = Hammer(this.dom.root, {
  20278. preventDefault: true
  20279. });
  20280. this.listeners = {};
  20281. var events = [
  20282. 'touch', 'pinch',
  20283. 'tap', 'doubletap', 'hold',
  20284. 'dragstart', 'drag', 'dragend',
  20285. 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
  20286. ];
  20287. events.forEach(function (event) {
  20288. var listener = function () {
  20289. var args = [event].concat(Array.prototype.slice.call(arguments, 0));
  20290. if (me.isActive()) {
  20291. me.emit.apply(me, args);
  20292. }
  20293. };
  20294. me.hammer.on(event, listener);
  20295. me.listeners[event] = listener;
  20296. });
  20297. // size properties of each of the panels
  20298. this.props = {
  20299. root: {},
  20300. background: {},
  20301. centerContainer: {},
  20302. leftContainer: {},
  20303. rightContainer: {},
  20304. center: {},
  20305. left: {},
  20306. right: {},
  20307. top: {},
  20308. bottom: {},
  20309. border: {},
  20310. scrollTop: 0,
  20311. scrollTopMin: 0
  20312. };
  20313. this.touch = {}; // store state information needed for touch events
  20314. this.redrawCount = 0;
  20315. // attach the root panel to the provided container
  20316. if (!container) throw new Error('No container provided');
  20317. container.appendChild(this.dom.root);
  20318. };
  20319. /**
  20320. * Set options. Options will be passed to all components loaded in the Timeline.
  20321. * @param {Object} [options]
  20322. * {String} orientation
  20323. * Vertical orientation for the Timeline,
  20324. * can be 'bottom' (default) or 'top'.
  20325. * {String | Number} width
  20326. * Width for the timeline, a number in pixels or
  20327. * a css string like '1000px' or '75%'. '100%' by default.
  20328. * {String | Number} height
  20329. * Fixed height for the Timeline, a number in pixels or
  20330. * a css string like '400px' or '75%'. If undefined,
  20331. * The Timeline will automatically size such that
  20332. * its contents fit.
  20333. * {String | Number} minHeight
  20334. * Minimum height for the Timeline, a number in pixels or
  20335. * a css string like '400px' or '75%'.
  20336. * {String | Number} maxHeight
  20337. * Maximum height for the Timeline, a number in pixels or
  20338. * a css string like '400px' or '75%'.
  20339. * {Number | Date | String} start
  20340. * Start date for the visible window
  20341. * {Number | Date | String} end
  20342. * End date for the visible window
  20343. */
  20344. Core.prototype.setOptions = function (options) {
  20345. if (options) {
  20346. // copy the known options
  20347. var fields = ['width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'start', 'end', 'clickToUse', 'dataAttributes', 'hiddenDates'];
  20348. util.selectiveExtend(fields, this.options, options);
  20349. if ('orientation' in options) {
  20350. if (typeof options.orientation === 'string') {
  20351. this.options.orientation = options.orientation;
  20352. }
  20353. else if (typeof options.orientation === 'object' && 'axis' in options.orientation) {
  20354. this.options.orientation = options.orientation.axis;
  20355. }
  20356. }
  20357. if (this.options.orientation === 'both') {
  20358. if (!this.timeAxis2) {
  20359. var timeAxis2 = this.timeAxis2 = new TimeAxis(this.body);
  20360. timeAxis2.setOptions = function (options) {
  20361. var _options = options ? util.extend({}, options) : {};
  20362. _options.orientation = 'top'; // override the orientation option, always top
  20363. TimeAxis.prototype.setOptions.call(timeAxis2, _options);
  20364. };
  20365. this.components.push(timeAxis2);
  20366. }
  20367. }
  20368. else {
  20369. if (this.timeAxis2) {
  20370. var index = this.components.indexOf(this.timeAxis2);
  20371. if (index !== -1) {
  20372. this.components.splice(index, 1);
  20373. }
  20374. this.timeAxis2.destroy();
  20375. this.timeAxis2 = null;
  20376. }
  20377. }
  20378. if ('hiddenDates' in this.options) {
  20379. DateUtil.convertHiddenOptions(this.body, this.options.hiddenDates);
  20380. }
  20381. if ('clickToUse' in options) {
  20382. if (options.clickToUse) {
  20383. if (!this.activator) {
  20384. this.activator = new Activator(this.dom.root);
  20385. }
  20386. }
  20387. else {
  20388. if (this.activator) {
  20389. this.activator.destroy();
  20390. delete this.activator;
  20391. }
  20392. }
  20393. }
  20394. // enable/disable autoResize
  20395. this._initAutoResize();
  20396. }
  20397. // propagate options to all components
  20398. this.components.forEach(function (component) {
  20399. component.setOptions(options);
  20400. });
  20401. // redraw everything
  20402. this._redraw();
  20403. };
  20404. /**
  20405. * Returns true when the Timeline is active.
  20406. * @returns {boolean}
  20407. */
  20408. Core.prototype.isActive = function () {
  20409. return !this.activator || this.activator.active;
  20410. };
  20411. /**
  20412. * Destroy the Core, clean up all DOM elements and event listeners.
  20413. */
  20414. Core.prototype.destroy = function () {
  20415. // unbind datasets
  20416. this.clear();
  20417. // remove all event listeners
  20418. this.off();
  20419. // stop checking for changed size
  20420. this._stopAutoResize();
  20421. // remove from DOM
  20422. if (this.dom.root.parentNode) {
  20423. this.dom.root.parentNode.removeChild(this.dom.root);
  20424. }
  20425. this.dom = null;
  20426. // remove Activator
  20427. if (this.activator) {
  20428. this.activator.destroy();
  20429. delete this.activator;
  20430. }
  20431. // cleanup hammer touch events
  20432. for (var event in this.listeners) {
  20433. if (this.listeners.hasOwnProperty(event)) {
  20434. delete this.listeners[event];
  20435. }
  20436. }
  20437. this.listeners = null;
  20438. this.hammer = null;
  20439. // give all components the opportunity to cleanup
  20440. this.components.forEach(function (component) {
  20441. component.destroy();
  20442. });
  20443. this.body = null;
  20444. };
  20445. /**
  20446. * Set a custom time bar
  20447. * @param {Date} time
  20448. * @param {int} id
  20449. */
  20450. Core.prototype.setCustomTime = function (time, id) {
  20451. if (!this.customTime) {
  20452. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  20453. }
  20454. var barId = id || 0;
  20455. this.components.forEach(function (element, index, components) {
  20456. if (element instanceof CustomTime && element.options.id === barId) {
  20457. element.setCustomTime(time);
  20458. }
  20459. });
  20460. };
  20461. /**
  20462. * Retrieve the current custom time.
  20463. * @return {Date} customTime
  20464. * @param {int} id
  20465. */
  20466. Core.prototype.getCustomTime = function(id) {
  20467. if (!this.customTime) {
  20468. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  20469. }
  20470. var barId = id || 0,
  20471. customTime = this.customTime.getCustomTime();
  20472. this.components.forEach(function (element, index, components) {
  20473. if (element instanceof CustomTime && element.options.id === barId) {
  20474. customTime = element.getCustomTime();
  20475. }
  20476. });
  20477. return customTime;
  20478. };
  20479. /**
  20480. * Add custom vertical bar
  20481. * @param {Date | String | Number} time A Date, unix timestamp, or
  20482. * ISO date string. Time point where the new bar should be placed
  20483. * @param {Number | String} ID of the new bar
  20484. * @return {Number | String} ID of the new bar
  20485. */
  20486. Core.prototype.addCustomTime = function (time, id) {
  20487. if (!this.currentTime) {
  20488. throw new Error('Option showCurrentTime must be true');
  20489. }
  20490. if (time === undefined) {
  20491. throw new Error('Time parameter for the custom bar must be provided');
  20492. }
  20493. var ts = util.convert(time, 'Date').valueOf(),
  20494. numIds, customTime, customBarId;
  20495. // All bar IDs are kept in 1 array, mixed types
  20496. // Bar with ID 0 is the default bar.
  20497. if (!this.customBarIds || this.customBarIds.constructor !== Array) {
  20498. this.customBarIds = [0];
  20499. }
  20500. // If the ID is not provided, generate one, otherwise just use it
  20501. if (id === undefined) {
  20502. numIds = this.customBarIds.filter(function (element) {
  20503. return util.isNumber(element);
  20504. });
  20505. customBarId = numIds.length > 0 ? Math.max.apply(null, numIds) + 1 : 1;
  20506. } else {
  20507. // Check for duplicates
  20508. this.customBarIds.forEach(function (element) {
  20509. if (element === id) {
  20510. throw new Error('Custom time ID already exists');
  20511. }
  20512. });
  20513. customBarId = id;
  20514. }
  20515. this.customBarIds.push(customBarId);
  20516. customTime = new CustomTime(this.body, {
  20517. showCustomTime : true,
  20518. time : ts,
  20519. id : customBarId
  20520. });
  20521. this.components.push(customTime);
  20522. this.redraw();
  20523. return customBarId;
  20524. };
  20525. /**
  20526. * Set a custom title for the custom time bar.
  20527. * @param {string} [title] Custom title
  20528. * @param {number} [id=undefined] Id of the custom time bar.
  20529. * @returns {*}
  20530. */
  20531. Core.prototype.setCustomTimeTitle = function (title, id) {
  20532. var customTimes = this.customTimes.filter(function (component) {
  20533. return component.options.id === id;
  20534. });
  20535. if (customTimes.length === 0) {
  20536. throw new Error('No custom time bar found with id ' + (0, _stringify2['default'])(id));
  20537. }
  20538. if (customTimes.length > 0) {
  20539. return customTimes[0].setCustomTitle(title);
  20540. }
  20541. };
  20542. /**
  20543. * Remove previously added custom bar
  20544. * @param {int} id ID of the custom bar to be removed
  20545. * @return {boolean} True if the bar exists and is removed, false otherwise
  20546. */
  20547. Core.prototype.removeCustomTime = function (id) {
  20548. var me = this;
  20549. this.components.forEach(function (bar, index, components) {
  20550. if (bar instanceof CustomTime && bar.options.id === id) {
  20551. // Only the lines added by the user will be removed
  20552. if (bar.options.id !== 0) {
  20553. me.customBarIds.splice(me.customBarIds.indexOf(id), 1);
  20554. components.splice(index, 1);
  20555. bar.destroy();
  20556. }
  20557. }
  20558. });
  20559. };
  20560. /**
  20561. * Get the id's of the currently visible items.
  20562. * @returns {Array} The ids of the visible items
  20563. */
  20564. Core.prototype.getVisibleItems = function() {
  20565. return this.itemSet && this.itemSet.getVisibleItems() || [];
  20566. };
  20567. /**
  20568. * Clear the Core. By Default, items, groups and options are cleared.
  20569. * Example usage:
  20570. *
  20571. * timeline.clear(); // clear items, groups, and options
  20572. * timeline.clear({options: true}); // clear options only
  20573. *
  20574. * @param {Object} [what] Optionally specify what to clear. By default:
  20575. * {items: true, groups: true, options: true}
  20576. */
  20577. Core.prototype.clear = function(what) {
  20578. // clear items
  20579. if (!what || what.items) {
  20580. this.setItems(null);
  20581. }
  20582. // clear groups
  20583. if (!what || what.groups) {
  20584. this.setGroups(null);
  20585. }
  20586. // clear options of timeline and of each of the components
  20587. if (!what || what.options) {
  20588. this.components.forEach(function (component) {
  20589. component.setOptions(component.defaultOptions);
  20590. });
  20591. this.setOptions(this.defaultOptions); // this will also do a redraw
  20592. }
  20593. };
  20594. /**
  20595. * Set Core window such that it fits all items
  20596. * @param {Object} [options] Available options:
  20597. * `animate: boolean | number`
  20598. * If true (default), the range is animated
  20599. * smoothly to the new window.
  20600. * If a number, the number is taken as duration
  20601. * for the animation. Default duration is 500 ms.
  20602. */
  20603. Core.prototype.fit = function(options) {
  20604. var range = this._getDataRange();
  20605. // skip range set if there is no start and end date
  20606. if (range.start === null && range.end === null) {
  20607. return;
  20608. }
  20609. var animate = (options && options.animate !== undefined) ? options.animate : true;
  20610. this.range.setRange(range.start, range.end, animate);
  20611. };
  20612. /**
  20613. * Calculate the data range of the items and applies a 5% window around it.
  20614. * @returns {{start: Date | null, end: Date | null}}
  20615. * @protected
  20616. */
  20617. Core.prototype._getDataRange = function() {
  20618. // apply the data range as range
  20619. var dataRange = this.getItemRange();
  20620. // add 5% space on both sides
  20621. var start = dataRange.min;
  20622. var end = dataRange.max;
  20623. if (start != null && end != null) {
  20624. var interval = (end.valueOf() - start.valueOf());
  20625. if (interval <= 0) {
  20626. // prevent an empty interval
  20627. interval = 24 * 60 * 60 * 1000; // 1 day
  20628. }
  20629. start = new Date(start.valueOf() - interval * 0.05);
  20630. end = new Date(end.valueOf() + interval * 0.05);
  20631. }
  20632. return {
  20633. start: start,
  20634. end: end
  20635. }
  20636. };
  20637. /**
  20638. * Set the visible window. Both parameters are optional, you can change only
  20639. * start or only end. Syntax:
  20640. *
  20641. * TimeLine.setWindow(start, end)
  20642. * TimeLine.setWindow(start, end, options)
  20643. * TimeLine.setWindow(range)
  20644. *
  20645. * Where start and end can be a Date, number, or string, and range is an
  20646. * object with properties start and end.
  20647. *
  20648. * @param {Date | Number | String | Object} [start] Start date of visible window
  20649. * @param {Date | Number | String} [end] End date of visible window
  20650. * @param {Object} [options] Available options:
  20651. * `animate: boolean | number`
  20652. * If true (default), the range is animated
  20653. * smoothly to the new window.
  20654. * If a number, the number is taken as duration
  20655. * for the animation. Default duration is 500 ms.
  20656. */
  20657. Core.prototype.setWindow = function(start, end, options) {
  20658. var animate;
  20659. if (arguments.length == 1) {
  20660. var range = arguments[0];
  20661. animate = (range.animate !== undefined) ? range.animate : true;
  20662. this.range.setRange(range.start, range.end, animate);
  20663. }
  20664. else {
  20665. animate = (options && options.animate !== undefined) ? options.animate : true;
  20666. this.range.setRange(start, end, animate);
  20667. }
  20668. };
  20669. /**
  20670. * Move the window such that given time is centered on screen.
  20671. * @param {Date | Number | String} time
  20672. * @param {Object} [options] Available options:
  20673. * `animate: boolean | number`
  20674. * If true (default), the range is animated
  20675. * smoothly to the new window.
  20676. * If a number, the number is taken as duration
  20677. * for the animation. Default duration is 500 ms.
  20678. */
  20679. Core.prototype.moveTo = function(time, options) {
  20680. var interval = this.range.end - this.range.start;
  20681. var t = util.convert(time, 'Date').valueOf();
  20682. var start = t - interval / 2;
  20683. var end = t + interval / 2;
  20684. var animate = (options && options.animate !== undefined) ? options.animate : true;
  20685. this.range.setRange(start, end, animate);
  20686. };
  20687. /**
  20688. * Get the visible window
  20689. * @return {{start: Date, end: Date}} Visible range
  20690. */
  20691. Core.prototype.getWindow = function() {
  20692. var range = this.range.getRange();
  20693. return {
  20694. start: new Date(range.start),
  20695. end: new Date(range.end)
  20696. };
  20697. };
  20698. /**
  20699. * Force a redraw. Can be overridden by implementations of Core
  20700. */
  20701. Core.prototype.redraw = function() {
  20702. this._redraw();
  20703. };
  20704. /**
  20705. * Redraw for internal use. Redraws all components. See also the public
  20706. * method redraw.
  20707. * @protected
  20708. */
  20709. Core.prototype._redraw = function() {
  20710. var resized = false;
  20711. var options = this.options;
  20712. var props = this.props;
  20713. var dom = this.dom;
  20714. if (!dom) return; // when destroyed
  20715. DateUtil.updateHiddenDates(this.body, this.options.hiddenDates);
  20716. // update class names
  20717. if (options.orientation == 'top') {
  20718. util.addClassName(dom.root, 'top');
  20719. util.removeClassName(dom.root, 'bottom');
  20720. }
  20721. else {
  20722. util.removeClassName(dom.root, 'top');
  20723. util.addClassName(dom.root, 'bottom');
  20724. }
  20725. // update root width and height options
  20726. dom.root.style.maxHeight = util.option.asSize(options.maxHeight, '');
  20727. dom.root.style.minHeight = util.option.asSize(options.minHeight, '');
  20728. dom.root.style.width = util.option.asSize(options.width, '');
  20729. // calculate border widths
  20730. props.border.left = (dom.centerContainer.offsetWidth - dom.centerContainer.clientWidth) / 2;
  20731. props.border.right = props.border.left;
  20732. props.border.top = (dom.centerContainer.offsetHeight - dom.centerContainer.clientHeight) / 2;
  20733. props.border.bottom = props.border.top;
  20734. var borderRootHeight= dom.root.offsetHeight - dom.root.clientHeight;
  20735. var borderRootWidth = dom.root.offsetWidth - dom.root.clientWidth;
  20736. // workaround for a bug in IE: the clientWidth of an element with
  20737. // a height:0px and overflow:hidden is not calculated and always has value 0
  20738. if (dom.centerContainer.clientHeight === 0) {
  20739. props.border.left = props.border.top;
  20740. props.border.right = props.border.left;
  20741. }
  20742. if (dom.root.clientHeight === 0) {
  20743. borderRootWidth = borderRootHeight;
  20744. }
  20745. // calculate the heights. If any of the side panels is empty, we set the height to
  20746. // minus the border width, such that the border will be invisible
  20747. props.center.height = dom.center.offsetHeight;
  20748. props.left.height = dom.left.offsetHeight;
  20749. props.right.height = dom.right.offsetHeight;
  20750. props.top.height = dom.top.clientHeight || -props.border.top;
  20751. props.bottom.height = dom.bottom.clientHeight || -props.border.bottom;
  20752. // TODO: compensate borders when any of the panels is empty.
  20753. // apply auto height
  20754. // TODO: only calculate autoHeight when needed (else we cause an extra reflow/repaint of the DOM)
  20755. var contentHeight = Math.max(props.left.height, props.center.height, props.right.height);
  20756. var autoHeight = props.top.height + contentHeight + props.bottom.height +
  20757. borderRootHeight + props.border.top + props.border.bottom;
  20758. dom.root.style.height = util.option.asSize(options.height, autoHeight + 'px');
  20759. // calculate heights of the content panels
  20760. props.root.height = dom.root.offsetHeight;
  20761. props.background.height = props.root.height - borderRootHeight;
  20762. var containerHeight = props.root.height - props.top.height - props.bottom.height -
  20763. borderRootHeight;
  20764. props.centerContainer.height = containerHeight;
  20765. props.leftContainer.height = containerHeight;
  20766. props.rightContainer.height = props.leftContainer.height;
  20767. // calculate the widths of the panels
  20768. props.root.width = dom.root.offsetWidth;
  20769. props.background.width = props.root.width - borderRootWidth;
  20770. props.left.width = dom.leftContainer.clientWidth || -props.border.left;
  20771. props.leftContainer.width = props.left.width;
  20772. props.right.width = dom.rightContainer.clientWidth || -props.border.right;
  20773. props.rightContainer.width = props.right.width;
  20774. var centerWidth = props.root.width - props.left.width - props.right.width - borderRootWidth;
  20775. props.center.width = centerWidth;
  20776. props.centerContainer.width = centerWidth;
  20777. props.top.width = centerWidth;
  20778. props.bottom.width = centerWidth;
  20779. // resize the panels
  20780. dom.background.style.height = props.background.height + 'px';
  20781. dom.backgroundVertical.style.height = props.background.height + 'px';
  20782. dom.backgroundHorizontal.style.height = props.centerContainer.height + 'px';
  20783. dom.centerContainer.style.height = props.centerContainer.height + 'px';
  20784. dom.leftContainer.style.height = props.leftContainer.height + 'px';
  20785. dom.rightContainer.style.height = props.rightContainer.height + 'px';
  20786. dom.background.style.width = props.background.width + 'px';
  20787. dom.backgroundVertical.style.width = props.centerContainer.width + 'px';
  20788. dom.backgroundHorizontal.style.width = props.background.width + 'px';
  20789. dom.centerContainer.style.width = props.center.width + 'px';
  20790. dom.top.style.width = props.top.width + 'px';
  20791. dom.bottom.style.width = props.bottom.width + 'px';
  20792. // reposition the panels
  20793. dom.background.style.left = '0';
  20794. dom.background.style.top = '0';
  20795. dom.backgroundVertical.style.left = (props.left.width + props.border.left) + 'px';
  20796. dom.backgroundVertical.style.top = '0';
  20797. dom.backgroundHorizontal.style.left = '0';
  20798. dom.backgroundHorizontal.style.top = props.top.height + 'px';
  20799. dom.centerContainer.style.left = props.left.width + 'px';
  20800. dom.centerContainer.style.top = props.top.height + 'px';
  20801. dom.leftContainer.style.left = '0';
  20802. dom.leftContainer.style.top = props.top.height + 'px';
  20803. dom.rightContainer.style.left = (props.left.width + props.center.width) + 'px';
  20804. dom.rightContainer.style.top = props.top.height + 'px';
  20805. dom.top.style.left = props.left.width + 'px';
  20806. dom.top.style.top = '0';
  20807. dom.bottom.style.left = props.left.width + 'px';
  20808. dom.bottom.style.top = (props.top.height + props.centerContainer.height) + 'px';
  20809. // update the scrollTop, feasible range for the offset can be changed
  20810. // when the height of the Core or of the contents of the center changed
  20811. this._updateScrollTop();
  20812. // reposition the scrollable contents
  20813. var offset = this.props.scrollTop;
  20814. if (options.orientation == 'bottom') {
  20815. offset += Math.max(this.props.centerContainer.height - this.props.center.height -
  20816. this.props.border.top - this.props.border.bottom, 0);
  20817. }
  20818. dom.center.style.left = '0';
  20819. dom.center.style.top = offset + 'px';
  20820. dom.left.style.left = '0';
  20821. dom.left.style.top = offset + 'px';
  20822. dom.right.style.left = '0';
  20823. dom.right.style.top = offset + 'px';
  20824. // show shadows when vertical scrolling is available
  20825. var visibilityTop = this.props.scrollTop == 0 ? 'hidden' : '';
  20826. var visibilityBottom = this.props.scrollTop == this.props.scrollTopMin ? 'hidden' : '';
  20827. dom.shadowTop.style.visibility = visibilityTop;
  20828. dom.shadowBottom.style.visibility = visibilityBottom;
  20829. dom.shadowTopLeft.style.visibility = visibilityTop;
  20830. dom.shadowBottomLeft.style.visibility = visibilityBottom;
  20831. dom.shadowTopRight.style.visibility = visibilityTop;
  20832. dom.shadowBottomRight.style.visibility = visibilityBottom;
  20833. // redraw all components
  20834. this.components.forEach(function (component) {
  20835. resized = component.redraw() || resized;
  20836. });
  20837. if (resized) {
  20838. // keep repainting until all sizes are settled
  20839. var MAX_REDRAWS = 3; // maximum number of consecutive redraws
  20840. if (this.redrawCount < MAX_REDRAWS) {
  20841. this.redrawCount++;
  20842. this._redraw();
  20843. }
  20844. else {
  20845. console.log('WARNING: infinite loop in redraw?');
  20846. }
  20847. this.redrawCount = 0;
  20848. }
  20849. this.emit("finishedRedraw");
  20850. };
  20851. // TODO: deprecated since version 1.1.0, remove some day
  20852. Core.prototype.repaint = function () {
  20853. throw new Error('Function repaint is deprecated. Use redraw instead.');
  20854. };
  20855. /**
  20856. * Set a current time. This can be used for example to ensure that a client's
  20857. * time is synchronized with a shared server time.
  20858. * Only applicable when option `showCurrentTime` is true.
  20859. * @param {Date | String | Number} time A Date, unix timestamp, or
  20860. * ISO date string.
  20861. */
  20862. Core.prototype.setCurrentTime = function(time) {
  20863. if (!this.currentTime) {
  20864. throw new Error('Option showCurrentTime must be true');
  20865. }
  20866. this.currentTime.setCurrentTime(time);
  20867. };
  20868. /**
  20869. * Get the current time.
  20870. * Only applicable when option `showCurrentTime` is true.
  20871. * @return {Date} Returns the current time.
  20872. */
  20873. Core.prototype.getCurrentTime = function() {
  20874. if (!this.currentTime) {
  20875. throw new Error('Option showCurrentTime must be true');
  20876. }
  20877. return this.currentTime.getCurrentTime();
  20878. };
  20879. /**
  20880. * Convert a position on screen (pixels) to a datetime
  20881. * @param {int} x Position on the screen in pixels
  20882. * @return {Date} time The datetime the corresponds with given position x
  20883. * @protected
  20884. */
  20885. // TODO: move this function to Range
  20886. Core.prototype._toTime = function(x) {
  20887. return DateUtil.toTime(this, x, this.props.center.width);
  20888. };
  20889. /**
  20890. * Convert a position on the global screen (pixels) to a datetime
  20891. * @param {int} x Position on the screen in pixels
  20892. * @return {Date} time The datetime the corresponds with given position x
  20893. * @protected
  20894. */
  20895. // TODO: move this function to Range
  20896. Core.prototype._toGlobalTime = function(x) {
  20897. return DateUtil.toTime(this, x, this.props.root.width);
  20898. //var conversion = this.range.conversion(this.props.root.width);
  20899. //return new Date(x / conversion.scale + conversion.offset);
  20900. };
  20901. /**
  20902. * Convert a datetime (Date object) into a position on the screen
  20903. * @param {Date} time A date
  20904. * @return {int} x The position on the screen in pixels which corresponds
  20905. * with the given date.
  20906. * @protected
  20907. */
  20908. // TODO: move this function to Range
  20909. Core.prototype._toScreen = function(time) {
  20910. return DateUtil.toScreen(this, time, this.props.center.width);
  20911. };
  20912. /**
  20913. * Convert a datetime (Date object) into a position on the root
  20914. * This is used to get the pixel density estimate for the screen, not the center panel
  20915. * @param {Date} time A date
  20916. * @return {int} x The position on root in pixels which corresponds
  20917. * with the given date.
  20918. * @protected
  20919. */
  20920. // TODO: move this function to Range
  20921. Core.prototype._toGlobalScreen = function(time) {
  20922. return DateUtil.toScreen(this, time, this.props.root.width);
  20923. //var conversion = this.range.conversion(this.props.root.width);
  20924. //return (time.valueOf() - conversion.offset) * conversion.scale;
  20925. };
  20926. /**
  20927. * Initialize watching when option autoResize is true
  20928. * @private
  20929. */
  20930. Core.prototype._initAutoResize = function () {
  20931. if (this.options.autoResize == true) {
  20932. this._startAutoResize();
  20933. }
  20934. else {
  20935. this._stopAutoResize();
  20936. }
  20937. };
  20938. /**
  20939. * Watch for changes in the size of the container. On resize, the Panel will
  20940. * automatically redraw itself.
  20941. * @private
  20942. */
  20943. Core.prototype._startAutoResize = function () {
  20944. var me = this;
  20945. this._stopAutoResize();
  20946. this._onResize = function() {
  20947. if (me.options.autoResize != true) {
  20948. // stop watching when the option autoResize is changed to false
  20949. me._stopAutoResize();
  20950. return;
  20951. }
  20952. if (me.dom.root) {
  20953. // check whether the frame is resized
  20954. // Note: we compare offsetWidth here, not clientWidth. For some reason,
  20955. // IE does not restore the clientWidth from 0 to the actual width after
  20956. // changing the timeline's container display style from none to visible
  20957. if ((me.dom.root.offsetWidth != me.props.lastWidth) ||
  20958. (me.dom.root.offsetHeight != me.props.lastHeight)) {
  20959. me.props.lastWidth = me.dom.root.offsetWidth;
  20960. me.props.lastHeight = me.dom.root.offsetHeight;
  20961. me.emit('change');
  20962. }
  20963. }
  20964. };
  20965. // add event listener to window resize
  20966. util.addEventListener(window, 'resize', this._onResize);
  20967. this.watchTimer = setInterval(this._onResize, 1000);
  20968. };
  20969. /**
  20970. * Stop watching for a resize of the frame.
  20971. * @private
  20972. */
  20973. Core.prototype._stopAutoResize = function () {
  20974. if (this.watchTimer) {
  20975. clearInterval(this.watchTimer);
  20976. this.watchTimer = undefined;
  20977. }
  20978. // remove event listener on window.resize
  20979. util.removeEventListener(window, 'resize', this._onResize);
  20980. this._onResize = null;
  20981. };
  20982. /**
  20983. * Start moving the timeline vertically
  20984. * @param {Event} event
  20985. * @private
  20986. */
  20987. Core.prototype._onTouch = function (event) {
  20988. this.touch.allowDragging = true;
  20989. };
  20990. /**
  20991. * Start moving the timeline vertically
  20992. * @param {Event} event
  20993. * @private
  20994. */
  20995. Core.prototype._onPinch = function (event) {
  20996. this.touch.allowDragging = false;
  20997. };
  20998. /**
  20999. * Start moving the timeline vertically
  21000. * @param {Event} event
  21001. * @private
  21002. */
  21003. Core.prototype._onDragStart = function (event) {
  21004. this.touch.initialScrollTop = this.props.scrollTop;
  21005. };
  21006. /**
  21007. * Move the timeline vertically
  21008. * @param {Event} event
  21009. * @private
  21010. */
  21011. Core.prototype._onDrag = function (event) {
  21012. // refuse to drag when we where pinching to prevent the timeline make a jump
  21013. // when releasing the fingers in opposite order from the touch screen
  21014. if (!this.touch.allowDragging) return;
  21015. var delta = event.gesture.deltaY;
  21016. var oldScrollTop = this._getScrollTop();
  21017. var newScrollTop = this._setScrollTop(this.touch.initialScrollTop + delta);
  21018. if (newScrollTop != oldScrollTop) {
  21019. this._redraw(); // TODO: this causes two redraws when dragging, the other is triggered by rangechange already
  21020. this.emit("verticalDrag");
  21021. }
  21022. };
  21023. /**
  21024. * Apply a scrollTop
  21025. * @param {Number} scrollTop
  21026. * @returns {Number} scrollTop Returns the applied scrollTop
  21027. * @private
  21028. */
  21029. Core.prototype._setScrollTop = function (scrollTop) {
  21030. this.props.scrollTop = scrollTop;
  21031. this._updateScrollTop();
  21032. return this.props.scrollTop;
  21033. };
  21034. /**
  21035. * Update the current scrollTop when the height of the containers has been changed
  21036. * @returns {Number} scrollTop Returns the applied scrollTop
  21037. * @private
  21038. */
  21039. Core.prototype._updateScrollTop = function () {
  21040. // recalculate the scrollTopMin
  21041. var scrollTopMin = Math.min(this.props.centerContainer.height - this.props.center.height, 0); // is negative or zero
  21042. if (scrollTopMin != this.props.scrollTopMin) {
  21043. // in case of bottom orientation, change the scrollTop such that the contents
  21044. // do not move relative to the time axis at the bottom
  21045. if (this.options.orientation == 'bottom') {
  21046. this.props.scrollTop += (scrollTopMin - this.props.scrollTopMin);
  21047. }
  21048. this.props.scrollTopMin = scrollTopMin;
  21049. }
  21050. // limit the scrollTop to the feasible scroll range
  21051. if (this.props.scrollTop > 0) this.props.scrollTop = 0;
  21052. if (this.props.scrollTop < scrollTopMin) this.props.scrollTop = scrollTopMin;
  21053. return this.props.scrollTop;
  21054. };
  21055. /**
  21056. * Get the current scrollTop
  21057. * @returns {number} scrollTop
  21058. * @private
  21059. */
  21060. Core.prototype._getScrollTop = function () {
  21061. return this.props.scrollTop;
  21062. };
  21063. /**
  21064. * Zoom in the window such that given time is centered on screen.
  21065. * @param {number} percentage - must be between [0..1]
  21066. * @param {Object} [options] Available options:
  21067. * `animation: boolean | {duration: number, easingFunction: string}`
  21068. * If true (default), the range is animated
  21069. * smoothly to the new window. An object can be
  21070. * provided to specify duration and easing function.
  21071. * Default duration is 500 ms, and default easing
  21072. * function is 'easeInOutQuad'.
  21073. * @param {function} [callback] a callback funtion to be executed at the end of this function
  21074. */
  21075. Core.prototype.zoomIn = function (percentage, options, callback) {
  21076. if (!percentage || percentage < 0 || percentage > 1) return;
  21077. if (typeof arguments[1] == "function") {
  21078. callback = arguments[1];
  21079. options = {};
  21080. }
  21081. var range = this.getWindow();
  21082. var start = range.start.valueOf();
  21083. var end = range.end.valueOf();
  21084. var interval = end - start;
  21085. var newInterval = interval / (1 + percentage);
  21086. var distance = (interval - newInterval) / 2;
  21087. var newStart = start + distance;
  21088. var newEnd = end - distance;
  21089. this.setWindow(newStart, newEnd, options, callback);
  21090. };
  21091. /**
  21092. * Zoom out the window such that given time is centered on screen.
  21093. * @param {number} percentage - must be between [0..1]
  21094. * @param {Object} [options] Available options:
  21095. * `animation: boolean | {duration: number, easingFunction: string}`
  21096. * If true (default), the range is animated
  21097. * smoothly to the new window. An object can be
  21098. * provided to specify duration and easing function.
  21099. * Default duration is 500 ms, and default easing
  21100. * function is 'easeInOutQuad'.
  21101. * @param {function} [callback] a callback funtion to be executed at the end of this function
  21102. */
  21103. Core.prototype.zoomOut = function (percentage, options, callback) {
  21104. if (!percentage || percentage < 0 || percentage > 1) return;
  21105. if (typeof arguments[1] == "function") {
  21106. callback = arguments[1];
  21107. options = {};
  21108. }
  21109. var range = this.getWindow();
  21110. var start = range.start.valueOf();
  21111. var end = range.end.valueOf();
  21112. var interval = end - start;
  21113. var newStart = start - interval * percentage / 2;
  21114. var newEnd = end + interval * percentage / 2;
  21115. this.setWindow(newStart, newEnd, options, callback);
  21116. };
  21117. module.exports = Core;
  21118. /***/ },
  21119. /* 47 */
  21120. /***/ function(module, exports, __webpack_require__) {
  21121. var Hammer = __webpack_require__(45);
  21122. /**
  21123. * Fake a hammer.js gesture. Event can be a ScrollEvent or MouseMoveEvent
  21124. * @param {Element} element
  21125. * @param {Event} event
  21126. */
  21127. exports.fakeGesture = function(element, event) {
  21128. var eventType = null;
  21129. // for hammer.js 1.0.5
  21130. // var gesture = Hammer.event.collectEventData(this, eventType, event);
  21131. // for hammer.js 1.0.6+
  21132. var touches = Hammer.event.getTouchList(event, eventType);
  21133. var gesture = Hammer.event.collectEventData(this, eventType, touches, event);
  21134. // on IE in standards mode, no touches are recognized by hammer.js,
  21135. // resulting in NaN values for center.pageX and center.pageY
  21136. if (isNaN(gesture.center.pageX)) {
  21137. gesture.center.pageX = event.pageX;
  21138. }
  21139. if (isNaN(gesture.center.pageY)) {
  21140. gesture.center.pageY = event.pageY;
  21141. }
  21142. return gesture;
  21143. };
  21144. /***/ },
  21145. /* 48 */
  21146. /***/ function(module, exports, __webpack_require__) {
  21147. // English
  21148. exports['en'] = {
  21149. current: 'current',
  21150. time: 'time'
  21151. };
  21152. exports['en_EN'] = exports['en'];
  21153. exports['en_US'] = exports['en'];
  21154. // Dutch
  21155. exports['nl'] = {
  21156. current: 'aangepaste',
  21157. time: 'tijd'
  21158. };
  21159. exports['nl_NL'] = exports['nl'];
  21160. exports['nl_BE'] = exports['nl'];
  21161. /***/ },
  21162. /* 49 */
  21163. /***/ function(module, exports, __webpack_require__) {
  21164. /**
  21165. * Created by Alex on 11/11/2014.
  21166. */
  21167. var DOMutil = __webpack_require__(2);
  21168. var Points = __webpack_require__(51);
  21169. function Line(groupId, options) {
  21170. this.groupId = groupId;
  21171. this.options = options;
  21172. }
  21173. Line.prototype.getYRange = function(groupData) {
  21174. var yMin = groupData[0].y;
  21175. var yMax = groupData[0].y;
  21176. for (var j = 0; j < groupData.length; j++) {
  21177. yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
  21178. yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
  21179. }
  21180. return {min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation};
  21181. };
  21182. /**
  21183. * draw a line graph
  21184. *
  21185. * @param dataset
  21186. * @param group
  21187. */
  21188. Line.prototype.draw = function (dataset, group, framework) {
  21189. if (dataset != null) {
  21190. if (dataset.length > 0) {
  21191. var path, d;
  21192. var svgHeight = Number(framework.svg.style.height.replace('px',''));
  21193. path = DOMutil.getSVGElement('path', framework.svgElements, framework.svg);
  21194. path.setAttributeNS(null, "class", group.className);
  21195. if(group.style !== undefined) {
  21196. path.setAttributeNS(null, "style", group.style);
  21197. }
  21198. // construct path from dataset
  21199. if (group.options.catmullRom.enabled == true) {
  21200. d = Line._catmullRom(dataset, group);
  21201. }
  21202. else {
  21203. d = Line._linear(dataset);
  21204. }
  21205. // append with points for fill and finalize the path
  21206. if (group.options.shaded.enabled == true) {
  21207. var fillPath = DOMutil.getSVGElement('path', framework.svgElements, framework.svg);
  21208. var dFill;
  21209. if (group.options.shaded.orientation == 'top') {
  21210. dFill = 'M' + dataset[0].x + ',' + 0 + ' ' + d + 'L' + dataset[dataset.length - 1].x + ',' + 0;
  21211. }
  21212. else {
  21213. dFill = 'M' + dataset[0].x + ',' + svgHeight + ' ' + d + 'L' + dataset[dataset.length - 1].x + ',' + svgHeight;
  21214. }
  21215. fillPath.setAttributeNS(null, "class", group.className + " fill");
  21216. if(group.options.shaded.style !== undefined) {
  21217. fillPath.setAttributeNS(null, "style", group.options.shaded.style);
  21218. }
  21219. fillPath.setAttributeNS(null, "d", dFill);
  21220. }
  21221. // copy properties to path for drawing.
  21222. path.setAttributeNS(null, 'd', 'M' + d);
  21223. // draw points
  21224. if (group.options.drawPoints.enabled == true) {
  21225. Points.draw(dataset, group, framework);
  21226. }
  21227. }
  21228. }
  21229. };
  21230. /**
  21231. * This uses an uniform parametrization of the CatmullRom algorithm:
  21232. * 'On the Parameterization of Catmull-Rom Curves' by Cem Yuksel et al.
  21233. * @param data
  21234. * @returns {string}
  21235. * @private
  21236. */
  21237. Line._catmullRomUniform = function(data) {
  21238. // catmull rom
  21239. var p0, p1, p2, p3, bp1, bp2;
  21240. var d = Math.round(data[0].x) + ',' + Math.round(data[0].y) + ' ';
  21241. var normalization = 1/6;
  21242. var length = data.length;
  21243. for (var i = 0; i < length - 1; i++) {
  21244. p0 = (i == 0) ? data[0] : data[i-1];
  21245. p1 = data[i];
  21246. p2 = data[i+1];
  21247. p3 = (i + 2 < length) ? data[i+2] : p2;
  21248. // Catmull-Rom to Cubic Bezier conversion matrix
  21249. // 0 1 0 0
  21250. // -1/6 1 1/6 0
  21251. // 0 1/6 1 -1/6
  21252. // 0 0 1 0
  21253. // bp0 = { x: p1.x, y: p1.y };
  21254. bp1 = { x: ((-p0.x + 6*p1.x + p2.x) *normalization), y: ((-p0.y + 6*p1.y + p2.y) *normalization)};
  21255. bp2 = { x: (( p1.x + 6*p2.x - p3.x) *normalization), y: (( p1.y + 6*p2.y - p3.y) *normalization)};
  21256. // bp0 = { x: p2.x, y: p2.y };
  21257. d += 'C' +
  21258. bp1.x + ',' +
  21259. bp1.y + ' ' +
  21260. bp2.x + ',' +
  21261. bp2.y + ' ' +
  21262. p2.x + ',' +
  21263. p2.y + ' ';
  21264. }
  21265. return d;
  21266. };
  21267. /**
  21268. * This uses either the chordal or centripetal parameterization of the catmull-rom algorithm.
  21269. * By default, the centripetal parameterization is used because this gives the nicest results.
  21270. * These parameterizations are relatively heavy because the distance between 4 points have to be calculated.
  21271. *
  21272. * One optimization can be used to reuse distances since this is a sliding window approach.
  21273. * @param data
  21274. * @param group
  21275. * @returns {string}
  21276. * @private
  21277. */
  21278. Line._catmullRom = function(data, group) {
  21279. var alpha = group.options.catmullRom.alpha;
  21280. if (alpha == 0 || alpha === undefined) {
  21281. return this._catmullRomUniform(data);
  21282. }
  21283. else {
  21284. var p0, p1, p2, p3, bp1, bp2, d1,d2,d3, A, B, N, M;
  21285. var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA;
  21286. var d = Math.round(data[0].x) + ',' + Math.round(data[0].y) + ' ';
  21287. var length = data.length;
  21288. for (var i = 0; i < length - 1; i++) {
  21289. p0 = (i == 0) ? data[0] : data[i-1];
  21290. p1 = data[i];
  21291. p2 = data[i+1];
  21292. p3 = (i + 2 < length) ? data[i+2] : p2;
  21293. d1 = Math.sqrt(Math.pow(p0.x - p1.x,2) + Math.pow(p0.y - p1.y,2));
  21294. d2 = Math.sqrt(Math.pow(p1.x - p2.x,2) + Math.pow(p1.y - p2.y,2));
  21295. d3 = Math.sqrt(Math.pow(p2.x - p3.x,2) + Math.pow(p2.y - p3.y,2));
  21296. // Catmull-Rom to Cubic Bezier conversion matrix
  21297. // A = 2d1^2a + 3d1^a * d2^a + d3^2a
  21298. // B = 2d3^2a + 3d3^a * d2^a + d2^2a
  21299. // [ 0 1 0 0 ]
  21300. // [ -d2^2a /N A/N d1^2a /N 0 ]
  21301. // [ 0 d3^2a /M B/M -d2^2a /M ]
  21302. // [ 0 0 1 0 ]
  21303. d3powA = Math.pow(d3, alpha);
  21304. d3pow2A = Math.pow(d3,2*alpha);
  21305. d2powA = Math.pow(d2, alpha);
  21306. d2pow2A = Math.pow(d2,2*alpha);
  21307. d1powA = Math.pow(d1, alpha);
  21308. d1pow2A = Math.pow(d1,2*alpha);
  21309. A = 2*d1pow2A + 3*d1powA * d2powA + d2pow2A;
  21310. B = 2*d3pow2A + 3*d3powA * d2powA + d2pow2A;
  21311. N = 3*d1powA * (d1powA + d2powA);
  21312. if (N > 0) {N = 1 / N;}
  21313. M = 3*d3powA * (d3powA + d2powA);
  21314. if (M > 0) {M = 1 / M;}
  21315. bp1 = { x: ((-d2pow2A * p0.x + A*p1.x + d1pow2A * p2.x) * N),
  21316. y: ((-d2pow2A * p0.y + A*p1.y + d1pow2A * p2.y) * N)};
  21317. bp2 = { x: (( d3pow2A * p1.x + B*p2.x - d2pow2A * p3.x) * M),
  21318. y: (( d3pow2A * p1.y + B*p2.y - d2pow2A * p3.y) * M)};
  21319. if (bp1.x == 0 && bp1.y == 0) {bp1 = p1;}
  21320. if (bp2.x == 0 && bp2.y == 0) {bp2 = p2;}
  21321. d += 'C' +
  21322. bp1.x + ',' +
  21323. bp1.y + ' ' +
  21324. bp2.x + ',' +
  21325. bp2.y + ' ' +
  21326. p2.x + ',' +
  21327. p2.y + ' ';
  21328. }
  21329. return d;
  21330. }
  21331. };
  21332. /**
  21333. * this generates the SVG path for a linear drawing between datapoints.
  21334. * @param data
  21335. * @returns {string}
  21336. * @private
  21337. */
  21338. Line._linear = function(data) {
  21339. // linear
  21340. var d = '';
  21341. for (var i = 0; i < data.length; i++) {
  21342. if (i == 0) {
  21343. d += data[i].x + ',' + data[i].y;
  21344. }
  21345. else {
  21346. d += ' ' + data[i].x + ',' + data[i].y;
  21347. }
  21348. }
  21349. return d;
  21350. };
  21351. module.exports = Line;
  21352. /***/ },
  21353. /* 50 */
  21354. /***/ function(module, exports, __webpack_require__) {
  21355. /**
  21356. * Created by Alex on 11/11/2014.
  21357. */
  21358. var DOMutil = __webpack_require__(2);
  21359. var Points = __webpack_require__(51);
  21360. function Bargraph(groupId, options) {
  21361. this.groupId = groupId;
  21362. this.options = options;
  21363. }
  21364. Bargraph.prototype.getYRange = function(groupData) {
  21365. if (this.options.barChart.handleOverlap != 'stack') {
  21366. var yMin = groupData[0].y;
  21367. var yMax = groupData[0].y;
  21368. for (var j = 0; j < groupData.length; j++) {
  21369. yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
  21370. yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
  21371. }
  21372. return {min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation};
  21373. }
  21374. else {
  21375. var barCombinedData = [];
  21376. for (var j = 0; j < groupData.length; j++) {
  21377. barCombinedData.push({
  21378. x: groupData[j].x,
  21379. y: groupData[j].y,
  21380. groupId: this.groupId
  21381. });
  21382. }
  21383. return barCombinedData;
  21384. }
  21385. };
  21386. /**
  21387. * draw a bar graph
  21388. *
  21389. * @param groupIds
  21390. * @param processedGroupData
  21391. */
  21392. Bargraph.draw = function (groupIds, processedGroupData, framework) {
  21393. var combinedData = [];
  21394. var intersections = {};
  21395. var coreDistance;
  21396. var key, drawData;
  21397. var group;
  21398. var i,j;
  21399. var barPoints = 0;
  21400. // combine all barchart data
  21401. for (i = 0; i < groupIds.length; i++) {
  21402. group = framework.groups[groupIds[i]];
  21403. if (group.options.style == 'bar') {
  21404. if (group.visible == true && (framework.options.groups.visibility[groupIds[i]] === undefined || framework.options.groups.visibility[groupIds[i]] == true)) {
  21405. for (j = 0; j < processedGroupData[groupIds[i]].length; j++) {
  21406. combinedData.push({
  21407. x: processedGroupData[groupIds[i]][j].x,
  21408. y: processedGroupData[groupIds[i]][j].y,
  21409. groupId: groupIds[i],
  21410. label: processedGroupData[groupIds[i]][j].label,
  21411. });
  21412. barPoints += 1;
  21413. }
  21414. }
  21415. }
  21416. }
  21417. if (barPoints == 0) {return;}
  21418. // sort by time and by group
  21419. combinedData.sort(function (a, b) {
  21420. if (a.x == b.x) {
  21421. return a.groupId - b.groupId;
  21422. } else {
  21423. return a.x - b.x;
  21424. }
  21425. });
  21426. // get intersections
  21427. Bargraph._getDataIntersections(intersections, combinedData);
  21428. // plot barchart
  21429. for (i = 0; i < combinedData.length; i++) {
  21430. group = framework.groups[combinedData[i].groupId];
  21431. var minWidth = 0.1 * group.options.barChart.width;
  21432. key = combinedData[i].x;
  21433. var heightOffset = 0;
  21434. if (intersections[key] === undefined) {
  21435. if (i+1 < combinedData.length) {coreDistance = Math.abs(combinedData[i+1].x - key);}
  21436. if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[i-1].x - key));}
  21437. drawData = Bargraph._getSafeDrawData(coreDistance, group, minWidth);
  21438. }
  21439. else {
  21440. var nextKey = i + (intersections[key].amount - intersections[key].resolved);
  21441. var prevKey = i - (intersections[key].resolved + 1);
  21442. if (nextKey < combinedData.length) {coreDistance = Math.abs(combinedData[nextKey].x - key);}
  21443. if (prevKey > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[prevKey].x - key));}
  21444. drawData = Bargraph._getSafeDrawData(coreDistance, group, minWidth);
  21445. intersections[key].resolved += 1;
  21446. if (group.options.barChart.handleOverlap == 'stack') {
  21447. heightOffset = intersections[key].accumulated;
  21448. intersections[key].accumulated += group.zeroPosition - combinedData[i].y;
  21449. }
  21450. else if (group.options.barChart.handleOverlap == 'sideBySide') {
  21451. drawData.width = drawData.width / intersections[key].amount;
  21452. drawData.offset += (intersections[key].resolved) * drawData.width - (0.5*drawData.width * (intersections[key].amount+1));
  21453. if (group.options.barChart.align == 'left') {drawData.offset -= 0.5*drawData.width;}
  21454. else if (group.options.barChart.align == 'right') {drawData.offset += 0.5*drawData.width;}
  21455. }
  21456. }
  21457. DOMutil.drawBar(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, drawData.width, group.zeroPosition - combinedData[i].y, group.className + ' bar', framework.svgElements, framework.svg);
  21458. // draw points
  21459. if (group.options.drawPoints.enabled == true) {
  21460. DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y, group, framework.svgElements, framework.svg, combinedData[i].label);
  21461. }
  21462. }
  21463. };
  21464. /**
  21465. * Fill the intersections object with counters of how many datapoints share the same x coordinates
  21466. * @param intersections
  21467. * @param combinedData
  21468. * @private
  21469. */
  21470. Bargraph._getDataIntersections = function (intersections, combinedData) {
  21471. // get intersections
  21472. var coreDistance;
  21473. for (var i = 0; i < combinedData.length; i++) {
  21474. if (i + 1 < combinedData.length) {
  21475. coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x);
  21476. }
  21477. if (i > 0) {
  21478. coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x));
  21479. }
  21480. if (coreDistance == 0) {
  21481. if (intersections[combinedData[i].x] === undefined) {
  21482. intersections[combinedData[i].x] = {amount: 0, resolved: 0, accumulated: 0};
  21483. }
  21484. intersections[combinedData[i].x].amount += 1;
  21485. }
  21486. }
  21487. };
  21488. /**
  21489. * Get the width and offset for bargraphs based on the coredistance between datapoints
  21490. *
  21491. * @param coreDistance
  21492. * @param group
  21493. * @param minWidth
  21494. * @returns {{width: Number, offset: Number}}
  21495. * @private
  21496. */
  21497. Bargraph._getSafeDrawData = function (coreDistance, group, minWidth) {
  21498. var width, offset;
  21499. if (coreDistance < group.options.barChart.width && coreDistance > 0) {
  21500. width = coreDistance < minWidth ? minWidth : coreDistance;
  21501. offset = 0; // recalculate offset with the new width;
  21502. if (group.options.barChart.align == 'left') {
  21503. offset -= 0.5 * coreDistance;
  21504. }
  21505. else if (group.options.barChart.align == 'right') {
  21506. offset += 0.5 * coreDistance;
  21507. }
  21508. }
  21509. else {
  21510. // default settings
  21511. width = group.options.barChart.width;
  21512. offset = 0;
  21513. if (group.options.barChart.align == 'left') {
  21514. offset -= 0.5 * group.options.barChart.width;
  21515. }
  21516. else if (group.options.barChart.align == 'right') {
  21517. offset += 0.5 * group.options.barChart.width;
  21518. }
  21519. }
  21520. return {width: width, offset: offset};
  21521. };
  21522. Bargraph.getStackedBarYRange = function(barCombinedData, groupRanges, groupIds, groupLabel, orientation) {
  21523. if (barCombinedData.length > 0) {
  21524. // sort by time and by group
  21525. barCombinedData.sort(function (a, b) {
  21526. if (a.x == b.x) {
  21527. return a.groupId - b.groupId;
  21528. } else {
  21529. return a.x - b.x;
  21530. }
  21531. });
  21532. var intersections = {};
  21533. Bargraph._getDataIntersections(intersections, barCombinedData);
  21534. groupRanges[groupLabel] = Bargraph._getStackedBarYRange(intersections, barCombinedData);
  21535. groupRanges[groupLabel].yAxisOrientation = orientation;
  21536. groupIds.push(groupLabel);
  21537. }
  21538. }
  21539. Bargraph._getStackedBarYRange = function (intersections, combinedData) {
  21540. var key;
  21541. var yMin = combinedData[0].y;
  21542. var yMax = combinedData[0].y;
  21543. for (var i = 0; i < combinedData.length; i++) {
  21544. key = combinedData[i].x;
  21545. if (intersections[key] === undefined) {
  21546. yMin = yMin > combinedData[i].y ? combinedData[i].y : yMin;
  21547. yMax = yMax < combinedData[i].y ? combinedData[i].y : yMax;
  21548. }
  21549. else {
  21550. intersections[key].accumulated += combinedData[i].y;
  21551. }
  21552. }
  21553. for (var xpos in intersections) {
  21554. if (intersections.hasOwnProperty(xpos)) {
  21555. yMin = yMin > intersections[xpos].accumulated ? intersections[xpos].accumulated : yMin;
  21556. yMax = yMax < intersections[xpos].accumulated ? intersections[xpos].accumulated : yMax;
  21557. }
  21558. }
  21559. return {min: yMin, max: yMax};
  21560. };
  21561. module.exports = Bargraph;
  21562. /***/ },
  21563. /* 51 */
  21564. /***/ function(module, exports, __webpack_require__) {
  21565. /**
  21566. * Created by Alex on 11/11/2014.
  21567. */
  21568. var DOMutil = __webpack_require__(2);
  21569. function Points(groupId, options) {
  21570. this.groupId = groupId;
  21571. this.options = options;
  21572. }
  21573. Points.prototype.getYRange = function(groupData) {
  21574. var yMin = groupData[0].y;
  21575. var yMax = groupData[0].y;
  21576. for (var j = 0; j < groupData.length; j++) {
  21577. yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
  21578. yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
  21579. }
  21580. return {min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation};
  21581. };
  21582. Points.prototype.draw = function(dataset, group, framework, offset) {
  21583. Points.draw(dataset, group, framework, offset);
  21584. }
  21585. /**
  21586. * draw the data points
  21587. *
  21588. * @param {Array} dataset
  21589. * @param {Object} JSONcontainer
  21590. * @param {Object} svg | SVG DOM element
  21591. * @param {GraphGroup} group
  21592. * @param {Number} [offset]
  21593. */
  21594. Points.draw = function (dataset, group, framework, offset) {
  21595. if (offset === undefined) {offset = 0;}
  21596. for (var i = 0; i < dataset.length; i++) {
  21597. DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, group, framework.svgElements, framework.svg, dataset[i].label);
  21598. }
  21599. };
  21600. module.exports = Points;
  21601. /***/ },
  21602. /* 52 */
  21603. /***/ function(module, exports, __webpack_require__) {
  21604. var PhysicsMixin = __webpack_require__(60);
  21605. var ClusterMixin = __webpack_require__(61);
  21606. var SectorsMixin = __webpack_require__(62);
  21607. var SelectionMixin = __webpack_require__(63);
  21608. var ManipulationMixin = __webpack_require__(64);
  21609. var NavigationMixin = __webpack_require__(65);
  21610. var HierarchicalLayoutMixin = __webpack_require__(66);
  21611. /**
  21612. * Load a mixin into the network object
  21613. *
  21614. * @param {Object} sourceVariable | this object has to contain functions.
  21615. * @private
  21616. */
  21617. exports._loadMixin = function (sourceVariable) {
  21618. for (var mixinFunction in sourceVariable) {
  21619. if (sourceVariable.hasOwnProperty(mixinFunction)) {
  21620. this[mixinFunction] = sourceVariable[mixinFunction];
  21621. }
  21622. }
  21623. };
  21624. /**
  21625. * removes a mixin from the network object.
  21626. *
  21627. * @param {Object} sourceVariable | this object has to contain functions.
  21628. * @private
  21629. */
  21630. exports._clearMixin = function (sourceVariable) {
  21631. for (var mixinFunction in sourceVariable) {
  21632. if (sourceVariable.hasOwnProperty(mixinFunction)) {
  21633. this[mixinFunction] = undefined;
  21634. }
  21635. }
  21636. };
  21637. /**
  21638. * Mixin the physics system and initialize the parameters required.
  21639. *
  21640. * @private
  21641. */
  21642. exports._loadPhysicsSystem = function () {
  21643. this._loadMixin(PhysicsMixin);
  21644. this._loadSelectedForceSolver();
  21645. if (this.constants.configurePhysics == true) {
  21646. this._loadPhysicsConfiguration();
  21647. }
  21648. else {
  21649. this._cleanupPhysicsConfiguration();
  21650. }
  21651. };
  21652. /**
  21653. * Mixin the cluster system and initialize the parameters required.
  21654. *
  21655. * @private
  21656. */
  21657. exports._loadClusterSystem = function () {
  21658. this.clusterSession = 0;
  21659. this.hubThreshold = 5;
  21660. this._loadMixin(ClusterMixin);
  21661. };
  21662. /**
  21663. * Mixin the sector system and initialize the parameters required
  21664. *
  21665. * @private
  21666. */
  21667. exports._loadSectorSystem = function () {
  21668. this.sectors = {};
  21669. this.activeSector = ["default"];
  21670. this.sectors["active"] = {};
  21671. this.sectors["active"]["default"] = {"nodes": {},
  21672. "edges": {},
  21673. "nodeIndices": [],
  21674. "formationScale": 1.0,
  21675. "drawingNode": undefined };
  21676. this.sectors["frozen"] = {};
  21677. this.sectors["support"] = {"nodes": {},
  21678. "edges": {},
  21679. "nodeIndices": [],
  21680. "formationScale": 1.0,
  21681. "drawingNode": undefined };
  21682. this.nodeIndices = this.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields
  21683. this._loadMixin(SectorsMixin);
  21684. };
  21685. /**
  21686. * Mixin the selection system and initialize the parameters required
  21687. *
  21688. * @private
  21689. */
  21690. exports._loadSelectionSystem = function () {
  21691. this.selectionObj = {nodes: {}, edges: {}};
  21692. this._loadMixin(SelectionMixin);
  21693. };
  21694. /**
  21695. * Mixin the navigationUI (User Interface) system and initialize the parameters required
  21696. *
  21697. * @private
  21698. */
  21699. exports._loadManipulationSystem = function () {
  21700. // reset global variables -- these are used by the selection of nodes and edges.
  21701. this.blockConnectingEdgeSelection = false;
  21702. this.forceAppendSelection = false;
  21703. if (this.constants.dataManipulation.enabled == true) {
  21704. // load the manipulator HTML elements. All styling done in css.
  21705. if (this.manipulationDiv === undefined) {
  21706. this.manipulationDiv = document.createElement('div');
  21707. this.manipulationDiv.className = 'network-manipulationDiv';
  21708. if (this.editMode == true) {
  21709. this.manipulationDiv.style.display = "block";
  21710. }
  21711. else {
  21712. this.manipulationDiv.style.display = "none";
  21713. }
  21714. this.frame.appendChild(this.manipulationDiv);
  21715. }
  21716. if (this.editModeDiv === undefined) {
  21717. this.editModeDiv = document.createElement('div');
  21718. this.editModeDiv.className = 'network-manipulation-editMode';
  21719. if (this.editMode == true) {
  21720. this.editModeDiv.style.display = "none";
  21721. }
  21722. else {
  21723. this.editModeDiv.style.display = "block";
  21724. }
  21725. this.frame.appendChild(this.editModeDiv);
  21726. }
  21727. if (this.closeDiv === undefined) {
  21728. this.closeDiv = document.createElement('div');
  21729. this.closeDiv.className = 'network-manipulation-closeDiv';
  21730. this.closeDiv.style.display = this.manipulationDiv.style.display;
  21731. this.frame.appendChild(this.closeDiv);
  21732. }
  21733. // load the manipulation functions
  21734. this._loadMixin(ManipulationMixin);
  21735. // create the manipulator toolbar
  21736. this._createManipulatorBar();
  21737. }
  21738. else {
  21739. if (this.manipulationDiv !== undefined) {
  21740. // removes all the bindings and overloads
  21741. this._createManipulatorBar();
  21742. // remove the manipulation divs
  21743. this.frame.removeChild(this.manipulationDiv);
  21744. this.frame.removeChild(this.editModeDiv);
  21745. this.frame.removeChild(this.closeDiv);
  21746. this.manipulationDiv = undefined;
  21747. this.editModeDiv = undefined;
  21748. this.closeDiv = undefined;
  21749. // remove the mixin functions
  21750. this._clearMixin(ManipulationMixin);
  21751. }
  21752. }
  21753. };
  21754. /**
  21755. * Mixin the navigation (User Interface) system and initialize the parameters required
  21756. *
  21757. * @private
  21758. */
  21759. exports._loadNavigationControls = function () {
  21760. this._loadMixin(NavigationMixin);
  21761. // the clean function removes the button divs, this is done to remove the bindings.
  21762. this._cleanNavigation();
  21763. if (this.constants.navigation.enabled == true) {
  21764. this._loadNavigationElements();
  21765. }
  21766. };
  21767. /**
  21768. * Mixin the hierarchical layout system.
  21769. *
  21770. * @private
  21771. */
  21772. exports._loadHierarchySystem = function () {
  21773. this._loadMixin(HierarchicalLayoutMixin);
  21774. };
  21775. /***/ },
  21776. /* 53 */
  21777. /***/ function(module, exports, __webpack_require__) {
  21778. var keycharm = __webpack_require__(57);
  21779. var Emitter = __webpack_require__(56);
  21780. var Hammer = __webpack_require__(45);
  21781. var util = __webpack_require__(1);
  21782. /**
  21783. * Turn an element into an clickToUse element.
  21784. * When not active, the element has a transparent overlay. When the overlay is
  21785. * clicked, the mode is changed to active.
  21786. * When active, the element is displayed with a blue border around it, and
  21787. * the interactive contents of the element can be used. When clicked outside
  21788. * the element, the elements mode is changed to inactive.
  21789. * @param {Element} container
  21790. * @constructor
  21791. */
  21792. function Activator(container) {
  21793. this.active = false;
  21794. this.dom = {
  21795. container: container
  21796. };
  21797. this.dom.overlay = document.createElement('div');
  21798. this.dom.overlay.className = 'overlay';
  21799. this.dom.container.appendChild(this.dom.overlay);
  21800. this.hammer = Hammer(this.dom.overlay, {prevent_default: false});
  21801. this.hammer.on('tap', this._onTapOverlay.bind(this));
  21802. // block all touch events (except tap)
  21803. var me = this;
  21804. var events = [
  21805. 'touch', 'pinch',
  21806. 'doubletap', 'hold',
  21807. 'dragstart', 'drag', 'dragend',
  21808. 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
  21809. ];
  21810. events.forEach(function (event) {
  21811. me.hammer.on(event, function (event) {
  21812. event.stopPropagation();
  21813. });
  21814. });
  21815. // attach a tap event to the window, in order to deactivate when clicking outside the timeline
  21816. this.windowHammer = Hammer(window, {prevent_default: false});
  21817. this.windowHammer.on('tap', function (event) {
  21818. // deactivate when clicked outside the container
  21819. if (!_hasParent(event.target, container)) {
  21820. me.deactivate();
  21821. }
  21822. });
  21823. if (this.keycharm !== undefined) {
  21824. this.keycharm.destroy();
  21825. }
  21826. this.keycharm = keycharm();
  21827. // keycharm listener only bounded when active)
  21828. this.escListener = this.deactivate.bind(this);
  21829. }
  21830. // turn into an event emitter
  21831. Emitter(Activator.prototype);
  21832. // The currently active activator
  21833. Activator.current = null;
  21834. /**
  21835. * Destroy the activator. Cleans up all created DOM and event listeners
  21836. */
  21837. Activator.prototype.destroy = function () {
  21838. this.deactivate();
  21839. // remove dom
  21840. this.dom.overlay.parentNode.removeChild(this.dom.overlay);
  21841. // cleanup hammer instances
  21842. this.hammer = null;
  21843. this.windowHammer = null;
  21844. // FIXME: cleaning up hammer instances doesn't work (Timeline not removed from memory)
  21845. };
  21846. /**
  21847. * Activate the element
  21848. * Overlay is hidden, element is decorated with a blue shadow border
  21849. */
  21850. Activator.prototype.activate = function () {
  21851. // we allow only one active activator at a time
  21852. if (Activator.current) {
  21853. Activator.current.deactivate();
  21854. }
  21855. Activator.current = this;
  21856. this.active = true;
  21857. this.dom.overlay.style.display = 'none';
  21858. util.addClassName(this.dom.container, 'vis-active');
  21859. this.emit('change');
  21860. this.emit('activate');
  21861. // ugly hack: bind ESC after emitting the events, as the Network rebinds all
  21862. // keyboard events on a 'change' event
  21863. this.keycharm.bind('esc', this.escListener);
  21864. };
  21865. /**
  21866. * Deactivate the element
  21867. * Overlay is displayed on top of the element
  21868. */
  21869. Activator.prototype.deactivate = function () {
  21870. this.active = false;
  21871. this.dom.overlay.style.display = '';
  21872. util.removeClassName(this.dom.container, 'vis-active');
  21873. this.keycharm.unbind('esc', this.escListener);
  21874. this.emit('change');
  21875. this.emit('deactivate');
  21876. };
  21877. /**
  21878. * Handle a tap event: activate the container
  21879. * @param event
  21880. * @private
  21881. */
  21882. Activator.prototype._onTapOverlay = function (event) {
  21883. // activate the container
  21884. this.activate();
  21885. event.stopPropagation();
  21886. };
  21887. /**
  21888. * Test whether the element has the requested parent element somewhere in
  21889. * its chain of parent nodes.
  21890. * @param {HTMLElement} element
  21891. * @param {HTMLElement} parent
  21892. * @returns {boolean} Returns true when the parent is found somewhere in the
  21893. * chain of parent nodes.
  21894. * @private
  21895. */
  21896. function _hasParent(element, parent) {
  21897. while (element) {
  21898. if (element === parent) {
  21899. return true
  21900. }
  21901. element = element.parentNode;
  21902. }
  21903. return false;
  21904. }
  21905. module.exports = Activator;
  21906. /***/ },
  21907. /* 54 */
  21908. /***/ function(module, exports, __webpack_require__) {
  21909. // English
  21910. exports['en'] = {
  21911. edit: 'Edit',
  21912. del: 'Delete selected',
  21913. back: 'Back',
  21914. addNode: 'Add Node',
  21915. addEdge: 'Add Edge',
  21916. editNode: 'Edit Node',
  21917. editEdge: 'Edit Edge',
  21918. addDescription: 'Click in an empty space to place a new node.',
  21919. edgeDescription: 'Click on a node and drag the edge to another node to connect them.',
  21920. editEdgeDescription: 'Click on the control points and drag them to a node to connect to it.',
  21921. createEdgeError: 'Cannot link edges to a cluster.',
  21922. deleteClusterError: 'Clusters cannot be deleted.'
  21923. };
  21924. exports['en_EN'] = exports['en'];
  21925. exports['en_US'] = exports['en'];
  21926. // Dutch
  21927. exports['nl'] = {
  21928. edit: 'Wijzigen',
  21929. del: 'Selectie verwijderen',
  21930. back: 'Terug',
  21931. addNode: 'Node toevoegen',
  21932. addEdge: 'Link toevoegen',
  21933. editNode: 'Node wijzigen',
  21934. editEdge: 'Link wijzigen',
  21935. addDescription: 'Klik op een leeg gebied om een nieuwe node te maken.',
  21936. edgeDescription: 'Klik op een node en sleep de link naar een andere node om ze te verbinden.',
  21937. editEdgeDescription: 'Klik op de verbindingspunten en sleep ze naar een node om daarmee te verbinden.',
  21938. createEdgeError: 'Kan geen link maken naar een cluster.',
  21939. deleteClusterError: 'Clusters kunnen niet worden verwijderd.'
  21940. };
  21941. exports['nl_NL'] = exports['nl'];
  21942. exports['nl_BE'] = exports['nl'];
  21943. /***/ },
  21944. /* 55 */
  21945. /***/ function(module, exports, __webpack_require__) {
  21946. /**
  21947. * Canvas shapes used by Network
  21948. */
  21949. if (typeof CanvasRenderingContext2D !== 'undefined') {
  21950. /**
  21951. * Draw a circle shape
  21952. */
  21953. CanvasRenderingContext2D.prototype.circle = function(x, y, r) {
  21954. this.beginPath();
  21955. this.arc(x, y, r, 0, 2*Math.PI, false);
  21956. };
  21957. /**
  21958. * Draw a square shape
  21959. * @param {Number} x horizontal center
  21960. * @param {Number} y vertical center
  21961. * @param {Number} r size, width and height of the square
  21962. */
  21963. CanvasRenderingContext2D.prototype.square = function(x, y, r) {
  21964. this.beginPath();
  21965. this.rect(x - r, y - r, r * 2, r * 2);
  21966. };
  21967. /**
  21968. * Draw a triangle shape
  21969. * @param {Number} x horizontal center
  21970. * @param {Number} y vertical center
  21971. * @param {Number} r radius, half the length of the sides of the triangle
  21972. */
  21973. CanvasRenderingContext2D.prototype.triangle = function(x, y, r) {
  21974. // http://en.wikipedia.org/wiki/Equilateral_triangle
  21975. this.beginPath();
  21976. var s = r * 2;
  21977. var s2 = s / 2;
  21978. var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
  21979. var h = Math.sqrt(s * s - s2 * s2); // height
  21980. this.moveTo(x, y - (h - ir));
  21981. this.lineTo(x + s2, y + ir);
  21982. this.lineTo(x - s2, y + ir);
  21983. this.lineTo(x, y - (h - ir));
  21984. this.closePath();
  21985. };
  21986. /**
  21987. * Draw a triangle shape in downward orientation
  21988. * @param {Number} x horizontal center
  21989. * @param {Number} y vertical center
  21990. * @param {Number} r radius
  21991. */
  21992. CanvasRenderingContext2D.prototype.triangleDown = function(x, y, r) {
  21993. // http://en.wikipedia.org/wiki/Equilateral_triangle
  21994. this.beginPath();
  21995. var s = r * 2;
  21996. var s2 = s / 2;
  21997. var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
  21998. var h = Math.sqrt(s * s - s2 * s2); // height
  21999. this.moveTo(x, y + (h - ir));
  22000. this.lineTo(x + s2, y - ir);
  22001. this.lineTo(x - s2, y - ir);
  22002. this.lineTo(x, y + (h - ir));
  22003. this.closePath();
  22004. };
  22005. /**
  22006. * Draw a star shape, a star with 5 points
  22007. * @param {Number} x horizontal center
  22008. * @param {Number} y vertical center
  22009. * @param {Number} r radius, half the length of the sides of the triangle
  22010. */
  22011. CanvasRenderingContext2D.prototype.star = function(x, y, r) {
  22012. // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/
  22013. this.beginPath();
  22014. for (var n = 0; n < 10; n++) {
  22015. var radius = (n % 2 === 0) ? r * 1.3 : r * 0.5;
  22016. this.lineTo(
  22017. x + radius * Math.sin(n * 2 * Math.PI / 10),
  22018. y - radius * Math.cos(n * 2 * Math.PI / 10)
  22019. );
  22020. }
  22021. this.closePath();
  22022. };
  22023. /**
  22024. * http://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas
  22025. */
  22026. CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
  22027. var r2d = Math.PI/180;
  22028. if( w - ( 2 * r ) < 0 ) { r = ( w / 2 ); } //ensure that the radius isn't too large for x
  22029. if( h - ( 2 * r ) < 0 ) { r = ( h / 2 ); } //ensure that the radius isn't too large for y
  22030. this.beginPath();
  22031. this.moveTo(x+r,y);
  22032. this.lineTo(x+w-r,y);
  22033. this.arc(x+w-r,y+r,r,r2d*270,r2d*360,false);
  22034. this.lineTo(x+w,y+h-r);
  22035. this.arc(x+w-r,y+h-r,r,0,r2d*90,false);
  22036. this.lineTo(x+r,y+h);
  22037. this.arc(x+r,y+h-r,r,r2d*90,r2d*180,false);
  22038. this.lineTo(x,y+r);
  22039. this.arc(x+r,y+r,r,r2d*180,r2d*270,false);
  22040. };
  22041. /**
  22042. * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  22043. */
  22044. CanvasRenderingContext2D.prototype.ellipse = function(x, y, w, h) {
  22045. var kappa = .5522848,
  22046. ox = (w / 2) * kappa, // control point offset horizontal
  22047. oy = (h / 2) * kappa, // control point offset vertical
  22048. xe = x + w, // x-end
  22049. ye = y + h, // y-end
  22050. xm = x + w / 2, // x-middle
  22051. ym = y + h / 2; // y-middle
  22052. this.beginPath();
  22053. this.moveTo(x, ym);
  22054. this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  22055. this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  22056. this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  22057. this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  22058. };
  22059. /**
  22060. * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  22061. */
  22062. CanvasRenderingContext2D.prototype.database = function(x, y, w, h) {
  22063. var f = 1/3;
  22064. var wEllipse = w;
  22065. var hEllipse = h * f;
  22066. var kappa = .5522848,
  22067. ox = (wEllipse / 2) * kappa, // control point offset horizontal
  22068. oy = (hEllipse / 2) * kappa, // control point offset vertical
  22069. xe = x + wEllipse, // x-end
  22070. ye = y + hEllipse, // y-end
  22071. xm = x + wEllipse / 2, // x-middle
  22072. ym = y + hEllipse / 2, // y-middle
  22073. ymb = y + (h - hEllipse/2), // y-midlle, bottom ellipse
  22074. yeb = y + h; // y-end, bottom ellipse
  22075. this.beginPath();
  22076. this.moveTo(xe, ym);
  22077. this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  22078. this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  22079. this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  22080. this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  22081. this.lineTo(xe, ymb);
  22082. this.bezierCurveTo(xe, ymb + oy, xm + ox, yeb, xm, yeb);
  22083. this.bezierCurveTo(xm - ox, yeb, x, ymb + oy, x, ymb);
  22084. this.lineTo(x, ym);
  22085. };
  22086. /**
  22087. * Draw an arrow point (no line)
  22088. */
  22089. CanvasRenderingContext2D.prototype.arrow = function(x, y, angle, length) {
  22090. // tail
  22091. var xt = x - length * Math.cos(angle);
  22092. var yt = y - length * Math.sin(angle);
  22093. // inner tail
  22094. // TODO: allow to customize different shapes
  22095. var xi = x - length * 0.9 * Math.cos(angle);
  22096. var yi = y - length * 0.9 * Math.sin(angle);
  22097. // left
  22098. var xl = xt + length / 3 * Math.cos(angle + 0.5 * Math.PI);
  22099. var yl = yt + length / 3 * Math.sin(angle + 0.5 * Math.PI);
  22100. // right
  22101. var xr = xt + length / 3 * Math.cos(angle - 0.5 * Math.PI);
  22102. var yr = yt + length / 3 * Math.sin(angle - 0.5 * Math.PI);
  22103. this.beginPath();
  22104. this.moveTo(x, y);
  22105. this.lineTo(xl, yl);
  22106. this.lineTo(xi, yi);
  22107. this.lineTo(xr, yr);
  22108. this.closePath();
  22109. };
  22110. /**
  22111. * Sets up the dashedLine functionality for drawing
  22112. * Original code came from http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
  22113. * @author David Jordan
  22114. * @date 2012-08-08
  22115. */
  22116. CanvasRenderingContext2D.prototype.dashedLine = function(x,y,x2,y2,dashArray){
  22117. if (!dashArray) dashArray=[10,5];
  22118. if (dashLength==0) dashLength = 0.001; // Hack for Safari
  22119. var dashCount = dashArray.length;
  22120. this.moveTo(x, y);
  22121. var dx = (x2-x), dy = (y2-y);
  22122. var slope = dy/dx;
  22123. var distRemaining = Math.sqrt( dx*dx + dy*dy );
  22124. var dashIndex=0, draw=true;
  22125. while (distRemaining>=0.1){
  22126. var dashLength = dashArray[dashIndex++%dashCount];
  22127. if (dashLength > distRemaining) dashLength = distRemaining;
  22128. var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) );
  22129. if (dx<0) xStep = -xStep;
  22130. x += xStep;
  22131. y += slope*xStep;
  22132. this[draw ? 'lineTo' : 'moveTo'](x,y);
  22133. distRemaining -= dashLength;
  22134. draw = !draw;
  22135. }
  22136. };
  22137. // TODO: add diamond shape
  22138. }
  22139. /***/ },
  22140. /* 56 */
  22141. /***/ function(module, exports, __webpack_require__) {
  22142. /**
  22143. * Expose `Emitter`.
  22144. */
  22145. module.exports = Emitter;
  22146. /**
  22147. * Initialize a new `Emitter`.
  22148. *
  22149. * @api public
  22150. */
  22151. function Emitter(obj) {
  22152. if (obj) return mixin(obj);
  22153. };
  22154. /**
  22155. * Mixin the emitter properties.
  22156. *
  22157. * @param {Object} obj
  22158. * @return {Object}
  22159. * @api private
  22160. */
  22161. function mixin(obj) {
  22162. for (var key in Emitter.prototype) {
  22163. obj[key] = Emitter.prototype[key];
  22164. }
  22165. return obj;
  22166. }
  22167. /**
  22168. * Listen on the given `event` with `fn`.
  22169. *
  22170. * @param {String} event
  22171. * @param {Function} fn
  22172. * @return {Emitter}
  22173. * @api public
  22174. */
  22175. Emitter.prototype.on =
  22176. Emitter.prototype.addEventListener = function(event, fn){
  22177. this._callbacks = this._callbacks || {};
  22178. (this._callbacks[event] = this._callbacks[event] || [])
  22179. .push(fn);
  22180. return this;
  22181. };
  22182. /**
  22183. * Adds an `event` listener that will be invoked a single
  22184. * time then automatically removed.
  22185. *
  22186. * @param {String} event
  22187. * @param {Function} fn
  22188. * @return {Emitter}
  22189. * @api public
  22190. */
  22191. Emitter.prototype.once = function(event, fn){
  22192. var self = this;
  22193. this._callbacks = this._callbacks || {};
  22194. function on() {
  22195. self.off(event, on);
  22196. fn.apply(this, arguments);
  22197. }
  22198. on.fn = fn;
  22199. this.on(event, on);
  22200. return this;
  22201. };
  22202. /**
  22203. * Remove the given callback for `event` or all
  22204. * registered callbacks.
  22205. *
  22206. * @param {String} event
  22207. * @param {Function} fn
  22208. * @return {Emitter}
  22209. * @api public
  22210. */
  22211. Emitter.prototype.off =
  22212. Emitter.prototype.removeListener =
  22213. Emitter.prototype.removeAllListeners =
  22214. Emitter.prototype.removeEventListener = function(event, fn){
  22215. this._callbacks = this._callbacks || {};
  22216. // all
  22217. if (0 == arguments.length) {
  22218. this._callbacks = {};
  22219. return this;
  22220. }
  22221. // specific event
  22222. var callbacks = this._callbacks[event];
  22223. if (!callbacks) return this;
  22224. // remove all handlers
  22225. if (1 == arguments.length) {
  22226. delete this._callbacks[event];
  22227. return this;
  22228. }
  22229. // remove specific handler
  22230. var cb;
  22231. for (var i = 0; i < callbacks.length; i++) {
  22232. cb = callbacks[i];
  22233. if (cb === fn || cb.fn === fn) {
  22234. callbacks.splice(i, 1);
  22235. break;
  22236. }
  22237. }
  22238. return this;
  22239. };
  22240. /**
  22241. * Emit `event` with the given args.
  22242. *
  22243. * @param {String} event
  22244. * @param {Mixed} ...
  22245. * @return {Emitter}
  22246. */
  22247. Emitter.prototype.emit = function(event){
  22248. this._callbacks = this._callbacks || {};
  22249. var args = [].slice.call(arguments, 1)
  22250. , callbacks = this._callbacks[event];
  22251. if (callbacks) {
  22252. callbacks = callbacks.slice(0);
  22253. for (var i = 0, len = callbacks.length; i < len; ++i) {
  22254. callbacks[i].apply(this, args);
  22255. }
  22256. }
  22257. return this;
  22258. };
  22259. /**
  22260. * Return array of callbacks for `event`.
  22261. *
  22262. * @param {String} event
  22263. * @return {Array}
  22264. * @api public
  22265. */
  22266. Emitter.prototype.listeners = function(event){
  22267. this._callbacks = this._callbacks || {};
  22268. return this._callbacks[event] || [];
  22269. };
  22270. /**
  22271. * Check if this emitter has `event` handlers.
  22272. *
  22273. * @param {String} event
  22274. * @return {Boolean}
  22275. * @api public
  22276. */
  22277. Emitter.prototype.hasListeners = function(event){
  22278. return !! this.listeners(event).length;
  22279. };
  22280. /***/ },
  22281. /* 57 */
  22282. /***/ function(module, exports, __webpack_require__) {
  22283. var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;"use strict";
  22284. /**
  22285. * Created by Alex on 11/6/2014.
  22286. */
  22287. // https://github.com/umdjs/umd/blob/master/returnExports.js#L40-L60
  22288. // if the module has no dependencies, the above pattern can be simplified to
  22289. (function (root, factory) {
  22290. if (true) {
  22291. // AMD. Register as an anonymous module.
  22292. !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  22293. } else if (typeof exports === 'object') {
  22294. // Node. Does not work with strict CommonJS, but
  22295. // only CommonJS-like environments that support module.exports,
  22296. // like Node.
  22297. module.exports = factory();
  22298. } else {
  22299. // Browser globals (root is window)
  22300. root.keycharm = factory();
  22301. }
  22302. }(this, function () {
  22303. function keycharm(options) {
  22304. var preventDefault = options && options.preventDefault || false;
  22305. var container = options && options.container || window;
  22306. var _exportFunctions = {};
  22307. var _bound = {keydown:{}, keyup:{}};
  22308. var _keys = {};
  22309. var i;
  22310. // a - z
  22311. for (i = 97; i <= 122; i++) {_keys[String.fromCharCode(i)] = {code:65 + (i - 97), shift: false};}
  22312. // A - Z
  22313. for (i = 65; i <= 90; i++) {_keys[String.fromCharCode(i)] = {code:i, shift: true};}
  22314. // 0 - 9
  22315. for (i = 0; i <= 9; i++) {_keys['' + i] = {code:48 + i, shift: false};}
  22316. // F1 - F12
  22317. for (i = 1; i <= 12; i++) {_keys['F' + i] = {code:111 + i, shift: false};}
  22318. // num0 - num9
  22319. for (i = 0; i <= 9; i++) {_keys['num' + i] = {code:96 + i, shift: false};}
  22320. // numpad misc
  22321. _keys['num*'] = {code:106, shift: false};
  22322. _keys['num+'] = {code:107, shift: false};
  22323. _keys['num-'] = {code:109, shift: false};
  22324. _keys['num/'] = {code:111, shift: false};
  22325. _keys['num.'] = {code:110, shift: false};
  22326. // arrows
  22327. _keys['left'] = {code:37, shift: false};
  22328. _keys['up'] = {code:38, shift: false};
  22329. _keys['right'] = {code:39, shift: false};
  22330. _keys['down'] = {code:40, shift: false};
  22331. // extra keys
  22332. _keys['space'] = {code:32, shift: false};
  22333. _keys['enter'] = {code:13, shift: false};
  22334. _keys['shift'] = {code:16, shift: undefined};
  22335. _keys['esc'] = {code:27, shift: false};
  22336. _keys['backspace'] = {code:8, shift: false};
  22337. _keys['tab'] = {code:9, shift: false};
  22338. _keys['ctrl'] = {code:17, shift: false};
  22339. _keys['alt'] = {code:18, shift: false};
  22340. _keys['delete'] = {code:46, shift: false};
  22341. _keys['pageup'] = {code:33, shift: false};
  22342. _keys['pagedown'] = {code:34, shift: false};
  22343. // symbols
  22344. _keys['='] = {code:187, shift: false};
  22345. _keys['-'] = {code:189, shift: false};
  22346. _keys[']'] = {code:221, shift: false};
  22347. _keys['['] = {code:219, shift: false};
  22348. var down = function(event) {handleEvent(event,'keydown');};
  22349. var up = function(event) {handleEvent(event,'keyup');};
  22350. // handle the actualy bound key with the event
  22351. var handleEvent = function(event,type) {
  22352. if (_bound[type][event.keyCode] !== undefined) {
  22353. var bound = _bound[type][event.keyCode];
  22354. for (var i = 0; i < bound.length; i++) {
  22355. if (bound[i].shift === undefined) {
  22356. bound[i].fn(event);
  22357. }
  22358. else if (bound[i].shift == true && event.shiftKey == true) {
  22359. bound[i].fn(event);
  22360. }
  22361. else if (bound[i].shift == false && event.shiftKey == false) {
  22362. bound[i].fn(event);
  22363. }
  22364. }
  22365. if (preventDefault == true) {
  22366. event.preventDefault();
  22367. }
  22368. }
  22369. };
  22370. // bind a key to a callback
  22371. _exportFunctions.bind = function(key, callback, type) {
  22372. if (type === undefined) {
  22373. type = 'keydown';
  22374. }
  22375. if (_keys[key] === undefined) {
  22376. throw new Error("unsupported key: " + key);
  22377. }
  22378. if (_bound[type][_keys[key].code] === undefined) {
  22379. _bound[type][_keys[key].code] = [];
  22380. }
  22381. _bound[type][_keys[key].code].push({fn:callback, shift:_keys[key].shift});
  22382. };
  22383. // bind all keys to a call back (demo purposes)
  22384. _exportFunctions.bindAll = function(callback, type) {
  22385. if (type === undefined) {
  22386. type = 'keydown';
  22387. }
  22388. for (var key in _keys) {
  22389. if (_keys.hasOwnProperty(key)) {
  22390. _exportFunctions.bind(key,callback,type);
  22391. }
  22392. }
  22393. };
  22394. // get the key label from an event
  22395. _exportFunctions.getKey = function(event) {
  22396. for (var key in _keys) {
  22397. if (_keys.hasOwnProperty(key)) {
  22398. if (event.shiftKey == true && _keys[key].shift == true && event.keyCode == _keys[key].code) {
  22399. return key;
  22400. }
  22401. else if (event.shiftKey == false && _keys[key].shift == false && event.keyCode == _keys[key].code) {
  22402. return key;
  22403. }
  22404. else if (event.keyCode == _keys[key].code && key == 'shift') {
  22405. return key;
  22406. }
  22407. }
  22408. }
  22409. return "unknown key, currently not supported";
  22410. };
  22411. // unbind either a specific callback from a key or all of them (by leaving callback undefined)
  22412. _exportFunctions.unbind = function(key, callback, type) {
  22413. if (type === undefined) {
  22414. type = 'keydown';
  22415. }
  22416. if (_keys[key] === undefined) {
  22417. throw new Error("unsupported key: " + key);
  22418. }
  22419. if (callback !== undefined) {
  22420. var newBindings = [];
  22421. var bound = _bound[type][_keys[key].code];
  22422. if (bound !== undefined) {
  22423. for (var i = 0; i < bound.length; i++) {
  22424. if (!(bound[i].fn == callback && bound[i].shift == _keys[key].shift)) {
  22425. newBindings.push(_bound[type][_keys[key].code][i]);
  22426. }
  22427. }
  22428. }
  22429. _bound[type][_keys[key].code] = newBindings;
  22430. }
  22431. else {
  22432. _bound[type][_keys[key].code] = [];
  22433. }
  22434. };
  22435. // reset all bound variables.
  22436. _exportFunctions.reset = function() {
  22437. _bound = {keydown:{}, keyup:{}};
  22438. };
  22439. // unbind all listeners and reset all variables.
  22440. _exportFunctions.destroy = function() {
  22441. _bound = {keydown:{}, keyup:{}};
  22442. container.removeEventListener('keydown', down, true);
  22443. container.removeEventListener('keyup', up, true);
  22444. };
  22445. // create listeners.
  22446. container.addEventListener('keydown',down,true);
  22447. container.addEventListener('keyup',up,true);
  22448. // return the public functions.
  22449. return _exportFunctions;
  22450. }
  22451. return keycharm;
  22452. }));
  22453. /***/ },
  22454. /* 58 */
  22455. /***/ function(module, exports, __webpack_require__) {
  22456. /* WEBPACK VAR INJECTION */(function(module) {//! moment.js
  22457. //! version : 2.10.0
  22458. //! authors : Tim Wood, Iskren Chernev, Moment.js contributors
  22459. //! license : MIT
  22460. //! momentjs.com
  22461. (function (global, factory) {
  22462. true ? module.exports = factory() :
  22463. typeof define === 'function' && define.amd ? define(factory) :
  22464. global.moment = factory()
  22465. }(this, function () { 'use strict';
  22466. var hookCallback;
  22467. function hooks__hooks () {
  22468. return hookCallback.apply(null, arguments);
  22469. }
  22470. // This is done to register the method called with moment()
  22471. // without creating circular dependencies.
  22472. function setHookCallback (callback) {
  22473. hookCallback = callback;
  22474. }
  22475. function defaultParsingFlags() {
  22476. // We need to deep clone this object.
  22477. return {
  22478. empty : false,
  22479. unusedTokens : [],
  22480. unusedInput : [],
  22481. overflow : -2,
  22482. charsLeftOver : 0,
  22483. nullInput : false,
  22484. invalidMonth : null,
  22485. invalidFormat : false,
  22486. userInvalidated : false,
  22487. iso : false
  22488. };
  22489. }
  22490. function isArray(input) {
  22491. return Object.prototype.toString.call(input) === '[object Array]';
  22492. }
  22493. function isDate(input) {
  22494. return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date;
  22495. }
  22496. function map(arr, fn) {
  22497. var res = [], i;
  22498. for (i = 0; i < arr.length; ++i) {
  22499. res.push(fn(arr[i], i));
  22500. }
  22501. return res;
  22502. }
  22503. function hasOwnProp(a, b) {
  22504. return Object.prototype.hasOwnProperty.call(a, b);
  22505. }
  22506. function extend(a, b) {
  22507. for (var i in b) {
  22508. if (hasOwnProp(b, i)) {
  22509. a[i] = b[i];
  22510. }
  22511. }
  22512. if (hasOwnProp(b, 'toString')) {
  22513. a.toString = b.toString;
  22514. }
  22515. if (hasOwnProp(b, 'valueOf')) {
  22516. a.valueOf = b.valueOf;
  22517. }
  22518. return a;
  22519. }
  22520. function utc__createUTC (input, format, locale, strict) {
  22521. return createLocalOrUTC(input, format, locale, strict, true).utc();
  22522. }
  22523. function valid__isValid(m) {
  22524. if (m._isValid == null) {
  22525. m._isValid = !isNaN(m._d.getTime()) &&
  22526. m._pf.overflow < 0 &&
  22527. !m._pf.empty &&
  22528. !m._pf.invalidMonth &&
  22529. !m._pf.nullInput &&
  22530. !m._pf.invalidFormat &&
  22531. !m._pf.userInvalidated;
  22532. if (m._strict) {
  22533. m._isValid = m._isValid &&
  22534. m._pf.charsLeftOver === 0 &&
  22535. m._pf.unusedTokens.length === 0 &&
  22536. m._pf.bigHour === undefined;
  22537. }
  22538. }
  22539. return m._isValid;
  22540. }
  22541. function valid__createInvalid (flags) {
  22542. var m = utc__createUTC(NaN);
  22543. if (flags != null) {
  22544. extend(m._pf, flags);
  22545. }
  22546. else {
  22547. m._pf.userInvalidated = true;
  22548. }
  22549. return m;
  22550. }
  22551. var momentProperties = hooks__hooks.momentProperties = [];
  22552. function copyConfig(to, from) {
  22553. var i, prop, val;
  22554. if (typeof from._isAMomentObject !== 'undefined') {
  22555. to._isAMomentObject = from._isAMomentObject;
  22556. }
  22557. if (typeof from._i !== 'undefined') {
  22558. to._i = from._i;
  22559. }
  22560. if (typeof from._f !== 'undefined') {
  22561. to._f = from._f;
  22562. }
  22563. if (typeof from._l !== 'undefined') {
  22564. to._l = from._l;
  22565. }
  22566. if (typeof from._strict !== 'undefined') {
  22567. to._strict = from._strict;
  22568. }
  22569. if (typeof from._tzm !== 'undefined') {
  22570. to._tzm = from._tzm;
  22571. }
  22572. if (typeof from._isUTC !== 'undefined') {
  22573. to._isUTC = from._isUTC;
  22574. }
  22575. if (typeof from._offset !== 'undefined') {
  22576. to._offset = from._offset;
  22577. }
  22578. if (typeof from._pf !== 'undefined') {
  22579. to._pf = from._pf;
  22580. }
  22581. if (typeof from._locale !== 'undefined') {
  22582. to._locale = from._locale;
  22583. }
  22584. if (momentProperties.length > 0) {
  22585. for (i in momentProperties) {
  22586. prop = momentProperties[i];
  22587. val = from[prop];
  22588. if (typeof val !== 'undefined') {
  22589. to[prop] = val;
  22590. }
  22591. }
  22592. }
  22593. return to;
  22594. }
  22595. var updateInProgress = false;
  22596. // Moment prototype object
  22597. function Moment(config) {
  22598. copyConfig(this, config);
  22599. this._d = new Date(+config._d);
  22600. // Prevent infinite loop in case updateOffset creates new moment
  22601. // objects.
  22602. if (updateInProgress === false) {
  22603. updateInProgress = true;
  22604. hooks__hooks.updateOffset(this);
  22605. updateInProgress = false;
  22606. }
  22607. }
  22608. function isMoment (obj) {
  22609. return obj instanceof Moment || (obj != null && hasOwnProp(obj, '_isAMomentObject'));
  22610. }
  22611. function toInt(argumentForCoercion) {
  22612. var coercedNumber = +argumentForCoercion,
  22613. value = 0;
  22614. if (coercedNumber !== 0 && isFinite(coercedNumber)) {
  22615. if (coercedNumber >= 0) {
  22616. value = Math.floor(coercedNumber);
  22617. } else {
  22618. value = Math.ceil(coercedNumber);
  22619. }
  22620. }
  22621. return value;
  22622. }
  22623. function compareArrays(array1, array2, dontConvert) {
  22624. var len = Math.min(array1.length, array2.length),
  22625. lengthDiff = Math.abs(array1.length - array2.length),
  22626. diffs = 0,
  22627. i;
  22628. for (i = 0; i < len; i++) {
  22629. if ((dontConvert && array1[i] !== array2[i]) ||
  22630. (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
  22631. diffs++;
  22632. }
  22633. }
  22634. return diffs + lengthDiff;
  22635. }
  22636. function Locale() {
  22637. }
  22638. var locales = {};
  22639. var globalLocale;
  22640. function normalizeLocale(key) {
  22641. return key ? key.toLowerCase().replace('_', '-') : key;
  22642. }
  22643. // pick the locale from the array
  22644. // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
  22645. // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
  22646. function chooseLocale(names) {
  22647. var i = 0, j, next, locale, split;
  22648. while (i < names.length) {
  22649. split = normalizeLocale(names[i]).split('-');
  22650. j = split.length;
  22651. next = normalizeLocale(names[i + 1]);
  22652. next = next ? next.split('-') : null;
  22653. while (j > 0) {
  22654. locale = loadLocale(split.slice(0, j).join('-'));
  22655. if (locale) {
  22656. return locale;
  22657. }
  22658. if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
  22659. //the next array item is better than a shallower substring of this one
  22660. break;
  22661. }
  22662. j--;
  22663. }
  22664. i++;
  22665. }
  22666. return null;
  22667. }
  22668. function loadLocale(name) {
  22669. var oldLocale = null;
  22670. // TODO: Find a better way to register and load all the locales in Node
  22671. if (!locales[name] && typeof module !== 'undefined' &&
  22672. module && module.exports) {
  22673. try {
  22674. oldLocale = globalLocale._abbr;
  22675. !(function webpackMissingModule() { var e = new Error("Cannot find module \"./locale\""); e.code = 'MODULE_NOT_FOUND'; throw e; }());
  22676. // because defineLocale currently also sets the global locale, we
  22677. // want to undo that for lazy loaded locales
  22678. locales__getSetGlobalLocale(oldLocale);
  22679. } catch (e) { }
  22680. }
  22681. return locales[name];
  22682. }
  22683. // This function will load locale and then set the global locale. If
  22684. // no arguments are passed in, it will simply return the current global
  22685. // locale key.
  22686. function locales__getSetGlobalLocale (key, values) {
  22687. var data;
  22688. if (key) {
  22689. if (typeof values === 'undefined') {
  22690. data = locales__getLocale(key);
  22691. }
  22692. else {
  22693. data = defineLocale(key, values);
  22694. }
  22695. if (data) {
  22696. // moment.duration._locale = moment._locale = data;
  22697. globalLocale = data;
  22698. }
  22699. }
  22700. return globalLocale._abbr;
  22701. }
  22702. function defineLocale (name, values) {
  22703. if (values !== null) {
  22704. values.abbr = name;
  22705. if (!locales[name]) {
  22706. locales[name] = new Locale();
  22707. }
  22708. locales[name].set(values);
  22709. // backwards compat for now: also set the locale
  22710. locales__getSetGlobalLocale(name);
  22711. return locales[name];
  22712. } else {
  22713. // useful for testing
  22714. delete locales[name];
  22715. return null;
  22716. }
  22717. }
  22718. // returns locale data
  22719. function locales__getLocale (key) {
  22720. var locale;
  22721. if (key && key._locale && key._locale._abbr) {
  22722. key = key._locale._abbr;
  22723. }
  22724. if (!key) {
  22725. return globalLocale;
  22726. }
  22727. if (!isArray(key)) {
  22728. //short-circuit everything else
  22729. locale = loadLocale(key);
  22730. if (locale) {
  22731. return locale;
  22732. }
  22733. key = [key];
  22734. }
  22735. return chooseLocale(key);
  22736. }
  22737. var aliases = {};
  22738. function addUnitAlias (unit, shorthand) {
  22739. var lowerCase = unit.toLowerCase();
  22740. aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
  22741. }
  22742. function normalizeUnits(units) {
  22743. return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
  22744. }
  22745. function normalizeObjectUnits(inputObject) {
  22746. var normalizedInput = {},
  22747. normalizedProp,
  22748. prop;
  22749. for (prop in inputObject) {
  22750. if (hasOwnProp(inputObject, prop)) {
  22751. normalizedProp = normalizeUnits(prop);
  22752. if (normalizedProp) {
  22753. normalizedInput[normalizedProp] = inputObject[prop];
  22754. }
  22755. }
  22756. }
  22757. return normalizedInput;
  22758. }
  22759. function makeGetSet (unit, keepTime) {
  22760. return function (value) {
  22761. if (value != null) {
  22762. get_set__set(this, unit, value);
  22763. hooks__hooks.updateOffset(this, keepTime);
  22764. return this;
  22765. } else {
  22766. return get_set__get(this, unit);
  22767. }
  22768. };
  22769. }
  22770. function get_set__get (mom, unit) {
  22771. return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]();
  22772. }
  22773. function get_set__set (mom, unit, value) {
  22774. return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
  22775. }
  22776. // MOMENTS
  22777. function getSet (units, value) {
  22778. var unit;
  22779. if (typeof units === 'object') {
  22780. for (unit in units) {
  22781. this.set(unit, units[unit]);
  22782. }
  22783. } else {
  22784. units = normalizeUnits(units);
  22785. if (typeof this[units] === 'function') {
  22786. return this[units](value);
  22787. }
  22788. }
  22789. return this;
  22790. }
  22791. function zeroFill(number, targetLength, forceSign) {
  22792. var output = '' + Math.abs(number),
  22793. sign = number >= 0;
  22794. while (output.length < targetLength) {
  22795. output = '0' + output;
  22796. }
  22797. return (sign ? (forceSign ? '+' : '') : '-') + output;
  22798. }
  22799. var 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|X|zz?|ZZ?|.)/g;
  22800. var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
  22801. var formatFunctions = {};
  22802. var formatTokenFunctions = {};
  22803. // token: 'M'
  22804. // padded: ['MM', 2]
  22805. // ordinal: 'Mo'
  22806. // callback: function () { this.month() + 1 }
  22807. function addFormatToken (token, padded, ordinal, callback) {
  22808. var func = callback;
  22809. if (typeof callback === 'string') {
  22810. func = function () {
  22811. return this[callback]();
  22812. };
  22813. }
  22814. if (token) {
  22815. formatTokenFunctions[token] = func;
  22816. }
  22817. if (padded) {
  22818. formatTokenFunctions[padded[0]] = function () {
  22819. return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
  22820. };
  22821. }
  22822. if (ordinal) {
  22823. formatTokenFunctions[ordinal] = function () {
  22824. return this.localeData().ordinal(func.apply(this, arguments), token);
  22825. };
  22826. }
  22827. }
  22828. function removeFormattingTokens(input) {
  22829. if (input.match(/\[[\s\S]/)) {
  22830. return input.replace(/^\[|\]$/g, '');
  22831. }
  22832. return input.replace(/\\/g, '');
  22833. }
  22834. function makeFormatFunction(format) {
  22835. var array = format.match(formattingTokens), i, length;
  22836. for (i = 0, length = array.length; i < length; i++) {
  22837. if (formatTokenFunctions[array[i]]) {
  22838. array[i] = formatTokenFunctions[array[i]];
  22839. } else {
  22840. array[i] = removeFormattingTokens(array[i]);
  22841. }
  22842. }
  22843. return function (mom) {
  22844. var output = '';
  22845. for (i = 0; i < length; i++) {
  22846. output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
  22847. }
  22848. return output;
  22849. };
  22850. }
  22851. // format date using native date object
  22852. function formatMoment(m, format) {
  22853. if (!m.isValid()) {
  22854. return m.localeData().invalidDate();
  22855. }
  22856. format = expandFormat(format, m.localeData());
  22857. if (!formatFunctions[format]) {
  22858. formatFunctions[format] = makeFormatFunction(format);
  22859. }
  22860. return formatFunctions[format](m);
  22861. }
  22862. function expandFormat(format, locale) {
  22863. var i = 5;
  22864. function replaceLongDateFormatTokens(input) {
  22865. return locale.longDateFormat(input) || input;
  22866. }
  22867. localFormattingTokens.lastIndex = 0;
  22868. while (i >= 0 && localFormattingTokens.test(format)) {
  22869. format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
  22870. localFormattingTokens.lastIndex = 0;
  22871. i -= 1;
  22872. }
  22873. return format;
  22874. }
  22875. var match1 = /\d/; // 0 - 9
  22876. var match2 = /\d\d/; // 00 - 99
  22877. var match3 = /\d{3}/; // 000 - 999
  22878. var match4 = /\d{4}/; // 0000 - 9999
  22879. var match6 = /[+-]?\d{6}/; // -999999 - 999999
  22880. var match1to2 = /\d\d?/; // 0 - 99
  22881. var match1to3 = /\d{1,3}/; // 0 - 999
  22882. var match1to4 = /\d{1,4}/; // 0 - 9999
  22883. var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999
  22884. var matchUnsigned = /\d+/; // 0 - inf
  22885. var matchSigned = /[+-]?\d+/; // -inf - inf
  22886. var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
  22887. var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123
  22888. // any word (or two) characters or numbers including two/three word month in arabic.
  22889. var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
  22890. var regexes = {};
  22891. function addRegexToken (token, regex, strictRegex) {
  22892. regexes[token] = typeof regex === 'function' ? regex : function (isStrict) {
  22893. return (isStrict && strictRegex) ? strictRegex : regex;
  22894. };
  22895. }
  22896. function getParseRegexForToken (token, config) {
  22897. if (!hasOwnProp(regexes, token)) {
  22898. return new RegExp(unescapeFormat(token));
  22899. }
  22900. return regexes[token](config._strict, config._locale);
  22901. }
  22902. // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
  22903. function unescapeFormat(s) {
  22904. return s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
  22905. return p1 || p2 || p3 || p4;
  22906. }).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
  22907. }
  22908. var tokens = {};
  22909. function addParseToken (token, callback) {
  22910. var i, func = callback;
  22911. if (typeof token === 'string') {
  22912. token = [token];
  22913. }
  22914. if (typeof callback === 'number') {
  22915. func = function (input, array) {
  22916. array[callback] = toInt(input);
  22917. };
  22918. }
  22919. for (i = 0; i < token.length; i++) {
  22920. tokens[token[i]] = func;
  22921. }
  22922. }
  22923. function addWeekParseToken (token, callback) {
  22924. addParseToken(token, function (input, array, config, token) {
  22925. config._w = config._w || {};
  22926. callback(input, config._w, config, token);
  22927. });
  22928. }
  22929. function addTimeToArrayFromToken(token, input, config) {
  22930. if (input != null && hasOwnProp(tokens, token)) {
  22931. tokens[token](input, config._a, config, token);
  22932. }
  22933. }
  22934. var YEAR = 0;
  22935. var MONTH = 1;
  22936. var DATE = 2;
  22937. var HOUR = 3;
  22938. var MINUTE = 4;
  22939. var SECOND = 5;
  22940. var MILLISECOND = 6;
  22941. function daysInMonth(year, month) {
  22942. return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
  22943. }
  22944. // FORMATTING
  22945. addFormatToken('M', ['MM', 2], 'Mo', function () {
  22946. return this.month() + 1;
  22947. });
  22948. addFormatToken('MMM', 0, 0, function (format) {
  22949. return this.localeData().monthsShort(this, format);
  22950. });
  22951. addFormatToken('MMMM', 0, 0, function (format) {
  22952. return this.localeData().months(this, format);
  22953. });
  22954. // ALIASES
  22955. addUnitAlias('month', 'M');
  22956. // PARSING
  22957. addRegexToken('M', match1to2);
  22958. addRegexToken('MM', match1to2, match2);
  22959. addRegexToken('MMM', matchWord);
  22960. addRegexToken('MMMM', matchWord);
  22961. addParseToken(['M', 'MM'], function (input, array) {
  22962. array[MONTH] = toInt(input) - 1;
  22963. });
  22964. addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
  22965. var month = config._locale.monthsParse(input, token, config._strict);
  22966. // if we didn't find a month name, mark the date as invalid.
  22967. if (month != null) {
  22968. array[MONTH] = month;
  22969. } else {
  22970. config._pf.invalidMonth = input;
  22971. }
  22972. });
  22973. // LOCALES
  22974. var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
  22975. function localeMonths (m) {
  22976. return this._months[m.month()];
  22977. }
  22978. var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
  22979. function localeMonthsShort (m) {
  22980. return this._monthsShort[m.month()];
  22981. }
  22982. function localeMonthsParse (monthName, format, strict) {
  22983. var i, mom, regex;
  22984. if (!this._monthsParse) {
  22985. this._monthsParse = [];
  22986. this._longMonthsParse = [];
  22987. this._shortMonthsParse = [];
  22988. }
  22989. for (i = 0; i < 12; i++) {
  22990. // make the regex if we don't have it already
  22991. mom = utc__createUTC([2000, i]);
  22992. if (strict && !this._longMonthsParse[i]) {
  22993. this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
  22994. this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
  22995. }
  22996. if (!strict && !this._monthsParse[i]) {
  22997. regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
  22998. this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
  22999. }
  23000. // test the regex
  23001. if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
  23002. return i;
  23003. } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
  23004. return i;
  23005. } else if (!strict && this._monthsParse[i].test(monthName)) {
  23006. return i;
  23007. }
  23008. }
  23009. }
  23010. // MOMENTS
  23011. function setMonth (mom, value) {
  23012. var dayOfMonth;
  23013. // TODO: Move this out of here!
  23014. if (typeof value === 'string') {
  23015. value = mom.localeData().monthsParse(value);
  23016. // TODO: Another silent failure?
  23017. if (typeof value !== 'number') {
  23018. return mom;
  23019. }
  23020. }
  23021. dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
  23022. mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
  23023. return mom;
  23024. }
  23025. function getSetMonth (value) {
  23026. if (value != null) {
  23027. setMonth(this, value);
  23028. hooks__hooks.updateOffset(this, true);
  23029. return this;
  23030. } else {
  23031. return get_set__get(this, 'Month');
  23032. }
  23033. }
  23034. function getDaysInMonth () {
  23035. return daysInMonth(this.year(), this.month());
  23036. }
  23037. function checkOverflow (m) {
  23038. var overflow;
  23039. var a = m._a;
  23040. if (a && m._pf.overflow === -2) {
  23041. overflow =
  23042. a[MONTH] < 0 || a[MONTH] > 11 ? MONTH :
  23043. a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
  23044. a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
  23045. a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE :
  23046. a[SECOND] < 0 || a[SECOND] > 59 ? SECOND :
  23047. a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
  23048. -1;
  23049. if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
  23050. overflow = DATE;
  23051. }
  23052. m._pf.overflow = overflow;
  23053. }
  23054. return m;
  23055. }
  23056. function warn(msg) {
  23057. if (hooks__hooks.suppressDeprecationWarnings === false && typeof console !== 'undefined' && console.warn) {
  23058. console.warn('Deprecation warning: ' + msg);
  23059. }
  23060. }
  23061. function deprecate(msg, fn) {
  23062. var firstTime = true;
  23063. return extend(function () {
  23064. if (firstTime) {
  23065. warn(msg);
  23066. firstTime = false;
  23067. }
  23068. return fn.apply(this, arguments);
  23069. }, fn);
  23070. }
  23071. var deprecations = {};
  23072. function deprecateSimple(name, msg) {
  23073. if (!deprecations[name]) {
  23074. warn(msg);
  23075. deprecations[name] = true;
  23076. }
  23077. }
  23078. hooks__hooks.suppressDeprecationWarnings = false;
  23079. var from_string__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)?)?$/;
  23080. var isoDates = [
  23081. ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/],
  23082. ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/],
  23083. ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/],
  23084. ['GGGG-[W]WW', /\d{4}-W\d{2}/],
  23085. ['YYYY-DDD', /\d{4}-\d{3}/]
  23086. ];
  23087. // iso time formats and regexes
  23088. var isoTimes = [
  23089. ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/],
  23090. ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
  23091. ['HH:mm', /(T| )\d\d:\d\d/],
  23092. ['HH', /(T| )\d\d/]
  23093. ];
  23094. var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
  23095. // date from iso format
  23096. function configFromISO(config) {
  23097. var i, l,
  23098. string = config._i,
  23099. match = from_string__isoRegex.exec(string);
  23100. if (match) {
  23101. config._pf.iso = true;
  23102. for (i = 0, l = isoDates.length; i < l; i++) {
  23103. if (isoDates[i][1].exec(string)) {
  23104. // match[5] should be 'T' or undefined
  23105. config._f = isoDates[i][0] + (match[6] || ' ');
  23106. break;
  23107. }
  23108. }
  23109. for (i = 0, l = isoTimes.length; i < l; i++) {
  23110. if (isoTimes[i][1].exec(string)) {
  23111. config._f += isoTimes[i][0];
  23112. break;
  23113. }
  23114. }
  23115. if (string.match(matchOffset)) {
  23116. config._f += 'Z';
  23117. }
  23118. configFromStringAndFormat(config);
  23119. } else {
  23120. config._isValid = false;
  23121. }
  23122. }
  23123. // date from iso format or fallback
  23124. function configFromString(config) {
  23125. var matched = aspNetJsonRegex.exec(config._i);
  23126. if (matched !== null) {
  23127. config._d = new Date(+matched[1]);
  23128. return;
  23129. }
  23130. configFromISO(config);
  23131. if (config._isValid === false) {
  23132. delete config._isValid;
  23133. hooks__hooks.createFromInputFallback(config);
  23134. }
  23135. }
  23136. hooks__hooks.createFromInputFallback = deprecate(
  23137. 'moment construction falls back to js Date. This is ' +
  23138. 'discouraged and will be removed in upcoming major ' +
  23139. 'release. Please refer to ' +
  23140. 'https://github.com/moment/moment/issues/1407 for more info.',
  23141. function (config) {
  23142. config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
  23143. }
  23144. );
  23145. function createDate (y, m, d, h, M, s, ms) {
  23146. //can't just apply() to create a date:
  23147. //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
  23148. var date = new Date(y, m, d, h, M, s, ms);
  23149. //the date constructor doesn't accept years < 1970
  23150. if (y < 1970) {
  23151. date.setFullYear(y);
  23152. }
  23153. return date;
  23154. }
  23155. function createUTCDate (y) {
  23156. var date = new Date(Date.UTC.apply(null, arguments));
  23157. if (y < 1970) {
  23158. date.setUTCFullYear(y);
  23159. }
  23160. return date;
  23161. }
  23162. addFormatToken(0, ['YY', 2], 0, function () {
  23163. return this.year() % 100;
  23164. });
  23165. addFormatToken(0, ['YYYY', 4], 0, 'year');
  23166. addFormatToken(0, ['YYYYY', 5], 0, 'year');
  23167. addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
  23168. // ALIASES
  23169. addUnitAlias('year', 'y');
  23170. // PARSING
  23171. addRegexToken('Y', matchSigned);
  23172. addRegexToken('YY', match1to2, match2);
  23173. addRegexToken('YYYY', match1to4, match4);
  23174. addRegexToken('YYYYY', match1to6, match6);
  23175. addRegexToken('YYYYYY', match1to6, match6);
  23176. addParseToken(['YYYY', 'YYYYY', 'YYYYYY'], YEAR);
  23177. addParseToken('YY', function (input, array) {
  23178. array[YEAR] = hooks__hooks.parseTwoDigitYear(input);
  23179. });
  23180. // HELPERS
  23181. function daysInYear(year) {
  23182. return isLeapYear(year) ? 366 : 365;
  23183. }
  23184. function isLeapYear(year) {
  23185. return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
  23186. }
  23187. // HOOKS
  23188. hooks__hooks.parseTwoDigitYear = function (input) {
  23189. return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
  23190. };
  23191. // MOMENTS
  23192. var getSetYear = makeGetSet('FullYear', false);
  23193. function getIsLeapYear () {
  23194. return isLeapYear(this.year());
  23195. }
  23196. addFormatToken('w', ['ww', 2], 'wo', 'week');
  23197. addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
  23198. // ALIASES
  23199. addUnitAlias('week', 'w');
  23200. addUnitAlias('isoWeek', 'W');
  23201. // PARSING
  23202. addRegexToken('w', match1to2);
  23203. addRegexToken('ww', match1to2, match2);
  23204. addRegexToken('W', match1to2);
  23205. addRegexToken('WW', match1to2, match2);
  23206. addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
  23207. week[token.substr(0, 1)] = toInt(input);
  23208. });
  23209. // HELPERS
  23210. // firstDayOfWeek 0 = sun, 6 = sat
  23211. // the day of the week that starts the week
  23212. // (usually sunday or monday)
  23213. // firstDayOfWeekOfYear 0 = sun, 6 = sat
  23214. // the first week is the week that contains the first
  23215. // of this day of the week
  23216. // (eg. ISO weeks use thursday (4))
  23217. function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
  23218. var end = firstDayOfWeekOfYear - firstDayOfWeek,
  23219. daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(),
  23220. adjustedMoment;
  23221. if (daysToDayOfWeek > end) {
  23222. daysToDayOfWeek -= 7;
  23223. }
  23224. if (daysToDayOfWeek < end - 7) {
  23225. daysToDayOfWeek += 7;
  23226. }
  23227. adjustedMoment = local__createLocal(mom).add(daysToDayOfWeek, 'd');
  23228. return {
  23229. week: Math.ceil(adjustedMoment.dayOfYear() / 7),
  23230. year: adjustedMoment.year()
  23231. };
  23232. }
  23233. // LOCALES
  23234. function localeWeek (mom) {
  23235. return weekOfYear(mom, this._week.dow, this._week.doy).week;
  23236. }
  23237. var defaultLocaleWeek = {
  23238. dow : 0, // Sunday is the first day of the week.
  23239. doy : 6 // The week that contains Jan 1st is the first week of the year.
  23240. };
  23241. function localeFirstDayOfWeek () {
  23242. return this._week.dow;
  23243. }
  23244. function localeFirstDayOfYear () {
  23245. return this._week.doy;
  23246. }
  23247. // MOMENTS
  23248. function getSetWeek (input) {
  23249. var week = this.localeData().week(this);
  23250. return input == null ? week : this.add((input - week) * 7, 'd');
  23251. }
  23252. function getSetISOWeek (input) {
  23253. var week = weekOfYear(this, 1, 4).week;
  23254. return input == null ? week : this.add((input - week) * 7, 'd');
  23255. }
  23256. addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
  23257. // ALIASES
  23258. addUnitAlias('dayOfYear', 'DDD');
  23259. // PARSING
  23260. addRegexToken('DDD', match1to3);
  23261. addRegexToken('DDDD', match3);
  23262. addParseToken(['DDD', 'DDDD'], function (input, array, config) {
  23263. config._dayOfYear = toInt(input);
  23264. });
  23265. // HELPERS
  23266. //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
  23267. function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) {
  23268. var d = createUTCDate(year, 0, 1).getUTCDay();
  23269. var daysToAdd;
  23270. var dayOfYear;
  23271. d = d === 0 ? 7 : d;
  23272. weekday = weekday != null ? weekday : firstDayOfWeek;
  23273. daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0);
  23274. dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1;
  23275. return {
  23276. year : dayOfYear > 0 ? year : year - 1,
  23277. dayOfYear : dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear
  23278. };
  23279. }
  23280. // MOMENTS
  23281. function getSetDayOfYear (input) {
  23282. var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
  23283. return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
  23284. }
  23285. // Pick the first defined of two or three arguments.
  23286. function defaults(a, b, c) {
  23287. if (a != null) {
  23288. return a;
  23289. }
  23290. if (b != null) {
  23291. return b;
  23292. }
  23293. return c;
  23294. }
  23295. function currentDateArray(config) {
  23296. var now = new Date();
  23297. if (config._useUTC) {
  23298. return [now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()];
  23299. }
  23300. return [now.getFullYear(), now.getMonth(), now.getDate()];
  23301. }
  23302. // convert an array to a date.
  23303. // the array should mirror the parameters below
  23304. // note: all values past the year are optional and will default to the lowest possible value.
  23305. // [year, month, day , hour, minute, second, millisecond]
  23306. function configFromArray (config) {
  23307. var i, date, input = [], currentDate, yearToUse;
  23308. if (config._d) {
  23309. return;
  23310. }
  23311. currentDate = currentDateArray(config);
  23312. //compute day of the year from weeks and weekdays
  23313. if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
  23314. dayOfYearFromWeekInfo(config);
  23315. }
  23316. //if the day of the year is set, figure out what it is
  23317. if (config._dayOfYear) {
  23318. yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
  23319. if (config._dayOfYear > daysInYear(yearToUse)) {
  23320. config._pf._overflowDayOfYear = true;
  23321. }
  23322. date = createUTCDate(yearToUse, 0, config._dayOfYear);
  23323. config._a[MONTH] = date.getUTCMonth();
  23324. config._a[DATE] = date.getUTCDate();
  23325. }
  23326. // Default to current date.
  23327. // * if no year, month, day of month are given, default to today
  23328. // * if day of month is given, default month and year
  23329. // * if month is given, default only year
  23330. // * if year is given, don't default anything
  23331. for (i = 0; i < 3 && config._a[i] == null; ++i) {
  23332. config._a[i] = input[i] = currentDate[i];
  23333. }
  23334. // Zero out whatever was not defaulted, including time
  23335. for (; i < 7; i++) {
  23336. config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
  23337. }
  23338. // Check for 24:00:00.000
  23339. if (config._a[HOUR] === 24 &&
  23340. config._a[MINUTE] === 0 &&
  23341. config._a[SECOND] === 0 &&
  23342. config._a[MILLISECOND] === 0) {
  23343. config._nextDay = true;
  23344. config._a[HOUR] = 0;
  23345. }
  23346. config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
  23347. // Apply timezone offset from input. The actual utcOffset can be changed
  23348. // with parseZone.
  23349. if (config._tzm != null) {
  23350. config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
  23351. }
  23352. if (config._nextDay) {
  23353. config._a[HOUR] = 24;
  23354. }
  23355. }
  23356. function dayOfYearFromWeekInfo(config) {
  23357. var w, weekYear, week, weekday, dow, doy, temp;
  23358. w = config._w;
  23359. if (w.GG != null || w.W != null || w.E != null) {
  23360. dow = 1;
  23361. doy = 4;
  23362. // TODO: We need to take the current isoWeekYear, but that depends on
  23363. // how we interpret now (local, utc, fixed offset). So create
  23364. // a now version of current config (take local/utc/offset flags, and
  23365. // create now).
  23366. weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(local__createLocal(), 1, 4).year);
  23367. week = defaults(w.W, 1);
  23368. weekday = defaults(w.E, 1);
  23369. } else {
  23370. dow = config._locale._week.dow;
  23371. doy = config._locale._week.doy;
  23372. weekYear = defaults(w.gg, config._a[YEAR], weekOfYear(local__createLocal(), dow, doy).year);
  23373. week = defaults(w.w, 1);
  23374. if (w.d != null) {
  23375. // weekday -- low day numbers are considered next week
  23376. weekday = w.d;
  23377. if (weekday < dow) {
  23378. ++week;
  23379. }
  23380. } else if (w.e != null) {
  23381. // local weekday -- counting starts from begining of week
  23382. weekday = w.e + dow;
  23383. } else {
  23384. // default to begining of week
  23385. weekday = dow;
  23386. }
  23387. }
  23388. temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow);
  23389. config._a[YEAR] = temp.year;
  23390. config._dayOfYear = temp.dayOfYear;
  23391. }
  23392. hooks__hooks.ISO_8601 = function () {};
  23393. // date from string and format string
  23394. function configFromStringAndFormat(config) {
  23395. // TODO: Move this to another part of the creation flow to prevent circular deps
  23396. if (config._f === hooks__hooks.ISO_8601) {
  23397. configFromISO(config);
  23398. return;
  23399. }
  23400. config._a = [];
  23401. config._pf.empty = true;
  23402. // This array is used to make a Date, either with `new Date` or `Date.UTC`
  23403. var string = '' + config._i,
  23404. i, parsedInput, tokens, token, skipped,
  23405. stringLength = string.length,
  23406. totalParsedInputLength = 0;
  23407. tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
  23408. for (i = 0; i < tokens.length; i++) {
  23409. token = tokens[i];
  23410. parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
  23411. if (parsedInput) {
  23412. skipped = string.substr(0, string.indexOf(parsedInput));
  23413. if (skipped.length > 0) {
  23414. config._pf.unusedInput.push(skipped);
  23415. }
  23416. string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
  23417. totalParsedInputLength += parsedInput.length;
  23418. }
  23419. // don't parse if it's not a known token
  23420. if (formatTokenFunctions[token]) {
  23421. if (parsedInput) {
  23422. config._pf.empty = false;
  23423. }
  23424. else {
  23425. config._pf.unusedTokens.push(token);
  23426. }
  23427. addTimeToArrayFromToken(token, parsedInput, config);
  23428. }
  23429. else if (config._strict && !parsedInput) {
  23430. config._pf.unusedTokens.push(token);
  23431. }
  23432. }
  23433. // add remaining unparsed input length to the string
  23434. config._pf.charsLeftOver = stringLength - totalParsedInputLength;
  23435. if (string.length > 0) {
  23436. config._pf.unusedInput.push(string);
  23437. }
  23438. // clear _12h flag if hour is <= 12
  23439. if (config._pf.bigHour === true && config._a[HOUR] <= 12) {
  23440. config._pf.bigHour = undefined;
  23441. }
  23442. // handle meridiem
  23443. config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
  23444. configFromArray(config);
  23445. checkOverflow(config);
  23446. }
  23447. function meridiemFixWrap (locale, hour, meridiem) {
  23448. var isPm;
  23449. if (meridiem == null) {
  23450. // nothing to do
  23451. return hour;
  23452. }
  23453. if (locale.meridiemHour != null) {
  23454. return locale.meridiemHour(hour, meridiem);
  23455. } else if (locale.isPM != null) {
  23456. // Fallback
  23457. isPm = locale.isPM(meridiem);
  23458. if (isPm && hour < 12) {
  23459. hour += 12;
  23460. }
  23461. if (!isPm && hour === 12) {
  23462. hour = 0;
  23463. }
  23464. return hour;
  23465. } else {
  23466. // this is not supposed to happen
  23467. return hour;
  23468. }
  23469. }
  23470. function configFromStringAndArray(config) {
  23471. var tempConfig,
  23472. bestMoment,
  23473. scoreToBeat,
  23474. i,
  23475. currentScore;
  23476. if (config._f.length === 0) {
  23477. config._pf.invalidFormat = true;
  23478. config._d = new Date(NaN);
  23479. return;
  23480. }
  23481. for (i = 0; i < config._f.length; i++) {
  23482. currentScore = 0;
  23483. tempConfig = copyConfig({}, config);
  23484. if (config._useUTC != null) {
  23485. tempConfig._useUTC = config._useUTC;
  23486. }
  23487. tempConfig._pf = defaultParsingFlags();
  23488. tempConfig._f = config._f[i];
  23489. configFromStringAndFormat(tempConfig);
  23490. if (!valid__isValid(tempConfig)) {
  23491. continue;
  23492. }
  23493. // if there is any input that was not parsed add a penalty for that format
  23494. currentScore += tempConfig._pf.charsLeftOver;
  23495. //or tokens
  23496. currentScore += tempConfig._pf.unusedTokens.length * 10;
  23497. tempConfig._pf.score = currentScore;
  23498. if (scoreToBeat == null || currentScore < scoreToBeat) {
  23499. scoreToBeat = currentScore;
  23500. bestMoment = tempConfig;
  23501. }
  23502. }
  23503. extend(config, bestMoment || tempConfig);
  23504. }
  23505. function configFromObject(config) {
  23506. if (config._d) {
  23507. return;
  23508. }
  23509. var i = normalizeObjectUnits(config._i);
  23510. config._a = [i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond];
  23511. configFromArray(config);
  23512. }
  23513. function createFromConfig (config) {
  23514. var input = config._i,
  23515. format = config._f,
  23516. res;
  23517. config._locale = config._locale || locales__getLocale(config._l);
  23518. if (input === null || (format === undefined && input === '')) {
  23519. return valid__createInvalid({nullInput: true});
  23520. }
  23521. if (typeof input === 'string') {
  23522. config._i = input = config._locale.preparse(input);
  23523. }
  23524. if (isMoment(input)) {
  23525. return new Moment(checkOverflow(input));
  23526. } else if (isArray(format)) {
  23527. configFromStringAndArray(config);
  23528. } else if (format) {
  23529. configFromStringAndFormat(config);
  23530. } else {
  23531. configFromInput(config);
  23532. }
  23533. res = new Moment(checkOverflow(config));
  23534. if (res._nextDay) {
  23535. // Adding is smart enough around DST
  23536. res.add(1, 'd');
  23537. res._nextDay = undefined;
  23538. }
  23539. return res;
  23540. }
  23541. function configFromInput(config) {
  23542. var input = config._i;
  23543. if (input === undefined) {
  23544. config._d = new Date();
  23545. } else if (isDate(input)) {
  23546. config._d = new Date(+input);
  23547. } else if (typeof input === 'string') {
  23548. configFromString(config);
  23549. } else if (isArray(input)) {
  23550. config._a = map(input.slice(0), function (obj) {
  23551. return parseInt(obj, 10);
  23552. });
  23553. configFromArray(config);
  23554. } else if (typeof(input) === 'object') {
  23555. configFromObject(config);
  23556. } else if (typeof(input) === 'number') {
  23557. // from milliseconds
  23558. config._d = new Date(input);
  23559. } else {
  23560. hooks__hooks.createFromInputFallback(config);
  23561. }
  23562. }
  23563. function createLocalOrUTC (input, format, locale, strict, isUTC) {
  23564. var c = {};
  23565. if (typeof(locale) === 'boolean') {
  23566. strict = locale;
  23567. locale = undefined;
  23568. }
  23569. // object construction must be done this way.
  23570. // https://github.com/moment/moment/issues/1423
  23571. c._isAMomentObject = true;
  23572. c._useUTC = c._isUTC = isUTC;
  23573. c._l = locale;
  23574. c._i = input;
  23575. c._f = format;
  23576. c._strict = strict;
  23577. c._pf = defaultParsingFlags();
  23578. return createFromConfig(c);
  23579. }
  23580. function local__createLocal (input, format, locale, strict) {
  23581. return createLocalOrUTC(input, format, locale, strict, false);
  23582. }
  23583. var prototypeMin = deprecate(
  23584. 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548',
  23585. function () {
  23586. var other = local__createLocal.apply(null, arguments);
  23587. return other < this ? this : other;
  23588. }
  23589. );
  23590. var prototypeMax = deprecate(
  23591. 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548',
  23592. function () {
  23593. var other = local__createLocal.apply(null, arguments);
  23594. return other > this ? this : other;
  23595. }
  23596. );
  23597. // Pick a moment m from moments so that m[fn](other) is true for all
  23598. // other. This relies on the function fn to be transitive.
  23599. //
  23600. // moments should either be an array of moment objects or an array, whose
  23601. // first element is an array of moment objects.
  23602. function pickBy(fn, moments) {
  23603. var res, i;
  23604. if (moments.length === 1 && isArray(moments[0])) {
  23605. moments = moments[0];
  23606. }
  23607. if (!moments.length) {
  23608. return local__createLocal();
  23609. }
  23610. res = moments[0];
  23611. for (i = 1; i < moments.length; ++i) {
  23612. if (moments[i][fn](res)) {
  23613. res = moments[i];
  23614. }
  23615. }
  23616. return res;
  23617. }
  23618. // TODO: Use [].sort instead?
  23619. function min () {
  23620. var args = [].slice.call(arguments, 0);
  23621. return pickBy('isBefore', args);
  23622. }
  23623. function max () {
  23624. var args = [].slice.call(arguments, 0);
  23625. return pickBy('isAfter', args);
  23626. }
  23627. function Duration (duration) {
  23628. var normalizedInput = normalizeObjectUnits(duration),
  23629. years = normalizedInput.year || 0,
  23630. quarters = normalizedInput.quarter || 0,
  23631. months = normalizedInput.month || 0,
  23632. weeks = normalizedInput.week || 0,
  23633. days = normalizedInput.day || 0,
  23634. hours = normalizedInput.hour || 0,
  23635. minutes = normalizedInput.minute || 0,
  23636. seconds = normalizedInput.second || 0,
  23637. milliseconds = normalizedInput.millisecond || 0;
  23638. // representation for dateAddRemove
  23639. this._milliseconds = +milliseconds +
  23640. seconds * 1e3 + // 1000
  23641. minutes * 6e4 + // 1000 * 60
  23642. hours * 36e5; // 1000 * 60 * 60
  23643. // Because of dateAddRemove treats 24 hours as different from a
  23644. // day when working around DST, we need to store them separately
  23645. this._days = +days +
  23646. weeks * 7;
  23647. // It is impossible translate months into days without knowing
  23648. // which months you are are talking about, so we have to store
  23649. // it separately.
  23650. this._months = +months +
  23651. quarters * 3 +
  23652. years * 12;
  23653. this._data = {};
  23654. this._locale = locales__getLocale();
  23655. this._bubble();
  23656. }
  23657. function isDuration (obj) {
  23658. return obj instanceof Duration;
  23659. }
  23660. function offset (token, separator) {
  23661. addFormatToken(token, 0, 0, function () {
  23662. var offset = this.utcOffset();
  23663. var sign = '+';
  23664. if (offset < 0) {
  23665. offset = -offset;
  23666. sign = '-';
  23667. }
  23668. return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
  23669. });
  23670. }
  23671. offset('Z', ':');
  23672. offset('ZZ', '');
  23673. // PARSING
  23674. addRegexToken('Z', matchOffset);
  23675. addRegexToken('ZZ', matchOffset);
  23676. addParseToken(['Z', 'ZZ'], function (input, array, config) {
  23677. config._useUTC = true;
  23678. config._tzm = offsetFromString(input);
  23679. });
  23680. // HELPERS
  23681. // timezone chunker
  23682. // '+10:00' > ['10', '00']
  23683. // '-1530' > ['-15', '30']
  23684. var chunkOffset = /([\+\-]|\d\d)/gi;
  23685. function offsetFromString(string) {
  23686. var matches = ((string || '').match(matchOffset) || []);
  23687. var chunk = matches[matches.length - 1] || [];
  23688. var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];
  23689. var minutes = +(parts[1] * 60) + toInt(parts[2]);
  23690. return parts[0] === '+' ? minutes : -minutes;
  23691. }
  23692. // Return a moment from input, that is local/utc/zone equivalent to model.
  23693. function cloneWithOffset(input, model) {
  23694. var res, diff;
  23695. if (model._isUTC) {
  23696. res = model.clone();
  23697. diff = (isMoment(input) || isDate(input) ? +input : +local__createLocal(input)) - (+res);
  23698. // Use low-level api, because this fn is low-level api.
  23699. res._d.setTime(+res._d + diff);
  23700. hooks__hooks.updateOffset(res, false);
  23701. return res;
  23702. } else {
  23703. return local__createLocal(input).local();
  23704. }
  23705. return model._isUTC ? local__createLocal(input).zone(model._offset || 0) : local__createLocal(input).local();
  23706. }
  23707. function getDateOffset (m) {
  23708. // On Firefox.24 Date#getTimezoneOffset returns a floating point.
  23709. // https://github.com/moment/moment/pull/1871
  23710. return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
  23711. }
  23712. // HOOKS
  23713. // This function will be called whenever a moment is mutated.
  23714. // It is intended to keep the offset in sync with the timezone.
  23715. hooks__hooks.updateOffset = function () {};
  23716. // MOMENTS
  23717. // keepLocalTime = true means only change the timezone, without
  23718. // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
  23719. // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
  23720. // +0200, so we adjust the time as needed, to be valid.
  23721. //
  23722. // Keeping the time actually adds/subtracts (one hour)
  23723. // from the actual represented time. That is why we call updateOffset
  23724. // a second time. In case it wants us to change the offset again
  23725. // _changeInProgress == true case, then we have to adjust, because
  23726. // there is no such time in the given timezone.
  23727. function getSetOffset (input, keepLocalTime) {
  23728. var offset = this._offset || 0,
  23729. localAdjust;
  23730. if (input != null) {
  23731. if (typeof input === 'string') {
  23732. input = offsetFromString(input);
  23733. }
  23734. if (Math.abs(input) < 16) {
  23735. input = input * 60;
  23736. }
  23737. if (!this._isUTC && keepLocalTime) {
  23738. localAdjust = getDateOffset(this);
  23739. }
  23740. this._offset = input;
  23741. this._isUTC = true;
  23742. if (localAdjust != null) {
  23743. this.add(localAdjust, 'm');
  23744. }
  23745. if (offset !== input) {
  23746. if (!keepLocalTime || this._changeInProgress) {
  23747. add_subtract__addSubtract(this, create__createDuration(input - offset, 'm'), 1, false);
  23748. } else if (!this._changeInProgress) {
  23749. this._changeInProgress = true;
  23750. hooks__hooks.updateOffset(this, true);
  23751. this._changeInProgress = null;
  23752. }
  23753. }
  23754. return this;
  23755. } else {
  23756. return this._isUTC ? offset : getDateOffset(this);
  23757. }
  23758. }
  23759. function getSetZone (input, keepLocalTime) {
  23760. if (input != null) {
  23761. if (typeof input !== 'string') {
  23762. input = -input;
  23763. }
  23764. this.utcOffset(input, keepLocalTime);
  23765. return this;
  23766. } else {
  23767. return -this.utcOffset();
  23768. }
  23769. }
  23770. function setOffsetToUTC (keepLocalTime) {
  23771. return this.utcOffset(0, keepLocalTime);
  23772. }
  23773. function setOffsetToLocal (keepLocalTime) {
  23774. if (this._isUTC) {
  23775. this.utcOffset(0, keepLocalTime);
  23776. this._isUTC = false;
  23777. if (keepLocalTime) {
  23778. this.subtract(getDateOffset(this), 'm');
  23779. }
  23780. }
  23781. return this;
  23782. }
  23783. function setOffsetToParsedOffset () {
  23784. if (this._tzm) {
  23785. this.utcOffset(this._tzm);
  23786. } else if (typeof this._i === 'string') {
  23787. this.utcOffset(offsetFromString(this._i));
  23788. }
  23789. return this;
  23790. }
  23791. function hasAlignedHourOffset (input) {
  23792. if (!input) {
  23793. input = 0;
  23794. }
  23795. else {
  23796. input = local__createLocal(input).utcOffset();
  23797. }
  23798. return (this.utcOffset() - input) % 60 === 0;
  23799. }
  23800. function isDaylightSavingTime () {
  23801. return (
  23802. this.utcOffset() > this.clone().month(0).utcOffset() ||
  23803. this.utcOffset() > this.clone().month(5).utcOffset()
  23804. );
  23805. }
  23806. function isDaylightSavingTimeShifted () {
  23807. if (this._a) {
  23808. var other = this._isUTC ? utc__createUTC(this._a) : local__createLocal(this._a);
  23809. return this.isValid() && compareArrays(this._a, other.toArray()) > 0;
  23810. }
  23811. return false;
  23812. }
  23813. function isLocal () {
  23814. return !this._isUTC;
  23815. }
  23816. function isUtcOffset () {
  23817. return this._isUTC;
  23818. }
  23819. function isUtc () {
  23820. return this._isUTC && this._offset === 0;
  23821. }
  23822. var aspNetRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/;
  23823. // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
  23824. // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
  23825. var create__isoRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;
  23826. function create__createDuration (input, key) {
  23827. var duration = input,
  23828. // matching against regexp is expensive, do it on demand
  23829. match = null,
  23830. sign,
  23831. ret,
  23832. diffRes;
  23833. if (isDuration(input)) {
  23834. duration = {
  23835. ms : input._milliseconds,
  23836. d : input._days,
  23837. M : input._months
  23838. };
  23839. } else if (typeof input === 'number') {
  23840. duration = {};
  23841. if (key) {
  23842. duration[key] = input;
  23843. } else {
  23844. duration.milliseconds = input;
  23845. }
  23846. } else if (!!(match = aspNetRegex.exec(input))) {
  23847. sign = (match[1] === '-') ? -1 : 1;
  23848. duration = {
  23849. y : 0,
  23850. d : toInt(match[DATE]) * sign,
  23851. h : toInt(match[HOUR]) * sign,
  23852. m : toInt(match[MINUTE]) * sign,
  23853. s : toInt(match[SECOND]) * sign,
  23854. ms : toInt(match[MILLISECOND]) * sign
  23855. };
  23856. } else if (!!(match = create__isoRegex.exec(input))) {
  23857. sign = (match[1] === '-') ? -1 : 1;
  23858. duration = {
  23859. y : parseIso(match[2], sign),
  23860. M : parseIso(match[3], sign),
  23861. d : parseIso(match[4], sign),
  23862. h : parseIso(match[5], sign),
  23863. m : parseIso(match[6], sign),
  23864. s : parseIso(match[7], sign),
  23865. w : parseIso(match[8], sign)
  23866. };
  23867. } else if (duration == null) {// checks for null or undefined
  23868. duration = {};
  23869. } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
  23870. diffRes = momentsDifference(local__createLocal(duration.from), local__createLocal(duration.to));
  23871. duration = {};
  23872. duration.ms = diffRes.milliseconds;
  23873. duration.M = diffRes.months;
  23874. }
  23875. ret = new Duration(duration);
  23876. if (isDuration(input) && hasOwnProp(input, '_locale')) {
  23877. ret._locale = input._locale;
  23878. }
  23879. return ret;
  23880. }
  23881. function parseIso (inp, sign) {
  23882. // We'd normally use ~~inp for this, but unfortunately it also
  23883. // converts floats to ints.
  23884. // inp may be undefined, so careful calling replace on it.
  23885. var res = inp && parseFloat(inp.replace(',', '.'));
  23886. // apply sign while we're at it
  23887. return (isNaN(res) ? 0 : res) * sign;
  23888. }
  23889. function positiveMomentsDifference(base, other) {
  23890. var res = {milliseconds: 0, months: 0};
  23891. res.months = other.month() - base.month() +
  23892. (other.year() - base.year()) * 12;
  23893. if (base.clone().add(res.months, 'M').isAfter(other)) {
  23894. --res.months;
  23895. }
  23896. res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
  23897. return res;
  23898. }
  23899. function momentsDifference(base, other) {
  23900. var res;
  23901. other = cloneWithOffset(other, base);
  23902. if (base.isBefore(other)) {
  23903. res = positiveMomentsDifference(base, other);
  23904. } else {
  23905. res = positiveMomentsDifference(other, base);
  23906. res.milliseconds = -res.milliseconds;
  23907. res.months = -res.months;
  23908. }
  23909. return res;
  23910. }
  23911. function createAdder(direction, name) {
  23912. return function (val, period) {
  23913. var dur, tmp;
  23914. //invert the arguments, but complain about it
  23915. if (period !== null && !isNaN(+period)) {
  23916. deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).');
  23917. tmp = val; val = period; period = tmp;
  23918. }
  23919. val = typeof val === 'string' ? +val : val;
  23920. dur = create__createDuration(val, period);
  23921. add_subtract__addSubtract(this, dur, direction);
  23922. return this;
  23923. };
  23924. }
  23925. function add_subtract__addSubtract (mom, duration, isAdding, updateOffset) {
  23926. var milliseconds = duration._milliseconds,
  23927. days = duration._days,
  23928. months = duration._months;
  23929. updateOffset = updateOffset == null ? true : updateOffset;
  23930. if (milliseconds) {
  23931. mom._d.setTime(+mom._d + milliseconds * isAdding);
  23932. }
  23933. if (days) {
  23934. get_set__set(mom, 'Date', get_set__get(mom, 'Date') + days * isAdding);
  23935. }
  23936. if (months) {
  23937. setMonth(mom, get_set__get(mom, 'Month') + months * isAdding);
  23938. }
  23939. if (updateOffset) {
  23940. hooks__hooks.updateOffset(mom, days || months);
  23941. }
  23942. }
  23943. var add_subtract__add = createAdder(1, 'add');
  23944. var add_subtract__subtract = createAdder(-1, 'subtract');
  23945. function calendar__calendar (time) {
  23946. // We want to compare the start of today, vs this.
  23947. // Getting start-of-today depends on whether we're local/utc/offset or not.
  23948. var now = time || local__createLocal(),
  23949. sod = cloneWithOffset(now, this).startOf('day'),
  23950. diff = this.diff(sod, 'days', true),
  23951. format = diff < -6 ? 'sameElse' :
  23952. diff < -1 ? 'lastWeek' :
  23953. diff < 0 ? 'lastDay' :
  23954. diff < 1 ? 'sameDay' :
  23955. diff < 2 ? 'nextDay' :
  23956. diff < 7 ? 'nextWeek' : 'sameElse';
  23957. return this.format(this.localeData().calendar(format, this, local__createLocal(now)));
  23958. }
  23959. function clone () {
  23960. return new Moment(this);
  23961. }
  23962. function isAfter (input, units) {
  23963. var inputMs;
  23964. units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond');
  23965. if (units === 'millisecond') {
  23966. input = isMoment(input) ? input : local__createLocal(input);
  23967. return +this > +input;
  23968. } else {
  23969. inputMs = isMoment(input) ? +input : +local__createLocal(input);
  23970. return inputMs < +this.clone().startOf(units);
  23971. }
  23972. }
  23973. function isBefore (input, units) {
  23974. var inputMs;
  23975. units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond');
  23976. if (units === 'millisecond') {
  23977. input = isMoment(input) ? input : local__createLocal(input);
  23978. return +this < +input;
  23979. } else {
  23980. inputMs = isMoment(input) ? +input : +local__createLocal(input);
  23981. return +this.clone().endOf(units) < inputMs;
  23982. }
  23983. }
  23984. function isBetween (from, to, units) {
  23985. return this.isAfter(from, units) && this.isBefore(to, units);
  23986. }
  23987. function isSame (input, units) {
  23988. var inputMs;
  23989. units = normalizeUnits(units || 'millisecond');
  23990. if (units === 'millisecond') {
  23991. input = isMoment(input) ? input : local__createLocal(input);
  23992. return +this === +input;
  23993. } else {
  23994. inputMs = +local__createLocal(input);
  23995. return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units));
  23996. }
  23997. }
  23998. function absFloor (number) {
  23999. if (number < 0) {
  24000. return Math.ceil(number);
  24001. } else {
  24002. return Math.floor(number);
  24003. }
  24004. }
  24005. function diff (input, units, asFloat) {
  24006. var that = cloneWithOffset(input, this),
  24007. zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4,
  24008. delta, output;
  24009. units = normalizeUnits(units);
  24010. if (units === 'year' || units === 'month' || units === 'quarter') {
  24011. output = monthDiff(this, that);
  24012. if (units === 'quarter') {
  24013. output = output / 3;
  24014. } else if (units === 'year') {
  24015. output = output / 12;
  24016. }
  24017. } else {
  24018. delta = this - that;
  24019. output = units === 'second' ? delta / 1e3 : // 1000
  24020. units === 'minute' ? delta / 6e4 : // 1000 * 60
  24021. units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60
  24022. units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
  24023. units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
  24024. delta;
  24025. }
  24026. return asFloat ? output : absFloor(output);
  24027. }
  24028. function monthDiff (a, b) {
  24029. // difference in months
  24030. var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
  24031. // b is in (anchor - 1 month, anchor + 1 month)
  24032. anchor = a.clone().add(wholeMonthDiff, 'months'),
  24033. anchor2, adjust;
  24034. if (b - anchor < 0) {
  24035. anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
  24036. // linear across the month
  24037. adjust = (b - anchor) / (anchor - anchor2);
  24038. } else {
  24039. anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
  24040. // linear across the month
  24041. adjust = (b - anchor) / (anchor2 - anchor);
  24042. }
  24043. return -(wholeMonthDiff + adjust);
  24044. }
  24045. hooks__hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
  24046. function toString () {
  24047. return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
  24048. }
  24049. function moment_format__toISOString () {
  24050. var m = this.clone().utc();
  24051. if (0 < m.year() && m.year() <= 9999) {
  24052. if ('function' === typeof Date.prototype.toISOString) {
  24053. // native implementation is ~50x faster, use it when we can
  24054. return this.toDate().toISOString();
  24055. } else {
  24056. return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  24057. }
  24058. } else {
  24059. return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  24060. }
  24061. }
  24062. function format (inputString) {
  24063. var output = formatMoment(this, inputString || hooks__hooks.defaultFormat);
  24064. return this.localeData().postformat(output);
  24065. }
  24066. function from (time, withoutSuffix) {
  24067. return create__createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
  24068. }
  24069. function fromNow (withoutSuffix) {
  24070. return this.from(local__createLocal(), withoutSuffix);
  24071. }
  24072. function locale (key) {
  24073. var newLocaleData;
  24074. if (key === undefined) {
  24075. return this._locale._abbr;
  24076. } else {
  24077. newLocaleData = locales__getLocale(key);
  24078. if (newLocaleData != null) {
  24079. this._locale = newLocaleData;
  24080. }
  24081. return this;
  24082. }
  24083. }
  24084. var lang = deprecate(
  24085. 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
  24086. function (key) {
  24087. if (key === undefined) {
  24088. return this.localeData();
  24089. } else {
  24090. return this.locale(key);
  24091. }
  24092. }
  24093. );
  24094. function localeData () {
  24095. return this._locale;
  24096. }
  24097. function startOf (units) {
  24098. units = normalizeUnits(units);
  24099. // the following switch intentionally omits break keywords
  24100. // to utilize falling through the cases.
  24101. switch (units) {
  24102. case 'year':
  24103. this.month(0);
  24104. /* falls through */
  24105. case 'quarter':
  24106. case 'month':
  24107. this.date(1);
  24108. /* falls through */
  24109. case 'week':
  24110. case 'isoWeek':
  24111. case 'day':
  24112. this.hours(0);
  24113. /* falls through */
  24114. case 'hour':
  24115. this.minutes(0);
  24116. /* falls through */
  24117. case 'minute':
  24118. this.seconds(0);
  24119. /* falls through */
  24120. case 'second':
  24121. this.milliseconds(0);
  24122. /* falls through */
  24123. }
  24124. // weeks are a special case
  24125. if (units === 'week') {
  24126. this.weekday(0);
  24127. }
  24128. if (units === 'isoWeek') {
  24129. this.isoWeekday(1);
  24130. }
  24131. // quarters are also special
  24132. if (units === 'quarter') {
  24133. this.month(Math.floor(this.month() / 3) * 3);
  24134. }
  24135. return this;
  24136. }
  24137. function endOf (units) {
  24138. units = normalizeUnits(units);
  24139. if (units === undefined || units === 'millisecond') {
  24140. return this;
  24141. }
  24142. return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
  24143. }
  24144. function to_type__valueOf () {
  24145. return +this._d - ((this._offset || 0) * 60000);
  24146. }
  24147. function unix () {
  24148. return Math.floor(+this / 1000);
  24149. }
  24150. function toDate () {
  24151. return this._offset ? new Date(+this) : this._d;
  24152. }
  24153. function toArray () {
  24154. var m = this;
  24155. return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
  24156. }
  24157. function moment_valid__isValid () {
  24158. return valid__isValid(this);
  24159. }
  24160. function parsingFlags () {
  24161. return extend({}, this._pf);
  24162. }
  24163. function invalidAt () {
  24164. return this._pf.overflow;
  24165. }
  24166. addFormatToken(0, ['gg', 2], 0, function () {
  24167. return this.weekYear() % 100;
  24168. });
  24169. addFormatToken(0, ['GG', 2], 0, function () {
  24170. return this.isoWeekYear() % 100;
  24171. });
  24172. function addWeekYearFormatToken (token, getter) {
  24173. addFormatToken(0, [token, token.length], 0, getter);
  24174. }
  24175. addWeekYearFormatToken('gggg', 'weekYear');
  24176. addWeekYearFormatToken('ggggg', 'weekYear');
  24177. addWeekYearFormatToken('GGGG', 'isoWeekYear');
  24178. addWeekYearFormatToken('GGGGG', 'isoWeekYear');
  24179. // ALIASES
  24180. addUnitAlias('weekYear', 'gg');
  24181. addUnitAlias('isoWeekYear', 'GG');
  24182. // PARSING
  24183. addRegexToken('G', matchSigned);
  24184. addRegexToken('g', matchSigned);
  24185. addRegexToken('GG', match1to2, match2);
  24186. addRegexToken('gg', match1to2, match2);
  24187. addRegexToken('GGGG', match1to4, match4);
  24188. addRegexToken('gggg', match1to4, match4);
  24189. addRegexToken('GGGGG', match1to6, match6);
  24190. addRegexToken('ggggg', match1to6, match6);
  24191. addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
  24192. week[token.substr(0, 2)] = toInt(input);
  24193. });
  24194. addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
  24195. week[token] = hooks__hooks.parseTwoDigitYear(input);
  24196. });
  24197. // HELPERS
  24198. function weeksInYear(year, dow, doy) {
  24199. return weekOfYear(local__createLocal([year, 11, 31 + dow - doy]), dow, doy).week;
  24200. }
  24201. // MOMENTS
  24202. function getSetWeekYear (input) {
  24203. var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year;
  24204. return input == null ? year : this.add((input - year), 'y');
  24205. }
  24206. function getSetISOWeekYear (input) {
  24207. var year = weekOfYear(this, 1, 4).year;
  24208. return input == null ? year : this.add((input - year), 'y');
  24209. }
  24210. function getISOWeeksInYear () {
  24211. return weeksInYear(this.year(), 1, 4);
  24212. }
  24213. function getWeeksInYear () {
  24214. var weekInfo = this.localeData()._week;
  24215. return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
  24216. }
  24217. addFormatToken('Q', 0, 0, 'quarter');
  24218. // ALIASES
  24219. addUnitAlias('quarter', 'Q');
  24220. // PARSING
  24221. addRegexToken('Q', match1);
  24222. addParseToken('Q', function (input, array) {
  24223. array[MONTH] = (toInt(input) - 1) * 3;
  24224. });
  24225. // MOMENTS
  24226. function getSetQuarter (input) {
  24227. return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
  24228. }
  24229. addFormatToken('D', ['DD', 2], 'Do', 'date');
  24230. // ALIASES
  24231. addUnitAlias('date', 'D');
  24232. // PARSING
  24233. addRegexToken('D', match1to2);
  24234. addRegexToken('DD', match1to2, match2);
  24235. addRegexToken('Do', function (isStrict, locale) {
  24236. return isStrict ? locale._ordinalParse : locale._ordinalParseLenient;
  24237. });
  24238. addParseToken(['D', 'DD'], DATE);
  24239. addParseToken('Do', function (input, array) {
  24240. array[DATE] = toInt(input.match(match1to2)[0], 10);
  24241. });
  24242. // MOMENTS
  24243. var getSetDayOfMonth = makeGetSet('Date', true);
  24244. addFormatToken('d', 0, 'do', 'day');
  24245. addFormatToken('dd', 0, 0, function (format) {
  24246. return this.localeData().weekdaysMin(this, format);
  24247. });
  24248. addFormatToken('ddd', 0, 0, function (format) {
  24249. return this.localeData().weekdaysShort(this, format);
  24250. });
  24251. addFormatToken('dddd', 0, 0, function (format) {
  24252. return this.localeData().weekdays(this, format);
  24253. });
  24254. addFormatToken('e', 0, 0, 'weekday');
  24255. addFormatToken('E', 0, 0, 'isoWeekday');
  24256. // ALIASES
  24257. addUnitAlias('day', 'd');
  24258. addUnitAlias('weekday', 'e');
  24259. addUnitAlias('isoWeekday', 'E');
  24260. // PARSING
  24261. addRegexToken('d', match1to2);
  24262. addRegexToken('e', match1to2);
  24263. addRegexToken('E', match1to2);
  24264. addRegexToken('dd', matchWord);
  24265. addRegexToken('ddd', matchWord);
  24266. addRegexToken('dddd', matchWord);
  24267. addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config) {
  24268. var weekday = config._locale.weekdaysParse(input);
  24269. // if we didn't get a weekday name, mark the date as invalid
  24270. if (weekday != null) {
  24271. week.d = weekday;
  24272. } else {
  24273. config._pf.invalidWeekday = input;
  24274. }
  24275. });
  24276. addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
  24277. week[token] = toInt(input);
  24278. });
  24279. // HELPERS
  24280. function parseWeekday(input, locale) {
  24281. if (typeof input === 'string') {
  24282. if (!isNaN(input)) {
  24283. input = parseInt(input, 10);
  24284. }
  24285. else {
  24286. input = locale.weekdaysParse(input);
  24287. if (typeof input !== 'number') {
  24288. return null;
  24289. }
  24290. }
  24291. }
  24292. return input;
  24293. }
  24294. // LOCALES
  24295. var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
  24296. function localeWeekdays (m) {
  24297. return this._weekdays[m.day()];
  24298. }
  24299. var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
  24300. function localeWeekdaysShort (m) {
  24301. return this._weekdaysShort[m.day()];
  24302. }
  24303. var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
  24304. function localeWeekdaysMin (m) {
  24305. return this._weekdaysMin[m.day()];
  24306. }
  24307. function localeWeekdaysParse (weekdayName) {
  24308. var i, mom, regex;
  24309. if (!this._weekdaysParse) {
  24310. this._weekdaysParse = [];
  24311. }
  24312. for (i = 0; i < 7; i++) {
  24313. // make the regex if we don't have it already
  24314. if (!this._weekdaysParse[i]) {
  24315. mom = local__createLocal([2000, 1]).day(i);
  24316. regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
  24317. this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
  24318. }
  24319. // test the regex
  24320. if (this._weekdaysParse[i].test(weekdayName)) {
  24321. return i;
  24322. }
  24323. }
  24324. }
  24325. // MOMENTS
  24326. function getSetDayOfWeek (input) {
  24327. var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
  24328. if (input != null) {
  24329. input = parseWeekday(input, this.localeData());
  24330. return this.add(input - day, 'd');
  24331. } else {
  24332. return day;
  24333. }
  24334. }
  24335. function getSetLocaleDayOfWeek (input) {
  24336. var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
  24337. return input == null ? weekday : this.add(input - weekday, 'd');
  24338. }
  24339. function getSetISODayOfWeek (input) {
  24340. // behaves the same as moment#day except
  24341. // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
  24342. // as a setter, sunday should belong to the previous week.
  24343. return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
  24344. }
  24345. addFormatToken('H', ['HH', 2], 0, 'hour');
  24346. addFormatToken('h', ['hh', 2], 0, function () {
  24347. return this.hours() % 12 || 12;
  24348. });
  24349. function meridiem (token, lowercase) {
  24350. addFormatToken(token, 0, 0, function () {
  24351. return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
  24352. });
  24353. }
  24354. meridiem('a', true);
  24355. meridiem('A', false);
  24356. // ALIASES
  24357. addUnitAlias('hour', 'h');
  24358. // PARSING
  24359. function matchMeridiem (isStrict, locale) {
  24360. return locale._meridiemParse;
  24361. }
  24362. addRegexToken('a', matchMeridiem);
  24363. addRegexToken('A', matchMeridiem);
  24364. addRegexToken('H', match1to2);
  24365. addRegexToken('h', match1to2);
  24366. addRegexToken('HH', match1to2, match2);
  24367. addRegexToken('hh', match1to2, match2);
  24368. addParseToken(['H', 'HH'], HOUR);
  24369. addParseToken(['a', 'A'], function (input, array, config) {
  24370. config._isPm = config._locale.isPM(input);
  24371. config._meridiem = input;
  24372. });
  24373. addParseToken(['h', 'hh'], function (input, array, config) {
  24374. array[HOUR] = toInt(input);
  24375. config._pf.bigHour = true;
  24376. });
  24377. // LOCALES
  24378. function localeIsPM (input) {
  24379. // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
  24380. // Using charAt should be more compatible.
  24381. return ((input + '').toLowerCase().charAt(0) === 'p');
  24382. }
  24383. var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
  24384. function localeMeridiem (hours, minutes, isLower) {
  24385. if (hours > 11) {
  24386. return isLower ? 'pm' : 'PM';
  24387. } else {
  24388. return isLower ? 'am' : 'AM';
  24389. }
  24390. }
  24391. // MOMENTS
  24392. // Setting the hour should keep the time, because the user explicitly
  24393. // specified which hour he wants. So trying to maintain the same hour (in
  24394. // a new timezone) makes sense. Adding/subtracting hours does not follow
  24395. // this rule.
  24396. var getSetHour = makeGetSet('Hours', true);
  24397. addFormatToken('m', ['mm', 2], 0, 'minute');
  24398. // ALIASES
  24399. addUnitAlias('minute', 'm');
  24400. // PARSING
  24401. addRegexToken('m', match1to2);
  24402. addRegexToken('mm', match1to2, match2);
  24403. addParseToken(['m', 'mm'], MINUTE);
  24404. // MOMENTS
  24405. var getSetMinute = makeGetSet('Minutes', false);
  24406. addFormatToken('s', ['ss', 2], 0, 'second');
  24407. // ALIASES
  24408. addUnitAlias('second', 's');
  24409. // PARSING
  24410. addRegexToken('s', match1to2);
  24411. addRegexToken('ss', match1to2, match2);
  24412. addParseToken(['s', 'ss'], SECOND);
  24413. // MOMENTS
  24414. var getSetSecond = makeGetSet('Seconds', false);
  24415. addFormatToken('S', 0, 0, function () {
  24416. return ~~(this.millisecond() / 100);
  24417. });
  24418. addFormatToken(0, ['SS', 2], 0, function () {
  24419. return ~~(this.millisecond() / 10);
  24420. });
  24421. function millisecond__milliseconds (token) {
  24422. addFormatToken(0, [token, 3], 0, 'millisecond');
  24423. }
  24424. millisecond__milliseconds('SSS');
  24425. millisecond__milliseconds('SSSS');
  24426. // ALIASES
  24427. addUnitAlias('millisecond', 'ms');
  24428. // PARSING
  24429. addRegexToken('S', match1to3, match1);
  24430. addRegexToken('SS', match1to3, match2);
  24431. addRegexToken('SSS', match1to3, match3);
  24432. addRegexToken('SSSS', matchUnsigned);
  24433. addParseToken(['S', 'SS', 'SSS', 'SSSS'], function (input, array) {
  24434. array[MILLISECOND] = toInt(('0.' + input) * 1000);
  24435. });
  24436. // MOMENTS
  24437. var getSetMillisecond = makeGetSet('Milliseconds', false);
  24438. addFormatToken('z', 0, 0, 'zoneAbbr');
  24439. addFormatToken('zz', 0, 0, 'zoneName');
  24440. // MOMENTS
  24441. function getZoneAbbr () {
  24442. return this._isUTC ? 'UTC' : '';
  24443. }
  24444. function getZoneName () {
  24445. return this._isUTC ? 'Coordinated Universal Time' : '';
  24446. }
  24447. var momentPrototype__proto = Moment.prototype;
  24448. momentPrototype__proto.add = add_subtract__add;
  24449. momentPrototype__proto.calendar = calendar__calendar;
  24450. momentPrototype__proto.clone = clone;
  24451. momentPrototype__proto.diff = diff;
  24452. momentPrototype__proto.endOf = endOf;
  24453. momentPrototype__proto.format = format;
  24454. momentPrototype__proto.from = from;
  24455. momentPrototype__proto.fromNow = fromNow;
  24456. momentPrototype__proto.get = getSet;
  24457. momentPrototype__proto.invalidAt = invalidAt;
  24458. momentPrototype__proto.isAfter = isAfter;
  24459. momentPrototype__proto.isBefore = isBefore;
  24460. momentPrototype__proto.isBetween = isBetween;
  24461. momentPrototype__proto.isSame = isSame;
  24462. momentPrototype__proto.isValid = moment_valid__isValid;
  24463. momentPrototype__proto.lang = lang;
  24464. momentPrototype__proto.locale = locale;
  24465. momentPrototype__proto.localeData = localeData;
  24466. momentPrototype__proto.max = prototypeMax;
  24467. momentPrototype__proto.min = prototypeMin;
  24468. momentPrototype__proto.parsingFlags = parsingFlags;
  24469. momentPrototype__proto.set = getSet;
  24470. momentPrototype__proto.startOf = startOf;
  24471. momentPrototype__proto.subtract = add_subtract__subtract;
  24472. momentPrototype__proto.toArray = toArray;
  24473. momentPrototype__proto.toDate = toDate;
  24474. momentPrototype__proto.toISOString = moment_format__toISOString;
  24475. momentPrototype__proto.toJSON = moment_format__toISOString;
  24476. momentPrototype__proto.toString = toString;
  24477. momentPrototype__proto.unix = unix;
  24478. momentPrototype__proto.valueOf = to_type__valueOf;
  24479. // Year
  24480. momentPrototype__proto.year = getSetYear;
  24481. momentPrototype__proto.isLeapYear = getIsLeapYear;
  24482. // Week Year
  24483. momentPrototype__proto.weekYear = getSetWeekYear;
  24484. momentPrototype__proto.isoWeekYear = getSetISOWeekYear;
  24485. // Quarter
  24486. momentPrototype__proto.quarter = momentPrototype__proto.quarters = getSetQuarter;
  24487. // Month
  24488. momentPrototype__proto.month = getSetMonth;
  24489. momentPrototype__proto.daysInMonth = getDaysInMonth;
  24490. // Week
  24491. momentPrototype__proto.week = momentPrototype__proto.weeks = getSetWeek;
  24492. momentPrototype__proto.isoWeek = momentPrototype__proto.isoWeeks = getSetISOWeek;
  24493. momentPrototype__proto.weeksInYear = getWeeksInYear;
  24494. momentPrototype__proto.isoWeeksInYear = getISOWeeksInYear;
  24495. // Day
  24496. momentPrototype__proto.date = getSetDayOfMonth;
  24497. momentPrototype__proto.day = momentPrototype__proto.days = getSetDayOfWeek;
  24498. momentPrototype__proto.weekday = getSetLocaleDayOfWeek;
  24499. momentPrototype__proto.isoWeekday = getSetISODayOfWeek;
  24500. momentPrototype__proto.dayOfYear = getSetDayOfYear;
  24501. // Hour
  24502. momentPrototype__proto.hour = momentPrototype__proto.hours = getSetHour;
  24503. // Minute
  24504. momentPrototype__proto.minute = momentPrototype__proto.minutes = getSetMinute;
  24505. // Second
  24506. momentPrototype__proto.second = momentPrototype__proto.seconds = getSetSecond;
  24507. // Millisecond
  24508. momentPrototype__proto.millisecond = momentPrototype__proto.milliseconds = getSetMillisecond;
  24509. // Offset
  24510. momentPrototype__proto.utcOffset = getSetOffset;
  24511. momentPrototype__proto.utc = setOffsetToUTC;
  24512. momentPrototype__proto.local = setOffsetToLocal;
  24513. momentPrototype__proto.parseZone = setOffsetToParsedOffset;
  24514. momentPrototype__proto.hasAlignedHourOffset = hasAlignedHourOffset;
  24515. momentPrototype__proto.isDST = isDaylightSavingTime;
  24516. momentPrototype__proto.isDSTShifted = isDaylightSavingTimeShifted;
  24517. momentPrototype__proto.isLocal = isLocal;
  24518. momentPrototype__proto.isUtcOffset = isUtcOffset;
  24519. momentPrototype__proto.isUtc = isUtc;
  24520. momentPrototype__proto.isUTC = isUtc;
  24521. // Timezone
  24522. momentPrototype__proto.zoneAbbr = getZoneAbbr;
  24523. momentPrototype__proto.zoneName = getZoneName;
  24524. // Deprecations
  24525. momentPrototype__proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
  24526. momentPrototype__proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
  24527. momentPrototype__proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear);
  24528. momentPrototype__proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779', getSetZone);
  24529. var momentPrototype = momentPrototype__proto;
  24530. function moment__createUnix (input) {
  24531. return local__createLocal(input * 1000);
  24532. }
  24533. function moment__createInZone () {
  24534. return local__createLocal.apply(null, arguments).parseZone();
  24535. }
  24536. var defaultCalendar = {
  24537. sameDay : '[Today at] LT',
  24538. nextDay : '[Tomorrow at] LT',
  24539. nextWeek : 'dddd [at] LT',
  24540. lastDay : '[Yesterday at] LT',
  24541. lastWeek : '[Last] dddd [at] LT',
  24542. sameElse : 'L'
  24543. };
  24544. function locale_calendar__calendar (key, mom, now) {
  24545. var output = this._calendar[key];
  24546. return typeof output === 'function' ? output.call(mom, now) : output;
  24547. }
  24548. var defaultLongDateFormat = {
  24549. LTS : 'h:mm:ss A',
  24550. LT : 'h:mm A',
  24551. L : 'MM/DD/YYYY',
  24552. LL : 'MMMM D, YYYY',
  24553. LLL : 'MMMM D, YYYY LT',
  24554. LLLL : 'dddd, MMMM D, YYYY LT'
  24555. };
  24556. function longDateFormat (key) {
  24557. var output = this._longDateFormat[key];
  24558. if (!output && this._longDateFormat[key.toUpperCase()]) {
  24559. output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
  24560. return val.slice(1);
  24561. });
  24562. this._longDateFormat[key] = output;
  24563. }
  24564. return output;
  24565. }
  24566. var defaultInvalidDate = 'Invalid date';
  24567. function invalidDate () {
  24568. return this._invalidDate;
  24569. }
  24570. var defaultOrdinal = '%d';
  24571. var defaultOrdinalParse = /\d{1,2}/;
  24572. function ordinal (number) {
  24573. return this._ordinal.replace('%d', number);
  24574. }
  24575. function preParsePostFormat (string) {
  24576. return string;
  24577. }
  24578. var defaultRelativeTime = {
  24579. future : 'in %s',
  24580. past : '%s ago',
  24581. s : 'a few seconds',
  24582. m : 'a minute',
  24583. mm : '%d minutes',
  24584. h : 'an hour',
  24585. hh : '%d hours',
  24586. d : 'a day',
  24587. dd : '%d days',
  24588. M : 'a month',
  24589. MM : '%d months',
  24590. y : 'a year',
  24591. yy : '%d years'
  24592. };
  24593. function relative__relativeTime (number, withoutSuffix, string, isFuture) {
  24594. var output = this._relativeTime[string];
  24595. return (typeof output === 'function') ?
  24596. output(number, withoutSuffix, string, isFuture) :
  24597. output.replace(/%d/i, number);
  24598. }
  24599. function pastFuture (diff, output) {
  24600. var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
  24601. return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
  24602. }
  24603. function set__set (config) {
  24604. var prop, i;
  24605. for (i in config) {
  24606. prop = config[i];
  24607. if (typeof prop === 'function') {
  24608. this[i] = prop;
  24609. } else {
  24610. this['_' + i] = prop;
  24611. }
  24612. }
  24613. // Lenient ordinal parsing accepts just a number in addition to
  24614. // number + (possibly) stuff coming from _ordinalParseLenient.
  24615. this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + /\d{1,2}/.source);
  24616. }
  24617. var prototype__proto = Locale.prototype;
  24618. prototype__proto._calendar = defaultCalendar;
  24619. prototype__proto.calendar = locale_calendar__calendar;
  24620. prototype__proto._longDateFormat = defaultLongDateFormat;
  24621. prototype__proto.longDateFormat = longDateFormat;
  24622. prototype__proto._invalidDate = defaultInvalidDate;
  24623. prototype__proto.invalidDate = invalidDate;
  24624. prototype__proto._ordinal = defaultOrdinal;
  24625. prototype__proto.ordinal = ordinal;
  24626. prototype__proto._ordinalParse = defaultOrdinalParse;
  24627. prototype__proto.preparse = preParsePostFormat;
  24628. prototype__proto.postformat = preParsePostFormat;
  24629. prototype__proto._relativeTime = defaultRelativeTime;
  24630. prototype__proto.relativeTime = relative__relativeTime;
  24631. prototype__proto.pastFuture = pastFuture;
  24632. prototype__proto.set = set__set;
  24633. // Month
  24634. prototype__proto.months = localeMonths;
  24635. prototype__proto._months = defaultLocaleMonths;
  24636. prototype__proto.monthsShort = localeMonthsShort;
  24637. prototype__proto._monthsShort = defaultLocaleMonthsShort;
  24638. prototype__proto.monthsParse = localeMonthsParse;
  24639. // Week
  24640. prototype__proto.week = localeWeek;
  24641. prototype__proto._week = defaultLocaleWeek;
  24642. prototype__proto.firstDayOfYear = localeFirstDayOfYear;
  24643. prototype__proto.firstDayOfWeek = localeFirstDayOfWeek;
  24644. // Day of Week
  24645. prototype__proto.weekdays = localeWeekdays;
  24646. prototype__proto._weekdays = defaultLocaleWeekdays;
  24647. prototype__proto.weekdaysMin = localeWeekdaysMin;
  24648. prototype__proto._weekdaysMin = defaultLocaleWeekdaysMin;
  24649. prototype__proto.weekdaysShort = localeWeekdaysShort;
  24650. prototype__proto._weekdaysShort = defaultLocaleWeekdaysShort;
  24651. prototype__proto.weekdaysParse = localeWeekdaysParse;
  24652. // Hours
  24653. prototype__proto.isPM = localeIsPM;
  24654. prototype__proto._meridiemParse = defaultLocaleMeridiemParse;
  24655. prototype__proto.meridiem = localeMeridiem;
  24656. function lists__get (format, index, field, setter) {
  24657. var locale = locales__getLocale();
  24658. var utc = utc__createUTC().set(setter, index);
  24659. return locale[field](utc, format);
  24660. }
  24661. function list (format, index, field, count, setter) {
  24662. if (typeof format === 'number') {
  24663. index = format;
  24664. format = undefined;
  24665. }
  24666. format = format || '';
  24667. if (index != null) {
  24668. return lists__get(format, index, field, setter);
  24669. }
  24670. var i;
  24671. var out = [];
  24672. for (i = 0; i < count; i++) {
  24673. out[i] = lists__get(format, i, field, setter);
  24674. }
  24675. return out;
  24676. }
  24677. function lists__listMonths (format, index) {
  24678. return list(format, index, 'months', 12, 'month');
  24679. }
  24680. function lists__listMonthsShort (format, index) {
  24681. return list(format, index, 'monthsShort', 12, 'month');
  24682. }
  24683. function lists__listWeekdays (format, index) {
  24684. return list(format, index, 'weekdays', 7, 'day');
  24685. }
  24686. function lists__listWeekdaysShort (format, index) {
  24687. return list(format, index, 'weekdaysShort', 7, 'day');
  24688. }
  24689. function lists__listWeekdaysMin (format, index) {
  24690. return list(format, index, 'weekdaysMin', 7, 'day');
  24691. }
  24692. locales__getSetGlobalLocale('en', {
  24693. ordinalParse: /\d{1,2}(th|st|nd|rd)/,
  24694. ordinal : function (number) {
  24695. var b = number % 10,
  24696. output = (toInt(number % 100 / 10) === 1) ? 'th' :
  24697. (b === 1) ? 'st' :
  24698. (b === 2) ? 'nd' :
  24699. (b === 3) ? 'rd' : 'th';
  24700. return number + output;
  24701. }
  24702. });
  24703. // Side effect imports
  24704. hooks__hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', locales__getSetGlobalLocale);
  24705. hooks__hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', locales__getLocale);
  24706. var mathAbs = Math.abs;
  24707. function abs__abs () {
  24708. var data = this._data;
  24709. this._milliseconds = mathAbs(this._milliseconds);
  24710. this._days = mathAbs(this._days);
  24711. this._months = mathAbs(this._months);
  24712. data.milliseconds = mathAbs(data.milliseconds);
  24713. data.seconds = mathAbs(data.seconds);
  24714. data.minutes = mathAbs(data.minutes);
  24715. data.hours = mathAbs(data.hours);
  24716. data.months = mathAbs(data.months);
  24717. data.years = mathAbs(data.years);
  24718. return this;
  24719. }
  24720. function duration_add_subtract__addSubtract (duration, input, value, direction) {
  24721. var other = create__createDuration(input, value);
  24722. duration._milliseconds += direction * other._milliseconds;
  24723. duration._days += direction * other._days;
  24724. duration._months += direction * other._months;
  24725. return duration._bubble();
  24726. }
  24727. // supports only 2.0-style add(1, 's') or add(duration)
  24728. function duration_add_subtract__add (input, value) {
  24729. return duration_add_subtract__addSubtract(this, input, value, 1);
  24730. }
  24731. // supports only 2.0-style subtract(1, 's') or subtract(duration)
  24732. function duration_add_subtract__subtract (input, value) {
  24733. return duration_add_subtract__addSubtract(this, input, value, -1);
  24734. }
  24735. function bubble () {
  24736. var milliseconds = this._milliseconds;
  24737. var days = this._days;
  24738. var months = this._months;
  24739. var data = this._data;
  24740. var seconds, minutes, hours, years = 0;
  24741. // The following code bubbles up values, see the tests for
  24742. // examples of what that means.
  24743. data.milliseconds = milliseconds % 1000;
  24744. seconds = absFloor(milliseconds / 1000);
  24745. data.seconds = seconds % 60;
  24746. minutes = absFloor(seconds / 60);
  24747. data.minutes = minutes % 60;
  24748. hours = absFloor(minutes / 60);
  24749. data.hours = hours % 24;
  24750. days += absFloor(hours / 24);
  24751. // Accurately convert days to years, assume start from year 0.
  24752. years = absFloor(daysToYears(days));
  24753. days -= absFloor(yearsToDays(years));
  24754. // 30 days to a month
  24755. // TODO (iskren): Use anchor date (like 1st Jan) to compute this.
  24756. months += absFloor(days / 30);
  24757. days %= 30;
  24758. // 12 months -> 1 year
  24759. years += absFloor(months / 12);
  24760. months %= 12;
  24761. data.days = days;
  24762. data.months = months;
  24763. data.years = years;
  24764. return this;
  24765. }
  24766. function daysToYears (days) {
  24767. // 400 years have 146097 days (taking into account leap year rules)
  24768. return days * 400 / 146097;
  24769. }
  24770. function yearsToDays (years) {
  24771. // years * 365 + absFloor(years / 4) -
  24772. // absFloor(years / 100) + absFloor(years / 400);
  24773. return years * 146097 / 400;
  24774. }
  24775. function as (units) {
  24776. var days;
  24777. var months;
  24778. var milliseconds = this._milliseconds;
  24779. units = normalizeUnits(units);
  24780. if (units === 'month' || units === 'year') {
  24781. days = this._days + milliseconds / 864e5;
  24782. months = this._months + daysToYears(days) * 12;
  24783. return units === 'month' ? months : months / 12;
  24784. } else {
  24785. // handle milliseconds separately because of floating point math errors (issue #1867)
  24786. days = this._days + Math.round(yearsToDays(this._months / 12));
  24787. switch (units) {
  24788. case 'week' : return days / 7 + milliseconds / 6048e5;
  24789. case 'day' : return days + milliseconds / 864e5;
  24790. case 'hour' : return days * 24 + milliseconds / 36e5;
  24791. case 'minute' : return days * 24 * 60 + milliseconds / 6e4;
  24792. case 'second' : return days * 24 * 60 * 60 + milliseconds / 1000;
  24793. // Math.floor prevents floating point math errors here
  24794. case 'millisecond': return Math.floor(days * 24 * 60 * 60 * 1000) + milliseconds;
  24795. default: throw new Error('Unknown unit ' + units);
  24796. }
  24797. }
  24798. }
  24799. // TODO: Use this.as('ms')?
  24800. function as__valueOf () {
  24801. return (
  24802. this._milliseconds +
  24803. this._days * 864e5 +
  24804. (this._months % 12) * 2592e6 +
  24805. toInt(this._months / 12) * 31536e6
  24806. );
  24807. }
  24808. function makeAs (alias) {
  24809. return function () {
  24810. return this.as(alias);
  24811. };
  24812. }
  24813. var asMilliseconds = makeAs('ms');
  24814. var asSeconds = makeAs('s');
  24815. var asMinutes = makeAs('m');
  24816. var asHours = makeAs('h');
  24817. var asDays = makeAs('d');
  24818. var asWeeks = makeAs('w');
  24819. var asMonths = makeAs('M');
  24820. var asYears = makeAs('y');
  24821. function get__get (units) {
  24822. units = normalizeUnits(units);
  24823. return this[units + 's']();
  24824. }
  24825. function makeGetter(name) {
  24826. return function () {
  24827. return this._data[name];
  24828. };
  24829. }
  24830. var get__milliseconds = makeGetter('milliseconds');
  24831. var seconds = makeGetter('seconds');
  24832. var minutes = makeGetter('minutes');
  24833. var hours = makeGetter('hours');
  24834. var days = makeGetter('days');
  24835. var months = makeGetter('months');
  24836. var years = makeGetter('years');
  24837. function weeks () {
  24838. return absFloor(this.days() / 7);
  24839. }
  24840. var round = Math.round;
  24841. var thresholds = {
  24842. s: 45, // seconds to minute
  24843. m: 45, // minutes to hour
  24844. h: 22, // hours to day
  24845. d: 26, // days to month
  24846. M: 11 // months to year
  24847. };
  24848. // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
  24849. function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
  24850. return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
  24851. }
  24852. function humanize__relativeTime (posNegDuration, withoutSuffix, locale) {
  24853. var duration = create__createDuration(posNegDuration).abs();
  24854. var seconds = round(duration.as('s'));
  24855. var minutes = round(duration.as('m'));
  24856. var hours = round(duration.as('h'));
  24857. var days = round(duration.as('d'));
  24858. var months = round(duration.as('M'));
  24859. var years = round(duration.as('y'));
  24860. var a = seconds < thresholds.s && ['s', seconds] ||
  24861. minutes === 1 && ['m'] ||
  24862. minutes < thresholds.m && ['mm', minutes] ||
  24863. hours === 1 && ['h'] ||
  24864. hours < thresholds.h && ['hh', hours] ||
  24865. days === 1 && ['d'] ||
  24866. days < thresholds.d && ['dd', days] ||
  24867. months === 1 && ['M'] ||
  24868. months < thresholds.M && ['MM', months] ||
  24869. years === 1 && ['y'] || ['yy', years];
  24870. a[2] = withoutSuffix;
  24871. a[3] = +posNegDuration > 0;
  24872. a[4] = locale;
  24873. return substituteTimeAgo.apply(null, a);
  24874. }
  24875. // This function allows you to set a threshold for relative time strings
  24876. function humanize__getSetRelativeTimeThreshold (threshold, limit) {
  24877. if (thresholds[threshold] === undefined) {
  24878. return false;
  24879. }
  24880. if (limit === undefined) {
  24881. return thresholds[threshold];
  24882. }
  24883. thresholds[threshold] = limit;
  24884. return true;
  24885. }
  24886. function humanize (withSuffix) {
  24887. var locale = this.localeData();
  24888. var output = humanize__relativeTime(this, !withSuffix, locale);
  24889. if (withSuffix) {
  24890. output = locale.pastFuture(+this, output);
  24891. }
  24892. return locale.postformat(output);
  24893. }
  24894. var iso_string__abs = Math.abs;
  24895. function iso_string__toISOString() {
  24896. // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
  24897. var Y = iso_string__abs(this.years());
  24898. var M = iso_string__abs(this.months());
  24899. var D = iso_string__abs(this.days());
  24900. var h = iso_string__abs(this.hours());
  24901. var m = iso_string__abs(this.minutes());
  24902. var s = iso_string__abs(this.seconds() + this.milliseconds() / 1000);
  24903. var total = this.asSeconds();
  24904. if (!total) {
  24905. // this is the same as C#'s (Noda) and python (isodate)...
  24906. // but not other JS (goog.date)
  24907. return 'P0D';
  24908. }
  24909. return (total < 0 ? '-' : '') +
  24910. 'P' +
  24911. (Y ? Y + 'Y' : '') +
  24912. (M ? M + 'M' : '') +
  24913. (D ? D + 'D' : '') +
  24914. ((h || m || s) ? 'T' : '') +
  24915. (h ? h + 'H' : '') +
  24916. (m ? m + 'M' : '') +
  24917. (s ? s + 'S' : '');
  24918. }
  24919. var duration_prototype__proto = Duration.prototype;
  24920. duration_prototype__proto.abs = abs__abs;
  24921. duration_prototype__proto.add = duration_add_subtract__add;
  24922. duration_prototype__proto.subtract = duration_add_subtract__subtract;
  24923. duration_prototype__proto.as = as;
  24924. duration_prototype__proto.asMilliseconds = asMilliseconds;
  24925. duration_prototype__proto.asSeconds = asSeconds;
  24926. duration_prototype__proto.asMinutes = asMinutes;
  24927. duration_prototype__proto.asHours = asHours;
  24928. duration_prototype__proto.asDays = asDays;
  24929. duration_prototype__proto.asWeeks = asWeeks;
  24930. duration_prototype__proto.asMonths = asMonths;
  24931. duration_prototype__proto.asYears = asYears;
  24932. duration_prototype__proto.valueOf = as__valueOf;
  24933. duration_prototype__proto._bubble = bubble;
  24934. duration_prototype__proto.get = get__get;
  24935. duration_prototype__proto.milliseconds = get__milliseconds;
  24936. duration_prototype__proto.seconds = seconds;
  24937. duration_prototype__proto.minutes = minutes;
  24938. duration_prototype__proto.hours = hours;
  24939. duration_prototype__proto.days = days;
  24940. duration_prototype__proto.weeks = weeks;
  24941. duration_prototype__proto.months = months;
  24942. duration_prototype__proto.years = years;
  24943. duration_prototype__proto.humanize = humanize;
  24944. duration_prototype__proto.toISOString = iso_string__toISOString;
  24945. duration_prototype__proto.toString = iso_string__toISOString;
  24946. duration_prototype__proto.toJSON = iso_string__toISOString;
  24947. duration_prototype__proto.locale = locale;
  24948. duration_prototype__proto.localeData = localeData;
  24949. // Deprecations
  24950. duration_prototype__proto.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', iso_string__toISOString);
  24951. duration_prototype__proto.lang = lang;
  24952. // Side effect imports
  24953. addFormatToken('X', 0, 0, 'unix');
  24954. addFormatToken('x', 0, 0, 'valueOf');
  24955. // PARSING
  24956. addRegexToken('x', matchSigned);
  24957. addRegexToken('X', matchTimestamp);
  24958. addParseToken('X', function (input, array, config) {
  24959. config._d = new Date(parseFloat(input, 10) * 1000);
  24960. });
  24961. addParseToken('x', function (input, array, config) {
  24962. config._d = new Date(toInt(input));
  24963. });
  24964. // Side effect imports
  24965. hooks__hooks.version = '2.10.0';
  24966. setHookCallback(local__createLocal);
  24967. hooks__hooks.fn = momentPrototype;
  24968. hooks__hooks.min = min;
  24969. hooks__hooks.max = max;
  24970. hooks__hooks.utc = utc__createUTC;
  24971. hooks__hooks.unix = moment__createUnix;
  24972. hooks__hooks.months = lists__listMonths;
  24973. hooks__hooks.isDate = isDate;
  24974. hooks__hooks.locale = locales__getSetGlobalLocale;
  24975. hooks__hooks.invalid = valid__createInvalid;
  24976. hooks__hooks.duration = create__createDuration;
  24977. hooks__hooks.isMoment = isMoment;
  24978. hooks__hooks.weekdays = lists__listWeekdays;
  24979. hooks__hooks.parseZone = moment__createInZone;
  24980. hooks__hooks.localeData = locales__getLocale;
  24981. hooks__hooks.isDuration = isDuration;
  24982. hooks__hooks.monthsShort = lists__listMonthsShort;
  24983. hooks__hooks.weekdaysMin = lists__listWeekdaysMin;
  24984. hooks__hooks.defineLocale = defineLocale;
  24985. hooks__hooks.weekdaysShort = lists__listWeekdaysShort;
  24986. hooks__hooks.normalizeUnits = normalizeUnits;
  24987. hooks__hooks.relativeTimeThreshold = humanize__getSetRelativeTimeThreshold;
  24988. var _moment = hooks__hooks;
  24989. return _moment;
  24990. }));
  24991. /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(71)(module)))
  24992. /***/ },
  24993. /* 59 */
  24994. /***/ function(module, exports, __webpack_require__) {
  24995. var __WEBPACK_AMD_DEFINE_RESULT__;/*! Hammer.JS - v1.1.3 - 2014-05-20
  24996. * http://eightmedia.github.io/hammer.js
  24997. *
  24998. * Copyright (c) 2014 Jorik Tangelder <j.tangelder@gmail.com>;
  24999. * Licensed under the MIT license */
  25000. (function(window, undefined) {
  25001. 'use strict';
  25002. /**
  25003. * @main
  25004. * @module hammer
  25005. *
  25006. * @class Hammer
  25007. * @static
  25008. */
  25009. /**
  25010. * Hammer, use this to create instances
  25011. * ````
  25012. * var hammertime = new Hammer(myElement);
  25013. * ````
  25014. *
  25015. * @method Hammer
  25016. * @param {HTMLElement} element
  25017. * @param {Object} [options={}]
  25018. * @return {Hammer.Instance}
  25019. */
  25020. var Hammer = function Hammer(element, options) {
  25021. return new Hammer.Instance(element, options || {});
  25022. };
  25023. /**
  25024. * version, as defined in package.json
  25025. * the value will be set at each build
  25026. * @property VERSION
  25027. * @final
  25028. * @type {String}
  25029. */
  25030. Hammer.VERSION = '1.1.3';
  25031. /**
  25032. * default settings.
  25033. * more settings are defined per gesture at `/gestures`. Each gesture can be disabled/enabled
  25034. * by setting it's name (like `swipe`) to false.
  25035. * You can set the defaults for all instances by changing this object before creating an instance.
  25036. * @example
  25037. * ````
  25038. * Hammer.defaults.drag = false;
  25039. * Hammer.defaults.behavior.touchAction = 'pan-y';
  25040. * delete Hammer.defaults.behavior.userSelect;
  25041. * ````
  25042. * @property defaults
  25043. * @type {Object}
  25044. */
  25045. Hammer.defaults = {
  25046. /**
  25047. * this setting object adds styles and attributes to the element to prevent the browser from doing
  25048. * its native behavior. The css properties are auto prefixed for the browsers when needed.
  25049. * @property defaults.behavior
  25050. * @type {Object}
  25051. */
  25052. behavior: {
  25053. /**
  25054. * Disables text selection to improve the dragging gesture. When the value is `none` it also sets
  25055. * `onselectstart=false` for IE on the element. Mainly for desktop browsers.
  25056. * @property defaults.behavior.userSelect
  25057. * @type {String}
  25058. * @default 'none'
  25059. */
  25060. userSelect: 'none',
  25061. /**
  25062. * Specifies whether and how a given region can be manipulated by the user (for instance, by panning or zooming).
  25063. * Used by Chrome 35> and IE10>. By default this makes the element blocking any touch event.
  25064. * @property defaults.behavior.touchAction
  25065. * @type {String}
  25066. * @default: 'pan-y'
  25067. */
  25068. touchAction: 'pan-y',
  25069. /**
  25070. * Disables the default callout shown when you touch and hold a touch target.
  25071. * On iOS, when you touch and hold a touch target such as a link, Safari displays
  25072. * a callout containing information about the link. This property allows you to disable that callout.
  25073. * @property defaults.behavior.touchCallout
  25074. * @type {String}
  25075. * @default 'none'
  25076. */
  25077. touchCallout: 'none',
  25078. /**
  25079. * Specifies whether zooming is enabled. Used by IE10>
  25080. * @property defaults.behavior.contentZooming
  25081. * @type {String}
  25082. * @default 'none'
  25083. */
  25084. contentZooming: 'none',
  25085. /**
  25086. * Specifies that an entire element should be draggable instead of its contents.
  25087. * Mainly for desktop browsers.
  25088. * @property defaults.behavior.userDrag
  25089. * @type {String}
  25090. * @default 'none'
  25091. */
  25092. userDrag: 'none',
  25093. /**
  25094. * Overrides the highlight color shown when the user taps a link or a JavaScript
  25095. * clickable element in Safari on iPhone. This property obeys the alpha value, if specified.
  25096. *
  25097. * If you don't specify an alpha value, Safari on iPhone applies a default alpha value
  25098. * to the color. To disable tap highlighting, set the alpha value to 0 (invisible).
  25099. * If you set the alpha value to 1.0 (opaque), the element is not visible when tapped.
  25100. * @property defaults.behavior.tapHighlightColor
  25101. * @type {String}
  25102. * @default 'rgba(0,0,0,0)'
  25103. */
  25104. tapHighlightColor: 'rgba(0,0,0,0)'
  25105. }
  25106. };
  25107. /**
  25108. * hammer document where the base events are added at
  25109. * @property DOCUMENT
  25110. * @type {HTMLElement}
  25111. * @default window.document
  25112. */
  25113. Hammer.DOCUMENT = document;
  25114. /**
  25115. * detect support for pointer events
  25116. * @property HAS_POINTEREVENTS
  25117. * @type {Boolean}
  25118. */
  25119. Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;
  25120. /**
  25121. * detect support for touch events
  25122. * @property HAS_TOUCHEVENTS
  25123. * @type {Boolean}
  25124. */
  25125. Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);
  25126. /**
  25127. * detect mobile browsers
  25128. * @property IS_MOBILE
  25129. * @type {Boolean}
  25130. */
  25131. Hammer.IS_MOBILE = /mobile|tablet|ip(ad|hone|od)|android|silk/i.test(navigator.userAgent);
  25132. /**
  25133. * detect if we want to support mouseevents at all
  25134. * @property NO_MOUSEEVENTS
  25135. * @type {Boolean}
  25136. */
  25137. Hammer.NO_MOUSEEVENTS = (Hammer.HAS_TOUCHEVENTS && Hammer.IS_MOBILE) || Hammer.HAS_POINTEREVENTS;
  25138. /**
  25139. * interval in which Hammer recalculates current velocity/direction/angle in ms
  25140. * @property CALCULATE_INTERVAL
  25141. * @type {Number}
  25142. * @default 25
  25143. */
  25144. Hammer.CALCULATE_INTERVAL = 25;
  25145. /**
  25146. * eventtypes per touchevent (start, move, end) are filled by `Event.determineEventTypes` on `setup`
  25147. * the object contains the DOM event names per type (`EVENT_START`, `EVENT_MOVE`, `EVENT_END`)
  25148. * @property EVENT_TYPES
  25149. * @private
  25150. * @writeOnce
  25151. * @type {Object}
  25152. */
  25153. var EVENT_TYPES = {};
  25154. /**
  25155. * direction strings, for safe comparisons
  25156. * @property DIRECTION_DOWN|LEFT|UP|RIGHT
  25157. * @final
  25158. * @type {String}
  25159. * @default 'down' 'left' 'up' 'right'
  25160. */
  25161. var DIRECTION_DOWN = Hammer.DIRECTION_DOWN = 'down';
  25162. var DIRECTION_LEFT = Hammer.DIRECTION_LEFT = 'left';
  25163. var DIRECTION_UP = Hammer.DIRECTION_UP = 'up';
  25164. var DIRECTION_RIGHT = Hammer.DIRECTION_RIGHT = 'right';
  25165. /**
  25166. * pointertype strings, for safe comparisons
  25167. * @property POINTER_MOUSE|TOUCH|PEN
  25168. * @final
  25169. * @type {String}
  25170. * @default 'mouse' 'touch' 'pen'
  25171. */
  25172. var POINTER_MOUSE = Hammer.POINTER_MOUSE = 'mouse';
  25173. var POINTER_TOUCH = Hammer.POINTER_TOUCH = 'touch';
  25174. var POINTER_PEN = Hammer.POINTER_PEN = 'pen';
  25175. /**
  25176. * eventtypes
  25177. * @property EVENT_START|MOVE|END|RELEASE|TOUCH
  25178. * @final
  25179. * @type {String}
  25180. * @default 'start' 'change' 'move' 'end' 'release' 'touch'
  25181. */
  25182. var EVENT_START = Hammer.EVENT_START = 'start';
  25183. var EVENT_MOVE = Hammer.EVENT_MOVE = 'move';
  25184. var EVENT_END = Hammer.EVENT_END = 'end';
  25185. var EVENT_RELEASE = Hammer.EVENT_RELEASE = 'release';
  25186. var EVENT_TOUCH = Hammer.EVENT_TOUCH = 'touch';
  25187. /**
  25188. * if the window events are set...
  25189. * @property READY
  25190. * @writeOnce
  25191. * @type {Boolean}
  25192. * @default false
  25193. */
  25194. Hammer.READY = false;
  25195. /**
  25196. * plugins namespace
  25197. * @property plugins
  25198. * @type {Object}
  25199. */
  25200. Hammer.plugins = Hammer.plugins || {};
  25201. /**
  25202. * gestures namespace
  25203. * see `/gestures` for the definitions
  25204. * @property gestures
  25205. * @type {Object}
  25206. */
  25207. Hammer.gestures = Hammer.gestures || {};
  25208. /**
  25209. * setup events to detect gestures on the document
  25210. * this function is called when creating an new instance
  25211. * @private
  25212. */
  25213. function setup() {
  25214. if(Hammer.READY) {
  25215. return;
  25216. }
  25217. // find what eventtypes we add listeners to
  25218. Event.determineEventTypes();
  25219. // Register all gestures inside Hammer.gestures
  25220. Utils.each(Hammer.gestures, function(gesture) {
  25221. Detection.register(gesture);
  25222. });
  25223. // Add touch events on the document
  25224. Event.onTouch(Hammer.DOCUMENT, EVENT_MOVE, Detection.detect);
  25225. Event.onTouch(Hammer.DOCUMENT, EVENT_END, Detection.detect);
  25226. // Hammer is ready...!
  25227. Hammer.READY = true;
  25228. }
  25229. /**
  25230. * @module hammer
  25231. *
  25232. * @class Utils
  25233. * @static
  25234. */
  25235. var Utils = Hammer.utils = {
  25236. /**
  25237. * extend method, could also be used for cloning when `dest` is an empty object.
  25238. * changes the dest object
  25239. * @method extend
  25240. * @param {Object} dest
  25241. * @param {Object} src
  25242. * @param {Boolean} [merge=false] do a merge
  25243. * @return {Object} dest
  25244. */
  25245. extend: function extend(dest, src, merge) {
  25246. for(var key in src) {
  25247. if(!src.hasOwnProperty(key) || (dest[key] !== undefined && merge)) {
  25248. continue;
  25249. }
  25250. dest[key] = src[key];
  25251. }
  25252. return dest;
  25253. },
  25254. /**
  25255. * simple addEventListener wrapper
  25256. * @method on
  25257. * @param {HTMLElement} element
  25258. * @param {String} type
  25259. * @param {Function} handler
  25260. */
  25261. on: function on(element, type, handler) {
  25262. element.addEventListener(type, handler, false);
  25263. },
  25264. /**
  25265. * simple removeEventListener wrapper
  25266. * @method off
  25267. * @param {HTMLElement} element
  25268. * @param {String} type
  25269. * @param {Function} handler
  25270. */
  25271. off: function off(element, type, handler) {
  25272. element.removeEventListener(type, handler, false);
  25273. },
  25274. /**
  25275. * forEach over arrays and objects
  25276. * @method each
  25277. * @param {Object|Array} obj
  25278. * @param {Function} iterator
  25279. * @param {any} iterator.item
  25280. * @param {Number} iterator.index
  25281. * @param {Object|Array} iterator.obj the source object
  25282. * @param {Object} context value to use as `this` in the iterator
  25283. */
  25284. each: function each(obj, iterator, context) {
  25285. var i, len;
  25286. // native forEach on arrays
  25287. if('forEach' in obj) {
  25288. obj.forEach(iterator, context);
  25289. // arrays
  25290. } else if(obj.length !== undefined) {
  25291. for(i = 0, len = obj.length; i < len; i++) {
  25292. if(iterator.call(context, obj[i], i, obj) === false) {
  25293. return;
  25294. }
  25295. }
  25296. // objects
  25297. } else {
  25298. for(i in obj) {
  25299. if(obj.hasOwnProperty(i) &&
  25300. iterator.call(context, obj[i], i, obj) === false) {
  25301. return;
  25302. }
  25303. }
  25304. }
  25305. },
  25306. /**
  25307. * find if a string contains the string using indexOf
  25308. * @method inStr
  25309. * @param {String} src
  25310. * @param {String} find
  25311. * @return {Boolean} found
  25312. */
  25313. inStr: function inStr(src, find) {
  25314. return src.indexOf(find) > -1;
  25315. },
  25316. /**
  25317. * find if a array contains the object using indexOf or a simple polyfill
  25318. * @method inArray
  25319. * @param {String} src
  25320. * @param {String} find
  25321. * @return {Boolean|Number} false when not found, or the index
  25322. */
  25323. inArray: function inArray(src, find) {
  25324. if(src.indexOf) {
  25325. var index = src.indexOf(find);
  25326. return (index === -1) ? false : index;
  25327. } else {
  25328. for(var i = 0, len = src.length; i < len; i++) {
  25329. if(src[i] === find) {
  25330. return i;
  25331. }
  25332. }
  25333. return false;
  25334. }
  25335. },
  25336. /**
  25337. * convert an array-like object (`arguments`, `touchlist`) to an array
  25338. * @method toArray
  25339. * @param {Object} obj
  25340. * @return {Array}
  25341. */
  25342. toArray: function toArray(obj) {
  25343. return Array.prototype.slice.call(obj, 0);
  25344. },
  25345. /**
  25346. * find if a node is in the given parent
  25347. * @method hasParent
  25348. * @param {HTMLElement} node
  25349. * @param {HTMLElement} parent
  25350. * @return {Boolean} found
  25351. */
  25352. hasParent: function hasParent(node, parent) {
  25353. while(node) {
  25354. if(node == parent) {
  25355. return true;
  25356. }
  25357. node = node.parentNode;
  25358. }
  25359. return false;
  25360. },
  25361. /**
  25362. * get the center of all the touches
  25363. * @method getCenter
  25364. * @param {Array} touches
  25365. * @return {Object} center contains `pageX`, `pageY`, `clientX` and `clientY` properties
  25366. */
  25367. getCenter: function getCenter(touches) {
  25368. var pageX = [],
  25369. pageY = [],
  25370. clientX = [],
  25371. clientY = [],
  25372. min = Math.min,
  25373. max = Math.max;
  25374. // no need to loop when only one touch
  25375. if(touches.length === 1) {
  25376. return {
  25377. pageX: touches[0].pageX,
  25378. pageY: touches[0].pageY,
  25379. clientX: touches[0].clientX,
  25380. clientY: touches[0].clientY
  25381. };
  25382. }
  25383. Utils.each(touches, function(touch) {
  25384. pageX.push(touch.pageX);
  25385. pageY.push(touch.pageY);
  25386. clientX.push(touch.clientX);
  25387. clientY.push(touch.clientY);
  25388. });
  25389. return {
  25390. pageX: (min.apply(Math, pageX) + max.apply(Math, pageX)) / 2,
  25391. pageY: (min.apply(Math, pageY) + max.apply(Math, pageY)) / 2,
  25392. clientX: (min.apply(Math, clientX) + max.apply(Math, clientX)) / 2,
  25393. clientY: (min.apply(Math, clientY) + max.apply(Math, clientY)) / 2
  25394. };
  25395. },
  25396. /**
  25397. * calculate the velocity between two points. unit is in px per ms.
  25398. * @method getVelocity
  25399. * @param {Number} deltaTime
  25400. * @param {Number} deltaX
  25401. * @param {Number} deltaY
  25402. * @return {Object} velocity `x` and `y`
  25403. */
  25404. getVelocity: function getVelocity(deltaTime, deltaX, deltaY) {
  25405. return {
  25406. x: Math.abs(deltaX / deltaTime) || 0,
  25407. y: Math.abs(deltaY / deltaTime) || 0
  25408. };
  25409. },
  25410. /**
  25411. * calculate the angle between two coordinates
  25412. * @method getAngle
  25413. * @param {Touch} touch1
  25414. * @param {Touch} touch2
  25415. * @return {Number} angle
  25416. */
  25417. getAngle: function getAngle(touch1, touch2) {
  25418. var x = touch2.clientX - touch1.clientX,
  25419. y = touch2.clientY - touch1.clientY;
  25420. return Math.atan2(y, x) * 180 / Math.PI;
  25421. },
  25422. /**
  25423. * do a small comparision to get the direction between two touches.
  25424. * @method getDirection
  25425. * @param {Touch} touch1
  25426. * @param {Touch} touch2
  25427. * @return {String} direction matches `DIRECTION_LEFT|RIGHT|UP|DOWN`
  25428. */
  25429. getDirection: function getDirection(touch1, touch2) {
  25430. var x = Math.abs(touch1.clientX - touch2.clientX),
  25431. y = Math.abs(touch1.clientY - touch2.clientY);
  25432. if(x >= y) {
  25433. return touch1.clientX - touch2.clientX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
  25434. }
  25435. return touch1.clientY - touch2.clientY > 0 ? DIRECTION_UP : DIRECTION_DOWN;
  25436. },
  25437. /**
  25438. * calculate the distance between two touches
  25439. * @method getDistance
  25440. * @param {Touch}touch1
  25441. * @param {Touch} touch2
  25442. * @return {Number} distance
  25443. */
  25444. getDistance: function getDistance(touch1, touch2) {
  25445. var x = touch2.clientX - touch1.clientX,
  25446. y = touch2.clientY - touch1.clientY;
  25447. return Math.sqrt((x * x) + (y * y));
  25448. },
  25449. /**
  25450. * calculate the scale factor between two touchLists
  25451. * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
  25452. * @method getScale
  25453. * @param {Array} start array of touches
  25454. * @param {Array} end array of touches
  25455. * @return {Number} scale
  25456. */
  25457. getScale: function getScale(start, end) {
  25458. // need two fingers...
  25459. if(start.length >= 2 && end.length >= 2) {
  25460. return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]);
  25461. }
  25462. return 1;
  25463. },
  25464. /**
  25465. * calculate the rotation degrees between two touchLists
  25466. * @method getRotation
  25467. * @param {Array} start array of touches
  25468. * @param {Array} end array of touches
  25469. * @return {Number} rotation
  25470. */
  25471. getRotation: function getRotation(start, end) {
  25472. // need two fingers
  25473. if(start.length >= 2 && end.length >= 2) {
  25474. return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]);
  25475. }
  25476. return 0;
  25477. },
  25478. /**
  25479. * find out if the direction is vertical *
  25480. * @method isVertical
  25481. * @param {String} direction matches `DIRECTION_UP|DOWN`
  25482. * @return {Boolean} is_vertical
  25483. */
  25484. isVertical: function isVertical(direction) {
  25485. return direction == DIRECTION_UP || direction == DIRECTION_DOWN;
  25486. },
  25487. /**
  25488. * set css properties with their prefixes
  25489. * @param {HTMLElement} element
  25490. * @param {String} prop
  25491. * @param {String} value
  25492. * @param {Boolean} [toggle=true]
  25493. * @return {Boolean}
  25494. */
  25495. setPrefixedCss: function setPrefixedCss(element, prop, value, toggle) {
  25496. var prefixes = ['', 'Webkit', 'Moz', 'O', 'ms'];
  25497. prop = Utils.toCamelCase(prop);
  25498. for(var i = 0; i < prefixes.length; i++) {
  25499. var p = prop;
  25500. // prefixes
  25501. if(prefixes[i]) {
  25502. p = prefixes[i] + p.slice(0, 1).toUpperCase() + p.slice(1);
  25503. }
  25504. // test the style
  25505. if(p in element.style) {
  25506. element.style[p] = (toggle == null || toggle) && value || '';
  25507. break;
  25508. }
  25509. }
  25510. },
  25511. /**
  25512. * toggle browser default behavior by setting css properties.
  25513. * `userSelect='none'` also sets `element.onselectstart` to false
  25514. * `userDrag='none'` also sets `element.ondragstart` to false
  25515. *
  25516. * @method toggleBehavior
  25517. * @param {HtmlElement} element
  25518. * @param {Object} props
  25519. * @param {Boolean} [toggle=true]
  25520. */
  25521. toggleBehavior: function toggleBehavior(element, props, toggle) {
  25522. if(!props || !element || !element.style) {
  25523. return;
  25524. }
  25525. // set the css properties
  25526. Utils.each(props, function(value, prop) {
  25527. Utils.setPrefixedCss(element, prop, value, toggle);
  25528. });
  25529. var falseFn = toggle && function() {
  25530. return false;
  25531. };
  25532. // also the disable onselectstart
  25533. if(props.userSelect == 'none') {
  25534. element.onselectstart = falseFn;
  25535. }
  25536. // and disable ondragstart
  25537. if(props.userDrag == 'none') {
  25538. element.ondragstart = falseFn;
  25539. }
  25540. },
  25541. /**
  25542. * convert a string with underscores to camelCase
  25543. * so prevent_default becomes preventDefault
  25544. * @param {String} str
  25545. * @return {String} camelCaseStr
  25546. */
  25547. toCamelCase: function toCamelCase(str) {
  25548. return str.replace(/[_-]([a-z])/g, function(s) {
  25549. return s[1].toUpperCase();
  25550. });
  25551. }
  25552. };
  25553. /**
  25554. * @module hammer
  25555. */
  25556. /**
  25557. * @class Event
  25558. * @static
  25559. */
  25560. var Event = Hammer.event = {
  25561. /**
  25562. * when touch events have been fired, this is true
  25563. * this is used to stop mouse events
  25564. * @property prevent_mouseevents
  25565. * @private
  25566. * @type {Boolean}
  25567. */
  25568. preventMouseEvents: false,
  25569. /**
  25570. * if EVENT_START has been fired
  25571. * @property started
  25572. * @private
  25573. * @type {Boolean}
  25574. */
  25575. started: false,
  25576. /**
  25577. * when the mouse is hold down, this is true
  25578. * @property should_detect
  25579. * @private
  25580. * @type {Boolean}
  25581. */
  25582. shouldDetect: false,
  25583. /**
  25584. * simple event binder with a hook and support for multiple types
  25585. * @method on
  25586. * @param {HTMLElement} element
  25587. * @param {String} type
  25588. * @param {Function} handler
  25589. * @param {Function} [hook]
  25590. * @param {Object} hook.type
  25591. */
  25592. on: function on(element, type, handler, hook) {
  25593. var types = type.split(' ');
  25594. Utils.each(types, function(type) {
  25595. Utils.on(element, type, handler);
  25596. hook && hook(type);
  25597. });
  25598. },
  25599. /**
  25600. * simple event unbinder with a hook and support for multiple types
  25601. * @method off
  25602. * @param {HTMLElement} element
  25603. * @param {String} type
  25604. * @param {Function} handler
  25605. * @param {Function} [hook]
  25606. * @param {Object} hook.type
  25607. */
  25608. off: function off(element, type, handler, hook) {
  25609. var types = type.split(' ');
  25610. Utils.each(types, function(type) {
  25611. Utils.off(element, type, handler);
  25612. hook && hook(type);
  25613. });
  25614. },
  25615. /**
  25616. * the core touch event handler.
  25617. * this finds out if we should to detect gestures
  25618. * @method onTouch
  25619. * @param {HTMLElement} element
  25620. * @param {String} eventType matches `EVENT_START|MOVE|END`
  25621. * @param {Function} handler
  25622. * @return onTouchHandler {Function} the core event handler
  25623. */
  25624. onTouch: function onTouch(element, eventType, handler) {
  25625. var self = this;
  25626. var onTouchHandler = function onTouchHandler(ev) {
  25627. var srcType = ev.type.toLowerCase(),
  25628. isPointer = Hammer.HAS_POINTEREVENTS,
  25629. isMouse = Utils.inStr(srcType, 'mouse'),
  25630. triggerType;
  25631. // if we are in a mouseevent, but there has been a touchevent triggered in this session
  25632. // we want to do nothing. simply break out of the event.
  25633. if(isMouse && self.preventMouseEvents) {
  25634. return;
  25635. // mousebutton must be down
  25636. } else if(isMouse && eventType == EVENT_START && ev.button === 0) {
  25637. self.preventMouseEvents = false;
  25638. self.shouldDetect = true;
  25639. } else if(isPointer && eventType == EVENT_START) {
  25640. self.shouldDetect = (ev.buttons === 1 || PointerEvent.matchType(POINTER_TOUCH, ev));
  25641. // just a valid start event, but no mouse
  25642. } else if(!isMouse && eventType == EVENT_START) {
  25643. self.preventMouseEvents = true;
  25644. self.shouldDetect = true;
  25645. }
  25646. // update the pointer event before entering the detection
  25647. if(isPointer && eventType != EVENT_END) {
  25648. PointerEvent.updatePointer(eventType, ev);
  25649. }
  25650. // we are in a touch/down state, so allowed detection of gestures
  25651. if(self.shouldDetect) {
  25652. triggerType = self.doDetect.call(self, ev, eventType, element, handler);
  25653. }
  25654. // ...and we are done with the detection
  25655. // so reset everything to start each detection totally fresh
  25656. if(triggerType == EVENT_END) {
  25657. self.preventMouseEvents = false;
  25658. self.shouldDetect = false;
  25659. PointerEvent.reset();
  25660. // update the pointerevent object after the detection
  25661. }
  25662. if(isPointer && eventType == EVENT_END) {
  25663. PointerEvent.updatePointer(eventType, ev);
  25664. }
  25665. };
  25666. this.on(element, EVENT_TYPES[eventType], onTouchHandler);
  25667. return onTouchHandler;
  25668. },
  25669. /**
  25670. * the core detection method
  25671. * this finds out what hammer-touch-events to trigger
  25672. * @method doDetect
  25673. * @param {Object} ev
  25674. * @param {String} eventType matches `EVENT_START|MOVE|END`
  25675. * @param {HTMLElement} element
  25676. * @param {Function} handler
  25677. * @return {String} triggerType matches `EVENT_START|MOVE|END`
  25678. */
  25679. doDetect: function doDetect(ev, eventType, element, handler) {
  25680. var touchList = this.getTouchList(ev, eventType);
  25681. var touchListLength = touchList.length;
  25682. var triggerType = eventType;
  25683. var triggerChange = touchList.trigger; // used by fakeMultitouch plugin
  25684. var changedLength = touchListLength;
  25685. // at each touchstart-like event we want also want to trigger a TOUCH event...
  25686. if(eventType == EVENT_START) {
  25687. triggerChange = EVENT_TOUCH;
  25688. // ...the same for a touchend-like event
  25689. } else if(eventType == EVENT_END) {
  25690. triggerChange = EVENT_RELEASE;
  25691. // keep track of how many touches have been removed
  25692. changedLength = touchList.length - ((ev.changedTouches) ? ev.changedTouches.length : 1);
  25693. }
  25694. // after there are still touches on the screen,
  25695. // we just want to trigger a MOVE event. so change the START or END to a MOVE
  25696. // but only after detection has been started, the first time we actualy want a START
  25697. if(changedLength > 0 && this.started) {
  25698. triggerType = EVENT_MOVE;
  25699. }
  25700. // detection has been started, we keep track of this, see above
  25701. this.started = true;
  25702. // generate some event data, some basic information
  25703. var evData = this.collectEventData(element, triggerType, touchList, ev);
  25704. // trigger the triggerType event before the change (TOUCH, RELEASE) events
  25705. // but the END event should be at last
  25706. if(eventType != EVENT_END) {
  25707. handler.call(Detection, evData);
  25708. }
  25709. // trigger a change (TOUCH, RELEASE) event, this means the length of the touches changed
  25710. if(triggerChange) {
  25711. evData.changedLength = changedLength;
  25712. evData.eventType = triggerChange;
  25713. handler.call(Detection, evData);
  25714. evData.eventType = triggerType;
  25715. delete evData.changedLength;
  25716. }
  25717. // trigger the END event
  25718. if(triggerType == EVENT_END) {
  25719. handler.call(Detection, evData);
  25720. // ...and we are done with the detection
  25721. // so reset everything to start each detection totally fresh
  25722. this.started = false;
  25723. }
  25724. return triggerType;
  25725. },
  25726. /**
  25727. * we have different events for each device/browser
  25728. * determine what we need and set them in the EVENT_TYPES constant
  25729. * the `onTouch` method is bind to these properties.
  25730. * @method determineEventTypes
  25731. * @return {Object} events
  25732. */
  25733. determineEventTypes: function determineEventTypes() {
  25734. var types;
  25735. if(Hammer.HAS_POINTEREVENTS) {
  25736. if(window.PointerEvent) {
  25737. types = [
  25738. 'pointerdown',
  25739. 'pointermove',
  25740. 'pointerup pointercancel lostpointercapture'
  25741. ];
  25742. } else {
  25743. types = [
  25744. 'MSPointerDown',
  25745. 'MSPointerMove',
  25746. 'MSPointerUp MSPointerCancel MSLostPointerCapture'
  25747. ];
  25748. }
  25749. } else if(Hammer.NO_MOUSEEVENTS) {
  25750. types = [
  25751. 'touchstart',
  25752. 'touchmove',
  25753. 'touchend touchcancel'
  25754. ];
  25755. } else {
  25756. types = [
  25757. 'touchstart mousedown',
  25758. 'touchmove mousemove',
  25759. 'touchend touchcancel mouseup'
  25760. ];
  25761. }
  25762. EVENT_TYPES[EVENT_START] = types[0];
  25763. EVENT_TYPES[EVENT_MOVE] = types[1];
  25764. EVENT_TYPES[EVENT_END] = types[2];
  25765. return EVENT_TYPES;
  25766. },
  25767. /**
  25768. * create touchList depending on the event
  25769. * @method getTouchList
  25770. * @param {Object} ev
  25771. * @param {String} eventType
  25772. * @return {Array} touches
  25773. */
  25774. getTouchList: function getTouchList(ev, eventType) {
  25775. // get the fake pointerEvent touchlist
  25776. if(Hammer.HAS_POINTEREVENTS) {
  25777. return PointerEvent.getTouchList();
  25778. }
  25779. // get the touchlist
  25780. if(ev.touches) {
  25781. if(eventType == EVENT_MOVE) {
  25782. return ev.touches;
  25783. }
  25784. var identifiers = [];
  25785. var concat = [].concat(Utils.toArray(ev.touches), Utils.toArray(ev.changedTouches));
  25786. var touchList = [];
  25787. Utils.each(concat, function(touch) {
  25788. if(Utils.inArray(identifiers, touch.identifier) === false) {
  25789. touchList.push(touch);
  25790. }
  25791. identifiers.push(touch.identifier);
  25792. });
  25793. return touchList;
  25794. }
  25795. // make fake touchList from mouse position
  25796. ev.identifier = 1;
  25797. return [ev];
  25798. },
  25799. /**
  25800. * collect basic event data
  25801. * @method collectEventData
  25802. * @param {HTMLElement} element
  25803. * @param {String} eventType matches `EVENT_START|MOVE|END`
  25804. * @param {Array} touches
  25805. * @param {Object} ev
  25806. * @return {Object} ev
  25807. */
  25808. collectEventData: function collectEventData(element, eventType, touches, ev) {
  25809. // find out pointerType
  25810. var pointerType = POINTER_TOUCH;
  25811. if(Utils.inStr(ev.type, 'mouse') || PointerEvent.matchType(POINTER_MOUSE, ev)) {
  25812. pointerType = POINTER_MOUSE;
  25813. } else if(PointerEvent.matchType(POINTER_PEN, ev)) {
  25814. pointerType = POINTER_PEN;
  25815. }
  25816. return {
  25817. center: Utils.getCenter(touches),
  25818. timeStamp: Date.now(),
  25819. target: ev.target,
  25820. touches: touches,
  25821. eventType: eventType,
  25822. pointerType: pointerType,
  25823. srcEvent: ev,
  25824. /**
  25825. * prevent the browser default actions
  25826. * mostly used to disable scrolling of the browser
  25827. */
  25828. preventDefault: function() {
  25829. var srcEvent = this.srcEvent;
  25830. srcEvent.preventManipulation && srcEvent.preventManipulation();
  25831. srcEvent.preventDefault && srcEvent.preventDefault();
  25832. },
  25833. /**
  25834. * stop bubbling the event up to its parents
  25835. */
  25836. stopPropagation: function() {
  25837. this.srcEvent.stopPropagation();
  25838. },
  25839. /**
  25840. * immediately stop gesture detection
  25841. * might be useful after a swipe was detected
  25842. * @return {*}
  25843. */
  25844. stopDetect: function() {
  25845. return Detection.stopDetect();
  25846. }
  25847. };
  25848. }
  25849. };
  25850. /**
  25851. * @module hammer
  25852. *
  25853. * @class PointerEvent
  25854. * @static
  25855. */
  25856. var PointerEvent = Hammer.PointerEvent = {
  25857. /**
  25858. * holds all pointers, by `identifier`
  25859. * @property pointers
  25860. * @type {Object}
  25861. */
  25862. pointers: {},
  25863. /**
  25864. * get the pointers as an array
  25865. * @method getTouchList
  25866. * @return {Array} touchlist
  25867. */
  25868. getTouchList: function getTouchList() {
  25869. var touchlist = [];
  25870. // we can use forEach since pointerEvents only is in IE10
  25871. Utils.each(this.pointers, function(pointer) {
  25872. touchlist.push(pointer);
  25873. });
  25874. return touchlist;
  25875. },
  25876. /**
  25877. * update the position of a pointer
  25878. * @method updatePointer
  25879. * @param {String} eventType matches `EVENT_START|MOVE|END`
  25880. * @param {Object} pointerEvent
  25881. */
  25882. updatePointer: function updatePointer(eventType, pointerEvent) {
  25883. if(eventType == EVENT_END || (eventType != EVENT_END && pointerEvent.buttons !== 1)) {
  25884. delete this.pointers[pointerEvent.pointerId];
  25885. } else {
  25886. pointerEvent.identifier = pointerEvent.pointerId;
  25887. this.pointers[pointerEvent.pointerId] = pointerEvent;
  25888. }
  25889. },
  25890. /**
  25891. * check if ev matches pointertype
  25892. * @method matchType
  25893. * @param {String} pointerType matches `POINTER_MOUSE|TOUCH|PEN`
  25894. * @param {PointerEvent} ev
  25895. */
  25896. matchType: function matchType(pointerType, ev) {
  25897. if(!ev.pointerType) {
  25898. return false;
  25899. }
  25900. var pt = ev.pointerType,
  25901. types = {};
  25902. types[POINTER_MOUSE] = (pt === (ev.MSPOINTER_TYPE_MOUSE || POINTER_MOUSE));
  25903. types[POINTER_TOUCH] = (pt === (ev.MSPOINTER_TYPE_TOUCH || POINTER_TOUCH));
  25904. types[POINTER_PEN] = (pt === (ev.MSPOINTER_TYPE_PEN || POINTER_PEN));
  25905. return types[pointerType];
  25906. },
  25907. /**
  25908. * reset the stored pointers
  25909. * @method reset
  25910. */
  25911. reset: function resetList() {
  25912. this.pointers = {};
  25913. }
  25914. };
  25915. /**
  25916. * @module hammer
  25917. *
  25918. * @class Detection
  25919. * @static
  25920. */
  25921. var Detection = Hammer.detection = {
  25922. // contains all registred Hammer.gestures in the correct order
  25923. gestures: [],
  25924. // data of the current Hammer.gesture detection session
  25925. current: null,
  25926. // the previous Hammer.gesture session data
  25927. // is a full clone of the previous gesture.current object
  25928. previous: null,
  25929. // when this becomes true, no gestures are fired
  25930. stopped: false,
  25931. /**
  25932. * start Hammer.gesture detection
  25933. * @method startDetect
  25934. * @param {Hammer.Instance} inst
  25935. * @param {Object} eventData
  25936. */
  25937. startDetect: function startDetect(inst, eventData) {
  25938. // already busy with a Hammer.gesture detection on an element
  25939. if(this.current) {
  25940. return;
  25941. }
  25942. this.stopped = false;
  25943. // holds current session
  25944. this.current = {
  25945. inst: inst, // reference to HammerInstance we're working for
  25946. startEvent: Utils.extend({}, eventData), // start eventData for distances, timing etc
  25947. lastEvent: false, // last eventData
  25948. lastCalcEvent: false, // last eventData for calculations.
  25949. futureCalcEvent: false, // last eventData for calculations.
  25950. lastCalcData: {}, // last lastCalcData
  25951. name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
  25952. };
  25953. this.detect(eventData);
  25954. },
  25955. /**
  25956. * Hammer.gesture detection
  25957. * @method detect
  25958. * @param {Object} eventData
  25959. * @return {any}
  25960. */
  25961. detect: function detect(eventData) {
  25962. if(!this.current || this.stopped) {
  25963. return;
  25964. }
  25965. // extend event data with calculations about scale, distance etc
  25966. eventData = this.extendEventData(eventData);
  25967. // hammer instance and instance options
  25968. var inst = this.current.inst,
  25969. instOptions = inst.options;
  25970. // call Hammer.gesture handlers
  25971. Utils.each(this.gestures, function triggerGesture(gesture) {
  25972. // only when the instance options have enabled this gesture
  25973. if(!this.stopped && inst.enabled && instOptions[gesture.name]) {
  25974. gesture.handler.call(gesture, eventData, inst);
  25975. }
  25976. }, this);
  25977. // store as previous event event
  25978. if(this.current) {
  25979. this.current.lastEvent = eventData;
  25980. }
  25981. if(eventData.eventType == EVENT_END) {
  25982. this.stopDetect();
  25983. }
  25984. return eventData;
  25985. },
  25986. /**
  25987. * clear the Hammer.gesture vars
  25988. * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
  25989. * to stop other Hammer.gestures from being fired
  25990. * @method stopDetect
  25991. */
  25992. stopDetect: function stopDetect() {
  25993. // clone current data to the store as the previous gesture
  25994. // used for the double tap gesture, since this is an other gesture detect session
  25995. this.previous = Utils.extend({}, this.current);
  25996. // reset the current
  25997. this.current = null;
  25998. this.stopped = true;
  25999. },
  26000. /**
  26001. * calculate velocity, angle and direction
  26002. * @method getVelocityData
  26003. * @param {Object} ev
  26004. * @param {Object} center
  26005. * @param {Number} deltaTime
  26006. * @param {Number} deltaX
  26007. * @param {Number} deltaY
  26008. */
  26009. getCalculatedData: function getCalculatedData(ev, center, deltaTime, deltaX, deltaY) {
  26010. var cur = this.current,
  26011. recalc = false,
  26012. calcEv = cur.lastCalcEvent,
  26013. calcData = cur.lastCalcData;
  26014. if(calcEv && ev.timeStamp - calcEv.timeStamp > Hammer.CALCULATE_INTERVAL) {
  26015. center = calcEv.center;
  26016. deltaTime = ev.timeStamp - calcEv.timeStamp;
  26017. deltaX = ev.center.clientX - calcEv.center.clientX;
  26018. deltaY = ev.center.clientY - calcEv.center.clientY;
  26019. recalc = true;
  26020. }
  26021. if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
  26022. cur.futureCalcEvent = ev;
  26023. }
  26024. if(!cur.lastCalcEvent || recalc) {
  26025. calcData.velocity = Utils.getVelocity(deltaTime, deltaX, deltaY);
  26026. calcData.angle = Utils.getAngle(center, ev.center);
  26027. calcData.direction = Utils.getDirection(center, ev.center);
  26028. cur.lastCalcEvent = cur.futureCalcEvent || ev;
  26029. cur.futureCalcEvent = ev;
  26030. }
  26031. ev.velocityX = calcData.velocity.x;
  26032. ev.velocityY = calcData.velocity.y;
  26033. ev.interimAngle = calcData.angle;
  26034. ev.interimDirection = calcData.direction;
  26035. },
  26036. /**
  26037. * extend eventData for Hammer.gestures
  26038. * @method extendEventData
  26039. * @param {Object} ev
  26040. * @return {Object} ev
  26041. */
  26042. extendEventData: function extendEventData(ev) {
  26043. var cur = this.current,
  26044. startEv = cur.startEvent,
  26045. lastEv = cur.lastEvent || startEv;
  26046. // update the start touchlist to calculate the scale/rotation
  26047. if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
  26048. startEv.touches = [];
  26049. Utils.each(ev.touches, function(touch) {
  26050. startEv.touches.push({
  26051. clientX: touch.clientX,
  26052. clientY: touch.clientY
  26053. });
  26054. });
  26055. }
  26056. var deltaTime = ev.timeStamp - startEv.timeStamp,
  26057. deltaX = ev.center.clientX - startEv.center.clientX,
  26058. deltaY = ev.center.clientY - startEv.center.clientY;
  26059. this.getCalculatedData(ev, lastEv.center, deltaTime, deltaX, deltaY);
  26060. Utils.extend(ev, {
  26061. startEvent: startEv,
  26062. deltaTime: deltaTime,
  26063. deltaX: deltaX,
  26064. deltaY: deltaY,
  26065. distance: Utils.getDistance(startEv.center, ev.center),
  26066. angle: Utils.getAngle(startEv.center, ev.center),
  26067. direction: Utils.getDirection(startEv.center, ev.center),
  26068. scale: Utils.getScale(startEv.touches, ev.touches),
  26069. rotation: Utils.getRotation(startEv.touches, ev.touches)
  26070. });
  26071. return ev;
  26072. },
  26073. /**
  26074. * register new gesture
  26075. * @method register
  26076. * @param {Object} gesture object, see `gestures/` for documentation
  26077. * @return {Array} gestures
  26078. */
  26079. register: function register(gesture) {
  26080. // add an enable gesture options if there is no given
  26081. var options = gesture.defaults || {};
  26082. if(options[gesture.name] === undefined) {
  26083. options[gesture.name] = true;
  26084. }
  26085. // extend Hammer default options with the Hammer.gesture options
  26086. Utils.extend(Hammer.defaults, options, true);
  26087. // set its index
  26088. gesture.index = gesture.index || 1000;
  26089. // add Hammer.gesture to the list
  26090. this.gestures.push(gesture);
  26091. // sort the list by index
  26092. this.gestures.sort(function(a, b) {
  26093. if(a.index < b.index) {
  26094. return -1;
  26095. }
  26096. if(a.index > b.index) {
  26097. return 1;
  26098. }
  26099. return 0;
  26100. });
  26101. return this.gestures;
  26102. }
  26103. };
  26104. /**
  26105. * @module hammer
  26106. */
  26107. /**
  26108. * create new hammer instance
  26109. * all methods should return the instance itself, so it is chainable.
  26110. *
  26111. * @class Instance
  26112. * @constructor
  26113. * @param {HTMLElement} element
  26114. * @param {Object} [options={}] options are merged with `Hammer.defaults`
  26115. * @return {Hammer.Instance}
  26116. */
  26117. Hammer.Instance = function(element, options) {
  26118. var self = this;
  26119. // setup HammerJS window events and register all gestures
  26120. // this also sets up the default options
  26121. setup();
  26122. /**
  26123. * @property element
  26124. * @type {HTMLElement}
  26125. */
  26126. this.element = element;
  26127. /**
  26128. * @property enabled
  26129. * @type {Boolean}
  26130. * @protected
  26131. */
  26132. this.enabled = true;
  26133. /**
  26134. * options, merged with the defaults
  26135. * options with an _ are converted to camelCase
  26136. * @property options
  26137. * @type {Object}
  26138. */
  26139. Utils.each(options, function(value, name) {
  26140. delete options[name];
  26141. options[Utils.toCamelCase(name)] = value;
  26142. });
  26143. this.options = Utils.extend(Utils.extend({}, Hammer.defaults), options || {});
  26144. // add some css to the element to prevent the browser from doing its native behavoir
  26145. if(this.options.behavior) {
  26146. Utils.toggleBehavior(this.element, this.options.behavior, true);
  26147. }
  26148. /**
  26149. * event start handler on the element to start the detection
  26150. * @property eventStartHandler
  26151. * @type {Object}
  26152. */
  26153. this.eventStartHandler = Event.onTouch(element, EVENT_START, function(ev) {
  26154. if(self.enabled && ev.eventType == EVENT_START) {
  26155. Detection.startDetect(self, ev);
  26156. } else if(ev.eventType == EVENT_TOUCH) {
  26157. Detection.detect(ev);
  26158. }
  26159. });
  26160. /**
  26161. * keep a list of user event handlers which needs to be removed when calling 'dispose'
  26162. * @property eventHandlers
  26163. * @type {Array}
  26164. */
  26165. this.eventHandlers = [];
  26166. };
  26167. Hammer.Instance.prototype = {
  26168. /**
  26169. * bind events to the instance
  26170. * @method on
  26171. * @chainable
  26172. * @param {String} gestures multiple gestures by splitting with a space
  26173. * @param {Function} handler
  26174. * @param {Object} handler.ev event object
  26175. */
  26176. on: function onEvent(gestures, handler) {
  26177. var self = this;
  26178. Event.on(self.element, gestures, handler, function(type) {
  26179. self.eventHandlers.push({ gesture: type, handler: handler });
  26180. });
  26181. return self;
  26182. },
  26183. /**
  26184. * unbind events to the instance
  26185. * @method off
  26186. * @chainable
  26187. * @param {String} gestures
  26188. * @param {Function} handler
  26189. */
  26190. off: function offEvent(gestures, handler) {
  26191. var self = this;
  26192. Event.off(self.element, gestures, handler, function(type) {
  26193. var index = Utils.inArray({ gesture: type, handler: handler });
  26194. if(index !== false) {
  26195. self.eventHandlers.splice(index, 1);
  26196. }
  26197. });
  26198. return self;
  26199. },
  26200. /**
  26201. * trigger gesture event
  26202. * @method trigger
  26203. * @chainable
  26204. * @param {String} gesture
  26205. * @param {Object} [eventData]
  26206. */
  26207. trigger: function triggerEvent(gesture, eventData) {
  26208. // optional
  26209. if(!eventData) {
  26210. eventData = {};
  26211. }
  26212. // create DOM event
  26213. var event = Hammer.DOCUMENT.createEvent('Event');
  26214. event.initEvent(gesture, true, true);
  26215. event.gesture = eventData;
  26216. // trigger on the target if it is in the instance element,
  26217. // this is for event delegation tricks
  26218. var element = this.element;
  26219. if(Utils.hasParent(eventData.target, element)) {
  26220. element = eventData.target;
  26221. }
  26222. element.dispatchEvent(event);
  26223. return this;
  26224. },
  26225. /**
  26226. * enable of disable hammer.js detection
  26227. * @method enable
  26228. * @chainable
  26229. * @param {Boolean} state
  26230. */
  26231. enable: function enable(state) {
  26232. this.enabled = state;
  26233. return this;
  26234. },
  26235. /**
  26236. * dispose this hammer instance
  26237. * @method dispose
  26238. * @return {Null}
  26239. */
  26240. dispose: function dispose() {
  26241. var i, eh;
  26242. // undo all changes made by stop_browser_behavior
  26243. Utils.toggleBehavior(this.element, this.options.behavior, false);
  26244. // unbind all custom event handlers
  26245. for(i = -1; (eh = this.eventHandlers[++i]);) {
  26246. Utils.off(this.element, eh.gesture, eh.handler);
  26247. }
  26248. this.eventHandlers = [];
  26249. // unbind the start event listener
  26250. Event.off(this.element, EVENT_TYPES[EVENT_START], this.eventStartHandler);
  26251. return null;
  26252. }
  26253. };
  26254. /**
  26255. * @module gestures
  26256. */
  26257. /**
  26258. * Move with x fingers (default 1) around on the page.
  26259. * Preventing the default browser behavior is a good way to improve feel and working.
  26260. * ````
  26261. * hammertime.on("drag", function(ev) {
  26262. * console.log(ev);
  26263. * ev.gesture.preventDefault();
  26264. * });
  26265. * ````
  26266. *
  26267. * @class Drag
  26268. * @static
  26269. */
  26270. /**
  26271. * @event drag
  26272. * @param {Object} ev
  26273. */
  26274. /**
  26275. * @event dragstart
  26276. * @param {Object} ev
  26277. */
  26278. /**
  26279. * @event dragend
  26280. * @param {Object} ev
  26281. */
  26282. /**
  26283. * @event drapleft
  26284. * @param {Object} ev
  26285. */
  26286. /**
  26287. * @event dragright
  26288. * @param {Object} ev
  26289. */
  26290. /**
  26291. * @event dragup
  26292. * @param {Object} ev
  26293. */
  26294. /**
  26295. * @event dragdown
  26296. * @param {Object} ev
  26297. */
  26298. /**
  26299. * @param {String} name
  26300. */
  26301. (function(name) {
  26302. var triggered = false;
  26303. function dragGesture(ev, inst) {
  26304. var cur = Detection.current;
  26305. // max touches
  26306. if(inst.options.dragMaxTouches > 0 &&
  26307. ev.touches.length > inst.options.dragMaxTouches) {
  26308. return;
  26309. }
  26310. switch(ev.eventType) {
  26311. case EVENT_START:
  26312. triggered = false;
  26313. break;
  26314. case EVENT_MOVE:
  26315. // when the distance we moved is too small we skip this gesture
  26316. // or we can be already in dragging
  26317. if(ev.distance < inst.options.dragMinDistance &&
  26318. cur.name != name) {
  26319. return;
  26320. }
  26321. var startCenter = cur.startEvent.center;
  26322. // we are dragging!
  26323. if(cur.name != name) {
  26324. cur.name = name;
  26325. if(inst.options.dragDistanceCorrection && ev.distance > 0) {
  26326. // When a drag is triggered, set the event center to dragMinDistance pixels from the original event center.
  26327. // Without this correction, the dragged distance would jumpstart at dragMinDistance pixels instead of at 0.
  26328. // It might be useful to save the original start point somewhere
  26329. var factor = Math.abs(inst.options.dragMinDistance / ev.distance);
  26330. startCenter.pageX += ev.deltaX * factor;
  26331. startCenter.pageY += ev.deltaY * factor;
  26332. startCenter.clientX += ev.deltaX * factor;
  26333. startCenter.clientY += ev.deltaY * factor;
  26334. // recalculate event data using new start point
  26335. ev = Detection.extendEventData(ev);
  26336. }
  26337. }
  26338. // lock drag to axis?
  26339. if(cur.lastEvent.dragLockToAxis ||
  26340. ( inst.options.dragLockToAxis &&
  26341. inst.options.dragLockMinDistance <= ev.distance
  26342. )) {
  26343. ev.dragLockToAxis = true;
  26344. }
  26345. // keep direction on the axis that the drag gesture started on
  26346. var lastDirection = cur.lastEvent.direction;
  26347. if(ev.dragLockToAxis && lastDirection !== ev.direction) {
  26348. if(Utils.isVertical(lastDirection)) {
  26349. ev.direction = (ev.deltaY < 0) ? DIRECTION_UP : DIRECTION_DOWN;
  26350. } else {
  26351. ev.direction = (ev.deltaX < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
  26352. }
  26353. }
  26354. // first time, trigger dragstart event
  26355. if(!triggered) {
  26356. inst.trigger(name + 'start', ev);
  26357. triggered = true;
  26358. }
  26359. // trigger events
  26360. inst.trigger(name, ev);
  26361. inst.trigger(name + ev.direction, ev);
  26362. var isVertical = Utils.isVertical(ev.direction);
  26363. // block the browser events
  26364. if((inst.options.dragBlockVertical && isVertical) ||
  26365. (inst.options.dragBlockHorizontal && !isVertical)) {
  26366. ev.preventDefault();
  26367. }
  26368. break;
  26369. case EVENT_RELEASE:
  26370. if(triggered && ev.changedLength <= inst.options.dragMaxTouches) {
  26371. inst.trigger(name + 'end', ev);
  26372. triggered = false;
  26373. }
  26374. break;
  26375. case EVENT_END:
  26376. triggered = false;
  26377. break;
  26378. }
  26379. }
  26380. Hammer.gestures.Drag = {
  26381. name: name,
  26382. index: 50,
  26383. handler: dragGesture,
  26384. defaults: {
  26385. /**
  26386. * minimal movement that have to be made before the drag event gets triggered
  26387. * @property dragMinDistance
  26388. * @type {Number}
  26389. * @default 10
  26390. */
  26391. dragMinDistance: 10,
  26392. /**
  26393. * Set dragDistanceCorrection to true to make the starting point of the drag
  26394. * be calculated from where the drag was triggered, not from where the touch started.
  26395. * Useful to avoid a jerk-starting drag, which can make fine-adjustments
  26396. * through dragging difficult, and be visually unappealing.
  26397. * @property dragDistanceCorrection
  26398. * @type {Boolean}
  26399. * @default true
  26400. */
  26401. dragDistanceCorrection: true,
  26402. /**
  26403. * set 0 for unlimited, but this can conflict with transform
  26404. * @property dragMaxTouches
  26405. * @type {Number}
  26406. * @default 1
  26407. */
  26408. dragMaxTouches: 1,
  26409. /**
  26410. * prevent default browser behavior when dragging occurs
  26411. * be careful with it, it makes the element a blocking element
  26412. * when you are using the drag gesture, it is a good practice to set this true
  26413. * @property dragBlockHorizontal
  26414. * @type {Boolean}
  26415. * @default false
  26416. */
  26417. dragBlockHorizontal: false,
  26418. /**
  26419. * same as `dragBlockHorizontal`, but for vertical movement
  26420. * @property dragBlockVertical
  26421. * @type {Boolean}
  26422. * @default false
  26423. */
  26424. dragBlockVertical: false,
  26425. /**
  26426. * dragLockToAxis keeps the drag gesture on the axis that it started on,
  26427. * It disallows vertical directions if the initial direction was horizontal, and vice versa.
  26428. * @property dragLockToAxis
  26429. * @type {Boolean}
  26430. * @default false
  26431. */
  26432. dragLockToAxis: false,
  26433. /**
  26434. * drag lock only kicks in when distance > dragLockMinDistance
  26435. * This way, locking occurs only when the distance has become large enough to reliably determine the direction
  26436. * @property dragLockMinDistance
  26437. * @type {Number}
  26438. * @default 25
  26439. */
  26440. dragLockMinDistance: 25
  26441. }
  26442. };
  26443. })('drag');
  26444. /**
  26445. * @module gestures
  26446. */
  26447. /**
  26448. * trigger a simple gesture event, so you can do anything in your handler.
  26449. * only usable if you know what your doing...
  26450. *
  26451. * @class Gesture
  26452. * @static
  26453. */
  26454. /**
  26455. * @event gesture
  26456. * @param {Object} ev
  26457. */
  26458. Hammer.gestures.Gesture = {
  26459. name: 'gesture',
  26460. index: 1337,
  26461. handler: function releaseGesture(ev, inst) {
  26462. inst.trigger(this.name, ev);
  26463. }
  26464. };
  26465. /**
  26466. * @module gestures
  26467. */
  26468. /**
  26469. * Touch stays at the same place for x time
  26470. *
  26471. * @class Hold
  26472. * @static
  26473. */
  26474. /**
  26475. * @event hold
  26476. * @param {Object} ev
  26477. */
  26478. /**
  26479. * @param {String} name
  26480. */
  26481. (function(name) {
  26482. var timer;
  26483. function holdGesture(ev, inst) {
  26484. var options = inst.options,
  26485. current = Detection.current;
  26486. switch(ev.eventType) {
  26487. case EVENT_START:
  26488. clearTimeout(timer);
  26489. // set the gesture so we can check in the timeout if it still is
  26490. current.name = name;
  26491. // set timer and if after the timeout it still is hold,
  26492. // we trigger the hold event
  26493. timer = setTimeout(function() {
  26494. if(current && current.name == name) {
  26495. inst.trigger(name, ev);
  26496. }
  26497. }, options.holdTimeout);
  26498. break;
  26499. case EVENT_MOVE:
  26500. if(ev.distance > options.holdThreshold) {
  26501. clearTimeout(timer);
  26502. }
  26503. break;
  26504. case EVENT_RELEASE:
  26505. clearTimeout(timer);
  26506. break;
  26507. }
  26508. }
  26509. Hammer.gestures.Hold = {
  26510. name: name,
  26511. index: 10,
  26512. defaults: {
  26513. /**
  26514. * @property holdTimeout
  26515. * @type {Number}
  26516. * @default 500
  26517. */
  26518. holdTimeout: 500,
  26519. /**
  26520. * movement allowed while holding
  26521. * @property holdThreshold
  26522. * @type {Number}
  26523. * @default 2
  26524. */
  26525. holdThreshold: 2
  26526. },
  26527. handler: holdGesture
  26528. };
  26529. })('hold');
  26530. /**
  26531. * @module gestures
  26532. */
  26533. /**
  26534. * when a touch is being released from the page
  26535. *
  26536. * @class Release
  26537. * @static
  26538. */
  26539. /**
  26540. * @event release
  26541. * @param {Object} ev
  26542. */
  26543. Hammer.gestures.Release = {
  26544. name: 'release',
  26545. index: Infinity,
  26546. handler: function releaseGesture(ev, inst) {
  26547. if(ev.eventType == EVENT_RELEASE) {
  26548. inst.trigger(this.name, ev);
  26549. }
  26550. }
  26551. };
  26552. /**
  26553. * @module gestures
  26554. */
  26555. /**
  26556. * triggers swipe events when the end velocity is above the threshold
  26557. * for best usage, set `preventDefault` (on the drag gesture) to `true`
  26558. * ````
  26559. * hammertime.on("dragleft swipeleft", function(ev) {
  26560. * console.log(ev);
  26561. * ev.gesture.preventDefault();
  26562. * });
  26563. * ````
  26564. *
  26565. * @class Swipe
  26566. * @static
  26567. */
  26568. /**
  26569. * @event swipe
  26570. * @param {Object} ev
  26571. */
  26572. /**
  26573. * @event swipeleft
  26574. * @param {Object} ev
  26575. */
  26576. /**
  26577. * @event swiperight
  26578. * @param {Object} ev
  26579. */
  26580. /**
  26581. * @event swipeup
  26582. * @param {Object} ev
  26583. */
  26584. /**
  26585. * @event swipedown
  26586. * @param {Object} ev
  26587. */
  26588. Hammer.gestures.Swipe = {
  26589. name: 'swipe',
  26590. index: 40,
  26591. defaults: {
  26592. /**
  26593. * @property swipeMinTouches
  26594. * @type {Number}
  26595. * @default 1
  26596. */
  26597. swipeMinTouches: 1,
  26598. /**
  26599. * @property swipeMaxTouches
  26600. * @type {Number}
  26601. * @default 1
  26602. */
  26603. swipeMaxTouches: 1,
  26604. /**
  26605. * horizontal swipe velocity
  26606. * @property swipeVelocityX
  26607. * @type {Number}
  26608. * @default 0.6
  26609. */
  26610. swipeVelocityX: 0.6,
  26611. /**
  26612. * vertical swipe velocity
  26613. * @property swipeVelocityY
  26614. * @type {Number}
  26615. * @default 0.6
  26616. */
  26617. swipeVelocityY: 0.6
  26618. },
  26619. handler: function swipeGesture(ev, inst) {
  26620. if(ev.eventType == EVENT_RELEASE) {
  26621. var touches = ev.touches.length,
  26622. options = inst.options;
  26623. // max touches
  26624. if(touches < options.swipeMinTouches ||
  26625. touches > options.swipeMaxTouches) {
  26626. return;
  26627. }
  26628. // when the distance we moved is too small we skip this gesture
  26629. // or we can be already in dragging
  26630. if(ev.velocityX > options.swipeVelocityX ||
  26631. ev.velocityY > options.swipeVelocityY) {
  26632. // trigger swipe events
  26633. inst.trigger(this.name, ev);
  26634. inst.trigger(this.name + ev.direction, ev);
  26635. }
  26636. }
  26637. }
  26638. };
  26639. /**
  26640. * @module gestures
  26641. */
  26642. /**
  26643. * Single tap and a double tap on a place
  26644. *
  26645. * @class Tap
  26646. * @static
  26647. */
  26648. /**
  26649. * @event tap
  26650. * @param {Object} ev
  26651. */
  26652. /**
  26653. * @event doubletap
  26654. * @param {Object} ev
  26655. */
  26656. /**
  26657. * @param {String} name
  26658. */
  26659. (function(name) {
  26660. var hasMoved = false;
  26661. function tapGesture(ev, inst) {
  26662. var options = inst.options,
  26663. current = Detection.current,
  26664. prev = Detection.previous,
  26665. sincePrev,
  26666. didDoubleTap;
  26667. switch(ev.eventType) {
  26668. case EVENT_START:
  26669. hasMoved = false;
  26670. break;
  26671. case EVENT_MOVE:
  26672. hasMoved = hasMoved || (ev.distance > options.tapMaxDistance);
  26673. break;
  26674. case EVENT_END:
  26675. if(!Utils.inStr(ev.srcEvent.type, 'cancel') && ev.deltaTime < options.tapMaxTime && !hasMoved) {
  26676. // previous gesture, for the double tap since these are two different gesture detections
  26677. sincePrev = prev && prev.lastEvent && ev.timeStamp - prev.lastEvent.timeStamp;
  26678. didDoubleTap = false;
  26679. // check if double tap
  26680. if(prev && prev.name == name &&
  26681. (sincePrev && sincePrev < options.doubleTapInterval) &&
  26682. ev.distance < options.doubleTapDistance) {
  26683. inst.trigger('doubletap', ev);
  26684. didDoubleTap = true;
  26685. }
  26686. // do a single tap
  26687. if(!didDoubleTap || options.tapAlways) {
  26688. current.name = name;
  26689. inst.trigger(current.name, ev);
  26690. }
  26691. }
  26692. break;
  26693. }
  26694. }
  26695. Hammer.gestures.Tap = {
  26696. name: name,
  26697. index: 100,
  26698. handler: tapGesture,
  26699. defaults: {
  26700. /**
  26701. * max time of a tap, this is for the slow tappers
  26702. * @property tapMaxTime
  26703. * @type {Number}
  26704. * @default 250
  26705. */
  26706. tapMaxTime: 250,
  26707. /**
  26708. * max distance of movement of a tap, this is for the slow tappers
  26709. * @property tapMaxDistance
  26710. * @type {Number}
  26711. * @default 10
  26712. */
  26713. tapMaxDistance: 10,
  26714. /**
  26715. * always trigger the `tap` event, even while double-tapping
  26716. * @property tapAlways
  26717. * @type {Boolean}
  26718. * @default true
  26719. */
  26720. tapAlways: true,
  26721. /**
  26722. * max distance between two taps
  26723. * @property doubleTapDistance
  26724. * @type {Number}
  26725. * @default 20
  26726. */
  26727. doubleTapDistance: 20,
  26728. /**
  26729. * max time between two taps
  26730. * @property doubleTapInterval
  26731. * @type {Number}
  26732. * @default 300
  26733. */
  26734. doubleTapInterval: 300
  26735. }
  26736. };
  26737. })('tap');
  26738. /**
  26739. * @module gestures
  26740. */
  26741. /**
  26742. * when a touch is being touched at the page
  26743. *
  26744. * @class Touch
  26745. * @static
  26746. */
  26747. /**
  26748. * @event touch
  26749. * @param {Object} ev
  26750. */
  26751. Hammer.gestures.Touch = {
  26752. name: 'touch',
  26753. index: -Infinity,
  26754. defaults: {
  26755. /**
  26756. * call preventDefault at touchstart, and makes the element blocking by disabling the scrolling of the page,
  26757. * but it improves gestures like transforming and dragging.
  26758. * be careful with using this, it can be very annoying for users to be stuck on the page
  26759. * @property preventDefault
  26760. * @type {Boolean}
  26761. * @default false
  26762. */
  26763. preventDefault: false,
  26764. /**
  26765. * disable mouse events, so only touch (or pen!) input triggers events
  26766. * @property preventMouse
  26767. * @type {Boolean}
  26768. * @default false
  26769. */
  26770. preventMouse: false
  26771. },
  26772. handler: function touchGesture(ev, inst) {
  26773. if(inst.options.preventMouse && ev.pointerType == POINTER_MOUSE) {
  26774. ev.stopDetect();
  26775. return;
  26776. }
  26777. if(inst.options.preventDefault) {
  26778. ev.preventDefault();
  26779. }
  26780. if(ev.eventType == EVENT_TOUCH) {
  26781. inst.trigger('touch', ev);
  26782. }
  26783. }
  26784. };
  26785. /**
  26786. * @module gestures
  26787. */
  26788. /**
  26789. * User want to scale or rotate with 2 fingers
  26790. * Preventing the default browser behavior is a good way to improve feel and working. This can be done with the
  26791. * `preventDefault` option.
  26792. *
  26793. * @class Transform
  26794. * @static
  26795. */
  26796. /**
  26797. * @event transform
  26798. * @param {Object} ev
  26799. */
  26800. /**
  26801. * @event transformstart
  26802. * @param {Object} ev
  26803. */
  26804. /**
  26805. * @event transformend
  26806. * @param {Object} ev
  26807. */
  26808. /**
  26809. * @event pinchin
  26810. * @param {Object} ev
  26811. */
  26812. /**
  26813. * @event pinchout
  26814. * @param {Object} ev
  26815. */
  26816. /**
  26817. * @event rotate
  26818. * @param {Object} ev
  26819. */
  26820. /**
  26821. * @param {String} name
  26822. */
  26823. (function(name) {
  26824. var triggered = false;
  26825. function transformGesture(ev, inst) {
  26826. switch(ev.eventType) {
  26827. case EVENT_START:
  26828. triggered = false;
  26829. break;
  26830. case EVENT_MOVE:
  26831. // at least multitouch
  26832. if(ev.touches.length < 2) {
  26833. return;
  26834. }
  26835. var scaleThreshold = Math.abs(1 - ev.scale);
  26836. var rotationThreshold = Math.abs(ev.rotation);
  26837. // when the distance we moved is too small we skip this gesture
  26838. // or we can be already in dragging
  26839. if(scaleThreshold < inst.options.transformMinScale &&
  26840. rotationThreshold < inst.options.transformMinRotation) {
  26841. return;
  26842. }
  26843. // we are transforming!
  26844. Detection.current.name = name;
  26845. // first time, trigger dragstart event
  26846. if(!triggered) {
  26847. inst.trigger(name + 'start', ev);
  26848. triggered = true;
  26849. }
  26850. inst.trigger(name, ev); // basic transform event
  26851. // trigger rotate event
  26852. if(rotationThreshold > inst.options.transformMinRotation) {
  26853. inst.trigger('rotate', ev);
  26854. }
  26855. // trigger pinch event
  26856. if(scaleThreshold > inst.options.transformMinScale) {
  26857. inst.trigger('pinch', ev);
  26858. inst.trigger('pinch' + (ev.scale < 1 ? 'in' : 'out'), ev);
  26859. }
  26860. break;
  26861. case EVENT_RELEASE:
  26862. if(triggered && ev.changedLength < 2) {
  26863. inst.trigger(name + 'end', ev);
  26864. triggered = false;
  26865. }
  26866. break;
  26867. }
  26868. }
  26869. Hammer.gestures.Transform = {
  26870. name: name,
  26871. index: 45,
  26872. defaults: {
  26873. /**
  26874. * minimal scale factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
  26875. * @property transformMinScale
  26876. * @type {Number}
  26877. * @default 0.01
  26878. */
  26879. transformMinScale: 0.01,
  26880. /**
  26881. * rotation in degrees
  26882. * @property transformMinRotation
  26883. * @type {Number}
  26884. * @default 1
  26885. */
  26886. transformMinRotation: 1
  26887. },
  26888. handler: transformGesture
  26889. };
  26890. })('transform');
  26891. /**
  26892. * @module hammer
  26893. */
  26894. // AMD export
  26895. if(true) {
  26896. !(__WEBPACK_AMD_DEFINE_RESULT__ = function() {
  26897. return Hammer;
  26898. }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  26899. // commonjs export
  26900. } else if(typeof module !== 'undefined' && module.exports) {
  26901. module.exports = Hammer;
  26902. // browser export
  26903. } else {
  26904. window.Hammer = Hammer;
  26905. }
  26906. })(window);
  26907. /***/ },
  26908. /* 60 */
  26909. /***/ function(module, exports, __webpack_require__) {
  26910. var util = __webpack_require__(1);
  26911. var RepulsionMixin = __webpack_require__(68);
  26912. var HierarchialRepulsionMixin = __webpack_require__(69);
  26913. var BarnesHutMixin = __webpack_require__(70);
  26914. /**
  26915. * Toggling barnes Hut calculation on and off.
  26916. *
  26917. * @private
  26918. */
  26919. exports._toggleBarnesHut = function () {
  26920. this.constants.physics.barnesHut.enabled = !this.constants.physics.barnesHut.enabled;
  26921. this._loadSelectedForceSolver();
  26922. this.moving = true;
  26923. this.start();
  26924. };
  26925. /**
  26926. * This loads the node force solver based on the barnes hut or repulsion algorithm
  26927. *
  26928. * @private
  26929. */
  26930. exports._loadSelectedForceSolver = function () {
  26931. // this overloads the this._calculateNodeForces
  26932. if (this.constants.physics.barnesHut.enabled == true) {
  26933. this._clearMixin(RepulsionMixin);
  26934. this._clearMixin(HierarchialRepulsionMixin);
  26935. this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity;
  26936. this.constants.physics.springLength = this.constants.physics.barnesHut.springLength;
  26937. this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant;
  26938. this.constants.physics.damping = this.constants.physics.barnesHut.damping;
  26939. this._loadMixin(BarnesHutMixin);
  26940. }
  26941. else if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  26942. this._clearMixin(BarnesHutMixin);
  26943. this._clearMixin(RepulsionMixin);
  26944. this.constants.physics.centralGravity = this.constants.physics.hierarchicalRepulsion.centralGravity;
  26945. this.constants.physics.springLength = this.constants.physics.hierarchicalRepulsion.springLength;
  26946. this.constants.physics.springConstant = this.constants.physics.hierarchicalRepulsion.springConstant;
  26947. this.constants.physics.damping = this.constants.physics.hierarchicalRepulsion.damping;
  26948. this._loadMixin(HierarchialRepulsionMixin);
  26949. }
  26950. else {
  26951. this._clearMixin(BarnesHutMixin);
  26952. this._clearMixin(HierarchialRepulsionMixin);
  26953. this.barnesHutTree = undefined;
  26954. this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity;
  26955. this.constants.physics.springLength = this.constants.physics.repulsion.springLength;
  26956. this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant;
  26957. this.constants.physics.damping = this.constants.physics.repulsion.damping;
  26958. this._loadMixin(RepulsionMixin);
  26959. }
  26960. };
  26961. /**
  26962. * Before calculating the forces, we check if we need to cluster to keep up performance and we check
  26963. * if there is more than one node. If it is just one node, we dont calculate anything.
  26964. *
  26965. * @private
  26966. */
  26967. exports._initializeForceCalculation = function () {
  26968. // stop calculation if there is only one node
  26969. if (this.nodeIndices.length == 1) {
  26970. this.nodes[this.nodeIndices[0]]._setForce(0, 0);
  26971. }
  26972. else {
  26973. // if there are too many nodes on screen, we cluster without repositioning
  26974. if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
  26975. this.clusterToFit(this.constants.clustering.reduceToNodes, false);
  26976. }
  26977. // we now start the force calculation
  26978. this._calculateForces();
  26979. }
  26980. };
  26981. /**
  26982. * Calculate the external forces acting on the nodes
  26983. * Forces are caused by: edges, repulsing forces between nodes, gravity
  26984. * @private
  26985. */
  26986. exports._calculateForces = function () {
  26987. // Gravity is required to keep separated groups from floating off
  26988. // the forces are reset to zero in this loop by using _setForce instead
  26989. // of _addForce
  26990. this._calculateGravitationalForces();
  26991. this._calculateNodeForces();
  26992. if (this.constants.physics.springConstant > 0) {
  26993. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  26994. this._calculateSpringForcesWithSupport();
  26995. }
  26996. else {
  26997. if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  26998. this._calculateHierarchicalSpringForces();
  26999. }
  27000. else {
  27001. this._calculateSpringForces();
  27002. }
  27003. }
  27004. }
  27005. };
  27006. /**
  27007. * Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also
  27008. * handled in the calculateForces function. We then use a quadratic curve with the center node as control.
  27009. * This function joins the datanodes and invisible (called support) nodes into one object.
  27010. * We do this so we do not contaminate this.nodes with the support nodes.
  27011. *
  27012. * @private
  27013. */
  27014. exports._updateCalculationNodes = function () {
  27015. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  27016. this.calculationNodes = {};
  27017. this.calculationNodeIndices = [];
  27018. for (var nodeId in this.nodes) {
  27019. if (this.nodes.hasOwnProperty(nodeId)) {
  27020. this.calculationNodes[nodeId] = this.nodes[nodeId];
  27021. }
  27022. }
  27023. var supportNodes = this.sectors['support']['nodes'];
  27024. for (var supportNodeId in supportNodes) {
  27025. if (supportNodes.hasOwnProperty(supportNodeId)) {
  27026. if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) {
  27027. this.calculationNodes[supportNodeId] = supportNodes[supportNodeId];
  27028. }
  27029. else {
  27030. supportNodes[supportNodeId]._setForce(0, 0);
  27031. }
  27032. }
  27033. }
  27034. for (var idx in this.calculationNodes) {
  27035. if (this.calculationNodes.hasOwnProperty(idx)) {
  27036. this.calculationNodeIndices.push(idx);
  27037. }
  27038. }
  27039. }
  27040. else {
  27041. this.calculationNodes = this.nodes;
  27042. this.calculationNodeIndices = this.nodeIndices;
  27043. }
  27044. };
  27045. /**
  27046. * this function applies the central gravity effect to keep groups from floating off
  27047. *
  27048. * @private
  27049. */
  27050. exports._calculateGravitationalForces = function () {
  27051. var dx, dy, distance, node, i;
  27052. var nodes = this.calculationNodes;
  27053. var gravity = this.constants.physics.centralGravity;
  27054. var gravityForce = 0;
  27055. for (i = 0; i < this.calculationNodeIndices.length; i++) {
  27056. node = nodes[this.calculationNodeIndices[i]];
  27057. node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters.
  27058. // gravity does not apply when we are in a pocket sector
  27059. if (this._sector() == "default" && gravity != 0) {
  27060. dx = -node.x;
  27061. dy = -node.y;
  27062. distance = Math.sqrt(dx * dx + dy * dy);
  27063. gravityForce = (distance == 0) ? 0 : (gravity / distance);
  27064. node.fx = dx * gravityForce;
  27065. node.fy = dy * gravityForce;
  27066. }
  27067. else {
  27068. node.fx = 0;
  27069. node.fy = 0;
  27070. }
  27071. }
  27072. };
  27073. /**
  27074. * this function calculates the effects of the springs in the case of unsmooth curves.
  27075. *
  27076. * @private
  27077. */
  27078. exports._calculateSpringForces = function () {
  27079. var edgeLength, edge, edgeId;
  27080. var dx, dy, fx, fy, springForce, distance;
  27081. var edges = this.edges;
  27082. // forces caused by the edges, modelled as springs
  27083. for (edgeId in edges) {
  27084. if (edges.hasOwnProperty(edgeId)) {
  27085. edge = edges[edgeId];
  27086. if (edge.connected) {
  27087. // only calculate forces if nodes are in the same sector
  27088. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  27089. edgeLength = edge.physics.springLength;
  27090. // this implies that the edges between big clusters are longer
  27091. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  27092. dx = (edge.from.x - edge.to.x);
  27093. dy = (edge.from.y - edge.to.y);
  27094. distance = Math.sqrt(dx * dx + dy * dy);
  27095. if (distance == 0) {
  27096. distance = 0.01;
  27097. }
  27098. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  27099. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  27100. fx = dx * springForce;
  27101. fy = dy * springForce;
  27102. edge.from.fx += fx;
  27103. edge.from.fy += fy;
  27104. edge.to.fx -= fx;
  27105. edge.to.fy -= fy;
  27106. }
  27107. }
  27108. }
  27109. }
  27110. };
  27111. /**
  27112. * This function calculates the springforces on the nodes, accounting for the support nodes.
  27113. *
  27114. * @private
  27115. */
  27116. exports._calculateSpringForcesWithSupport = function () {
  27117. var edgeLength, edge, edgeId, combinedClusterSize;
  27118. var edges = this.edges;
  27119. // forces caused by the edges, modelled as springs
  27120. for (edgeId in edges) {
  27121. if (edges.hasOwnProperty(edgeId)) {
  27122. edge = edges[edgeId];
  27123. if (edge.connected) {
  27124. // only calculate forces if nodes are in the same sector
  27125. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  27126. if (edge.via != null) {
  27127. var node1 = edge.to;
  27128. var node2 = edge.via;
  27129. var node3 = edge.from;
  27130. edgeLength = edge.physics.springLength;
  27131. combinedClusterSize = node1.clusterSize + node3.clusterSize - 2;
  27132. // this implies that the edges between big clusters are longer
  27133. edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth;
  27134. this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
  27135. this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
  27136. }
  27137. }
  27138. }
  27139. }
  27140. }
  27141. };
  27142. /**
  27143. * This is the code actually performing the calculation for the function above. It is split out to avoid repetition.
  27144. *
  27145. * @param node1
  27146. * @param node2
  27147. * @param edgeLength
  27148. * @private
  27149. */
  27150. exports._calculateSpringForce = function (node1, node2, edgeLength) {
  27151. var dx, dy, fx, fy, springForce, distance;
  27152. dx = (node1.x - node2.x);
  27153. dy = (node1.y - node2.y);
  27154. distance = Math.sqrt(dx * dx + dy * dy);
  27155. if (distance == 0) {
  27156. distance = 0.01;
  27157. }
  27158. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  27159. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  27160. fx = dx * springForce;
  27161. fy = dy * springForce;
  27162. node1.fx += fx;
  27163. node1.fy += fy;
  27164. node2.fx -= fx;
  27165. node2.fy -= fy;
  27166. };
  27167. exports._cleanupPhysicsConfiguration = function() {
  27168. if (this.physicsConfiguration !== undefined) {
  27169. while (this.physicsConfiguration.hasChildNodes()) {
  27170. this.physicsConfiguration.removeChild(this.physicsConfiguration.firstChild);
  27171. }
  27172. this.physicsConfiguration.parentNode.removeChild(this.physicsConfiguration);
  27173. this.physicsConfiguration = undefined;
  27174. }
  27175. }
  27176. /**
  27177. * Load the HTML for the physics config and bind it
  27178. * @private
  27179. */
  27180. exports._loadPhysicsConfiguration = function () {
  27181. if (this.physicsConfiguration === undefined) {
  27182. this.backupConstants = {};
  27183. util.deepExtend(this.backupConstants,this.constants);
  27184. var maxGravitational = Math.max(20000, (-1 * this.constants.physics.barnesHut.gravitationalConstant) * 10);
  27185. var maxSpring = Math.min(0.05, this.constants.physics.barnesHut.springConstant * 10)
  27186. var hierarchicalLayoutDirections = ["LR", "RL", "UD", "DU"];
  27187. this.physicsConfiguration = document.createElement('div');
  27188. this.physicsConfiguration.className = "PhysicsConfiguration";
  27189. this.physicsConfiguration.innerHTML = '' +
  27190. '<table><tr><td><b>Simulation Mode:</b></td></tr>' +
  27191. '<tr>' +
  27192. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod1" value="BH" checked="checked">Barnes Hut</td>' +
  27193. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod2" value="R">Repulsion</td>' +
  27194. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod3" value="H">Hierarchical</td>' +
  27195. '</tr>' +
  27196. '</table>' +
  27197. '<table id="graph_BH_table" style="display:none">' +
  27198. '<tr><td><b>Barnes Hut</b></td></tr>' +
  27199. '<tr>' +
  27200. '<td width="150px">gravitationalConstant</td><td>0</td><td><input type="range" min="0" max="'+maxGravitational+'" value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" step="25" style="width:300px" id="graph_BH_gc"></td><td width="50px">-'+maxGravitational+'</td><td><input value="' + (this.constants.physics.barnesHut.gravitationalConstant) + '" id="graph_BH_gc_value" style="width:60px"></td>' +
  27201. '</tr>' +
  27202. '<tr>' +
  27203. '<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="6" 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>' +
  27204. '</tr>' +
  27205. '<tr>' +
  27206. '<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>' +
  27207. '</tr>' +
  27208. '<tr>' +
  27209. '<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="'+maxSpring+'" value="' + this.constants.physics.barnesHut.springConstant + '" step="0.0001" style="width:300px" id="graph_BH_sc"></td><td>'+maxSpring+'</td><td><input value="' + this.constants.physics.barnesHut.springConstant + '" id="graph_BH_sc_value" style="width:60px"></td>' +
  27210. '</tr>' +
  27211. '<tr>' +
  27212. '<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>' +
  27213. '</tr>' +
  27214. '</table>' +
  27215. '<table id="graph_R_table" style="display:none">' +
  27216. '<tr><td><b>Repulsion</b></td></tr>' +
  27217. '<tr>' +
  27218. '<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>' +
  27219. '</tr>' +
  27220. '<tr>' +
  27221. '<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>' +
  27222. '</tr>' +
  27223. '<tr>' +
  27224. '<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>' +
  27225. '</tr>' +
  27226. '<tr>' +
  27227. '<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>' +
  27228. '</tr>' +
  27229. '<tr>' +
  27230. '<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>' +
  27231. '</tr>' +
  27232. '</table>' +
  27233. '<table id="graph_H_table" style="display:none">' +
  27234. '<tr><td width="150"><b>Hierarchical</b></td></tr>' +
  27235. '<tr>' +
  27236. '<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>' +
  27237. '</tr>' +
  27238. '<tr>' +
  27239. '<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>' +
  27240. '</tr>' +
  27241. '<tr>' +
  27242. '<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>' +
  27243. '</tr>' +
  27244. '<tr>' +
  27245. '<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>' +
  27246. '</tr>' +
  27247. '<tr>' +
  27248. '<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>' +
  27249. '</tr>' +
  27250. '<tr>' +
  27251. '<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>' +
  27252. '</tr>' +
  27253. '<tr>' +
  27254. '<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>' +
  27255. '</tr>' +
  27256. '<tr>' +
  27257. '<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>' +
  27258. '</tr>' +
  27259. '</table>' +
  27260. '<table><tr><td><b>Options:</b></td></tr>' +
  27261. '<tr>' +
  27262. '<td width="180px"><input type="button" id="graph_toggleSmooth" value="Toggle smoothCurves" style="width:150px"></td>' +
  27263. '<td width="180px"><input type="button" id="graph_repositionNodes" value="Reinitialize" style="width:150px"></td>' +
  27264. '<td width="180px"><input type="button" id="graph_generateOptions" value="Generate Options" style="width:150px"></td>' +
  27265. '</tr>' +
  27266. '</table>'
  27267. this.containerElement.parentElement.insertBefore(this.physicsConfiguration, this.containerElement);
  27268. this.optionsDiv = document.createElement("div");
  27269. this.optionsDiv.style.fontSize = "14px";
  27270. this.optionsDiv.style.fontFamily = "verdana";
  27271. this.containerElement.parentElement.insertBefore(this.optionsDiv, this.containerElement);
  27272. var rangeElement;
  27273. rangeElement = document.getElementById('graph_BH_gc');
  27274. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_gc', -1, "physics_barnesHut_gravitationalConstant");
  27275. rangeElement = document.getElementById('graph_BH_cg');
  27276. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_cg', 1, "physics_centralGravity");
  27277. rangeElement = document.getElementById('graph_BH_sc');
  27278. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sc', 1, "physics_springConstant");
  27279. rangeElement = document.getElementById('graph_BH_sl');
  27280. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sl', 1, "physics_springLength");
  27281. rangeElement = document.getElementById('graph_BH_damp');
  27282. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_damp', 1, "physics_damping");
  27283. rangeElement = document.getElementById('graph_R_nd');
  27284. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_nd', 1, "physics_repulsion_nodeDistance");
  27285. rangeElement = document.getElementById('graph_R_cg');
  27286. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_cg', 1, "physics_centralGravity");
  27287. rangeElement = document.getElementById('graph_R_sc');
  27288. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sc', 1, "physics_springConstant");
  27289. rangeElement = document.getElementById('graph_R_sl');
  27290. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sl', 1, "physics_springLength");
  27291. rangeElement = document.getElementById('graph_R_damp');
  27292. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_damp', 1, "physics_damping");
  27293. rangeElement = document.getElementById('graph_H_nd');
  27294. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  27295. rangeElement = document.getElementById('graph_H_cg');
  27296. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_cg', 1, "physics_centralGravity");
  27297. rangeElement = document.getElementById('graph_H_sc');
  27298. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sc', 1, "physics_springConstant");
  27299. rangeElement = document.getElementById('graph_H_sl');
  27300. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sl', 1, "physics_springLength");
  27301. rangeElement = document.getElementById('graph_H_damp');
  27302. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_damp', 1, "physics_damping");
  27303. rangeElement = document.getElementById('graph_H_direction');
  27304. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_direction', hierarchicalLayoutDirections, "hierarchicalLayout_direction");
  27305. rangeElement = document.getElementById('graph_H_levsep');
  27306. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_levsep', 1, "hierarchicalLayout_levelSeparation");
  27307. rangeElement = document.getElementById('graph_H_nspac');
  27308. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nspac', 1, "hierarchicalLayout_nodeSpacing");
  27309. var radioButton1 = document.getElementById("graph_physicsMethod1");
  27310. var radioButton2 = document.getElementById("graph_physicsMethod2");
  27311. var radioButton3 = document.getElementById("graph_physicsMethod3");
  27312. radioButton2.checked = true;
  27313. if (this.constants.physics.barnesHut.enabled) {
  27314. radioButton1.checked = true;
  27315. }
  27316. if (this.constants.hierarchicalLayout.enabled) {
  27317. radioButton3.checked = true;
  27318. }
  27319. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  27320. var graph_repositionNodes = document.getElementById("graph_repositionNodes");
  27321. var graph_generateOptions = document.getElementById("graph_generateOptions");
  27322. graph_toggleSmooth.onclick = graphToggleSmoothCurves.bind(this);
  27323. graph_repositionNodes.onclick = graphRepositionNodes.bind(this);
  27324. graph_generateOptions.onclick = graphGenerateOptions.bind(this);
  27325. if (this.constants.smoothCurves == true && this.constants.dynamicSmoothCurves == false) {
  27326. graph_toggleSmooth.style.background = "#A4FF56";
  27327. }
  27328. else {
  27329. graph_toggleSmooth.style.background = "#FF8532";
  27330. }
  27331. switchConfigurations.apply(this);
  27332. radioButton1.onchange = switchConfigurations.bind(this);
  27333. radioButton2.onchange = switchConfigurations.bind(this);
  27334. radioButton3.onchange = switchConfigurations.bind(this);
  27335. }
  27336. };
  27337. /**
  27338. * This overwrites the this.constants.
  27339. *
  27340. * @param constantsVariableName
  27341. * @param value
  27342. * @private
  27343. */
  27344. exports._overWriteGraphConstants = function (constantsVariableName, value) {
  27345. var nameArray = constantsVariableName.split("_");
  27346. if (nameArray.length == 1) {
  27347. this.constants[nameArray[0]] = value;
  27348. }
  27349. else if (nameArray.length == 2) {
  27350. this.constants[nameArray[0]][nameArray[1]] = value;
  27351. }
  27352. else if (nameArray.length == 3) {
  27353. this.constants[nameArray[0]][nameArray[1]][nameArray[2]] = value;
  27354. }
  27355. };
  27356. /**
  27357. * this function is bound to the toggle smooth curves button. That is also why it is not in the prototype.
  27358. */
  27359. function graphToggleSmoothCurves () {
  27360. this.constants.smoothCurves.enabled = !this.constants.smoothCurves.enabled;
  27361. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  27362. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  27363. else {graph_toggleSmooth.style.background = "#FF8532";}
  27364. this._configureSmoothCurves(false);
  27365. }
  27366. /**
  27367. * this function is used to scramble the nodes
  27368. *
  27369. */
  27370. function graphRepositionNodes () {
  27371. for (var nodeId in this.calculationNodes) {
  27372. if (this.calculationNodes.hasOwnProperty(nodeId)) {
  27373. this.calculationNodes[nodeId].vx = 0; this.calculationNodes[nodeId].vy = 0;
  27374. this.calculationNodes[nodeId].fx = 0; this.calculationNodes[nodeId].fy = 0;
  27375. }
  27376. }
  27377. if (this.constants.hierarchicalLayout.enabled == true) {
  27378. this._setupHierarchicalLayout();
  27379. showValueOfRange.call(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  27380. showValueOfRange.call(this, 'graph_H_cg', 1, "physics_centralGravity");
  27381. showValueOfRange.call(this, 'graph_H_sc', 1, "physics_springConstant");
  27382. showValueOfRange.call(this, 'graph_H_sl', 1, "physics_springLength");
  27383. showValueOfRange.call(this, 'graph_H_damp', 1, "physics_damping");
  27384. }
  27385. else {
  27386. this.repositionNodes();
  27387. }
  27388. this.moving = true;
  27389. this.start();
  27390. }
  27391. /**
  27392. * this is used to generate an options file from the playing with physics system.
  27393. */
  27394. function graphGenerateOptions () {
  27395. var options = "No options are required, default values used.";
  27396. var optionsSpecific = [];
  27397. var radioButton1 = document.getElementById("graph_physicsMethod1");
  27398. var radioButton2 = document.getElementById("graph_physicsMethod2");
  27399. if (radioButton1.checked == true) {
  27400. if (this.constants.physics.barnesHut.gravitationalConstant != this.backupConstants.physics.barnesHut.gravitationalConstant) {optionsSpecific.push("gravitationalConstant: " + this.constants.physics.barnesHut.gravitationalConstant);}
  27401. if (this.constants.physics.centralGravity != this.backupConstants.physics.barnesHut.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  27402. if (this.constants.physics.springLength != this.backupConstants.physics.barnesHut.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  27403. if (this.constants.physics.springConstant != this.backupConstants.physics.barnesHut.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  27404. if (this.constants.physics.damping != this.backupConstants.physics.barnesHut.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  27405. if (optionsSpecific.length != 0) {
  27406. options = "var options = {";
  27407. options += "physics: {barnesHut: {";
  27408. for (var i = 0; i < optionsSpecific.length; i++) {
  27409. options += optionsSpecific[i];
  27410. if (i < optionsSpecific.length - 1) {
  27411. options += ", "
  27412. }
  27413. }
  27414. options += '}}'
  27415. }
  27416. if (this.constants.smoothCurves.enabled != this.backupConstants.smoothCurves.enabled) {
  27417. if (optionsSpecific.length == 0) {options = "var options = {";}
  27418. else {options += ", "}
  27419. options += "smoothCurves: " + this.constants.smoothCurves.enabled;
  27420. }
  27421. if (options != "No options are required, default values used.") {
  27422. options += '};'
  27423. }
  27424. }
  27425. else if (radioButton2.checked == true) {
  27426. options = "var options = {";
  27427. options += "physics: {barnesHut: {enabled: false}";
  27428. if (this.constants.physics.repulsion.nodeDistance != this.backupConstants.physics.repulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.repulsion.nodeDistance);}
  27429. if (this.constants.physics.centralGravity != this.backupConstants.physics.repulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  27430. if (this.constants.physics.springLength != this.backupConstants.physics.repulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  27431. if (this.constants.physics.springConstant != this.backupConstants.physics.repulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  27432. if (this.constants.physics.damping != this.backupConstants.physics.repulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  27433. if (optionsSpecific.length != 0) {
  27434. options += ", repulsion: {";
  27435. for (var i = 0; i < optionsSpecific.length; i++) {
  27436. options += optionsSpecific[i];
  27437. if (i < optionsSpecific.length - 1) {
  27438. options += ", "
  27439. }
  27440. }
  27441. options += '}}'
  27442. }
  27443. if (optionsSpecific.length == 0) {options += "}"}
  27444. if (this.constants.smoothCurves != this.backupConstants.smoothCurves) {
  27445. options += ", smoothCurves: " + this.constants.smoothCurves;
  27446. }
  27447. options += '};'
  27448. }
  27449. else {
  27450. options = "var options = {";
  27451. if (this.constants.physics.hierarchicalRepulsion.nodeDistance != this.backupConstants.physics.hierarchicalRepulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.hierarchicalRepulsion.nodeDistance);}
  27452. if (this.constants.physics.centralGravity != this.backupConstants.physics.hierarchicalRepulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  27453. if (this.constants.physics.springLength != this.backupConstants.physics.hierarchicalRepulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  27454. if (this.constants.physics.springConstant != this.backupConstants.physics.hierarchicalRepulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  27455. if (this.constants.physics.damping != this.backupConstants.physics.hierarchicalRepulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  27456. if (optionsSpecific.length != 0) {
  27457. options += "physics: {hierarchicalRepulsion: {";
  27458. for (var i = 0; i < optionsSpecific.length; i++) {
  27459. options += optionsSpecific[i];
  27460. if (i < optionsSpecific.length - 1) {
  27461. options += ", ";
  27462. }
  27463. }
  27464. options += '}},';
  27465. }
  27466. options += 'hierarchicalLayout: {';
  27467. optionsSpecific = [];
  27468. if (this.constants.hierarchicalLayout.direction != this.backupConstants.hierarchicalLayout.direction) {optionsSpecific.push("direction: " + this.constants.hierarchicalLayout.direction);}
  27469. if (Math.abs(this.constants.hierarchicalLayout.levelSeparation) != this.backupConstants.hierarchicalLayout.levelSeparation) {optionsSpecific.push("levelSeparation: " + this.constants.hierarchicalLayout.levelSeparation);}
  27470. if (this.constants.hierarchicalLayout.nodeSpacing != this.backupConstants.hierarchicalLayout.nodeSpacing) {optionsSpecific.push("nodeSpacing: " + this.constants.hierarchicalLayout.nodeSpacing);}
  27471. if (optionsSpecific.length != 0) {
  27472. for (var i = 0; i < optionsSpecific.length; i++) {
  27473. options += optionsSpecific[i];
  27474. if (i < optionsSpecific.length - 1) {
  27475. options += ", "
  27476. }
  27477. }
  27478. options += '}'
  27479. }
  27480. else {
  27481. options += "enabled:true}";
  27482. }
  27483. options += '};'
  27484. }
  27485. this.optionsDiv.innerHTML = options;
  27486. }
  27487. /**
  27488. * this is used to switch between barnesHut, repulsion and hierarchical.
  27489. *
  27490. */
  27491. function switchConfigurations () {
  27492. var ids = ["graph_BH_table", "graph_R_table", "graph_H_table"];
  27493. var radioButton = document.querySelector('input[name="graph_physicsMethod"]:checked').value;
  27494. var tableId = "graph_" + radioButton + "_table";
  27495. var table = document.getElementById(tableId);
  27496. table.style.display = "block";
  27497. for (var i = 0; i < ids.length; i++) {
  27498. if (ids[i] != tableId) {
  27499. table = document.getElementById(ids[i]);
  27500. table.style.display = "none";
  27501. }
  27502. }
  27503. this._restoreNodes();
  27504. if (radioButton == "R") {
  27505. this.constants.hierarchicalLayout.enabled = false;
  27506. this.constants.physics.hierarchicalRepulsion.enabled = false;
  27507. this.constants.physics.barnesHut.enabled = false;
  27508. }
  27509. else if (radioButton == "H") {
  27510. if (this.constants.hierarchicalLayout.enabled == false) {
  27511. this.constants.hierarchicalLayout.enabled = true;
  27512. this.constants.physics.hierarchicalRepulsion.enabled = true;
  27513. this.constants.physics.barnesHut.enabled = false;
  27514. this.constants.smoothCurves.enabled = false;
  27515. this._setupHierarchicalLayout();
  27516. }
  27517. }
  27518. else {
  27519. this.constants.hierarchicalLayout.enabled = false;
  27520. this.constants.physics.hierarchicalRepulsion.enabled = false;
  27521. this.constants.physics.barnesHut.enabled = true;
  27522. }
  27523. this._loadSelectedForceSolver();
  27524. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  27525. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  27526. else {graph_toggleSmooth.style.background = "#FF8532";}
  27527. this.moving = true;
  27528. this.start();
  27529. }
  27530. /**
  27531. * this generates the ranges depending on the iniital values.
  27532. *
  27533. * @param id
  27534. * @param map
  27535. * @param constantsVariableName
  27536. */
  27537. function showValueOfRange (id,map,constantsVariableName) {
  27538. var valueId = id + "_value";
  27539. var rangeValue = document.getElementById(id).value;
  27540. if (Array.isArray(map)) {
  27541. document.getElementById(valueId).value = map[parseInt(rangeValue)];
  27542. this._overWriteGraphConstants(constantsVariableName,map[parseInt(rangeValue)]);
  27543. }
  27544. else {
  27545. document.getElementById(valueId).value = parseInt(map) * parseFloat(rangeValue);
  27546. this._overWriteGraphConstants(constantsVariableName, parseInt(map) * parseFloat(rangeValue));
  27547. }
  27548. if (constantsVariableName == "hierarchicalLayout_direction" ||
  27549. constantsVariableName == "hierarchicalLayout_levelSeparation" ||
  27550. constantsVariableName == "hierarchicalLayout_nodeSpacing") {
  27551. this._setupHierarchicalLayout();
  27552. }
  27553. this.moving = true;
  27554. this.start();
  27555. }
  27556. /***/ },
  27557. /* 61 */
  27558. /***/ function(module, exports, __webpack_require__) {
  27559. /**
  27560. * Creation of the ClusterMixin var.
  27561. *
  27562. * This contains all the functions the Network object can use to employ clustering
  27563. */
  27564. /**
  27565. * This is only called in the constructor of the network object
  27566. *
  27567. */
  27568. exports.startWithClustering = function() {
  27569. // cluster if the data set is big
  27570. this.clusterToFit(this.constants.clustering.initialMaxNodes, true);
  27571. // updates the lables after clustering
  27572. this.updateLabels();
  27573. // this is called here because if clusterin is disabled, the start and stabilize are called in
  27574. // the setData function.
  27575. if (this.constants.stabilize == true) {
  27576. this._stabilize();
  27577. }
  27578. this.start();
  27579. };
  27580. /**
  27581. * This function clusters until the initialMaxNodes has been reached
  27582. *
  27583. * @param {Number} maxNumberOfNodes
  27584. * @param {Boolean} reposition
  27585. */
  27586. exports.clusterToFit = function(maxNumberOfNodes, reposition) {
  27587. var numberOfNodes = this.nodeIndices.length;
  27588. var maxLevels = 50;
  27589. var level = 0;
  27590. // we first cluster the hubs, then we pull in the outliers, repeat
  27591. while (numberOfNodes > maxNumberOfNodes && level < maxLevels) {
  27592. if (level % 3 == 0.0) {
  27593. this.forceAggregateHubs(true);
  27594. this.normalizeClusterLevels();
  27595. }
  27596. else {
  27597. this.increaseClusterLevel(); // this also includes a cluster normalization
  27598. }
  27599. this.forceAggregateHubs(true);
  27600. numberOfNodes = this.nodeIndices.length;
  27601. level += 1;
  27602. }
  27603. // after the clustering we reposition the nodes to reduce the initial chaos
  27604. if (level > 0 && reposition == true) {
  27605. this.repositionNodes();
  27606. }
  27607. this._updateCalculationNodes();
  27608. };
  27609. /**
  27610. * This function can be called to open up a specific cluster.
  27611. * It will unpack the cluster back one level.
  27612. *
  27613. * @param node | Node object: cluster to open.
  27614. */
  27615. exports.openCluster = function(node) {
  27616. var isMovingBeforeClustering = this.moving;
  27617. if (node.clusterSize > this.constants.clustering.sectorThreshold && this._nodeInActiveArea(node) &&
  27618. !(this._sector() == "default" && this.nodeIndices.length == 1)) {
  27619. // this loads a new sector, loads the nodes and edges and nodeIndices of it.
  27620. this._addSector(node);
  27621. var level = 0;
  27622. // we decluster until we reach a decent number of nodes
  27623. while ((this.nodeIndices.length < this.constants.clustering.initialMaxNodes) && (level < 10)) {
  27624. this.decreaseClusterLevel();
  27625. level += 1;
  27626. }
  27627. }
  27628. else {
  27629. this._expandClusterNode(node,false,true);
  27630. // update the index list and labels
  27631. this._updateNodeIndexList();
  27632. this._updateCalculationNodes();
  27633. this.updateLabels();
  27634. }
  27635. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  27636. if (this.moving != isMovingBeforeClustering) {
  27637. this.start();
  27638. }
  27639. };
  27640. /**
  27641. * This calls the updateClustes with default arguments
  27642. */
  27643. exports.updateClustersDefault = function() {
  27644. if (this.constants.clustering.enabled == true && this.constants.clustering.clusterByZoom == true) {
  27645. this.updateClusters(0,false,false);
  27646. }
  27647. };
  27648. /**
  27649. * This function can be called to increase the cluster level. This means that the nodes with only one edge connection will
  27650. * be clustered with their connected node. This can be repeated as many times as needed.
  27651. * This can be called externally (by a keybind for instance) to reduce the complexity of big datasets.
  27652. */
  27653. exports.increaseClusterLevel = function() {
  27654. this.updateClusters(-1,false,true);
  27655. };
  27656. /**
  27657. * This function can be called to decrease the cluster level. This means that the nodes with only one edge connection will
  27658. * be unpacked if they are a cluster. This can be repeated as many times as needed.
  27659. * This can be called externally (by a key-bind for instance) to look into clusters without zooming.
  27660. */
  27661. exports.decreaseClusterLevel = function() {
  27662. this.updateClusters(1,false,true);
  27663. };
  27664. /**
  27665. * This is the main clustering function. It clusters and declusters on zoom or forced
  27666. * This function clusters on zoom, it can be called with a predefined zoom direction
  27667. * If out, check if we can form clusters, if in, check if we can open clusters.
  27668. * This function is only called from _zoom()
  27669. *
  27670. * @param {Number} zoomDirection | -1 / 0 / +1 for zoomOut / determineByZoom / zoomIn
  27671. * @param {Boolean} recursive | enabled or disable recursive calling of the opening of clusters
  27672. * @param {Boolean} force | enabled or disable forcing
  27673. * @param {Boolean} doNotStart | if true do not call start
  27674. *
  27675. */
  27676. exports.updateClusters = function(zoomDirection,recursive,force,doNotStart) {
  27677. var isMovingBeforeClustering = this.moving;
  27678. var amountOfNodes = this.nodeIndices.length;
  27679. var detectedZoomingIn = (this.previousScale < this.scale && zoomDirection == 0);
  27680. var detectedZoomingOut = (this.previousScale > this.scale && zoomDirection == 0);
  27681. // on zoom out collapse the sector if the scale is at the level the sector was made
  27682. if (detectedZoomingOut == true) {
  27683. this._collapseSector();
  27684. }
  27685. // check if we zoom in or out
  27686. if (detectedZoomingOut == true || zoomDirection == -1) { // zoom out
  27687. // forming clusters when forced pulls outliers in. When not forced, the edge length of the
  27688. // outer nodes determines if it is being clustered
  27689. this._formClusters(force);
  27690. }
  27691. else if (detectedZoomingIn == true || zoomDirection == 1) { // zoom in
  27692. if (force == true) {
  27693. // _openClusters checks for each node if the formationScale of the cluster is smaller than
  27694. // the current scale and if so, declusters. When forced, all clusters are reduced by one step
  27695. this._openClusters(recursive,force);
  27696. }
  27697. else {
  27698. // if a cluster takes up a set percentage of the active window
  27699. //this._openClustersBySize();
  27700. this._openClusters(recursive, false);
  27701. }
  27702. }
  27703. this._updateNodeIndexList();
  27704. // if a cluster was NOT formed and the user zoomed out, we try clustering by hubs
  27705. if (this.nodeIndices.length == amountOfNodes && (detectedZoomingOut == true || zoomDirection == -1)) {
  27706. this._aggregateHubs(force);
  27707. this._updateNodeIndexList();
  27708. }
  27709. // we now reduce chains.
  27710. if (detectedZoomingOut == true || zoomDirection == -1) { // zoom out
  27711. this.handleChains();
  27712. this._updateNodeIndexList();
  27713. }
  27714. this.previousScale = this.scale;
  27715. // update labels
  27716. this.updateLabels();
  27717. // if a cluster was formed, we increase the clusterSession
  27718. if (this.nodeIndices.length < amountOfNodes) { // this means a clustering operation has taken place
  27719. this.clusterSession += 1;
  27720. // if clusters have been made, we normalize the cluster level
  27721. this.normalizeClusterLevels();
  27722. }
  27723. if (doNotStart == false || doNotStart === undefined) {
  27724. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  27725. if (this.moving != isMovingBeforeClustering) {
  27726. this.start();
  27727. }
  27728. }
  27729. this._updateCalculationNodes();
  27730. };
  27731. /**
  27732. * This function handles the chains. It is called on every updateClusters().
  27733. */
  27734. exports.handleChains = function() {
  27735. // after clustering we check how many chains there are
  27736. var chainPercentage = this._getChainFraction();
  27737. if (chainPercentage > this.constants.clustering.chainThreshold) {
  27738. this._reduceAmountOfChains(1 - this.constants.clustering.chainThreshold / chainPercentage)
  27739. }
  27740. };
  27741. /**
  27742. * this functions starts clustering by hubs
  27743. * The minimum hub threshold is set globally
  27744. *
  27745. * @private
  27746. */
  27747. exports._aggregateHubs = function(force) {
  27748. this._getHubSize();
  27749. this._formClustersByHub(force,false);
  27750. };
  27751. /**
  27752. * This function forces hubs to form.
  27753. *
  27754. */
  27755. exports.forceAggregateHubs = function(doNotStart) {
  27756. var isMovingBeforeClustering = this.moving;
  27757. var amountOfNodes = this.nodeIndices.length;
  27758. this._aggregateHubs(true);
  27759. // update the index list, dynamic edges and labels
  27760. this._updateNodeIndexList();
  27761. this.updateLabels();
  27762. this._updateCalculationNodes();
  27763. // if a cluster was formed, we increase the clusterSession
  27764. if (this.nodeIndices.length != amountOfNodes) {
  27765. this.clusterSession += 1;
  27766. }
  27767. if (doNotStart == false || doNotStart === undefined) {
  27768. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  27769. if (this.moving != isMovingBeforeClustering) {
  27770. this.start();
  27771. }
  27772. }
  27773. };
  27774. /**
  27775. * If a cluster takes up more than a set percentage of the screen, open the cluster
  27776. *
  27777. * @private
  27778. */
  27779. exports._openClustersBySize = function() {
  27780. if (this.constants.clustering.clusterByZoom == true) {
  27781. for (var nodeId in this.nodes) {
  27782. if (this.nodes.hasOwnProperty(nodeId)) {
  27783. var node = this.nodes[nodeId];
  27784. if (node.inView() == true) {
  27785. if ((node.width * this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  27786. (node.height * this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  27787. this.openCluster(node);
  27788. }
  27789. }
  27790. }
  27791. }
  27792. }
  27793. };
  27794. /**
  27795. * This function loops over all nodes in the nodeIndices list. For each node it checks if it is a cluster and if it
  27796. * has to be opened based on the current zoom level.
  27797. *
  27798. * @private
  27799. */
  27800. exports._openClusters = function(recursive,force) {
  27801. for (var i = 0; i < this.nodeIndices.length; i++) {
  27802. var node = this.nodes[this.nodeIndices[i]];
  27803. this._expandClusterNode(node,recursive,force);
  27804. this._updateCalculationNodes();
  27805. }
  27806. };
  27807. /**
  27808. * This function checks if a node has to be opened. This is done by checking the zoom level.
  27809. * If the node contains child nodes, this function is recursively called on the child nodes as well.
  27810. * This recursive behaviour is optional and can be set by the recursive argument.
  27811. *
  27812. * @param {Node} parentNode | to check for cluster and expand
  27813. * @param {Boolean} recursive | enabled or disable recursive calling
  27814. * @param {Boolean} force | enabled or disable forcing
  27815. * @param {Boolean} [openAll] | This will recursively force all nodes in the parent to be released
  27816. * @private
  27817. */
  27818. exports._expandClusterNode = function(parentNode, recursive, force, openAll) {
  27819. // first check if node is a cluster
  27820. if (parentNode.clusterSize > 1) {
  27821. if (openAll === undefined) {
  27822. openAll = false;
  27823. }
  27824. // this means that on a double tap event or a zoom event, the cluster fully unpacks if it is smaller than 20
  27825. recursive = openAll || recursive;
  27826. // if the last child has been added on a smaller scale than current scale decluster
  27827. if (parentNode.formationScale < this.scale || force == true) {
  27828. // we will check if any of the contained child nodes should be removed from the cluster
  27829. for (var containedNodeId in parentNode.containedNodes) {
  27830. if (parentNode.containedNodes.hasOwnProperty(containedNodeId)) {
  27831. var childNode = parentNode.containedNodes[containedNodeId];
  27832. // force expand will expand the largest cluster size clusters. Since we cluster from outside in, we assume that
  27833. // the largest cluster is the one that comes from outside
  27834. if (force == true) {
  27835. if (childNode.clusterSession == parentNode.clusterSessions[parentNode.clusterSessions.length-1]
  27836. || openAll) {
  27837. this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
  27838. }
  27839. }
  27840. else {
  27841. if (this._nodeInActiveArea(parentNode)) {
  27842. this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
  27843. }
  27844. }
  27845. }
  27846. }
  27847. }
  27848. }
  27849. };
  27850. /**
  27851. * ONLY CALLED FROM _expandClusterNode
  27852. *
  27853. * This function will expel a child_node from a parent_node. This is to de-cluster the node. This function will remove
  27854. * the child node from the parent contained_node object and put it back into the global nodes object.
  27855. * The same holds for the edge that was connected to the child node. It is moved back into the global edges object.
  27856. *
  27857. * @param {Node} parentNode | the parent node
  27858. * @param {String} containedNodeId | child_node id as it is contained in the containedNodes object of the parent node
  27859. * @param {Boolean} recursive | This will also check if the child needs to be expanded.
  27860. * With force and recursive both true, the entire cluster is unpacked
  27861. * @param {Boolean} force | This will disregard the zoom level and will expel this child from the parent
  27862. * @param {Boolean} openAll | This will recursively force all nodes in the parent to be released
  27863. * @private
  27864. */
  27865. exports._expelChildFromParent = function(parentNode, containedNodeId, recursive, force, openAll) {
  27866. var childNode = parentNode.containedNodes[containedNodeId]
  27867. // if child node has been added on smaller scale than current, kick out
  27868. if (childNode.formationScale < this.scale || force == true) {
  27869. // unselect all selected items
  27870. this._unselectAll();
  27871. // put the child node back in the global nodes object
  27872. this.nodes[containedNodeId] = childNode;
  27873. // release the contained edges from this childNode back into the global edges
  27874. this._releaseContainedEdges(parentNode,childNode);
  27875. // reconnect rerouted edges to the childNode
  27876. this._connectEdgeBackToChild(parentNode,childNode);
  27877. // validate all edges in dynamicEdges
  27878. this._validateEdges(parentNode);
  27879. // undo the changes from the clustering operation on the parent node
  27880. parentNode.options.mass -= childNode.options.mass;
  27881. parentNode.clusterSize -= childNode.clusterSize;
  27882. parentNode.options.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*(parentNode.clusterSize-1));
  27883. // place the child node near the parent, not at the exact same location to avoid chaos in the system
  27884. childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random());
  27885. childNode.y = parentNode.y + parentNode.growthIndicator * (0.5 - Math.random());
  27886. // remove node from the list
  27887. delete parentNode.containedNodes[containedNodeId];
  27888. // check if there are other childs with this clusterSession in the parent.
  27889. var othersPresent = false;
  27890. for (var childNodeId in parentNode.containedNodes) {
  27891. if (parentNode.containedNodes.hasOwnProperty(childNodeId)) {
  27892. if (parentNode.containedNodes[childNodeId].clusterSession == childNode.clusterSession) {
  27893. othersPresent = true;
  27894. break;
  27895. }
  27896. }
  27897. }
  27898. // if there are no others, remove the cluster session from the list
  27899. if (othersPresent == false) {
  27900. parentNode.clusterSessions.pop();
  27901. }
  27902. this._repositionBezierNodes(childNode);
  27903. // this._repositionBezierNodes(parentNode);
  27904. // remove the clusterSession from the child node
  27905. childNode.clusterSession = 0;
  27906. // recalculate the size of the node on the next time the node is rendered
  27907. parentNode.clearSizeCache();
  27908. // restart the simulation to reorganise all nodes
  27909. this.moving = true;
  27910. }
  27911. // check if a further expansion step is possible if recursivity is enabled
  27912. if (recursive == true) {
  27913. this._expandClusterNode(childNode,recursive,force,openAll);
  27914. }
  27915. };
  27916. /**
  27917. * position the bezier nodes at the center of the edges
  27918. *
  27919. * @param node
  27920. * @private
  27921. */
  27922. exports._repositionBezierNodes = function(node) {
  27923. for (var i = 0; i < node.dynamicEdges.length; i++) {
  27924. node.dynamicEdges[i].positionBezierNode();
  27925. }
  27926. };
  27927. /**
  27928. * This function checks if any nodes at the end of their trees have edges below a threshold length
  27929. * This function is called only from updateClusters()
  27930. * forceLevelCollapse ignores the length of the edge and collapses one level
  27931. * This means that a node with only one edge will be clustered with its connected node
  27932. *
  27933. * @private
  27934. * @param {Boolean} force
  27935. */
  27936. exports._formClusters = function(force) {
  27937. if (force == false) {
  27938. if (this.constants.clustering.clusterByZoom == true) {
  27939. this._formClustersByZoom();
  27940. }
  27941. }
  27942. else {
  27943. this._forceClustersByZoom();
  27944. }
  27945. };
  27946. /**
  27947. * This function handles the clustering by zooming out, this is based on a minimum edge distance
  27948. *
  27949. * @private
  27950. */
  27951. exports._formClustersByZoom = function() {
  27952. var dx,dy,length;
  27953. var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
  27954. // check if any edges are shorter than minLength and start the clustering
  27955. // the clustering favours the node with the larger mass
  27956. for (var edgeId in this.edges) {
  27957. if (this.edges.hasOwnProperty(edgeId)) {
  27958. var edge = this.edges[edgeId];
  27959. if (edge.connected) {
  27960. if (edge.toId != edge.fromId) {
  27961. dx = (edge.to.x - edge.from.x);
  27962. dy = (edge.to.y - edge.from.y);
  27963. length = Math.sqrt(dx * dx + dy * dy);
  27964. if (length < minLength) {
  27965. // first check which node is larger
  27966. var parentNode = edge.from;
  27967. var childNode = edge.to;
  27968. if (edge.to.options.mass > edge.from.options.mass) {
  27969. parentNode = edge.to;
  27970. childNode = edge.from;
  27971. }
  27972. if (childNode.dynamicEdges.length == 1) {
  27973. this._addToCluster(parentNode,childNode,false);
  27974. }
  27975. else if (parentNode.dynamicEdges.length == 1) {
  27976. this._addToCluster(childNode,parentNode,false);
  27977. }
  27978. }
  27979. }
  27980. }
  27981. }
  27982. }
  27983. };
  27984. /**
  27985. * This function forces the network to cluster all nodes with only one connecting edge to their
  27986. * connected node.
  27987. *
  27988. * @private
  27989. */
  27990. exports._forceClustersByZoom = function() {
  27991. for (var nodeId in this.nodes) {
  27992. // another node could have absorbed this child.
  27993. if (this.nodes.hasOwnProperty(nodeId)) {
  27994. var childNode = this.nodes[nodeId];
  27995. // the edges can be swallowed by another decrease
  27996. if (childNode.dynamicEdges.length == 1) {
  27997. var edge = childNode.dynamicEdges[0];
  27998. var parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId];
  27999. // group to the largest node
  28000. if (childNode.id != parentNode.id) {
  28001. if (parentNode.options.mass > childNode.options.mass) {
  28002. this._addToCluster(parentNode,childNode,true);
  28003. }
  28004. else {
  28005. this._addToCluster(childNode,parentNode,true);
  28006. }
  28007. }
  28008. }
  28009. }
  28010. }
  28011. };
  28012. /**
  28013. * To keep the nodes of roughly equal size we normalize the cluster levels.
  28014. * This function clusters a node to its smallest connected neighbour.
  28015. *
  28016. * @param node
  28017. * @private
  28018. */
  28019. exports._clusterToSmallestNeighbour = function(node) {
  28020. var smallestNeighbour = -1;
  28021. var smallestNeighbourNode = null;
  28022. for (var i = 0; i < node.dynamicEdges.length; i++) {
  28023. if (node.dynamicEdges[i] !== undefined) {
  28024. var neighbour = null;
  28025. if (node.dynamicEdges[i].fromId != node.id) {
  28026. neighbour = node.dynamicEdges[i].from;
  28027. }
  28028. else if (node.dynamicEdges[i].toId != node.id) {
  28029. neighbour = node.dynamicEdges[i].to;
  28030. }
  28031. if (neighbour != null && smallestNeighbour > neighbour.clusterSessions.length) {
  28032. smallestNeighbour = neighbour.clusterSessions.length;
  28033. smallestNeighbourNode = neighbour;
  28034. }
  28035. }
  28036. }
  28037. if (neighbour != null && this.nodes[neighbour.id] !== undefined) {
  28038. this._addToCluster(neighbour, node, true);
  28039. }
  28040. };
  28041. /**
  28042. * This function forms clusters from hubs, it loops over all nodes
  28043. *
  28044. * @param {Boolean} force | Disregard zoom level
  28045. * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
  28046. * @private
  28047. */
  28048. exports._formClustersByHub = function(force, onlyEqual) {
  28049. // we loop over all nodes in the list
  28050. for (var nodeId in this.nodes) {
  28051. // we check if it is still available since it can be used by the clustering in this loop
  28052. if (this.nodes.hasOwnProperty(nodeId)) {
  28053. this._formClusterFromHub(this.nodes[nodeId],force,onlyEqual);
  28054. }
  28055. }
  28056. };
  28057. /**
  28058. * This function forms a cluster from a specific preselected hub node
  28059. *
  28060. * @param {Node} hubNode | the node we will cluster as a hub
  28061. * @param {Boolean} force | Disregard zoom level
  28062. * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
  28063. * @param {Number} [absorptionSizeOffset] |
  28064. * @private
  28065. */
  28066. exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSizeOffset) {
  28067. if (absorptionSizeOffset === undefined) {
  28068. absorptionSizeOffset = 0;
  28069. }
  28070. //this.hubThreshold = 43
  28071. //if (hubNode.dynamicEdgesLength < 0) {
  28072. // console.error(hubNode.dynamicEdgesLength, this.hubThreshold, onlyEqual)
  28073. //}
  28074. // we decide if the node is a hub
  28075. if ((hubNode.dynamicEdges.length >= this.hubThreshold && onlyEqual == false) ||
  28076. (hubNode.dynamicEdges.length == this.hubThreshold && onlyEqual == true)) {
  28077. // initialize variables
  28078. var dx,dy,length;
  28079. var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
  28080. var allowCluster = false;
  28081. // we create a list of edges because the dynamicEdges change over the course of this loop
  28082. var edgesIdarray = [];
  28083. var amountOfInitialEdges = hubNode.dynamicEdges.length;
  28084. for (var j = 0; j < amountOfInitialEdges; j++) {
  28085. edgesIdarray.push(hubNode.dynamicEdges[j].id);
  28086. }
  28087. // if the hub clustering is not forced, we check if one of the edges connected
  28088. // to a cluster is small enough based on the constants.clustering.clusterEdgeThreshold
  28089. if (force == false) {
  28090. allowCluster = false;
  28091. for (j = 0; j < amountOfInitialEdges; j++) {
  28092. var edge = this.edges[edgesIdarray[j]];
  28093. if (edge !== undefined) {
  28094. if (edge.connected) {
  28095. if (edge.toId != edge.fromId) {
  28096. dx = (edge.to.x - edge.from.x);
  28097. dy = (edge.to.y - edge.from.y);
  28098. length = Math.sqrt(dx * dx + dy * dy);
  28099. if (length < minLength) {
  28100. allowCluster = true;
  28101. break;
  28102. }
  28103. }
  28104. }
  28105. }
  28106. }
  28107. }
  28108. // start the clustering if allowed
  28109. if ((!force && allowCluster) || force) {
  28110. var children = [];
  28111. var childrenIds = {};
  28112. // we loop over all edges INITIALLY connected to this hub to get a list of the childNodes
  28113. for (j = 0; j < amountOfInitialEdges; j++) {
  28114. edge = this.edges[edgesIdarray[j]];
  28115. var childNode = this.nodes[(edge.fromId == hubNode.id) ? edge.toId : edge.fromId];
  28116. if (childrenIds[childNode.id] === undefined) {
  28117. childrenIds[childNode.id] = true;
  28118. children.push(childNode);
  28119. }
  28120. }
  28121. for (j = 0; j < children.length; j++) {
  28122. var childNode = children[j];
  28123. // we do not want hubs to merge with other hubs nor do we want to cluster itself.
  28124. if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) &&
  28125. (childNode.id != hubNode.id)) {
  28126. this._addToCluster(hubNode,childNode,force);
  28127. }
  28128. else {
  28129. //console.log("WILL NOT MERGE:",childNode.dynamicEdges.length , (this.hubThreshold + absorptionSizeOffset))
  28130. }
  28131. }
  28132. }
  28133. }
  28134. };
  28135. /**
  28136. * This function adds the child node to the parent node, creating a cluster if it is not already.
  28137. *
  28138. * @param {Node} parentNode | this is the node that will house the child node
  28139. * @param {Node} childNode | this node will be deleted from the global this.nodes and stored in the parent node
  28140. * @param {Boolean} force | true will only update the remainingEdges at the very end of the clustering, ensuring single level collapse
  28141. * @private
  28142. */
  28143. exports._addToCluster = function(parentNode, childNode, force) {
  28144. // join child node in the parent node
  28145. parentNode.containedNodes[childNode.id] = childNode;
  28146. //console.log(parentNode.id, childNode.id)
  28147. // manage all the edges connected to the child and parent nodes
  28148. for (var i = 0; i < childNode.dynamicEdges.length; i++) {
  28149. var edge = childNode.dynamicEdges[i];
  28150. if (edge.toId == parentNode.id || edge.fromId == parentNode.id) { // edge connected to parentNode
  28151. //console.log("COLLECT",parentNode.id, childNode.id, edge.toId, edge.fromId)
  28152. this._addToContainedEdges(parentNode,childNode,edge);
  28153. }
  28154. else {
  28155. //console.log("REWIRE",parentNode.id, childNode.id, edge.toId, edge.fromId)
  28156. this._connectEdgeToCluster(parentNode,childNode,edge);
  28157. }
  28158. }
  28159. // a contained node has no dynamic edges.
  28160. childNode.dynamicEdges = [];
  28161. // remove circular edges from clusters
  28162. this._containCircularEdgesFromNode(parentNode,childNode);
  28163. // remove the childNode from the global nodes object
  28164. delete this.nodes[childNode.id];
  28165. // update the properties of the child and parent
  28166. var massBefore = parentNode.options.mass;
  28167. childNode.clusterSession = this.clusterSession;
  28168. parentNode.options.mass += childNode.options.mass;
  28169. parentNode.clusterSize += childNode.clusterSize;
  28170. parentNode.options.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
  28171. // keep track of the clustersessions so we can open the cluster up as it has been formed.
  28172. if (parentNode.clusterSessions[parentNode.clusterSessions.length - 1] != this.clusterSession) {
  28173. parentNode.clusterSessions.push(this.clusterSession);
  28174. }
  28175. // forced clusters only open from screen size and double tap
  28176. if (force == true) {
  28177. parentNode.formationScale = 0;
  28178. }
  28179. else {
  28180. parentNode.formationScale = this.scale; // The latest child has been added on this scale
  28181. }
  28182. // recalculate the size of the node on the next time the node is rendered
  28183. parentNode.clearSizeCache();
  28184. // set the pop-out scale for the childnode
  28185. parentNode.containedNodes[childNode.id].formationScale = parentNode.formationScale;
  28186. // nullify the movement velocity of the child, this is to avoid hectic behaviour
  28187. childNode.clearVelocity();
  28188. // the mass has altered, preservation of energy dictates the velocity to be updated
  28189. parentNode.updateVelocity(massBefore);
  28190. // restart the simulation to reorganise all nodes
  28191. this.moving = true;
  28192. };
  28193. /**
  28194. * This adds an edge from the childNode to the contained edges of the parent node
  28195. *
  28196. * @param parentNode | Node object
  28197. * @param childNode | Node object
  28198. * @param edge | Edge object
  28199. * @private
  28200. */
  28201. exports._addToContainedEdges = function(parentNode, childNode, edge) {
  28202. // create an array object if it does not yet exist for this childNode
  28203. if (parentNode.containedEdges[childNode.id] === undefined) {
  28204. parentNode.containedEdges[childNode.id] = []
  28205. }
  28206. // add this edge to the list
  28207. parentNode.containedEdges[childNode.id].push(edge);
  28208. // remove the edge from the global edges object
  28209. delete this.edges[edge.id];
  28210. // remove the edge from the parent object
  28211. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  28212. if (parentNode.dynamicEdges[i].id == edge.id) {
  28213. parentNode.dynamicEdges.splice(i,1);
  28214. break;
  28215. }
  28216. }
  28217. };
  28218. /**
  28219. * This function connects an edge that was connected to a child node to the parent node.
  28220. * It keeps track of which nodes it has been connected to with the originalId array.
  28221. *
  28222. * @param {Node} parentNode | Node object
  28223. * @param {Node} childNode | Node object
  28224. * @param {Edge} edge | Edge object
  28225. * @private
  28226. */
  28227. exports._connectEdgeToCluster = function(parentNode, childNode, edge) {
  28228. // handle circular edges
  28229. if (edge.toId == edge.fromId) {
  28230. this._addToContainedEdges(parentNode, childNode, edge);
  28231. }
  28232. else {
  28233. if (edge.toId == childNode.id) { // edge connected to other node on the "to" side
  28234. edge.originalToId.push(childNode.id);
  28235. edge.to = parentNode;
  28236. edge.toId = parentNode.id;
  28237. }
  28238. else { // edge connected to other node with the "from" side
  28239. edge.originalFromId.push(childNode.id);
  28240. edge.from = parentNode;
  28241. edge.fromId = parentNode.id;
  28242. }
  28243. this._addToReroutedEdges(parentNode,childNode,edge);
  28244. }
  28245. };
  28246. /**
  28247. * If a node is connected to itself, a circular edge is drawn. When clustering we want to contain
  28248. * these edges inside of the cluster.
  28249. *
  28250. * @param parentNode
  28251. * @param childNode
  28252. * @private
  28253. */
  28254. exports._containCircularEdgesFromNode = function(parentNode, childNode) {
  28255. // manage all the edges connected to the child and parent nodes
  28256. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  28257. var edge = parentNode.dynamicEdges[i];
  28258. // handle circular edges
  28259. if (edge.toId == edge.fromId) {
  28260. this._addToContainedEdges(parentNode, childNode, edge);
  28261. }
  28262. }
  28263. };
  28264. /**
  28265. * This adds an edge from the childNode to the rerouted edges of the parent node
  28266. *
  28267. * @param parentNode | Node object
  28268. * @param childNode | Node object
  28269. * @param edge | Edge object
  28270. * @private
  28271. */
  28272. exports._addToReroutedEdges = function(parentNode, childNode, edge) {
  28273. // create an array object if it does not yet exist for this childNode
  28274. // we store the edge in the rerouted edges so we can restore it when the cluster pops open
  28275. if (!(parentNode.reroutedEdges.hasOwnProperty(childNode.id))) {
  28276. parentNode.reroutedEdges[childNode.id] = [];
  28277. }
  28278. parentNode.reroutedEdges[childNode.id].push(edge);
  28279. // this edge becomes part of the dynamicEdges of the cluster node
  28280. parentNode.dynamicEdges.push(edge);
  28281. };
  28282. /**
  28283. * This function connects an edge that was connected to a cluster node back to the child node.
  28284. *
  28285. * @param parentNode | Node object
  28286. * @param childNode | Node object
  28287. * @private
  28288. */
  28289. exports._connectEdgeBackToChild = function(parentNode, childNode) {
  28290. if (parentNode.reroutedEdges.hasOwnProperty(childNode.id)) {
  28291. for (var i = 0; i < parentNode.reroutedEdges[childNode.id].length; i++) {
  28292. var edge = parentNode.reroutedEdges[childNode.id][i];
  28293. if (edge.originalFromId[edge.originalFromId.length-1] == childNode.id) {
  28294. edge.originalFromId.pop();
  28295. edge.fromId = childNode.id;
  28296. edge.from = childNode;
  28297. }
  28298. else {
  28299. edge.originalToId.pop();
  28300. edge.toId = childNode.id;
  28301. edge.to = childNode;
  28302. }
  28303. // append this edge to the list of edges connecting to the childnode
  28304. childNode.dynamicEdges.push(edge);
  28305. // remove the edge from the parent object
  28306. for (var j = 0; j < parentNode.dynamicEdges.length; j++) {
  28307. if (parentNode.dynamicEdges[j].id == edge.id) {
  28308. parentNode.dynamicEdges.splice(j,1);
  28309. break;
  28310. }
  28311. }
  28312. }
  28313. // remove the entry from the rerouted edges
  28314. delete parentNode.reroutedEdges[childNode.id];
  28315. }
  28316. };
  28317. /**
  28318. * When loops are clustered, an edge can be both in the rerouted array and the contained array.
  28319. * This function is called last to verify that all edges in dynamicEdges are in fact connected to the
  28320. * parentNode
  28321. *
  28322. * @param parentNode | Node object
  28323. * @private
  28324. */
  28325. exports._validateEdges = function(parentNode) {
  28326. var dynamicEdges = []
  28327. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  28328. var edge = parentNode.dynamicEdges[i];
  28329. if (parentNode.id == edge.toId || parentNode.id == edge.fromId) {
  28330. dynamicEdges.push(edge);
  28331. }
  28332. }
  28333. parentNode.dynamicEdges = dynamicEdges;
  28334. };
  28335. /**
  28336. * This function released the contained edges back into the global domain and puts them back into the
  28337. * dynamic edges of both parent and child.
  28338. *
  28339. * @param {Node} parentNode |
  28340. * @param {Node} childNode |
  28341. * @private
  28342. */
  28343. exports._releaseContainedEdges = function(parentNode, childNode) {
  28344. for (var i = 0; i < parentNode.containedEdges[childNode.id].length; i++) {
  28345. var edge = parentNode.containedEdges[childNode.id][i];
  28346. // put the edge back in the global edges object
  28347. this.edges[edge.id] = edge;
  28348. // put the edge back in the dynamic edges of the child and parent
  28349. childNode.dynamicEdges.push(edge);
  28350. parentNode.dynamicEdges.push(edge);
  28351. }
  28352. // remove the entry from the contained edges
  28353. delete parentNode.containedEdges[childNode.id];
  28354. };
  28355. // ------------------- UTILITY FUNCTIONS ---------------------------- //
  28356. /**
  28357. * This updates the node labels for all nodes (for debugging purposes)
  28358. */
  28359. exports.updateLabels = function() {
  28360. var nodeId;
  28361. // update node labels
  28362. for (nodeId in this.nodes) {
  28363. if (this.nodes.hasOwnProperty(nodeId)) {
  28364. var node = this.nodes[nodeId];
  28365. if (node.clusterSize > 1) {
  28366. node.label = "[".concat(String(node.clusterSize),"]");
  28367. }
  28368. }
  28369. }
  28370. // update node labels
  28371. for (nodeId in this.nodes) {
  28372. if (this.nodes.hasOwnProperty(nodeId)) {
  28373. node = this.nodes[nodeId];
  28374. if (node.clusterSize == 1) {
  28375. if (node.originalLabel !== undefined) {
  28376. node.label = node.originalLabel;
  28377. }
  28378. else {
  28379. node.label = String(node.id);
  28380. }
  28381. }
  28382. }
  28383. }
  28384. // /* Debug Override */
  28385. // for (nodeId in this.nodes) {
  28386. // if (this.nodes.hasOwnProperty(nodeId)) {
  28387. // node = this.nodes[nodeId];
  28388. // node.label = String(node.clusterSize + ":" + node.dynamicEdges.length);
  28389. // }
  28390. // }
  28391. };
  28392. /**
  28393. * We want to keep the cluster level distribution rather small. This means we do not want unclustered nodes
  28394. * if the rest of the nodes are already a few cluster levels in.
  28395. * To fix this we use this function. It determines the min and max cluster level and sends nodes that have not
  28396. * clustered enough to the clusterToSmallestNeighbours function.
  28397. */
  28398. exports.normalizeClusterLevels = function() {
  28399. var maxLevel = 0;
  28400. var minLevel = 1e9;
  28401. var clusterLevel = 0;
  28402. var nodeId;
  28403. // we loop over all nodes in the list
  28404. for (nodeId in this.nodes) {
  28405. if (this.nodes.hasOwnProperty(nodeId)) {
  28406. clusterLevel = this.nodes[nodeId].clusterSessions.length;
  28407. if (maxLevel < clusterLevel) {maxLevel = clusterLevel;}
  28408. if (minLevel > clusterLevel) {minLevel = clusterLevel;}
  28409. }
  28410. }
  28411. if (maxLevel - minLevel > this.constants.clustering.clusterLevelDifference) {
  28412. var amountOfNodes = this.nodeIndices.length;
  28413. var targetLevel = maxLevel - this.constants.clustering.clusterLevelDifference;
  28414. // we loop over all nodes in the list
  28415. for (nodeId in this.nodes) {
  28416. if (this.nodes.hasOwnProperty(nodeId)) {
  28417. if (this.nodes[nodeId].clusterSessions.length < targetLevel) {
  28418. this._clusterToSmallestNeighbour(this.nodes[nodeId]);
  28419. }
  28420. }
  28421. }
  28422. this._updateNodeIndexList();
  28423. // if a cluster was formed, we increase the clusterSession
  28424. if (this.nodeIndices.length != amountOfNodes) {
  28425. this.clusterSession += 1;
  28426. }
  28427. }
  28428. };
  28429. /**
  28430. * This function determines if the cluster we want to decluster is in the active area
  28431. * this means around the zoom center
  28432. *
  28433. * @param {Node} node
  28434. * @returns {boolean}
  28435. * @private
  28436. */
  28437. exports._nodeInActiveArea = function(node) {
  28438. return (
  28439. Math.abs(node.x - this.areaCenter.x) <= this.constants.clustering.activeAreaBoxSize/this.scale
  28440. &&
  28441. Math.abs(node.y - this.areaCenter.y) <= this.constants.clustering.activeAreaBoxSize/this.scale
  28442. )
  28443. };
  28444. /**
  28445. * This is an adaptation of the original repositioning function. This is called if the system is clustered initially
  28446. * It puts large clusters away from the center and randomizes the order.
  28447. *
  28448. */
  28449. exports.repositionNodes = function() {
  28450. for (var i = 0; i < this.nodeIndices.length; i++) {
  28451. var node = this.nodes[this.nodeIndices[i]];
  28452. if ((node.xFixed == false || node.yFixed == false)) {
  28453. var radius = 10 * 0.1*this.nodeIndices.length * Math.min(100,node.options.mass);
  28454. var angle = 2 * Math.PI * Math.random();
  28455. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  28456. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  28457. this._repositionBezierNodes(node);
  28458. }
  28459. }
  28460. };
  28461. /**
  28462. * We determine how many connections denote an important hub.
  28463. * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
  28464. *
  28465. * @private
  28466. */
  28467. exports._getHubSize = function() {
  28468. var average = 0;
  28469. var averageSquared = 0;
  28470. var hubCounter = 0;
  28471. var largestHub = 0;
  28472. for (var i = 0; i < this.nodeIndices.length; i++) {
  28473. var node = this.nodes[this.nodeIndices[i]];
  28474. if (node.dynamicEdges.length > largestHub) {
  28475. largestHub = node.dynamicEdges.length;
  28476. }
  28477. average += node.dynamicEdges.length;
  28478. averageSquared += Math.pow(node.dynamicEdges.length,2);
  28479. hubCounter += 1;
  28480. }
  28481. average = average / hubCounter;
  28482. averageSquared = averageSquared / hubCounter;
  28483. var variance = averageSquared - Math.pow(average,2);
  28484. var standardDeviation = Math.sqrt(variance);
  28485. this.hubThreshold = Math.floor(average + 2*standardDeviation);
  28486. // always have at least one to cluster
  28487. if (this.hubThreshold > largestHub) {
  28488. this.hubThreshold = largestHub;
  28489. }
  28490. // console.log("average",average,"averageSQ",averageSquared,"var",variance,"std",standardDeviation);
  28491. // console.log("hubThreshold:",this.hubThreshold);
  28492. };
  28493. /**
  28494. * We reduce the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
  28495. * with this amount we can cluster specifically on these chains.
  28496. *
  28497. * @param {Number} fraction | between 0 and 1, the percentage of chains to reduce
  28498. * @private
  28499. */
  28500. exports._reduceAmountOfChains = function(fraction) {
  28501. this.hubThreshold = 2;
  28502. var reduceAmount = Math.floor(this.nodeIndices.length * fraction);
  28503. for (var nodeId in this.nodes) {
  28504. if (this.nodes.hasOwnProperty(nodeId)) {
  28505. if (this.nodes[nodeId].dynamicEdges.length == 2) {
  28506. if (reduceAmount > 0) {
  28507. this._formClusterFromHub(this.nodes[nodeId],true,true,1);
  28508. reduceAmount -= 1;
  28509. }
  28510. }
  28511. }
  28512. }
  28513. };
  28514. /**
  28515. * We get the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
  28516. * with this amount we can cluster specifically on these chains.
  28517. *
  28518. * @private
  28519. */
  28520. exports._getChainFraction = function() {
  28521. var chains = 0;
  28522. var total = 0;
  28523. for (var nodeId in this.nodes) {
  28524. if (this.nodes.hasOwnProperty(nodeId)) {
  28525. if (this.nodes[nodeId].dynamicEdges.length == 2) {
  28526. chains += 1;
  28527. }
  28528. total += 1;
  28529. }
  28530. }
  28531. return chains/total;
  28532. };
  28533. /***/ },
  28534. /* 62 */
  28535. /***/ function(module, exports, __webpack_require__) {
  28536. var util = __webpack_require__(1);
  28537. var Node = __webpack_require__(40);
  28538. /**
  28539. * Creation of the SectorMixin var.
  28540. *
  28541. * This contains all the functions the Network object can use to employ the sector system.
  28542. * The sector system is always used by Network, though the benefits only apply to the use of clustering.
  28543. * If clustering is not used, there is no overhead except for a duplicate object with references to nodes and edges.
  28544. */
  28545. /**
  28546. * This function is only called by the setData function of the Network object.
  28547. * This loads the global references into the active sector. This initializes the sector.
  28548. *
  28549. * @private
  28550. */
  28551. exports._putDataInSector = function() {
  28552. this.sectors["active"][this._sector()].nodes = this.nodes;
  28553. this.sectors["active"][this._sector()].edges = this.edges;
  28554. this.sectors["active"][this._sector()].nodeIndices = this.nodeIndices;
  28555. };
  28556. /**
  28557. * /**
  28558. * This function sets the global references to nodes, edges and nodeIndices back to
  28559. * those of the supplied (active) sector. If a type is defined, do the specific type
  28560. *
  28561. * @param {String} sectorId
  28562. * @param {String} [sectorType] | "active" or "frozen"
  28563. * @private
  28564. */
  28565. exports._switchToSector = function(sectorId, sectorType) {
  28566. if (sectorType === undefined || sectorType == "active") {
  28567. this._switchToActiveSector(sectorId);
  28568. }
  28569. else {
  28570. this._switchToFrozenSector(sectorId);
  28571. }
  28572. };
  28573. /**
  28574. * This function sets the global references to nodes, edges and nodeIndices back to
  28575. * those of the supplied active sector.
  28576. *
  28577. * @param sectorId
  28578. * @private
  28579. */
  28580. exports._switchToActiveSector = function(sectorId) {
  28581. this.nodeIndices = this.sectors["active"][sectorId]["nodeIndices"];
  28582. this.nodes = this.sectors["active"][sectorId]["nodes"];
  28583. this.edges = this.sectors["active"][sectorId]["edges"];
  28584. };
  28585. /**
  28586. * This function sets the global references to nodes, edges and nodeIndices back to
  28587. * those of the supplied active sector.
  28588. *
  28589. * @private
  28590. */
  28591. exports._switchToSupportSector = function() {
  28592. this.nodeIndices = this.sectors["support"]["nodeIndices"];
  28593. this.nodes = this.sectors["support"]["nodes"];
  28594. this.edges = this.sectors["support"]["edges"];
  28595. };
  28596. /**
  28597. * This function sets the global references to nodes, edges and nodeIndices back to
  28598. * those of the supplied frozen sector.
  28599. *
  28600. * @param sectorId
  28601. * @private
  28602. */
  28603. exports._switchToFrozenSector = function(sectorId) {
  28604. this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"];
  28605. this.nodes = this.sectors["frozen"][sectorId]["nodes"];
  28606. this.edges = this.sectors["frozen"][sectorId]["edges"];
  28607. };
  28608. /**
  28609. * This function sets the global references to nodes, edges and nodeIndices back to
  28610. * those of the currently active sector.
  28611. *
  28612. * @private
  28613. */
  28614. exports._loadLatestSector = function() {
  28615. this._switchToSector(this._sector());
  28616. };
  28617. /**
  28618. * This function returns the currently active sector Id
  28619. *
  28620. * @returns {String}
  28621. * @private
  28622. */
  28623. exports._sector = function() {
  28624. return this.activeSector[this.activeSector.length-1];
  28625. };
  28626. /**
  28627. * This function returns the previously active sector Id
  28628. *
  28629. * @returns {String}
  28630. * @private
  28631. */
  28632. exports._previousSector = function() {
  28633. if (this.activeSector.length > 1) {
  28634. return this.activeSector[this.activeSector.length-2];
  28635. }
  28636. else {
  28637. throw new TypeError('there are not enough sectors in the this.activeSector array.');
  28638. }
  28639. };
  28640. /**
  28641. * We add the active sector at the end of the this.activeSector array
  28642. * This ensures it is the currently active sector returned by _sector() and it reaches the top
  28643. * of the activeSector stack. When we reverse our steps we move from the end to the beginning of this stack.
  28644. *
  28645. * @param newId
  28646. * @private
  28647. */
  28648. exports._setActiveSector = function(newId) {
  28649. this.activeSector.push(newId);
  28650. };
  28651. /**
  28652. * We remove the currently active sector id from the active sector stack. This happens when
  28653. * we reactivate the previously active sector
  28654. *
  28655. * @private
  28656. */
  28657. exports._forgetLastSector = function() {
  28658. this.activeSector.pop();
  28659. };
  28660. /**
  28661. * This function creates a new active sector with the supplied newId. This newId
  28662. * is the expanding node id.
  28663. *
  28664. * @param {String} newId | Id of the new active sector
  28665. * @private
  28666. */
  28667. exports._createNewSector = function(newId) {
  28668. // create the new sector
  28669. this.sectors["active"][newId] = {"nodes":{},
  28670. "edges":{},
  28671. "nodeIndices":[],
  28672. "formationScale": this.scale,
  28673. "drawingNode": undefined};
  28674. // create the new sector render node. This gives visual feedback that you are in a new sector.
  28675. this.sectors["active"][newId]['drawingNode'] = new Node(
  28676. {id:newId,
  28677. color: {
  28678. background: "#eaefef",
  28679. border: "495c5e"
  28680. }
  28681. },{},{},this.constants);
  28682. this.sectors["active"][newId]['drawingNode'].clusterSize = 2;
  28683. };
  28684. /**
  28685. * This function removes the currently active sector. This is called when we create a new
  28686. * active sector.
  28687. *
  28688. * @param {String} sectorId | Id of the active sector that will be removed
  28689. * @private
  28690. */
  28691. exports._deleteActiveSector = function(sectorId) {
  28692. delete this.sectors["active"][sectorId];
  28693. };
  28694. /**
  28695. * This function removes the currently active sector. This is called when we reactivate
  28696. * the previously active sector.
  28697. *
  28698. * @param {String} sectorId | Id of the active sector that will be removed
  28699. * @private
  28700. */
  28701. exports._deleteFrozenSector = function(sectorId) {
  28702. delete this.sectors["frozen"][sectorId];
  28703. };
  28704. /**
  28705. * Freezing an active sector means moving it from the "active" object to the "frozen" object.
  28706. * We copy the references, then delete the active entree.
  28707. *
  28708. * @param sectorId
  28709. * @private
  28710. */
  28711. exports._freezeSector = function(sectorId) {
  28712. // we move the set references from the active to the frozen stack.
  28713. this.sectors["frozen"][sectorId] = this.sectors["active"][sectorId];
  28714. // we have moved the sector data into the frozen set, we now remove it from the active set
  28715. this._deleteActiveSector(sectorId);
  28716. };
  28717. /**
  28718. * This is the reverse operation of _freezeSector. Activating means moving the sector from the "frozen"
  28719. * object to the "active" object.
  28720. *
  28721. * @param sectorId
  28722. * @private
  28723. */
  28724. exports._activateSector = function(sectorId) {
  28725. // we move the set references from the frozen to the active stack.
  28726. this.sectors["active"][sectorId] = this.sectors["frozen"][sectorId];
  28727. // we have moved the sector data into the active set, we now remove it from the frozen stack
  28728. this._deleteFrozenSector(sectorId);
  28729. };
  28730. /**
  28731. * This function merges the data from the currently active sector with a frozen sector. This is used
  28732. * in the process of reverting back to the previously active sector.
  28733. * The data that is placed in the frozen (the previously active) sector is the node that has been removed from it
  28734. * upon the creation of a new active sector.
  28735. *
  28736. * @param sectorId
  28737. * @private
  28738. */
  28739. exports._mergeThisWithFrozen = function(sectorId) {
  28740. // copy all nodes
  28741. for (var nodeId in this.nodes) {
  28742. if (this.nodes.hasOwnProperty(nodeId)) {
  28743. this.sectors["frozen"][sectorId]["nodes"][nodeId] = this.nodes[nodeId];
  28744. }
  28745. }
  28746. // copy all edges (if not fully clustered, else there are no edges)
  28747. for (var edgeId in this.edges) {
  28748. if (this.edges.hasOwnProperty(edgeId)) {
  28749. this.sectors["frozen"][sectorId]["edges"][edgeId] = this.edges[edgeId];
  28750. }
  28751. }
  28752. // merge the nodeIndices
  28753. for (var i = 0; i < this.nodeIndices.length; i++) {
  28754. this.sectors["frozen"][sectorId]["nodeIndices"].push(this.nodeIndices[i]);
  28755. }
  28756. };
  28757. /**
  28758. * This clusters the sector to one cluster. It was a single cluster before this process started so
  28759. * we revert to that state. The clusterToFit function with a maximum size of 1 node does this.
  28760. *
  28761. * @private
  28762. */
  28763. exports._collapseThisToSingleCluster = function() {
  28764. this.clusterToFit(1,false);
  28765. };
  28766. /**
  28767. * We create a new active sector from the node that we want to open.
  28768. *
  28769. * @param node
  28770. * @private
  28771. */
  28772. exports._addSector = function(node) {
  28773. // this is the currently active sector
  28774. var sector = this._sector();
  28775. // // this should allow me to select nodes from a frozen set.
  28776. // if (this.sectors['active'][sector]["nodes"].hasOwnProperty(node.id)) {
  28777. // console.log("the node is part of the active sector");
  28778. // }
  28779. // else {
  28780. // console.log("I dont know what the fuck happened!!");
  28781. // }
  28782. // when we switch to a new sector, we remove the node that will be expanded from the current nodes list.
  28783. delete this.nodes[node.id];
  28784. var unqiueIdentifier = util.randomUUID();
  28785. // we fully freeze the currently active sector
  28786. this._freezeSector(sector);
  28787. // we create a new active sector. This sector has the Id of the node to ensure uniqueness
  28788. this._createNewSector(unqiueIdentifier);
  28789. // we add the active sector to the sectors array to be able to revert these steps later on
  28790. this._setActiveSector(unqiueIdentifier);
  28791. // we redirect the global references to the new sector's references. this._sector() now returns unqiueIdentifier
  28792. this._switchToSector(this._sector());
  28793. // finally we add the node we removed from our previous active sector to the new active sector
  28794. this.nodes[node.id] = node;
  28795. };
  28796. /**
  28797. * We close the sector that is currently open and revert back to the one before.
  28798. * If the active sector is the "default" sector, nothing happens.
  28799. *
  28800. * @private
  28801. */
  28802. exports._collapseSector = function() {
  28803. // the currently active sector
  28804. var sector = this._sector();
  28805. // we cannot collapse the default sector
  28806. if (sector != "default") {
  28807. if ((this.nodeIndices.length == 1) ||
  28808. (this.sectors["active"][sector]["drawingNode"].width*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  28809. (this.sectors["active"][sector]["drawingNode"].height*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  28810. var previousSector = this._previousSector();
  28811. // we collapse the sector back to a single cluster
  28812. this._collapseThisToSingleCluster();
  28813. // we move the remaining nodes, edges and nodeIndices to the previous sector.
  28814. // This previous sector is the one we will reactivate
  28815. this._mergeThisWithFrozen(previousSector);
  28816. // the previously active (frozen) sector now has all the data from the currently active sector.
  28817. // we can now delete the active sector.
  28818. this._deleteActiveSector(sector);
  28819. // we activate the previously active (and currently frozen) sector.
  28820. this._activateSector(previousSector);
  28821. // we load the references from the newly active sector into the global references
  28822. this._switchToSector(previousSector);
  28823. // we forget the previously active sector because we reverted to the one before
  28824. this._forgetLastSector();
  28825. // finally, we update the node index list.
  28826. this._updateNodeIndexList();
  28827. // we refresh the list with calulation nodes and calculation node indices.
  28828. this._updateCalculationNodes();
  28829. }
  28830. }
  28831. };
  28832. /**
  28833. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  28834. *
  28835. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  28836. * | we dont pass the function itself because then the "this" is the window object
  28837. * | instead of the Network object
  28838. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  28839. * @private
  28840. */
  28841. exports._doInAllActiveSectors = function(runFunction,argument) {
  28842. var returnValues = [];
  28843. if (argument === undefined) {
  28844. for (var sector in this.sectors["active"]) {
  28845. if (this.sectors["active"].hasOwnProperty(sector)) {
  28846. // switch the global references to those of this sector
  28847. this._switchToActiveSector(sector);
  28848. returnValues.push( this[runFunction]() );
  28849. }
  28850. }
  28851. }
  28852. else {
  28853. for (var sector in this.sectors["active"]) {
  28854. if (this.sectors["active"].hasOwnProperty(sector)) {
  28855. // switch the global references to those of this sector
  28856. this._switchToActiveSector(sector);
  28857. var args = Array.prototype.splice.call(arguments, 1);
  28858. if (args.length > 1) {
  28859. returnValues.push( this[runFunction](args[0],args[1]) );
  28860. }
  28861. else {
  28862. returnValues.push( this[runFunction](argument) );
  28863. }
  28864. }
  28865. }
  28866. }
  28867. // we revert the global references back to our active sector
  28868. this._loadLatestSector();
  28869. return returnValues;
  28870. };
  28871. /**
  28872. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  28873. *
  28874. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  28875. * | we dont pass the function itself because then the "this" is the window object
  28876. * | instead of the Network object
  28877. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  28878. * @private
  28879. */
  28880. exports._doInSupportSector = function(runFunction,argument) {
  28881. var returnValues = false;
  28882. if (argument === undefined) {
  28883. this._switchToSupportSector();
  28884. returnValues = this[runFunction]();
  28885. }
  28886. else {
  28887. this._switchToSupportSector();
  28888. var args = Array.prototype.splice.call(arguments, 1);
  28889. if (args.length > 1) {
  28890. returnValues = this[runFunction](args[0],args[1]);
  28891. }
  28892. else {
  28893. returnValues = this[runFunction](argument);
  28894. }
  28895. }
  28896. // we revert the global references back to our active sector
  28897. this._loadLatestSector();
  28898. return returnValues;
  28899. };
  28900. /**
  28901. * This runs a function in all frozen sectors. This is used in the _redraw().
  28902. *
  28903. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  28904. * | we don't pass the function itself because then the "this" is the window object
  28905. * | instead of the Network object
  28906. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  28907. * @private
  28908. */
  28909. exports._doInAllFrozenSectors = function(runFunction,argument) {
  28910. if (argument === undefined) {
  28911. for (var sector in this.sectors["frozen"]) {
  28912. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  28913. // switch the global references to those of this sector
  28914. this._switchToFrozenSector(sector);
  28915. this[runFunction]();
  28916. }
  28917. }
  28918. }
  28919. else {
  28920. for (var sector in this.sectors["frozen"]) {
  28921. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  28922. // switch the global references to those of this sector
  28923. this._switchToFrozenSector(sector);
  28924. var args = Array.prototype.splice.call(arguments, 1);
  28925. if (args.length > 1) {
  28926. this[runFunction](args[0],args[1]);
  28927. }
  28928. else {
  28929. this[runFunction](argument);
  28930. }
  28931. }
  28932. }
  28933. }
  28934. this._loadLatestSector();
  28935. };
  28936. /**
  28937. * This runs a function in all sectors. This is used in the _redraw().
  28938. *
  28939. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  28940. * | we don't pass the function itself because then the "this" is the window object
  28941. * | instead of the Network object
  28942. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  28943. * @private
  28944. */
  28945. exports._doInAllSectors = function(runFunction,argument) {
  28946. var args = Array.prototype.splice.call(arguments, 1);
  28947. if (argument === undefined) {
  28948. this._doInAllActiveSectors(runFunction);
  28949. this._doInAllFrozenSectors(runFunction);
  28950. }
  28951. else {
  28952. if (args.length > 1) {
  28953. this._doInAllActiveSectors(runFunction,args[0],args[1]);
  28954. this._doInAllFrozenSectors(runFunction,args[0],args[1]);
  28955. }
  28956. else {
  28957. this._doInAllActiveSectors(runFunction,argument);
  28958. this._doInAllFrozenSectors(runFunction,argument);
  28959. }
  28960. }
  28961. };
  28962. /**
  28963. * This clears the nodeIndices list. We cannot use this.nodeIndices = [] because we would break the link with the
  28964. * active sector. Thus we clear the nodeIndices in the active sector, then reconnect the this.nodeIndices to it.
  28965. *
  28966. * @private
  28967. */
  28968. exports._clearNodeIndexList = function() {
  28969. var sector = this._sector();
  28970. this.sectors["active"][sector]["nodeIndices"] = [];
  28971. this.nodeIndices = this.sectors["active"][sector]["nodeIndices"];
  28972. };
  28973. /**
  28974. * Draw the encompassing sector node
  28975. *
  28976. * @param ctx
  28977. * @param sectorType
  28978. * @private
  28979. */
  28980. exports._drawSectorNodes = function(ctx,sectorType) {
  28981. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  28982. for (var sector in this.sectors[sectorType]) {
  28983. if (this.sectors[sectorType].hasOwnProperty(sector)) {
  28984. if (this.sectors[sectorType][sector]["drawingNode"] !== undefined) {
  28985. this._switchToSector(sector,sectorType);
  28986. minY = 1e9; maxY = -1e9; minX = 1e9; maxX = -1e9;
  28987. for (var nodeId in this.nodes) {
  28988. if (this.nodes.hasOwnProperty(nodeId)) {
  28989. node = this.nodes[nodeId];
  28990. node.resize(ctx);
  28991. if (minX > node.x - 0.5 * node.width) {minX = node.x - 0.5 * node.width;}
  28992. if (maxX < node.x + 0.5 * node.width) {maxX = node.x + 0.5 * node.width;}
  28993. if (minY > node.y - 0.5 * node.height) {minY = node.y - 0.5 * node.height;}
  28994. if (maxY < node.y + 0.5 * node.height) {maxY = node.y + 0.5 * node.height;}
  28995. }
  28996. }
  28997. node = this.sectors[sectorType][sector]["drawingNode"];
  28998. node.x = 0.5 * (maxX + minX);
  28999. node.y = 0.5 * (maxY + minY);
  29000. node.width = 2 * (node.x - minX);
  29001. node.height = 2 * (node.y - minY);
  29002. node.options.radius = Math.sqrt(Math.pow(0.5*node.width,2) + Math.pow(0.5*node.height,2));
  29003. node.setScale(this.scale);
  29004. node._drawCircle(ctx);
  29005. }
  29006. }
  29007. }
  29008. };
  29009. exports._drawAllSectorNodes = function(ctx) {
  29010. this._drawSectorNodes(ctx,"frozen");
  29011. this._drawSectorNodes(ctx,"active");
  29012. this._loadLatestSector();
  29013. };
  29014. /***/ },
  29015. /* 63 */
  29016. /***/ function(module, exports, __webpack_require__) {
  29017. var Node = __webpack_require__(40);
  29018. /**
  29019. * This function can be called from the _doInAllSectors function
  29020. *
  29021. * @param object
  29022. * @param overlappingNodes
  29023. * @private
  29024. */
  29025. exports._getNodesOverlappingWith = function(object, overlappingNodes) {
  29026. var nodes = this.nodes;
  29027. for (var nodeId in nodes) {
  29028. if (nodes.hasOwnProperty(nodeId)) {
  29029. if (nodes[nodeId].isOverlappingWith(object)) {
  29030. overlappingNodes.push(nodeId);
  29031. }
  29032. }
  29033. }
  29034. };
  29035. /**
  29036. * retrieve all nodes overlapping with given object
  29037. * @param {Object} object An object with parameters left, top, right, bottom
  29038. * @return {Number[]} An array with id's of the overlapping nodes
  29039. * @private
  29040. */
  29041. exports._getAllNodesOverlappingWith = function (object) {
  29042. var overlappingNodes = [];
  29043. this._doInAllActiveSectors("_getNodesOverlappingWith",object,overlappingNodes);
  29044. return overlappingNodes;
  29045. };
  29046. /**
  29047. * Return a position object in canvasspace from a single point in screenspace
  29048. *
  29049. * @param pointer
  29050. * @returns {{left: number, top: number, right: number, bottom: number}}
  29051. * @private
  29052. */
  29053. exports._pointerToPositionObject = function(pointer) {
  29054. var x = this._XconvertDOMtoCanvas(pointer.x);
  29055. var y = this._YconvertDOMtoCanvas(pointer.y);
  29056. return {
  29057. left: x,
  29058. top: y,
  29059. right: x,
  29060. bottom: y
  29061. };
  29062. };
  29063. /**
  29064. * Get the top node at the a specific point (like a click)
  29065. *
  29066. * @param {{x: Number, y: Number}} pointer
  29067. * @return {Node | null} node
  29068. * @private
  29069. */
  29070. exports._getNodeAt = function (pointer) {
  29071. // we first check if this is an navigation controls element
  29072. var positionObject = this._pointerToPositionObject(pointer);
  29073. var overlappingNodes = this._getAllNodesOverlappingWith(positionObject);
  29074. // if there are overlapping nodes, select the last one, this is the
  29075. // one which is drawn on top of the others
  29076. if (overlappingNodes.length > 0) {
  29077. return this.nodes[overlappingNodes[overlappingNodes.length - 1]];
  29078. }
  29079. else {
  29080. return null;
  29081. }
  29082. };
  29083. /**
  29084. * retrieve all edges overlapping with given object, selector is around center
  29085. * @param {Object} object An object with parameters left, top, right, bottom
  29086. * @return {Number[]} An array with id's of the overlapping nodes
  29087. * @private
  29088. */
  29089. exports._getEdgesOverlappingWith = function (object, overlappingEdges) {
  29090. var edges = this.edges;
  29091. for (var edgeId in edges) {
  29092. if (edges.hasOwnProperty(edgeId)) {
  29093. if (edges[edgeId].isOverlappingWith(object)) {
  29094. overlappingEdges.push(edgeId);
  29095. }
  29096. }
  29097. }
  29098. };
  29099. /**
  29100. * retrieve all nodes overlapping with given object
  29101. * @param {Object} object An object with parameters left, top, right, bottom
  29102. * @return {Number[]} An array with id's of the overlapping nodes
  29103. * @private
  29104. */
  29105. exports._getAllEdgesOverlappingWith = function (object) {
  29106. var overlappingEdges = [];
  29107. this._doInAllActiveSectors("_getEdgesOverlappingWith",object,overlappingEdges);
  29108. return overlappingEdges;
  29109. };
  29110. /**
  29111. * Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call
  29112. * _getNodeAt and _getEdgesAt, then priortize the selection to user preferences.
  29113. *
  29114. * @param pointer
  29115. * @returns {null}
  29116. * @private
  29117. */
  29118. exports._getEdgeAt = function(pointer) {
  29119. var positionObject = this._pointerToPositionObject(pointer);
  29120. var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject);
  29121. if (overlappingEdges.length > 0) {
  29122. return this.edges[overlappingEdges[overlappingEdges.length - 1]];
  29123. }
  29124. else {
  29125. return null;
  29126. }
  29127. };
  29128. /**
  29129. * Add object to the selection array.
  29130. *
  29131. * @param obj
  29132. * @private
  29133. */
  29134. exports._addToSelection = function(obj) {
  29135. if (obj instanceof Node) {
  29136. this.selectionObj.nodes[obj.id] = obj;
  29137. }
  29138. else {
  29139. this.selectionObj.edges[obj.id] = obj;
  29140. }
  29141. };
  29142. /**
  29143. * Add object to the selection array.
  29144. *
  29145. * @param obj
  29146. * @private
  29147. */
  29148. exports._addToHover = function(obj) {
  29149. if (obj instanceof Node) {
  29150. this.hoverObj.nodes[obj.id] = obj;
  29151. }
  29152. else {
  29153. this.hoverObj.edges[obj.id] = obj;
  29154. }
  29155. };
  29156. /**
  29157. * Remove a single option from selection.
  29158. *
  29159. * @param {Object} obj
  29160. * @private
  29161. */
  29162. exports._removeFromSelection = function(obj) {
  29163. if (obj instanceof Node) {
  29164. delete this.selectionObj.nodes[obj.id];
  29165. }
  29166. else {
  29167. delete this.selectionObj.edges[obj.id];
  29168. }
  29169. };
  29170. /**
  29171. * Unselect all. The selectionObj is useful for this.
  29172. *
  29173. * @param {Boolean} [doNotTrigger] | ignore trigger
  29174. * @private
  29175. */
  29176. exports._unselectAll = function(doNotTrigger) {
  29177. if (doNotTrigger === undefined) {
  29178. doNotTrigger = false;
  29179. }
  29180. for(var nodeId in this.selectionObj.nodes) {
  29181. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  29182. this.selectionObj.nodes[nodeId].unselect();
  29183. }
  29184. }
  29185. for(var edgeId in this.selectionObj.edges) {
  29186. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  29187. this.selectionObj.edges[edgeId].unselect();
  29188. }
  29189. }
  29190. this.selectionObj = {nodes:{},edges:{}};
  29191. if (doNotTrigger == false) {
  29192. this.emit('select', this.getSelection());
  29193. }
  29194. };
  29195. /**
  29196. * Unselect all clusters. The selectionObj is useful for this.
  29197. *
  29198. * @param {Boolean} [doNotTrigger] | ignore trigger
  29199. * @private
  29200. */
  29201. exports._unselectClusters = function(doNotTrigger) {
  29202. if (doNotTrigger === undefined) {
  29203. doNotTrigger = false;
  29204. }
  29205. for (var nodeId in this.selectionObj.nodes) {
  29206. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  29207. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  29208. this.selectionObj.nodes[nodeId].unselect();
  29209. this._removeFromSelection(this.selectionObj.nodes[nodeId]);
  29210. }
  29211. }
  29212. }
  29213. if (doNotTrigger == false) {
  29214. this.emit('select', this.getSelection());
  29215. }
  29216. };
  29217. /**
  29218. * return the number of selected nodes
  29219. *
  29220. * @returns {number}
  29221. * @private
  29222. */
  29223. exports._getSelectedNodeCount = function() {
  29224. var count = 0;
  29225. for (var nodeId in this.selectionObj.nodes) {
  29226. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  29227. count += 1;
  29228. }
  29229. }
  29230. return count;
  29231. };
  29232. /**
  29233. * return the selected node
  29234. *
  29235. * @returns {number}
  29236. * @private
  29237. */
  29238. exports._getSelectedNode = function() {
  29239. for (var nodeId in this.selectionObj.nodes) {
  29240. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  29241. return this.selectionObj.nodes[nodeId];
  29242. }
  29243. }
  29244. return null;
  29245. };
  29246. /**
  29247. * return the selected edge
  29248. *
  29249. * @returns {number}
  29250. * @private
  29251. */
  29252. exports._getSelectedEdge = function() {
  29253. for (var edgeId in this.selectionObj.edges) {
  29254. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  29255. return this.selectionObj.edges[edgeId];
  29256. }
  29257. }
  29258. return null;
  29259. };
  29260. /**
  29261. * return the number of selected edges
  29262. *
  29263. * @returns {number}
  29264. * @private
  29265. */
  29266. exports._getSelectedEdgeCount = function() {
  29267. var count = 0;
  29268. for (var edgeId in this.selectionObj.edges) {
  29269. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  29270. count += 1;
  29271. }
  29272. }
  29273. return count;
  29274. };
  29275. /**
  29276. * return the number of selected objects.
  29277. *
  29278. * @returns {number}
  29279. * @private
  29280. */
  29281. exports._getSelectedObjectCount = function() {
  29282. var count = 0;
  29283. for(var nodeId in this.selectionObj.nodes) {
  29284. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  29285. count += 1;
  29286. }
  29287. }
  29288. for(var edgeId in this.selectionObj.edges) {
  29289. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  29290. count += 1;
  29291. }
  29292. }
  29293. return count;
  29294. };
  29295. /**
  29296. * Check if anything is selected
  29297. *
  29298. * @returns {boolean}
  29299. * @private
  29300. */
  29301. exports._selectionIsEmpty = function() {
  29302. for(var nodeId in this.selectionObj.nodes) {
  29303. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  29304. return false;
  29305. }
  29306. }
  29307. for(var edgeId in this.selectionObj.edges) {
  29308. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  29309. return false;
  29310. }
  29311. }
  29312. return true;
  29313. };
  29314. /**
  29315. * check if one of the selected nodes is a cluster.
  29316. *
  29317. * @returns {boolean}
  29318. * @private
  29319. */
  29320. exports._clusterInSelection = function() {
  29321. for(var nodeId in this.selectionObj.nodes) {
  29322. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  29323. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  29324. return true;
  29325. }
  29326. }
  29327. }
  29328. return false;
  29329. };
  29330. /**
  29331. * select the edges connected to the node that is being selected
  29332. *
  29333. * @param {Node} node
  29334. * @private
  29335. */
  29336. exports._selectConnectedEdges = function(node) {
  29337. for (var i = 0; i < node.dynamicEdges.length; i++) {
  29338. var edge = node.dynamicEdges[i];
  29339. edge.select();
  29340. this._addToSelection(edge);
  29341. }
  29342. };
  29343. /**
  29344. * select the edges connected to the node that is being selected
  29345. *
  29346. * @param {Node} node
  29347. * @private
  29348. */
  29349. exports._hoverConnectedEdges = function(node) {
  29350. for (var i = 0; i < node.dynamicEdges.length; i++) {
  29351. var edge = node.dynamicEdges[i];
  29352. edge.hover = true;
  29353. this._addToHover(edge);
  29354. }
  29355. };
  29356. /**
  29357. * unselect the edges connected to the node that is being selected
  29358. *
  29359. * @param {Node} node
  29360. * @private
  29361. */
  29362. exports._unselectConnectedEdges = function(node) {
  29363. for (var i = 0; i < node.dynamicEdges.length; i++) {
  29364. var edge = node.dynamicEdges[i];
  29365. edge.unselect();
  29366. this._removeFromSelection(edge);
  29367. }
  29368. };
  29369. /**
  29370. * This is called when someone clicks on a node. either select or deselect it.
  29371. * If there is an existing selection and we don't want to append to it, clear the existing selection
  29372. *
  29373. * @param {Node || Edge} object
  29374. * @param {Boolean} append
  29375. * @param {Boolean} [doNotTrigger] | ignore trigger
  29376. * @private
  29377. */
  29378. exports._selectObject = function(object, append, doNotTrigger, highlightEdges, overrideSelectable) {
  29379. if (doNotTrigger === undefined) {
  29380. doNotTrigger = false;
  29381. }
  29382. if (highlightEdges === undefined) {
  29383. highlightEdges = true;
  29384. }
  29385. if (this._selectionIsEmpty() == false && append == false && this.forceAppendSelection == false) {
  29386. this._unselectAll(true);
  29387. }
  29388. // selectable allows the object to be selected. Override can be used if needed to bypass this.
  29389. if (object.selected == false && (this.constants.selectable == true || overrideSelectable)) {
  29390. object.select();
  29391. this._addToSelection(object);
  29392. if (object instanceof Node && this.blockConnectingEdgeSelection == false && highlightEdges == true) {
  29393. this._selectConnectedEdges(object);
  29394. }
  29395. }
  29396. // do not select the object if selectable is false, only add it to selection to allow drag to work
  29397. else if (object.selected == false) {
  29398. this._addToSelection(object);
  29399. doNotTrigger = true;
  29400. }
  29401. else {
  29402. object.unselect();
  29403. this._removeFromSelection(object);
  29404. }
  29405. if (doNotTrigger == false) {
  29406. this.emit('select', this.getSelection());
  29407. }
  29408. };
  29409. /**
  29410. * This is called when someone clicks on a node. either select or deselect it.
  29411. * If there is an existing selection and we don't want to append to it, clear the existing selection
  29412. *
  29413. * @param {Node || Edge} object
  29414. * @private
  29415. */
  29416. exports._blurObject = function(object) {
  29417. if (object.hover == true) {
  29418. object.hover = false;
  29419. this.emit("blurNode",{node:object.id});
  29420. }
  29421. };
  29422. /**
  29423. * This is called when someone clicks on a node. either select or deselect it.
  29424. * If there is an existing selection and we don't want to append to it, clear the existing selection
  29425. *
  29426. * @param {Node || Edge} object
  29427. * @private
  29428. */
  29429. exports._hoverObject = function(object) {
  29430. if (object.hover == false) {
  29431. object.hover = true;
  29432. this._addToHover(object);
  29433. if (object instanceof Node) {
  29434. this.emit("hoverNode",{node:object.id});
  29435. }
  29436. }
  29437. if (object instanceof Node) {
  29438. this._hoverConnectedEdges(object);
  29439. }
  29440. };
  29441. /**
  29442. * handles the selection part of the touch, only for navigation controls elements;
  29443. * Touch is triggered before tap, also before hold. Hold triggers after a while.
  29444. * This is the most responsive solution
  29445. *
  29446. * @param {Object} pointer
  29447. * @private
  29448. */
  29449. exports._handleTouch = function(pointer) {
  29450. };
  29451. /**
  29452. * handles the selection part of the tap;
  29453. *
  29454. * @param {Object} pointer
  29455. * @private
  29456. */
  29457. exports._handleTap = function(pointer) {
  29458. var node = this._getNodeAt(pointer);
  29459. if (node != null) {
  29460. this._selectObject(node, false);
  29461. }
  29462. else {
  29463. var edge = this._getEdgeAt(pointer);
  29464. if (edge != null) {
  29465. this._selectObject(edge, false);
  29466. }
  29467. else {
  29468. this._unselectAll();
  29469. }
  29470. }
  29471. var properties = this.getSelection();
  29472. properties['pointer'] = {
  29473. DOM: {x: pointer.x, y: pointer.y},
  29474. canvas: {x: this._XconvertDOMtoCanvas(pointer.x), y: this._YconvertDOMtoCanvas(pointer.y)}
  29475. }
  29476. this.emit("click", properties);
  29477. this._requestRedraw();
  29478. };
  29479. /**
  29480. * handles the selection part of the double tap and opens a cluster if needed
  29481. *
  29482. * @param {Object} pointer
  29483. * @private
  29484. */
  29485. exports._handleDoubleTap = function(pointer) {
  29486. var node = this._getNodeAt(pointer);
  29487. if (node != null && node !== undefined) {
  29488. // we reset the areaCenter here so the opening of the node will occur
  29489. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  29490. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  29491. this.openCluster(node);
  29492. }
  29493. var properties = this.getSelection();
  29494. properties['pointer'] = {
  29495. DOM: {x: pointer.x, y: pointer.y},
  29496. canvas: {x: this._XconvertDOMtoCanvas(pointer.x), y: this._YconvertDOMtoCanvas(pointer.y)}
  29497. }
  29498. this.emit("doubleClick", properties);
  29499. };
  29500. /**
  29501. * Handle the onHold selection part
  29502. *
  29503. * @param pointer
  29504. * @private
  29505. */
  29506. exports._handleOnHold = function(pointer) {
  29507. var node = this._getNodeAt(pointer);
  29508. if (node != null) {
  29509. this._selectObject(node,true);
  29510. }
  29511. else {
  29512. var edge = this._getEdgeAt(pointer);
  29513. if (edge != null) {
  29514. this._selectObject(edge,true);
  29515. }
  29516. }
  29517. this._requestRedraw();
  29518. };
  29519. /**
  29520. * handle the onRelease event. These functions are here for the navigation controls module
  29521. * and data manipulation module.
  29522. *
  29523. * @private
  29524. */
  29525. exports._handleOnRelease = function(pointer) {
  29526. this._manipulationReleaseOverload(pointer);
  29527. this._navigationReleaseOverload(pointer);
  29528. };
  29529. exports._manipulationReleaseOverload = function (pointer) {};
  29530. exports._navigationReleaseOverload = function (pointer) {};
  29531. /**
  29532. *
  29533. * retrieve the currently selected objects
  29534. * @return {{nodes: Array.<String>, edges: Array.<String>}} selection
  29535. */
  29536. exports.getSelection = function() {
  29537. var nodeIds = this.getSelectedNodes();
  29538. var edgeIds = this.getSelectedEdges();
  29539. return {nodes:nodeIds, edges:edgeIds};
  29540. };
  29541. /**
  29542. *
  29543. * retrieve the currently selected nodes
  29544. * @return {String[]} selection An array with the ids of the
  29545. * selected nodes.
  29546. */
  29547. exports.getSelectedNodes = function() {
  29548. var idArray = [];
  29549. if (this.constants.selectable == true) {
  29550. for (var nodeId in this.selectionObj.nodes) {
  29551. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  29552. idArray.push(nodeId);
  29553. }
  29554. }
  29555. }
  29556. return idArray
  29557. };
  29558. /**
  29559. *
  29560. * retrieve the currently selected edges
  29561. * @return {Array} selection An array with the ids of the
  29562. * selected nodes.
  29563. */
  29564. exports.getSelectedEdges = function() {
  29565. var idArray = [];
  29566. if (this.constants.selectable == true) {
  29567. for (var edgeId in this.selectionObj.edges) {
  29568. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  29569. idArray.push(edgeId);
  29570. }
  29571. }
  29572. }
  29573. return idArray;
  29574. };
  29575. /**
  29576. * select zero or more nodes DEPRICATED
  29577. * @param {Number[] | String[]} selection An array with the ids of the
  29578. * selected nodes.
  29579. */
  29580. exports.setSelection = function() {
  29581. console.log("setSelection is deprecated. Please use selectNodes instead.")
  29582. };
  29583. /**
  29584. * select zero or more nodes with the option to highlight edges
  29585. * @param {Number[] | String[]} selection An array with the ids of the
  29586. * selected nodes.
  29587. * @param {boolean} [highlightEdges]
  29588. */
  29589. exports.selectNodes = function(selection, highlightEdges) {
  29590. var i, iMax, id;
  29591. if (!selection || (selection.length == undefined))
  29592. throw 'Selection must be an array with ids';
  29593. // first unselect any selected node
  29594. this._unselectAll(true);
  29595. for (i = 0, iMax = selection.length; i < iMax; i++) {
  29596. id = selection[i];
  29597. var node = this.nodes[id];
  29598. if (!node) {
  29599. throw new RangeError('Node with id "' + id + '" not found');
  29600. }
  29601. this._selectObject(node,true,true,highlightEdges,true);
  29602. }
  29603. this.redraw();
  29604. };
  29605. /**
  29606. * select zero or more edges
  29607. * @param {Number[] | String[]} selection An array with the ids of the
  29608. * selected nodes.
  29609. */
  29610. exports.selectEdges = function(selection) {
  29611. var i, iMax, id;
  29612. if (!selection || (selection.length == undefined))
  29613. throw 'Selection must be an array with ids';
  29614. // first unselect any selected node
  29615. this._unselectAll(true);
  29616. for (i = 0, iMax = selection.length; i < iMax; i++) {
  29617. id = selection[i];
  29618. var edge = this.edges[id];
  29619. if (!edge) {
  29620. throw new RangeError('Edge with id "' + id + '" not found');
  29621. }
  29622. this._selectObject(edge,true,true,false,true);
  29623. }
  29624. this.redraw();
  29625. };
  29626. /**
  29627. * Validate the selection: remove ids of nodes which no longer exist
  29628. * @private
  29629. */
  29630. exports._updateSelection = function () {
  29631. for(var nodeId in this.selectionObj.nodes) {
  29632. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  29633. if (!this.nodes.hasOwnProperty(nodeId)) {
  29634. delete this.selectionObj.nodes[nodeId];
  29635. }
  29636. }
  29637. }
  29638. for(var edgeId in this.selectionObj.edges) {
  29639. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  29640. if (!this.edges.hasOwnProperty(edgeId)) {
  29641. delete this.selectionObj.edges[edgeId];
  29642. }
  29643. }
  29644. }
  29645. };
  29646. /***/ },
  29647. /* 64 */
  29648. /***/ function(module, exports, __webpack_require__) {
  29649. var util = __webpack_require__(1);
  29650. var Node = __webpack_require__(40);
  29651. var Edge = __webpack_require__(37);
  29652. var Hammer = __webpack_require__(45);
  29653. /**
  29654. * clears the toolbar div element of children
  29655. *
  29656. * @private
  29657. */
  29658. exports._clearManipulatorBar = function() {
  29659. this._recursiveDOMDelete(this.manipulationDiv);
  29660. this.manipulationDOM = {};
  29661. this._cleanManipulatorHammers();
  29662. this._manipulationReleaseOverload = function () {};
  29663. delete this.sectors['support']['nodes']['targetNode'];
  29664. delete this.sectors['support']['nodes']['targetViaNode'];
  29665. this.controlNodesActive = false;
  29666. this.freezeSimulation(false);
  29667. };
  29668. exports._cleanManipulatorHammers = function() {
  29669. // clean hammer bindings
  29670. if (this.manipulationHammers.length != 0) {
  29671. for (var i = 0; i < this.manipulationHammers.length; i++) {
  29672. this.manipulationHammers[i].dispose();
  29673. }
  29674. this.manipulationHammers = [];
  29675. }
  29676. };
  29677. /**
  29678. * Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore
  29679. * these functions to their original functionality, we saved them in this.cachedFunctions.
  29680. * This function restores these functions to their original function.
  29681. *
  29682. * @private
  29683. */
  29684. exports._restoreOverloadedFunctions = function() {
  29685. for (var functionName in this.cachedFunctions) {
  29686. if (this.cachedFunctions.hasOwnProperty(functionName)) {
  29687. this[functionName] = this.cachedFunctions[functionName];
  29688. delete this.cachedFunctions[functionName];
  29689. }
  29690. }
  29691. };
  29692. /**
  29693. * Enable or disable edit-mode.
  29694. *
  29695. * @private
  29696. */
  29697. exports._toggleEditMode = function() {
  29698. this.editMode = !this.editMode;
  29699. var toolbar = this.manipulationDiv;
  29700. var closeDiv = this.closeDiv;
  29701. var editModeDiv = this.editModeDiv;
  29702. if (this.editMode == true) {
  29703. toolbar.style.display="block";
  29704. closeDiv.style.display="block";
  29705. editModeDiv.style.display="none";
  29706. this._bindHammerToDiv(closeDiv,'_toggleEditMode');
  29707. }
  29708. else {
  29709. toolbar.style.display="none";
  29710. closeDiv.style.display="none";
  29711. editModeDiv.style.display="block";
  29712. }
  29713. this._createManipulatorBar()
  29714. };
  29715. /**
  29716. * main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
  29717. *
  29718. * @private
  29719. */
  29720. exports._createManipulatorBar = function() {
  29721. // remove bound functions
  29722. if (this.boundFunction) {
  29723. this.off('select', this.boundFunction);
  29724. }
  29725. this._cleanManipulatorHammers();
  29726. var locale = this.constants.locales[this.constants.locale];
  29727. if (this.edgeBeingEdited !== undefined) {
  29728. this.edgeBeingEdited._disableControlNodes();
  29729. this.edgeBeingEdited = undefined;
  29730. this.selectedControlNode = null;
  29731. this.controlNodesActive = false;
  29732. this._redraw();
  29733. }
  29734. // restore overloaded functions
  29735. this._restoreOverloadedFunctions();
  29736. // resume calculation
  29737. this.freezeSimulation(false);
  29738. // reset global variables
  29739. this.blockConnectingEdgeSelection = false;
  29740. this.forceAppendSelection = false;
  29741. this.manipulationDOM = {};
  29742. if (this.editMode == true) {
  29743. while (this.manipulationDiv.hasChildNodes()) {
  29744. this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
  29745. }
  29746. this.manipulationDOM['addNodeSpan'] = document.createElement('div');
  29747. this.manipulationDOM['addNodeSpan'].className = 'network-manipulationUI add';
  29748. this.manipulationDOM['addNodeLabelSpan'] = document.createElement('div');
  29749. this.manipulationDOM['addNodeLabelSpan'].className = 'network-manipulationLabel';
  29750. this.manipulationDOM['addNodeLabelSpan'].innerHTML = locale['addNode'];
  29751. this.manipulationDOM['addNodeSpan'].appendChild(this.manipulationDOM['addNodeLabelSpan']);
  29752. this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
  29753. this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
  29754. this.manipulationDOM['addEdgeSpan'] = document.createElement('div');
  29755. this.manipulationDOM['addEdgeSpan'].className = 'network-manipulationUI connect';
  29756. this.manipulationDOM['addEdgeLabelSpan'] = document.createElement('div');
  29757. this.manipulationDOM['addEdgeLabelSpan'].className = 'network-manipulationLabel';
  29758. this.manipulationDOM['addEdgeLabelSpan'].innerHTML = locale['addEdge'];
  29759. this.manipulationDOM['addEdgeSpan'].appendChild(this.manipulationDOM['addEdgeLabelSpan']);
  29760. this.manipulationDiv.appendChild(this.manipulationDOM['addNodeSpan']);
  29761. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
  29762. this.manipulationDiv.appendChild(this.manipulationDOM['addEdgeSpan']);
  29763. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  29764. this.manipulationDOM['seperatorLineDiv2'] = document.createElement('div');
  29765. this.manipulationDOM['seperatorLineDiv2'].className = 'network-seperatorLine';
  29766. this.manipulationDOM['editNodeSpan'] = document.createElement('div');
  29767. this.manipulationDOM['editNodeSpan'].className = 'network-manipulationUI edit node';
  29768. this.manipulationDOM['editNodeLabelSpan'] = document.createElement('div');
  29769. this.manipulationDOM['editNodeLabelSpan'].className = 'network-manipulationLabel';
  29770. this.manipulationDOM['editNodeLabelSpan'].innerHTML = locale['editNode'];
  29771. this.manipulationDOM['editNodeSpan'].appendChild(this.manipulationDOM['editNodeLabelSpan']);
  29772. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv2']);
  29773. this.manipulationDiv.appendChild(this.manipulationDOM['editNodeSpan']);
  29774. }
  29775. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  29776. this.manipulationDOM['seperatorLineDiv3'] = document.createElement('div');
  29777. this.manipulationDOM['seperatorLineDiv3'].className = 'network-seperatorLine';
  29778. this.manipulationDOM['editEdgeSpan'] = document.createElement('div');
  29779. this.manipulationDOM['editEdgeSpan'].className = 'network-manipulationUI edit edge';
  29780. this.manipulationDOM['editEdgeLabelSpan'] = document.createElement('div');
  29781. this.manipulationDOM['editEdgeLabelSpan'].className = 'network-manipulationLabel';
  29782. this.manipulationDOM['editEdgeLabelSpan'].innerHTML = locale['editEdge'];
  29783. this.manipulationDOM['editEdgeSpan'].appendChild(this.manipulationDOM['editEdgeLabelSpan']);
  29784. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv3']);
  29785. this.manipulationDiv.appendChild(this.manipulationDOM['editEdgeSpan']);
  29786. }
  29787. if (this._selectionIsEmpty() == false) {
  29788. this.manipulationDOM['seperatorLineDiv4'] = document.createElement('div');
  29789. this.manipulationDOM['seperatorLineDiv4'].className = 'network-seperatorLine';
  29790. this.manipulationDOM['deleteSpan'] = document.createElement('div');
  29791. this.manipulationDOM['deleteSpan'].className = 'network-manipulationUI delete';
  29792. this.manipulationDOM['deleteLabelSpan'] = document.createElement('div');
  29793. this.manipulationDOM['deleteLabelSpan'].className = 'network-manipulationLabel';
  29794. this.manipulationDOM['deleteLabelSpan'].innerHTML = locale['del'];
  29795. this.manipulationDOM['deleteSpan'].appendChild(this.manipulationDOM['deleteLabelSpan']);
  29796. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv4']);
  29797. this.manipulationDiv.appendChild(this.manipulationDOM['deleteSpan']);
  29798. }
  29799. // bind the icons
  29800. this._bindHammerToDiv(this.manipulationDOM['addNodeSpan'],'_createAddNodeToolbar');
  29801. this._bindHammerToDiv(this.manipulationDOM['addEdgeSpan'],'_createAddEdgeToolbar');
  29802. this._bindHammerToDiv(this.closeDiv,'_toggleEditMode');
  29803. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  29804. this._bindHammerToDiv(this.manipulationDOM['editNodeSpan'],'_editNode');
  29805. }
  29806. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  29807. this._bindHammerToDiv(this.manipulationDOM['editEdgeSpan'],'_createEditEdgeToolbar');
  29808. }
  29809. if (this._selectionIsEmpty() == false) {
  29810. this._bindHammerToDiv(this.manipulationDOM['deleteSpan'],'_deleteSelected');
  29811. }
  29812. var me = this;
  29813. this.boundFunction = me._createManipulatorBar;
  29814. this.on('select', this.boundFunction);
  29815. }
  29816. else {
  29817. while (this.editModeDiv.hasChildNodes()) {
  29818. this.editModeDiv.removeChild(this.editModeDiv.firstChild);
  29819. }
  29820. this.manipulationDOM['editModeSpan'] = document.createElement('div');
  29821. this.manipulationDOM['editModeSpan'].className = 'network-manipulationUI edit editmode';
  29822. this.manipulationDOM['editModeLabelSpan'] = document.createElement('div');
  29823. this.manipulationDOM['editModeLabelSpan'].className = 'network-manipulationLabel';
  29824. this.manipulationDOM['editModeLabelSpan'].innerHTML = locale['edit'];
  29825. this.manipulationDOM['editModeSpan'].appendChild(this.manipulationDOM['editModeLabelSpan']);
  29826. this.editModeDiv.appendChild(this.manipulationDOM['editModeSpan']);
  29827. this._bindHammerToDiv(this.manipulationDOM['editModeSpan'],'_toggleEditMode');
  29828. }
  29829. };
  29830. exports._bindHammerToDiv = function(domElement, funct) {
  29831. var hammer = Hammer(domElement, {prevent_default: true});
  29832. hammer.on('touch', this[funct].bind(this));
  29833. this.manipulationHammers.push(hammer);
  29834. }
  29835. /**
  29836. * Create the toolbar for adding Nodes
  29837. *
  29838. * @private
  29839. */
  29840. exports._createAddNodeToolbar = function() {
  29841. // clear the toolbar
  29842. this._clearManipulatorBar();
  29843. if (this.boundFunction) {
  29844. this.off('select', this.boundFunction);
  29845. }
  29846. var locale = this.constants.locales[this.constants.locale];
  29847. this.manipulationDOM = {};
  29848. this.manipulationDOM['backSpan'] = document.createElement('div');
  29849. this.manipulationDOM['backSpan'].className = 'network-manipulationUI back';
  29850. this.manipulationDOM['backLabelSpan'] = document.createElement('div');
  29851. this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel';
  29852. this.manipulationDOM['backLabelSpan'].innerHTML = locale['back'];
  29853. this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']);
  29854. this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
  29855. this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
  29856. this.manipulationDOM['descriptionSpan'] = document.createElement('div');
  29857. this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none';
  29858. this.manipulationDOM['descriptionLabelSpan'] = document.createElement('div');
  29859. this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel';
  29860. this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['addDescription'];
  29861. this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']);
  29862. this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']);
  29863. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
  29864. this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']);
  29865. // bind the icon
  29866. this._bindHammerToDiv(this.manipulationDOM['backSpan'],'_createManipulatorBar');
  29867. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  29868. var me = this;
  29869. this.boundFunction = me._addNode;
  29870. this.on('select', this.boundFunction);
  29871. };
  29872. /**
  29873. * create the toolbar to connect nodes
  29874. *
  29875. * @private
  29876. */
  29877. exports._createAddEdgeToolbar = function() {
  29878. // clear the toolbar
  29879. this._clearManipulatorBar();
  29880. this._unselectAll(true);
  29881. this.freezeSimulation(true);
  29882. if (this.boundFunction) {
  29883. this.off('select', this.boundFunction);
  29884. }
  29885. var locale = this.constants.locales[this.constants.locale];
  29886. this._unselectAll();
  29887. this.forceAppendSelection = false;
  29888. this.blockConnectingEdgeSelection = true;
  29889. this.manipulationDOM = {};
  29890. this.manipulationDOM['backSpan'] = document.createElement('div');
  29891. this.manipulationDOM['backSpan'].className = 'network-manipulationUI back';
  29892. this.manipulationDOM['backLabelSpan'] = document.createElement('div');
  29893. this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel';
  29894. this.manipulationDOM['backLabelSpan'].innerHTML = locale['back'];
  29895. this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']);
  29896. this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
  29897. this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
  29898. this.manipulationDOM['descriptionSpan'] = document.createElement('div');
  29899. this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none';
  29900. this.manipulationDOM['descriptionLabelSpan'] = document.createElement('div');
  29901. this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel';
  29902. this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['edgeDescription'];
  29903. this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']);
  29904. this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']);
  29905. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
  29906. this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']);
  29907. // bind the icon
  29908. this._bindHammerToDiv(this.manipulationDOM['backSpan'],'_createManipulatorBar');
  29909. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  29910. var me = this;
  29911. this.boundFunction = me._handleConnect;
  29912. this.on('select', this.boundFunction);
  29913. // temporarily overload functions
  29914. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  29915. this.cachedFunctions["_manipulationReleaseOverload"] = this._manipulationReleaseOverload;
  29916. this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
  29917. this.cachedFunctions["_handleDragEnd"] = this._handleDragEnd;
  29918. this.cachedFunctions["_handleOnHold"] = this._handleOnHold;
  29919. this._handleTouch = this._handleConnect;
  29920. this._manipulationReleaseOverload = function () {};
  29921. this._handleOnHold = function () {};
  29922. this._handleDragStart = function () {};
  29923. this._handleDragEnd = this._finishConnect;
  29924. // redraw to show the unselect
  29925. this._redraw();
  29926. };
  29927. /**
  29928. * create the toolbar to edit edges
  29929. *
  29930. * @private
  29931. */
  29932. exports._createEditEdgeToolbar = function() {
  29933. // clear the toolbar
  29934. this._clearManipulatorBar();
  29935. this.controlNodesActive = true;
  29936. if (this.boundFunction) {
  29937. this.off('select', this.boundFunction);
  29938. }
  29939. this.edgeBeingEdited = this._getSelectedEdge();
  29940. this.edgeBeingEdited._enableControlNodes();
  29941. var locale = this.constants.locales[this.constants.locale];
  29942. this.manipulationDOM = {};
  29943. this.manipulationDOM['backSpan'] = document.createElement('div');
  29944. this.manipulationDOM['backSpan'].className = 'network-manipulationUI back';
  29945. this.manipulationDOM['backLabelSpan'] = document.createElement('div');
  29946. this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel';
  29947. this.manipulationDOM['backLabelSpan'].innerHTML = locale['back'];
  29948. this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']);
  29949. this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
  29950. this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
  29951. this.manipulationDOM['descriptionSpan'] = document.createElement('div');
  29952. this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none';
  29953. this.manipulationDOM['descriptionLabelSpan'] = document.createElement('div');
  29954. this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel';
  29955. this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['editEdgeDescription'];
  29956. this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']);
  29957. this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']);
  29958. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
  29959. this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']);
  29960. // bind the icon
  29961. this._bindHammerToDiv(this.manipulationDOM['backSpan'],'_createManipulatorBar');
  29962. // temporarily overload functions
  29963. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  29964. this.cachedFunctions["_manipulationReleaseOverload"] = this._manipulationReleaseOverload;
  29965. this.cachedFunctions["_handleTap"] = this._handleTap;
  29966. this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
  29967. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  29968. this._handleTouch = this._selectControlNode;
  29969. this._handleTap = function () {};
  29970. this._handleOnDrag = this._controlNodeDrag;
  29971. this._handleDragStart = function () {}
  29972. this._manipulationReleaseOverload = this._releaseControlNode;
  29973. // redraw to show the unselect
  29974. this._redraw();
  29975. };
  29976. /**
  29977. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  29978. * to walk the user through the process.
  29979. *
  29980. * @private
  29981. */
  29982. exports._selectControlNode = function(pointer) {
  29983. this.edgeBeingEdited.controlNodes.from.unselect();
  29984. this.edgeBeingEdited.controlNodes.to.unselect();
  29985. this.selectedControlNode = this.edgeBeingEdited._getSelectedControlNode(this._XconvertDOMtoCanvas(pointer.x),this._YconvertDOMtoCanvas(pointer.y));
  29986. if (this.selectedControlNode !== null) {
  29987. this.selectedControlNode.select();
  29988. this.freezeSimulation(true);
  29989. }
  29990. this._redraw();
  29991. };
  29992. /**
  29993. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  29994. * to walk the user through the process.
  29995. *
  29996. * @private
  29997. */
  29998. exports._controlNodeDrag = function(event) {
  29999. var pointer = this._getPointer(event.gesture.center);
  30000. if (this.selectedControlNode !== null && this.selectedControlNode !== undefined) {
  30001. this.selectedControlNode.x = this._XconvertDOMtoCanvas(pointer.x);
  30002. this.selectedControlNode.y = this._YconvertDOMtoCanvas(pointer.y);
  30003. }
  30004. this._redraw();
  30005. };
  30006. /**
  30007. *
  30008. * @param pointer
  30009. * @private
  30010. */
  30011. exports._releaseControlNode = function(pointer) {
  30012. var newNode = this._getNodeAt(pointer);
  30013. if (newNode !== null) {
  30014. if (this.edgeBeingEdited.controlNodes.from.selected == true) {
  30015. this.edgeBeingEdited._restoreControlNodes();
  30016. this._editEdge(newNode.id, this.edgeBeingEdited.to.id);
  30017. this.edgeBeingEdited.controlNodes.from.unselect();
  30018. }
  30019. if (this.edgeBeingEdited.controlNodes.to.selected == true) {
  30020. this.edgeBeingEdited._restoreControlNodes();
  30021. this._editEdge(this.edgeBeingEdited.from.id, newNode.id);
  30022. this.edgeBeingEdited.controlNodes.to.unselect();
  30023. }
  30024. }
  30025. else {
  30026. this.edgeBeingEdited._restoreControlNodes();
  30027. }
  30028. this.freezeSimulation(false);
  30029. this._redraw();
  30030. };
  30031. /**
  30032. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  30033. * to walk the user through the process.
  30034. *
  30035. * @private
  30036. */
  30037. exports._handleConnect = function(pointer) {
  30038. if (this._getSelectedNodeCount() == 0) {
  30039. var node = this._getNodeAt(pointer);
  30040. if (node != null) {
  30041. if (node.clusterSize > 1) {
  30042. alert(this.constants.locales[this.constants.locale]['createEdgeError'])
  30043. }
  30044. else {
  30045. this._selectObject(node,false);
  30046. var supportNodes = this.sectors['support']['nodes'];
  30047. // create a node the temporary line can look at
  30048. supportNodes['targetNode'] = new Node({id:'targetNode'},{},{},this.constants);
  30049. var targetNode = supportNodes['targetNode'];
  30050. targetNode.x = node.x;
  30051. targetNode.y = node.y;
  30052. // create a temporary edge
  30053. this.edges['connectionEdge'] = new Edge({id:"connectionEdge",from:node.id,to:targetNode.id}, this, this.constants);
  30054. var connectionEdge = this.edges['connectionEdge'];
  30055. connectionEdge.from = node;
  30056. connectionEdge.connected = true;
  30057. connectionEdge.options.smoothCurves = {enabled: true,
  30058. dynamic: false,
  30059. type: "continuous",
  30060. roundness: 0.5
  30061. };
  30062. connectionEdge.selected = true;
  30063. connectionEdge.to = targetNode;
  30064. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  30065. var me = this;
  30066. this._handleOnDrag = function(event) {
  30067. var pointer = this._getPointer(event.gesture.center);
  30068. var connectionEdge = me.edges['connectionEdge'];
  30069. connectionEdge.to.x = me._XconvertDOMtoCanvas(pointer.x);
  30070. connectionEdge.to.y = me._YconvertDOMtoCanvas(pointer.y);
  30071. me._redraw();
  30072. };
  30073. this.moving = true;
  30074. this.start();
  30075. }
  30076. }
  30077. }
  30078. };
  30079. exports._finishConnect = function(event) {
  30080. if (this._getSelectedNodeCount() == 1) {
  30081. var pointer = this._getPointer(event.gesture.center);
  30082. // restore the drag function
  30083. this._handleOnDrag = this.cachedFunctions["_handleOnDrag"];
  30084. delete this.cachedFunctions["_handleOnDrag"];
  30085. // remember the edge id
  30086. var connectFromId = this.edges['connectionEdge'].fromId;
  30087. // remove the temporary nodes and edge
  30088. delete this.edges['connectionEdge'];
  30089. delete this.sectors['support']['nodes']['targetNode'];
  30090. delete this.sectors['support']['nodes']['targetViaNode'];
  30091. var node = this._getNodeAt(pointer);
  30092. if (node != null) {
  30093. if (node.clusterSize > 1) {
  30094. alert(this.constants.locales[this.constants.locale]["createEdgeError"])
  30095. }
  30096. else {
  30097. this._createEdge(connectFromId,node.id);
  30098. this._createManipulatorBar();
  30099. }
  30100. }
  30101. this._unselectAll();
  30102. }
  30103. };
  30104. /**
  30105. * Adds a node on the specified location
  30106. */
  30107. exports._addNode = function() {
  30108. if (this._selectionIsEmpty() && this.editMode == true) {
  30109. var positionObject = this._pointerToPositionObject(this.pointerPosition);
  30110. var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",allowedToMoveX:true,allowedToMoveY:true};
  30111. if (this.triggerFunctions.add) {
  30112. if (this.triggerFunctions.add.length == 2) {
  30113. var me = this;
  30114. this.triggerFunctions.add(defaultData, function(finalizedData) {
  30115. me.nodesData.add(finalizedData);
  30116. me._createManipulatorBar();
  30117. me.moving = true;
  30118. me.start();
  30119. });
  30120. }
  30121. else {
  30122. throw new Error('The function for add does not support two arguments (data,callback)');
  30123. this._createManipulatorBar();
  30124. this.moving = true;
  30125. this.start();
  30126. }
  30127. }
  30128. else {
  30129. this.nodesData.add(defaultData);
  30130. this._createManipulatorBar();
  30131. this.moving = true;
  30132. this.start();
  30133. }
  30134. }
  30135. };
  30136. /**
  30137. * connect two nodes with a new edge.
  30138. *
  30139. * @private
  30140. */
  30141. exports._createEdge = function(sourceNodeId,targetNodeId) {
  30142. if (this.editMode == true) {
  30143. var defaultData = {from:sourceNodeId, to:targetNodeId};
  30144. if (this.triggerFunctions.connect) {
  30145. if (this.triggerFunctions.connect.length == 2) {
  30146. var me = this;
  30147. this.triggerFunctions.connect(defaultData, function(finalizedData) {
  30148. me.edgesData.add(finalizedData);
  30149. me.moving = true;
  30150. me.start();
  30151. });
  30152. }
  30153. else {
  30154. throw new Error('The function for connect does not support two arguments (data,callback)');
  30155. this.moving = true;
  30156. this.start();
  30157. }
  30158. }
  30159. else {
  30160. this.edgesData.add(defaultData);
  30161. this.moving = true;
  30162. this.start();
  30163. }
  30164. }
  30165. };
  30166. /**
  30167. * connect two nodes with a new edge.
  30168. *
  30169. * @private
  30170. */
  30171. exports._editEdge = function(sourceNodeId,targetNodeId) {
  30172. if (this.editMode == true) {
  30173. var defaultData = {id: this.edgeBeingEdited.id, from:sourceNodeId, to:targetNodeId};
  30174. if (this.triggerFunctions.editEdge) {
  30175. if (this.triggerFunctions.editEdge.length == 2) {
  30176. var me = this;
  30177. this.triggerFunctions.editEdge(defaultData, function(finalizedData) {
  30178. me.edgesData.update(finalizedData);
  30179. me.moving = true;
  30180. me.start();
  30181. });
  30182. }
  30183. else {
  30184. throw new Error('The function for edit does not support two arguments (data, callback)');
  30185. this.moving = true;
  30186. this.start();
  30187. }
  30188. }
  30189. else {
  30190. this.edgesData.update(defaultData);
  30191. this.moving = true;
  30192. this.start();
  30193. }
  30194. }
  30195. };
  30196. /**
  30197. * Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color.
  30198. *
  30199. * @private
  30200. */
  30201. exports._editNode = function() {
  30202. if (this.triggerFunctions.edit && this.editMode == true) {
  30203. var node = this._getSelectedNode();
  30204. var data = {id:node.id,
  30205. label: node.label,
  30206. group: node.options.group,
  30207. shape: node.options.shape,
  30208. color: {
  30209. background:node.options.color.background,
  30210. border:node.options.color.border,
  30211. highlight: {
  30212. background:node.options.color.highlight.background,
  30213. border:node.options.color.highlight.border
  30214. }
  30215. }};
  30216. if (this.triggerFunctions.edit.length == 2) {
  30217. var me = this;
  30218. this.triggerFunctions.edit(data, function (finalizedData) {
  30219. me.nodesData.update(finalizedData);
  30220. me._createManipulatorBar();
  30221. me.moving = true;
  30222. me.start();
  30223. });
  30224. }
  30225. else {
  30226. throw new Error('The function for edit does not support two arguments (data, callback)');
  30227. }
  30228. }
  30229. else {
  30230. throw new Error('No edit function has been bound to this button');
  30231. }
  30232. };
  30233. /**
  30234. * delete everything in the selection
  30235. *
  30236. * @private
  30237. */
  30238. exports._deleteSelected = function() {
  30239. if (!this._selectionIsEmpty() && this.editMode == true) {
  30240. if (!this._clusterInSelection()) {
  30241. var selectedNodes = this.getSelectedNodes();
  30242. var selectedEdges = this.getSelectedEdges();
  30243. if (this.triggerFunctions.del) {
  30244. var me = this;
  30245. var data = {nodes: selectedNodes, edges: selectedEdges};
  30246. if (this.triggerFunctions.del.length == 2) {
  30247. this.triggerFunctions.del(data, function (finalizedData) {
  30248. me.edgesData.remove(finalizedData.edges);
  30249. me.nodesData.remove(finalizedData.nodes);
  30250. me._unselectAll();
  30251. me.moving = true;
  30252. me.start();
  30253. });
  30254. }
  30255. else {
  30256. throw new Error('The function for delete does not support two arguments (data, callback)')
  30257. }
  30258. }
  30259. else {
  30260. this.edgesData.remove(selectedEdges);
  30261. this.nodesData.remove(selectedNodes);
  30262. this._unselectAll();
  30263. this.moving = true;
  30264. this.start();
  30265. }
  30266. }
  30267. else {
  30268. alert(this.constants.locales[this.constants.locale]["deleteClusterError"]);
  30269. }
  30270. }
  30271. };
  30272. /***/ },
  30273. /* 65 */
  30274. /***/ function(module, exports, __webpack_require__) {
  30275. var util = __webpack_require__(1);
  30276. var Hammer = __webpack_require__(45);
  30277. exports._cleanNavigation = function() {
  30278. // clean hammer bindings
  30279. if (this.navigationHammers.length != 0) {
  30280. for (var i = 0; i < this.navigationHammers.length; i++) {
  30281. this.navigationHammers[i].dispose();
  30282. }
  30283. this.navigationHammers = [];
  30284. }
  30285. this._navigationReleaseOverload = function () {};
  30286. // clean up previous navigation items
  30287. if (this.navigationDOM && this.navigationDOM['wrapper'] && this.navigationDOM['wrapper'].parentNode) {
  30288. this.navigationDOM['wrapper'].parentNode.removeChild(this.navigationDOM['wrapper']);
  30289. }
  30290. };
  30291. /**
  30292. * Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation
  30293. * they have a triggerFunction which is called on click. If the position of the navigation controls is dependent
  30294. * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false.
  30295. * This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas.
  30296. *
  30297. * @private
  30298. */
  30299. exports._loadNavigationElements = function() {
  30300. this._cleanNavigation();
  30301. this.navigationDOM = {};
  30302. var navigationDivs = ['up','down','left','right','zoomIn','zoomOut','zoomExtends'];
  30303. var navigationDivActions = ['_moveUp','_moveDown','_moveLeft','_moveRight','_zoomIn','_zoomOut','_zoomExtent'];
  30304. this.navigationDOM['wrapper'] = document.createElement('div');
  30305. this.frame.appendChild(this.navigationDOM['wrapper']);
  30306. for (var i = 0; i < navigationDivs.length; i++) {
  30307. this.navigationDOM[navigationDivs[i]] = document.createElement('div');
  30308. this.navigationDOM[navigationDivs[i]].className = 'network-navigation ' + navigationDivs[i];
  30309. this.navigationDOM['wrapper'].appendChild(this.navigationDOM[navigationDivs[i]]);
  30310. var hammer = Hammer(this.navigationDOM[navigationDivs[i]], {prevent_default: true});
  30311. hammer.on('touch', this[navigationDivActions[i]].bind(this));
  30312. this.navigationHammers.push(hammer);
  30313. }
  30314. this._navigationReleaseOverload = this._stopMovement;
  30315. };
  30316. /**
  30317. * this stops all movement induced by the navigation buttons
  30318. *
  30319. * @private
  30320. */
  30321. exports._zoomExtent = function(event) {
  30322. this.zoomExtent({duration:700});
  30323. event.stopPropagation();
  30324. };
  30325. /**
  30326. * this stops all movement induced by the navigation buttons
  30327. *
  30328. * @private
  30329. */
  30330. exports._stopMovement = function() {
  30331. this._xStopMoving();
  30332. this._yStopMoving();
  30333. this._stopZoom();
  30334. };
  30335. /**
  30336. * move the screen up
  30337. * By using the increments, instead of adding a fixed number to the translation, we keep fluent and
  30338. * instant movement. The onKeypress event triggers immediately, then pauses, then triggers frequently
  30339. * To avoid this behaviour, we do the translation in the start loop.
  30340. *
  30341. * @private
  30342. */
  30343. exports._moveUp = function(event) {
  30344. this.yIncrement = this.constants.keyboard.speed.y;
  30345. this.start(); // if there is no node movement, the calculation wont be done
  30346. event.preventDefault();
  30347. };
  30348. /**
  30349. * move the screen down
  30350. * @private
  30351. */
  30352. exports._moveDown = function(event) {
  30353. this.yIncrement = -this.constants.keyboard.speed.y;
  30354. this.start(); // if there is no node movement, the calculation wont be done
  30355. event.preventDefault();
  30356. };
  30357. /**
  30358. * move the screen left
  30359. * @private
  30360. */
  30361. exports._moveLeft = function(event) {
  30362. this.xIncrement = this.constants.keyboard.speed.x;
  30363. this.start(); // if there is no node movement, the calculation wont be done
  30364. event.preventDefault();
  30365. };
  30366. /**
  30367. * move the screen right
  30368. * @private
  30369. */
  30370. exports._moveRight = function(event) {
  30371. this.xIncrement = -this.constants.keyboard.speed.y;
  30372. this.start(); // if there is no node movement, the calculation wont be done
  30373. event.preventDefault();
  30374. };
  30375. /**
  30376. * Zoom in, using the same method as the movement.
  30377. * @private
  30378. */
  30379. exports._zoomIn = function(event) {
  30380. this.zoomIncrement = this.constants.keyboard.speed.zoom;
  30381. this.start(); // if there is no node movement, the calculation wont be done
  30382. event.preventDefault();
  30383. };
  30384. /**
  30385. * Zoom out
  30386. * @private
  30387. */
  30388. exports._zoomOut = function(event) {
  30389. this.zoomIncrement = -this.constants.keyboard.speed.zoom;
  30390. this.start(); // if there is no node movement, the calculation wont be done
  30391. event.preventDefault();
  30392. };
  30393. /**
  30394. * Stop zooming and unhighlight the zoom controls
  30395. * @private
  30396. */
  30397. exports._stopZoom = function(event) {
  30398. this.zoomIncrement = 0;
  30399. event && event.preventDefault();
  30400. };
  30401. /**
  30402. * Stop moving in the Y direction and unHighlight the up and down
  30403. * @private
  30404. */
  30405. exports._yStopMoving = function(event) {
  30406. this.yIncrement = 0;
  30407. event && event.preventDefault();
  30408. };
  30409. /**
  30410. * Stop moving in the X direction and unHighlight left and right.
  30411. * @private
  30412. */
  30413. exports._xStopMoving = function(event) {
  30414. this.xIncrement = 0;
  30415. event && event.preventDefault();
  30416. };
  30417. /***/ },
  30418. /* 66 */
  30419. /***/ function(module, exports, __webpack_require__) {
  30420. exports._resetLevels = function() {
  30421. for (var nodeId in this.nodes) {
  30422. if (this.nodes.hasOwnProperty(nodeId)) {
  30423. var node = this.nodes[nodeId];
  30424. if (node.preassignedLevel == false) {
  30425. node.level = -1;
  30426. node.hierarchyEnumerated = false;
  30427. }
  30428. }
  30429. }
  30430. };
  30431. /**
  30432. * This is the main function to layout the nodes in a hierarchical way.
  30433. * It checks if the node details are supplied correctly
  30434. *
  30435. * @private
  30436. */
  30437. exports._setupHierarchicalLayout = function() {
  30438. if (this.constants.hierarchicalLayout.enabled == true && this.nodeIndices.length > 0) {
  30439. // get the size of the largest hubs and check if the user has defined a level for a node.
  30440. var hubsize = 0;
  30441. var node, nodeId;
  30442. var definedLevel = false;
  30443. var undefinedLevel = false;
  30444. for (nodeId in this.nodes) {
  30445. if (this.nodes.hasOwnProperty(nodeId)) {
  30446. node = this.nodes[nodeId];
  30447. if (node.level != -1) {
  30448. definedLevel = true;
  30449. }
  30450. else {
  30451. undefinedLevel = true;
  30452. }
  30453. if (hubsize < node.edges.length) {
  30454. hubsize = node.edges.length;
  30455. }
  30456. }
  30457. }
  30458. // if the user defined some levels but not all, alert and run without hierarchical layout
  30459. if (undefinedLevel == true && definedLevel == true) {
  30460. throw new Error("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.");
  30461. this.zoomExtent({duration:0},true,this.constants.clustering.enabled);
  30462. if (!this.constants.clustering.enabled) {
  30463. this.start();
  30464. }
  30465. }
  30466. else {
  30467. // setup the system to use hierarchical method.
  30468. this._changeConstants();
  30469. // define levels if undefined by the users. Based on hubsize
  30470. if (undefinedLevel == true) {
  30471. if (this.constants.hierarchicalLayout.layout == "hubsize") {
  30472. this._determineLevels(hubsize);
  30473. }
  30474. else {
  30475. this._determineLevelsDirected(false);
  30476. }
  30477. }
  30478. // check the distribution of the nodes per level.
  30479. var distribution = this._getDistribution();
  30480. // place the nodes on the canvas. This also stablilizes the system.
  30481. this._placeNodesByHierarchy(distribution);
  30482. // start the simulation.
  30483. this.start();
  30484. }
  30485. }
  30486. };
  30487. /**
  30488. * This function places the nodes on the canvas based on the hierarchial distribution.
  30489. *
  30490. * @param {Object} distribution | obtained by the function this._getDistribution()
  30491. * @private
  30492. */
  30493. exports._placeNodesByHierarchy = function(distribution) {
  30494. var nodeId, node;
  30495. // start placing all the level 0 nodes first. Then recursively position their branches.
  30496. for (var level in distribution) {
  30497. if (distribution.hasOwnProperty(level)) {
  30498. for (nodeId in distribution[level].nodes) {
  30499. if (distribution[level].nodes.hasOwnProperty(nodeId)) {
  30500. node = distribution[level].nodes[nodeId];
  30501. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  30502. if (node.xFixed) {
  30503. node.x = distribution[level].minPos;
  30504. node.xFixed = false;
  30505. distribution[level].minPos += distribution[level].nodeSpacing;
  30506. }
  30507. }
  30508. else {
  30509. if (node.yFixed) {
  30510. node.y = distribution[level].minPos;
  30511. node.yFixed = false;
  30512. distribution[level].minPos += distribution[level].nodeSpacing;
  30513. }
  30514. }
  30515. this._placeBranchNodes(node.edges,node.id,distribution,node.level);
  30516. }
  30517. }
  30518. }
  30519. }
  30520. // stabilize the system after positioning. This function calls zoomExtent.
  30521. this._stabilize();
  30522. };
  30523. /**
  30524. * This function get the distribution of levels based on hubsize
  30525. *
  30526. * @returns {Object}
  30527. * @private
  30528. */
  30529. exports._getDistribution = function() {
  30530. var distribution = {};
  30531. var nodeId, node, level;
  30532. // 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.
  30533. // the fix of X is removed after the x value has been set.
  30534. for (nodeId in this.nodes) {
  30535. if (this.nodes.hasOwnProperty(nodeId)) {
  30536. node = this.nodes[nodeId];
  30537. node.xFixed = true;
  30538. node.yFixed = true;
  30539. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  30540. node.y = this.constants.hierarchicalLayout.levelSeparation*node.level;
  30541. }
  30542. else {
  30543. node.x = this.constants.hierarchicalLayout.levelSeparation*node.level;
  30544. }
  30545. if (distribution[node.level] === undefined) {
  30546. distribution[node.level] = {amount: 0, nodes: {}, minPos:0, nodeSpacing:0};
  30547. }
  30548. distribution[node.level].amount += 1;
  30549. distribution[node.level].nodes[nodeId] = node;
  30550. }
  30551. }
  30552. // determine the largest amount of nodes of all levels
  30553. var maxCount = 0;
  30554. for (level in distribution) {
  30555. if (distribution.hasOwnProperty(level)) {
  30556. if (maxCount < distribution[level].amount) {
  30557. maxCount = distribution[level].amount;
  30558. }
  30559. }
  30560. }
  30561. // set the initial position and spacing of each nodes accordingly
  30562. for (level in distribution) {
  30563. if (distribution.hasOwnProperty(level)) {
  30564. distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing;
  30565. distribution[level].nodeSpacing /= (distribution[level].amount + 1);
  30566. distribution[level].minPos = distribution[level].nodeSpacing - (0.5 * (distribution[level].amount + 1) * distribution[level].nodeSpacing);
  30567. }
  30568. }
  30569. return distribution;
  30570. };
  30571. /**
  30572. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  30573. *
  30574. * @param hubsize
  30575. * @private
  30576. */
  30577. exports._determineLevels = function(hubsize) {
  30578. var nodeId, node;
  30579. // determine hubs
  30580. for (nodeId in this.nodes) {
  30581. if (this.nodes.hasOwnProperty(nodeId)) {
  30582. node = this.nodes[nodeId];
  30583. if (node.edges.length == hubsize) {
  30584. node.level = 0;
  30585. }
  30586. }
  30587. }
  30588. // branch from hubs
  30589. for (nodeId in this.nodes) {
  30590. if (this.nodes.hasOwnProperty(nodeId)) {
  30591. node = this.nodes[nodeId];
  30592. if (node.level == 0) {
  30593. this._setLevel(1,node.edges,node.id);
  30594. }
  30595. }
  30596. }
  30597. };
  30598. /**
  30599. * this function allocates nodes in levels based on the direction of the edges
  30600. *
  30601. * @param hubsize
  30602. * @private
  30603. */
  30604. exports._determineLevelsDirected = function() {
  30605. var nodeId, node, firstNode;
  30606. var minLevel = 10000;
  30607. // set first node to source
  30608. firstNode = this.nodes[this.nodeIndices[0]];
  30609. firstNode.level = minLevel;
  30610. this._setLevelDirected(minLevel,firstNode.edges,firstNode.id);
  30611. // get the minimum level
  30612. for (nodeId in this.nodes) {
  30613. if (this.nodes.hasOwnProperty(nodeId)) {
  30614. node = this.nodes[nodeId];
  30615. minLevel = node.level < minLevel ? node.level : minLevel;
  30616. }
  30617. }
  30618. // subtract the minimum from the set so we have a range starting from 0
  30619. for (nodeId in this.nodes) {
  30620. if (this.nodes.hasOwnProperty(nodeId)) {
  30621. node = this.nodes[nodeId];
  30622. node.level -= minLevel;
  30623. }
  30624. }
  30625. };
  30626. /**
  30627. * Since hierarchical layout does not support:
  30628. * - smooth curves (based on the physics),
  30629. * - clustering (based on dynamic node counts)
  30630. *
  30631. * We disable both features so there will be no problems.
  30632. *
  30633. * @private
  30634. */
  30635. exports._changeConstants = function() {
  30636. this.constants.clustering.enabled = false;
  30637. this.constants.physics.barnesHut.enabled = false;
  30638. this.constants.physics.hierarchicalRepulsion.enabled = true;
  30639. this._loadSelectedForceSolver();
  30640. if (this.constants.smoothCurves.enabled == true) {
  30641. this.constants.smoothCurves.dynamic = false;
  30642. }
  30643. this._configureSmoothCurves();
  30644. var config = this.constants.hierarchicalLayout;
  30645. config.levelSeparation = Math.abs(config.levelSeparation);
  30646. if (config.direction == "RL" || config.direction == "DU") {
  30647. config.levelSeparation *= -1;
  30648. }
  30649. if (config.direction == "RL" || config.direction == "LR") {
  30650. if (this.constants.smoothCurves.enabled == true) {
  30651. this.constants.smoothCurves.type = "vertical";
  30652. }
  30653. }
  30654. else {
  30655. if (this.constants.smoothCurves.enabled == true) {
  30656. this.constants.smoothCurves.type = "horizontal";
  30657. }
  30658. }
  30659. };
  30660. /**
  30661. * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
  30662. * on a X position that ensures there will be no overlap.
  30663. *
  30664. * @param edges
  30665. * @param parentId
  30666. * @param distribution
  30667. * @param parentLevel
  30668. * @private
  30669. */
  30670. exports._placeBranchNodes = function(edges, parentId, distribution, parentLevel) {
  30671. for (var i = 0; i < edges.length; i++) {
  30672. var childNode = null;
  30673. if (edges[i].toId == parentId) {
  30674. childNode = edges[i].from;
  30675. }
  30676. else {
  30677. childNode = edges[i].to;
  30678. }
  30679. // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
  30680. var nodeMoved = false;
  30681. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  30682. if (childNode.xFixed && childNode.level > parentLevel) {
  30683. childNode.xFixed = false;
  30684. childNode.x = distribution[childNode.level].minPos;
  30685. nodeMoved = true;
  30686. }
  30687. }
  30688. else {
  30689. if (childNode.yFixed && childNode.level > parentLevel) {
  30690. childNode.yFixed = false;
  30691. childNode.y = distribution[childNode.level].minPos;
  30692. nodeMoved = true;
  30693. }
  30694. }
  30695. if (nodeMoved == true) {
  30696. distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing;
  30697. if (childNode.edges.length > 1) {
  30698. this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level);
  30699. }
  30700. }
  30701. }
  30702. };
  30703. /**
  30704. * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
  30705. *
  30706. * @param level
  30707. * @param edges
  30708. * @param parentId
  30709. * @private
  30710. */
  30711. exports._setLevel = function(level, edges, parentId) {
  30712. for (var i = 0; i < edges.length; i++) {
  30713. var childNode = null;
  30714. if (edges[i].toId == parentId) {
  30715. childNode = edges[i].from;
  30716. }
  30717. else {
  30718. childNode = edges[i].to;
  30719. }
  30720. if (childNode.level == -1 || childNode.level > level) {
  30721. childNode.level = level;
  30722. if (childNode.edges.length > 1) {
  30723. this._setLevel(level+1, childNode.edges, childNode.id);
  30724. }
  30725. }
  30726. }
  30727. };
  30728. /**
  30729. * this function is called recursively to enumerate the branched of the first node and give each node a level based on edge direction
  30730. *
  30731. * @param level
  30732. * @param edges
  30733. * @param parentId
  30734. * @private
  30735. */
  30736. exports._setLevelDirected = function(level, edges, parentId) {
  30737. this.nodes[parentId].hierarchyEnumerated = true;
  30738. var childNode, direction;
  30739. for (var i = 0; i < edges.length; i++) {
  30740. direction = 1;
  30741. if (edges[i].toId == parentId) {
  30742. childNode = edges[i].from;
  30743. direction = -1;
  30744. }
  30745. else {
  30746. childNode = edges[i].to;
  30747. }
  30748. if (childNode.level == -1) {
  30749. childNode.level = level + direction;
  30750. }
  30751. }
  30752. for (var i = 0; i < edges.length; i++) {
  30753. if (edges[i].toId == parentId) {childNode = edges[i].from;}
  30754. else {childNode = edges[i].to;}
  30755. if (childNode.edges.length > 1 && childNode.hierarchyEnumerated === false) {
  30756. this._setLevelDirected(childNode.level, childNode.edges, childNode.id);
  30757. }
  30758. }
  30759. };
  30760. /**
  30761. * Unfix nodes
  30762. *
  30763. * @private
  30764. */
  30765. exports._restoreNodes = function() {
  30766. for (var nodeId in this.nodes) {
  30767. if (this.nodes.hasOwnProperty(nodeId)) {
  30768. this.nodes[nodeId].xFixed = false;
  30769. this.nodes[nodeId].yFixed = false;
  30770. }
  30771. }
  30772. };
  30773. /***/ },
  30774. /* 67 */
  30775. /***/ function(module, exports, __webpack_require__) {
  30776. function webpackContext(req) {
  30777. throw new Error("Cannot find module '" + req + "'.");
  30778. }
  30779. webpackContext.keys = function() { return []; };
  30780. webpackContext.resolve = webpackContext;
  30781. module.exports = webpackContext;
  30782. webpackContext.id = 67;
  30783. /***/ },
  30784. /* 68 */
  30785. /***/ function(module, exports, __webpack_require__) {
  30786. /**
  30787. * Calculate the forces the nodes apply on each other based on a repulsion field.
  30788. * This field is linearly approximated.
  30789. *
  30790. * @private
  30791. */
  30792. exports._calculateNodeForces = function () {
  30793. var dx, dy, angle, distance, fx, fy, combinedClusterSize,
  30794. repulsingForce, node1, node2, i, j;
  30795. var nodes = this.calculationNodes;
  30796. var nodeIndices = this.calculationNodeIndices;
  30797. // approximation constants
  30798. var a_base = -2 / 3;
  30799. var b = 4 / 3;
  30800. // repulsing forces between nodes
  30801. var nodeDistance = this.constants.physics.repulsion.nodeDistance;
  30802. var minimumDistance = nodeDistance;
  30803. // we loop from i over all but the last entree in the array
  30804. // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  30805. for (i = 0; i < nodeIndices.length - 1; i++) {
  30806. node1 = nodes[nodeIndices[i]];
  30807. for (j = i + 1; j < nodeIndices.length; j++) {
  30808. node2 = nodes[nodeIndices[j]];
  30809. combinedClusterSize = node1.clusterSize + node2.clusterSize - 2;
  30810. dx = node2.x - node1.x;
  30811. dy = node2.y - node1.y;
  30812. distance = Math.sqrt(dx * dx + dy * dy);
  30813. // same condition as BarnesHut, making sure nodes are never 100% overlapping.
  30814. if (distance == 0) {
  30815. distance = 0.1*Math.random();
  30816. dx = distance;
  30817. }
  30818. minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification));
  30819. var a = a_base / minimumDistance;
  30820. if (distance < 2 * minimumDistance) {
  30821. if (distance < 0.5 * minimumDistance) {
  30822. repulsingForce = 1.0;
  30823. }
  30824. else {
  30825. repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness))
  30826. }
  30827. // amplify the repulsion for clusters.
  30828. repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification;
  30829. repulsingForce = repulsingForce / Math.max(distance,0.01*minimumDistance);
  30830. fx = dx * repulsingForce;
  30831. fy = dy * repulsingForce;
  30832. node1.fx -= fx;
  30833. node1.fy -= fy;
  30834. node2.fx += fx;
  30835. node2.fy += fy;
  30836. }
  30837. }
  30838. }
  30839. };
  30840. /***/ },
  30841. /* 69 */
  30842. /***/ function(module, exports, __webpack_require__) {
  30843. /**
  30844. * Calculate the forces the nodes apply on eachother based on a repulsion field.
  30845. * This field is linearly approximated.
  30846. *
  30847. * @private
  30848. */
  30849. exports._calculateNodeForces = function () {
  30850. var dx, dy, distance, fx, fy,
  30851. repulsingForce, node1, node2, i, j;
  30852. var nodes = this.calculationNodes;
  30853. var nodeIndices = this.calculationNodeIndices;
  30854. // repulsing forces between nodes
  30855. var nodeDistance = this.constants.physics.hierarchicalRepulsion.nodeDistance;
  30856. // we loop from i over all but the last entree in the array
  30857. // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  30858. for (i = 0; i < nodeIndices.length - 1; i++) {
  30859. node1 = nodes[nodeIndices[i]];
  30860. for (j = i + 1; j < nodeIndices.length; j++) {
  30861. node2 = nodes[nodeIndices[j]];
  30862. // nodes only affect nodes on their level
  30863. if (node1.level == node2.level) {
  30864. dx = node2.x - node1.x;
  30865. dy = node2.y - node1.y;
  30866. distance = Math.sqrt(dx * dx + dy * dy);
  30867. var steepness = 0.05;
  30868. if (distance < nodeDistance) {
  30869. repulsingForce = -Math.pow(steepness*distance,2) + Math.pow(steepness*nodeDistance,2);
  30870. }
  30871. else {
  30872. repulsingForce = 0;
  30873. }
  30874. // normalize force with
  30875. if (distance == 0) {
  30876. distance = 0.01;
  30877. }
  30878. else {
  30879. repulsingForce = repulsingForce / distance;
  30880. }
  30881. fx = dx * repulsingForce;
  30882. fy = dy * repulsingForce;
  30883. node1.fx -= fx;
  30884. node1.fy -= fy;
  30885. node2.fx += fx;
  30886. node2.fy += fy;
  30887. }
  30888. }
  30889. }
  30890. };
  30891. /**
  30892. * this function calculates the effects of the springs in the case of unsmooth curves.
  30893. *
  30894. * @private
  30895. */
  30896. exports._calculateHierarchicalSpringForces = function () {
  30897. var edgeLength, edge, edgeId;
  30898. var dx, dy, fx, fy, springForce, distance;
  30899. var edges = this.edges;
  30900. var nodes = this.calculationNodes;
  30901. var nodeIndices = this.calculationNodeIndices;
  30902. for (var i = 0; i < nodeIndices.length; i++) {
  30903. var node1 = nodes[nodeIndices[i]];
  30904. node1.springFx = 0;
  30905. node1.springFy = 0;
  30906. }
  30907. // forces caused by the edges, modelled as springs
  30908. for (edgeId in edges) {
  30909. if (edges.hasOwnProperty(edgeId)) {
  30910. edge = edges[edgeId];
  30911. if (edge.connected) {
  30912. // only calculate forces if nodes are in the same sector
  30913. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  30914. edgeLength = edge.physics.springLength;
  30915. // this implies that the edges between big clusters are longer
  30916. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  30917. dx = (edge.from.x - edge.to.x);
  30918. dy = (edge.from.y - edge.to.y);
  30919. distance = Math.sqrt(dx * dx + dy * dy);
  30920. if (distance == 0) {
  30921. distance = 0.01;
  30922. }
  30923. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  30924. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  30925. fx = dx * springForce;
  30926. fy = dy * springForce;
  30927. if (edge.to.level != edge.from.level) {
  30928. edge.to.springFx -= fx;
  30929. edge.to.springFy -= fy;
  30930. edge.from.springFx += fx;
  30931. edge.from.springFy += fy;
  30932. }
  30933. else {
  30934. var factor = 0.5;
  30935. edge.to.fx -= factor*fx;
  30936. edge.to.fy -= factor*fy;
  30937. edge.from.fx += factor*fx;
  30938. edge.from.fy += factor*fy;
  30939. }
  30940. }
  30941. }
  30942. }
  30943. }
  30944. // normalize spring forces
  30945. var springForce = 1;
  30946. var springFx, springFy;
  30947. for (i = 0; i < nodeIndices.length; i++) {
  30948. var node = nodes[nodeIndices[i]];
  30949. springFx = Math.min(springForce,Math.max(-springForce,node.springFx));
  30950. springFy = Math.min(springForce,Math.max(-springForce,node.springFy));
  30951. node.fx += springFx;
  30952. node.fy += springFy;
  30953. }
  30954. // retain energy balance
  30955. var totalFx = 0;
  30956. var totalFy = 0;
  30957. for (i = 0; i < nodeIndices.length; i++) {
  30958. var node = nodes[nodeIndices[i]];
  30959. totalFx += node.fx;
  30960. totalFy += node.fy;
  30961. }
  30962. var correctionFx = totalFx / nodeIndices.length;
  30963. var correctionFy = totalFy / nodeIndices.length;
  30964. for (i = 0; i < nodeIndices.length; i++) {
  30965. var node = nodes[nodeIndices[i]];
  30966. node.fx -= correctionFx;
  30967. node.fy -= correctionFy;
  30968. }
  30969. };
  30970. /***/ },
  30971. /* 70 */
  30972. /***/ function(module, exports, __webpack_require__) {
  30973. /**
  30974. * This function calculates the forces the nodes apply on eachother based on a gravitational model.
  30975. * The Barnes Hut method is used to speed up this N-body simulation.
  30976. *
  30977. * @private
  30978. */
  30979. exports._calculateNodeForces = function() {
  30980. if (this.constants.physics.barnesHut.gravitationalConstant != 0) {
  30981. var node;
  30982. var nodes = this.calculationNodes;
  30983. var nodeIndices = this.calculationNodeIndices;
  30984. var nodeCount = nodeIndices.length;
  30985. this._formBarnesHutTree(nodes,nodeIndices);
  30986. var barnesHutTree = this.barnesHutTree;
  30987. // place the nodes one by one recursively
  30988. for (var i = 0; i < nodeCount; i++) {
  30989. node = nodes[nodeIndices[i]];
  30990. if (node.options.mass > 0) {
  30991. // starting with root is irrelevant, it never passes the BarnesHut condition
  30992. this._getForceContribution(barnesHutTree.root.children.NW,node);
  30993. this._getForceContribution(barnesHutTree.root.children.NE,node);
  30994. this._getForceContribution(barnesHutTree.root.children.SW,node);
  30995. this._getForceContribution(barnesHutTree.root.children.SE,node);
  30996. }
  30997. }
  30998. }
  30999. };
  31000. /**
  31001. * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass.
  31002. * If a region contains a single node, we check if it is not itself, then we apply the force.
  31003. *
  31004. * @param parentBranch
  31005. * @param node
  31006. * @private
  31007. */
  31008. exports._getForceContribution = function(parentBranch,node) {
  31009. // we get no force contribution from an empty region
  31010. if (parentBranch.childrenCount > 0) {
  31011. var dx,dy,distance;
  31012. // get the distance from the center of mass to the node.
  31013. dx = parentBranch.centerOfMass.x - node.x;
  31014. dy = parentBranch.centerOfMass.y - node.y;
  31015. distance = Math.sqrt(dx * dx + dy * dy);
  31016. // BarnesHut condition
  31017. // original condition : s/d < thetaInverted = passed === d/s > 1/theta = passed
  31018. // calcSize = 1/s --> d * 1/s > 1/theta = passed
  31019. if (distance * parentBranch.calcSize > this.constants.physics.barnesHut.thetaInverted) {
  31020. // duplicate code to reduce function calls to speed up program
  31021. if (distance == 0) {
  31022. distance = 0.1*Math.random();
  31023. dx = distance;
  31024. }
  31025. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
  31026. var fx = dx * gravityForce;
  31027. var fy = dy * gravityForce;
  31028. node.fx += fx;
  31029. node.fy += fy;
  31030. }
  31031. else {
  31032. // Did not pass the condition, go into children if available
  31033. if (parentBranch.childrenCount == 4) {
  31034. this._getForceContribution(parentBranch.children.NW,node);
  31035. this._getForceContribution(parentBranch.children.NE,node);
  31036. this._getForceContribution(parentBranch.children.SW,node);
  31037. this._getForceContribution(parentBranch.children.SE,node);
  31038. }
  31039. else { // parentBranch must have only one node, if it was empty we wouldnt be here
  31040. if (parentBranch.children.data.id != node.id) { // if it is not self
  31041. // duplicate code to reduce function calls to speed up program
  31042. if (distance == 0) {
  31043. distance = 0.5*Math.random();
  31044. dx = distance;
  31045. }
  31046. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
  31047. var fx = dx * gravityForce;
  31048. var fy = dy * gravityForce;
  31049. node.fx += fx;
  31050. node.fy += fy;
  31051. }
  31052. }
  31053. }
  31054. }
  31055. };
  31056. /**
  31057. * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes.
  31058. *
  31059. * @param nodes
  31060. * @param nodeIndices
  31061. * @private
  31062. */
  31063. exports._formBarnesHutTree = function(nodes,nodeIndices) {
  31064. var node;
  31065. var nodeCount = nodeIndices.length;
  31066. var minX = Number.MAX_VALUE,
  31067. minY = Number.MAX_VALUE,
  31068. maxX =-Number.MAX_VALUE,
  31069. maxY =-Number.MAX_VALUE;
  31070. // get the range of the nodes
  31071. for (var i = 0; i < nodeCount; i++) {
  31072. var x = nodes[nodeIndices[i]].x;
  31073. var y = nodes[nodeIndices[i]].y;
  31074. if (nodes[nodeIndices[i]].options.mass > 0) {
  31075. if (x < minX) { minX = x; }
  31076. if (x > maxX) { maxX = x; }
  31077. if (y < minY) { minY = y; }
  31078. if (y > maxY) { maxY = y; }
  31079. }
  31080. }
  31081. // make the range a square
  31082. var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
  31083. if (sizeDiff > 0) {minY -= 0.5 * sizeDiff; maxY += 0.5 * sizeDiff;} // xSize > ySize
  31084. else {minX += 0.5 * sizeDiff; maxX -= 0.5 * sizeDiff;} // xSize < ySize
  31085. var minimumTreeSize = 1e-5;
  31086. var rootSize = Math.max(minimumTreeSize,Math.abs(maxX - minX));
  31087. var halfRootSize = 0.5 * rootSize;
  31088. var centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY);
  31089. // construct the barnesHutTree
  31090. var barnesHutTree = {
  31091. root:{
  31092. centerOfMass: {x:0, y:0},
  31093. mass:0,
  31094. range: {
  31095. minX: centerX-halfRootSize,maxX:centerX+halfRootSize,
  31096. minY: centerY-halfRootSize,maxY:centerY+halfRootSize
  31097. },
  31098. size: rootSize,
  31099. calcSize: 1 / rootSize,
  31100. children: { data:null},
  31101. maxWidth: 0,
  31102. level: 0,
  31103. childrenCount: 4
  31104. }
  31105. };
  31106. this._splitBranch(barnesHutTree.root);
  31107. // place the nodes one by one recursively
  31108. for (i = 0; i < nodeCount; i++) {
  31109. node = nodes[nodeIndices[i]];
  31110. if (node.options.mass > 0) {
  31111. this._placeInTree(barnesHutTree.root,node);
  31112. }
  31113. }
  31114. // make global
  31115. this.barnesHutTree = barnesHutTree
  31116. };
  31117. /**
  31118. * this updates the mass of a branch. this is increased by adding a node.
  31119. *
  31120. * @param parentBranch
  31121. * @param node
  31122. * @private
  31123. */
  31124. exports._updateBranchMass = function(parentBranch, node) {
  31125. var totalMass = parentBranch.mass + node.options.mass;
  31126. var totalMassInv = 1/totalMass;
  31127. parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass;
  31128. parentBranch.centerOfMass.x *= totalMassInv;
  31129. parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass;
  31130. parentBranch.centerOfMass.y *= totalMassInv;
  31131. parentBranch.mass = totalMass;
  31132. var biggestSize = Math.max(Math.max(node.height,node.radius),node.width);
  31133. parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth;
  31134. };
  31135. /**
  31136. * determine in which branch the node will be placed.
  31137. *
  31138. * @param parentBranch
  31139. * @param node
  31140. * @param skipMassUpdate
  31141. * @private
  31142. */
  31143. exports._placeInTree = function(parentBranch,node,skipMassUpdate) {
  31144. if (skipMassUpdate != true || skipMassUpdate === undefined) {
  31145. // update the mass of the branch.
  31146. this._updateBranchMass(parentBranch,node);
  31147. }
  31148. if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW
  31149. if (parentBranch.children.NW.range.maxY > node.y) { // in NW
  31150. this._placeInRegion(parentBranch,node,"NW");
  31151. }
  31152. else { // in SW
  31153. this._placeInRegion(parentBranch,node,"SW");
  31154. }
  31155. }
  31156. else { // in NE or SE
  31157. if (parentBranch.children.NW.range.maxY > node.y) { // in NE
  31158. this._placeInRegion(parentBranch,node,"NE");
  31159. }
  31160. else { // in SE
  31161. this._placeInRegion(parentBranch,node,"SE");
  31162. }
  31163. }
  31164. };
  31165. /**
  31166. * actually place the node in a region (or branch)
  31167. *
  31168. * @param parentBranch
  31169. * @param node
  31170. * @param region
  31171. * @private
  31172. */
  31173. exports._placeInRegion = function(parentBranch,node,region) {
  31174. switch (parentBranch.children[region].childrenCount) {
  31175. case 0: // place node here
  31176. parentBranch.children[region].children.data = node;
  31177. parentBranch.children[region].childrenCount = 1;
  31178. this._updateBranchMass(parentBranch.children[region],node);
  31179. break;
  31180. case 1: // convert into children
  31181. // if there are two nodes exactly overlapping (on init, on opening of cluster etc.)
  31182. // we move one node a pixel and we do not put it in the tree.
  31183. if (parentBranch.children[region].children.data.x == node.x &&
  31184. parentBranch.children[region].children.data.y == node.y) {
  31185. node.x += Math.random();
  31186. node.y += Math.random();
  31187. }
  31188. else {
  31189. this._splitBranch(parentBranch.children[region]);
  31190. this._placeInTree(parentBranch.children[region],node);
  31191. }
  31192. break;
  31193. case 4: // place in branch
  31194. this._placeInTree(parentBranch.children[region],node);
  31195. break;
  31196. }
  31197. };
  31198. /**
  31199. * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch
  31200. * after the split is complete.
  31201. *
  31202. * @param parentBranch
  31203. * @private
  31204. */
  31205. exports._splitBranch = function(parentBranch) {
  31206. // if the branch is shaded with a node, replace the node in the new subset.
  31207. var containedNode = null;
  31208. if (parentBranch.childrenCount == 1) {
  31209. containedNode = parentBranch.children.data;
  31210. parentBranch.mass = 0; parentBranch.centerOfMass.x = 0; parentBranch.centerOfMass.y = 0;
  31211. }
  31212. parentBranch.childrenCount = 4;
  31213. parentBranch.children.data = null;
  31214. this._insertRegion(parentBranch,"NW");
  31215. this._insertRegion(parentBranch,"NE");
  31216. this._insertRegion(parentBranch,"SW");
  31217. this._insertRegion(parentBranch,"SE");
  31218. if (containedNode != null) {
  31219. this._placeInTree(parentBranch,containedNode);
  31220. }
  31221. };
  31222. /**
  31223. * This function subdivides the region into four new segments.
  31224. * Specifically, this inserts a single new segment.
  31225. * It fills the children section of the parentBranch
  31226. *
  31227. * @param parentBranch
  31228. * @param region
  31229. * @param parentRange
  31230. * @private
  31231. */
  31232. exports._insertRegion = function(parentBranch, region) {
  31233. var minX,maxX,minY,maxY;
  31234. var childSize = 0.5 * parentBranch.size;
  31235. switch (region) {
  31236. case "NW":
  31237. minX = parentBranch.range.minX;
  31238. maxX = parentBranch.range.minX + childSize;
  31239. minY = parentBranch.range.minY;
  31240. maxY = parentBranch.range.minY + childSize;
  31241. break;
  31242. case "NE":
  31243. minX = parentBranch.range.minX + childSize;
  31244. maxX = parentBranch.range.maxX;
  31245. minY = parentBranch.range.minY;
  31246. maxY = parentBranch.range.minY + childSize;
  31247. break;
  31248. case "SW":
  31249. minX = parentBranch.range.minX;
  31250. maxX = parentBranch.range.minX + childSize;
  31251. minY = parentBranch.range.minY + childSize;
  31252. maxY = parentBranch.range.maxY;
  31253. break;
  31254. case "SE":
  31255. minX = parentBranch.range.minX + childSize;
  31256. maxX = parentBranch.range.maxX;
  31257. minY = parentBranch.range.minY + childSize;
  31258. maxY = parentBranch.range.maxY;
  31259. break;
  31260. }
  31261. parentBranch.children[region] = {
  31262. centerOfMass:{x:0,y:0},
  31263. mass:0,
  31264. range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY},
  31265. size: 0.5 * parentBranch.size,
  31266. calcSize: 2 * parentBranch.calcSize,
  31267. children: {data:null},
  31268. maxWidth: 0,
  31269. level: parentBranch.level+1,
  31270. childrenCount: 0
  31271. };
  31272. };
  31273. /**
  31274. * This function is for debugging purposed, it draws the tree.
  31275. *
  31276. * @param ctx
  31277. * @param color
  31278. * @private
  31279. */
  31280. exports._drawTree = function(ctx,color) {
  31281. if (this.barnesHutTree !== undefined) {
  31282. ctx.lineWidth = 1;
  31283. this._drawBranch(this.barnesHutTree.root,ctx,color);
  31284. }
  31285. };
  31286. /**
  31287. * This function is for debugging purposes. It draws the branches recursively.
  31288. *
  31289. * @param branch
  31290. * @param ctx
  31291. * @param color
  31292. * @private
  31293. */
  31294. exports._drawBranch = function(branch,ctx,color) {
  31295. if (color === undefined) {
  31296. color = "#FF0000";
  31297. }
  31298. if (branch.childrenCount == 4) {
  31299. this._drawBranch(branch.children.NW,ctx);
  31300. this._drawBranch(branch.children.NE,ctx);
  31301. this._drawBranch(branch.children.SE,ctx);
  31302. this._drawBranch(branch.children.SW,ctx);
  31303. }
  31304. ctx.strokeStyle = color;
  31305. ctx.beginPath();
  31306. ctx.moveTo(branch.range.minX,branch.range.minY);
  31307. ctx.lineTo(branch.range.maxX,branch.range.minY);
  31308. ctx.stroke();
  31309. ctx.beginPath();
  31310. ctx.moveTo(branch.range.maxX,branch.range.minY);
  31311. ctx.lineTo(branch.range.maxX,branch.range.maxY);
  31312. ctx.stroke();
  31313. ctx.beginPath();
  31314. ctx.moveTo(branch.range.maxX,branch.range.maxY);
  31315. ctx.lineTo(branch.range.minX,branch.range.maxY);
  31316. ctx.stroke();
  31317. ctx.beginPath();
  31318. ctx.moveTo(branch.range.minX,branch.range.maxY);
  31319. ctx.lineTo(branch.range.minX,branch.range.minY);
  31320. ctx.stroke();
  31321. /*
  31322. if (branch.mass > 0) {
  31323. ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass);
  31324. ctx.stroke();
  31325. }
  31326. */
  31327. };
  31328. /***/ },
  31329. /* 71 */
  31330. /***/ function(module, exports, __webpack_require__) {
  31331. module.exports = function(module) {
  31332. if(!module.webpackPolyfill) {
  31333. module.deprecate = function() {};
  31334. module.paths = [];
  31335. // module.parent = undefined by default
  31336. module.children = [];
  31337. module.webpackPolyfill = 1;
  31338. }
  31339. return module;
  31340. }
  31341. /***/ }
  31342. /******/ ])
  31343. });
  31344. ;