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

35617 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.11.0
  8. * @date 2015-03-05
  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', // '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.components.push(this.itemSet);
  5753. this.itemsData = null; // DataSet
  5754. this.groupsData = null; // DataSet
  5755. this.on('tap', function (event) {
  5756. me.emit('click', me.getEventProperties(event))
  5757. });
  5758. this.on('doubletap', function (event) {
  5759. me.emit('doubleClick', me.getEventProperties(event))
  5760. });
  5761. this.dom.root.oncontextmenu = function (event) {
  5762. me.emit('contextmenu', me.getEventProperties(event))
  5763. };
  5764. // apply options
  5765. if (options) {
  5766. this.setOptions(options);
  5767. }
  5768. // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
  5769. if (groups) {
  5770. this.setGroups(groups);
  5771. }
  5772. // create itemset
  5773. if (items) {
  5774. this.setItems(items);
  5775. }
  5776. else {
  5777. this._redraw();
  5778. }
  5779. }
  5780. // Extend the functionality from Core
  5781. Timeline.prototype = new Core();
  5782. /**
  5783. * Force a redraw. The size of all items will be recalculated.
  5784. * Can be useful to manually redraw when option autoResize=false and the window
  5785. * has been resized, or when the items CSS has been changed.
  5786. */
  5787. Timeline.prototype.redraw = function() {
  5788. this.itemSet && this.itemSet.markDirty({refreshItems: true});
  5789. this._redraw();
  5790. };
  5791. /**
  5792. * Set items
  5793. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  5794. */
  5795. Timeline.prototype.setItems = function(items) {
  5796. var initialLoad = (this.itemsData == null);
  5797. // convert to type DataSet when needed
  5798. var newDataSet;
  5799. if (!items) {
  5800. newDataSet = null;
  5801. }
  5802. else if (items instanceof DataSet || items instanceof DataView) {
  5803. newDataSet = items;
  5804. }
  5805. else {
  5806. // turn an array into a dataset
  5807. newDataSet = new DataSet(items, {
  5808. type: {
  5809. start: 'Date',
  5810. end: 'Date'
  5811. }
  5812. });
  5813. }
  5814. // set items
  5815. this.itemsData = newDataSet;
  5816. this.itemSet && this.itemSet.setItems(newDataSet);
  5817. if (initialLoad) {
  5818. if (this.options.start != undefined || this.options.end != undefined) {
  5819. if (this.options.start == undefined || this.options.end == undefined) {
  5820. var dataRange = this._getDataRange();
  5821. }
  5822. var start = this.options.start != undefined ? this.options.start : dataRange.start;
  5823. var end = this.options.end != undefined ? this.options.end : dataRange.end;
  5824. this.setWindow(start, end, {animate: false});
  5825. }
  5826. else {
  5827. this.fit({animate: false});
  5828. }
  5829. }
  5830. };
  5831. /**
  5832. * Set groups
  5833. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  5834. */
  5835. Timeline.prototype.setGroups = function(groups) {
  5836. // convert to type DataSet when needed
  5837. var newDataSet;
  5838. if (!groups) {
  5839. newDataSet = null;
  5840. }
  5841. else if (groups instanceof DataSet || groups instanceof DataView) {
  5842. newDataSet = groups;
  5843. }
  5844. else {
  5845. // turn an array into a dataset
  5846. newDataSet = new DataSet(groups);
  5847. }
  5848. this.groupsData = newDataSet;
  5849. this.itemSet.setGroups(newDataSet);
  5850. };
  5851. /**
  5852. * Set selected items by their id. Replaces the current selection
  5853. * Unknown id's are silently ignored.
  5854. * @param {string[] | string} [ids] An array with zero or more id's of the items to be
  5855. * selected. If ids is an empty array, all items will be
  5856. * unselected.
  5857. * @param {Object} [options] Available options:
  5858. * `focus: boolean`
  5859. * If true, focus will be set to the selected item(s)
  5860. * `animate: boolean | number`
  5861. * If true (default), the range is animated
  5862. * smoothly to the new window.
  5863. * If a number, the number is taken as duration
  5864. * for the animation. Default duration is 500 ms.
  5865. * Only applicable when option focus is true.
  5866. */
  5867. Timeline.prototype.setSelection = function(ids, options) {
  5868. this.itemSet && this.itemSet.setSelection(ids);
  5869. if (options && options.focus) {
  5870. this.focus(ids, options);
  5871. }
  5872. };
  5873. /**
  5874. * Get the selected items by their id
  5875. * @return {Array} ids The ids of the selected items
  5876. */
  5877. Timeline.prototype.getSelection = function() {
  5878. return this.itemSet && this.itemSet.getSelection() || [];
  5879. };
  5880. /**
  5881. * Adjust the visible window such that the selected item (or multiple items)
  5882. * are centered on screen.
  5883. * @param {String | String[]} id An item id or array with item ids
  5884. * @param {Object} [options] Available options:
  5885. * `animate: boolean | number`
  5886. * If true (default), the range is animated
  5887. * smoothly to the new window.
  5888. * If a number, the number is taken as duration
  5889. * for the animation. Default duration is 500 ms.
  5890. * Only applicable when option focus is true
  5891. */
  5892. Timeline.prototype.focus = function(id, options) {
  5893. if (!this.itemsData || id == undefined) return;
  5894. var ids = Array.isArray(id) ? id : [id];
  5895. // get the specified item(s)
  5896. var itemsData = this.itemsData.getDataSet().get(ids, {
  5897. type: {
  5898. start: 'Date',
  5899. end: 'Date'
  5900. }
  5901. });
  5902. // calculate minimum start and maximum end of specified items
  5903. var start = null;
  5904. var end = null;
  5905. itemsData.forEach(function (itemData) {
  5906. var s = itemData.start.valueOf();
  5907. var e = 'end' in itemData ? itemData.end.valueOf() : itemData.start.valueOf();
  5908. if (start === null || s < start) {
  5909. start = s;
  5910. }
  5911. if (end === null || e > end) {
  5912. end = e;
  5913. }
  5914. });
  5915. if (start !== null && end !== null) {
  5916. // calculate the new middle and interval for the window
  5917. var middle = (start + end) / 2;
  5918. var interval = Math.max((this.range.end - this.range.start), (end - start) * 1.1);
  5919. var animate = (options && options.animate !== undefined) ? options.animate : true;
  5920. this.range.setRange(middle - interval / 2, middle + interval / 2, animate);
  5921. }
  5922. };
  5923. /**
  5924. * Get the data range of the item set.
  5925. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  5926. * When no minimum is found, min==null
  5927. * When no maximum is found, max==null
  5928. */
  5929. Timeline.prototype.getItemRange = function() {
  5930. // calculate min from start filed
  5931. var dataset = this.itemsData.getDataSet(),
  5932. min = null,
  5933. max = null;
  5934. if (dataset) {
  5935. // calculate the minimum value of the field 'start'
  5936. var minItem = dataset.min('start');
  5937. min = minItem ? util.convert(minItem.start, 'Date').valueOf() : null;
  5938. // Note: we convert first to Date and then to number because else
  5939. // a conversion from ISODate to Number will fail
  5940. // calculate maximum value of fields 'start' and 'end'
  5941. var maxStartItem = dataset.max('start');
  5942. if (maxStartItem) {
  5943. max = util.convert(maxStartItem.start, 'Date').valueOf();
  5944. }
  5945. var maxEndItem = dataset.max('end');
  5946. if (maxEndItem) {
  5947. if (max == null) {
  5948. max = util.convert(maxEndItem.end, 'Date').valueOf();
  5949. }
  5950. else {
  5951. max = Math.max(max, util.convert(maxEndItem.end, 'Date').valueOf());
  5952. }
  5953. }
  5954. }
  5955. return {
  5956. min: (min != null) ? new Date(min) : null,
  5957. max: (max != null) ? new Date(max) : null
  5958. };
  5959. };
  5960. /**
  5961. * Generate Timeline related information from an event
  5962. * @param {Event} event
  5963. * @return {Object} An object with related information, like on which area
  5964. * The event happened, whether clicked on an item, etc.
  5965. */
  5966. Timeline.prototype.getEventProperties = function (event) {
  5967. var item = this.itemSet.itemFromTarget(event);
  5968. var group = this.itemSet.groupFromTarget(event);
  5969. var pageX = event.gesture ? event.gesture.center.pageX : event.pageX;
  5970. var pageY = event.gesture ? event.gesture.center.pageY : event.pageY;
  5971. var x = pageX - util.getAbsoluteLeft(this.dom.centerContainer);
  5972. var y = pageY - util.getAbsoluteTop(this.dom.centerContainer);
  5973. var snap = this.itemSet.options.snap || null;
  5974. var scale = this.body.util.getScale();
  5975. var step = this.body.util.getStep();
  5976. var time = this._toTime(x);
  5977. var snappedTime = snap ? snap(time, scale, step) : time;
  5978. var element = util.getTarget(event);
  5979. var what = null;
  5980. if (item != null) {what = 'item';}
  5981. else if (util.hasParent(element, this.timeAxis.dom.foreground)) {what = 'axis';}
  5982. else if (this.timeAxis2 && util.hasParent(element, this.timeAxis2.dom.foreground)) {what = 'axis';}
  5983. else if (util.hasParent(element, this.itemSet.dom.labelSet)) {what = 'group-label';}
  5984. else if (util.hasParent(element, this.customTime.bar)) {what = 'custom-time';} // TODO: fix for multiple custom time bars
  5985. else if (util.hasParent(element, this.currentTime.bar)) {what = 'current-time';}
  5986. else if (util.hasParent(element, this.dom.center)) {what = 'background';}
  5987. return {
  5988. event: event,
  5989. item: item ? item.id : null,
  5990. group: group ? group.groupId : null,
  5991. what: what,
  5992. pageX: pageX,
  5993. pageY: pageY,
  5994. x: x,
  5995. y: y,
  5996. time: time,
  5997. snappedTime: snappedTime
  5998. }
  5999. };
  6000. module.exports = Timeline;
  6001. /***/ },
  6002. /* 14 */
  6003. /***/ function(module, exports, __webpack_require__) {
  6004. var Emitter = __webpack_require__(56);
  6005. var Hammer = __webpack_require__(45);
  6006. var util = __webpack_require__(1);
  6007. var DataSet = __webpack_require__(3);
  6008. var DataView = __webpack_require__(4);
  6009. var Range = __webpack_require__(17);
  6010. var Core = __webpack_require__(46);
  6011. var TimeAxis = __webpack_require__(35);
  6012. var CurrentTime = __webpack_require__(26);
  6013. var CustomTime = __webpack_require__(27);
  6014. var LineGraph = __webpack_require__(34);
  6015. /**
  6016. * Create a timeline visualization
  6017. * @param {HTMLElement} container
  6018. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  6019. * @param {Object} [options] See Graph2d.setOptions for the available options.
  6020. * @constructor
  6021. * @extends Core
  6022. */
  6023. function Graph2d (container, items, groups, options) {
  6024. // if the third element is options, the forth is groups (optionally);
  6025. if (!(Array.isArray(groups) || groups instanceof DataSet) && groups instanceof Object) {
  6026. var forthArgument = options;
  6027. options = groups;
  6028. groups = forthArgument;
  6029. }
  6030. var me = this;
  6031. this.defaultOptions = {
  6032. start: null,
  6033. end: null,
  6034. autoResize: true,
  6035. orientation: 'bottom',
  6036. width: null,
  6037. height: null,
  6038. maxHeight: null,
  6039. minHeight: null
  6040. };
  6041. this.options = util.deepExtend({}, this.defaultOptions);
  6042. // Create the DOM, props, and emitter
  6043. this._create(container);
  6044. // all components listed here will be repainted automatically
  6045. this.components = [];
  6046. this.body = {
  6047. dom: this.dom,
  6048. domProps: this.props,
  6049. emitter: {
  6050. on: this.on.bind(this),
  6051. off: this.off.bind(this),
  6052. emit: this.emit.bind(this)
  6053. },
  6054. hiddenDates: [],
  6055. util: {
  6056. toScreen: me._toScreen.bind(me),
  6057. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  6058. toTime: me._toTime.bind(me),
  6059. toGlobalTime : me._toGlobalTime.bind(me)
  6060. }
  6061. };
  6062. // range
  6063. this.range = new Range(this.body);
  6064. this.components.push(this.range);
  6065. this.body.range = this.range;
  6066. // time axis
  6067. this.timeAxis = new TimeAxis(this.body);
  6068. this.components.push(this.timeAxis);
  6069. //this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
  6070. // current time bar
  6071. this.currentTime = new CurrentTime(this.body);
  6072. this.components.push(this.currentTime);
  6073. // custom time bar
  6074. // Note: time bar will be attached in this.setOptions when selected
  6075. this.customTime = new CustomTime(this.body);
  6076. this.components.push(this.customTime);
  6077. // item set
  6078. this.linegraph = new LineGraph(this.body);
  6079. this.components.push(this.linegraph);
  6080. this.itemsData = null; // DataSet
  6081. this.groupsData = null; // DataSet
  6082. this.on('tap', function (event) {
  6083. me.emit('click', me.getEventProperties(event))
  6084. });
  6085. this.on('doubletap', function (event) {
  6086. me.emit('doubleClick', me.getEventProperties(event))
  6087. });
  6088. this.dom.root.oncontextmenu = function (event) {
  6089. me.emit('contextmenu', me.getEventProperties(event))
  6090. };
  6091. // apply options
  6092. if (options) {
  6093. this.setOptions(options);
  6094. }
  6095. // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
  6096. if (groups) {
  6097. this.setGroups(groups);
  6098. }
  6099. // create itemset
  6100. if (items) {
  6101. this.setItems(items);
  6102. }
  6103. else {
  6104. this._redraw();
  6105. }
  6106. }
  6107. // Extend the functionality from Core
  6108. Graph2d.prototype = new Core();
  6109. /**
  6110. * Set items
  6111. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  6112. */
  6113. Graph2d.prototype.setItems = function(items) {
  6114. var initialLoad = (this.itemsData == null);
  6115. // convert to type DataSet when needed
  6116. var newDataSet;
  6117. if (!items) {
  6118. newDataSet = null;
  6119. }
  6120. else if (items instanceof DataSet || items instanceof DataView) {
  6121. newDataSet = items;
  6122. }
  6123. else {
  6124. // turn an array into a dataset
  6125. newDataSet = new DataSet(items, {
  6126. type: {
  6127. start: 'Date',
  6128. end: 'Date'
  6129. }
  6130. });
  6131. }
  6132. // set items
  6133. this.itemsData = newDataSet;
  6134. this.linegraph && this.linegraph.setItems(newDataSet);
  6135. if (initialLoad) {
  6136. if (this.options.start != undefined || this.options.end != undefined) {
  6137. var start = this.options.start != undefined ? this.options.start : null;
  6138. var end = this.options.end != undefined ? this.options.end : null;
  6139. this.setWindow(start, end, {animate: false});
  6140. }
  6141. else {
  6142. this.fit({animate: false});
  6143. }
  6144. }
  6145. };
  6146. /**
  6147. * Set groups
  6148. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  6149. */
  6150. Graph2d.prototype.setGroups = function(groups) {
  6151. // convert to type DataSet when needed
  6152. var newDataSet;
  6153. if (!groups) {
  6154. newDataSet = null;
  6155. }
  6156. else if (groups instanceof DataSet || groups instanceof DataView) {
  6157. newDataSet = groups;
  6158. }
  6159. else {
  6160. // turn an array into a dataset
  6161. newDataSet = new DataSet(groups);
  6162. }
  6163. this.groupsData = newDataSet;
  6164. this.linegraph.setGroups(newDataSet);
  6165. };
  6166. /**
  6167. * 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).
  6168. * @param groupId
  6169. * @param width
  6170. * @param height
  6171. */
  6172. Graph2d.prototype.getLegend = function(groupId, width, height) {
  6173. if (width === undefined) {width = 15;}
  6174. if (height === undefined) {height = 15;}
  6175. if (this.linegraph.groups[groupId] !== undefined) {
  6176. return this.linegraph.groups[groupId].getLegend(width,height);
  6177. }
  6178. else {
  6179. return "cannot find group:" + groupId;
  6180. }
  6181. };
  6182. /**
  6183. * This checks if the visible option of the supplied group (by ID) is true or false.
  6184. * @param groupId
  6185. * @returns {*}
  6186. */
  6187. Graph2d.prototype.isGroupVisible = function(groupId) {
  6188. if (this.linegraph.groups[groupId] !== undefined) {
  6189. return (this.linegraph.groups[groupId].visible && (this.linegraph.options.groups.visibility[groupId] === undefined || this.linegraph.options.groups.visibility[groupId] == true));
  6190. }
  6191. else {
  6192. return false;
  6193. }
  6194. };
  6195. /**
  6196. * Get the data range of the item set.
  6197. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  6198. * When no minimum is found, min==null
  6199. * When no maximum is found, max==null
  6200. */
  6201. Graph2d.prototype.getItemRange = function() {
  6202. var min = null;
  6203. var max = null;
  6204. // calculate min from start filed
  6205. for (var groupId in this.linegraph.groups) {
  6206. if (this.linegraph.groups.hasOwnProperty(groupId)) {
  6207. if (this.linegraph.groups[groupId].visible == true) {
  6208. for (var i = 0; i < this.linegraph.groups[groupId].itemsData.length; i++) {
  6209. var item = this.linegraph.groups[groupId].itemsData[i];
  6210. var value = util.convert(item.x, 'Date').valueOf();
  6211. min = min == null ? value : min > value ? value : min;
  6212. max = max == null ? value : max < value ? value : max;
  6213. }
  6214. }
  6215. }
  6216. }
  6217. return {
  6218. min: (min != null) ? new Date(min) : null,
  6219. max: (max != null) ? new Date(max) : null
  6220. };
  6221. };
  6222. /**
  6223. * Generate Timeline related information from an event
  6224. * @param {Event} event
  6225. * @return {Object} An object with related information, like on which area
  6226. * The event happened, whether clicked on an item, etc.
  6227. */
  6228. Graph2d.prototype.getEventProperties = function (event) {
  6229. var pageX = event.gesture ? event.gesture.center.pageX : event.pageX;
  6230. var pageY = event.gesture ? event.gesture.center.pageY : event.pageY;
  6231. var x = pageX - util.getAbsoluteLeft(this.dom.centerContainer);
  6232. var y = pageY - util.getAbsoluteTop(this.dom.centerContainer);
  6233. var time = this._toTime(x);
  6234. var element = util.getTarget(event);
  6235. var what = null;
  6236. if (util.hasParent(element, this.timeAxis.dom.foreground)) {what = 'axis';}
  6237. else if (this.timeAxis2 && util.hasParent(element, this.timeAxis2.dom.foreground)) {what = 'axis';}
  6238. else if (util.hasParent(element, this.linegraph.yAxisLeft.dom.frame)) {what = 'data-axis';}
  6239. else if (util.hasParent(element, this.linegraph.yAxisRight.dom.frame)) {what = 'data-axis';}
  6240. else if (util.hasParent(element, this.linegraph.legendLeft.dom.frame)) {what = 'legend';}
  6241. else if (util.hasParent(element, this.linegraph.legendRight.dom.frame)) {what = 'legend';}
  6242. else if (util.hasParent(element, this.customTime.bar)) {what = 'custom-time';} // TODO: fix for multiple custom time bars
  6243. else if (util.hasParent(element, this.currentTime.bar)) {what = 'current-time';}
  6244. else if (util.hasParent(element, this.dom.center)) {what = 'background';}
  6245. var value = [];
  6246. var yAxisLeft = this.linegraph.yAxisLeft;
  6247. var yAxisRight = this.linegraph.yAxisRight;
  6248. if (!yAxisLeft.hidden) {
  6249. value.push(yAxisLeft.screenToValue(y));
  6250. }
  6251. if (!yAxisRight.hidden) {
  6252. value.push(yAxisRight.screenToValue(y));
  6253. }
  6254. return {
  6255. event: event,
  6256. what: what,
  6257. pageX: pageX,
  6258. pageY: pageY,
  6259. x: x,
  6260. y: y,
  6261. time: time,
  6262. value: value
  6263. }
  6264. };
  6265. module.exports = Graph2d;
  6266. /***/ },
  6267. /* 15 */
  6268. /***/ function(module, exports, __webpack_require__) {
  6269. /**
  6270. * Created by Alex on 10/3/2014.
  6271. */
  6272. var moment = __webpack_require__(44);
  6273. /**
  6274. * used in Core to convert the options into a volatile variable
  6275. *
  6276. * @param Core
  6277. */
  6278. exports.convertHiddenOptions = function(body, hiddenDates) {
  6279. body.hiddenDates = [];
  6280. if (hiddenDates) {
  6281. if (Array.isArray(hiddenDates) == true) {
  6282. for (var i = 0; i < hiddenDates.length; i++) {
  6283. if (hiddenDates[i].repeat === undefined) {
  6284. var dateItem = {};
  6285. dateItem.start = moment(hiddenDates[i].start).toDate().valueOf();
  6286. dateItem.end = moment(hiddenDates[i].end).toDate().valueOf();
  6287. body.hiddenDates.push(dateItem);
  6288. }
  6289. }
  6290. body.hiddenDates.sort(function (a, b) {
  6291. return a.start - b.start;
  6292. }); // sort by start time
  6293. }
  6294. }
  6295. };
  6296. /**
  6297. * create new entrees for the repeating hidden dates
  6298. * @param body
  6299. * @param hiddenDates
  6300. */
  6301. exports.updateHiddenDates = function (body, hiddenDates) {
  6302. if (hiddenDates && body.domProps.centerContainer.width !== undefined) {
  6303. exports.convertHiddenOptions(body, hiddenDates);
  6304. var start = moment(body.range.start);
  6305. var end = moment(body.range.end);
  6306. var totalRange = (body.range.end - body.range.start);
  6307. var pixelTime = totalRange / body.domProps.centerContainer.width;
  6308. for (var i = 0; i < hiddenDates.length; i++) {
  6309. if (hiddenDates[i].repeat !== undefined) {
  6310. var startDate = moment(hiddenDates[i].start);
  6311. var endDate = moment(hiddenDates[i].end);
  6312. if (startDate._d == "Invalid Date") {
  6313. throw new Error("Supplied start date is not valid: " + hiddenDates[i].start);
  6314. }
  6315. if (endDate._d == "Invalid Date") {
  6316. throw new Error("Supplied end date is not valid: " + hiddenDates[i].end);
  6317. }
  6318. var duration = endDate - startDate;
  6319. if (duration >= 4 * pixelTime) {
  6320. var offset = 0;
  6321. var runUntil = end.clone();
  6322. switch (hiddenDates[i].repeat) {
  6323. case "daily": // case of time
  6324. if (startDate.day() != endDate.day()) {
  6325. offset = 1;
  6326. }
  6327. startDate.dayOfYear(start.dayOfYear());
  6328. startDate.year(start.year());
  6329. startDate.subtract(7,'days');
  6330. endDate.dayOfYear(start.dayOfYear());
  6331. endDate.year(start.year());
  6332. endDate.subtract(7 - offset,'days');
  6333. runUntil.add(1, 'weeks');
  6334. break;
  6335. case "weekly":
  6336. var dayOffset = endDate.diff(startDate,'days')
  6337. var day = startDate.day();
  6338. // set the start date to the range.start
  6339. startDate.date(start.date());
  6340. startDate.month(start.month());
  6341. startDate.year(start.year());
  6342. endDate = startDate.clone();
  6343. // force
  6344. startDate.day(day);
  6345. endDate.day(day);
  6346. endDate.add(dayOffset,'days');
  6347. startDate.subtract(1,'weeks');
  6348. endDate.subtract(1,'weeks');
  6349. runUntil.add(1, 'weeks');
  6350. break
  6351. case "monthly":
  6352. if (startDate.month() != endDate.month()) {
  6353. offset = 1;
  6354. }
  6355. startDate.month(start.month());
  6356. startDate.year(start.year());
  6357. startDate.subtract(1,'months');
  6358. endDate.month(start.month());
  6359. endDate.year(start.year());
  6360. endDate.subtract(1,'months');
  6361. endDate.add(offset,'months');
  6362. runUntil.add(1, 'months');
  6363. break;
  6364. case "yearly":
  6365. if (startDate.year() != endDate.year()) {
  6366. offset = 1;
  6367. }
  6368. startDate.year(start.year());
  6369. startDate.subtract(1,'years');
  6370. endDate.year(start.year());
  6371. endDate.subtract(1,'years');
  6372. endDate.add(offset,'years');
  6373. runUntil.add(1, 'years');
  6374. break;
  6375. default:
  6376. console.log("Wrong repeat format, allowed are: daily, weekly, monthly, yearly. Given:", hiddenDates[i].repeat);
  6377. return;
  6378. }
  6379. while (startDate < runUntil) {
  6380. body.hiddenDates.push({start: startDate.valueOf(), end: endDate.valueOf()});
  6381. switch (hiddenDates[i].repeat) {
  6382. case "daily":
  6383. startDate.add(1, 'days');
  6384. endDate.add(1, 'days');
  6385. break;
  6386. case "weekly":
  6387. startDate.add(1, 'weeks');
  6388. endDate.add(1, 'weeks');
  6389. break
  6390. case "monthly":
  6391. startDate.add(1, 'months');
  6392. endDate.add(1, 'months');
  6393. break;
  6394. case "yearly":
  6395. startDate.add(1, 'y');
  6396. endDate.add(1, 'y');
  6397. break;
  6398. default:
  6399. console.log("Wrong repeat format, allowed are: daily, weekly, monthly, yearly. Given:", hiddenDates[i].repeat);
  6400. return;
  6401. }
  6402. }
  6403. body.hiddenDates.push({start: startDate.valueOf(), end: endDate.valueOf()});
  6404. }
  6405. }
  6406. }
  6407. // remove duplicates, merge where possible
  6408. exports.removeDuplicates(body);
  6409. // ensure the new positions are not on hidden dates
  6410. var startHidden = exports.isHidden(body.range.start, body.hiddenDates);
  6411. var endHidden = exports.isHidden(body.range.end,body.hiddenDates);
  6412. var rangeStart = body.range.start;
  6413. var rangeEnd = body.range.end;
  6414. if (startHidden.hidden == true) {rangeStart = body.range.startToFront == true ? startHidden.startDate - 1 : startHidden.endDate + 1;}
  6415. if (endHidden.hidden == true) {rangeEnd = body.range.endToFront == true ? endHidden.startDate - 1 : endHidden.endDate + 1;}
  6416. if (startHidden.hidden == true || endHidden.hidden == true) {
  6417. body.range._applyRange(rangeStart, rangeEnd);
  6418. }
  6419. }
  6420. }
  6421. /**
  6422. * remove duplicates from the hidden dates list. Duplicates are evil. They mess everything up.
  6423. * Scales with N^2
  6424. * @param body
  6425. */
  6426. exports.removeDuplicates = function(body) {
  6427. var hiddenDates = body.hiddenDates;
  6428. var safeDates = [];
  6429. for (var i = 0; i < hiddenDates.length; i++) {
  6430. for (var j = 0; j < hiddenDates.length; j++) {
  6431. if (i != j && hiddenDates[j].remove != true && hiddenDates[i].remove != true) {
  6432. // j inside i
  6433. if (hiddenDates[j].start >= hiddenDates[i].start && hiddenDates[j].end <= hiddenDates[i].end) {
  6434. hiddenDates[j].remove = true;
  6435. }
  6436. // j start inside i
  6437. else if (hiddenDates[j].start >= hiddenDates[i].start && hiddenDates[j].start <= hiddenDates[i].end) {
  6438. hiddenDates[i].end = hiddenDates[j].end;
  6439. hiddenDates[j].remove = true;
  6440. }
  6441. // j end inside i
  6442. else if (hiddenDates[j].end >= hiddenDates[i].start && hiddenDates[j].end <= hiddenDates[i].end) {
  6443. hiddenDates[i].start = hiddenDates[j].start;
  6444. hiddenDates[j].remove = true;
  6445. }
  6446. }
  6447. }
  6448. }
  6449. for (var i = 0; i < hiddenDates.length; i++) {
  6450. if (hiddenDates[i].remove !== true) {
  6451. safeDates.push(hiddenDates[i]);
  6452. }
  6453. }
  6454. body.hiddenDates = safeDates;
  6455. body.hiddenDates.sort(function (a, b) {
  6456. return a.start - b.start;
  6457. }); // sort by start time
  6458. }
  6459. exports.printDates = function(dates) {
  6460. for (var i =0; i < dates.length; i++) {
  6461. console.log(i, new Date(dates[i].start),new Date(dates[i].end), dates[i].start, dates[i].end, dates[i].remove);
  6462. }
  6463. }
  6464. /**
  6465. * Used in TimeStep to avoid the hidden times.
  6466. * @param timeStep
  6467. * @param previousTime
  6468. */
  6469. exports.stepOverHiddenDates = function(timeStep, previousTime) {
  6470. var stepInHidden = false;
  6471. var currentValue = timeStep.current.valueOf();
  6472. for (var i = 0; i < timeStep.hiddenDates.length; i++) {
  6473. var startDate = timeStep.hiddenDates[i].start;
  6474. var endDate = timeStep.hiddenDates[i].end;
  6475. if (currentValue >= startDate && currentValue < endDate) {
  6476. stepInHidden = true;
  6477. break;
  6478. }
  6479. }
  6480. if (stepInHidden == true && currentValue < timeStep._end.valueOf() && currentValue != previousTime) {
  6481. var prevValue = moment(previousTime);
  6482. var newValue = moment(endDate);
  6483. //check if the next step should be major
  6484. if (prevValue.year() != newValue.year()) {timeStep.switchedYear = true;}
  6485. else if (prevValue.month() != newValue.month()) {timeStep.switchedMonth = true;}
  6486. else if (prevValue.dayOfYear() != newValue.dayOfYear()) {timeStep.switchedDay = true;}
  6487. timeStep.current = newValue.toDate();
  6488. }
  6489. };
  6490. ///**
  6491. // * Used in TimeStep to avoid the hidden times.
  6492. // * @param timeStep
  6493. // * @param previousTime
  6494. // */
  6495. //exports.checkFirstStep = function(timeStep) {
  6496. // var stepInHidden = false;
  6497. // var currentValue = timeStep.current.valueOf();
  6498. // for (var i = 0; i < timeStep.hiddenDates.length; i++) {
  6499. // var startDate = timeStep.hiddenDates[i].start;
  6500. // var endDate = timeStep.hiddenDates[i].end;
  6501. // if (currentValue >= startDate && currentValue < endDate) {
  6502. // stepInHidden = true;
  6503. // break;
  6504. // }
  6505. // }
  6506. //
  6507. // if (stepInHidden == true && currentValue <= timeStep._end.valueOf()) {
  6508. // var newValue = moment(endDate);
  6509. // timeStep.current = newValue.toDate();
  6510. // }
  6511. //};
  6512. /**
  6513. * replaces the Core toScreen methods
  6514. * @param Core
  6515. * @param time
  6516. * @param width
  6517. * @returns {number}
  6518. */
  6519. exports.toScreen = function(Core, time, width) {
  6520. if (Core.body.hiddenDates.length == 0) {
  6521. var conversion = Core.range.conversion(width);
  6522. return (time.valueOf() - conversion.offset) * conversion.scale;
  6523. }
  6524. else {
  6525. var hidden = exports.isHidden(time, Core.body.hiddenDates)
  6526. if (hidden.hidden == true) {
  6527. time = hidden.startDate;
  6528. }
  6529. var duration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
  6530. time = exports.correctTimeForHidden(Core.body.hiddenDates, Core.range, time);
  6531. var conversion = Core.range.conversion(width, duration);
  6532. return (time.valueOf() - conversion.offset) * conversion.scale;
  6533. }
  6534. };
  6535. /**
  6536. * Replaces the core toTime methods
  6537. * @param body
  6538. * @param range
  6539. * @param x
  6540. * @param width
  6541. * @returns {Date}
  6542. */
  6543. exports.toTime = function(Core, x, width) {
  6544. if (Core.body.hiddenDates.length == 0) {
  6545. var conversion = Core.range.conversion(width);
  6546. return new Date(x / conversion.scale + conversion.offset);
  6547. }
  6548. else {
  6549. var hiddenDuration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
  6550. var totalDuration = Core.range.end - Core.range.start - hiddenDuration;
  6551. var partialDuration = totalDuration * x / width;
  6552. var accumulatedHiddenDuration = exports.getAccumulatedHiddenDuration(Core.body.hiddenDates, Core.range, partialDuration);
  6553. var newTime = new Date(accumulatedHiddenDuration + partialDuration + Core.range.start);
  6554. return newTime;
  6555. }
  6556. };
  6557. /**
  6558. * Support function
  6559. *
  6560. * @param hiddenDates
  6561. * @param range
  6562. * @returns {number}
  6563. */
  6564. exports.getHiddenDurationBetween = function(hiddenDates, start, end) {
  6565. var duration = 0;
  6566. for (var i = 0; i < hiddenDates.length; i++) {
  6567. var startDate = hiddenDates[i].start;
  6568. var endDate = hiddenDates[i].end;
  6569. // if time after the cutout, and the
  6570. if (startDate >= start && endDate < end) {
  6571. duration += endDate - startDate;
  6572. }
  6573. }
  6574. return duration;
  6575. };
  6576. /**
  6577. * Support function
  6578. * @param hiddenDates
  6579. * @param range
  6580. * @param time
  6581. * @returns {{duration: number, time: *, offset: number}}
  6582. */
  6583. exports.correctTimeForHidden = function(hiddenDates, range, time) {
  6584. time = moment(time).toDate().valueOf();
  6585. time -= exports.getHiddenDurationBefore(hiddenDates,range,time);
  6586. return time;
  6587. };
  6588. exports.getHiddenDurationBefore = function(hiddenDates, range, time) {
  6589. var timeOffset = 0;
  6590. time = moment(time).toDate().valueOf();
  6591. for (var i = 0; i < hiddenDates.length; i++) {
  6592. var startDate = hiddenDates[i].start;
  6593. var endDate = hiddenDates[i].end;
  6594. // if time after the cutout, and the
  6595. if (startDate >= range.start && endDate < range.end) {
  6596. if (time >= endDate) {
  6597. timeOffset += (endDate - startDate);
  6598. }
  6599. }
  6600. }
  6601. return timeOffset;
  6602. }
  6603. /**
  6604. * sum the duration from start to finish, including the hidden duration,
  6605. * until the required amount has been reached, return the accumulated hidden duration
  6606. * @param hiddenDates
  6607. * @param range
  6608. * @param time
  6609. * @returns {{duration: number, time: *, offset: number}}
  6610. */
  6611. exports.getAccumulatedHiddenDuration = function(hiddenDates, range, requiredDuration) {
  6612. var hiddenDuration = 0;
  6613. var duration = 0;
  6614. var previousPoint = range.start;
  6615. //exports.printDates(hiddenDates)
  6616. for (var i = 0; i < hiddenDates.length; i++) {
  6617. var startDate = hiddenDates[i].start;
  6618. var endDate = hiddenDates[i].end;
  6619. // if time after the cutout, and the
  6620. if (startDate >= range.start && endDate < range.end) {
  6621. duration += startDate - previousPoint;
  6622. previousPoint = endDate;
  6623. if (duration >= requiredDuration) {
  6624. break;
  6625. }
  6626. else {
  6627. hiddenDuration += endDate - startDate;
  6628. }
  6629. }
  6630. }
  6631. return hiddenDuration;
  6632. };
  6633. /**
  6634. * used to step over to either side of a hidden block. Correction is disabled on tablets, might be set to true
  6635. * @param hiddenDates
  6636. * @param time
  6637. * @param direction
  6638. * @param correctionEnabled
  6639. * @returns {*}
  6640. */
  6641. exports.snapAwayFromHidden = function(hiddenDates, time, direction, correctionEnabled) {
  6642. var isHidden = exports.isHidden(time, hiddenDates);
  6643. if (isHidden.hidden == true) {
  6644. if (direction < 0) {
  6645. if (correctionEnabled == true) {
  6646. return isHidden.startDate - (isHidden.endDate - time) - 1;
  6647. }
  6648. else {
  6649. return isHidden.startDate - 1;
  6650. }
  6651. }
  6652. else {
  6653. if (correctionEnabled == true) {
  6654. return isHidden.endDate + (time - isHidden.startDate) + 1;
  6655. }
  6656. else {
  6657. return isHidden.endDate + 1;
  6658. }
  6659. }
  6660. }
  6661. else {
  6662. return time;
  6663. }
  6664. }
  6665. /**
  6666. * Check if a time is hidden
  6667. *
  6668. * @param time
  6669. * @param hiddenDates
  6670. * @returns {{hidden: boolean, startDate: Window.start, endDate: *}}
  6671. */
  6672. exports.isHidden = function(time, hiddenDates) {
  6673. for (var i = 0; i < hiddenDates.length; i++) {
  6674. var startDate = hiddenDates[i].start;
  6675. var endDate = hiddenDates[i].end;
  6676. if (time >= startDate && time < endDate) { // if the start is entering a hidden zone
  6677. return {hidden: true, startDate: startDate, endDate: endDate};
  6678. break;
  6679. }
  6680. }
  6681. return {hidden: false, startDate: startDate, endDate: endDate};
  6682. }
  6683. /***/ },
  6684. /* 16 */
  6685. /***/ function(module, exports, __webpack_require__) {
  6686. /**
  6687. * @constructor DataStep
  6688. * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an
  6689. * end data point. The class itself determines the best scale (step size) based on the
  6690. * provided start Date, end Date, and minimumStep.
  6691. *
  6692. * If minimumStep is provided, the step size is chosen as close as possible
  6693. * to the minimumStep but larger than minimumStep. If minimumStep is not
  6694. * provided, the scale is set to 1 DAY.
  6695. * The minimumStep should correspond with the onscreen size of about 6 characters
  6696. *
  6697. * Alternatively, you can set a scale by hand.
  6698. * After creation, you can initialize the class by executing first(). Then you
  6699. * can iterate from the start date to the end date via next(). You can check if
  6700. * the end date is reached with the function hasNext(). After each step, you can
  6701. * retrieve the current date via getCurrent().
  6702. * The DataStep has scales ranging from milliseconds, seconds, minutes, hours,
  6703. * days, to years.
  6704. *
  6705. * Version: 1.2
  6706. *
  6707. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  6708. * or new Date(2010, 9, 21, 23, 45, 00)
  6709. * @param {Date} [end] The end date
  6710. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  6711. */
  6712. function DataStep(start, end, minimumStep, containerHeight, customRange, alignZeros) {
  6713. // variables
  6714. this.current = 0;
  6715. this.autoScale = true;
  6716. this.stepIndex = 0;
  6717. this.step = 1;
  6718. this.scale = 1;
  6719. this.marginStart;
  6720. this.marginEnd;
  6721. this.deadSpace = 0;
  6722. this.majorSteps = [1, 2, 5, 10];
  6723. this.minorSteps = [0.25, 0.5, 1, 2];
  6724. this.alignZeros = alignZeros;
  6725. this.setRange(start, end, minimumStep, containerHeight, customRange);
  6726. }
  6727. /**
  6728. * Set a new range
  6729. * If minimumStep is provided, the step size is chosen as close as possible
  6730. * to the minimumStep but larger than minimumStep. If minimumStep is not
  6731. * provided, the scale is set to 1 DAY.
  6732. * The minimumStep should correspond with the onscreen size of about 6 characters
  6733. * @param {Number} [start] The start date and time.
  6734. * @param {Number} [end] The end date and time.
  6735. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  6736. */
  6737. DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, customRange) {
  6738. this._start = customRange.min === undefined ? start : customRange.min;
  6739. this._end = customRange.max === undefined ? end : customRange.max;
  6740. if (this._start == this._end) {
  6741. this._start -= 0.75;
  6742. this._end += 1;
  6743. }
  6744. if (this.autoScale == true) {
  6745. this.setMinimumStep(minimumStep, containerHeight);
  6746. }
  6747. this.setFirst(customRange);
  6748. };
  6749. /**
  6750. * Automatically determine the scale that bests fits the provided minimum step
  6751. * @param {Number} [minimumStep] The minimum step size in milliseconds
  6752. */
  6753. DataStep.prototype.setMinimumStep = function(minimumStep, containerHeight) {
  6754. // round to floor
  6755. var size = this._end - this._start;
  6756. var safeSize = size * 1.2;
  6757. var minimumStepValue = minimumStep * (safeSize / containerHeight);
  6758. var orderOfMagnitude = Math.round(Math.log(safeSize)/Math.LN10);
  6759. var minorStepIdx = -1;
  6760. var magnitudefactor = Math.pow(10,orderOfMagnitude);
  6761. var start = 0;
  6762. if (orderOfMagnitude < 0) {
  6763. start = orderOfMagnitude;
  6764. }
  6765. var solutionFound = false;
  6766. for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) {
  6767. magnitudefactor = Math.pow(10,i);
  6768. for (var j = 0; j < this.minorSteps.length; j++) {
  6769. var stepSize = magnitudefactor * this.minorSteps[j];
  6770. if (stepSize >= minimumStepValue) {
  6771. solutionFound = true;
  6772. minorStepIdx = j;
  6773. break;
  6774. }
  6775. }
  6776. if (solutionFound == true) {
  6777. break;
  6778. }
  6779. }
  6780. this.stepIndex = minorStepIdx;
  6781. this.scale = magnitudefactor;
  6782. this.step = magnitudefactor * this.minorSteps[minorStepIdx];
  6783. };
  6784. /**
  6785. * Round the current date to the first minor date value
  6786. * This must be executed once when the current date is set to start Date
  6787. */
  6788. DataStep.prototype.setFirst = function(customRange) {
  6789. if (customRange === undefined) {
  6790. customRange = {};
  6791. }
  6792. var niceStart = customRange.min === undefined ? this._start - (this.scale * 2 * this.minorSteps[this.stepIndex]) : customRange.min;
  6793. var niceEnd = customRange.max === undefined ? this._end + (this.scale * this.minorSteps[this.stepIndex]) : customRange.max;
  6794. this.marginEnd = customRange.max === undefined ? this.roundToMinor(niceEnd) : customRange.max;
  6795. this.marginStart = customRange.min === undefined ? this.roundToMinor(niceStart) : customRange.min;
  6796. // if we need to align the zero's we need to make sure that there is a zero to use.
  6797. if (this.alignZeros == true && (this.marginEnd - this.marginStart) % this.step != 0) {
  6798. this.marginEnd += this.marginEnd % this.step;
  6799. }
  6800. this.deadSpace = this.roundToMinor(niceEnd) - niceEnd + this.roundToMinor(niceStart) - niceStart;
  6801. this.marginRange = this.marginEnd - this.marginStart;
  6802. this.current = this.marginEnd;
  6803. };
  6804. DataStep.prototype.roundToMinor = function(value) {
  6805. var rounded = value - (value % (this.scale * this.minorSteps[this.stepIndex]));
  6806. if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) {
  6807. return rounded + (this.scale * this.minorSteps[this.stepIndex]);
  6808. }
  6809. else {
  6810. return rounded;
  6811. }
  6812. }
  6813. /**
  6814. * Check if the there is a next step
  6815. * @return {boolean} true if the current date has not passed the end date
  6816. */
  6817. DataStep.prototype.hasNext = function () {
  6818. return (this.current >= this.marginStart);
  6819. };
  6820. /**
  6821. * Do the next step
  6822. */
  6823. DataStep.prototype.next = function() {
  6824. var prev = this.current;
  6825. this.current -= this.step;
  6826. // safety mechanism: if current time is still unchanged, move to the end
  6827. if (this.current == prev) {
  6828. this.current = this._end;
  6829. }
  6830. };
  6831. /**
  6832. * Do the next step
  6833. */
  6834. DataStep.prototype.previous = function() {
  6835. this.current += this.step;
  6836. this.marginEnd += this.step;
  6837. this.marginRange = this.marginEnd - this.marginStart;
  6838. };
  6839. /**
  6840. * Get the current datetime
  6841. * @return {String} current The current date
  6842. */
  6843. DataStep.prototype.getCurrent = function(decimals) {
  6844. // prevent round-off errors when close to zero
  6845. var current = (Math.abs(this.current) < this.step / 2) ? 0 : this.current;
  6846. var toPrecision = '' + Number(current).toPrecision(5);
  6847. // If decimals is specified, then limit or extend the string as required
  6848. if(decimals !== undefined && !isNaN(Number(decimals))) {
  6849. // If string includes exponent, then we need to add it to the end
  6850. var exp = "";
  6851. var index = toPrecision.indexOf("e");
  6852. if(index != -1) {
  6853. // Get the exponent
  6854. exp = toPrecision.slice(index);
  6855. // Remove the exponent in case we need to zero-extend
  6856. toPrecision = toPrecision.slice(0, index);
  6857. }
  6858. index = Math.max(toPrecision.indexOf(","), toPrecision.indexOf("."));
  6859. if(index === -1) {
  6860. // No decimal found - if we want decimals, then we need to add it
  6861. if(decimals !== 0) {
  6862. toPrecision += '.';
  6863. }
  6864. // Calculate how long the string should be
  6865. index = toPrecision.length + decimals;
  6866. }
  6867. else if(decimals !== 0) {
  6868. // Calculate how long the string should be - accounting for the decimal place
  6869. index += decimals + 1;
  6870. }
  6871. if(index > toPrecision.length) {
  6872. // We need to add zeros!
  6873. for(var cnt = index - toPrecision.length; cnt > 0; cnt--) {
  6874. toPrecision += '0';
  6875. }
  6876. }
  6877. else {
  6878. // we need to remove characters
  6879. toPrecision = toPrecision.slice(0, index);
  6880. }
  6881. // Add the exponent if there is one
  6882. toPrecision += exp;
  6883. }
  6884. else {
  6885. if (toPrecision.indexOf(",") != -1 || toPrecision.indexOf(".") != -1) {
  6886. // If no decimal is specified, and there are decimal places, remove trailing zeros
  6887. for (var i = toPrecision.length - 1; i > 0; i--) {
  6888. if (toPrecision[i] == "0") {
  6889. toPrecision = toPrecision.slice(0, i);
  6890. }
  6891. else if (toPrecision[i] == "." || toPrecision[i] == ",") {
  6892. toPrecision = toPrecision.slice(0, i);
  6893. break;
  6894. }
  6895. else {
  6896. break;
  6897. }
  6898. }
  6899. }
  6900. }
  6901. return toPrecision;
  6902. };
  6903. /**
  6904. * Check if the current value is a major value (for example when the step
  6905. * is DAY, a major value is each first day of the MONTH)
  6906. * @return {boolean} true if current date is major, else false.
  6907. */
  6908. DataStep.prototype.isMajor = function() {
  6909. return (this.current % (this.scale * this.majorSteps[this.stepIndex]) == 0);
  6910. };
  6911. module.exports = DataStep;
  6912. /***/ },
  6913. /* 17 */
  6914. /***/ function(module, exports, __webpack_require__) {
  6915. var util = __webpack_require__(1);
  6916. var hammerUtil = __webpack_require__(47);
  6917. var moment = __webpack_require__(44);
  6918. var Component = __webpack_require__(25);
  6919. var DateUtil = __webpack_require__(15);
  6920. /**
  6921. * @constructor Range
  6922. * A Range controls a numeric range with a start and end value.
  6923. * The Range adjusts the range based on mouse events or programmatic changes,
  6924. * and triggers events when the range is changing or has been changed.
  6925. * @param {{dom: Object, domProps: Object, emitter: Emitter}} body
  6926. * @param {Object} [options] See description at Range.setOptions
  6927. */
  6928. function Range(body, options) {
  6929. var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
  6930. this.start = now.clone().add(-3, 'days').valueOf(); // Number
  6931. this.end = now.clone().add(4, 'days').valueOf(); // Number
  6932. this.body = body;
  6933. this.deltaDifference = 0;
  6934. this.scaleOffset = 0;
  6935. this.startToFront = false;
  6936. this.endToFront = true;
  6937. // default options
  6938. this.defaultOptions = {
  6939. start: null,
  6940. end: null,
  6941. direction: 'horizontal', // 'horizontal' or 'vertical'
  6942. moveable: true,
  6943. zoomable: true,
  6944. min: null,
  6945. max: null,
  6946. zoomMin: 10, // milliseconds
  6947. zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000 // milliseconds
  6948. };
  6949. this.options = util.extend({}, this.defaultOptions);
  6950. this.props = {
  6951. touch: {}
  6952. };
  6953. this.animateTimer = null;
  6954. // drag listeners for dragging
  6955. this.body.emitter.on('dragstart', this._onDragStart.bind(this));
  6956. this.body.emitter.on('drag', this._onDrag.bind(this));
  6957. this.body.emitter.on('dragend', this._onDragEnd.bind(this));
  6958. // ignore dragging when holding
  6959. this.body.emitter.on('hold', this._onHold.bind(this));
  6960. // mouse wheel for zooming
  6961. this.body.emitter.on('mousewheel', this._onMouseWheel.bind(this));
  6962. this.body.emitter.on('DOMMouseScroll', this._onMouseWheel.bind(this)); // For FF
  6963. // pinch to zoom
  6964. this.body.emitter.on('touch', this._onTouch.bind(this));
  6965. this.body.emitter.on('pinch', this._onPinch.bind(this));
  6966. this.setOptions(options);
  6967. }
  6968. Range.prototype = new Component();
  6969. /**
  6970. * Set options for the range controller
  6971. * @param {Object} options Available options:
  6972. * {Number | Date | String} start Start date for the range
  6973. * {Number | Date | String} end End date for the range
  6974. * {Number} min Minimum value for start
  6975. * {Number} max Maximum value for end
  6976. * {Number} zoomMin Set a minimum value for
  6977. * (end - start).
  6978. * {Number} zoomMax Set a maximum value for
  6979. * (end - start).
  6980. * {Boolean} moveable Enable moving of the range
  6981. * by dragging. True by default
  6982. * {Boolean} zoomable Enable zooming of the range
  6983. * by pinching/scrolling. True by default
  6984. */
  6985. Range.prototype.setOptions = function (options) {
  6986. if (options) {
  6987. // copy the options that we know
  6988. var fields = ['direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable', 'activate', 'hiddenDates'];
  6989. util.selectiveExtend(fields, this.options, options);
  6990. if ('start' in options || 'end' in options) {
  6991. // apply a new range. both start and end are optional
  6992. this.setRange(options.start, options.end);
  6993. }
  6994. }
  6995. };
  6996. /**
  6997. * Test whether direction has a valid value
  6998. * @param {String} direction 'horizontal' or 'vertical'
  6999. */
  7000. function validateDirection (direction) {
  7001. if (direction != 'horizontal' && direction != 'vertical') {
  7002. throw new TypeError('Unknown direction "' + direction + '". ' +
  7003. 'Choose "horizontal" or "vertical".');
  7004. }
  7005. }
  7006. /**
  7007. * Set a new start and end range
  7008. * @param {Date | Number | String} [start]
  7009. * @param {Date | Number | String} [end]
  7010. * @param {boolean | number} [animate=false] If true, the range is animated
  7011. * smoothly to the new window.
  7012. * If animate is a number, the
  7013. * number is taken as duration
  7014. * Default duration is 500 ms.
  7015. * @param {Boolean} [byUser=false]
  7016. *
  7017. */
  7018. Range.prototype.setRange = function(start, end, animate, byUser) {
  7019. if (byUser !== true) {
  7020. byUser = false;
  7021. }
  7022. var _start = start != undefined ? util.convert(start, 'Date').valueOf() : null;
  7023. var _end = end != undefined ? util.convert(end, 'Date').valueOf() : null;
  7024. this._cancelAnimation();
  7025. if (animate) {
  7026. var me = this;
  7027. var initStart = this.start;
  7028. var initEnd = this.end;
  7029. var duration = typeof animate === 'number' ? animate : 500;
  7030. var initTime = new Date().valueOf();
  7031. var anyChanged = false;
  7032. var next = function () {
  7033. if (!me.props.touch.dragging) {
  7034. var now = new Date().valueOf();
  7035. var time = now - initTime;
  7036. var done = time > duration;
  7037. var s = (done || _start === null) ? _start : util.easeInOutQuad(time, initStart, _start, duration);
  7038. var e = (done || _end === null) ? _end : util.easeInOutQuad(time, initEnd, _end, duration);
  7039. changed = me._applyRange(s, e);
  7040. DateUtil.updateHiddenDates(me.body, me.options.hiddenDates);
  7041. anyChanged = anyChanged || changed;
  7042. if (changed) {
  7043. me.body.emitter.emit('rangechange', {start: new Date(me.start), end: new Date(me.end), byUser:byUser});
  7044. }
  7045. if (done) {
  7046. if (anyChanged) {
  7047. me.body.emitter.emit('rangechanged', {start: new Date(me.start), end: new Date(me.end), byUser:byUser});
  7048. }
  7049. }
  7050. else {
  7051. // animate with as high as possible frame rate, leave 20 ms in between
  7052. // each to prevent the browser from blocking
  7053. me.animateTimer = setTimeout(next, 20);
  7054. }
  7055. }
  7056. };
  7057. return next();
  7058. }
  7059. else {
  7060. var changed = this._applyRange(_start, _end);
  7061. DateUtil.updateHiddenDates(this.body, this.options.hiddenDates);
  7062. if (changed) {
  7063. var params = {start: new Date(this.start), end: new Date(this.end), byUser:byUser};
  7064. this.body.emitter.emit('rangechange', params);
  7065. this.body.emitter.emit('rangechanged', params);
  7066. }
  7067. }
  7068. };
  7069. /**
  7070. * Stop an animation
  7071. * @private
  7072. */
  7073. Range.prototype._cancelAnimation = function () {
  7074. if (this.animateTimer) {
  7075. clearTimeout(this.animateTimer);
  7076. this.animateTimer = null;
  7077. }
  7078. };
  7079. /**
  7080. * Set a new start and end range. This method is the same as setRange, but
  7081. * does not trigger a range change and range changed event, and it returns
  7082. * true when the range is changed
  7083. * @param {Number} [start]
  7084. * @param {Number} [end]
  7085. * @return {Boolean} changed
  7086. * @private
  7087. */
  7088. Range.prototype._applyRange = function(start, end) {
  7089. var newStart = (start != null) ? util.convert(start, 'Date').valueOf() : this.start,
  7090. newEnd = (end != null) ? util.convert(end, 'Date').valueOf() : this.end,
  7091. max = (this.options.max != null) ? util.convert(this.options.max, 'Date').valueOf() : null,
  7092. min = (this.options.min != null) ? util.convert(this.options.min, 'Date').valueOf() : null,
  7093. diff;
  7094. // check for valid number
  7095. if (isNaN(newStart) || newStart === null) {
  7096. throw new Error('Invalid start "' + start + '"');
  7097. }
  7098. if (isNaN(newEnd) || newEnd === null) {
  7099. throw new Error('Invalid end "' + end + '"');
  7100. }
  7101. // prevent start < end
  7102. if (newEnd < newStart) {
  7103. newEnd = newStart;
  7104. }
  7105. // prevent start < min
  7106. if (min !== null) {
  7107. if (newStart < min) {
  7108. diff = (min - newStart);
  7109. newStart += diff;
  7110. newEnd += diff;
  7111. // prevent end > max
  7112. if (max != null) {
  7113. if (newEnd > max) {
  7114. newEnd = max;
  7115. }
  7116. }
  7117. }
  7118. }
  7119. // prevent end > max
  7120. if (max !== null) {
  7121. if (newEnd > max) {
  7122. diff = (newEnd - max);
  7123. newStart -= diff;
  7124. newEnd -= diff;
  7125. // prevent start < min
  7126. if (min != null) {
  7127. if (newStart < min) {
  7128. newStart = min;
  7129. }
  7130. }
  7131. }
  7132. }
  7133. // prevent (end-start) < zoomMin
  7134. if (this.options.zoomMin !== null) {
  7135. var zoomMin = parseFloat(this.options.zoomMin);
  7136. if (zoomMin < 0) {
  7137. zoomMin = 0;
  7138. }
  7139. if ((newEnd - newStart) < zoomMin) {
  7140. if ((this.end - this.start) === zoomMin && newStart > this.start && newEnd < this.end) {
  7141. // ignore this action, we are already zoomed to the minimum
  7142. newStart = this.start;
  7143. newEnd = this.end;
  7144. }
  7145. else {
  7146. // zoom to the minimum
  7147. diff = (zoomMin - (newEnd - newStart));
  7148. newStart -= diff / 2;
  7149. newEnd += diff / 2;
  7150. }
  7151. }
  7152. }
  7153. // prevent (end-start) > zoomMax
  7154. if (this.options.zoomMax !== null) {
  7155. var zoomMax = parseFloat(this.options.zoomMax);
  7156. if (zoomMax < 0) {
  7157. zoomMax = 0;
  7158. }
  7159. if ((newEnd - newStart) > zoomMax) {
  7160. if ((this.end - this.start) === zoomMax && newStart < this.start && newEnd > this.end) {
  7161. // ignore this action, we are already zoomed to the maximum
  7162. newStart = this.start;
  7163. newEnd = this.end;
  7164. }
  7165. else {
  7166. // zoom to the maximum
  7167. diff = ((newEnd - newStart) - zoomMax);
  7168. newStart += diff / 2;
  7169. newEnd -= diff / 2;
  7170. }
  7171. }
  7172. }
  7173. var changed = (this.start != newStart || this.end != newEnd);
  7174. // 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)
  7175. if (!((newStart >= this.start && newStart <= this.end) || (newEnd >= this.start && newEnd <= this.end)) &&
  7176. !((this.start >= newStart && this.start <= newEnd) || (this.end >= newStart && this.end <= newEnd) )) {
  7177. this.body.emitter.emit('checkRangedItems');
  7178. }
  7179. this.start = newStart;
  7180. this.end = newEnd;
  7181. return changed;
  7182. };
  7183. /**
  7184. * Retrieve the current range.
  7185. * @return {Object} An object with start and end properties
  7186. */
  7187. Range.prototype.getRange = function() {
  7188. return {
  7189. start: this.start,
  7190. end: this.end
  7191. };
  7192. };
  7193. /**
  7194. * Calculate the conversion offset and scale for current range, based on
  7195. * the provided width
  7196. * @param {Number} width
  7197. * @returns {{offset: number, scale: number}} conversion
  7198. */
  7199. Range.prototype.conversion = function (width, totalHidden) {
  7200. return Range.conversion(this.start, this.end, width, totalHidden);
  7201. };
  7202. /**
  7203. * Static method to calculate the conversion offset and scale for a range,
  7204. * based on the provided start, end, and width
  7205. * @param {Number} start
  7206. * @param {Number} end
  7207. * @param {Number} width
  7208. * @returns {{offset: number, scale: number}} conversion
  7209. */
  7210. Range.conversion = function (start, end, width, totalHidden) {
  7211. if (totalHidden === undefined) {
  7212. totalHidden = 0;
  7213. }
  7214. if (width != 0 && (end - start != 0)) {
  7215. return {
  7216. offset: start,
  7217. scale: width / (end - start - totalHidden)
  7218. }
  7219. }
  7220. else {
  7221. return {
  7222. offset: 0,
  7223. scale: 1
  7224. };
  7225. }
  7226. };
  7227. /**
  7228. * Start dragging horizontally or vertically
  7229. * @param {Event} event
  7230. * @private
  7231. */
  7232. Range.prototype._onDragStart = function(event) {
  7233. this.deltaDifference = 0;
  7234. this.previousDelta = 0;
  7235. // only allow dragging when configured as movable
  7236. if (!this.options.moveable) return;
  7237. // refuse to drag when we where pinching to prevent the timeline make a jump
  7238. // when releasing the fingers in opposite order from the touch screen
  7239. if (!this.props.touch.allowDragging) return;
  7240. this.props.touch.start = this.start;
  7241. this.props.touch.end = this.end;
  7242. this.props.touch.dragging = true;
  7243. if (this.body.dom.root) {
  7244. this.body.dom.root.style.cursor = 'move';
  7245. }
  7246. };
  7247. /**
  7248. * Perform dragging operation
  7249. * @param {Event} event
  7250. * @private
  7251. */
  7252. Range.prototype._onDrag = function (event) {
  7253. // only allow dragging when configured as movable
  7254. if (!this.options.moveable) return;
  7255. // refuse to drag when we where pinching to prevent the timeline make a jump
  7256. // when releasing the fingers in opposite order from the touch screen
  7257. if (!this.props.touch.allowDragging) return;
  7258. var direction = this.options.direction;
  7259. validateDirection(direction);
  7260. var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY;
  7261. delta -= this.deltaDifference;
  7262. var interval = (this.props.touch.end - this.props.touch.start);
  7263. // normalize dragging speed if cutout is in between.
  7264. var duration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  7265. interval -= duration;
  7266. var width = (direction == 'horizontal') ? this.body.domProps.center.width : this.body.domProps.center.height;
  7267. var diffRange = -delta / width * interval;
  7268. var newStart = this.props.touch.start + diffRange;
  7269. var newEnd = this.props.touch.end + diffRange;
  7270. // snapping times away from hidden zones
  7271. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, this.previousDelta-delta, true);
  7272. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, this.previousDelta-delta, true);
  7273. if (safeStart != newStart || safeEnd != newEnd) {
  7274. this.deltaDifference += delta;
  7275. this.props.touch.start = safeStart;
  7276. this.props.touch.end = safeEnd;
  7277. this._onDrag(event);
  7278. return;
  7279. }
  7280. this.previousDelta = delta;
  7281. this._applyRange(newStart, newEnd);
  7282. // fire a rangechange event
  7283. this.body.emitter.emit('rangechange', {
  7284. start: new Date(this.start),
  7285. end: new Date(this.end),
  7286. byUser: true
  7287. });
  7288. };
  7289. /**
  7290. * Stop dragging operation
  7291. * @param {event} event
  7292. * @private
  7293. */
  7294. Range.prototype._onDragEnd = function (event) {
  7295. // only allow dragging when configured as movable
  7296. if (!this.options.moveable) return;
  7297. // refuse to drag when we where pinching to prevent the timeline make a jump
  7298. // when releasing the fingers in opposite order from the touch screen
  7299. if (!this.props.touch.allowDragging) return;
  7300. this.props.touch.dragging = false;
  7301. if (this.body.dom.root) {
  7302. this.body.dom.root.style.cursor = 'auto';
  7303. }
  7304. // fire a rangechanged event
  7305. this.body.emitter.emit('rangechanged', {
  7306. start: new Date(this.start),
  7307. end: new Date(this.end),
  7308. byUser: true
  7309. });
  7310. };
  7311. /**
  7312. * Event handler for mouse wheel event, used to zoom
  7313. * Code from http://adomas.org/javascript-mouse-wheel/
  7314. * @param {Event} event
  7315. * @private
  7316. */
  7317. Range.prototype._onMouseWheel = function(event) {
  7318. // only allow zooming when configured as zoomable and moveable
  7319. if (!(this.options.zoomable && this.options.moveable)) return;
  7320. // retrieve delta
  7321. var delta = 0;
  7322. if (event.wheelDelta) { /* IE/Opera. */
  7323. delta = event.wheelDelta / 120;
  7324. } else if (event.detail) { /* Mozilla case. */
  7325. // In Mozilla, sign of delta is different than in IE.
  7326. // Also, delta is multiple of 3.
  7327. delta = -event.detail / 3;
  7328. }
  7329. // If delta is nonzero, handle it.
  7330. // Basically, delta is now positive if wheel was scrolled up,
  7331. // and negative, if wheel was scrolled down.
  7332. if (delta) {
  7333. // perform the zoom action. Delta is normally 1 or -1
  7334. // adjust a negative delta such that zooming in with delta 0.1
  7335. // equals zooming out with a delta -0.1
  7336. var scale;
  7337. if (delta < 0) {
  7338. scale = 1 - (delta / 5);
  7339. }
  7340. else {
  7341. scale = 1 / (1 + (delta / 5)) ;
  7342. }
  7343. // calculate center, the date to zoom around
  7344. var gesture = hammerUtil.fakeGesture(this, event),
  7345. pointer = getPointer(gesture.center, this.body.dom.center),
  7346. pointerDate = this._pointerToDate(pointer);
  7347. this.zoom(scale, pointerDate, delta);
  7348. }
  7349. // Prevent default actions caused by mouse wheel
  7350. // (else the page and timeline both zoom and scroll)
  7351. event.preventDefault();
  7352. };
  7353. /**
  7354. * Start of a touch gesture
  7355. * @private
  7356. */
  7357. Range.prototype._onTouch = function (event) {
  7358. this.props.touch.start = this.start;
  7359. this.props.touch.end = this.end;
  7360. this.props.touch.allowDragging = true;
  7361. this.props.touch.center = null;
  7362. this.scaleOffset = 0;
  7363. this.deltaDifference = 0;
  7364. };
  7365. /**
  7366. * On start of a hold gesture
  7367. * @private
  7368. */
  7369. Range.prototype._onHold = function () {
  7370. this.props.touch.allowDragging = false;
  7371. };
  7372. /**
  7373. * Handle pinch event
  7374. * @param {Event} event
  7375. * @private
  7376. */
  7377. Range.prototype._onPinch = function (event) {
  7378. // only allow zooming when configured as zoomable and moveable
  7379. if (!(this.options.zoomable && this.options.moveable)) return;
  7380. this.props.touch.allowDragging = false;
  7381. if (event.gesture.touches.length > 1) {
  7382. if (!this.props.touch.center) {
  7383. this.props.touch.center = getPointer(event.gesture.center, this.body.dom.center);
  7384. }
  7385. var scale = 1 / (event.gesture.scale + this.scaleOffset);
  7386. var centerDate = this._pointerToDate(this.props.touch.center);
  7387. var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  7388. var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this, centerDate);
  7389. var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore;
  7390. // calculate new start and end
  7391. var newStart = (centerDate - hiddenDurationBefore) + (this.props.touch.start - (centerDate - hiddenDurationBefore)) * scale;
  7392. var newEnd = (centerDate + hiddenDurationAfter) + (this.props.touch.end - (centerDate + hiddenDurationAfter)) * scale;
  7393. // snapping times away from hidden zones
  7394. this.startToFront = 1 - scale > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  7395. this.endToFront = scale - 1 > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  7396. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, 1 - scale, true);
  7397. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, scale - 1, true);
  7398. if (safeStart != newStart || safeEnd != newEnd) {
  7399. this.props.touch.start = safeStart;
  7400. this.props.touch.end = safeEnd;
  7401. this.scaleOffset = 1 - event.gesture.scale;
  7402. newStart = safeStart;
  7403. newEnd = safeEnd;
  7404. }
  7405. this.setRange(newStart, newEnd, false, true);
  7406. this.startToFront = false; // revert to default
  7407. this.endToFront = true; // revert to default
  7408. }
  7409. };
  7410. /**
  7411. * Helper function to calculate the center date for zooming
  7412. * @param {{x: Number, y: Number}} pointer
  7413. * @return {number} date
  7414. * @private
  7415. */
  7416. Range.prototype._pointerToDate = function (pointer) {
  7417. var conversion;
  7418. var direction = this.options.direction;
  7419. validateDirection(direction);
  7420. if (direction == 'horizontal') {
  7421. return this.body.util.toTime(pointer.x).valueOf();
  7422. }
  7423. else {
  7424. var height = this.body.domProps.center.height;
  7425. conversion = this.conversion(height);
  7426. return pointer.y / conversion.scale + conversion.offset;
  7427. }
  7428. };
  7429. /**
  7430. * Get the pointer location relative to the location of the dom element
  7431. * @param {{pageX: Number, pageY: Number}} touch
  7432. * @param {Element} element HTML DOM element
  7433. * @return {{x: Number, y: Number}} pointer
  7434. * @private
  7435. */
  7436. function getPointer (touch, element) {
  7437. return {
  7438. x: touch.pageX - util.getAbsoluteLeft(element),
  7439. y: touch.pageY - util.getAbsoluteTop(element)
  7440. };
  7441. }
  7442. /**
  7443. * Zoom the range the given scale in or out. Start and end date will
  7444. * be adjusted, and the timeline will be redrawn. You can optionally give a
  7445. * date around which to zoom.
  7446. * For example, try scale = 0.9 or 1.1
  7447. * @param {Number} scale Scaling factor. Values above 1 will zoom out,
  7448. * values below 1 will zoom in.
  7449. * @param {Number} [center] Value representing a date around which will
  7450. * be zoomed.
  7451. */
  7452. Range.prototype.zoom = function(scale, center, delta) {
  7453. // if centerDate is not provided, take it half between start Date and end Date
  7454. if (center == null) {
  7455. center = (this.start + this.end) / 2;
  7456. }
  7457. var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  7458. var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this, center);
  7459. var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore;
  7460. // calculate new start and end
  7461. var newStart = (center-hiddenDurationBefore) + (this.start - (center-hiddenDurationBefore)) * scale;
  7462. var newEnd = (center+hiddenDurationAfter) + (this.end - (center+hiddenDurationAfter)) * scale;
  7463. // snapping times away from hidden zones
  7464. this.startToFront = delta > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  7465. this.endToFront = -delta > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  7466. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, delta, true);
  7467. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, -delta, true);
  7468. if (safeStart != newStart || safeEnd != newEnd) {
  7469. newStart = safeStart;
  7470. newEnd = safeEnd;
  7471. }
  7472. this.setRange(newStart, newEnd, false, true);
  7473. this.startToFront = false; // revert to default
  7474. this.endToFront = true; // revert to default
  7475. };
  7476. /**
  7477. * Move the range with a given delta to the left or right. Start and end
  7478. * value will be adjusted. For example, try delta = 0.1 or -0.1
  7479. * @param {Number} delta Moving amount. Positive value will move right,
  7480. * negative value will move left
  7481. */
  7482. Range.prototype.move = function(delta) {
  7483. // zoom start Date and end Date relative to the centerDate
  7484. var diff = (this.end - this.start);
  7485. // apply new values
  7486. var newStart = this.start + diff * delta;
  7487. var newEnd = this.end + diff * delta;
  7488. // TODO: reckon with min and max range
  7489. this.start = newStart;
  7490. this.end = newEnd;
  7491. };
  7492. /**
  7493. * Move the range to a new center point
  7494. * @param {Number} moveTo New center point of the range
  7495. */
  7496. Range.prototype.moveTo = function(moveTo) {
  7497. var center = (this.start + this.end) / 2;
  7498. var diff = center - moveTo;
  7499. // calculate new start and end
  7500. var newStart = this.start - diff;
  7501. var newEnd = this.end - diff;
  7502. this.setRange(newStart, newEnd);
  7503. };
  7504. module.exports = Range;
  7505. /***/ },
  7506. /* 18 */
  7507. /***/ function(module, exports, __webpack_require__) {
  7508. // Utility functions for ordering and stacking of items
  7509. var EPSILON = 0.001; // used when checking collisions, to prevent round-off errors
  7510. /**
  7511. * Order items by their start data
  7512. * @param {Item[]} items
  7513. */
  7514. exports.orderByStart = function(items) {
  7515. items.sort(function (a, b) {
  7516. return a.data.start - b.data.start;
  7517. });
  7518. };
  7519. /**
  7520. * Order items by their end date. If they have no end date, their start date
  7521. * is used.
  7522. * @param {Item[]} items
  7523. */
  7524. exports.orderByEnd = function(items) {
  7525. items.sort(function (a, b) {
  7526. var aTime = ('end' in a.data) ? a.data.end : a.data.start,
  7527. bTime = ('end' in b.data) ? b.data.end : b.data.start;
  7528. return aTime - bTime;
  7529. });
  7530. };
  7531. /**
  7532. * Adjust vertical positions of the items such that they don't overlap each
  7533. * other.
  7534. * @param {Item[]} items
  7535. * All visible items
  7536. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  7537. * Margins between items and between items and the axis.
  7538. * @param {boolean} [force=false]
  7539. * If true, all items will be repositioned. If false (default), only
  7540. * items having a top===null will be re-stacked
  7541. */
  7542. exports.stack = function(items, margin, force) {
  7543. var i, iMax;
  7544. if (force) {
  7545. // reset top position of all items
  7546. for (i = 0, iMax = items.length; i < iMax; i++) {
  7547. items[i].top = null;
  7548. }
  7549. }
  7550. // calculate new, non-overlapping positions
  7551. for (i = 0, iMax = items.length; i < iMax; i++) {
  7552. var item = items[i];
  7553. if (item.stack && item.top === null) {
  7554. // initialize top position
  7555. item.top = margin.axis;
  7556. do {
  7557. // TODO: optimize checking for overlap. when there is a gap without items,
  7558. // you only need to check for items from the next item on, not from zero
  7559. var collidingItem = null;
  7560. for (var j = 0, jj = items.length; j < jj; j++) {
  7561. var other = items[j];
  7562. if (other.top !== null && other !== item && other.stack && exports.collision(item, other, margin.item)) {
  7563. collidingItem = other;
  7564. break;
  7565. }
  7566. }
  7567. if (collidingItem != null) {
  7568. // There is a collision. Reposition the items above the colliding element
  7569. item.top = collidingItem.top + collidingItem.height + margin.item.vertical;
  7570. }
  7571. } while (collidingItem);
  7572. }
  7573. }
  7574. };
  7575. /**
  7576. * Adjust vertical positions of the items without stacking them
  7577. * @param {Item[]} items
  7578. * All visible items
  7579. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  7580. * Margins between items and between items and the axis.
  7581. */
  7582. exports.nostack = function(items, margin, subgroups) {
  7583. var i, iMax, newTop;
  7584. // reset top position of all items
  7585. for (i = 0, iMax = items.length; i < iMax; i++) {
  7586. if (items[i].data.subgroup !== undefined) {
  7587. newTop = margin.axis;
  7588. for (var subgroup in subgroups) {
  7589. if (subgroups.hasOwnProperty(subgroup)) {
  7590. if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroups[items[i].data.subgroup].index) {
  7591. newTop += subgroups[subgroup].height + margin.item.vertical;
  7592. }
  7593. }
  7594. }
  7595. items[i].top = newTop;
  7596. }
  7597. else {
  7598. items[i].top = margin.axis;
  7599. }
  7600. }
  7601. };
  7602. /**
  7603. * Test if the two provided items collide
  7604. * The items must have parameters left, width, top, and height.
  7605. * @param {Item} a The first item
  7606. * @param {Item} b The second item
  7607. * @param {{horizontal: number, vertical: number}} margin
  7608. * An object containing a horizontal and vertical
  7609. * minimum required margin.
  7610. * @return {boolean} true if a and b collide, else false
  7611. */
  7612. exports.collision = function(a, b, margin) {
  7613. return ((a.left - margin.horizontal + EPSILON) < (b.left + b.width) &&
  7614. (a.left + a.width + margin.horizontal - EPSILON) > b.left &&
  7615. (a.top - margin.vertical + EPSILON) < (b.top + b.height) &&
  7616. (a.top + a.height + margin.vertical - EPSILON) > b.top);
  7617. };
  7618. /***/ },
  7619. /* 19 */
  7620. /***/ function(module, exports, __webpack_require__) {
  7621. var moment = __webpack_require__(44);
  7622. var DateUtil = __webpack_require__(15);
  7623. var util = __webpack_require__(1);
  7624. /**
  7625. * @constructor TimeStep
  7626. * The class TimeStep is an iterator for dates. You provide a start date and an
  7627. * end date. The class itself determines the best scale (step size) based on the
  7628. * provided start Date, end Date, and minimumStep.
  7629. *
  7630. * If minimumStep is provided, the step size is chosen as close as possible
  7631. * to the minimumStep but larger than minimumStep. If minimumStep is not
  7632. * provided, the scale is set to 1 DAY.
  7633. * The minimumStep should correspond with the onscreen size of about 6 characters
  7634. *
  7635. * Alternatively, you can set a scale by hand.
  7636. * After creation, you can initialize the class by executing first(). Then you
  7637. * can iterate from the start date to the end date via next(). You can check if
  7638. * the end date is reached with the function hasNext(). After each step, you can
  7639. * retrieve the current date via getCurrent().
  7640. * The TimeStep has scales ranging from milliseconds, seconds, minutes, hours,
  7641. * days, to years.
  7642. *
  7643. * Version: 1.2
  7644. *
  7645. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  7646. * or new Date(2010, 9, 21, 23, 45, 00)
  7647. * @param {Date} [end] The end date
  7648. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  7649. */
  7650. function TimeStep(start, end, minimumStep, hiddenDates) {
  7651. // variables
  7652. this.current = new Date();
  7653. this._start = new Date();
  7654. this._end = new Date();
  7655. this.autoScale = true;
  7656. this.scale = 'day';
  7657. this.step = 1;
  7658. // initialize the range
  7659. this.setRange(start, end, minimumStep);
  7660. // hidden Dates options
  7661. this.switchedDay = false;
  7662. this.switchedMonth = false;
  7663. this.switchedYear = false;
  7664. this.hiddenDates = hiddenDates;
  7665. if (hiddenDates === undefined) {
  7666. this.hiddenDates = [];
  7667. }
  7668. this.format = TimeStep.FORMAT; // default formatting
  7669. }
  7670. // Time formatting
  7671. TimeStep.FORMAT = {
  7672. minorLabels: {
  7673. millisecond:'SSS',
  7674. second: 's',
  7675. minute: 'HH:mm',
  7676. hour: 'HH:mm',
  7677. weekday: 'ddd D',
  7678. day: 'D',
  7679. month: 'MMM',
  7680. year: 'YYYY'
  7681. },
  7682. majorLabels: {
  7683. millisecond:'HH:mm:ss',
  7684. second: 'D MMMM HH:mm',
  7685. minute: 'ddd D MMMM',
  7686. hour: 'ddd D MMMM',
  7687. weekday: 'MMMM YYYY',
  7688. day: 'MMMM YYYY',
  7689. month: 'YYYY',
  7690. year: ''
  7691. }
  7692. };
  7693. /**
  7694. * Set custom formatting for the minor an major labels of the TimeStep.
  7695. * Both `minorLabels` and `majorLabels` are an Object with properties:
  7696. * 'millisecond, 'second, 'minute', 'hour', 'weekday, 'day, 'month, 'year'.
  7697. * @param {{minorLabels: Object, majorLabels: Object}} format
  7698. */
  7699. TimeStep.prototype.setFormat = function (format) {
  7700. var defaultFormat = util.deepExtend({}, TimeStep.FORMAT);
  7701. this.format = util.deepExtend(defaultFormat, format);
  7702. };
  7703. /**
  7704. * Set a new range
  7705. * If minimumStep is provided, the step size is chosen as close as possible
  7706. * to the minimumStep but larger than minimumStep. If minimumStep is not
  7707. * provided, the scale is set to 1 DAY.
  7708. * The minimumStep should correspond with the onscreen size of about 6 characters
  7709. * @param {Date} [start] The start date and time.
  7710. * @param {Date} [end] The end date and time.
  7711. * @param {int} [minimumStep] Optional. Minimum step size in milliseconds
  7712. */
  7713. TimeStep.prototype.setRange = function(start, end, minimumStep) {
  7714. if (!(start instanceof Date) || !(end instanceof Date)) {
  7715. throw "No legal start or end date in method setRange";
  7716. }
  7717. this._start = (start != undefined) ? new Date(start.valueOf()) : new Date();
  7718. this._end = (end != undefined) ? new Date(end.valueOf()) : new Date();
  7719. if (this.autoScale) {
  7720. this.setMinimumStep(minimumStep);
  7721. }
  7722. };
  7723. /**
  7724. * Set the range iterator to the start date.
  7725. */
  7726. TimeStep.prototype.first = function() {
  7727. this.current = new Date(this._start.valueOf());
  7728. this.roundToMinor();
  7729. };
  7730. /**
  7731. * Round the current date to the first minor date value
  7732. * This must be executed once when the current date is set to start Date
  7733. */
  7734. TimeStep.prototype.roundToMinor = function() {
  7735. // round to floor
  7736. // IMPORTANT: we have no breaks in this switch! (this is no bug)
  7737. // noinspection FallThroughInSwitchStatementJS
  7738. switch (this.scale) {
  7739. case 'year':
  7740. this.current.setFullYear(this.step * Math.floor(this.current.getFullYear() / this.step));
  7741. this.current.setMonth(0);
  7742. case 'month': this.current.setDate(1);
  7743. case 'day': // intentional fall through
  7744. case 'weekday': this.current.setHours(0);
  7745. case 'hour': this.current.setMinutes(0);
  7746. case 'minute': this.current.setSeconds(0);
  7747. case 'second': this.current.setMilliseconds(0);
  7748. //case 'millisecond': // nothing to do for milliseconds
  7749. }
  7750. if (this.step != 1) {
  7751. // round down to the first minor value that is a multiple of the current step size
  7752. switch (this.scale) {
  7753. case 'millisecond': this.current.setMilliseconds(this.current.getMilliseconds() - this.current.getMilliseconds() % this.step); break;
  7754. case 'second': this.current.setSeconds(this.current.getSeconds() - this.current.getSeconds() % this.step); break;
  7755. case 'minute': this.current.setMinutes(this.current.getMinutes() - this.current.getMinutes() % this.step); break;
  7756. case 'hour': this.current.setHours(this.current.getHours() - this.current.getHours() % this.step); break;
  7757. case 'weekday': // intentional fall through
  7758. case 'day': this.current.setDate((this.current.getDate()-1) - (this.current.getDate()-1) % this.step + 1); break;
  7759. case 'month': this.current.setMonth(this.current.getMonth() - this.current.getMonth() % this.step); break;
  7760. case 'year': this.current.setFullYear(this.current.getFullYear() - this.current.getFullYear() % this.step); break;
  7761. default: break;
  7762. }
  7763. }
  7764. };
  7765. /**
  7766. * Check if the there is a next step
  7767. * @return {boolean} true if the current date has not passed the end date
  7768. */
  7769. TimeStep.prototype.hasNext = function () {
  7770. return (this.current.valueOf() <= this._end.valueOf());
  7771. };
  7772. /**
  7773. * Do the next step
  7774. */
  7775. TimeStep.prototype.next = function() {
  7776. var prev = this.current.valueOf();
  7777. // Two cases, needed to prevent issues with switching daylight savings
  7778. // (end of March and end of October)
  7779. if (this.current.getMonth() < 6) {
  7780. switch (this.scale) {
  7781. case 'millisecond':
  7782. this.current = new Date(this.current.valueOf() + this.step); break;
  7783. case 'second': this.current = new Date(this.current.valueOf() + this.step * 1000); break;
  7784. case 'minute': this.current = new Date(this.current.valueOf() + this.step * 1000 * 60); break;
  7785. case 'hour':
  7786. this.current = new Date(this.current.valueOf() + this.step * 1000 * 60 * 60);
  7787. // in case of skipping an hour for daylight savings, adjust the hour again (else you get: 0h 5h 9h ... instead of 0h 4h 8h ...)
  7788. var h = this.current.getHours();
  7789. this.current.setHours(h - (h % this.step));
  7790. break;
  7791. case 'weekday': // intentional fall through
  7792. case 'day': this.current.setDate(this.current.getDate() + this.step); break;
  7793. case 'month': this.current.setMonth(this.current.getMonth() + this.step); break;
  7794. case 'year': this.current.setFullYear(this.current.getFullYear() + this.step); break;
  7795. default: break;
  7796. }
  7797. }
  7798. else {
  7799. switch (this.scale) {
  7800. case 'millisecond': this.current = new Date(this.current.valueOf() + this.step); break;
  7801. case 'second': this.current.setSeconds(this.current.getSeconds() + this.step); break;
  7802. case 'minute': this.current.setMinutes(this.current.getMinutes() + this.step); break;
  7803. case 'hour': this.current.setHours(this.current.getHours() + this.step); break;
  7804. case 'weekday': // intentional fall through
  7805. case 'day': this.current.setDate(this.current.getDate() + this.step); break;
  7806. case 'month': this.current.setMonth(this.current.getMonth() + this.step); break;
  7807. case 'year': this.current.setFullYear(this.current.getFullYear() + this.step); break;
  7808. default: break;
  7809. }
  7810. }
  7811. if (this.step != 1) {
  7812. // round down to the correct major value
  7813. switch (this.scale) {
  7814. case 'millisecond': if(this.current.getMilliseconds() < this.step) this.current.setMilliseconds(0); break;
  7815. case 'second': if(this.current.getSeconds() < this.step) this.current.setSeconds(0); break;
  7816. case 'minute': if(this.current.getMinutes() < this.step) this.current.setMinutes(0); break;
  7817. case 'hour': if(this.current.getHours() < this.step) this.current.setHours(0); break;
  7818. case 'weekday': // intentional fall through
  7819. case 'day': if(this.current.getDate() < this.step+1) this.current.setDate(1); break;
  7820. case 'month': if(this.current.getMonth() < this.step) this.current.setMonth(0); break;
  7821. case 'year': break; // nothing to do for year
  7822. default: break;
  7823. }
  7824. }
  7825. // safety mechanism: if current time is still unchanged, move to the end
  7826. if (this.current.valueOf() == prev) {
  7827. this.current = new Date(this._end.valueOf());
  7828. }
  7829. DateUtil.stepOverHiddenDates(this, prev);
  7830. };
  7831. /**
  7832. * Get the current datetime
  7833. * @return {Date} current The current date
  7834. */
  7835. TimeStep.prototype.getCurrent = function() {
  7836. return this.current;
  7837. };
  7838. /**
  7839. * Set a custom scale. Autoscaling will be disabled.
  7840. * For example setScale('minute', 5) will result
  7841. * in minor steps of 5 minutes, and major steps of an hour.
  7842. *
  7843. * @param {{scale: string, step: number}} params
  7844. * An object containing two properties:
  7845. * - A string 'scale'. Choose from 'millisecond', 'second',
  7846. * 'minute', 'hour', 'weekday, 'day, 'month, 'year'.
  7847. * - A number 'step'. A step size, by default 1.
  7848. * Choose for example 1, 2, 5, or 10.
  7849. */
  7850. TimeStep.prototype.setScale = function(params) {
  7851. if (params && typeof params.scale == 'string') {
  7852. this.scale = params.scale;
  7853. this.step = params.step > 0 ? params.step : 1;
  7854. this.autoScale = false;
  7855. }
  7856. };
  7857. /**
  7858. * Enable or disable autoscaling
  7859. * @param {boolean} enable If true, autoascaling is set true
  7860. */
  7861. TimeStep.prototype.setAutoScale = function (enable) {
  7862. this.autoScale = enable;
  7863. };
  7864. /**
  7865. * Automatically determine the scale that bests fits the provided minimum step
  7866. * @param {Number} [minimumStep] The minimum step size in milliseconds
  7867. */
  7868. TimeStep.prototype.setMinimumStep = function(minimumStep) {
  7869. if (minimumStep == undefined) {
  7870. return;
  7871. }
  7872. //var b = asc + ds;
  7873. var stepYear = (1000 * 60 * 60 * 24 * 30 * 12);
  7874. var stepMonth = (1000 * 60 * 60 * 24 * 30);
  7875. var stepDay = (1000 * 60 * 60 * 24);
  7876. var stepHour = (1000 * 60 * 60);
  7877. var stepMinute = (1000 * 60);
  7878. var stepSecond = (1000);
  7879. var stepMillisecond= (1);
  7880. // find the smallest step that is larger than the provided minimumStep
  7881. if (stepYear*1000 > minimumStep) {this.scale = 'year'; this.step = 1000;}
  7882. if (stepYear*500 > minimumStep) {this.scale = 'year'; this.step = 500;}
  7883. if (stepYear*100 > minimumStep) {this.scale = 'year'; this.step = 100;}
  7884. if (stepYear*50 > minimumStep) {this.scale = 'year'; this.step = 50;}
  7885. if (stepYear*10 > minimumStep) {this.scale = 'year'; this.step = 10;}
  7886. if (stepYear*5 > minimumStep) {this.scale = 'year'; this.step = 5;}
  7887. if (stepYear > minimumStep) {this.scale = 'year'; this.step = 1;}
  7888. if (stepMonth*3 > minimumStep) {this.scale = 'month'; this.step = 3;}
  7889. if (stepMonth > minimumStep) {this.scale = 'month'; this.step = 1;}
  7890. if (stepDay*5 > minimumStep) {this.scale = 'day'; this.step = 5;}
  7891. if (stepDay*2 > minimumStep) {this.scale = 'day'; this.step = 2;}
  7892. if (stepDay > minimumStep) {this.scale = 'day'; this.step = 1;}
  7893. if (stepDay/2 > minimumStep) {this.scale = 'weekday'; this.step = 1;}
  7894. if (stepHour*4 > minimumStep) {this.scale = 'hour'; this.step = 4;}
  7895. if (stepHour > minimumStep) {this.scale = 'hour'; this.step = 1;}
  7896. if (stepMinute*15 > minimumStep) {this.scale = 'minute'; this.step = 15;}
  7897. if (stepMinute*10 > minimumStep) {this.scale = 'minute'; this.step = 10;}
  7898. if (stepMinute*5 > minimumStep) {this.scale = 'minute'; this.step = 5;}
  7899. if (stepMinute > minimumStep) {this.scale = 'minute'; this.step = 1;}
  7900. if (stepSecond*15 > minimumStep) {this.scale = 'second'; this.step = 15;}
  7901. if (stepSecond*10 > minimumStep) {this.scale = 'second'; this.step = 10;}
  7902. if (stepSecond*5 > minimumStep) {this.scale = 'second'; this.step = 5;}
  7903. if (stepSecond > minimumStep) {this.scale = 'second'; this.step = 1;}
  7904. if (stepMillisecond*200 > minimumStep) {this.scale = 'millisecond'; this.step = 200;}
  7905. if (stepMillisecond*100 > minimumStep) {this.scale = 'millisecond'; this.step = 100;}
  7906. if (stepMillisecond*50 > minimumStep) {this.scale = 'millisecond'; this.step = 50;}
  7907. if (stepMillisecond*10 > minimumStep) {this.scale = 'millisecond'; this.step = 10;}
  7908. if (stepMillisecond*5 > minimumStep) {this.scale = 'millisecond'; this.step = 5;}
  7909. if (stepMillisecond > minimumStep) {this.scale = 'millisecond'; this.step = 1;}
  7910. };
  7911. /**
  7912. * Snap a date to a rounded value.
  7913. * The snap intervals are dependent on the current scale and step.
  7914. * Static function
  7915. * @param {Date} date the date to be snapped.
  7916. * @param {string} scale Current scale, can be 'millisecond', 'second',
  7917. * 'minute', 'hour', 'weekday, 'day, 'month, 'year'.
  7918. * @param {number} step Current step (1, 2, 4, 5, ...
  7919. * @return {Date} snappedDate
  7920. */
  7921. TimeStep.snap = function(date, scale, step) {
  7922. var clone = new Date(date.valueOf());
  7923. if (scale == 'year') {
  7924. var year = clone.getFullYear() + Math.round(clone.getMonth() / 12);
  7925. clone.setFullYear(Math.round(year / step) * step);
  7926. clone.setMonth(0);
  7927. clone.setDate(0);
  7928. clone.setHours(0);
  7929. clone.setMinutes(0);
  7930. clone.setSeconds(0);
  7931. clone.setMilliseconds(0);
  7932. }
  7933. else if (scale == 'month') {
  7934. if (clone.getDate() > 15) {
  7935. clone.setDate(1);
  7936. clone.setMonth(clone.getMonth() + 1);
  7937. // important: first set Date to 1, after that change the month.
  7938. }
  7939. else {
  7940. clone.setDate(1);
  7941. }
  7942. clone.setHours(0);
  7943. clone.setMinutes(0);
  7944. clone.setSeconds(0);
  7945. clone.setMilliseconds(0);
  7946. }
  7947. else if (scale == 'day') {
  7948. //noinspection FallthroughInSwitchStatementJS
  7949. switch (step) {
  7950. case 5:
  7951. case 2:
  7952. clone.setHours(Math.round(clone.getHours() / 24) * 24); break;
  7953. default:
  7954. clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
  7955. }
  7956. clone.setMinutes(0);
  7957. clone.setSeconds(0);
  7958. clone.setMilliseconds(0);
  7959. }
  7960. else if (scale == 'weekday') {
  7961. //noinspection FallthroughInSwitchStatementJS
  7962. switch (step) {
  7963. case 5:
  7964. case 2:
  7965. clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
  7966. default:
  7967. clone.setHours(Math.round(clone.getHours() / 6) * 6); break;
  7968. }
  7969. clone.setMinutes(0);
  7970. clone.setSeconds(0);
  7971. clone.setMilliseconds(0);
  7972. }
  7973. else if (scale == 'hour') {
  7974. switch (step) {
  7975. case 4:
  7976. clone.setMinutes(Math.round(clone.getMinutes() / 60) * 60); break;
  7977. default:
  7978. clone.setMinutes(Math.round(clone.getMinutes() / 30) * 30); break;
  7979. }
  7980. clone.setSeconds(0);
  7981. clone.setMilliseconds(0);
  7982. } else if (scale == 'minute') {
  7983. //noinspection FallthroughInSwitchStatementJS
  7984. switch (step) {
  7985. case 15:
  7986. case 10:
  7987. clone.setMinutes(Math.round(clone.getMinutes() / 5) * 5);
  7988. clone.setSeconds(0);
  7989. break;
  7990. case 5:
  7991. clone.setSeconds(Math.round(clone.getSeconds() / 60) * 60); break;
  7992. default:
  7993. clone.setSeconds(Math.round(clone.getSeconds() / 30) * 30); break;
  7994. }
  7995. clone.setMilliseconds(0);
  7996. }
  7997. else if (scale == 'second') {
  7998. //noinspection FallthroughInSwitchStatementJS
  7999. switch (step) {
  8000. case 15:
  8001. case 10:
  8002. clone.setSeconds(Math.round(clone.getSeconds() / 5) * 5);
  8003. clone.setMilliseconds(0);
  8004. break;
  8005. case 5:
  8006. clone.setMilliseconds(Math.round(clone.getMilliseconds() / 1000) * 1000); break;
  8007. default:
  8008. clone.setMilliseconds(Math.round(clone.getMilliseconds() / 500) * 500); break;
  8009. }
  8010. }
  8011. else if (scale == 'millisecond') {
  8012. var _step = step > 5 ? step / 2 : 1;
  8013. clone.setMilliseconds(Math.round(clone.getMilliseconds() / _step) * _step);
  8014. }
  8015. return clone;
  8016. };
  8017. /**
  8018. * Check if the current value is a major value (for example when the step
  8019. * is DAY, a major value is each first day of the MONTH)
  8020. * @return {boolean} true if current date is major, else false.
  8021. */
  8022. TimeStep.prototype.isMajor = function() {
  8023. if (this.switchedYear == true) {
  8024. this.switchedYear = false;
  8025. switch (this.scale) {
  8026. case 'year':
  8027. case 'month':
  8028. case 'weekday':
  8029. case 'day':
  8030. case 'hour':
  8031. case 'minute':
  8032. case 'second':
  8033. case 'millisecond':
  8034. return true;
  8035. default:
  8036. return false;
  8037. }
  8038. }
  8039. else if (this.switchedMonth == true) {
  8040. this.switchedMonth = false;
  8041. switch (this.scale) {
  8042. case 'weekday':
  8043. case 'day':
  8044. case 'hour':
  8045. case 'minute':
  8046. case 'second':
  8047. case 'millisecond':
  8048. return true;
  8049. default:
  8050. return false;
  8051. }
  8052. }
  8053. else if (this.switchedDay == true) {
  8054. this.switchedDay = false;
  8055. switch (this.scale) {
  8056. case 'millisecond':
  8057. case 'second':
  8058. case 'minute':
  8059. case 'hour':
  8060. return true;
  8061. default:
  8062. return false;
  8063. }
  8064. }
  8065. switch (this.scale) {
  8066. case 'millisecond':
  8067. return (this.current.getMilliseconds() == 0);
  8068. case 'second':
  8069. return (this.current.getSeconds() == 0);
  8070. case 'minute':
  8071. return (this.current.getHours() == 0) && (this.current.getMinutes() == 0);
  8072. case 'hour':
  8073. return (this.current.getHours() == 0);
  8074. case 'weekday': // intentional fall through
  8075. case 'day':
  8076. return (this.current.getDate() == 1);
  8077. case 'month':
  8078. return (this.current.getMonth() == 0);
  8079. case 'year':
  8080. return false;
  8081. default:
  8082. return false;
  8083. }
  8084. };
  8085. /**
  8086. * Returns formatted text for the minor axislabel, depending on the current
  8087. * date and the scale. For example when scale is MINUTE, the current time is
  8088. * formatted as "hh:mm".
  8089. * @param {Date} [date] custom date. if not provided, current date is taken
  8090. */
  8091. TimeStep.prototype.getLabelMinor = function(date) {
  8092. if (date == undefined) {
  8093. date = this.current;
  8094. }
  8095. var format = this.format.minorLabels[this.scale];
  8096. return (format && format.length > 0) ? moment(date).format(format) : '';
  8097. };
  8098. /**
  8099. * Returns formatted text for the major axis label, depending on the current
  8100. * date and the scale. For example when scale is MINUTE, the major scale is
  8101. * hours, and the hour will be formatted as "hh".
  8102. * @param {Date} [date] custom date. if not provided, current date is taken
  8103. */
  8104. TimeStep.prototype.getLabelMajor = function(date) {
  8105. if (date == undefined) {
  8106. date = this.current;
  8107. }
  8108. var format = this.format.majorLabels[this.scale];
  8109. return (format && format.length > 0) ? moment(date).format(format) : '';
  8110. };
  8111. TimeStep.prototype.getClassName = function() {
  8112. var m = moment(this.current);
  8113. var date = m.locale ? m.locale('en') : m.lang('en'); // old versions of moment have .lang() function
  8114. var step = this.step;
  8115. function even(value) {
  8116. return (value / step % 2 == 0) ? ' even' : ' odd';
  8117. }
  8118. function today(date) {
  8119. if (date.isSame(new Date(), 'day')) {
  8120. return ' today';
  8121. }
  8122. if (date.isSame(moment().add(1, 'day'), 'day')) {
  8123. return ' tomorrow';
  8124. }
  8125. if (date.isSame(moment().add(-1, 'day'), 'day')) {
  8126. return ' yesterday';
  8127. }
  8128. return '';
  8129. }
  8130. function currentWeek(date) {
  8131. return date.isSame(new Date(), 'week') ? ' current-week' : '';
  8132. }
  8133. function currentMonth(date) {
  8134. return date.isSame(new Date(), 'month') ? ' current-month' : '';
  8135. }
  8136. function currentYear(date) {
  8137. return date.isSame(new Date(), 'year') ? ' current-year' : '';
  8138. }
  8139. switch (this.scale) {
  8140. case 'millisecond':
  8141. return even(date.milliseconds()).trim();
  8142. case 'second':
  8143. return even(date.seconds()).trim();
  8144. case 'minute':
  8145. return even(date.minutes()).trim();
  8146. case 'hour':
  8147. var hours = date.hours();
  8148. if (this.step == 4) {
  8149. hours = hours + '-' + (hours + 4);
  8150. }
  8151. return hours + 'h' + today(date) + even(date.hours());
  8152. case 'weekday':
  8153. return date.format('dddd').toLowerCase() +
  8154. today(date) + currentWeek(date) + even(date.date());
  8155. case 'day':
  8156. var day = date.date();
  8157. var month = date.format('MMMM').toLowerCase();
  8158. return 'day' + day + ' ' + month + currentMonth(date) + even(day - 1);
  8159. case 'month':
  8160. return date.format('MMMM').toLowerCase() +
  8161. currentMonth(date) + even(date.month());
  8162. case 'year':
  8163. var year = date.year();
  8164. return 'year' + year + currentYear(date)+ even(year);
  8165. default:
  8166. return '';
  8167. }
  8168. };
  8169. module.exports = TimeStep;
  8170. /***/ },
  8171. /* 20 */
  8172. /***/ function(module, exports, __webpack_require__) {
  8173. var Hammer = __webpack_require__(45);
  8174. var util = __webpack_require__(1);
  8175. /**
  8176. * @constructor Item
  8177. * @param {Object} data Object containing (optional) parameters type,
  8178. * start, end, content, group, className.
  8179. * @param {{toScreen: function, toTime: function}} conversion
  8180. * Conversion functions from time to screen and vice versa
  8181. * @param {Object} options Configuration options
  8182. * // TODO: describe available options
  8183. */
  8184. function Item (data, conversion, options) {
  8185. this.id = null;
  8186. this.parent = null;
  8187. this.data = data;
  8188. this.dom = null;
  8189. this.conversion = conversion || {};
  8190. this.options = options || {};
  8191. this.selected = false;
  8192. this.displayed = false;
  8193. this.dirty = true;
  8194. this.top = null;
  8195. this.left = null;
  8196. this.width = null;
  8197. this.height = null;
  8198. }
  8199. Item.prototype.stack = true;
  8200. /**
  8201. * Select current item
  8202. */
  8203. Item.prototype.select = function() {
  8204. this.selected = true;
  8205. this.dirty = true;
  8206. if (this.displayed) this.redraw();
  8207. };
  8208. /**
  8209. * Unselect current item
  8210. */
  8211. Item.prototype.unselect = function() {
  8212. this.selected = false;
  8213. this.dirty = true;
  8214. if (this.displayed) this.redraw();
  8215. };
  8216. /**
  8217. * Set data for the item. Existing data will be updated. The id should not
  8218. * be changed. When the item is displayed, it will be redrawn immediately.
  8219. * @param {Object} data
  8220. */
  8221. Item.prototype.setData = function(data) {
  8222. this.data = data;
  8223. this.dirty = true;
  8224. if (this.displayed) this.redraw();
  8225. };
  8226. /**
  8227. * Set a parent for the item
  8228. * @param {ItemSet | Group} parent
  8229. */
  8230. Item.prototype.setParent = function(parent) {
  8231. if (this.displayed) {
  8232. this.hide();
  8233. this.parent = parent;
  8234. if (this.parent) {
  8235. this.show();
  8236. }
  8237. }
  8238. else {
  8239. this.parent = parent;
  8240. }
  8241. };
  8242. /**
  8243. * Check whether this item is visible inside given range
  8244. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  8245. * @returns {boolean} True if visible
  8246. */
  8247. Item.prototype.isVisible = function(range) {
  8248. // Should be implemented by Item implementations
  8249. return false;
  8250. };
  8251. /**
  8252. * Show the Item in the DOM (when not already visible)
  8253. * @return {Boolean} changed
  8254. */
  8255. Item.prototype.show = function() {
  8256. return false;
  8257. };
  8258. /**
  8259. * Hide the Item from the DOM (when visible)
  8260. * @return {Boolean} changed
  8261. */
  8262. Item.prototype.hide = function() {
  8263. return false;
  8264. };
  8265. /**
  8266. * Repaint the item
  8267. */
  8268. Item.prototype.redraw = function() {
  8269. // should be implemented by the item
  8270. };
  8271. /**
  8272. * Reposition the Item horizontally
  8273. */
  8274. Item.prototype.repositionX = function() {
  8275. // should be implemented by the item
  8276. };
  8277. /**
  8278. * Reposition the Item vertically
  8279. */
  8280. Item.prototype.repositionY = function() {
  8281. // should be implemented by the item
  8282. };
  8283. /**
  8284. * Repaint a delete button on the top right of the item when the item is selected
  8285. * @param {HTMLElement} anchor
  8286. * @protected
  8287. */
  8288. Item.prototype._repaintDeleteButton = function (anchor) {
  8289. if (this.selected && this.options.editable.remove && !this.dom.deleteButton) {
  8290. // create and show button
  8291. var me = this;
  8292. var deleteButton = document.createElement('div');
  8293. deleteButton.className = 'delete';
  8294. deleteButton.title = 'Delete this item';
  8295. Hammer(deleteButton, {
  8296. preventDefault: true
  8297. }).on('tap', function (event) {
  8298. me.parent.removeFromDataSet(me);
  8299. event.stopPropagation();
  8300. });
  8301. anchor.appendChild(deleteButton);
  8302. this.dom.deleteButton = deleteButton;
  8303. }
  8304. else if (!this.selected && this.dom.deleteButton) {
  8305. // remove button
  8306. if (this.dom.deleteButton.parentNode) {
  8307. this.dom.deleteButton.parentNode.removeChild(this.dom.deleteButton);
  8308. }
  8309. this.dom.deleteButton = null;
  8310. }
  8311. };
  8312. /**
  8313. * Set HTML contents for the item
  8314. * @param {Element} element HTML element to fill with the contents
  8315. * @private
  8316. */
  8317. Item.prototype._updateContents = function (element) {
  8318. var content;
  8319. if (this.options.template) {
  8320. var itemData = this.parent.itemSet.itemsData.get(this.id); // get a clone of the data from the dataset
  8321. content = this.options.template(itemData);
  8322. }
  8323. else {
  8324. content = this.data.content;
  8325. }
  8326. if(content !== this.content) {
  8327. // only replace the content when changed
  8328. if (content instanceof Element) {
  8329. element.innerHTML = '';
  8330. element.appendChild(content);
  8331. }
  8332. else if (content != undefined) {
  8333. element.innerHTML = content;
  8334. }
  8335. else {
  8336. if (!(this.data.type == 'background' && this.data.content === undefined)) {
  8337. throw new Error('Property "content" missing in item ' + this.id);
  8338. }
  8339. }
  8340. this.content = content;
  8341. }
  8342. };
  8343. /**
  8344. * Set HTML contents for the item
  8345. * @param {Element} element HTML element to fill with the contents
  8346. * @private
  8347. */
  8348. Item.prototype._updateTitle = function (element) {
  8349. if (this.data.title != null) {
  8350. element.title = this.data.title || '';
  8351. }
  8352. else {
  8353. element.removeAttribute('title');
  8354. }
  8355. };
  8356. /**
  8357. * Process dataAttributes timeline option and set as data- attributes on dom.content
  8358. * @param {Element} element HTML element to which the attributes will be attached
  8359. * @private
  8360. */
  8361. Item.prototype._updateDataAttributes = function(element) {
  8362. if (this.options.dataAttributes && this.options.dataAttributes.length > 0) {
  8363. var attributes = [];
  8364. if (Array.isArray(this.options.dataAttributes)) {
  8365. attributes = this.options.dataAttributes;
  8366. }
  8367. else if (this.options.dataAttributes == 'all') {
  8368. attributes = Object.keys(this.data);
  8369. }
  8370. else {
  8371. return;
  8372. }
  8373. for (var i = 0; i < attributes.length; i++) {
  8374. var name = attributes[i];
  8375. var value = this.data[name];
  8376. if (value != null) {
  8377. element.setAttribute('data-' + name, value);
  8378. }
  8379. else {
  8380. element.removeAttribute('data-' + name);
  8381. }
  8382. }
  8383. }
  8384. };
  8385. /**
  8386. * Update custom styles of the element
  8387. * @param element
  8388. * @private
  8389. */
  8390. Item.prototype._updateStyle = function(element) {
  8391. // remove old styles
  8392. if (this.style) {
  8393. util.removeCssText(element, this.style);
  8394. this.style = null;
  8395. }
  8396. // append new styles
  8397. if (this.data.style) {
  8398. util.addCssText(element, this.data.style);
  8399. this.style = this.data.style;
  8400. }
  8401. };
  8402. module.exports = Item;
  8403. /***/ },
  8404. /* 21 */
  8405. /***/ function(module, exports, __webpack_require__) {
  8406. var Hammer = __webpack_require__(45);
  8407. var Item = __webpack_require__(20);
  8408. var BackgroundGroup = __webpack_require__(31);
  8409. var RangeItem = __webpack_require__(24);
  8410. /**
  8411. * @constructor BackgroundItem
  8412. * @extends Item
  8413. * @param {Object} data Object containing parameters start, end
  8414. * content, className.
  8415. * @param {{toScreen: function, toTime: function}} conversion
  8416. * Conversion functions from time to screen and vice versa
  8417. * @param {Object} [options] Configuration options
  8418. * // TODO: describe options
  8419. */
  8420. // TODO: implement support for the BackgroundItem just having a start, then being displayed as a sort of an annotation
  8421. function BackgroundItem (data, conversion, options) {
  8422. this.props = {
  8423. content: {
  8424. width: 0
  8425. }
  8426. };
  8427. this.overflow = false; // if contents can overflow (css styling), this flag is set to true
  8428. // validate data
  8429. if (data) {
  8430. if (data.start == undefined) {
  8431. throw new Error('Property "start" missing in item ' + data.id);
  8432. }
  8433. if (data.end == undefined) {
  8434. throw new Error('Property "end" missing in item ' + data.id);
  8435. }
  8436. }
  8437. Item.call(this, data, conversion, options);
  8438. this.emptyContent = false;
  8439. }
  8440. BackgroundItem.prototype = new Item (null, null, null);
  8441. BackgroundItem.prototype.baseClassName = 'item background';
  8442. BackgroundItem.prototype.stack = false;
  8443. /**
  8444. * Check whether this item is visible inside given range
  8445. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  8446. * @returns {boolean} True if visible
  8447. */
  8448. BackgroundItem.prototype.isVisible = function(range) {
  8449. // determine visibility
  8450. return (this.data.start < range.end) && (this.data.end > range.start);
  8451. };
  8452. /**
  8453. * Repaint the item
  8454. */
  8455. BackgroundItem.prototype.redraw = function() {
  8456. var dom = this.dom;
  8457. if (!dom) {
  8458. // create DOM
  8459. this.dom = {};
  8460. dom = this.dom;
  8461. // background box
  8462. dom.box = document.createElement('div');
  8463. // className is updated in redraw()
  8464. // contents box
  8465. dom.content = document.createElement('div');
  8466. dom.content.className = 'content';
  8467. dom.box.appendChild(dom.content);
  8468. // Note: we do NOT attach this item as attribute to the DOM,
  8469. // such that background items cannot be selected
  8470. //dom.box['timeline-item'] = this;
  8471. this.dirty = true;
  8472. }
  8473. // append DOM to parent DOM
  8474. if (!this.parent) {
  8475. throw new Error('Cannot redraw item: no parent attached');
  8476. }
  8477. if (!dom.box.parentNode) {
  8478. var background = this.parent.dom.background;
  8479. if (!background) {
  8480. throw new Error('Cannot redraw item: parent has no background container element');
  8481. }
  8482. background.appendChild(dom.box);
  8483. }
  8484. this.displayed = true;
  8485. // Update DOM when item is marked dirty. An item is marked dirty when:
  8486. // - the item is not yet rendered
  8487. // - the item's data is changed
  8488. // - the item is selected/deselected
  8489. if (this.dirty) {
  8490. this._updateContents(this.dom.content);
  8491. this._updateTitle(this.dom.content);
  8492. this._updateDataAttributes(this.dom.content);
  8493. this._updateStyle(this.dom.box);
  8494. // update class
  8495. var className = (this.data.className ? (' ' + this.data.className) : '') +
  8496. (this.selected ? ' selected' : '');
  8497. dom.box.className = this.baseClassName + className;
  8498. // determine from css whether this box has overflow
  8499. this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden';
  8500. // recalculate size
  8501. this.props.content.width = this.dom.content.offsetWidth;
  8502. this.height = 0; // set height zero, so this item will be ignored when stacking items
  8503. this.dirty = false;
  8504. }
  8505. };
  8506. /**
  8507. * Show the item in the DOM (when not already visible). The items DOM will
  8508. * be created when needed.
  8509. */
  8510. BackgroundItem.prototype.show = RangeItem.prototype.show;
  8511. /**
  8512. * Hide the item from the DOM (when visible)
  8513. * @return {Boolean} changed
  8514. */
  8515. BackgroundItem.prototype.hide = RangeItem.prototype.hide;
  8516. /**
  8517. * Reposition the item horizontally
  8518. * @Override
  8519. */
  8520. BackgroundItem.prototype.repositionX = RangeItem.prototype.repositionX;
  8521. /**
  8522. * Reposition the item vertically
  8523. * @Override
  8524. */
  8525. BackgroundItem.prototype.repositionY = function(margin) {
  8526. var onTop = this.options.orientation === 'top';
  8527. this.dom.content.style.top = onTop ? '' : '0';
  8528. this.dom.content.style.bottom = onTop ? '0' : '';
  8529. var height;
  8530. // special positioning for subgroups
  8531. if (this.data.subgroup !== undefined) {
  8532. // TODO: instead of calculating the top position of the subgroups here for every BackgroundItem, calculate the top of the subgroup once in Itemset
  8533. var itemSubgroup = this.data.subgroup;
  8534. var subgroups = this.parent.subgroups;
  8535. var subgroupIndex = subgroups[itemSubgroup].index;
  8536. // if the orientation is top, we need to take the difference in height into account.
  8537. if (onTop == true) {
  8538. // the first subgroup will have to account for the distance from the top to the first item.
  8539. height = this.parent.subgroups[itemSubgroup].height + margin.item.vertical;
  8540. height += subgroupIndex == 0 ? margin.axis - 0.5*margin.item.vertical : 0;
  8541. var newTop = this.parent.top;
  8542. for (var subgroup in subgroups) {
  8543. if (subgroups.hasOwnProperty(subgroup)) {
  8544. if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroupIndex) {
  8545. newTop += subgroups[subgroup].height + margin.item.vertical;
  8546. }
  8547. }
  8548. }
  8549. // the others will have to be offset downwards with this same distance.
  8550. newTop += subgroupIndex != 0 ? margin.axis - 0.5 * margin.item.vertical : 0;
  8551. this.dom.box.style.top = newTop + 'px';
  8552. this.dom.box.style.bottom = '';
  8553. }
  8554. // and when the orientation is bottom:
  8555. else {
  8556. var newTop = this.parent.top;
  8557. var totalHeight = 0;
  8558. for (var subgroup in subgroups) {
  8559. if (subgroups.hasOwnProperty(subgroup)) {
  8560. if (subgroups[subgroup].visible == true) {
  8561. var newHeight = subgroups[subgroup].height + margin.item.vertical;
  8562. totalHeight += newHeight;
  8563. if (subgroups[subgroup].index > subgroupIndex) {
  8564. newTop += newHeight;
  8565. }
  8566. }
  8567. }
  8568. }
  8569. height = this.parent.subgroups[itemSubgroup].height + margin.item.vertical;
  8570. this.dom.box.style.top = (this.parent.height - totalHeight + newTop) + 'px';
  8571. this.dom.box.style.bottom = '';
  8572. }
  8573. }
  8574. // and in the case of no subgroups:
  8575. else {
  8576. // we want backgrounds with groups to only show in groups.
  8577. if (this.parent instanceof BackgroundGroup) {
  8578. // if the item is not in a group:
  8579. height = Math.max(this.parent.height,
  8580. this.parent.itemSet.body.domProps.center.height,
  8581. this.parent.itemSet.body.domProps.centerContainer.height);
  8582. this.dom.box.style.top = onTop ? '0' : '';
  8583. this.dom.box.style.bottom = onTop ? '' : '0';
  8584. }
  8585. else {
  8586. height = this.parent.height;
  8587. // same alignment for items when orientation is top or bottom
  8588. this.dom.box.style.top = this.parent.top + 'px';
  8589. this.dom.box.style.bottom = '';
  8590. }
  8591. }
  8592. this.dom.box.style.height = height + 'px';
  8593. };
  8594. module.exports = BackgroundItem;
  8595. /***/ },
  8596. /* 22 */
  8597. /***/ function(module, exports, __webpack_require__) {
  8598. var Item = __webpack_require__(20);
  8599. var util = __webpack_require__(1);
  8600. /**
  8601. * @constructor BoxItem
  8602. * @extends Item
  8603. * @param {Object} data Object containing parameters start
  8604. * content, className.
  8605. * @param {{toScreen: function, toTime: function}} conversion
  8606. * Conversion functions from time to screen and vice versa
  8607. * @param {Object} [options] Configuration options
  8608. * // TODO: describe available options
  8609. */
  8610. function BoxItem (data, conversion, options) {
  8611. this.props = {
  8612. dot: {
  8613. width: 0,
  8614. height: 0
  8615. },
  8616. line: {
  8617. width: 0,
  8618. height: 0
  8619. }
  8620. };
  8621. // validate data
  8622. if (data) {
  8623. if (data.start == undefined) {
  8624. throw new Error('Property "start" missing in item ' + data);
  8625. }
  8626. }
  8627. Item.call(this, data, conversion, options);
  8628. }
  8629. BoxItem.prototype = new Item (null, null, null);
  8630. /**
  8631. * Check whether this item is visible inside given range
  8632. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  8633. * @returns {boolean} True if visible
  8634. */
  8635. BoxItem.prototype.isVisible = function(range) {
  8636. // determine visibility
  8637. // TODO: account for the real width of the item. Right now we just add 1/4 to the window
  8638. var interval = (range.end - range.start) / 4;
  8639. return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
  8640. };
  8641. /**
  8642. * Repaint the item
  8643. */
  8644. BoxItem.prototype.redraw = function() {
  8645. var dom = this.dom;
  8646. if (!dom) {
  8647. // create DOM
  8648. this.dom = {};
  8649. dom = this.dom;
  8650. // create main box
  8651. dom.box = document.createElement('DIV');
  8652. // contents box (inside the background box). used for making margins
  8653. dom.content = document.createElement('DIV');
  8654. dom.content.className = 'content';
  8655. dom.box.appendChild(dom.content);
  8656. // line to axis
  8657. dom.line = document.createElement('DIV');
  8658. dom.line.className = 'line';
  8659. // dot on axis
  8660. dom.dot = document.createElement('DIV');
  8661. dom.dot.className = 'dot';
  8662. // attach this item as attribute
  8663. dom.box['timeline-item'] = this;
  8664. this.dirty = true;
  8665. }
  8666. // append DOM to parent DOM
  8667. if (!this.parent) {
  8668. throw new Error('Cannot redraw item: no parent attached');
  8669. }
  8670. if (!dom.box.parentNode) {
  8671. var foreground = this.parent.dom.foreground;
  8672. if (!foreground) throw new Error('Cannot redraw item: parent has no foreground container element');
  8673. foreground.appendChild(dom.box);
  8674. }
  8675. if (!dom.line.parentNode) {
  8676. var background = this.parent.dom.background;
  8677. if (!background) throw new Error('Cannot redraw item: parent has no background container element');
  8678. background.appendChild(dom.line);
  8679. }
  8680. if (!dom.dot.parentNode) {
  8681. var axis = this.parent.dom.axis;
  8682. if (!background) throw new Error('Cannot redraw item: parent has no axis container element');
  8683. axis.appendChild(dom.dot);
  8684. }
  8685. this.displayed = true;
  8686. // Update DOM when item is marked dirty. An item is marked dirty when:
  8687. // - the item is not yet rendered
  8688. // - the item's data is changed
  8689. // - the item is selected/deselected
  8690. if (this.dirty) {
  8691. this._updateContents(this.dom.content);
  8692. this._updateTitle(this.dom.box);
  8693. this._updateDataAttributes(this.dom.box);
  8694. this._updateStyle(this.dom.box);
  8695. // update class
  8696. var className = (this.data.className? ' ' + this.data.className : '') +
  8697. (this.selected ? ' selected' : '');
  8698. dom.box.className = 'item box' + className;
  8699. dom.line.className = 'item line' + className;
  8700. dom.dot.className = 'item dot' + className;
  8701. // recalculate size
  8702. this.props.dot.height = dom.dot.offsetHeight;
  8703. this.props.dot.width = dom.dot.offsetWidth;
  8704. this.props.line.width = dom.line.offsetWidth;
  8705. this.width = dom.box.offsetWidth;
  8706. this.height = dom.box.offsetHeight;
  8707. this.dirty = false;
  8708. }
  8709. this._repaintDeleteButton(dom.box);
  8710. };
  8711. /**
  8712. * Show the item in the DOM (when not already displayed). The items DOM will
  8713. * be created when needed.
  8714. */
  8715. BoxItem.prototype.show = function() {
  8716. if (!this.displayed) {
  8717. this.redraw();
  8718. }
  8719. };
  8720. /**
  8721. * Hide the item from the DOM (when visible)
  8722. */
  8723. BoxItem.prototype.hide = function() {
  8724. if (this.displayed) {
  8725. var dom = this.dom;
  8726. if (dom.box.parentNode) dom.box.parentNode.removeChild(dom.box);
  8727. if (dom.line.parentNode) dom.line.parentNode.removeChild(dom.line);
  8728. if (dom.dot.parentNode) dom.dot.parentNode.removeChild(dom.dot);
  8729. this.displayed = false;
  8730. }
  8731. };
  8732. /**
  8733. * Reposition the item horizontally
  8734. * @Override
  8735. */
  8736. BoxItem.prototype.repositionX = function() {
  8737. var start = this.conversion.toScreen(this.data.start);
  8738. var align = this.options.align;
  8739. var left;
  8740. // calculate left position of the box
  8741. if (align == 'right') {
  8742. this.left = start - this.width;
  8743. }
  8744. else if (align == 'left') {
  8745. this.left = start;
  8746. }
  8747. else {
  8748. // default or 'center'
  8749. this.left = start - this.width / 2;
  8750. }
  8751. // reposition box
  8752. this.dom.box.style.left = this.left + 'px';
  8753. // reposition line
  8754. this.dom.line.style.left = (start - this.props.line.width / 2) + 'px';
  8755. // reposition dot
  8756. this.dom.dot.style.left = (start - this.props.dot.width / 2) + 'px';
  8757. };
  8758. /**
  8759. * Reposition the item vertically
  8760. * @Override
  8761. */
  8762. BoxItem.prototype.repositionY = function() {
  8763. var orientation = this.options.orientation;
  8764. var box = this.dom.box;
  8765. var line = this.dom.line;
  8766. var dot = this.dom.dot;
  8767. if (orientation == 'top') {
  8768. box.style.top = (this.top || 0) + 'px';
  8769. line.style.top = '0';
  8770. line.style.height = (this.parent.top + this.top + 1) + 'px';
  8771. line.style.bottom = '';
  8772. }
  8773. else { // orientation 'bottom'
  8774. var itemSetHeight = this.parent.itemSet.props.height; // TODO: this is nasty
  8775. var lineHeight = itemSetHeight - this.parent.top - this.parent.height + this.top;
  8776. box.style.top = (this.parent.height - this.top - this.height || 0) + 'px';
  8777. line.style.top = (itemSetHeight - lineHeight) + 'px';
  8778. line.style.bottom = '0';
  8779. }
  8780. dot.style.top = (-this.props.dot.height / 2) + 'px';
  8781. };
  8782. module.exports = BoxItem;
  8783. /***/ },
  8784. /* 23 */
  8785. /***/ function(module, exports, __webpack_require__) {
  8786. var Item = __webpack_require__(20);
  8787. /**
  8788. * @constructor PointItem
  8789. * @extends Item
  8790. * @param {Object} data Object containing parameters start
  8791. * content, className.
  8792. * @param {{toScreen: function, toTime: function}} conversion
  8793. * Conversion functions from time to screen and vice versa
  8794. * @param {Object} [options] Configuration options
  8795. * // TODO: describe available options
  8796. */
  8797. function PointItem (data, conversion, options) {
  8798. this.props = {
  8799. dot: {
  8800. top: 0,
  8801. width: 0,
  8802. height: 0
  8803. },
  8804. content: {
  8805. height: 0,
  8806. marginLeft: 0
  8807. }
  8808. };
  8809. // validate data
  8810. if (data) {
  8811. if (data.start == undefined) {
  8812. throw new Error('Property "start" missing in item ' + data);
  8813. }
  8814. }
  8815. Item.call(this, data, conversion, options);
  8816. }
  8817. PointItem.prototype = new Item (null, null, null);
  8818. /**
  8819. * Check whether this item is visible inside given range
  8820. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  8821. * @returns {boolean} True if visible
  8822. */
  8823. PointItem.prototype.isVisible = function(range) {
  8824. // determine visibility
  8825. // TODO: account for the real width of the item. Right now we just add 1/4 to the window
  8826. var interval = (range.end - range.start) / 4;
  8827. return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
  8828. };
  8829. /**
  8830. * Repaint the item
  8831. */
  8832. PointItem.prototype.redraw = function() {
  8833. var dom = this.dom;
  8834. if (!dom) {
  8835. // create DOM
  8836. this.dom = {};
  8837. dom = this.dom;
  8838. // background box
  8839. dom.point = document.createElement('div');
  8840. // className is updated in redraw()
  8841. // contents box, right from the dot
  8842. dom.content = document.createElement('div');
  8843. dom.content.className = 'content';
  8844. dom.point.appendChild(dom.content);
  8845. // dot at start
  8846. dom.dot = document.createElement('div');
  8847. dom.point.appendChild(dom.dot);
  8848. // attach this item as attribute
  8849. dom.point['timeline-item'] = this;
  8850. this.dirty = true;
  8851. }
  8852. // append DOM to parent DOM
  8853. if (!this.parent) {
  8854. throw new Error('Cannot redraw item: no parent attached');
  8855. }
  8856. if (!dom.point.parentNode) {
  8857. var foreground = this.parent.dom.foreground;
  8858. if (!foreground) {
  8859. throw new Error('Cannot redraw item: parent has no foreground container element');
  8860. }
  8861. foreground.appendChild(dom.point);
  8862. }
  8863. this.displayed = true;
  8864. // Update DOM when item is marked dirty. An item is marked dirty when:
  8865. // - the item is not yet rendered
  8866. // - the item's data is changed
  8867. // - the item is selected/deselected
  8868. if (this.dirty) {
  8869. this._updateContents(this.dom.content);
  8870. this._updateTitle(this.dom.point);
  8871. this._updateDataAttributes(this.dom.point);
  8872. this._updateStyle(this.dom.point);
  8873. // update class
  8874. var className = (this.data.className? ' ' + this.data.className : '') +
  8875. (this.selected ? ' selected' : '');
  8876. dom.point.className = 'item point' + className;
  8877. dom.dot.className = 'item dot' + className;
  8878. // recalculate size
  8879. this.width = dom.point.offsetWidth;
  8880. this.height = dom.point.offsetHeight;
  8881. this.props.dot.width = dom.dot.offsetWidth;
  8882. this.props.dot.height = dom.dot.offsetHeight;
  8883. this.props.content.height = dom.content.offsetHeight;
  8884. // resize contents
  8885. dom.content.style.marginLeft = 2 * this.props.dot.width + 'px';
  8886. //dom.content.style.marginRight = ... + 'px'; // TODO: margin right
  8887. dom.dot.style.top = ((this.height - this.props.dot.height) / 2) + 'px';
  8888. dom.dot.style.left = (this.props.dot.width / 2) + 'px';
  8889. this.dirty = false;
  8890. }
  8891. this._repaintDeleteButton(dom.point);
  8892. };
  8893. /**
  8894. * Show the item in the DOM (when not already visible). The items DOM will
  8895. * be created when needed.
  8896. */
  8897. PointItem.prototype.show = function() {
  8898. if (!this.displayed) {
  8899. this.redraw();
  8900. }
  8901. };
  8902. /**
  8903. * Hide the item from the DOM (when visible)
  8904. */
  8905. PointItem.prototype.hide = function() {
  8906. if (this.displayed) {
  8907. if (this.dom.point.parentNode) {
  8908. this.dom.point.parentNode.removeChild(this.dom.point);
  8909. }
  8910. this.displayed = false;
  8911. }
  8912. };
  8913. /**
  8914. * Reposition the item horizontally
  8915. * @Override
  8916. */
  8917. PointItem.prototype.repositionX = function() {
  8918. var start = this.conversion.toScreen(this.data.start);
  8919. this.left = start - this.props.dot.width;
  8920. // reposition point
  8921. this.dom.point.style.left = this.left + 'px';
  8922. };
  8923. /**
  8924. * Reposition the item vertically
  8925. * @Override
  8926. */
  8927. PointItem.prototype.repositionY = function() {
  8928. var orientation = this.options.orientation,
  8929. point = this.dom.point;
  8930. if (orientation == 'top') {
  8931. point.style.top = this.top + 'px';
  8932. }
  8933. else {
  8934. point.style.top = (this.parent.height - this.top - this.height) + 'px';
  8935. }
  8936. };
  8937. module.exports = PointItem;
  8938. /***/ },
  8939. /* 24 */
  8940. /***/ function(module, exports, __webpack_require__) {
  8941. var Hammer = __webpack_require__(45);
  8942. var Item = __webpack_require__(20);
  8943. /**
  8944. * @constructor RangeItem
  8945. * @extends Item
  8946. * @param {Object} data Object containing parameters start, end
  8947. * content, className.
  8948. * @param {{toScreen: function, toTime: function}} conversion
  8949. * Conversion functions from time to screen and vice versa
  8950. * @param {Object} [options] Configuration options
  8951. * // TODO: describe options
  8952. */
  8953. function RangeItem (data, conversion, options) {
  8954. this.props = {
  8955. content: {
  8956. width: 0
  8957. }
  8958. };
  8959. this.overflow = false; // if contents can overflow (css styling), this flag is set to true
  8960. // validate data
  8961. if (data) {
  8962. if (data.start == undefined) {
  8963. throw new Error('Property "start" missing in item ' + data.id);
  8964. }
  8965. if (data.end == undefined) {
  8966. throw new Error('Property "end" missing in item ' + data.id);
  8967. }
  8968. }
  8969. Item.call(this, data, conversion, options);
  8970. }
  8971. RangeItem.prototype = new Item (null, null, null);
  8972. RangeItem.prototype.baseClassName = 'item range';
  8973. /**
  8974. * Check whether this item is visible inside given range
  8975. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  8976. * @returns {boolean} True if visible
  8977. */
  8978. RangeItem.prototype.isVisible = function(range) {
  8979. // determine visibility
  8980. return (this.data.start < range.end) && (this.data.end > range.start);
  8981. };
  8982. /**
  8983. * Repaint the item
  8984. */
  8985. RangeItem.prototype.redraw = function() {
  8986. var dom = this.dom;
  8987. if (!dom) {
  8988. // create DOM
  8989. this.dom = {};
  8990. dom = this.dom;
  8991. // background box
  8992. dom.box = document.createElement('div');
  8993. // className is updated in redraw()
  8994. // contents box
  8995. dom.content = document.createElement('div');
  8996. dom.content.className = 'content';
  8997. dom.box.appendChild(dom.content);
  8998. // attach this item as attribute
  8999. dom.box['timeline-item'] = this;
  9000. this.dirty = true;
  9001. }
  9002. // append DOM to parent DOM
  9003. if (!this.parent) {
  9004. throw new Error('Cannot redraw item: no parent attached');
  9005. }
  9006. if (!dom.box.parentNode) {
  9007. var foreground = this.parent.dom.foreground;
  9008. if (!foreground) {
  9009. throw new Error('Cannot redraw item: parent has no foreground container element');
  9010. }
  9011. foreground.appendChild(dom.box);
  9012. }
  9013. this.displayed = true;
  9014. // Update DOM when item is marked dirty. An item is marked dirty when:
  9015. // - the item is not yet rendered
  9016. // - the item's data is changed
  9017. // - the item is selected/deselected
  9018. if (this.dirty) {
  9019. this._updateContents(this.dom.content);
  9020. this._updateTitle(this.dom.box);
  9021. this._updateDataAttributes(this.dom.box);
  9022. this._updateStyle(this.dom.box);
  9023. // update class
  9024. var className = (this.data.className ? (' ' + this.data.className) : '') +
  9025. (this.selected ? ' selected' : '');
  9026. dom.box.className = this.baseClassName + className;
  9027. // determine from css whether this box has overflow
  9028. this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden';
  9029. // recalculate size
  9030. // turn off max-width to be able to calculate the real width
  9031. // this causes an extra browser repaint/reflow, but so be it
  9032. this.dom.content.style.maxWidth = 'none';
  9033. this.props.content.width = this.dom.content.offsetWidth;
  9034. this.height = this.dom.box.offsetHeight;
  9035. this.dom.content.style.maxWidth = '';
  9036. this.dirty = false;
  9037. }
  9038. this._repaintDeleteButton(dom.box);
  9039. this._repaintDragLeft();
  9040. this._repaintDragRight();
  9041. };
  9042. /**
  9043. * Show the item in the DOM (when not already visible). The items DOM will
  9044. * be created when needed.
  9045. */
  9046. RangeItem.prototype.show = function() {
  9047. if (!this.displayed) {
  9048. this.redraw();
  9049. }
  9050. };
  9051. /**
  9052. * Hide the item from the DOM (when visible)
  9053. * @return {Boolean} changed
  9054. */
  9055. RangeItem.prototype.hide = function() {
  9056. if (this.displayed) {
  9057. var box = this.dom.box;
  9058. if (box.parentNode) {
  9059. box.parentNode.removeChild(box);
  9060. }
  9061. this.displayed = false;
  9062. }
  9063. };
  9064. /**
  9065. * Reposition the item horizontally
  9066. * @param {boolean} [limitSize=true] If true (default), the width of the range
  9067. * item will be limited, as the browser cannot
  9068. * display very wide divs. This means though
  9069. * that the applied left and width may
  9070. * not correspond to the ranges start and end
  9071. * @Override
  9072. */
  9073. RangeItem.prototype.repositionX = function(limitSize) {
  9074. var parentWidth = this.parent.width;
  9075. var start = this.conversion.toScreen(this.data.start);
  9076. var end = this.conversion.toScreen(this.data.end);
  9077. var contentLeft;
  9078. var contentWidth;
  9079. // limit the width of the range, as browsers cannot draw very wide divs
  9080. if (limitSize === undefined || limitSize === true) {
  9081. if (start < -parentWidth) {
  9082. start = -parentWidth;
  9083. }
  9084. if (end > 2 * parentWidth) {
  9085. end = 2 * parentWidth;
  9086. }
  9087. }
  9088. var boxWidth = Math.max(end - start, 1);
  9089. if (this.overflow) {
  9090. this.left = start;
  9091. this.width = boxWidth + this.props.content.width;
  9092. contentWidth = this.props.content.width;
  9093. // Note: The calculation of width is an optimistic calculation, giving
  9094. // a width which will not change when moving the Timeline
  9095. // So no re-stacking needed, which is nicer for the eye;
  9096. }
  9097. else {
  9098. this.left = start;
  9099. this.width = boxWidth;
  9100. contentWidth = Math.min(end - start - 2 * this.options.padding, this.props.content.width);
  9101. }
  9102. this.dom.box.style.left = this.left + 'px';
  9103. this.dom.box.style.width = boxWidth + 'px';
  9104. switch (this.options.align) {
  9105. case 'left':
  9106. this.dom.content.style.left = '0';
  9107. break;
  9108. case 'right':
  9109. this.dom.content.style.left = Math.max((boxWidth - contentWidth - 2 * this.options.padding), 0) + 'px';
  9110. break;
  9111. case 'center':
  9112. this.dom.content.style.left = Math.max((boxWidth - contentWidth - 2 * this.options.padding) / 2, 0) + 'px';
  9113. break;
  9114. default: // 'auto'
  9115. // when range exceeds left of the window, position the contents at the left of the visible area
  9116. if (this.overflow) {
  9117. if (end > 0) {
  9118. contentLeft = Math.max(-start, 0);
  9119. }
  9120. else {
  9121. contentLeft = -contentWidth; // ensure it's not visible anymore
  9122. }
  9123. }
  9124. else {
  9125. if (start < 0) {
  9126. contentLeft = Math.min(-start,
  9127. (end - start - contentWidth - 2 * this.options.padding));
  9128. // TODO: remove the need for options.padding. it's terrible.
  9129. }
  9130. else {
  9131. contentLeft = 0;
  9132. }
  9133. }
  9134. this.dom.content.style.left = contentLeft + 'px';
  9135. }
  9136. };
  9137. /**
  9138. * Reposition the item vertically
  9139. * @Override
  9140. */
  9141. RangeItem.prototype.repositionY = function() {
  9142. var orientation = this.options.orientation,
  9143. box = this.dom.box;
  9144. if (orientation == 'top') {
  9145. box.style.top = this.top + 'px';
  9146. }
  9147. else {
  9148. box.style.top = (this.parent.height - this.top - this.height) + 'px';
  9149. }
  9150. };
  9151. /**
  9152. * Repaint a drag area on the left side of the range when the range is selected
  9153. * @protected
  9154. */
  9155. RangeItem.prototype._repaintDragLeft = function () {
  9156. if (this.selected && this.options.editable.updateTime && !this.dom.dragLeft) {
  9157. // create and show drag area
  9158. var dragLeft = document.createElement('div');
  9159. dragLeft.className = 'drag-left';
  9160. dragLeft.dragLeftItem = this;
  9161. // TODO: this should be redundant?
  9162. Hammer(dragLeft, {
  9163. preventDefault: true
  9164. }).on('drag', function () {
  9165. //console.log('drag left')
  9166. });
  9167. this.dom.box.appendChild(dragLeft);
  9168. this.dom.dragLeft = dragLeft;
  9169. }
  9170. else if (!this.selected && this.dom.dragLeft) {
  9171. // delete drag area
  9172. if (this.dom.dragLeft.parentNode) {
  9173. this.dom.dragLeft.parentNode.removeChild(this.dom.dragLeft);
  9174. }
  9175. this.dom.dragLeft = null;
  9176. }
  9177. };
  9178. /**
  9179. * Repaint a drag area on the right side of the range when the range is selected
  9180. * @protected
  9181. */
  9182. RangeItem.prototype._repaintDragRight = function () {
  9183. if (this.selected && this.options.editable.updateTime && !this.dom.dragRight) {
  9184. // create and show drag area
  9185. var dragRight = document.createElement('div');
  9186. dragRight.className = 'drag-right';
  9187. dragRight.dragRightItem = this;
  9188. // TODO: this should be redundant?
  9189. Hammer(dragRight, {
  9190. preventDefault: true
  9191. }).on('drag', function () {
  9192. //console.log('drag right')
  9193. });
  9194. this.dom.box.appendChild(dragRight);
  9195. this.dom.dragRight = dragRight;
  9196. }
  9197. else if (!this.selected && this.dom.dragRight) {
  9198. // delete drag area
  9199. if (this.dom.dragRight.parentNode) {
  9200. this.dom.dragRight.parentNode.removeChild(this.dom.dragRight);
  9201. }
  9202. this.dom.dragRight = null;
  9203. }
  9204. };
  9205. module.exports = RangeItem;
  9206. /***/ },
  9207. /* 25 */
  9208. /***/ function(module, exports, __webpack_require__) {
  9209. /**
  9210. * Prototype for visual components
  9211. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} [body]
  9212. * @param {Object} [options]
  9213. */
  9214. function Component (body, options) {
  9215. this.options = null;
  9216. this.props = null;
  9217. }
  9218. /**
  9219. * Set options for the component. The new options will be merged into the
  9220. * current options.
  9221. * @param {Object} options
  9222. */
  9223. Component.prototype.setOptions = function(options) {
  9224. if (options) {
  9225. util.extend(this.options, options);
  9226. }
  9227. };
  9228. /**
  9229. * Repaint the component
  9230. * @return {boolean} Returns true if the component is resized
  9231. */
  9232. Component.prototype.redraw = function() {
  9233. // should be implemented by the component
  9234. return false;
  9235. };
  9236. /**
  9237. * Destroy the component. Cleanup DOM and event listeners
  9238. */
  9239. Component.prototype.destroy = function() {
  9240. // should be implemented by the component
  9241. };
  9242. /**
  9243. * Test whether the component is resized since the last time _isResized() was
  9244. * called.
  9245. * @return {Boolean} Returns true if the component is resized
  9246. * @protected
  9247. */
  9248. Component.prototype._isResized = function() {
  9249. var resized = (this.props._previousWidth !== this.props.width ||
  9250. this.props._previousHeight !== this.props.height);
  9251. this.props._previousWidth = this.props.width;
  9252. this.props._previousHeight = this.props.height;
  9253. return resized;
  9254. };
  9255. module.exports = Component;
  9256. /***/ },
  9257. /* 26 */
  9258. /***/ function(module, exports, __webpack_require__) {
  9259. var util = __webpack_require__(1);
  9260. var Component = __webpack_require__(25);
  9261. var moment = __webpack_require__(44);
  9262. var locales = __webpack_require__(48);
  9263. /**
  9264. * A current time bar
  9265. * @param {{range: Range, dom: Object, domProps: Object}} body
  9266. * @param {Object} [options] Available parameters:
  9267. * {Boolean} [showCurrentTime]
  9268. * @constructor CurrentTime
  9269. * @extends Component
  9270. */
  9271. function CurrentTime (body, options) {
  9272. this.body = body;
  9273. // default options
  9274. this.defaultOptions = {
  9275. showCurrentTime: true,
  9276. locales: locales,
  9277. locale: 'en'
  9278. };
  9279. this.options = util.extend({}, this.defaultOptions);
  9280. this.offset = 0;
  9281. this._create();
  9282. this.setOptions(options);
  9283. }
  9284. CurrentTime.prototype = new Component();
  9285. /**
  9286. * Create the HTML DOM for the current time bar
  9287. * @private
  9288. */
  9289. CurrentTime.prototype._create = function() {
  9290. var bar = document.createElement('div');
  9291. bar.className = 'currenttime';
  9292. bar.style.position = 'absolute';
  9293. bar.style.top = '0px';
  9294. bar.style.height = '100%';
  9295. this.bar = bar;
  9296. };
  9297. /**
  9298. * Destroy the CurrentTime bar
  9299. */
  9300. CurrentTime.prototype.destroy = function () {
  9301. this.options.showCurrentTime = false;
  9302. this.redraw(); // will remove the bar from the DOM and stop refreshing
  9303. this.body = null;
  9304. };
  9305. /**
  9306. * Set options for the component. Options will be merged in current options.
  9307. * @param {Object} options Available parameters:
  9308. * {boolean} [showCurrentTime]
  9309. */
  9310. CurrentTime.prototype.setOptions = function(options) {
  9311. if (options) {
  9312. // copy all options that we know
  9313. util.selectiveExtend(['showCurrentTime', 'locale', 'locales'], this.options, options);
  9314. }
  9315. };
  9316. /**
  9317. * Repaint the component
  9318. * @return {boolean} Returns true if the component is resized
  9319. */
  9320. CurrentTime.prototype.redraw = function() {
  9321. if (this.options.showCurrentTime) {
  9322. var parent = this.body.dom.backgroundVertical;
  9323. if (this.bar.parentNode != parent) {
  9324. // attach to the dom
  9325. if (this.bar.parentNode) {
  9326. this.bar.parentNode.removeChild(this.bar);
  9327. }
  9328. parent.appendChild(this.bar);
  9329. this.start();
  9330. }
  9331. var now = new Date(new Date().valueOf() + this.offset);
  9332. var x = this.body.util.toScreen(now);
  9333. var locale = this.options.locales[this.options.locale];
  9334. var title = locale.current + ' ' + locale.time + ': ' + moment(now).format('dddd, MMMM Do YYYY, H:mm:ss');
  9335. title = title.charAt(0).toUpperCase() + title.substring(1);
  9336. this.bar.style.left = x + 'px';
  9337. this.bar.title = title;
  9338. }
  9339. else {
  9340. // remove the line from the DOM
  9341. if (this.bar.parentNode) {
  9342. this.bar.parentNode.removeChild(this.bar);
  9343. }
  9344. this.stop();
  9345. }
  9346. return false;
  9347. };
  9348. /**
  9349. * Start auto refreshing the current time bar
  9350. */
  9351. CurrentTime.prototype.start = function() {
  9352. var me = this;
  9353. function update () {
  9354. me.stop();
  9355. // determine interval to refresh
  9356. var scale = me.body.range.conversion(me.body.domProps.center.width).scale;
  9357. var interval = 1 / scale / 10;
  9358. if (interval < 30) interval = 30;
  9359. if (interval > 1000) interval = 1000;
  9360. me.redraw();
  9361. // start a timer to adjust for the new time
  9362. me.currentTimeTimer = setTimeout(update, interval);
  9363. }
  9364. update();
  9365. };
  9366. /**
  9367. * Stop auto refreshing the current time bar
  9368. */
  9369. CurrentTime.prototype.stop = function() {
  9370. if (this.currentTimeTimer !== undefined) {
  9371. clearTimeout(this.currentTimeTimer);
  9372. delete this.currentTimeTimer;
  9373. }
  9374. };
  9375. /**
  9376. * Set a current time. This can be used for example to ensure that a client's
  9377. * time is synchronized with a shared server time.
  9378. * @param {Date | String | Number} time A Date, unix timestamp, or
  9379. * ISO date string.
  9380. */
  9381. CurrentTime.prototype.setCurrentTime = function(time) {
  9382. var t = util.convert(time, 'Date').valueOf();
  9383. var now = new Date().valueOf();
  9384. this.offset = t - now;
  9385. this.redraw();
  9386. };
  9387. /**
  9388. * Get the current time.
  9389. * @return {Date} Returns the current time.
  9390. */
  9391. CurrentTime.prototype.getCurrentTime = function() {
  9392. return new Date(new Date().valueOf() + this.offset);
  9393. };
  9394. module.exports = CurrentTime;
  9395. /***/ },
  9396. /* 27 */
  9397. /***/ function(module, exports, __webpack_require__) {
  9398. var Hammer = __webpack_require__(45);
  9399. var util = __webpack_require__(1);
  9400. var Component = __webpack_require__(25);
  9401. var moment = __webpack_require__(44);
  9402. var locales = __webpack_require__(48);
  9403. /**
  9404. * A custom time bar
  9405. * @param {{range: Range, dom: Object}} body
  9406. * @param {Object} [options] Available parameters:
  9407. * {Boolean} [showCustomTime]
  9408. * @constructor CustomTime
  9409. * @extends Component
  9410. */
  9411. function CustomTime (body, options) {
  9412. this.body = body;
  9413. // default options
  9414. this.defaultOptions = {
  9415. showCustomTime: false,
  9416. locales: locales,
  9417. locale: 'en',
  9418. id: 0
  9419. };
  9420. this.options = util.extend({}, this.defaultOptions);
  9421. if (options && options.time) {
  9422. this.customTime = options.time;
  9423. } else {
  9424. this.customTime = new Date();
  9425. }
  9426. this.eventParams = {}; // stores state parameters while dragging the bar
  9427. // create the DOM
  9428. this._create();
  9429. this.setOptions(options);
  9430. }
  9431. CustomTime.prototype = new Component();
  9432. /**
  9433. * Set options for the component. Options will be merged in current options.
  9434. * @param {Object} options Available parameters:
  9435. * {boolean} [showCustomTime]
  9436. */
  9437. CustomTime.prototype.setOptions = function(options) {
  9438. if (options) {
  9439. // copy all options that we know
  9440. util.selectiveExtend(['showCustomTime', 'locale', 'locales', 'id'], this.options, options);
  9441. // Triggered by addCustomTimeBar, redraw to add new bar
  9442. if (this.options.id) {
  9443. this.redraw();
  9444. }
  9445. }
  9446. };
  9447. /**
  9448. * Create the DOM for the custom time
  9449. * @private
  9450. */
  9451. CustomTime.prototype._create = function() {
  9452. var bar = document.createElement('div');
  9453. bar.className = 'customtime';
  9454. bar.style.position = 'absolute';
  9455. bar.style.top = '0px';
  9456. bar.style.height = '100%';
  9457. this.bar = bar;
  9458. var drag = document.createElement('div');
  9459. drag.style.position = 'relative';
  9460. drag.style.top = '0px';
  9461. drag.style.left = '-10px';
  9462. drag.style.height = '100%';
  9463. drag.style.width = '20px';
  9464. bar.appendChild(drag);
  9465. // attach event listeners
  9466. this.hammer = Hammer(bar, {
  9467. prevent_default: true
  9468. });
  9469. this.hammer.on('dragstart', this._onDragStart.bind(this));
  9470. this.hammer.on('drag', this._onDrag.bind(this));
  9471. this.hammer.on('dragend', this._onDragEnd.bind(this));
  9472. };
  9473. /**
  9474. * Destroy the CustomTime bar
  9475. */
  9476. CustomTime.prototype.destroy = function () {
  9477. this.options.showCustomTime = false;
  9478. this.redraw(); // will remove the bar from the DOM
  9479. this.hammer.enable(false);
  9480. this.hammer = null;
  9481. this.body = null;
  9482. };
  9483. /**
  9484. * Repaint the component
  9485. * @return {boolean} Returns true if the component is resized
  9486. */
  9487. CustomTime.prototype.redraw = function () {
  9488. if (this.options.showCustomTime) {
  9489. var parent = this.body.dom.backgroundVertical;
  9490. if (this.bar.parentNode != parent) {
  9491. // attach to the dom
  9492. if (this.bar.parentNode) {
  9493. this.bar.parentNode.removeChild(this.bar);
  9494. }
  9495. parent.appendChild(this.bar);
  9496. }
  9497. var x = this.body.util.toScreen(this.customTime);
  9498. var locale = this.options.locales[this.options.locale];
  9499. var title = locale.time + ': ' + moment(this.customTime).format('dddd, MMMM Do YYYY, H:mm:ss');
  9500. title = title.charAt(0).toUpperCase() + title.substring(1);
  9501. this.bar.style.left = x + 'px';
  9502. this.bar.title = title;
  9503. }
  9504. else {
  9505. // remove the line from the DOM
  9506. if (this.bar.parentNode) {
  9507. this.bar.parentNode.removeChild(this.bar);
  9508. }
  9509. }
  9510. return false;
  9511. };
  9512. /**
  9513. * Set custom time.
  9514. * @param {Date | number | string} time
  9515. */
  9516. CustomTime.prototype.setCustomTime = function(time) {
  9517. this.customTime = util.convert(time, 'Date');
  9518. this.redraw();
  9519. };
  9520. /**
  9521. * Retrieve the current custom time.
  9522. * @return {Date} customTime
  9523. */
  9524. CustomTime.prototype.getCustomTime = function() {
  9525. return new Date(this.customTime.valueOf());
  9526. };
  9527. /**
  9528. * Start moving horizontally
  9529. * @param {Event} event
  9530. * @private
  9531. */
  9532. CustomTime.prototype._onDragStart = function(event) {
  9533. this.eventParams.dragging = true;
  9534. this.eventParams.customTime = this.customTime;
  9535. event.stopPropagation();
  9536. event.preventDefault();
  9537. };
  9538. /**
  9539. * Perform moving operating.
  9540. * @param {Event} event
  9541. * @private
  9542. */
  9543. CustomTime.prototype._onDrag = function (event) {
  9544. if (!this.eventParams.dragging) return;
  9545. var deltaX = event.gesture.deltaX,
  9546. x = this.body.util.toScreen(this.eventParams.customTime) + deltaX,
  9547. time = this.body.util.toTime(x);
  9548. this.setCustomTime(time);
  9549. // fire a timechange event
  9550. this.body.emitter.emit('timechange', {
  9551. id: this.options.id,
  9552. time: new Date(this.customTime.valueOf())
  9553. });
  9554. event.stopPropagation();
  9555. event.preventDefault();
  9556. };
  9557. /**
  9558. * Stop moving operating.
  9559. * @param {event} event
  9560. * @private
  9561. */
  9562. CustomTime.prototype._onDragEnd = function (event) {
  9563. if (!this.eventParams.dragging) return;
  9564. // fire a timechanged event
  9565. this.body.emitter.emit('timechanged', {
  9566. id: this.options.id,
  9567. time: new Date(this.customTime.valueOf())
  9568. });
  9569. event.stopPropagation();
  9570. event.preventDefault();
  9571. };
  9572. module.exports = CustomTime;
  9573. /***/ },
  9574. /* 28 */
  9575. /***/ function(module, exports, __webpack_require__) {
  9576. var util = __webpack_require__(1);
  9577. var DOMutil = __webpack_require__(2);
  9578. var Component = __webpack_require__(25);
  9579. var DataStep = __webpack_require__(16);
  9580. /**
  9581. * A horizontal time axis
  9582. * @param {Object} [options] See DataAxis.setOptions for the available
  9583. * options.
  9584. * @constructor DataAxis
  9585. * @extends Component
  9586. * @param body
  9587. */
  9588. function DataAxis (body, options, svg, linegraphOptions) {
  9589. this.id = util.randomUUID();
  9590. this.body = body;
  9591. this.defaultOptions = {
  9592. orientation: 'left', // supported: 'left', 'right'
  9593. showMinorLabels: true,
  9594. showMajorLabels: true,
  9595. icons: true,
  9596. majorLinesOffset: 7,
  9597. minorLinesOffset: 4,
  9598. labelOffsetX: 10,
  9599. labelOffsetY: 2,
  9600. iconWidth: 20,
  9601. width: '40px',
  9602. visible: true,
  9603. alignZeros: true,
  9604. customRange: {
  9605. left: {min:undefined, max:undefined},
  9606. right: {min:undefined, max:undefined}
  9607. },
  9608. title: {
  9609. left: {text:undefined},
  9610. right: {text:undefined}
  9611. },
  9612. format: {
  9613. left: {decimals: undefined},
  9614. right: {decimals: undefined}
  9615. }
  9616. };
  9617. this.linegraphOptions = linegraphOptions;
  9618. this.linegraphSVG = svg;
  9619. this.props = {};
  9620. this.DOMelements = { // dynamic elements
  9621. lines: {},
  9622. labels: {},
  9623. title: {}
  9624. };
  9625. this.dom = {};
  9626. this.range = {start:0, end:0};
  9627. this.options = util.extend({}, this.defaultOptions);
  9628. this.conversionFactor = 1;
  9629. this.setOptions(options);
  9630. this.width = Number(('' + this.options.width).replace("px",""));
  9631. this.minWidth = this.width;
  9632. this.height = this.linegraphSVG.offsetHeight;
  9633. this.hidden = false;
  9634. this.stepPixels = 25;
  9635. this.stepPixelsForced = 25;
  9636. this.zeroCrossing = -1;
  9637. this.lineOffset = 0;
  9638. this.master = true;
  9639. this.svgElements = {};
  9640. this.iconsRemoved = false;
  9641. this.groups = {};
  9642. this.amountOfGroups = 0;
  9643. // create the HTML DOM
  9644. this._create();
  9645. var me = this;
  9646. this.body.emitter.on("verticalDrag", function() {
  9647. me.dom.lineContainer.style.top = me.body.domProps.scrollTop + 'px';
  9648. });
  9649. }
  9650. DataAxis.prototype = new Component();
  9651. DataAxis.prototype.addGroup = function(label, graphOptions) {
  9652. if (!this.groups.hasOwnProperty(label)) {
  9653. this.groups[label] = graphOptions;
  9654. }
  9655. this.amountOfGroups += 1;
  9656. };
  9657. DataAxis.prototype.updateGroup = function(label, graphOptions) {
  9658. this.groups[label] = graphOptions;
  9659. };
  9660. DataAxis.prototype.removeGroup = function(label) {
  9661. if (this.groups.hasOwnProperty(label)) {
  9662. delete this.groups[label];
  9663. this.amountOfGroups -= 1;
  9664. }
  9665. };
  9666. DataAxis.prototype.setOptions = function (options) {
  9667. if (options) {
  9668. var redraw = false;
  9669. if (this.options.orientation != options.orientation && options.orientation !== undefined) {
  9670. redraw = true;
  9671. }
  9672. var fields = [
  9673. 'orientation',
  9674. 'showMinorLabels',
  9675. 'showMajorLabels',
  9676. 'icons',
  9677. 'majorLinesOffset',
  9678. 'minorLinesOffset',
  9679. 'labelOffsetX',
  9680. 'labelOffsetY',
  9681. 'iconWidth',
  9682. 'width',
  9683. 'visible',
  9684. 'customRange',
  9685. 'title',
  9686. 'format',
  9687. 'alignZeros'
  9688. ];
  9689. util.selectiveExtend(fields, this.options, options);
  9690. this.minWidth = Number(('' + this.options.width).replace("px",""));
  9691. if (redraw == true && this.dom.frame) {
  9692. this.hide();
  9693. this.show();
  9694. }
  9695. }
  9696. };
  9697. /**
  9698. * Create the HTML DOM for the DataAxis
  9699. */
  9700. DataAxis.prototype._create = function() {
  9701. this.dom.frame = document.createElement('div');
  9702. this.dom.frame.style.width = this.options.width;
  9703. this.dom.frame.style.height = this.height;
  9704. this.dom.lineContainer = document.createElement('div');
  9705. this.dom.lineContainer.style.width = '100%';
  9706. this.dom.lineContainer.style.height = this.height;
  9707. this.dom.lineContainer.style.position = 'relative';
  9708. // create svg element for graph drawing.
  9709. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  9710. this.svg.style.position = "absolute";
  9711. this.svg.style.top = '0px';
  9712. this.svg.style.height = '100%';
  9713. this.svg.style.width = '100%';
  9714. this.svg.style.display = "block";
  9715. this.dom.frame.appendChild(this.svg);
  9716. };
  9717. DataAxis.prototype._redrawGroupIcons = function () {
  9718. DOMutil.prepareElements(this.svgElements);
  9719. var x;
  9720. var iconWidth = this.options.iconWidth;
  9721. var iconHeight = 15;
  9722. var iconOffset = 4;
  9723. var y = iconOffset + 0.5 * iconHeight;
  9724. if (this.options.orientation == 'left') {
  9725. x = iconOffset;
  9726. }
  9727. else {
  9728. x = this.width - iconWidth - iconOffset;
  9729. }
  9730. for (var groupId in this.groups) {
  9731. if (this.groups.hasOwnProperty(groupId)) {
  9732. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  9733. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  9734. y += iconHeight + iconOffset;
  9735. }
  9736. }
  9737. }
  9738. DOMutil.cleanupElements(this.svgElements);
  9739. this.iconsRemoved = false;
  9740. };
  9741. DataAxis.prototype._cleanupIcons = function() {
  9742. if (this.iconsRemoved == false) {
  9743. DOMutil.prepareElements(this.svgElements);
  9744. DOMutil.cleanupElements(this.svgElements);
  9745. this.iconsRemoved = true;
  9746. }
  9747. }
  9748. /**
  9749. * Create the HTML DOM for the DataAxis
  9750. */
  9751. DataAxis.prototype.show = function() {
  9752. this.hidden = false;
  9753. if (!this.dom.frame.parentNode) {
  9754. if (this.options.orientation == 'left') {
  9755. this.body.dom.left.appendChild(this.dom.frame);
  9756. }
  9757. else {
  9758. this.body.dom.right.appendChild(this.dom.frame);
  9759. }
  9760. }
  9761. if (!this.dom.lineContainer.parentNode) {
  9762. this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer);
  9763. }
  9764. };
  9765. /**
  9766. * Create the HTML DOM for the DataAxis
  9767. */
  9768. DataAxis.prototype.hide = function() {
  9769. this.hidden = true;
  9770. if (this.dom.frame.parentNode) {
  9771. this.dom.frame.parentNode.removeChild(this.dom.frame);
  9772. }
  9773. if (this.dom.lineContainer.parentNode) {
  9774. this.dom.lineContainer.parentNode.removeChild(this.dom.lineContainer);
  9775. }
  9776. };
  9777. /**
  9778. * Set a range (start and end)
  9779. * @param end
  9780. * @param start
  9781. * @param end
  9782. */
  9783. DataAxis.prototype.setRange = function (start, end) {
  9784. if (this.master == false && this.options.alignZeros == true && this.zeroCrossing != -1) {
  9785. if (start > 0) {
  9786. start = 0;
  9787. }
  9788. }
  9789. this.range.start = start;
  9790. this.range.end = end;
  9791. };
  9792. /**
  9793. * Repaint the component
  9794. * @return {boolean} Returns true if the component is resized
  9795. */
  9796. DataAxis.prototype.redraw = function () {
  9797. var resized = false;
  9798. var activeGroups = 0;
  9799. // Make sure the line container adheres to the vertical scrolling.
  9800. this.dom.lineContainer.style.top = this.body.domProps.scrollTop + 'px';
  9801. for (var groupId in this.groups) {
  9802. if (this.groups.hasOwnProperty(groupId)) {
  9803. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  9804. activeGroups++;
  9805. }
  9806. }
  9807. }
  9808. if (this.amountOfGroups == 0 || activeGroups == 0) {
  9809. this.hide();
  9810. }
  9811. else {
  9812. this.show();
  9813. this.height = Number(this.linegraphSVG.style.height.replace("px",""));
  9814. // svg offsetheight did not work in firefox and explorer...
  9815. this.dom.lineContainer.style.height = this.height + 'px';
  9816. this.width = this.options.visible == true ? Number(('' + this.options.width).replace("px","")) : 0;
  9817. var props = this.props;
  9818. var frame = this.dom.frame;
  9819. // update classname
  9820. frame.className = 'dataaxis';
  9821. // calculate character width and height
  9822. this._calculateCharSize();
  9823. var orientation = this.options.orientation;
  9824. var showMinorLabels = this.options.showMinorLabels;
  9825. var showMajorLabels = this.options.showMajorLabels;
  9826. // determine the width and height of the elements for the axis
  9827. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  9828. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  9829. props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset;
  9830. props.minorLineHeight = 1;
  9831. props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset;
  9832. props.majorLineHeight = 1;
  9833. // take frame offline while updating (is almost twice as fast)
  9834. if (orientation == 'left') {
  9835. frame.style.top = '0';
  9836. frame.style.left = '0';
  9837. frame.style.bottom = '';
  9838. frame.style.width = this.width + 'px';
  9839. frame.style.height = this.height + "px";
  9840. this.props.width = this.body.domProps.left.width;
  9841. this.props.height = this.body.domProps.left.height;
  9842. }
  9843. else { // right
  9844. frame.style.top = '';
  9845. frame.style.bottom = '0';
  9846. frame.style.left = '0';
  9847. frame.style.width = this.width + 'px';
  9848. frame.style.height = this.height + "px";
  9849. this.props.width = this.body.domProps.right.width;
  9850. this.props.height = this.body.domProps.right.height;
  9851. }
  9852. resized = this._redrawLabels();
  9853. resized = this._isResized() || resized;
  9854. if (this.options.icons == true) {
  9855. this._redrawGroupIcons();
  9856. }
  9857. else {
  9858. this._cleanupIcons();
  9859. }
  9860. this._redrawTitle(orientation);
  9861. }
  9862. return resized;
  9863. };
  9864. /**
  9865. * Repaint major and minor text labels and vertical grid lines
  9866. * @private
  9867. */
  9868. DataAxis.prototype._redrawLabels = function () {
  9869. var resized = false;
  9870. DOMutil.prepareElements(this.DOMelements.lines);
  9871. DOMutil.prepareElements(this.DOMelements.labels);
  9872. var orientation = this.options['orientation'];
  9873. // calculate range and step (step such that we have space for 7 characters per label)
  9874. var minimumStep = this.master ? this.props.majorCharHeight || 10 : this.stepPixelsForced;
  9875. var step = new DataStep(
  9876. this.range.start,
  9877. this.range.end,
  9878. minimumStep,
  9879. this.dom.frame.offsetHeight,
  9880. this.options.customRange[this.options.orientation],
  9881. this.master == false && this.options.alignZeros // doess the step have to align zeros? only if not master and the options is on
  9882. );
  9883. this.step = step;
  9884. // get the distance in pixels for a step
  9885. // dead space is space that is "left over" after a step
  9886. var stepPixels = (this.dom.frame.offsetHeight - (step.deadSpace * (this.dom.frame.offsetHeight / step.marginRange))) / (((step.marginRange - step.deadSpace) / step.step));
  9887. this.stepPixels = stepPixels;
  9888. var amountOfSteps = this.height / stepPixels;
  9889. var stepDifference = 0;
  9890. // the slave axis needs to use the same horizontal lines as the master axis.
  9891. if (this.master == false) {
  9892. stepPixels = this.stepPixelsForced;
  9893. stepDifference = Math.round((this.dom.frame.offsetHeight / stepPixels) - amountOfSteps);
  9894. for (var i = 0; i < 0.5 * stepDifference; i++) {
  9895. step.previous();
  9896. }
  9897. amountOfSteps = this.height / stepPixels;
  9898. if (this.zeroCrossing != -1 && this.options.alignZeros == true) {
  9899. var zeroStepDifference = (step.marginEnd / step.step) - this.zeroCrossing;
  9900. if (zeroStepDifference > 0) {
  9901. for (var i = 0; i < zeroStepDifference; i++) {step.next();}
  9902. }
  9903. else if (zeroStepDifference < 0) {
  9904. for (var i = 0; i < -zeroStepDifference; i++) {step.previous();}
  9905. }
  9906. }
  9907. }
  9908. else {
  9909. amountOfSteps += 0.25;
  9910. }
  9911. this.valueAtZero = step.marginEnd;
  9912. var marginStartPos = 0;
  9913. // do not draw the first label
  9914. var max = 1;
  9915. // Get the number of decimal places
  9916. var decimals;
  9917. if(this.options.format[orientation] !== undefined) {
  9918. decimals = this.options.format[orientation].decimals;
  9919. }
  9920. this.maxLabelSize = 0;
  9921. var y = 0;
  9922. while (max < Math.round(amountOfSteps)) {
  9923. step.next();
  9924. y = Math.round(max * stepPixels);
  9925. marginStartPos = max * stepPixels;
  9926. var isMajor = step.isMajor();
  9927. if (this.options['showMinorLabels'] && isMajor == false || this.master == false && this.options['showMinorLabels'] == true) {
  9928. this._redrawLabel(y - 2, step.getCurrent(decimals), orientation, 'yAxis minor', this.props.minorCharHeight);
  9929. }
  9930. if (isMajor && this.options['showMajorLabels'] && this.master == true ||
  9931. this.options['showMinorLabels'] == false && this.master == false && isMajor == true) {
  9932. if (y >= 0) {
  9933. this._redrawLabel(y - 2, step.getCurrent(decimals), orientation, 'yAxis major', this.props.majorCharHeight);
  9934. }
  9935. this._redrawLine(y, orientation, 'grid horizontal major', this.options.majorLinesOffset, this.props.majorLineWidth);
  9936. }
  9937. else {
  9938. this._redrawLine(y, orientation, 'grid horizontal minor', this.options.minorLinesOffset, this.props.minorLineWidth);
  9939. }
  9940. if (this.master == true && step.current == 0) {
  9941. this.zeroCrossing = max;
  9942. }
  9943. max++;
  9944. }
  9945. if (this.master == false) {
  9946. this.conversionFactor = y / (this.valueAtZero - step.current);
  9947. }
  9948. else {
  9949. this.conversionFactor = this.dom.frame.offsetHeight / step.marginRange;
  9950. }
  9951. // Note that title is rotated, so we're using the height, not width!
  9952. var titleWidth = 0;
  9953. if (this.options.title[orientation] !== undefined && this.options.title[orientation].text !== undefined) {
  9954. titleWidth = this.props.titleCharHeight;
  9955. }
  9956. var offset = this.options.icons == true ? Math.max(this.options.iconWidth, titleWidth) + this.options.labelOffsetX + 15 : titleWidth + this.options.labelOffsetX + 15;
  9957. // this will resize the yAxis to accommodate the labels.
  9958. if (this.maxLabelSize > (this.width - offset) && this.options.visible == true) {
  9959. this.width = this.maxLabelSize + offset;
  9960. this.options.width = this.width + "px";
  9961. DOMutil.cleanupElements(this.DOMelements.lines);
  9962. DOMutil.cleanupElements(this.DOMelements.labels);
  9963. this.redraw();
  9964. resized = true;
  9965. }
  9966. // this will resize the yAxis if it is too big for the labels.
  9967. else if (this.maxLabelSize < (this.width - offset) && this.options.visible == true && this.width > this.minWidth) {
  9968. this.width = Math.max(this.minWidth,this.maxLabelSize + offset);
  9969. this.options.width = this.width + "px";
  9970. DOMutil.cleanupElements(this.DOMelements.lines);
  9971. DOMutil.cleanupElements(this.DOMelements.labels);
  9972. this.redraw();
  9973. resized = true;
  9974. }
  9975. else {
  9976. DOMutil.cleanupElements(this.DOMelements.lines);
  9977. DOMutil.cleanupElements(this.DOMelements.labels);
  9978. resized = false;
  9979. }
  9980. return resized;
  9981. };
  9982. DataAxis.prototype.convertValue = function (value) {
  9983. var invertedValue = this.valueAtZero - value;
  9984. var convertedValue = invertedValue * this.conversionFactor;
  9985. return convertedValue;
  9986. };
  9987. DataAxis.prototype.screenToValue = function (x) {
  9988. return this.valueAtZero - (x / this.conversionFactor);
  9989. };
  9990. /**
  9991. * Create a label for the axis at position x
  9992. * @private
  9993. * @param y
  9994. * @param text
  9995. * @param orientation
  9996. * @param className
  9997. * @param characterHeight
  9998. */
  9999. DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) {
  10000. // reuse redundant label
  10001. var label = DOMutil.getDOMElement('div',this.DOMelements.labels, this.dom.frame); //this.dom.redundant.labels.shift();
  10002. label.className = className;
  10003. label.innerHTML = text;
  10004. if (orientation == 'left') {
  10005. label.style.left = '-' + this.options.labelOffsetX + 'px';
  10006. label.style.textAlign = "right";
  10007. }
  10008. else {
  10009. label.style.right = '-' + this.options.labelOffsetX + 'px';
  10010. label.style.textAlign = "left";
  10011. }
  10012. label.style.top = y - 0.5 * characterHeight + this.options.labelOffsetY + 'px';
  10013. text += '';
  10014. var largestWidth = Math.max(this.props.majorCharWidth,this.props.minorCharWidth);
  10015. if (this.maxLabelSize < text.length * largestWidth) {
  10016. this.maxLabelSize = text.length * largestWidth;
  10017. }
  10018. };
  10019. /**
  10020. * Create a minor line for the axis at position y
  10021. * @param y
  10022. * @param orientation
  10023. * @param className
  10024. * @param offset
  10025. * @param width
  10026. */
  10027. DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) {
  10028. if (this.master == true) {
  10029. var line = DOMutil.getDOMElement('div',this.DOMelements.lines, this.dom.lineContainer);//this.dom.redundant.lines.shift();
  10030. line.className = className;
  10031. line.innerHTML = '';
  10032. if (orientation == 'left') {
  10033. line.style.left = (this.width - offset) + 'px';
  10034. }
  10035. else {
  10036. line.style.right = (this.width - offset) + 'px';
  10037. }
  10038. line.style.width = width + 'px';
  10039. line.style.top = y + 'px';
  10040. }
  10041. };
  10042. /**
  10043. * Create a title for the axis
  10044. * @private
  10045. * @param orientation
  10046. */
  10047. DataAxis.prototype._redrawTitle = function (orientation) {
  10048. DOMutil.prepareElements(this.DOMelements.title);
  10049. // Check if the title is defined for this axes
  10050. if (this.options.title[orientation] !== undefined && this.options.title[orientation].text !== undefined) {
  10051. var title = DOMutil.getDOMElement('div', this.DOMelements.title, this.dom.frame);
  10052. title.className = 'yAxis title ' + orientation;
  10053. title.innerHTML = this.options.title[orientation].text;
  10054. // Add style - if provided
  10055. if (this.options.title[orientation].style !== undefined) {
  10056. util.addCssText(title, this.options.title[orientation].style);
  10057. }
  10058. if (orientation == 'left') {
  10059. title.style.left = this.props.titleCharHeight + 'px';
  10060. }
  10061. else {
  10062. title.style.right = this.props.titleCharHeight + 'px';
  10063. }
  10064. title.style.width = this.height + 'px';
  10065. }
  10066. // we need to clean up in case we did not use all elements.
  10067. DOMutil.cleanupElements(this.DOMelements.title);
  10068. };
  10069. /**
  10070. * Determine the size of text on the axis (both major and minor axis).
  10071. * The size is calculated only once and then cached in this.props.
  10072. * @private
  10073. */
  10074. DataAxis.prototype._calculateCharSize = function () {
  10075. // determine the char width and height on the minor axis
  10076. if (!('minorCharHeight' in this.props)) {
  10077. var textMinor = document.createTextNode('0');
  10078. var measureCharMinor = document.createElement('div');
  10079. measureCharMinor.className = 'yAxis minor measure';
  10080. measureCharMinor.appendChild(textMinor);
  10081. this.dom.frame.appendChild(measureCharMinor);
  10082. this.props.minorCharHeight = measureCharMinor.clientHeight;
  10083. this.props.minorCharWidth = measureCharMinor.clientWidth;
  10084. this.dom.frame.removeChild(measureCharMinor);
  10085. }
  10086. if (!('majorCharHeight' in this.props)) {
  10087. var textMajor = document.createTextNode('0');
  10088. var measureCharMajor = document.createElement('div');
  10089. measureCharMajor.className = 'yAxis major measure';
  10090. measureCharMajor.appendChild(textMajor);
  10091. this.dom.frame.appendChild(measureCharMajor);
  10092. this.props.majorCharHeight = measureCharMajor.clientHeight;
  10093. this.props.majorCharWidth = measureCharMajor.clientWidth;
  10094. this.dom.frame.removeChild(measureCharMajor);
  10095. }
  10096. if (!('titleCharHeight' in this.props)) {
  10097. var textTitle = document.createTextNode('0');
  10098. var measureCharTitle = document.createElement('div');
  10099. measureCharTitle.className = 'yAxis title measure';
  10100. measureCharTitle.appendChild(textTitle);
  10101. this.dom.frame.appendChild(measureCharTitle);
  10102. this.props.titleCharHeight = measureCharTitle.clientHeight;
  10103. this.props.titleCharWidth = measureCharTitle.clientWidth;
  10104. this.dom.frame.removeChild(measureCharTitle);
  10105. }
  10106. };
  10107. module.exports = DataAxis;
  10108. /***/ },
  10109. /* 29 */
  10110. /***/ function(module, exports, __webpack_require__) {
  10111. var util = __webpack_require__(1);
  10112. var DOMutil = __webpack_require__(2);
  10113. var Line = __webpack_require__(49);
  10114. var Bar = __webpack_require__(50);
  10115. var Points = __webpack_require__(51);
  10116. /**
  10117. * /**
  10118. * @param {object} group | the object of the group from the dataset
  10119. * @param {string} groupId | ID of the group
  10120. * @param {object} options | the default options
  10121. * @param {array} groupsUsingDefaultStyles | this array has one entree.
  10122. * It is passed as an array so it is passed by reference.
  10123. * It enumerates through the default styles
  10124. * @constructor
  10125. */
  10126. function GraphGroup (group, groupId, options, groupsUsingDefaultStyles) {
  10127. this.id = groupId;
  10128. var fields = ['sampling','style','sort','yAxisOrientation','barChart','drawPoints','shaded','catmullRom']
  10129. this.options = util.selectiveBridgeObject(fields,options);
  10130. this.usingDefaultStyle = group.className === undefined;
  10131. this.groupsUsingDefaultStyles = groupsUsingDefaultStyles;
  10132. this.zeroPosition = 0;
  10133. this.update(group);
  10134. if (this.usingDefaultStyle == true) {
  10135. this.groupsUsingDefaultStyles[0] += 1;
  10136. }
  10137. this.itemsData = [];
  10138. this.visible = group.visible === undefined ? true : group.visible;
  10139. }
  10140. /**
  10141. * this loads a reference to all items in this group into this group.
  10142. * @param {array} items
  10143. */
  10144. GraphGroup.prototype.setItems = function(items) {
  10145. if (items != null) {
  10146. this.itemsData = items;
  10147. if (this.options.sort == true) {
  10148. this.itemsData.sort(function (a,b) {return a.x - b.x;})
  10149. }
  10150. }
  10151. else {
  10152. this.itemsData = [];
  10153. }
  10154. };
  10155. /**
  10156. * this is used for plotting barcharts, this way, we only have to calculate it once.
  10157. * @param pos
  10158. */
  10159. GraphGroup.prototype.setZeroPosition = function(pos) {
  10160. this.zeroPosition = pos;
  10161. };
  10162. /**
  10163. * set the options of the graph group over the default options.
  10164. * @param options
  10165. */
  10166. GraphGroup.prototype.setOptions = function(options) {
  10167. if (options !== undefined) {
  10168. var fields = ['sampling','style','sort','yAxisOrientation','barChart'];
  10169. util.selectiveDeepExtend(fields, this.options, options);
  10170. util.mergeOptions(this.options, options,'catmullRom');
  10171. util.mergeOptions(this.options, options,'drawPoints');
  10172. util.mergeOptions(this.options, options,'shaded');
  10173. if (options.catmullRom) {
  10174. if (typeof options.catmullRom == 'object') {
  10175. if (options.catmullRom.parametrization) {
  10176. if (options.catmullRom.parametrization == 'uniform') {
  10177. this.options.catmullRom.alpha = 0;
  10178. }
  10179. else if (options.catmullRom.parametrization == 'chordal') {
  10180. this.options.catmullRom.alpha = 1.0;
  10181. }
  10182. else {
  10183. this.options.catmullRom.parametrization = 'centripetal';
  10184. this.options.catmullRom.alpha = 0.5;
  10185. }
  10186. }
  10187. }
  10188. }
  10189. }
  10190. if (this.options.style == 'line') {
  10191. this.type = new Line(this.id, this.options);
  10192. }
  10193. else if (this.options.style == 'bar') {
  10194. this.type = new Bar(this.id, this.options);
  10195. }
  10196. else if (this.options.style == 'points') {
  10197. this.type = new Points(this.id, this.options);
  10198. }
  10199. };
  10200. /**
  10201. * this updates the current group class with the latest group dataset entree, used in _updateGroup in linegraph
  10202. * @param group
  10203. */
  10204. GraphGroup.prototype.update = function(group) {
  10205. this.group = group;
  10206. this.content = group.content || 'graph';
  10207. this.className = group.className || this.className || "graphGroup" + this.groupsUsingDefaultStyles[0] % 10;
  10208. this.visible = group.visible === undefined ? true : group.visible;
  10209. this.style = group.style;
  10210. this.setOptions(group.options);
  10211. };
  10212. /**
  10213. * draw the icon for the legend.
  10214. *
  10215. * @param x
  10216. * @param y
  10217. * @param JSONcontainer
  10218. * @param SVGcontainer
  10219. * @param iconWidth
  10220. * @param iconHeight
  10221. */
  10222. GraphGroup.prototype.drawIcon = function(x, y, JSONcontainer, SVGcontainer, iconWidth, iconHeight) {
  10223. var fillHeight = iconHeight * 0.5;
  10224. var path, fillPath;
  10225. var outline = DOMutil.getSVGElement("rect", JSONcontainer, SVGcontainer);
  10226. outline.setAttributeNS(null, "x", x);
  10227. outline.setAttributeNS(null, "y", y - fillHeight);
  10228. outline.setAttributeNS(null, "width", iconWidth);
  10229. outline.setAttributeNS(null, "height", 2*fillHeight);
  10230. outline.setAttributeNS(null, "class", "outline");
  10231. if (this.options.style == 'line') {
  10232. path = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
  10233. path.setAttributeNS(null, "class", this.className);
  10234. if(this.style !== undefined) {
  10235. path.setAttributeNS(null, "style", this.style);
  10236. }
  10237. path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+"");
  10238. if (this.options.shaded.enabled == true) {
  10239. fillPath = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
  10240. if (this.options.shaded.orientation == 'top') {
  10241. fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) +
  10242. "L"+x+","+y+" L"+ (x + iconWidth) + ","+y+" L"+ (x + iconWidth) + "," + (y - fillHeight));
  10243. }
  10244. else {
  10245. fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " +
  10246. "L"+x+"," + (y + fillHeight) + " " +
  10247. "L"+ (x + iconWidth) + "," + (y + fillHeight) +
  10248. "L"+ (x + iconWidth) + ","+y);
  10249. }
  10250. fillPath.setAttributeNS(null, "class", this.className + " iconFill");
  10251. }
  10252. if (this.options.drawPoints.enabled == true) {
  10253. DOMutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer);
  10254. }
  10255. }
  10256. else {
  10257. var barWidth = Math.round(0.3 * iconWidth);
  10258. var bar1Height = Math.round(0.4 * iconHeight);
  10259. var bar2Height = Math.round(0.75 * iconHeight);
  10260. var offset = Math.round((iconWidth - (2 * barWidth))/3);
  10261. DOMutil.drawBar(x + 0.5*barWidth + offset , y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' bar', JSONcontainer, SVGcontainer);
  10262. DOMutil.drawBar(x + 1.5*barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' bar', JSONcontainer, SVGcontainer);
  10263. }
  10264. };
  10265. /**
  10266. * return the legend entree for this group.
  10267. *
  10268. * @param iconWidth
  10269. * @param iconHeight
  10270. * @returns {{icon: HTMLElement, label: (group.content|*|string), orientation: (.options.yAxisOrientation|*)}}
  10271. */
  10272. GraphGroup.prototype.getLegend = function(iconWidth, iconHeight) {
  10273. var svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  10274. this.drawIcon(0,0.5*iconHeight,[],svg,iconWidth,iconHeight);
  10275. return {icon: svg, label: this.content, orientation:this.options.yAxisOrientation};
  10276. }
  10277. GraphGroup.prototype.getYRange = function(groupData) {
  10278. return this.type.getYRange(groupData);
  10279. }
  10280. GraphGroup.prototype.draw = function(dataset, group, framework) {
  10281. this.type.draw(dataset, group, framework);
  10282. }
  10283. module.exports = GraphGroup;
  10284. /***/ },
  10285. /* 30 */
  10286. /***/ function(module, exports, __webpack_require__) {
  10287. var util = __webpack_require__(1);
  10288. var stack = __webpack_require__(18);
  10289. var RangeItem = __webpack_require__(24);
  10290. /**
  10291. * @constructor Group
  10292. * @param {Number | String} groupId
  10293. * @param {Object} data
  10294. * @param {ItemSet} itemSet
  10295. */
  10296. function Group (groupId, data, itemSet) {
  10297. this.groupId = groupId;
  10298. this.subgroups = {};
  10299. this.subgroupIndex = 0;
  10300. this.subgroupOrderer = data && data.subgroupOrder;
  10301. this.itemSet = itemSet;
  10302. this.dom = {};
  10303. this.props = {
  10304. label: {
  10305. width: 0,
  10306. height: 0
  10307. }
  10308. };
  10309. this.className = null;
  10310. this.items = {}; // items filtered by groupId of this group
  10311. this.visibleItems = []; // items currently visible in window
  10312. this.orderedItems = {
  10313. byStart: [],
  10314. byEnd: []
  10315. };
  10316. this.checkRangedItems = false; // needed to refresh the ranged items if the window is programatically changed with NO overlap.
  10317. var me = this;
  10318. this.itemSet.body.emitter.on("checkRangedItems", function () {
  10319. me.checkRangedItems = true;
  10320. })
  10321. this._create();
  10322. this.setData(data);
  10323. }
  10324. /**
  10325. * Create DOM elements for the group
  10326. * @private
  10327. */
  10328. Group.prototype._create = function() {
  10329. var label = document.createElement('div');
  10330. label.className = 'vlabel';
  10331. this.dom.label = label;
  10332. var inner = document.createElement('div');
  10333. inner.className = 'inner';
  10334. label.appendChild(inner);
  10335. this.dom.inner = inner;
  10336. var foreground = document.createElement('div');
  10337. foreground.className = 'group';
  10338. foreground['timeline-group'] = this;
  10339. this.dom.foreground = foreground;
  10340. this.dom.background = document.createElement('div');
  10341. this.dom.background.className = 'group';
  10342. this.dom.axis = document.createElement('div');
  10343. this.dom.axis.className = 'group';
  10344. // create a hidden marker to detect when the Timelines container is attached
  10345. // to the DOM, or the style of a parent of the Timeline is changed from
  10346. // display:none is changed to visible.
  10347. this.dom.marker = document.createElement('div');
  10348. this.dom.marker.style.visibility = 'hidden'; // TODO: ask jos why this is not none?
  10349. this.dom.marker.innerHTML = '?';
  10350. this.dom.background.appendChild(this.dom.marker);
  10351. };
  10352. /**
  10353. * Set the group data for this group
  10354. * @param {Object} data Group data, can contain properties content and className
  10355. */
  10356. Group.prototype.setData = function(data) {
  10357. // update contents
  10358. var content = data && data.content;
  10359. if (content instanceof Element) {
  10360. this.dom.inner.appendChild(content);
  10361. }
  10362. else if (content !== undefined && content !== null) {
  10363. this.dom.inner.innerHTML = content;
  10364. }
  10365. else {
  10366. this.dom.inner.innerHTML = this.groupId || ''; // groupId can be null
  10367. }
  10368. // update title
  10369. this.dom.label.title = data && data.title || '';
  10370. if (!this.dom.inner.firstChild) {
  10371. util.addClassName(this.dom.inner, 'hidden');
  10372. }
  10373. else {
  10374. util.removeClassName(this.dom.inner, 'hidden');
  10375. }
  10376. // update className
  10377. var className = data && data.className || null;
  10378. if (className != this.className) {
  10379. if (this.className) {
  10380. util.removeClassName(this.dom.label, this.className);
  10381. util.removeClassName(this.dom.foreground, this.className);
  10382. util.removeClassName(this.dom.background, this.className);
  10383. util.removeClassName(this.dom.axis, this.className);
  10384. }
  10385. util.addClassName(this.dom.label, className);
  10386. util.addClassName(this.dom.foreground, className);
  10387. util.addClassName(this.dom.background, className);
  10388. util.addClassName(this.dom.axis, className);
  10389. this.className = className;
  10390. }
  10391. // update style
  10392. if (this.style) {
  10393. util.removeCssText(this.dom.label, this.style);
  10394. this.style = null;
  10395. }
  10396. if (data && data.style) {
  10397. util.addCssText(this.dom.label, data.style);
  10398. this.style = data.style;
  10399. }
  10400. };
  10401. /**
  10402. * Get the width of the group label
  10403. * @return {number} width
  10404. */
  10405. Group.prototype.getLabelWidth = function() {
  10406. return this.props.label.width;
  10407. };
  10408. /**
  10409. * Repaint this group
  10410. * @param {{start: number, end: number}} range
  10411. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  10412. * @param {boolean} [restack=false] Force restacking of all items
  10413. * @return {boolean} Returns true if the group is resized
  10414. */
  10415. Group.prototype.redraw = function(range, margin, restack) {
  10416. var resized = false;
  10417. // force recalculation of the height of the items when the marker height changed
  10418. // (due to the Timeline being attached to the DOM or changed from display:none to visible)
  10419. var markerHeight = this.dom.marker.clientHeight;
  10420. if (markerHeight != this.lastMarkerHeight) {
  10421. this.lastMarkerHeight = markerHeight;
  10422. util.forEach(this.items, function (item) {
  10423. item.dirty = true;
  10424. if (item.displayed) item.redraw();
  10425. });
  10426. restack = true;
  10427. }
  10428. // reposition visible items vertically
  10429. if (typeof this.itemSet.options.order === 'function') {
  10430. // a custom order function
  10431. if (restack) {
  10432. // brute force restack of all items
  10433. // show all items
  10434. var me = this;
  10435. var limitSize = false;
  10436. util.forEach(this.items, function (item) {
  10437. if (!item.displayed) {
  10438. item.redraw();
  10439. me.visibleItems.push(item);
  10440. }
  10441. item.repositionX(limitSize);
  10442. });
  10443. // order all items and force a restacking
  10444. var customOrderedItems = this.orderedItems.byStart.slice().sort(function (a, b) {
  10445. return me.itemSet.options.order(a.data, b.data);
  10446. });
  10447. stack.stack(customOrderedItems, margin, true /* restack=true */);
  10448. }
  10449. this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
  10450. }
  10451. else {
  10452. // no custom order function, lazy stacking
  10453. this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
  10454. if (this.itemSet.options.stack) { // TODO: ugly way to access options...
  10455. stack.stack(this.visibleItems, margin, restack);
  10456. }
  10457. else { // no stacking
  10458. stack.nostack(this.visibleItems, margin, this.subgroups);
  10459. }
  10460. }
  10461. // recalculate the height of the group
  10462. var height = this._calculateHeight(margin);
  10463. // calculate actual size and position
  10464. var foreground = this.dom.foreground;
  10465. this.top = foreground.offsetTop;
  10466. this.left = foreground.offsetLeft;
  10467. this.width = foreground.offsetWidth;
  10468. resized = util.updateProperty(this, 'height', height) || resized;
  10469. // recalculate size of label
  10470. resized = util.updateProperty(this.props.label, 'width', this.dom.inner.clientWidth) || resized;
  10471. resized = util.updateProperty(this.props.label, 'height', this.dom.inner.clientHeight) || resized;
  10472. // apply new height
  10473. this.dom.background.style.height = height + 'px';
  10474. this.dom.foreground.style.height = height + 'px';
  10475. this.dom.label.style.height = height + 'px';
  10476. // update vertical position of items after they are re-stacked and the height of the group is calculated
  10477. for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
  10478. var item = this.visibleItems[i];
  10479. item.repositionY(margin);
  10480. }
  10481. return resized;
  10482. };
  10483. /**
  10484. * recalculate the height of the group
  10485. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  10486. * @returns {number} Returns the height
  10487. * @private
  10488. */
  10489. Group.prototype._calculateHeight = function (margin) {
  10490. // recalculate the height of the group
  10491. var height;
  10492. var visibleItems = this.visibleItems;
  10493. //var visibleSubgroups = [];
  10494. //this.visibleSubgroups = 0;
  10495. this.resetSubgroups();
  10496. var me = this;
  10497. if (visibleItems.length) {
  10498. var min = visibleItems[0].top;
  10499. var max = visibleItems[0].top + visibleItems[0].height;
  10500. util.forEach(visibleItems, function (item) {
  10501. min = Math.min(min, item.top);
  10502. max = Math.max(max, (item.top + item.height));
  10503. if (item.data.subgroup !== undefined) {
  10504. me.subgroups[item.data.subgroup].height = Math.max(me.subgroups[item.data.subgroup].height,item.height);
  10505. me.subgroups[item.data.subgroup].visible = true;
  10506. //if (visibleSubgroups.indexOf(item.data.subgroup) == -1){
  10507. // visibleSubgroups.push(item.data.subgroup);
  10508. // me.visibleSubgroups += 1;
  10509. //}
  10510. }
  10511. });
  10512. if (min > margin.axis) {
  10513. // there is an empty gap between the lowest item and the axis
  10514. var offset = min - margin.axis;
  10515. max -= offset;
  10516. util.forEach(visibleItems, function (item) {
  10517. item.top -= offset;
  10518. });
  10519. }
  10520. height = max + margin.item.vertical / 2;
  10521. }
  10522. else {
  10523. height = margin.axis + margin.item.vertical;
  10524. }
  10525. height = Math.max(height, this.props.label.height);
  10526. return height;
  10527. };
  10528. /**
  10529. * Show this group: attach to the DOM
  10530. */
  10531. Group.prototype.show = function() {
  10532. if (!this.dom.label.parentNode) {
  10533. this.itemSet.dom.labelSet.appendChild(this.dom.label);
  10534. }
  10535. if (!this.dom.foreground.parentNode) {
  10536. this.itemSet.dom.foreground.appendChild(this.dom.foreground);
  10537. }
  10538. if (!this.dom.background.parentNode) {
  10539. this.itemSet.dom.background.appendChild(this.dom.background);
  10540. }
  10541. if (!this.dom.axis.parentNode) {
  10542. this.itemSet.dom.axis.appendChild(this.dom.axis);
  10543. }
  10544. };
  10545. /**
  10546. * Hide this group: remove from the DOM
  10547. */
  10548. Group.prototype.hide = function() {
  10549. var label = this.dom.label;
  10550. if (label.parentNode) {
  10551. label.parentNode.removeChild(label);
  10552. }
  10553. var foreground = this.dom.foreground;
  10554. if (foreground.parentNode) {
  10555. foreground.parentNode.removeChild(foreground);
  10556. }
  10557. var background = this.dom.background;
  10558. if (background.parentNode) {
  10559. background.parentNode.removeChild(background);
  10560. }
  10561. var axis = this.dom.axis;
  10562. if (axis.parentNode) {
  10563. axis.parentNode.removeChild(axis);
  10564. }
  10565. };
  10566. /**
  10567. * Add an item to the group
  10568. * @param {Item} item
  10569. */
  10570. Group.prototype.add = function(item) {
  10571. this.items[item.id] = item;
  10572. item.setParent(this);
  10573. // add to
  10574. if (item.data.subgroup !== undefined) {
  10575. if (this.subgroups[item.data.subgroup] === undefined) {
  10576. this.subgroups[item.data.subgroup] = {height:0, visible: false, index:this.subgroupIndex, items: []};
  10577. this.subgroupIndex++;
  10578. }
  10579. this.subgroups[item.data.subgroup].items.push(item);
  10580. }
  10581. this.orderSubgroups();
  10582. if (this.visibleItems.indexOf(item) == -1) {
  10583. var range = this.itemSet.body.range; // TODO: not nice accessing the range like this
  10584. this._checkIfVisible(item, this.visibleItems, range);
  10585. }
  10586. };
  10587. Group.prototype.orderSubgroups = function() {
  10588. if (this.subgroupOrderer !== undefined) {
  10589. var sortArray = [];
  10590. if (typeof this.subgroupOrderer == 'string') {
  10591. for (var subgroup in this.subgroups) {
  10592. sortArray.push({subgroup: subgroup, sortField: this.subgroups[subgroup].items[0].data[this.subgroupOrderer]})
  10593. }
  10594. sortArray.sort(function (a, b) {
  10595. return a.sortField - b.sortField;
  10596. })
  10597. }
  10598. else if (typeof this.subgroupOrderer == 'function') {
  10599. for (var subgroup in this.subgroups) {
  10600. sortArray.push(this.subgroups[subgroup].items[0].data);
  10601. }
  10602. sortArray.sort(this.subgroupOrderer);
  10603. }
  10604. if (sortArray.length > 0) {
  10605. for (var i = 0; i < sortArray.length; i++) {
  10606. this.subgroups[sortArray[i].subgroup].index = i;
  10607. }
  10608. }
  10609. }
  10610. };
  10611. Group.prototype.resetSubgroups = function() {
  10612. for (var subgroup in this.subgroups) {
  10613. if (this.subgroups.hasOwnProperty(subgroup)) {
  10614. this.subgroups[subgroup].visible = false;
  10615. }
  10616. }
  10617. };
  10618. /**
  10619. * Remove an item from the group
  10620. * @param {Item} item
  10621. */
  10622. Group.prototype.remove = function(item) {
  10623. delete this.items[item.id];
  10624. item.setParent(null);
  10625. // remove from visible items
  10626. var index = this.visibleItems.indexOf(item);
  10627. if (index != -1) this.visibleItems.splice(index, 1);
  10628. // TODO: also remove from ordered items?
  10629. };
  10630. /**
  10631. * Remove an item from the corresponding DataSet
  10632. * @param {Item} item
  10633. */
  10634. Group.prototype.removeFromDataSet = function(item) {
  10635. this.itemSet.removeItem(item.id);
  10636. };
  10637. /**
  10638. * Reorder the items
  10639. */
  10640. Group.prototype.order = function() {
  10641. var array = util.toArray(this.items);
  10642. var startArray = [];
  10643. var endArray = [];
  10644. for (var i = 0; i < array.length; i++) {
  10645. if (array[i].data.end !== undefined) {
  10646. endArray.push(array[i]);
  10647. }
  10648. startArray.push(array[i]);
  10649. }
  10650. this.orderedItems = {
  10651. byStart: startArray,
  10652. byEnd: endArray
  10653. };
  10654. stack.orderByStart(this.orderedItems.byStart);
  10655. stack.orderByEnd(this.orderedItems.byEnd);
  10656. };
  10657. /**
  10658. * Update the visible items
  10659. * @param {{byStart: Item[], byEnd: Item[]}} orderedItems All items ordered by start date and by end date
  10660. * @param {Item[]} visibleItems The previously visible items.
  10661. * @param {{start: number, end: number}} range Visible range
  10662. * @return {Item[]} visibleItems The new visible items.
  10663. * @private
  10664. */
  10665. Group.prototype._updateVisibleItems = function(orderedItems, oldVisibleItems, range) {
  10666. var visibleItems = [];
  10667. var visibleItemsLookup = {}; // we keep this to quickly look up if an item already exists in the list without using indexOf on visibleItems
  10668. var interval = (range.end - range.start) / 4;
  10669. var lowerBound = range.start - interval;
  10670. var upperBound = range.end + interval;
  10671. var item, i;
  10672. // this function is used to do the binary search.
  10673. var searchFunction = function (value) {
  10674. if (value < lowerBound) {return -1;}
  10675. else if (value <= upperBound) {return 0;}
  10676. else {return 1;}
  10677. }
  10678. // first check if the items that were in view previously are still in view.
  10679. // IMPORTANT: this handles the case for the items with startdate before the window and enddate after the window!
  10680. // also cleans up invisible items.
  10681. if (oldVisibleItems.length > 0) {
  10682. for (i = 0; i < oldVisibleItems.length; i++) {
  10683. this._checkIfVisibleWithReference(oldVisibleItems[i], visibleItems, visibleItemsLookup, range);
  10684. }
  10685. }
  10686. // we do a binary search for the items that have only start values.
  10687. var initialPosByStart = util.binarySearchCustom(orderedItems.byStart, searchFunction, 'data','start');
  10688. // trace the visible items from the inital start pos both ways until an invisible item is found, we only look at the start values.
  10689. this._traceVisible(initialPosByStart, orderedItems.byStart, visibleItems, visibleItemsLookup, function (item) {
  10690. return (item.data.start < lowerBound || item.data.start > upperBound);
  10691. });
  10692. // if the window has changed programmatically without overlapping the old window, the ranged items with start < lowerBound and end > upperbound are not shown.
  10693. // We therefore have to brute force check all items in the byEnd list
  10694. if (this.checkRangedItems == true) {
  10695. this.checkRangedItems = false;
  10696. for (i = 0; i < orderedItems.byEnd.length; i++) {
  10697. this._checkIfVisibleWithReference(orderedItems.byEnd[i], visibleItems, visibleItemsLookup, range);
  10698. }
  10699. }
  10700. else {
  10701. // we do a binary search for the items that have defined end times.
  10702. var initialPosByEnd = util.binarySearchCustom(orderedItems.byEnd, searchFunction, 'data','end');
  10703. // trace the visible items from the inital start pos both ways until an invisible item is found, we only look at the end values.
  10704. this._traceVisible(initialPosByEnd, orderedItems.byEnd, visibleItems, visibleItemsLookup, function (item) {
  10705. return (item.data.end < lowerBound || item.data.end > upperBound);
  10706. });
  10707. }
  10708. // finally, we reposition all the visible items.
  10709. for (i = 0; i < visibleItems.length; i++) {
  10710. item = visibleItems[i];
  10711. if (!item.displayed) item.show();
  10712. // reposition item horizontally
  10713. item.repositionX();
  10714. }
  10715. // debug
  10716. //console.log("new line")
  10717. //if (this.groupId == null) {
  10718. // for (i = 0; i < orderedItems.byStart.length; i++) {
  10719. // item = orderedItems.byStart[i].data;
  10720. // console.log('start',i,initialPosByStart, item.start.valueOf(), item.content, item.start >= lowerBound && item.start <= upperBound,i == initialPosByStart ? "<------------------- HEREEEE" : "")
  10721. // }
  10722. // for (i = 0; i < orderedItems.byEnd.length; i++) {
  10723. // item = orderedItems.byEnd[i].data;
  10724. // console.log('rangeEnd',i,initialPosByEnd, item.end.valueOf(), item.content, item.end >= range.start && item.end <= range.end,i == initialPosByEnd ? "<------------------- HEREEEE" : "")
  10725. // }
  10726. //}
  10727. return visibleItems;
  10728. };
  10729. Group.prototype._traceVisible = function (initialPos, items, visibleItems, visibleItemsLookup, breakCondition) {
  10730. var item;
  10731. var i;
  10732. if (initialPos != -1) {
  10733. for (i = initialPos; i >= 0; i--) {
  10734. item = items[i];
  10735. if (breakCondition(item)) {
  10736. break;
  10737. }
  10738. else {
  10739. if (visibleItemsLookup[item.id] === undefined) {
  10740. visibleItemsLookup[item.id] = true;
  10741. visibleItems.push(item);
  10742. }
  10743. }
  10744. }
  10745. for (i = initialPos + 1; i < items.length; i++) {
  10746. item = items[i];
  10747. if (breakCondition(item)) {
  10748. break;
  10749. }
  10750. else {
  10751. if (visibleItemsLookup[item.id] === undefined) {
  10752. visibleItemsLookup[item.id] = true;
  10753. visibleItems.push(item);
  10754. }
  10755. }
  10756. }
  10757. }
  10758. }
  10759. /**
  10760. * this function is very similar to the _checkIfInvisible() but it does not
  10761. * return booleans, hides the item if it should not be seen and always adds to
  10762. * the visibleItems.
  10763. * this one is for brute forcing and hiding.
  10764. *
  10765. * @param {Item} item
  10766. * @param {Array} visibleItems
  10767. * @param {{start:number, end:number}} range
  10768. * @private
  10769. */
  10770. Group.prototype._checkIfVisible = function(item, visibleItems, range) {
  10771. if (item.isVisible(range)) {
  10772. if (!item.displayed) item.show();
  10773. // reposition item horizontally
  10774. item.repositionX();
  10775. visibleItems.push(item);
  10776. }
  10777. else {
  10778. if (item.displayed) item.hide();
  10779. }
  10780. };
  10781. /**
  10782. * this function is very similar to the _checkIfInvisible() but it does not
  10783. * return booleans, hides the item if it should not be seen and always adds to
  10784. * the visibleItems.
  10785. * this one is for brute forcing and hiding.
  10786. *
  10787. * @param {Item} item
  10788. * @param {Array} visibleItems
  10789. * @param {{start:number, end:number}} range
  10790. * @private
  10791. */
  10792. Group.prototype._checkIfVisibleWithReference = function(item, visibleItems, visibleItemsLookup, range) {
  10793. if (item.isVisible(range)) {
  10794. if (visibleItemsLookup[item.id] === undefined) {
  10795. visibleItemsLookup[item.id] = true;
  10796. visibleItems.push(item);
  10797. }
  10798. }
  10799. else {
  10800. if (item.displayed) item.hide();
  10801. }
  10802. };
  10803. module.exports = Group;
  10804. /***/ },
  10805. /* 31 */
  10806. /***/ function(module, exports, __webpack_require__) {
  10807. var util = __webpack_require__(1);
  10808. var Group = __webpack_require__(30);
  10809. /**
  10810. * @constructor BackgroundGroup
  10811. * @param {Number | String} groupId
  10812. * @param {Object} data
  10813. * @param {ItemSet} itemSet
  10814. */
  10815. function BackgroundGroup (groupId, data, itemSet) {
  10816. Group.call(this, groupId, data, itemSet);
  10817. this.width = 0;
  10818. this.height = 0;
  10819. this.top = 0;
  10820. this.left = 0;
  10821. }
  10822. BackgroundGroup.prototype = Object.create(Group.prototype);
  10823. /**
  10824. * Repaint this group
  10825. * @param {{start: number, end: number}} range
  10826. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  10827. * @param {boolean} [restack=false] Force restacking of all items
  10828. * @return {boolean} Returns true if the group is resized
  10829. */
  10830. BackgroundGroup.prototype.redraw = function(range, margin, restack) {
  10831. var resized = false;
  10832. this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
  10833. // calculate actual size
  10834. this.width = this.dom.background.offsetWidth;
  10835. // apply new height (just always zero for BackgroundGroup
  10836. this.dom.background.style.height = '0';
  10837. // update vertical position of items after they are re-stacked and the height of the group is calculated
  10838. for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
  10839. var item = this.visibleItems[i];
  10840. item.repositionY(margin);
  10841. }
  10842. return resized;
  10843. };
  10844. /**
  10845. * Show this group: attach to the DOM
  10846. */
  10847. BackgroundGroup.prototype.show = function() {
  10848. if (!this.dom.background.parentNode) {
  10849. this.itemSet.dom.background.appendChild(this.dom.background);
  10850. }
  10851. };
  10852. module.exports = BackgroundGroup;
  10853. /***/ },
  10854. /* 32 */
  10855. /***/ function(module, exports, __webpack_require__) {
  10856. var Hammer = __webpack_require__(45);
  10857. var util = __webpack_require__(1);
  10858. var DataSet = __webpack_require__(3);
  10859. var DataView = __webpack_require__(4);
  10860. var TimeStep = __webpack_require__(19);
  10861. var Component = __webpack_require__(25);
  10862. var Group = __webpack_require__(30);
  10863. var BackgroundGroup = __webpack_require__(31);
  10864. var BoxItem = __webpack_require__(22);
  10865. var PointItem = __webpack_require__(23);
  10866. var RangeItem = __webpack_require__(24);
  10867. var BackgroundItem = __webpack_require__(21);
  10868. var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
  10869. var BACKGROUND = '__background__'; // reserved group id for background items without group
  10870. /**
  10871. * An ItemSet holds a set of items and ranges which can be displayed in a
  10872. * range. The width is determined by the parent of the ItemSet, and the height
  10873. * is determined by the size of the items.
  10874. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
  10875. * @param {Object} [options] See ItemSet.setOptions for the available options.
  10876. * @constructor ItemSet
  10877. * @extends Component
  10878. */
  10879. function ItemSet(body, options) {
  10880. this.body = body;
  10881. this.defaultOptions = {
  10882. type: null, // 'box', 'point', 'range', 'background'
  10883. orientation: 'bottom', // 'top' or 'bottom'
  10884. align: 'auto', // alignment of box items
  10885. stack: true,
  10886. groupOrder: null,
  10887. selectable: true,
  10888. editable: {
  10889. updateTime: false,
  10890. updateGroup: false,
  10891. add: false,
  10892. remove: false
  10893. },
  10894. snap: TimeStep.snap,
  10895. onAdd: function (item, callback) {
  10896. callback(item);
  10897. },
  10898. onUpdate: function (item, callback) {
  10899. callback(item);
  10900. },
  10901. onMove: function (item, callback) {
  10902. callback(item);
  10903. },
  10904. onRemove: function (item, callback) {
  10905. callback(item);
  10906. },
  10907. onMoving: function (item, callback) {
  10908. callback(item);
  10909. },
  10910. margin: {
  10911. item: {
  10912. horizontal: 10,
  10913. vertical: 10
  10914. },
  10915. axis: 20
  10916. },
  10917. padding: 5
  10918. };
  10919. // options is shared by this ItemSet and all its items
  10920. this.options = util.extend({}, this.defaultOptions);
  10921. // options for getting items from the DataSet with the correct type
  10922. this.itemOptions = {
  10923. type: {start: 'Date', end: 'Date'}
  10924. };
  10925. this.conversion = {
  10926. toScreen: body.util.toScreen,
  10927. toTime: body.util.toTime
  10928. };
  10929. this.dom = {};
  10930. this.props = {};
  10931. this.hammer = null;
  10932. var me = this;
  10933. this.itemsData = null; // DataSet
  10934. this.groupsData = null; // DataSet
  10935. // listeners for the DataSet of the items
  10936. this.itemListeners = {
  10937. 'add': function (event, params, senderId) {
  10938. me._onAdd(params.items);
  10939. },
  10940. 'update': function (event, params, senderId) {
  10941. me._onUpdate(params.items);
  10942. },
  10943. 'remove': function (event, params, senderId) {
  10944. me._onRemove(params.items);
  10945. }
  10946. };
  10947. // listeners for the DataSet of the groups
  10948. this.groupListeners = {
  10949. 'add': function (event, params, senderId) {
  10950. me._onAddGroups(params.items);
  10951. },
  10952. 'update': function (event, params, senderId) {
  10953. me._onUpdateGroups(params.items);
  10954. },
  10955. 'remove': function (event, params, senderId) {
  10956. me._onRemoveGroups(params.items);
  10957. }
  10958. };
  10959. this.items = {}; // object with an Item for every data item
  10960. this.groups = {}; // Group object for every group
  10961. this.groupIds = [];
  10962. this.selection = []; // list with the ids of all selected nodes
  10963. this.stackDirty = true; // if true, all items will be restacked on next redraw
  10964. this.touchParams = {}; // stores properties while dragging
  10965. // create the HTML DOM
  10966. this._create();
  10967. this.setOptions(options);
  10968. }
  10969. ItemSet.prototype = new Component();
  10970. // available item types will be registered here
  10971. ItemSet.types = {
  10972. background: BackgroundItem,
  10973. box: BoxItem,
  10974. range: RangeItem,
  10975. point: PointItem
  10976. };
  10977. /**
  10978. * Create the HTML DOM for the ItemSet
  10979. */
  10980. ItemSet.prototype._create = function(){
  10981. var frame = document.createElement('div');
  10982. frame.className = 'itemset';
  10983. frame['timeline-itemset'] = this;
  10984. this.dom.frame = frame;
  10985. // create background panel
  10986. var background = document.createElement('div');
  10987. background.className = 'background';
  10988. frame.appendChild(background);
  10989. this.dom.background = background;
  10990. // create foreground panel
  10991. var foreground = document.createElement('div');
  10992. foreground.className = 'foreground';
  10993. frame.appendChild(foreground);
  10994. this.dom.foreground = foreground;
  10995. // create axis panel
  10996. var axis = document.createElement('div');
  10997. axis.className = 'axis';
  10998. this.dom.axis = axis;
  10999. // create labelset
  11000. var labelSet = document.createElement('div');
  11001. labelSet.className = 'labelset';
  11002. this.dom.labelSet = labelSet;
  11003. // create ungrouped Group
  11004. this._updateUngrouped();
  11005. // create background Group
  11006. var backgroundGroup = new BackgroundGroup(BACKGROUND, null, this);
  11007. backgroundGroup.show();
  11008. this.groups[BACKGROUND] = backgroundGroup;
  11009. // attach event listeners
  11010. // Note: we bind to the centerContainer for the case where the height
  11011. // of the center container is larger than of the ItemSet, so we
  11012. // can click in the empty area to create a new item or deselect an item.
  11013. this.hammer = Hammer(this.body.dom.centerContainer, {
  11014. preventDefault: true
  11015. });
  11016. // drag items when selected
  11017. this.hammer.on('touch', this._onTouch.bind(this));
  11018. this.hammer.on('dragstart', this._onDragStart.bind(this));
  11019. this.hammer.on('drag', this._onDrag.bind(this));
  11020. this.hammer.on('dragend', this._onDragEnd.bind(this));
  11021. // single select (or unselect) when tapping an item
  11022. this.hammer.on('tap', this._onSelectItem.bind(this));
  11023. // multi select when holding mouse/touch, or on ctrl+click
  11024. this.hammer.on('hold', this._onMultiSelectItem.bind(this));
  11025. // add item on doubletap
  11026. this.hammer.on('doubletap', this._onAddItem.bind(this));
  11027. // attach to the DOM
  11028. this.show();
  11029. };
  11030. /**
  11031. * Set options for the ItemSet. Existing options will be extended/overwritten.
  11032. * @param {Object} [options] The following options are available:
  11033. * {String} type
  11034. * Default type for the items. Choose from 'box'
  11035. * (default), 'point', 'range', or 'background'.
  11036. * The default style can be overwritten by
  11037. * individual items.
  11038. * {String} align
  11039. * Alignment for the items, only applicable for
  11040. * BoxItem. Choose 'center' (default), 'left', or
  11041. * 'right'.
  11042. * {String} orientation
  11043. * Orientation of the item set. Choose 'top' or
  11044. * 'bottom' (default).
  11045. * {Function} groupOrder
  11046. * A sorting function for ordering groups
  11047. * {Boolean} stack
  11048. * If true (deafult), items will be stacked on
  11049. * top of each other.
  11050. * {Number} margin.axis
  11051. * Margin between the axis and the items in pixels.
  11052. * Default is 20.
  11053. * {Number} margin.item.horizontal
  11054. * Horizontal margin between items in pixels.
  11055. * Default is 10.
  11056. * {Number} margin.item.vertical
  11057. * Vertical Margin between items in pixels.
  11058. * Default is 10.
  11059. * {Number} margin.item
  11060. * Margin between items in pixels in both horizontal
  11061. * and vertical direction. Default is 10.
  11062. * {Number} margin
  11063. * Set margin for both axis and items in pixels.
  11064. * {Number} padding
  11065. * Padding of the contents of an item in pixels.
  11066. * Must correspond with the items css. Default is 5.
  11067. * {Boolean} selectable
  11068. * If true (default), items can be selected.
  11069. * {Boolean} editable
  11070. * Set all editable options to true or false
  11071. * {Boolean} editable.updateTime
  11072. * Allow dragging an item to an other moment in time
  11073. * {Boolean} editable.updateGroup
  11074. * Allow dragging an item to an other group
  11075. * {Boolean} editable.add
  11076. * Allow creating new items on double tap
  11077. * {Boolean} editable.remove
  11078. * Allow removing items by clicking the delete button
  11079. * top right of a selected item.
  11080. * {Function(item: Item, callback: Function)} onAdd
  11081. * Callback function triggered when an item is about to be added:
  11082. * when the user double taps an empty space in the Timeline.
  11083. * {Function(item: Item, callback: Function)} onUpdate
  11084. * Callback function fired when an item is about to be updated.
  11085. * This function typically has to show a dialog where the user
  11086. * change the item. If not implemented, nothing happens.
  11087. * {Function(item: Item, callback: Function)} onMove
  11088. * Fired when an item has been moved. If not implemented,
  11089. * the move action will be accepted.
  11090. * {Function(item: Item, callback: Function)} onRemove
  11091. * Fired when an item is about to be deleted.
  11092. * If not implemented, the item will be always removed.
  11093. */
  11094. ItemSet.prototype.setOptions = function(options) {
  11095. if (options) {
  11096. // copy all options that we know
  11097. var fields = ['type', 'align', 'orientation', 'order', 'padding', 'stack', 'selectable', 'groupOrder', 'dataAttributes', 'template','hide', 'snap'];
  11098. util.selectiveExtend(fields, this.options, options);
  11099. if ('margin' in options) {
  11100. if (typeof options.margin === 'number') {
  11101. this.options.margin.axis = options.margin;
  11102. this.options.margin.item.horizontal = options.margin;
  11103. this.options.margin.item.vertical = options.margin;
  11104. }
  11105. else if (typeof options.margin === 'object') {
  11106. util.selectiveExtend(['axis'], this.options.margin, options.margin);
  11107. if ('item' in options.margin) {
  11108. if (typeof options.margin.item === 'number') {
  11109. this.options.margin.item.horizontal = options.margin.item;
  11110. this.options.margin.item.vertical = options.margin.item;
  11111. }
  11112. else if (typeof options.margin.item === 'object') {
  11113. util.selectiveExtend(['horizontal', 'vertical'], this.options.margin.item, options.margin.item);
  11114. }
  11115. }
  11116. }
  11117. }
  11118. if ('editable' in options) {
  11119. if (typeof options.editable === 'boolean') {
  11120. this.options.editable.updateTime = options.editable;
  11121. this.options.editable.updateGroup = options.editable;
  11122. this.options.editable.add = options.editable;
  11123. this.options.editable.remove = options.editable;
  11124. }
  11125. else if (typeof options.editable === 'object') {
  11126. util.selectiveExtend(['updateTime', 'updateGroup', 'add', 'remove'], this.options.editable, options.editable);
  11127. }
  11128. }
  11129. // callback functions
  11130. var addCallback = (function (name) {
  11131. var fn = options[name];
  11132. if (fn) {
  11133. if (!(fn instanceof Function)) {
  11134. throw new Error('option ' + name + ' must be a function ' + name + '(item, callback)');
  11135. }
  11136. this.options[name] = fn;
  11137. }
  11138. }).bind(this);
  11139. ['onAdd', 'onUpdate', 'onRemove', 'onMove', 'onMoving'].forEach(addCallback);
  11140. // force the itemSet to refresh: options like orientation and margins may be changed
  11141. this.markDirty();
  11142. }
  11143. };
  11144. /**
  11145. * Mark the ItemSet dirty so it will refresh everything with next redraw.
  11146. * Optionally, all items can be marked as dirty and be refreshed.
  11147. * @param {{refreshItems: boolean}} [options]
  11148. */
  11149. ItemSet.prototype.markDirty = function(options) {
  11150. this.groupIds = [];
  11151. this.stackDirty = true;
  11152. if (options && options.refreshItems) {
  11153. util.forEach(this.items, function (item) {
  11154. item.dirty = true;
  11155. if (item.displayed) item.redraw();
  11156. });
  11157. }
  11158. };
  11159. /**
  11160. * Destroy the ItemSet
  11161. */
  11162. ItemSet.prototype.destroy = function() {
  11163. this.hide();
  11164. this.setItems(null);
  11165. this.setGroups(null);
  11166. this.hammer = null;
  11167. this.body = null;
  11168. this.conversion = null;
  11169. };
  11170. /**
  11171. * Hide the component from the DOM
  11172. */
  11173. ItemSet.prototype.hide = function() {
  11174. // remove the frame containing the items
  11175. if (this.dom.frame.parentNode) {
  11176. this.dom.frame.parentNode.removeChild(this.dom.frame);
  11177. }
  11178. // remove the axis with dots
  11179. if (this.dom.axis.parentNode) {
  11180. this.dom.axis.parentNode.removeChild(this.dom.axis);
  11181. }
  11182. // remove the labelset containing all group labels
  11183. if (this.dom.labelSet.parentNode) {
  11184. this.dom.labelSet.parentNode.removeChild(this.dom.labelSet);
  11185. }
  11186. };
  11187. /**
  11188. * Show the component in the DOM (when not already visible).
  11189. * @return {Boolean} changed
  11190. */
  11191. ItemSet.prototype.show = function() {
  11192. // show frame containing the items
  11193. if (!this.dom.frame.parentNode) {
  11194. this.body.dom.center.appendChild(this.dom.frame);
  11195. }
  11196. // show axis with dots
  11197. if (!this.dom.axis.parentNode) {
  11198. this.body.dom.backgroundVertical.appendChild(this.dom.axis);
  11199. }
  11200. // show labelset containing labels
  11201. if (!this.dom.labelSet.parentNode) {
  11202. this.body.dom.left.appendChild(this.dom.labelSet);
  11203. }
  11204. };
  11205. /**
  11206. * Set selected items by their id. Replaces the current selection
  11207. * Unknown id's are silently ignored.
  11208. * @param {string[] | string} [ids] An array with zero or more id's of the items to be
  11209. * selected, or a single item id. If ids is undefined
  11210. * or an empty array, all items will be unselected.
  11211. */
  11212. ItemSet.prototype.setSelection = function(ids) {
  11213. var i, ii, id, item;
  11214. if (ids == undefined) ids = [];
  11215. if (!Array.isArray(ids)) ids = [ids];
  11216. // unselect currently selected items
  11217. for (i = 0, ii = this.selection.length; i < ii; i++) {
  11218. id = this.selection[i];
  11219. item = this.items[id];
  11220. if (item) item.unselect();
  11221. }
  11222. // select items
  11223. this.selection = [];
  11224. for (i = 0, ii = ids.length; i < ii; i++) {
  11225. id = ids[i];
  11226. item = this.items[id];
  11227. if (item) {
  11228. this.selection.push(id);
  11229. item.select();
  11230. }
  11231. }
  11232. };
  11233. /**
  11234. * Get the selected items by their id
  11235. * @return {Array} ids The ids of the selected items
  11236. */
  11237. ItemSet.prototype.getSelection = function() {
  11238. return this.selection.concat([]);
  11239. };
  11240. /**
  11241. * Get the id's of the currently visible items.
  11242. * @returns {Array} The ids of the visible items
  11243. */
  11244. ItemSet.prototype.getVisibleItems = function() {
  11245. var range = this.body.range.getRange();
  11246. var left = this.body.util.toScreen(range.start);
  11247. var right = this.body.util.toScreen(range.end);
  11248. var ids = [];
  11249. for (var groupId in this.groups) {
  11250. if (this.groups.hasOwnProperty(groupId)) {
  11251. var group = this.groups[groupId];
  11252. var rawVisibleItems = group.visibleItems;
  11253. // filter the "raw" set with visibleItems into a set which is really
  11254. // visible by pixels
  11255. for (var i = 0; i < rawVisibleItems.length; i++) {
  11256. var item = rawVisibleItems[i];
  11257. // TODO: also check whether visible vertically
  11258. if ((item.left < right) && (item.left + item.width > left)) {
  11259. ids.push(item.id);
  11260. }
  11261. }
  11262. }
  11263. }
  11264. return ids;
  11265. };
  11266. /**
  11267. * Deselect a selected item
  11268. * @param {String | Number} id
  11269. * @private
  11270. */
  11271. ItemSet.prototype._deselect = function(id) {
  11272. var selection = this.selection;
  11273. for (var i = 0, ii = selection.length; i < ii; i++) {
  11274. if (selection[i] == id) { // non-strict comparison!
  11275. selection.splice(i, 1);
  11276. break;
  11277. }
  11278. }
  11279. };
  11280. /**
  11281. * Repaint the component
  11282. * @return {boolean} Returns true if the component is resized
  11283. */
  11284. ItemSet.prototype.redraw = function() {
  11285. var margin = this.options.margin,
  11286. range = this.body.range,
  11287. asSize = util.option.asSize,
  11288. options = this.options,
  11289. orientation = options.orientation,
  11290. resized = false,
  11291. frame = this.dom.frame,
  11292. editable = options.editable.updateTime || options.editable.updateGroup;
  11293. // recalculate absolute position (before redrawing groups)
  11294. this.props.top = this.body.domProps.top.height + this.body.domProps.border.top;
  11295. this.props.left = this.body.domProps.left.width + this.body.domProps.border.left;
  11296. // update class name
  11297. frame.className = 'itemset' + (editable ? ' editable' : '');
  11298. // reorder the groups (if needed)
  11299. resized = this._orderGroups() || resized;
  11300. // check whether zoomed (in that case we need to re-stack everything)
  11301. // TODO: would be nicer to get this as a trigger from Range
  11302. var visibleInterval = range.end - range.start;
  11303. var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.props.width != this.props.lastWidth);
  11304. if (zoomed) this.stackDirty = true;
  11305. this.lastVisibleInterval = visibleInterval;
  11306. this.props.lastWidth = this.props.width;
  11307. var restack = this.stackDirty;
  11308. var firstGroup = this._firstGroup();
  11309. var firstMargin = {
  11310. item: margin.item,
  11311. axis: margin.axis
  11312. };
  11313. var nonFirstMargin = {
  11314. item: margin.item,
  11315. axis: margin.item.vertical / 2
  11316. };
  11317. var height = 0;
  11318. var minHeight = margin.axis + margin.item.vertical;
  11319. // redraw the background group
  11320. this.groups[BACKGROUND].redraw(range, nonFirstMargin, restack);
  11321. // redraw all regular groups
  11322. util.forEach(this.groups, function (group) {
  11323. var groupMargin = (group == firstGroup) ? firstMargin : nonFirstMargin;
  11324. var groupResized = group.redraw(range, groupMargin, restack);
  11325. resized = groupResized || resized;
  11326. height += group.height;
  11327. });
  11328. height = Math.max(height, minHeight);
  11329. this.stackDirty = false;
  11330. // update frame height
  11331. frame.style.height = asSize(height);
  11332. // calculate actual size
  11333. this.props.width = frame.offsetWidth;
  11334. this.props.height = height;
  11335. // reposition axis
  11336. this.dom.axis.style.top = asSize((orientation == 'top') ?
  11337. (this.body.domProps.top.height + this.body.domProps.border.top) :
  11338. (this.body.domProps.top.height + this.body.domProps.centerContainer.height));
  11339. this.dom.axis.style.left = '0';
  11340. // check if this component is resized
  11341. resized = this._isResized() || resized;
  11342. return resized;
  11343. };
  11344. /**
  11345. * Get the first group, aligned with the axis
  11346. * @return {Group | null} firstGroup
  11347. * @private
  11348. */
  11349. ItemSet.prototype._firstGroup = function() {
  11350. var firstGroupIndex = (this.options.orientation == 'top') ? 0 : (this.groupIds.length - 1);
  11351. var firstGroupId = this.groupIds[firstGroupIndex];
  11352. var firstGroup = this.groups[firstGroupId] || this.groups[UNGROUPED];
  11353. return firstGroup || null;
  11354. };
  11355. /**
  11356. * Create or delete the group holding all ungrouped items. This group is used when
  11357. * there are no groups specified.
  11358. * @protected
  11359. */
  11360. ItemSet.prototype._updateUngrouped = function() {
  11361. var ungrouped = this.groups[UNGROUPED];
  11362. var background = this.groups[BACKGROUND];
  11363. var item, itemId;
  11364. if (this.groupsData) {
  11365. // remove the group holding all ungrouped items
  11366. if (ungrouped) {
  11367. ungrouped.hide();
  11368. delete this.groups[UNGROUPED];
  11369. for (itemId in this.items) {
  11370. if (this.items.hasOwnProperty(itemId)) {
  11371. item = this.items[itemId];
  11372. item.parent && item.parent.remove(item);
  11373. var groupId = this._getGroupId(item.data);
  11374. var group = this.groups[groupId];
  11375. group && group.add(item) || item.hide();
  11376. }
  11377. }
  11378. }
  11379. }
  11380. else {
  11381. // create a group holding all (unfiltered) items
  11382. if (!ungrouped) {
  11383. var id = null;
  11384. var data = null;
  11385. ungrouped = new Group(id, data, this);
  11386. this.groups[UNGROUPED] = ungrouped;
  11387. for (itemId in this.items) {
  11388. if (this.items.hasOwnProperty(itemId)) {
  11389. item = this.items[itemId];
  11390. ungrouped.add(item);
  11391. }
  11392. }
  11393. ungrouped.show();
  11394. }
  11395. }
  11396. };
  11397. /**
  11398. * Get the element for the labelset
  11399. * @return {HTMLElement} labelSet
  11400. */
  11401. ItemSet.prototype.getLabelSet = function() {
  11402. return this.dom.labelSet;
  11403. };
  11404. /**
  11405. * Set items
  11406. * @param {vis.DataSet | null} items
  11407. */
  11408. ItemSet.prototype.setItems = function(items) {
  11409. var me = this,
  11410. ids,
  11411. oldItemsData = this.itemsData;
  11412. // replace the dataset
  11413. if (!items) {
  11414. this.itemsData = null;
  11415. }
  11416. else if (items instanceof DataSet || items instanceof DataView) {
  11417. this.itemsData = items;
  11418. }
  11419. else {
  11420. throw new TypeError('Data must be an instance of DataSet or DataView');
  11421. }
  11422. if (oldItemsData) {
  11423. // unsubscribe from old dataset
  11424. util.forEach(this.itemListeners, function (callback, event) {
  11425. oldItemsData.off(event, callback);
  11426. });
  11427. // remove all drawn items
  11428. ids = oldItemsData.getIds();
  11429. this._onRemove(ids);
  11430. }
  11431. if (this.itemsData) {
  11432. // subscribe to new dataset
  11433. var id = this.id;
  11434. util.forEach(this.itemListeners, function (callback, event) {
  11435. me.itemsData.on(event, callback, id);
  11436. });
  11437. // add all new items
  11438. ids = this.itemsData.getIds();
  11439. this._onAdd(ids);
  11440. // update the group holding all ungrouped items
  11441. this._updateUngrouped();
  11442. }
  11443. };
  11444. /**
  11445. * Get the current items
  11446. * @returns {vis.DataSet | null}
  11447. */
  11448. ItemSet.prototype.getItems = function() {
  11449. return this.itemsData;
  11450. };
  11451. /**
  11452. * Set groups
  11453. * @param {vis.DataSet} groups
  11454. */
  11455. ItemSet.prototype.setGroups = function(groups) {
  11456. var me = this,
  11457. ids;
  11458. // unsubscribe from current dataset
  11459. if (this.groupsData) {
  11460. util.forEach(this.groupListeners, function (callback, event) {
  11461. me.groupsData.unsubscribe(event, callback);
  11462. });
  11463. // remove all drawn groups
  11464. ids = this.groupsData.getIds();
  11465. this.groupsData = null;
  11466. this._onRemoveGroups(ids); // note: this will cause a redraw
  11467. }
  11468. // replace the dataset
  11469. if (!groups) {
  11470. this.groupsData = null;
  11471. }
  11472. else if (groups instanceof DataSet || groups instanceof DataView) {
  11473. this.groupsData = groups;
  11474. }
  11475. else {
  11476. throw new TypeError('Data must be an instance of DataSet or DataView');
  11477. }
  11478. if (this.groupsData) {
  11479. // subscribe to new dataset
  11480. var id = this.id;
  11481. util.forEach(this.groupListeners, function (callback, event) {
  11482. me.groupsData.on(event, callback, id);
  11483. });
  11484. // draw all ms
  11485. ids = this.groupsData.getIds();
  11486. this._onAddGroups(ids);
  11487. }
  11488. // update the group holding all ungrouped items
  11489. this._updateUngrouped();
  11490. // update the order of all items in each group
  11491. this._order();
  11492. this.body.emitter.emit('change', {queue: true});
  11493. };
  11494. /**
  11495. * Get the current groups
  11496. * @returns {vis.DataSet | null} groups
  11497. */
  11498. ItemSet.prototype.getGroups = function() {
  11499. return this.groupsData;
  11500. };
  11501. /**
  11502. * Remove an item by its id
  11503. * @param {String | Number} id
  11504. */
  11505. ItemSet.prototype.removeItem = function(id) {
  11506. var item = this.itemsData.get(id),
  11507. dataset = this.itemsData.getDataSet();
  11508. if (item) {
  11509. // confirm deletion
  11510. this.options.onRemove(item, function (item) {
  11511. if (item) {
  11512. // remove by id here, it is possible that an item has no id defined
  11513. // itself, so better not delete by the item itself
  11514. dataset.remove(id);
  11515. }
  11516. });
  11517. }
  11518. };
  11519. /**
  11520. * Get the time of an item based on it's data and options.type
  11521. * @param {Object} itemData
  11522. * @returns {string} Returns the type
  11523. * @private
  11524. */
  11525. ItemSet.prototype._getType = function (itemData) {
  11526. return itemData.type || this.options.type || (itemData.end ? 'range' : 'box');
  11527. };
  11528. /**
  11529. * Get the group id for an item
  11530. * @param {Object} itemData
  11531. * @returns {string} Returns the groupId
  11532. * @private
  11533. */
  11534. ItemSet.prototype._getGroupId = function (itemData) {
  11535. var type = this._getType(itemData);
  11536. if (type == 'background' && itemData.group == undefined) {
  11537. return BACKGROUND;
  11538. }
  11539. else {
  11540. return this.groupsData ? itemData.group : UNGROUPED;
  11541. }
  11542. };
  11543. /**
  11544. * Handle updated items
  11545. * @param {Number[]} ids
  11546. * @protected
  11547. */
  11548. ItemSet.prototype._onUpdate = function(ids) {
  11549. var me = this;
  11550. ids.forEach(function (id) {
  11551. var itemData = me.itemsData.get(id, me.itemOptions);
  11552. var item = me.items[id];
  11553. var type = me._getType(itemData);
  11554. var constructor = ItemSet.types[type];
  11555. if (item) {
  11556. // update item
  11557. if (!constructor || !(item instanceof constructor)) {
  11558. // item type has changed, delete the item and recreate it
  11559. me._removeItem(item);
  11560. item = null;
  11561. }
  11562. else {
  11563. me._updateItem(item, itemData);
  11564. }
  11565. }
  11566. if (!item) {
  11567. // create item
  11568. if (constructor) {
  11569. item = new constructor(itemData, me.conversion, me.options);
  11570. item.id = id; // TODO: not so nice setting id afterwards
  11571. me._addItem(item);
  11572. }
  11573. else if (type == 'rangeoverflow') {
  11574. // TODO: deprecated since version 2.1.0 (or 3.0.0?). cleanup some day
  11575. throw new TypeError('Item type "rangeoverflow" is deprecated. Use css styling instead: ' +
  11576. '.vis.timeline .item.range .content {overflow: visible;}');
  11577. }
  11578. else {
  11579. throw new TypeError('Unknown item type "' + type + '"');
  11580. }
  11581. }
  11582. });
  11583. this._order();
  11584. this.stackDirty = true; // force re-stacking of all items next redraw
  11585. this.body.emitter.emit('change', {queue: true});
  11586. };
  11587. /**
  11588. * Handle added items
  11589. * @param {Number[]} ids
  11590. * @protected
  11591. */
  11592. ItemSet.prototype._onAdd = ItemSet.prototype._onUpdate;
  11593. /**
  11594. * Handle removed items
  11595. * @param {Number[]} ids
  11596. * @protected
  11597. */
  11598. ItemSet.prototype._onRemove = function(ids) {
  11599. var count = 0;
  11600. var me = this;
  11601. ids.forEach(function (id) {
  11602. var item = me.items[id];
  11603. if (item) {
  11604. count++;
  11605. me._removeItem(item);
  11606. }
  11607. });
  11608. if (count) {
  11609. // update order
  11610. this._order();
  11611. this.stackDirty = true; // force re-stacking of all items next redraw
  11612. this.body.emitter.emit('change', {queue: true});
  11613. }
  11614. };
  11615. /**
  11616. * Update the order of item in all groups
  11617. * @private
  11618. */
  11619. ItemSet.prototype._order = function() {
  11620. // reorder the items in all groups
  11621. // TODO: optimization: only reorder groups affected by the changed items
  11622. util.forEach(this.groups, function (group) {
  11623. group.order();
  11624. });
  11625. };
  11626. /**
  11627. * Handle updated groups
  11628. * @param {Number[]} ids
  11629. * @private
  11630. */
  11631. ItemSet.prototype._onUpdateGroups = function(ids) {
  11632. this._onAddGroups(ids);
  11633. };
  11634. /**
  11635. * Handle changed groups (added or updated)
  11636. * @param {Number[]} ids
  11637. * @private
  11638. */
  11639. ItemSet.prototype._onAddGroups = function(ids) {
  11640. var me = this;
  11641. ids.forEach(function (id) {
  11642. var groupData = me.groupsData.get(id);
  11643. var group = me.groups[id];
  11644. if (!group) {
  11645. // check for reserved ids
  11646. if (id == UNGROUPED || id == BACKGROUND) {
  11647. throw new Error('Illegal group id. ' + id + ' is a reserved id.');
  11648. }
  11649. var groupOptions = Object.create(me.options);
  11650. util.extend(groupOptions, {
  11651. height: null
  11652. });
  11653. group = new Group(id, groupData, me);
  11654. me.groups[id] = group;
  11655. // add items with this groupId to the new group
  11656. for (var itemId in me.items) {
  11657. if (me.items.hasOwnProperty(itemId)) {
  11658. var item = me.items[itemId];
  11659. if (item.data.group == id) {
  11660. group.add(item);
  11661. }
  11662. }
  11663. }
  11664. group.order();
  11665. group.show();
  11666. }
  11667. else {
  11668. // update group
  11669. group.setData(groupData);
  11670. }
  11671. });
  11672. this.body.emitter.emit('change', {queue: true});
  11673. };
  11674. /**
  11675. * Handle removed groups
  11676. * @param {Number[]} ids
  11677. * @private
  11678. */
  11679. ItemSet.prototype._onRemoveGroups = function(ids) {
  11680. var groups = this.groups;
  11681. ids.forEach(function (id) {
  11682. var group = groups[id];
  11683. if (group) {
  11684. group.hide();
  11685. delete groups[id];
  11686. }
  11687. });
  11688. this.markDirty();
  11689. this.body.emitter.emit('change', {queue: true});
  11690. };
  11691. /**
  11692. * Reorder the groups if needed
  11693. * @return {boolean} changed
  11694. * @private
  11695. */
  11696. ItemSet.prototype._orderGroups = function () {
  11697. if (this.groupsData) {
  11698. // reorder the groups
  11699. var groupIds = this.groupsData.getIds({
  11700. order: this.options.groupOrder
  11701. });
  11702. var changed = !util.equalArray(groupIds, this.groupIds);
  11703. if (changed) {
  11704. // hide all groups, removes them from the DOM
  11705. var groups = this.groups;
  11706. groupIds.forEach(function (groupId) {
  11707. groups[groupId].hide();
  11708. });
  11709. // show the groups again, attach them to the DOM in correct order
  11710. groupIds.forEach(function (groupId) {
  11711. groups[groupId].show();
  11712. });
  11713. this.groupIds = groupIds;
  11714. }
  11715. return changed;
  11716. }
  11717. else {
  11718. return false;
  11719. }
  11720. };
  11721. /**
  11722. * Add a new item
  11723. * @param {Item} item
  11724. * @private
  11725. */
  11726. ItemSet.prototype._addItem = function(item) {
  11727. this.items[item.id] = item;
  11728. // add to group
  11729. var groupId = this._getGroupId(item.data);
  11730. var group = this.groups[groupId];
  11731. if (group) group.add(item);
  11732. };
  11733. /**
  11734. * Update an existing item
  11735. * @param {Item} item
  11736. * @param {Object} itemData
  11737. * @private
  11738. */
  11739. ItemSet.prototype._updateItem = function(item, itemData) {
  11740. var oldGroupId = item.data.group;
  11741. // update the items data (will redraw the item when displayed)
  11742. item.setData(itemData);
  11743. // update group
  11744. if (oldGroupId != item.data.group) {
  11745. var oldGroup = this.groups[oldGroupId];
  11746. if (oldGroup) oldGroup.remove(item);
  11747. var groupId = this._getGroupId(item.data);
  11748. var group = this.groups[groupId];
  11749. if (group) group.add(item);
  11750. }
  11751. };
  11752. /**
  11753. * Delete an item from the ItemSet: remove it from the DOM, from the map
  11754. * with items, and from the map with visible items, and from the selection
  11755. * @param {Item} item
  11756. * @private
  11757. */
  11758. ItemSet.prototype._removeItem = function(item) {
  11759. // remove from DOM
  11760. item.hide();
  11761. // remove from items
  11762. delete this.items[item.id];
  11763. // remove from selection
  11764. var index = this.selection.indexOf(item.id);
  11765. if (index != -1) this.selection.splice(index, 1);
  11766. // remove from group
  11767. item.parent && item.parent.remove(item);
  11768. };
  11769. /**
  11770. * Create an array containing all items being a range (having an end date)
  11771. * @param array
  11772. * @returns {Array}
  11773. * @private
  11774. */
  11775. ItemSet.prototype._constructByEndArray = function(array) {
  11776. var endArray = [];
  11777. for (var i = 0; i < array.length; i++) {
  11778. if (array[i] instanceof RangeItem) {
  11779. endArray.push(array[i]);
  11780. }
  11781. }
  11782. return endArray;
  11783. };
  11784. /**
  11785. * Register the clicked item on touch, before dragStart is initiated.
  11786. *
  11787. * dragStart is initiated from a mousemove event, which can have left the item
  11788. * already resulting in an item == null
  11789. *
  11790. * @param {Event} event
  11791. * @private
  11792. */
  11793. ItemSet.prototype._onTouch = function (event) {
  11794. // store the touched item, used in _onDragStart
  11795. this.touchParams.item = this.itemFromTarget(event);
  11796. };
  11797. /**
  11798. * Start dragging the selected events
  11799. * @param {Event} event
  11800. * @private
  11801. */
  11802. ItemSet.prototype._onDragStart = function (event) {
  11803. if (!this.options.editable.updateTime && !this.options.editable.updateGroup) {
  11804. return;
  11805. }
  11806. var item = this.touchParams.item || null;
  11807. var me = this;
  11808. var props;
  11809. if (item && item.selected) {
  11810. var dragLeftItem = event.target.dragLeftItem;
  11811. var dragRightItem = event.target.dragRightItem;
  11812. if (dragLeftItem) {
  11813. props = {
  11814. item: dragLeftItem,
  11815. initialX: event.gesture.center.clientX
  11816. };
  11817. if (me.options.editable.updateTime) {
  11818. props.start = item.data.start.valueOf();
  11819. }
  11820. if (me.options.editable.updateGroup) {
  11821. if ('group' in item.data) props.group = item.data.group;
  11822. }
  11823. this.touchParams.itemProps = [props];
  11824. }
  11825. else if (dragRightItem) {
  11826. props = {
  11827. item: dragRightItem,
  11828. initialX: event.gesture.center.clientX
  11829. };
  11830. if (me.options.editable.updateTime) {
  11831. props.end = item.data.end.valueOf();
  11832. }
  11833. if (me.options.editable.updateGroup) {
  11834. if ('group' in item.data) props.group = item.data.group;
  11835. }
  11836. this.touchParams.itemProps = [props];
  11837. }
  11838. else {
  11839. this.touchParams.itemProps = this.getSelection().map(function (id) {
  11840. var item = me.items[id];
  11841. var props = {
  11842. item: item,
  11843. initialX: event.gesture.center.clientX
  11844. };
  11845. if (me.options.editable.updateTime) {
  11846. if ('start' in item.data) {
  11847. props.start = item.data.start.valueOf();
  11848. if ('end' in item.data) {
  11849. // we store a duration here in order not to change the width
  11850. // of the item when moving it.
  11851. props.duration = item.data.end.valueOf() - props.start;
  11852. }
  11853. }
  11854. }
  11855. if (me.options.editable.updateGroup) {
  11856. if ('group' in item.data) props.group = item.data.group;
  11857. }
  11858. return props;
  11859. });
  11860. }
  11861. event.stopPropagation();
  11862. }
  11863. else if (this.options.editable.add && event.gesture.srcEvent.ctrlKey) {
  11864. // create a new range item when dragging with ctrl key down
  11865. this._onDragStartAddItem(event);
  11866. }
  11867. };
  11868. /**
  11869. * Start creating a new range item by dragging.
  11870. * @param {Event} event
  11871. * @private
  11872. */
  11873. ItemSet.prototype._onDragStartAddItem = function (event) {
  11874. var snap = this.options.snap || null;
  11875. var xAbs = util.getAbsoluteLeft(this.dom.frame);
  11876. var x = event.gesture.center.pageX - xAbs - 10; // minus 10 to compensate for the drag starting as soon as you've moved 10px
  11877. var time = this.body.util.toTime(x);
  11878. var scale = this.body.util.getScale();
  11879. var step = this.body.util.getStep();
  11880. var start = snap ? snap(time, scale, step) : start;
  11881. var end = start;
  11882. var itemData = {
  11883. type: 'range',
  11884. start: start,
  11885. end: end,
  11886. content: 'new item'
  11887. };
  11888. var id = util.randomUUID();
  11889. itemData[this.itemsData._fieldId] = id;
  11890. var group = this.groupFromTarget(event);
  11891. if (group) {
  11892. itemData.group = group.groupId;
  11893. }
  11894. var newItem = new RangeItem(itemData, this.conversion, this.options);
  11895. newItem.id = id; // TODO: not so nice setting id afterwards
  11896. this._addItem(newItem);
  11897. var props = {
  11898. item: newItem,
  11899. end: end.valueOf(),
  11900. initialX: event.gesture.center.clientX
  11901. };
  11902. this.touchParams.itemProps = [props];
  11903. event.stopPropagation();
  11904. };
  11905. /**
  11906. * Drag selected items
  11907. * @param {Event} event
  11908. * @private
  11909. */
  11910. ItemSet.prototype._onDrag = function (event) {
  11911. event.preventDefault();
  11912. if (this.touchParams.itemProps) {
  11913. var me = this;
  11914. var snap = this.options.snap || null;
  11915. var xOffset = this.body.dom.root.offsetLeft + this.body.domProps.left.width;
  11916. var scale = this.body.util.getScale();
  11917. var step = this.body.util.getStep();
  11918. // move
  11919. this.touchParams.itemProps.forEach(function (props) {
  11920. var newProps = {};
  11921. var current = me.body.util.toTime(event.gesture.center.clientX - xOffset);
  11922. var initial = me.body.util.toTime(props.initialX - xOffset);
  11923. var offset = current - initial;
  11924. if ('start' in props) {
  11925. var start = new Date(props.start + offset);
  11926. newProps.start = snap ? snap(start, scale, step) : start;
  11927. }
  11928. if ('end' in props) {
  11929. var end = new Date(props.end + offset);
  11930. newProps.end = snap ? snap(end, scale, step) : end;
  11931. }
  11932. else if ('duration' in props) {
  11933. newProps.end = new Date(newProps.start.valueOf() + props.duration);
  11934. }
  11935. if ('group' in props) {
  11936. // drag from one group to another
  11937. var group = me.groupFromTarget(event);
  11938. newProps.group = group && group.groupId;
  11939. }
  11940. // confirm moving the item
  11941. var itemData = util.extend({}, props.item.data, newProps);
  11942. me.options.onMoving(itemData, function (itemData) {
  11943. if (itemData) {
  11944. me._updateItemProps(props.item, itemData);
  11945. }
  11946. });
  11947. });
  11948. this.stackDirty = true; // force re-stacking of all items next redraw
  11949. this.body.emitter.emit('change');
  11950. event.stopPropagation();
  11951. }
  11952. };
  11953. /**
  11954. * Update an items properties
  11955. * @param {Item} item
  11956. * @param {Object} props Can contain properties start, end, and group.
  11957. * @private
  11958. */
  11959. ItemSet.prototype._updateItemProps = function(item, props) {
  11960. // TODO: copy all properties from props to item? (also new ones)
  11961. if ('start' in props) {
  11962. item.data.start = props.start;
  11963. }
  11964. if ('end' in props) {
  11965. item.data.end = props.end;
  11966. }
  11967. else if ('duration' in props) {
  11968. item.data.end = new Date(props.start.valueOf() + props.duration);
  11969. }
  11970. if ('group' in props && item.data.group != props.group) {
  11971. this._moveToGroup(item, props.group)
  11972. }
  11973. };
  11974. /**
  11975. * Move an item to another group
  11976. * @param {Item} item
  11977. * @param {String | Number} groupId
  11978. * @private
  11979. */
  11980. ItemSet.prototype._moveToGroup = function(item, groupId) {
  11981. var group = this.groups[groupId];
  11982. if (group && group.groupId != item.data.group) {
  11983. var oldGroup = item.parent;
  11984. oldGroup.remove(item);
  11985. oldGroup.order();
  11986. group.add(item);
  11987. group.order();
  11988. item.data.group = group.groupId;
  11989. }
  11990. };
  11991. /**
  11992. * End of dragging selected items
  11993. * @param {Event} event
  11994. * @private
  11995. */
  11996. ItemSet.prototype._onDragEnd = function (event) {
  11997. event.preventDefault()
  11998. if (this.touchParams.itemProps) {
  11999. // prepare a change set for the changed items
  12000. var changes = [];
  12001. var me = this;
  12002. var dataset = this.itemsData.getDataSet();
  12003. var itemProps = this.touchParams.itemProps ;
  12004. this.touchParams.itemProps = null;
  12005. itemProps.forEach(function (props) {
  12006. var id = props.item.id;
  12007. var itemData = me.itemsData.get(id, me.itemOptions);
  12008. if (!itemData) {
  12009. // add a new item
  12010. me.options.onAdd(props.item.data, function (itemData) {
  12011. me._removeItem(props.item); // remove temporary item
  12012. if (itemData) {
  12013. me.itemsData.getDataSet().add(itemData);
  12014. }
  12015. // force re-stacking of all items next redraw
  12016. me.stackDirty = true;
  12017. me.body.emitter.emit('change');
  12018. });
  12019. }
  12020. else {
  12021. // update existing item
  12022. var changed = false;
  12023. if ('start' in props.item.data) {
  12024. changed = (props.start != props.item.data.start.valueOf());
  12025. itemData.start = util.convert(props.item.data.start,
  12026. dataset._options.type && dataset._options.type.start || 'Date');
  12027. }
  12028. if ('end' in props.item.data) {
  12029. changed = changed || (props.end != props.item.data.end.valueOf());
  12030. itemData.end = util.convert(props.item.data.end,
  12031. dataset._options.type && dataset._options.type.end || 'Date');
  12032. }
  12033. if ('group' in props.item.data) {
  12034. changed = changed || (props.group != props.item.data.group);
  12035. itemData.group = props.item.data.group;
  12036. }
  12037. // only apply changes when start or end is actually changed
  12038. if (changed) {
  12039. me.options.onMove(itemData, function (itemData) {
  12040. if (itemData) {
  12041. // apply changes
  12042. itemData[dataset._fieldId] = id; // ensure the item contains its id (can be undefined)
  12043. changes.push(itemData);
  12044. }
  12045. else {
  12046. // restore original values
  12047. me._updateItemProps(props.item, props);
  12048. me.stackDirty = true; // force re-stacking of all items next redraw
  12049. me.body.emitter.emit('change');
  12050. }
  12051. });
  12052. }
  12053. }
  12054. });
  12055. // apply the changes to the data (if there are changes)
  12056. if (changes.length) {
  12057. dataset.update(changes);
  12058. }
  12059. event.stopPropagation();
  12060. }
  12061. };
  12062. /**
  12063. * Handle selecting/deselecting an item when tapping it
  12064. * @param {Event} event
  12065. * @private
  12066. */
  12067. ItemSet.prototype._onSelectItem = function (event) {
  12068. if (!this.options.selectable) return;
  12069. var ctrlKey = event.gesture.srcEvent && event.gesture.srcEvent.ctrlKey;
  12070. var shiftKey = event.gesture.srcEvent && event.gesture.srcEvent.shiftKey;
  12071. if (ctrlKey || shiftKey) {
  12072. this._onMultiSelectItem(event);
  12073. return;
  12074. }
  12075. var oldSelection = this.getSelection();
  12076. var item = this.itemFromTarget(event);
  12077. var selection = item ? [item.id] : [];
  12078. this.setSelection(selection);
  12079. var newSelection = this.getSelection();
  12080. // emit a select event,
  12081. // except when old selection is empty and new selection is still empty
  12082. if (newSelection.length > 0 || oldSelection.length > 0) {
  12083. this.body.emitter.emit('select', {
  12084. items: newSelection
  12085. });
  12086. }
  12087. };
  12088. /**
  12089. * Handle creation and updates of an item on double tap
  12090. * @param event
  12091. * @private
  12092. */
  12093. ItemSet.prototype._onAddItem = function (event) {
  12094. if (!this.options.selectable) return;
  12095. if (!this.options.editable.add) return;
  12096. var me = this,
  12097. snap = this.options.snap || null,
  12098. item = this.itemFromTarget(event);
  12099. if (item) {
  12100. // update item
  12101. // execute async handler to update the item (or cancel it)
  12102. var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset
  12103. this.options.onUpdate(itemData, function (itemData) {
  12104. if (itemData) {
  12105. me.itemsData.getDataSet().update(itemData);
  12106. }
  12107. });
  12108. }
  12109. else {
  12110. // add item
  12111. var xAbs = util.getAbsoluteLeft(this.dom.frame);
  12112. var x = event.gesture.center.pageX - xAbs;
  12113. var start = this.body.util.toTime(x);
  12114. var scale = this.body.util.getScale();
  12115. var step = this.body.util.getStep();
  12116. var newItem = {
  12117. start: snap ? snap(start, scale, step) : start,
  12118. content: 'new item'
  12119. };
  12120. // when default type is a range, add a default end date to the new item
  12121. if (this.options.type === 'range') {
  12122. var end = this.body.util.toTime(x + this.props.width / 5);
  12123. newItem.end = snap ? snap(end, scale, step) : end;
  12124. }
  12125. newItem[this.itemsData._fieldId] = util.randomUUID();
  12126. var group = this.groupFromTarget(event);
  12127. if (group) {
  12128. newItem.group = group.groupId;
  12129. }
  12130. // execute async handler to customize (or cancel) adding an item
  12131. this.options.onAdd(newItem, function (item) {
  12132. if (item) {
  12133. me.itemsData.getDataSet().add(item);
  12134. // TODO: need to trigger a redraw?
  12135. }
  12136. });
  12137. }
  12138. };
  12139. /**
  12140. * Handle selecting/deselecting multiple items when holding an item
  12141. * @param {Event} event
  12142. * @private
  12143. */
  12144. ItemSet.prototype._onMultiSelectItem = function (event) {
  12145. if (!this.options.selectable) return;
  12146. var selection,
  12147. item = this.itemFromTarget(event);
  12148. if (item) {
  12149. // multi select items
  12150. selection = this.getSelection(); // current selection
  12151. var shiftKey = event.gesture.touches[0] && event.gesture.touches[0].shiftKey || false;
  12152. if (shiftKey) {
  12153. // select all items between the old selection and the tapped item
  12154. // determine the selection range
  12155. selection.push(item.id);
  12156. var range = ItemSet._getItemRange(this.itemsData.get(selection, this.itemOptions));
  12157. // select all items within the selection range
  12158. selection = [];
  12159. for (var id in this.items) {
  12160. if (this.items.hasOwnProperty(id)) {
  12161. var _item = this.items[id];
  12162. var start = _item.data.start;
  12163. var end = (_item.data.end !== undefined) ? _item.data.end : start;
  12164. if (start >= range.min && end <= range.max) {
  12165. selection.push(_item.id); // do not use id but item.id, id itself is stringified
  12166. }
  12167. }
  12168. }
  12169. }
  12170. else {
  12171. // add/remove this item from the current selection
  12172. var index = selection.indexOf(item.id);
  12173. if (index == -1) {
  12174. // item is not yet selected -> select it
  12175. selection.push(item.id);
  12176. }
  12177. else {
  12178. // item is already selected -> deselect it
  12179. selection.splice(index, 1);
  12180. }
  12181. }
  12182. this.setSelection(selection);
  12183. this.body.emitter.emit('select', {
  12184. items: this.getSelection()
  12185. });
  12186. }
  12187. };
  12188. /**
  12189. * Calculate the time range of a list of items
  12190. * @param {Array.<Object>} itemsData
  12191. * @return {{min: Date, max: Date}} Returns the range of the provided items
  12192. * @private
  12193. */
  12194. ItemSet._getItemRange = function(itemsData) {
  12195. var max = null;
  12196. var min = null;
  12197. itemsData.forEach(function (data) {
  12198. if (min == null || data.start < min) {
  12199. min = data.start;
  12200. }
  12201. if (data.end != undefined) {
  12202. if (max == null || data.end > max) {
  12203. max = data.end;
  12204. }
  12205. }
  12206. else {
  12207. if (max == null || data.start > max) {
  12208. max = data.start;
  12209. }
  12210. }
  12211. });
  12212. return {
  12213. min: min,
  12214. max: max
  12215. }
  12216. };
  12217. /**
  12218. * Find an item from an event target:
  12219. * searches for the attribute 'timeline-item' in the event target's element tree
  12220. * @param {Event} event
  12221. * @return {Item | null} item
  12222. */
  12223. ItemSet.prototype.itemFromTarget = function(event) {
  12224. var target = event.target;
  12225. while (target) {
  12226. if (target.hasOwnProperty('timeline-item')) {
  12227. return target['timeline-item'];
  12228. }
  12229. target = target.parentNode;
  12230. }
  12231. return null;
  12232. };
  12233. /**
  12234. * Find the Group from an event target:
  12235. * searches for the attribute 'timeline-group' in the event target's element tree
  12236. * @param {Event} event
  12237. * @return {Group | null} group
  12238. */
  12239. ItemSet.prototype.groupFromTarget = function(event) {
  12240. var clientY = event.gesture ? event.gesture.center.clientY : event.clientY;
  12241. for (var i = 0; i < this.groupIds.length; i++) {
  12242. var groupId = this.groupIds[i];
  12243. var group = this.groups[groupId];
  12244. var foreground = group.dom.foreground;
  12245. var top = util.getAbsoluteTop(foreground);
  12246. if (clientY > top && clientY < top + foreground.offsetHeight) {
  12247. return group;
  12248. }
  12249. if (this.options.orientation === 'top') {
  12250. if (i === this.groupIds.length - 1 && clientY > top) {
  12251. return group;
  12252. }
  12253. }
  12254. else {
  12255. if (i === 0 && clientY < top + foreground.offset) {
  12256. return group;
  12257. }
  12258. }
  12259. }
  12260. return null;
  12261. };
  12262. /**
  12263. * Find the ItemSet from an event target:
  12264. * searches for the attribute 'timeline-itemset' in the event target's element tree
  12265. * @param {Event} event
  12266. * @return {ItemSet | null} item
  12267. */
  12268. ItemSet.itemSetFromTarget = function(event) {
  12269. var target = event.target;
  12270. while (target) {
  12271. if (target.hasOwnProperty('timeline-itemset')) {
  12272. return target['timeline-itemset'];
  12273. }
  12274. target = target.parentNode;
  12275. }
  12276. return null;
  12277. };
  12278. module.exports = ItemSet;
  12279. /***/ },
  12280. /* 33 */
  12281. /***/ function(module, exports, __webpack_require__) {
  12282. var util = __webpack_require__(1);
  12283. var DOMutil = __webpack_require__(2);
  12284. var Component = __webpack_require__(25);
  12285. /**
  12286. * Legend for Graph2d
  12287. */
  12288. function Legend(body, options, side, linegraphOptions) {
  12289. this.body = body;
  12290. this.defaultOptions = {
  12291. enabled: true,
  12292. icons: true,
  12293. iconSize: 20,
  12294. iconSpacing: 6,
  12295. left: {
  12296. visible: true,
  12297. position: 'top-left' // top/bottom - left,center,right
  12298. },
  12299. right: {
  12300. visible: true,
  12301. position: 'top-left' // top/bottom - left,center,right
  12302. }
  12303. }
  12304. this.side = side;
  12305. this.options = util.extend({},this.defaultOptions);
  12306. this.linegraphOptions = linegraphOptions;
  12307. this.svgElements = {};
  12308. this.dom = {};
  12309. this.groups = {};
  12310. this.amountOfGroups = 0;
  12311. this._create();
  12312. this.setOptions(options);
  12313. }
  12314. Legend.prototype = new Component();
  12315. Legend.prototype.clear = function() {
  12316. this.groups = {};
  12317. this.amountOfGroups = 0;
  12318. }
  12319. Legend.prototype.addGroup = function(label, graphOptions) {
  12320. if (!this.groups.hasOwnProperty(label)) {
  12321. this.groups[label] = graphOptions;
  12322. }
  12323. this.amountOfGroups += 1;
  12324. };
  12325. Legend.prototype.updateGroup = function(label, graphOptions) {
  12326. this.groups[label] = graphOptions;
  12327. };
  12328. Legend.prototype.removeGroup = function(label) {
  12329. if (this.groups.hasOwnProperty(label)) {
  12330. delete this.groups[label];
  12331. this.amountOfGroups -= 1;
  12332. }
  12333. };
  12334. Legend.prototype._create = function() {
  12335. this.dom.frame = document.createElement('div');
  12336. this.dom.frame.className = 'legend';
  12337. this.dom.frame.style.position = "absolute";
  12338. this.dom.frame.style.top = "10px";
  12339. this.dom.frame.style.display = "block";
  12340. this.dom.textArea = document.createElement('div');
  12341. this.dom.textArea.className = 'legendText';
  12342. this.dom.textArea.style.position = "relative";
  12343. this.dom.textArea.style.top = "0px";
  12344. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  12345. this.svg.style.position = 'absolute';
  12346. this.svg.style.top = 0 +'px';
  12347. this.svg.style.width = this.options.iconSize + 5 + 'px';
  12348. this.svg.style.height = '100%';
  12349. this.dom.frame.appendChild(this.svg);
  12350. this.dom.frame.appendChild(this.dom.textArea);
  12351. };
  12352. /**
  12353. * Hide the component from the DOM
  12354. */
  12355. Legend.prototype.hide = function() {
  12356. // remove the frame containing the items
  12357. if (this.dom.frame.parentNode) {
  12358. this.dom.frame.parentNode.removeChild(this.dom.frame);
  12359. }
  12360. };
  12361. /**
  12362. * Show the component in the DOM (when not already visible).
  12363. * @return {Boolean} changed
  12364. */
  12365. Legend.prototype.show = function() {
  12366. // show frame containing the items
  12367. if (!this.dom.frame.parentNode) {
  12368. this.body.dom.center.appendChild(this.dom.frame);
  12369. }
  12370. };
  12371. Legend.prototype.setOptions = function(options) {
  12372. var fields = ['enabled','orientation','icons','left','right'];
  12373. util.selectiveDeepExtend(fields, this.options, options);
  12374. };
  12375. Legend.prototype.redraw = function() {
  12376. var activeGroups = 0;
  12377. for (var groupId in this.groups) {
  12378. if (this.groups.hasOwnProperty(groupId)) {
  12379. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  12380. activeGroups++;
  12381. }
  12382. }
  12383. }
  12384. if (this.options[this.side].visible == false || this.amountOfGroups == 0 || this.options.enabled == false || activeGroups == 0) {
  12385. this.hide();
  12386. }
  12387. else {
  12388. this.show();
  12389. if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'bottom-left') {
  12390. this.dom.frame.style.left = '4px';
  12391. this.dom.frame.style.textAlign = "left";
  12392. this.dom.textArea.style.textAlign = "left";
  12393. this.dom.textArea.style.left = (this.options.iconSize + 15) + 'px';
  12394. this.dom.textArea.style.right = '';
  12395. this.svg.style.left = 0 +'px';
  12396. this.svg.style.right = '';
  12397. }
  12398. else {
  12399. this.dom.frame.style.right = '4px';
  12400. this.dom.frame.style.textAlign = "right";
  12401. this.dom.textArea.style.textAlign = "right";
  12402. this.dom.textArea.style.right = (this.options.iconSize + 15) + 'px';
  12403. this.dom.textArea.style.left = '';
  12404. this.svg.style.right = 0 +'px';
  12405. this.svg.style.left = '';
  12406. }
  12407. if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'top-right') {
  12408. this.dom.frame.style.top = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
  12409. this.dom.frame.style.bottom = '';
  12410. }
  12411. else {
  12412. var scrollableHeight = this.body.domProps.center.height - this.body.domProps.centerContainer.height;
  12413. this.dom.frame.style.bottom = 4 + scrollableHeight + Number(this.body.dom.center.style.top.replace("px","")) + 'px';
  12414. this.dom.frame.style.top = '';
  12415. }
  12416. if (this.options.icons == false) {
  12417. this.dom.frame.style.width = this.dom.textArea.offsetWidth + 10 + 'px';
  12418. this.dom.textArea.style.right = '';
  12419. this.dom.textArea.style.left = '';
  12420. this.svg.style.width = '0px';
  12421. }
  12422. else {
  12423. this.dom.frame.style.width = this.options.iconSize + 15 + this.dom.textArea.offsetWidth + 10 + 'px'
  12424. this.drawLegendIcons();
  12425. }
  12426. var content = '';
  12427. for (var groupId in this.groups) {
  12428. if (this.groups.hasOwnProperty(groupId)) {
  12429. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  12430. content += this.groups[groupId].content + '<br />';
  12431. }
  12432. }
  12433. }
  12434. this.dom.textArea.innerHTML = content;
  12435. this.dom.textArea.style.lineHeight = ((0.75 * this.options.iconSize) + this.options.iconSpacing) + 'px';
  12436. }
  12437. };
  12438. Legend.prototype.drawLegendIcons = function() {
  12439. if (this.dom.frame.parentNode) {
  12440. DOMutil.prepareElements(this.svgElements);
  12441. var padding = window.getComputedStyle(this.dom.frame).paddingTop;
  12442. var iconOffset = Number(padding.replace('px',''));
  12443. var x = iconOffset;
  12444. var iconWidth = this.options.iconSize;
  12445. var iconHeight = 0.75 * this.options.iconSize;
  12446. var y = iconOffset + 0.5 * iconHeight + 3;
  12447. this.svg.style.width = iconWidth + 5 + iconOffset + 'px';
  12448. for (var groupId in this.groups) {
  12449. if (this.groups.hasOwnProperty(groupId)) {
  12450. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  12451. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  12452. y += iconHeight + this.options.iconSpacing;
  12453. }
  12454. }
  12455. }
  12456. DOMutil.cleanupElements(this.svgElements);
  12457. }
  12458. };
  12459. module.exports = Legend;
  12460. /***/ },
  12461. /* 34 */
  12462. /***/ function(module, exports, __webpack_require__) {
  12463. var util = __webpack_require__(1);
  12464. var DOMutil = __webpack_require__(2);
  12465. var DataSet = __webpack_require__(3);
  12466. var DataView = __webpack_require__(4);
  12467. var Component = __webpack_require__(25);
  12468. var DataAxis = __webpack_require__(28);
  12469. var GraphGroup = __webpack_require__(29);
  12470. var Legend = __webpack_require__(33);
  12471. var BarGraphFunctions = __webpack_require__(50);
  12472. var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
  12473. /**
  12474. * This is the constructor of the LineGraph. It requires a Timeline body and options.
  12475. *
  12476. * @param body
  12477. * @param options
  12478. * @constructor
  12479. */
  12480. function LineGraph(body, options) {
  12481. this.id = util.randomUUID();
  12482. this.body = body;
  12483. this.defaultOptions = {
  12484. yAxisOrientation: 'left',
  12485. defaultGroup: 'default',
  12486. sort: true,
  12487. sampling: true,
  12488. graphHeight: '400px',
  12489. shaded: {
  12490. enabled: false,
  12491. orientation: 'bottom' // top, bottom
  12492. },
  12493. style: 'line', // line, bar
  12494. barChart: {
  12495. width: 50,
  12496. handleOverlap: 'overlap',
  12497. align: 'center' // left, center, right
  12498. },
  12499. catmullRom: {
  12500. enabled: true,
  12501. parametrization: 'centripetal', // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5)
  12502. alpha: 0.5
  12503. },
  12504. drawPoints: {
  12505. enabled: true,
  12506. size: 6,
  12507. style: 'square' // square, circle
  12508. },
  12509. dataAxis: {
  12510. showMinorLabels: true,
  12511. showMajorLabels: true,
  12512. icons: false,
  12513. width: '40px',
  12514. visible: true,
  12515. alignZeros: true,
  12516. customRange: {
  12517. left: {min:undefined, max:undefined},
  12518. right: {min:undefined, max:undefined}
  12519. }
  12520. //, these options are not set by default, but this shows the format they will be in
  12521. //format: {
  12522. // left: {decimals: 2},
  12523. // right: {decimals: 2}
  12524. //},
  12525. //title: {
  12526. // left: {
  12527. // text: 'left',
  12528. // style: 'color:black;'
  12529. // },
  12530. // right: {
  12531. // text: 'right',
  12532. // style: 'color:black;'
  12533. // }
  12534. //}
  12535. },
  12536. legend: {
  12537. enabled: false,
  12538. icons: true,
  12539. left: {
  12540. visible: true,
  12541. position: 'top-left' // top/bottom - left,right
  12542. },
  12543. right: {
  12544. visible: true,
  12545. position: 'top-right' // top/bottom - left,right
  12546. }
  12547. },
  12548. groups: {
  12549. visibility: {}
  12550. }
  12551. };
  12552. // options is shared by this ItemSet and all its items
  12553. this.options = util.extend({}, this.defaultOptions);
  12554. this.dom = {};
  12555. this.props = {};
  12556. this.hammer = null;
  12557. this.groups = {};
  12558. this.abortedGraphUpdate = false;
  12559. this.updateSVGheight = false;
  12560. this.updateSVGheightOnResize = false;
  12561. var me = this;
  12562. this.itemsData = null; // DataSet
  12563. this.groupsData = null; // DataSet
  12564. // listeners for the DataSet of the items
  12565. this.itemListeners = {
  12566. 'add': function (event, params, senderId) {
  12567. me._onAdd(params.items);
  12568. },
  12569. 'update': function (event, params, senderId) {
  12570. me._onUpdate(params.items);
  12571. },
  12572. 'remove': function (event, params, senderId) {
  12573. me._onRemove(params.items);
  12574. }
  12575. };
  12576. // listeners for the DataSet of the groups
  12577. this.groupListeners = {
  12578. 'add': function (event, params, senderId) {
  12579. me._onAddGroups(params.items);
  12580. },
  12581. 'update': function (event, params, senderId) {
  12582. me._onUpdateGroups(params.items);
  12583. },
  12584. 'remove': function (event, params, senderId) {
  12585. me._onRemoveGroups(params.items);
  12586. }
  12587. };
  12588. this.items = {}; // object with an Item for every data item
  12589. this.selection = []; // list with the ids of all selected nodes
  12590. this.lastStart = this.body.range.start;
  12591. this.touchParams = {}; // stores properties while dragging
  12592. this.svgElements = {};
  12593. this.setOptions(options);
  12594. this.groupsUsingDefaultStyles = [0];
  12595. this.COUNTER = 0;
  12596. this.body.emitter.on('rangechanged', function() {
  12597. me.lastStart = me.body.range.start;
  12598. me.svg.style.left = util.option.asSize(-me.props.width);
  12599. me.redraw.call(me,true);
  12600. });
  12601. // create the HTML DOM
  12602. this._create();
  12603. this.framework = {svg: this.svg, svgElements: this.svgElements, options: this.options, groups: this.groups};
  12604. this.body.emitter.emit('change');
  12605. }
  12606. LineGraph.prototype = new Component();
  12607. /**
  12608. * Create the HTML DOM for the ItemSet
  12609. */
  12610. LineGraph.prototype._create = function(){
  12611. var frame = document.createElement('div');
  12612. frame.className = 'LineGraph';
  12613. this.dom.frame = frame;
  12614. // create svg element for graph drawing.
  12615. this.svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
  12616. this.svg.style.position = 'relative';
  12617. this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px';
  12618. this.svg.style.display = 'block';
  12619. frame.appendChild(this.svg);
  12620. // data axis
  12621. this.options.dataAxis.orientation = 'left';
  12622. this.yAxisLeft = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
  12623. this.options.dataAxis.orientation = 'right';
  12624. this.yAxisRight = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
  12625. delete this.options.dataAxis.orientation;
  12626. // legends
  12627. this.legendLeft = new Legend(this.body, this.options.legend, 'left', this.options.groups);
  12628. this.legendRight = new Legend(this.body, this.options.legend, 'right', this.options.groups);
  12629. this.show();
  12630. };
  12631. /**
  12632. * set the options of the LineGraph. the mergeOptions is used for subObjects that have an enabled element.
  12633. * @param {object} options
  12634. */
  12635. LineGraph.prototype.setOptions = function(options) {
  12636. if (options) {
  12637. var fields = ['sampling','defaultGroup','height','graphHeight','yAxisOrientation','style','barChart','dataAxis','sort','groups'];
  12638. if (options.graphHeight === undefined && options.height !== undefined && this.body.domProps.centerContainer.height !== undefined) {
  12639. this.updateSVGheight = true;
  12640. this.updateSVGheightOnResize = true;
  12641. }
  12642. else if (this.body.domProps.centerContainer.height !== undefined && options.graphHeight !== undefined) {
  12643. if (parseInt((options.graphHeight + '').replace("px",'')) < this.body.domProps.centerContainer.height) {
  12644. this.updateSVGheight = true;
  12645. }
  12646. }
  12647. util.selectiveDeepExtend(fields, this.options, options);
  12648. util.mergeOptions(this.options, options,'catmullRom');
  12649. util.mergeOptions(this.options, options,'drawPoints');
  12650. util.mergeOptions(this.options, options,'shaded');
  12651. util.mergeOptions(this.options, options,'legend');
  12652. if (options.catmullRom) {
  12653. if (typeof options.catmullRom == 'object') {
  12654. if (options.catmullRom.parametrization) {
  12655. if (options.catmullRom.parametrization == 'uniform') {
  12656. this.options.catmullRom.alpha = 0;
  12657. }
  12658. else if (options.catmullRom.parametrization == 'chordal') {
  12659. this.options.catmullRom.alpha = 1.0;
  12660. }
  12661. else {
  12662. this.options.catmullRom.parametrization = 'centripetal';
  12663. this.options.catmullRom.alpha = 0.5;
  12664. }
  12665. }
  12666. }
  12667. }
  12668. if (this.yAxisLeft) {
  12669. if (options.dataAxis !== undefined) {
  12670. this.yAxisLeft.setOptions(this.options.dataAxis);
  12671. this.yAxisRight.setOptions(this.options.dataAxis);
  12672. }
  12673. }
  12674. if (this.legendLeft) {
  12675. if (options.legend !== undefined) {
  12676. this.legendLeft.setOptions(this.options.legend);
  12677. this.legendRight.setOptions(this.options.legend);
  12678. }
  12679. }
  12680. if (this.groups.hasOwnProperty(UNGROUPED)) {
  12681. this.groups[UNGROUPED].setOptions(options);
  12682. }
  12683. }
  12684. // this is used to redraw the graph if the visibility of the groups is changed.
  12685. if (this.dom.frame) {
  12686. this.redraw(true);
  12687. }
  12688. };
  12689. /**
  12690. * Hide the component from the DOM
  12691. */
  12692. LineGraph.prototype.hide = function() {
  12693. // remove the frame containing the items
  12694. if (this.dom.frame.parentNode) {
  12695. this.dom.frame.parentNode.removeChild(this.dom.frame);
  12696. }
  12697. };
  12698. /**
  12699. * Show the component in the DOM (when not already visible).
  12700. * @return {Boolean} changed
  12701. */
  12702. LineGraph.prototype.show = function() {
  12703. // show frame containing the items
  12704. if (!this.dom.frame.parentNode) {
  12705. this.body.dom.center.appendChild(this.dom.frame);
  12706. }
  12707. };
  12708. /**
  12709. * Set items
  12710. * @param {vis.DataSet | null} items
  12711. */
  12712. LineGraph.prototype.setItems = function(items) {
  12713. var me = this,
  12714. ids,
  12715. oldItemsData = this.itemsData;
  12716. // replace the dataset
  12717. if (!items) {
  12718. this.itemsData = null;
  12719. }
  12720. else if (items instanceof DataSet || items instanceof DataView) {
  12721. this.itemsData = items;
  12722. }
  12723. else {
  12724. throw new TypeError('Data must be an instance of DataSet or DataView');
  12725. }
  12726. if (oldItemsData) {
  12727. // unsubscribe from old dataset
  12728. util.forEach(this.itemListeners, function (callback, event) {
  12729. oldItemsData.off(event, callback);
  12730. });
  12731. // remove all drawn items
  12732. ids = oldItemsData.getIds();
  12733. this._onRemove(ids);
  12734. }
  12735. if (this.itemsData) {
  12736. // subscribe to new dataset
  12737. var id = this.id;
  12738. util.forEach(this.itemListeners, function (callback, event) {
  12739. me.itemsData.on(event, callback, id);
  12740. });
  12741. // add all new items
  12742. ids = this.itemsData.getIds();
  12743. this._onAdd(ids);
  12744. }
  12745. this._updateUngrouped();
  12746. //this._updateGraph();
  12747. this.redraw(true);
  12748. };
  12749. /**
  12750. * Set groups
  12751. * @param {vis.DataSet} groups
  12752. */
  12753. LineGraph.prototype.setGroups = function(groups) {
  12754. var me = this;
  12755. var ids;
  12756. // unsubscribe from current dataset
  12757. if (this.groupsData) {
  12758. util.forEach(this.groupListeners, function (callback, event) {
  12759. me.groupsData.unsubscribe(event, callback);
  12760. });
  12761. // remove all drawn groups
  12762. ids = this.groupsData.getIds();
  12763. this.groupsData = null;
  12764. this._onRemoveGroups(ids); // note: this will cause a redraw
  12765. }
  12766. // replace the dataset
  12767. if (!groups) {
  12768. this.groupsData = null;
  12769. }
  12770. else if (groups instanceof DataSet || groups instanceof DataView) {
  12771. this.groupsData = groups;
  12772. }
  12773. else {
  12774. throw new TypeError('Data must be an instance of DataSet or DataView');
  12775. }
  12776. if (this.groupsData) {
  12777. // subscribe to new dataset
  12778. var id = this.id;
  12779. util.forEach(this.groupListeners, function (callback, event) {
  12780. me.groupsData.on(event, callback, id);
  12781. });
  12782. // draw all ms
  12783. ids = this.groupsData.getIds();
  12784. this._onAddGroups(ids);
  12785. }
  12786. this._onUpdate();
  12787. };
  12788. /**
  12789. * Update the data
  12790. * @param [ids]
  12791. * @private
  12792. */
  12793. LineGraph.prototype._onUpdate = function(ids) {
  12794. this._updateUngrouped();
  12795. this._updateAllGroupData();
  12796. //this._updateGraph();
  12797. this.redraw(true);
  12798. };
  12799. LineGraph.prototype._onAdd = function (ids) {this._onUpdate(ids);};
  12800. LineGraph.prototype._onRemove = function (ids) {this._onUpdate(ids);};
  12801. LineGraph.prototype._onUpdateGroups = function (groupIds) {
  12802. for (var i = 0; i < groupIds.length; i++) {
  12803. var group = this.groupsData.get(groupIds[i]);
  12804. this._updateGroup(group, groupIds[i]);
  12805. }
  12806. //this._updateGraph();
  12807. this.redraw(true);
  12808. };
  12809. LineGraph.prototype._onAddGroups = function (groupIds) {this._onUpdateGroups(groupIds);};
  12810. /**
  12811. * this cleans the group out off the legends and the dataaxis, updates the ungrouped and updates the graph
  12812. * @param {Array} groupIds
  12813. * @private
  12814. */
  12815. LineGraph.prototype._onRemoveGroups = function (groupIds) {
  12816. for (var i = 0; i < groupIds.length; i++) {
  12817. if (this.groups.hasOwnProperty(groupIds[i])) {
  12818. if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') {
  12819. this.yAxisRight.removeGroup(groupIds[i]);
  12820. this.legendRight.removeGroup(groupIds[i]);
  12821. this.legendRight.redraw();
  12822. }
  12823. else {
  12824. this.yAxisLeft.removeGroup(groupIds[i]);
  12825. this.legendLeft.removeGroup(groupIds[i]);
  12826. this.legendLeft.redraw();
  12827. }
  12828. delete this.groups[groupIds[i]];
  12829. }
  12830. }
  12831. this._updateUngrouped();
  12832. //this._updateGraph();
  12833. this.redraw(true);
  12834. };
  12835. /**
  12836. * update a group object with the group dataset entree
  12837. *
  12838. * @param group
  12839. * @param groupId
  12840. * @private
  12841. */
  12842. LineGraph.prototype._updateGroup = function (group, groupId) {
  12843. if (!this.groups.hasOwnProperty(groupId)) {
  12844. this.groups[groupId] = new GraphGroup(group, groupId, this.options, this.groupsUsingDefaultStyles);
  12845. if (this.groups[groupId].options.yAxisOrientation == 'right') {
  12846. this.yAxisRight.addGroup(groupId, this.groups[groupId]);
  12847. this.legendRight.addGroup(groupId, this.groups[groupId]);
  12848. }
  12849. else {
  12850. this.yAxisLeft.addGroup(groupId, this.groups[groupId]);
  12851. this.legendLeft.addGroup(groupId, this.groups[groupId]);
  12852. }
  12853. }
  12854. else {
  12855. this.groups[groupId].update(group);
  12856. if (this.groups[groupId].options.yAxisOrientation == 'right') {
  12857. this.yAxisRight.updateGroup(groupId, this.groups[groupId]);
  12858. this.legendRight.updateGroup(groupId, this.groups[groupId]);
  12859. }
  12860. else {
  12861. this.yAxisLeft.updateGroup(groupId, this.groups[groupId]);
  12862. this.legendLeft.updateGroup(groupId, this.groups[groupId]);
  12863. }
  12864. }
  12865. this.legendLeft.redraw();
  12866. this.legendRight.redraw();
  12867. };
  12868. /**
  12869. * this updates all groups, it is used when there is an update the the itemset.
  12870. *
  12871. * @private
  12872. */
  12873. LineGraph.prototype._updateAllGroupData = function () {
  12874. if (this.itemsData != null) {
  12875. var groupsContent = {};
  12876. var groupId;
  12877. for (groupId in this.groups) {
  12878. if (this.groups.hasOwnProperty(groupId)) {
  12879. groupsContent[groupId] = [];
  12880. }
  12881. }
  12882. for (var itemId in this.itemsData._data) {
  12883. if (this.itemsData._data.hasOwnProperty(itemId)) {
  12884. var item = this.itemsData._data[itemId];
  12885. if (groupsContent[item.group] === undefined) {
  12886. 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.')
  12887. }
  12888. item.x = util.convert(item.x,'Date');
  12889. groupsContent[item.group].push(item);
  12890. }
  12891. }
  12892. for (groupId in this.groups) {
  12893. if (this.groups.hasOwnProperty(groupId)) {
  12894. this.groups[groupId].setItems(groupsContent[groupId]);
  12895. }
  12896. }
  12897. }
  12898. };
  12899. /**
  12900. * Create or delete the group holding all ungrouped items. This group is used when
  12901. * there are no groups specified. This anonymous group is called 'graph'.
  12902. * @protected
  12903. */
  12904. LineGraph.prototype._updateUngrouped = function() {
  12905. if (this.itemsData && this.itemsData != null) {
  12906. var ungroupedCounter = 0;
  12907. for (var itemId in this.itemsData._data) {
  12908. if (this.itemsData._data.hasOwnProperty(itemId)) {
  12909. var item = this.itemsData._data[itemId];
  12910. if (item != undefined) {
  12911. if (item.hasOwnProperty('group')) {
  12912. if (item.group === undefined) {
  12913. item.group = UNGROUPED;
  12914. }
  12915. }
  12916. else {
  12917. item.group = UNGROUPED;
  12918. }
  12919. ungroupedCounter = item.group == UNGROUPED ? ungroupedCounter + 1 : ungroupedCounter;
  12920. }
  12921. }
  12922. }
  12923. if (ungroupedCounter == 0) {
  12924. delete this.groups[UNGROUPED];
  12925. this.legendLeft.removeGroup(UNGROUPED);
  12926. this.legendRight.removeGroup(UNGROUPED);
  12927. this.yAxisLeft.removeGroup(UNGROUPED);
  12928. this.yAxisRight.removeGroup(UNGROUPED);
  12929. }
  12930. else {
  12931. var group = {id: UNGROUPED, content: this.options.defaultGroup};
  12932. this._updateGroup(group, UNGROUPED);
  12933. }
  12934. }
  12935. else {
  12936. delete this.groups[UNGROUPED];
  12937. this.legendLeft.removeGroup(UNGROUPED);
  12938. this.legendRight.removeGroup(UNGROUPED);
  12939. this.yAxisLeft.removeGroup(UNGROUPED);
  12940. this.yAxisRight.removeGroup(UNGROUPED);
  12941. }
  12942. this.legendLeft.redraw();
  12943. this.legendRight.redraw();
  12944. };
  12945. /**
  12946. * Redraw the component, mandatory function
  12947. * @return {boolean} Returns true if the component is resized
  12948. */
  12949. LineGraph.prototype.redraw = function(forceGraphUpdate) {
  12950. var resized = false;
  12951. // calculate actual size and position
  12952. this.props.width = this.dom.frame.offsetWidth;
  12953. this.props.height = this.body.domProps.centerContainer.height;
  12954. // update the graph if there is no lastWidth or with, used for the initial draw
  12955. if (this.lastWidth === undefined && this.props.width) {
  12956. forceGraphUpdate = true;
  12957. }
  12958. // check if this component is resized
  12959. resized = this._isResized() || resized;
  12960. // check whether zoomed (in that case we need to re-stack everything)
  12961. var visibleInterval = this.body.range.end - this.body.range.start;
  12962. var zoomed = (visibleInterval != this.lastVisibleInterval);
  12963. this.lastVisibleInterval = visibleInterval;
  12964. // the svg element is three times as big as the width, this allows for fully dragging left and right
  12965. // without reloading the graph. the controls for this are bound to events in the constructor
  12966. if (resized == true) {
  12967. this.svg.style.width = util.option.asSize(3*this.props.width);
  12968. this.svg.style.left = util.option.asSize(-this.props.width);
  12969. // if the height of the graph is set as proportional, change the height of the svg
  12970. if ((this.options.height + '').indexOf("%") != -1 || this.updateSVGheightOnResize == true) {
  12971. this.updateSVGheight = true;
  12972. }
  12973. }
  12974. // update the height of the graph on each redraw of the graph.
  12975. if (this.updateSVGheight == true) {
  12976. if (this.options.graphHeight != this.body.domProps.centerContainer.height + 'px') {
  12977. this.options.graphHeight = this.body.domProps.centerContainer.height + 'px';
  12978. this.svg.style.height = this.body.domProps.centerContainer.height + 'px';
  12979. }
  12980. this.updateSVGheight = false;
  12981. }
  12982. else {
  12983. this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px';
  12984. }
  12985. // zoomed is here to ensure that animations are shown correctly.
  12986. if (resized == true || zoomed == true || this.abortedGraphUpdate == true || forceGraphUpdate == true) {
  12987. resized = this._updateGraph() || resized;
  12988. }
  12989. else {
  12990. // move the whole svg while dragging
  12991. if (this.lastStart != 0) {
  12992. var offset = this.body.range.start - this.lastStart;
  12993. var range = this.body.range.end - this.body.range.start;
  12994. if (this.props.width != 0) {
  12995. var rangePerPixelInv = this.props.width/range;
  12996. var xOffset = offset * rangePerPixelInv;
  12997. this.svg.style.left = (-this.props.width - xOffset) + 'px';
  12998. }
  12999. }
  13000. }
  13001. this.legendLeft.redraw();
  13002. this.legendRight.redraw();
  13003. return resized;
  13004. };
  13005. /**
  13006. * Update and redraw the graph.
  13007. *
  13008. */
  13009. LineGraph.prototype._updateGraph = function () {
  13010. // reset the svg elements
  13011. DOMutil.prepareElements(this.svgElements);
  13012. if (this.props.width != 0 && this.itemsData != null) {
  13013. var group, i;
  13014. var preprocessedGroupData = {};
  13015. var processedGroupData = {};
  13016. var groupRanges = {};
  13017. var changeCalled = false;
  13018. // getting group Ids
  13019. var groupIds = [];
  13020. for (var groupId in this.groups) {
  13021. if (this.groups.hasOwnProperty(groupId)) {
  13022. group = this.groups[groupId];
  13023. if (group.visible == true && (this.options.groups.visibility[groupId] === undefined || this.options.groups.visibility[groupId] == true)) {
  13024. groupIds.push(groupId);
  13025. }
  13026. }
  13027. }
  13028. if (groupIds.length > 0) {
  13029. // this is the range of the SVG canvas
  13030. var minDate = this.body.util.toGlobalTime(-this.body.domProps.root.width);
  13031. var maxDate = this.body.util.toGlobalTime(2 * this.body.domProps.root.width);
  13032. var groupsData = {};
  13033. // fill groups data, this only loads the data we require based on the timewindow
  13034. this._getRelevantData(groupIds, groupsData, minDate, maxDate);
  13035. // apply sampling, if disabled, it will pass through this function.
  13036. this._applySampling(groupIds, groupsData);
  13037. // we transform the X coordinates to detect collisions
  13038. for (i = 0; i < groupIds.length; i++) {
  13039. preprocessedGroupData[groupIds[i]] = this._convertXcoordinates(groupsData[groupIds[i]]);
  13040. }
  13041. // now all needed data has been collected we start the processing.
  13042. this._getYRanges(groupIds, preprocessedGroupData, groupRanges);
  13043. // update the Y axis first, we use this data to draw at the correct Y points
  13044. // changeCalled is required to clean the SVG on a change emit.
  13045. changeCalled = this._updateYAxis(groupIds, groupRanges);
  13046. var MAX_CYCLES = 5;
  13047. if (changeCalled == true && this.COUNTER < MAX_CYCLES) {
  13048. DOMutil.cleanupElements(this.svgElements);
  13049. this.abortedGraphUpdate = true;
  13050. this.COUNTER++;
  13051. this.body.emitter.emit('change');
  13052. return true;
  13053. }
  13054. else {
  13055. if (this.COUNTER > MAX_CYCLES) {
  13056. console.log("WARNING: there may be an infinite loop in the _updateGraph emitter cycle.")
  13057. }
  13058. this.COUNTER = 0;
  13059. this.abortedGraphUpdate = false;
  13060. // With the yAxis scaled correctly, use this to get the Y values of the points.
  13061. for (i = 0; i < groupIds.length; i++) {
  13062. group = this.groups[groupIds[i]];
  13063. processedGroupData[groupIds[i]] = this._convertYcoordinates(groupsData[groupIds[i]], group);
  13064. }
  13065. // draw the groups
  13066. for (i = 0; i < groupIds.length; i++) {
  13067. group = this.groups[groupIds[i]];
  13068. if (group.options.style != 'bar') { // bar needs to be drawn enmasse
  13069. group.draw(processedGroupData[groupIds[i]], group, this.framework);
  13070. }
  13071. }
  13072. BarGraphFunctions.draw(groupIds, processedGroupData, this.framework);
  13073. }
  13074. }
  13075. }
  13076. // cleanup unused svg elements
  13077. DOMutil.cleanupElements(this.svgElements);
  13078. return false;
  13079. };
  13080. /**
  13081. * first select and preprocess the data from the datasets.
  13082. * the groups have their preselection of data, we now loop over this data to see
  13083. * what data we need to draw. Sorted data is much faster.
  13084. * more optimization is possible by doing the sampling before and using the binary search
  13085. * to find the end date to determine the increment.
  13086. *
  13087. * @param {array} groupIds
  13088. * @param {object} groupsData
  13089. * @param {date} minDate
  13090. * @param {date} maxDate
  13091. * @private
  13092. */
  13093. LineGraph.prototype._getRelevantData = function (groupIds, groupsData, minDate, maxDate) {
  13094. var group, i, j, item;
  13095. if (groupIds.length > 0) {
  13096. for (i = 0; i < groupIds.length; i++) {
  13097. group = this.groups[groupIds[i]];
  13098. groupsData[groupIds[i]] = [];
  13099. var dataContainer = groupsData[groupIds[i]];
  13100. // optimization for sorted data
  13101. if (group.options.sort == true) {
  13102. var guess = Math.max(0, util.binarySearchValue(group.itemsData, minDate, 'x', 'before'));
  13103. for (j = guess; j < group.itemsData.length; j++) {
  13104. item = group.itemsData[j];
  13105. if (item !== undefined) {
  13106. if (item.x > maxDate) {
  13107. dataContainer.push(item);
  13108. break;
  13109. }
  13110. else {
  13111. dataContainer.push(item);
  13112. }
  13113. }
  13114. }
  13115. }
  13116. else {
  13117. for (j = 0; j < group.itemsData.length; j++) {
  13118. item = group.itemsData[j];
  13119. if (item !== undefined) {
  13120. if (item.x > minDate && item.x < maxDate) {
  13121. dataContainer.push(item);
  13122. }
  13123. }
  13124. }
  13125. }
  13126. }
  13127. }
  13128. };
  13129. /**
  13130. *
  13131. * @param groupIds
  13132. * @param groupsData
  13133. * @private
  13134. */
  13135. LineGraph.prototype._applySampling = function (groupIds, groupsData) {
  13136. var group;
  13137. if (groupIds.length > 0) {
  13138. for (var i = 0; i < groupIds.length; i++) {
  13139. group = this.groups[groupIds[i]];
  13140. if (group.options.sampling == true) {
  13141. var dataContainer = groupsData[groupIds[i]];
  13142. if (dataContainer.length > 0) {
  13143. var increment = 1;
  13144. var amountOfPoints = dataContainer.length;
  13145. // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop
  13146. // of width changing of the yAxis.
  13147. var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x);
  13148. var pointsPerPixel = amountOfPoints / xDistance;
  13149. increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel)));
  13150. var sampledData = [];
  13151. for (var j = 0; j < amountOfPoints; j += increment) {
  13152. sampledData.push(dataContainer[j]);
  13153. }
  13154. groupsData[groupIds[i]] = sampledData;
  13155. }
  13156. }
  13157. }
  13158. }
  13159. };
  13160. /**
  13161. *
  13162. *
  13163. * @param {array} groupIds
  13164. * @param {object} groupsData
  13165. * @param {object} groupRanges | this is being filled here
  13166. * @private
  13167. */
  13168. LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) {
  13169. var groupData, group, i;
  13170. var barCombinedDataLeft = [];
  13171. var barCombinedDataRight = [];
  13172. var options;
  13173. if (groupIds.length > 0) {
  13174. for (i = 0; i < groupIds.length; i++) {
  13175. groupData = groupsData[groupIds[i]];
  13176. options = this.groups[groupIds[i]].options;
  13177. if (groupData.length > 0) {
  13178. group = this.groups[groupIds[i]];
  13179. // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups.
  13180. if (options.barChart.handleOverlap == 'stack' && options.style == 'bar') {
  13181. if (options.yAxisOrientation == 'left') {barCombinedDataLeft = barCombinedDataLeft.concat(group.getYRange(groupData)) ;}
  13182. else {barCombinedDataRight = barCombinedDataRight.concat(group.getYRange(groupData));}
  13183. }
  13184. else {
  13185. groupRanges[groupIds[i]] = group.getYRange(groupData,groupIds[i]);
  13186. }
  13187. }
  13188. }
  13189. // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups.
  13190. BarGraphFunctions.getStackedBarYRange(barCombinedDataLeft , groupRanges, groupIds, '__barchartLeft' , 'left' );
  13191. BarGraphFunctions.getStackedBarYRange(barCombinedDataRight, groupRanges, groupIds, '__barchartRight', 'right');
  13192. }
  13193. };
  13194. /**
  13195. * this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden.
  13196. * @param {Array} groupIds
  13197. * @param {Object} groupRanges
  13198. * @private
  13199. */
  13200. LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
  13201. var resized = false;
  13202. var yAxisLeftUsed = false;
  13203. var yAxisRightUsed = false;
  13204. var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal;
  13205. // if groups are present
  13206. if (groupIds.length > 0) {
  13207. // 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.
  13208. for (var i = 0; i < groupIds.length; i++) {
  13209. var group = this.groups[groupIds[i]];
  13210. if (group && group.options.yAxisOrientation != 'right') {
  13211. yAxisLeftUsed = true;
  13212. minLeft = 0;
  13213. maxLeft = 0;
  13214. }
  13215. else if (group && group.options.yAxisOrientation) {
  13216. yAxisRightUsed = true;
  13217. minRight = 0;
  13218. maxRight = 0;
  13219. }
  13220. }
  13221. // if there are items:
  13222. for (var i = 0; i < groupIds.length; i++) {
  13223. if (groupRanges.hasOwnProperty(groupIds[i])) {
  13224. if (groupRanges[groupIds[i]].ignore !== true) {
  13225. minVal = groupRanges[groupIds[i]].min;
  13226. maxVal = groupRanges[groupIds[i]].max;
  13227. if (groupRanges[groupIds[i]].yAxisOrientation != 'right') {
  13228. yAxisLeftUsed = true;
  13229. minLeft = minLeft > minVal ? minVal : minLeft;
  13230. maxLeft = maxLeft < maxVal ? maxVal : maxLeft;
  13231. }
  13232. else {
  13233. yAxisRightUsed = true;
  13234. minRight = minRight > minVal ? minVal : minRight;
  13235. maxRight = maxRight < maxVal ? maxVal : maxRight;
  13236. }
  13237. }
  13238. }
  13239. }
  13240. if (yAxisLeftUsed == true) {
  13241. this.yAxisLeft.setRange(minLeft, maxLeft);
  13242. }
  13243. if (yAxisRightUsed == true) {
  13244. this.yAxisRight.setRange(minRight, maxRight);
  13245. }
  13246. }
  13247. resized = this._toggleAxisVisiblity(yAxisLeftUsed , this.yAxisLeft) || resized;
  13248. resized = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || resized;
  13249. if (yAxisRightUsed == true && yAxisLeftUsed == true) {
  13250. this.yAxisLeft.drawIcons = true;
  13251. this.yAxisRight.drawIcons = true;
  13252. }
  13253. else {
  13254. this.yAxisLeft.drawIcons = false;
  13255. this.yAxisRight.drawIcons = false;
  13256. }
  13257. this.yAxisRight.master = !yAxisLeftUsed;
  13258. if (this.yAxisRight.master == false) {
  13259. if (yAxisRightUsed == true) {this.yAxisLeft.lineOffset = this.yAxisRight.width;}
  13260. else {this.yAxisLeft.lineOffset = 0;}
  13261. resized = this.yAxisLeft.redraw() || resized;
  13262. this.yAxisRight.stepPixelsForced = this.yAxisLeft.stepPixels;
  13263. this.yAxisRight.zeroCrossing = this.yAxisLeft.zeroCrossing;
  13264. resized = this.yAxisRight.redraw() || resized;
  13265. }
  13266. else {
  13267. resized = this.yAxisRight.redraw() || resized;
  13268. }
  13269. // clean the accumulated lists
  13270. if (groupIds.indexOf('__barchartLeft') != -1) {
  13271. groupIds.splice(groupIds.indexOf('__barchartLeft'),1);
  13272. }
  13273. if (groupIds.indexOf('__barchartRight') != -1) {
  13274. groupIds.splice(groupIds.indexOf('__barchartRight'),1);
  13275. }
  13276. return resized;
  13277. };
  13278. /**
  13279. * This shows or hides the Y axis if needed. If there is a change, the changed event is emitted by the updateYAxis function
  13280. *
  13281. * @param {boolean} axisUsed
  13282. * @returns {boolean}
  13283. * @private
  13284. * @param axis
  13285. */
  13286. LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) {
  13287. var changed = false;
  13288. if (axisUsed == false) {
  13289. if (axis.dom.frame.parentNode && axis.hidden == false) {
  13290. axis.hide()
  13291. changed = true;
  13292. }
  13293. }
  13294. else {
  13295. if (!axis.dom.frame.parentNode && axis.hidden == true) {
  13296. axis.show();
  13297. changed = true;
  13298. }
  13299. }
  13300. return changed;
  13301. };
  13302. /**
  13303. * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
  13304. * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
  13305. * the yAxis.
  13306. *
  13307. * @param datapoints
  13308. * @returns {Array}
  13309. * @private
  13310. */
  13311. LineGraph.prototype._convertXcoordinates = function (datapoints) {
  13312. var extractedData = [];
  13313. var xValue, yValue;
  13314. var toScreen = this.body.util.toScreen;
  13315. for (var i = 0; i < datapoints.length; i++) {
  13316. xValue = toScreen(datapoints[i].x) + this.props.width;
  13317. yValue = datapoints[i].y;
  13318. extractedData.push({x: xValue, y: yValue});
  13319. }
  13320. return extractedData;
  13321. };
  13322. /**
  13323. * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
  13324. * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
  13325. * the yAxis.
  13326. *
  13327. * @param datapoints
  13328. * @param group
  13329. * @returns {Array}
  13330. * @private
  13331. */
  13332. LineGraph.prototype._convertYcoordinates = function (datapoints, group) {
  13333. var extractedData = [];
  13334. var xValue, yValue;
  13335. var toScreen = this.body.util.toScreen;
  13336. var axis = this.yAxisLeft;
  13337. var svgHeight = Number(this.svg.style.height.replace('px',''));
  13338. if (group.options.yAxisOrientation == 'right') {
  13339. axis = this.yAxisRight;
  13340. }
  13341. for (var i = 0; i < datapoints.length; i++) {
  13342. var labelValue;
  13343. //if (datapoints[i].label) {
  13344. // labelValue = datapoints[i].label;
  13345. //}
  13346. //else {
  13347. // labelValue = null;
  13348. //}
  13349. labelValue = datapoints[i].label ? datapoints[i].label : null;
  13350. xValue = toScreen(datapoints[i].x) + this.props.width;
  13351. yValue = Math.round(axis.convertValue(datapoints[i].y));
  13352. extractedData.push({x: xValue, y: yValue, label:labelValue});
  13353. }
  13354. group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0)));
  13355. return extractedData;
  13356. };
  13357. module.exports = LineGraph;
  13358. /***/ },
  13359. /* 35 */
  13360. /***/ function(module, exports, __webpack_require__) {
  13361. var util = __webpack_require__(1);
  13362. var Component = __webpack_require__(25);
  13363. var TimeStep = __webpack_require__(19);
  13364. var DateUtil = __webpack_require__(15);
  13365. var moment = __webpack_require__(44);
  13366. /**
  13367. * A horizontal time axis
  13368. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
  13369. * @param {Object} [options] See TimeAxis.setOptions for the available
  13370. * options.
  13371. * @constructor TimeAxis
  13372. * @extends Component
  13373. */
  13374. function TimeAxis (body, options) {
  13375. this.dom = {
  13376. foreground: null,
  13377. lines: [],
  13378. majorTexts: [],
  13379. minorTexts: [],
  13380. redundant: {
  13381. lines: [],
  13382. majorTexts: [],
  13383. minorTexts: []
  13384. }
  13385. };
  13386. this.props = {
  13387. range: {
  13388. start: 0,
  13389. end: 0,
  13390. minimumStep: 0
  13391. },
  13392. lineTop: 0
  13393. };
  13394. this.defaultOptions = {
  13395. orientation: 'bottom', // supported: 'top', 'bottom'
  13396. // TODO: implement timeaxis orientations 'left' and 'right'
  13397. showMinorLabels: true,
  13398. showMajorLabels: true,
  13399. format: null,
  13400. timeAxis: null
  13401. };
  13402. this.options = util.extend({}, this.defaultOptions);
  13403. this.body = body;
  13404. // create the HTML DOM
  13405. this._create();
  13406. this.setOptions(options);
  13407. }
  13408. TimeAxis.prototype = new Component();
  13409. /**
  13410. * Set options for the TimeAxis.
  13411. * Parameters will be merged in current options.
  13412. * @param {Object} options Available options:
  13413. * {string} [orientation]
  13414. * {boolean} [showMinorLabels]
  13415. * {boolean} [showMajorLabels]
  13416. */
  13417. TimeAxis.prototype.setOptions = function(options) {
  13418. if (options) {
  13419. // copy all options that we know
  13420. util.selectiveExtend([
  13421. 'orientation',
  13422. 'showMinorLabels',
  13423. 'showMajorLabels',
  13424. 'hiddenDates',
  13425. 'format',
  13426. 'timeAxis'
  13427. ], this.options, options);
  13428. // apply locale to moment.js
  13429. // TODO: not so nice, this is applied globally to moment.js
  13430. if ('locale' in options) {
  13431. if (typeof moment.locale === 'function') {
  13432. // moment.js 2.8.1+
  13433. moment.locale(options.locale);
  13434. }
  13435. else {
  13436. moment.lang(options.locale);
  13437. }
  13438. }
  13439. }
  13440. };
  13441. /**
  13442. * Create the HTML DOM for the TimeAxis
  13443. */
  13444. TimeAxis.prototype._create = function() {
  13445. this.dom.foreground = document.createElement('div');
  13446. this.dom.background = document.createElement('div');
  13447. this.dom.foreground.className = 'timeaxis foreground';
  13448. this.dom.background.className = 'timeaxis background';
  13449. };
  13450. /**
  13451. * Destroy the TimeAxis
  13452. */
  13453. TimeAxis.prototype.destroy = function() {
  13454. // remove from DOM
  13455. if (this.dom.foreground.parentNode) {
  13456. this.dom.foreground.parentNode.removeChild(this.dom.foreground);
  13457. }
  13458. if (this.dom.background.parentNode) {
  13459. this.dom.background.parentNode.removeChild(this.dom.background);
  13460. }
  13461. this.body = null;
  13462. };
  13463. /**
  13464. * Repaint the component
  13465. * @return {boolean} Returns true if the component is resized
  13466. */
  13467. TimeAxis.prototype.redraw = function () {
  13468. var options = this.options;
  13469. var props = this.props;
  13470. var foreground = this.dom.foreground;
  13471. var background = this.dom.background;
  13472. // determine the correct parent DOM element (depending on option orientation)
  13473. var parent = (options.orientation == 'top') ? this.body.dom.top : this.body.dom.bottom;
  13474. var parentChanged = (foreground.parentNode !== parent);
  13475. // calculate character width and height
  13476. this._calculateCharSize();
  13477. // TODO: recalculate sizes only needed when parent is resized or options is changed
  13478. var orientation = this.options.orientation,
  13479. showMinorLabels = this.options.showMinorLabels,
  13480. showMajorLabels = this.options.showMajorLabels;
  13481. // determine the width and height of the elemens for the axis
  13482. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  13483. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  13484. props.height = props.minorLabelHeight + props.majorLabelHeight;
  13485. props.width = foreground.offsetWidth;
  13486. props.minorLineHeight = this.body.domProps.root.height - props.majorLabelHeight -
  13487. (options.orientation == 'top' ? this.body.domProps.bottom.height : this.body.domProps.top.height);
  13488. props.minorLineWidth = 1; // TODO: really calculate width
  13489. props.majorLineHeight = props.minorLineHeight + props.majorLabelHeight;
  13490. props.majorLineWidth = 1; // TODO: really calculate width
  13491. // take foreground and background offline while updating (is almost twice as fast)
  13492. var foregroundNextSibling = foreground.nextSibling;
  13493. var backgroundNextSibling = background.nextSibling;
  13494. foreground.parentNode && foreground.parentNode.removeChild(foreground);
  13495. background.parentNode && background.parentNode.removeChild(background);
  13496. foreground.style.height = this.props.height + 'px';
  13497. this._repaintLabels();
  13498. // put DOM online again (at the same place)
  13499. if (foregroundNextSibling) {
  13500. parent.insertBefore(foreground, foregroundNextSibling);
  13501. }
  13502. else {
  13503. parent.appendChild(foreground)
  13504. }
  13505. if (backgroundNextSibling) {
  13506. this.body.dom.backgroundVertical.insertBefore(background, backgroundNextSibling);
  13507. }
  13508. else {
  13509. this.body.dom.backgroundVertical.appendChild(background)
  13510. }
  13511. return this._isResized() || parentChanged;
  13512. };
  13513. /**
  13514. * Repaint major and minor text labels and vertical grid lines
  13515. * @private
  13516. */
  13517. TimeAxis.prototype._repaintLabels = function () {
  13518. var orientation = this.options.orientation;
  13519. // calculate range and step (step such that we have space for 7 characters per label)
  13520. var start = util.convert(this.body.range.start, 'Number');
  13521. var end = util.convert(this.body.range.end, 'Number');
  13522. var timeLabelsize = this.body.util.toTime((this.props.minorCharWidth || 10) * 7).valueOf();
  13523. var minimumStep = timeLabelsize - DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this.body.range, timeLabelsize);
  13524. minimumStep -= this.body.util.toTime(0).valueOf();
  13525. var step = new TimeStep(new Date(start), new Date(end), minimumStep, this.body.hiddenDates);
  13526. if (this.options.format) {
  13527. step.setFormat(this.options.format);
  13528. }
  13529. if (this.options.timeAxis) {
  13530. step.setScale(this.options.timeAxis);
  13531. }
  13532. this.step = step;
  13533. // Move all DOM elements to a "redundant" list, where they
  13534. // can be picked for re-use, and clear the lists with lines and texts.
  13535. // At the end of the function _repaintLabels, left over elements will be cleaned up
  13536. var dom = this.dom;
  13537. dom.redundant.lines = dom.lines;
  13538. dom.redundant.majorTexts = dom.majorTexts;
  13539. dom.redundant.minorTexts = dom.minorTexts;
  13540. dom.lines = [];
  13541. dom.majorTexts = [];
  13542. dom.minorTexts = [];
  13543. var cur;
  13544. var x = 0;
  13545. var isMajor;
  13546. var xPrev = 0;
  13547. var width = 0;
  13548. var prevLine;
  13549. var xFirstMajorLabel = undefined;
  13550. var max = 0;
  13551. var className;
  13552. step.first();
  13553. while (step.hasNext() && max < 1000) {
  13554. max++;
  13555. cur = step.getCurrent();
  13556. isMajor = step.isMajor();
  13557. className = step.getClassName();
  13558. xPrev = x;
  13559. x = this.body.util.toScreen(cur);
  13560. width = x - xPrev;
  13561. if (prevLine) {
  13562. prevLine.style.width = width + 'px';
  13563. }
  13564. if (this.options.showMinorLabels) {
  13565. this._repaintMinorText(x, step.getLabelMinor(), orientation, className);
  13566. }
  13567. if (isMajor && this.options.showMajorLabels) {
  13568. if (x > 0) {
  13569. if (xFirstMajorLabel == undefined) {
  13570. xFirstMajorLabel = x;
  13571. }
  13572. this._repaintMajorText(x, step.getLabelMajor(), orientation, className);
  13573. }
  13574. prevLine = this._repaintMajorLine(x, orientation, className);
  13575. }
  13576. else {
  13577. prevLine = this._repaintMinorLine(x, orientation, className);
  13578. }
  13579. step.next();
  13580. }
  13581. // create a major label on the left when needed
  13582. if (this.options.showMajorLabels) {
  13583. var leftTime = this.body.util.toTime(0),
  13584. leftText = step.getLabelMajor(leftTime),
  13585. widthText = leftText.length * (this.props.majorCharWidth || 10) + 10; // upper bound estimation
  13586. if (xFirstMajorLabel == undefined || widthText < xFirstMajorLabel) {
  13587. this._repaintMajorText(0, leftText, orientation, className);
  13588. }
  13589. }
  13590. // Cleanup leftover DOM elements from the redundant list
  13591. util.forEach(this.dom.redundant, function (arr) {
  13592. while (arr.length) {
  13593. var elem = arr.pop();
  13594. if (elem && elem.parentNode) {
  13595. elem.parentNode.removeChild(elem);
  13596. }
  13597. }
  13598. });
  13599. };
  13600. /**
  13601. * Create a minor label for the axis at position x
  13602. * @param {Number} x
  13603. * @param {String} text
  13604. * @param {String} orientation "top" or "bottom" (default)
  13605. * @param {String} className
  13606. * @private
  13607. */
  13608. TimeAxis.prototype._repaintMinorText = function (x, text, orientation, className) {
  13609. // reuse redundant label
  13610. var label = this.dom.redundant.minorTexts.shift();
  13611. if (!label) {
  13612. // create new label
  13613. var content = document.createTextNode('');
  13614. label = document.createElement('div');
  13615. label.appendChild(content);
  13616. this.dom.foreground.appendChild(label);
  13617. }
  13618. this.dom.minorTexts.push(label);
  13619. label.childNodes[0].nodeValue = text;
  13620. label.style.top = (orientation == 'top') ? (this.props.majorLabelHeight + 'px') : '0';
  13621. label.style.left = x + 'px';
  13622. label.className = 'text minor ' + className;
  13623. //label.title = title; // TODO: this is a heavy operation
  13624. };
  13625. /**
  13626. * Create a Major label for the axis at position x
  13627. * @param {Number} x
  13628. * @param {String} text
  13629. * @param {String} orientation "top" or "bottom" (default)
  13630. * @param {String} className
  13631. * @private
  13632. */
  13633. TimeAxis.prototype._repaintMajorText = function (x, text, orientation, className) {
  13634. // reuse redundant label
  13635. var label = this.dom.redundant.majorTexts.shift();
  13636. if (!label) {
  13637. // create label
  13638. var content = document.createTextNode(text);
  13639. label = document.createElement('div');
  13640. label.appendChild(content);
  13641. this.dom.foreground.appendChild(label);
  13642. }
  13643. this.dom.majorTexts.push(label);
  13644. label.childNodes[0].nodeValue = text;
  13645. label.className = 'text major ' + className;
  13646. //label.title = title; // TODO: this is a heavy operation
  13647. label.style.top = (orientation == 'top') ? '0' : (this.props.minorLabelHeight + 'px');
  13648. label.style.left = x + 'px';
  13649. };
  13650. /**
  13651. * Create a minor line for the axis at position x
  13652. * @param {Number} x
  13653. * @param {String} orientation "top" or "bottom" (default)
  13654. * @param {String} className
  13655. * @return {Element} Returns the created line
  13656. * @private
  13657. */
  13658. TimeAxis.prototype._repaintMinorLine = function (x, orientation, className) {
  13659. // reuse redundant line
  13660. var line = this.dom.redundant.lines.shift();
  13661. if (!line) {
  13662. // create vertical line
  13663. line = document.createElement('div');
  13664. this.dom.background.appendChild(line);
  13665. }
  13666. this.dom.lines.push(line);
  13667. var props = this.props;
  13668. if (orientation == 'top') {
  13669. line.style.top = props.majorLabelHeight + 'px';
  13670. }
  13671. else {
  13672. line.style.top = this.body.domProps.top.height + 'px';
  13673. }
  13674. line.style.height = props.minorLineHeight + 'px';
  13675. line.style.left = (x - props.minorLineWidth / 2) + 'px';
  13676. line.className = 'grid vertical minor ' + className;
  13677. return line;
  13678. };
  13679. /**
  13680. * Create a Major line for the axis at position x
  13681. * @param {Number} x
  13682. * @param {String} orientation "top" or "bottom" (default)
  13683. * @param {String} className
  13684. * @return {Element} Returns the created line
  13685. * @private
  13686. */
  13687. TimeAxis.prototype._repaintMajorLine = function (x, orientation, className) {
  13688. // reuse redundant line
  13689. var line = this.dom.redundant.lines.shift();
  13690. if (!line) {
  13691. // create vertical line
  13692. line = document.createElement('div');
  13693. this.dom.background.appendChild(line);
  13694. }
  13695. this.dom.lines.push(line);
  13696. var props = this.props;
  13697. if (orientation == 'top') {
  13698. line.style.top = '0';
  13699. }
  13700. else {
  13701. line.style.top = this.body.domProps.top.height + 'px';
  13702. }
  13703. line.style.left = (x - props.majorLineWidth / 2) + 'px';
  13704. line.style.height = props.majorLineHeight + 'px';
  13705. line.className = 'grid vertical major ' + className;
  13706. return line;
  13707. };
  13708. /**
  13709. * Determine the size of text on the axis (both major and minor axis).
  13710. * The size is calculated only once and then cached in this.props.
  13711. * @private
  13712. */
  13713. TimeAxis.prototype._calculateCharSize = function () {
  13714. // Note: We calculate char size with every redraw. Size may change, for
  13715. // example when any of the timelines parents had display:none for example.
  13716. // determine the char width and height on the minor axis
  13717. if (!this.dom.measureCharMinor) {
  13718. this.dom.measureCharMinor = document.createElement('DIV');
  13719. this.dom.measureCharMinor.className = 'text minor measure';
  13720. this.dom.measureCharMinor.style.position = 'absolute';
  13721. this.dom.measureCharMinor.appendChild(document.createTextNode('0'));
  13722. this.dom.foreground.appendChild(this.dom.measureCharMinor);
  13723. }
  13724. this.props.minorCharHeight = this.dom.measureCharMinor.clientHeight;
  13725. this.props.minorCharWidth = this.dom.measureCharMinor.clientWidth;
  13726. // determine the char width and height on the major axis
  13727. if (!this.dom.measureCharMajor) {
  13728. this.dom.measureCharMajor = document.createElement('DIV');
  13729. this.dom.measureCharMajor.className = 'text major measure';
  13730. this.dom.measureCharMajor.style.position = 'absolute';
  13731. this.dom.measureCharMajor.appendChild(document.createTextNode('0'));
  13732. this.dom.foreground.appendChild(this.dom.measureCharMajor);
  13733. }
  13734. this.props.majorCharHeight = this.dom.measureCharMajor.clientHeight;
  13735. this.props.majorCharWidth = this.dom.measureCharMajor.clientWidth;
  13736. };
  13737. module.exports = TimeAxis;
  13738. /***/ },
  13739. /* 36 */
  13740. /***/ function(module, exports, __webpack_require__) {
  13741. var Emitter = __webpack_require__(56);
  13742. var Hammer = __webpack_require__(45);
  13743. var keycharm = __webpack_require__(57);
  13744. var util = __webpack_require__(1);
  13745. var hammerUtil = __webpack_require__(47);
  13746. var DataSet = __webpack_require__(3);
  13747. var DataView = __webpack_require__(4);
  13748. var dotparser = __webpack_require__(42);
  13749. var gephiParser = __webpack_require__(43);
  13750. var Groups = __webpack_require__(38);
  13751. var Images = __webpack_require__(39);
  13752. var Node = __webpack_require__(40);
  13753. var Edge = __webpack_require__(37);
  13754. var Popup = __webpack_require__(41);
  13755. var MixinLoader = __webpack_require__(52);
  13756. var Activator = __webpack_require__(53);
  13757. var locales = __webpack_require__(54);
  13758. // Load custom shapes into CanvasRenderingContext2D
  13759. __webpack_require__(55);
  13760. /**
  13761. * @constructor Network
  13762. * Create a network visualization, displaying nodes and edges.
  13763. *
  13764. * @param {Element} container The DOM element in which the Network will
  13765. * be created. Normally a div element.
  13766. * @param {Object} data An object containing parameters
  13767. * {Array} nodes
  13768. * {Array} edges
  13769. * @param {Object} options Options
  13770. */
  13771. function Network (container, data, options) {
  13772. if (!(this instanceof Network)) {
  13773. throw new SyntaxError('Constructor must be called with the new operator');
  13774. }
  13775. this._determineBrowserMethod();
  13776. this._initializeMixinLoaders();
  13777. // create variables and set default values
  13778. this.containerElement = container;
  13779. // render and calculation settings
  13780. this.renderRefreshRate = 60; // hz (fps)
  13781. this.renderTimestep = 1000 / this.renderRefreshRate; // ms -- saves calculation later on
  13782. this.renderTime = 0; // measured time it takes to render a frame
  13783. this.physicsTime = 0; // measured time it takes to render a frame
  13784. this.runDoubleSpeed = false;
  13785. this.physicsDiscreteStepsize = 0.50; // discrete stepsize of the simulation
  13786. this.initializing = true;
  13787. this.triggerFunctions = {add:null,edit:null,editEdge:null,connect:null,del:null};
  13788. var customScalingFunction = function (min,max,total,value) {
  13789. if (max == min) {
  13790. return 0.5;
  13791. }
  13792. else {
  13793. var scale = 1 / (max - min);
  13794. return Math.max(0,(value - min)*scale);
  13795. }
  13796. };
  13797. // set constant values
  13798. this.defaultOptions = {
  13799. nodes: {
  13800. customScalingFunction: customScalingFunction,
  13801. mass: 1,
  13802. radiusMin: 10,
  13803. radiusMax: 30,
  13804. radius: 10,
  13805. shape: 'ellipse',
  13806. image: undefined,
  13807. widthMin: 16, // px
  13808. widthMax: 64, // px
  13809. fontColor: 'black',
  13810. fontSize: 14, // px
  13811. fontFace: 'verdana',
  13812. fontFill: undefined,
  13813. fontStrokeWidth: 0, // px
  13814. fontStrokeColor: '#ffffff',
  13815. fontDrawThreshold: 3,
  13816. scaleFontWithValue: false,
  13817. fontSizeMin: 14,
  13818. fontSizeMax: 30,
  13819. fontSizeMaxVisible: 30,
  13820. level: -1,
  13821. color: {
  13822. border: '#2B7CE9',
  13823. background: '#97C2FC',
  13824. highlight: {
  13825. border: '#2B7CE9',
  13826. background: '#D2E5FF'
  13827. },
  13828. hover: {
  13829. border: '#2B7CE9',
  13830. background: '#D2E5FF'
  13831. }
  13832. },
  13833. group: undefined,
  13834. borderWidth: 1,
  13835. borderWidthSelected: undefined
  13836. },
  13837. edges: {
  13838. customScalingFunction: customScalingFunction,
  13839. widthMin: 1, //
  13840. widthMax: 15,//
  13841. width: 1,
  13842. widthSelectionMultiplier: 2,
  13843. hoverWidth: 1.5,
  13844. style: 'line',
  13845. color: {
  13846. color:'#848484',
  13847. highlight:'#848484',
  13848. hover: '#848484'
  13849. },
  13850. opacity:1.0,
  13851. fontColor: '#343434',
  13852. fontSize: 14, // px
  13853. fontFace: 'arial',
  13854. fontFill: 'white',
  13855. fontStrokeWidth: 0, // px
  13856. fontStrokeColor: 'white',
  13857. labelAlignment:'horizontal',
  13858. arrowScaleFactor: 1,
  13859. dash: {
  13860. length: 10,
  13861. gap: 5,
  13862. altLength: undefined
  13863. },
  13864. inheritColor: "from", // to, from, false, true (== from)
  13865. useGradients: false // release in 4.0
  13866. },
  13867. configurePhysics:false,
  13868. physics: {
  13869. barnesHut: {
  13870. enabled: true,
  13871. thetaInverted: 1 / 0.5, // inverted to save time during calculation
  13872. gravitationalConstant: -2000,
  13873. centralGravity: 0.3,
  13874. springLength: 95,
  13875. springConstant: 0.04,
  13876. damping: 0.09
  13877. },
  13878. repulsion: {
  13879. centralGravity: 0.0,
  13880. springLength: 200,
  13881. springConstant: 0.05,
  13882. nodeDistance: 100,
  13883. damping: 0.09
  13884. },
  13885. hierarchicalRepulsion: {
  13886. enabled: false,
  13887. centralGravity: 0.0,
  13888. springLength: 100,
  13889. springConstant: 0.01,
  13890. nodeDistance: 150,
  13891. damping: 0.09
  13892. },
  13893. damping: null,
  13894. centralGravity: null,
  13895. springLength: null,
  13896. springConstant: null
  13897. },
  13898. clustering: { // Per Node in Cluster = PNiC
  13899. enabled: false, // (Boolean) | global on/off switch for clustering.
  13900. initialMaxNodes: 100, // (# nodes) | if the initial amount of nodes is larger than this, we cluster until the total number is less than this threshold.
  13901. 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
  13902. 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
  13903. chainThreshold: 0.4, // (% of all drawn nodes)| maximum percentage of allowed chainnodes (long strings of connected nodes) within all nodes. (lower means less chains).
  13904. clusterEdgeThreshold: 20, // (px) | edge length threshold. if smaller, this node is clustered.
  13905. sectorThreshold: 100, // (# nodes in cluster) | cluster size threshold. If larger, expanding in own sector.
  13906. 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.
  13907. fontSizeMultiplier: 4.0, // (px PNiC) | how much the cluster font size grows per node in cluster (in px).
  13908. maxFontSize: 1000,
  13909. forceAmplification: 0.1, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster).
  13910. distanceAmplification: 0.1, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster).
  13911. edgeGrowth: 20, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
  13912. nodeScaling: {width: 1, // (px PNiC) | growth of the width per node in cluster.
  13913. height: 1, // (px PNiC) | growth of the height per node in cluster.
  13914. radius: 1}, // (px PNiC) | growth of the radius per node in cluster.
  13915. maxNodeSizeIncrements: 600, // (# increments) | max growth of the width per node in cluster.
  13916. activeAreaBoxSize: 80, // (px) | box area around the curser where clusters are popped open.
  13917. clusterLevelDifference: 2, // used for normalization of the cluster levels
  13918. clusterByZoom: true // enable clustering through zooming in and out
  13919. },
  13920. navigation: {
  13921. enabled: false
  13922. },
  13923. keyboard: {
  13924. enabled: false,
  13925. speed: {x: 10, y: 10, zoom: 0.02},
  13926. bindToWindow: true
  13927. },
  13928. dataManipulation: {
  13929. enabled: false,
  13930. initiallyVisible: false
  13931. },
  13932. hierarchicalLayout: {
  13933. enabled:false,
  13934. levelSeparation: 150,
  13935. nodeSpacing: 100,
  13936. direction: "UD", // UD, DU, LR, RL
  13937. layout: "hubsize" // hubsize, directed
  13938. },
  13939. freezeForStabilization: false,
  13940. smoothCurves: {
  13941. enabled: true,
  13942. dynamic: true,
  13943. type: "continuous",
  13944. roundness: 0.5
  13945. },
  13946. maxVelocity: 50,
  13947. minVelocity: 0.1, // px/s
  13948. stabilize: true, // stabilize before displaying the network
  13949. stabilizationIterations: 1000, // maximum number of iteration to stabilize
  13950. zoomExtentOnStabilize: true,
  13951. locale: 'en',
  13952. locales: locales,
  13953. tooltip: {
  13954. delay: 300,
  13955. fontColor: 'black',
  13956. fontSize: 14, // px
  13957. fontFace: 'verdana',
  13958. color: {
  13959. border: '#666',
  13960. background: '#FFFFC6'
  13961. }
  13962. },
  13963. dragNetwork: true,
  13964. dragNodes: true,
  13965. zoomable: true,
  13966. hover: false,
  13967. hideEdgesOnDrag: false,
  13968. hideNodesOnDrag: false,
  13969. width : '100%',
  13970. height : '100%',
  13971. selectable: true,
  13972. useDefaultGroups: true
  13973. };
  13974. this.constants = util.extend({}, this.defaultOptions);
  13975. this.pixelRatio = 1;
  13976. this.hoverObj = {nodes:{},edges:{}};
  13977. this.controlNodesActive = false;
  13978. this.navigationHammers = [];
  13979. this.manipulationHammers = [];
  13980. // animation properties
  13981. this.animationSpeed = 1/this.renderRefreshRate;
  13982. this.animationEasingFunction = "easeInOutQuint";
  13983. this.animating = false;
  13984. this.easingTime = 0;
  13985. this.sourceScale = 0;
  13986. this.targetScale = 0;
  13987. this.sourceTranslation = 0;
  13988. this.targetTranslation = 0;
  13989. this.lockedOnNodeId = null;
  13990. this.lockedOnNodeOffset = null;
  13991. this.touchTime = 0;
  13992. this.redrawRequested = false;
  13993. // Node variables
  13994. var network = this;
  13995. this.groups = new Groups(); // object with groups
  13996. this.images = new Images(); // object with images
  13997. this.images.setOnloadCallback(function (status) {
  13998. network._requestRedraw();
  13999. });
  14000. // keyboard navigation variables
  14001. this.xIncrement = 0;
  14002. this.yIncrement = 0;
  14003. this.zoomIncrement = 0;
  14004. // loading all the mixins:
  14005. // load the force calculation functions, grouped under the physics system.
  14006. this._loadPhysicsSystem();
  14007. // create a frame and canvas
  14008. this._create();
  14009. // load the sector system. (mandatory, fully integrated with Network)
  14010. this._loadSectorSystem();
  14011. // load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it)
  14012. this._loadClusterSystem();
  14013. // load the selection system. (mandatory, required by Network)
  14014. this._loadSelectionSystem();
  14015. // load the selection system. (mandatory, required by Network)
  14016. this._loadHierarchySystem();
  14017. // apply options
  14018. this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
  14019. this._setScale(1);
  14020. this.setOptions(options);
  14021. // other vars
  14022. this.freezeSimulationEnabled = false;// freeze the simulation
  14023. this.cachedFunctions = {};
  14024. this.startedStabilization = false;
  14025. this.stabilized = false;
  14026. this.stabilizationIterations = null;
  14027. this.draggingNodes = false;
  14028. // containers for nodes and edges
  14029. this.calculationNodes = {};
  14030. this.calculationNodeIndices = [];
  14031. this.nodeIndices = []; // array with all the indices of the nodes. Used to speed up forces calculation
  14032. this.nodes = {}; // object with Node objects
  14033. this.edges = {}; // object with Edge objects
  14034. // position and scale variables and objects
  14035. this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw.
  14036. this.canvasBottomRight = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  14037. this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  14038. this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action
  14039. this.scale = 1; // defining the global scale variable in the constructor
  14040. this.previousScale = this.scale; // this is used to check if the zoom operation is zooming in or out
  14041. // datasets or dataviews
  14042. this.nodesData = null; // A DataSet or DataView
  14043. this.edgesData = null; // A DataSet or DataView
  14044. // create event listeners used to subscribe on the DataSets of the nodes and edges
  14045. this.nodesListeners = {
  14046. 'add': function (event, params) {
  14047. network._addNodes(params.items);
  14048. network.start();
  14049. },
  14050. 'update': function (event, params) {
  14051. network._updateNodes(params.items);
  14052. network.start();
  14053. },
  14054. 'remove': function (event, params) {
  14055. network._removeNodes(params.items);
  14056. network.start();
  14057. }
  14058. };
  14059. this.edgesListeners = {
  14060. 'add': function (event, params) {
  14061. network._addEdges(params.items);
  14062. network.start();
  14063. },
  14064. 'update': function (event, params) {
  14065. network._updateEdges(params.items);
  14066. network.start();
  14067. },
  14068. 'remove': function (event, params) {
  14069. network._removeEdges(params.items);
  14070. network.start();
  14071. }
  14072. };
  14073. // properties for the animation
  14074. this.moving = true;
  14075. this.timer = undefined; // Scheduling function. Is definded in this.start();
  14076. // load data (the disable start variable will be the same as the enabled clustering)
  14077. this.setData(data,this.constants.clustering.enabled || this.constants.hierarchicalLayout.enabled);
  14078. // hierarchical layout
  14079. this.initializing = false;
  14080. if (this.constants.hierarchicalLayout.enabled == true) {
  14081. this._setupHierarchicalLayout();
  14082. }
  14083. else {
  14084. // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
  14085. if (this.constants.stabilize == false) {
  14086. this.zoomExtent({duration:0}, true, this.constants.clustering.enabled);
  14087. }
  14088. }
  14089. // if clustering is disabled, the simulation will have started in the setData function
  14090. if (this.constants.clustering.enabled) {
  14091. this.startWithClustering();
  14092. }
  14093. }
  14094. // Extend Network with an Emitter mixin
  14095. Emitter(Network.prototype);
  14096. /**
  14097. * Determine if the browser requires a setTimeout or a requestAnimationFrame. This was required because
  14098. * some implementations (safari and IE9) did not support requestAnimationFrame
  14099. * @private
  14100. */
  14101. Network.prototype._determineBrowserMethod = function() {
  14102. var browserType = navigator.userAgent.toLowerCase();
  14103. this.requiresTimeout = false;
  14104. if (browserType.indexOf('msie 9.0') != -1) { // IE 9
  14105. this.requiresTimeout = true;
  14106. }
  14107. else if (browserType.indexOf('safari') != -1) { // safari
  14108. if (browserType.indexOf('chrome') <= -1) {
  14109. this.requiresTimeout = true;
  14110. }
  14111. }
  14112. }
  14113. /**
  14114. * Get the script path where the vis.js library is located
  14115. *
  14116. * @returns {string | null} path Path or null when not found. Path does not
  14117. * end with a slash.
  14118. * @private
  14119. */
  14120. Network.prototype._getScriptPath = function() {
  14121. var scripts = document.getElementsByTagName( 'script' );
  14122. // find script named vis.js or vis.min.js
  14123. for (var i = 0; i < scripts.length; i++) {
  14124. var src = scripts[i].src;
  14125. var match = src && /\/?vis(.min)?\.js$/.exec(src);
  14126. if (match) {
  14127. // return path without the script name
  14128. return src.substring(0, src.length - match[0].length);
  14129. }
  14130. }
  14131. return null;
  14132. };
  14133. /**
  14134. * Find the center position of the network
  14135. * @private
  14136. */
  14137. Network.prototype._getRange = function(specificNodes) {
  14138. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  14139. if (specificNodes.length > 0) {
  14140. for (var i = 0; i < specificNodes.length; i++) {
  14141. node = this.nodes[specificNodes[i]];
  14142. if (minX > (node.boundingBox.left)) {
  14143. minX = node.boundingBox.left;
  14144. }
  14145. if (maxX < (node.boundingBox.right)) {
  14146. maxX = node.boundingBox.right;
  14147. }
  14148. if (minY > (node.boundingBox.bottom)) {
  14149. minY = node.boundingBox.top;
  14150. } // top is negative, bottom is positive
  14151. if (maxY < (node.boundingBox.top)) {
  14152. maxY = node.boundingBox.bottom;
  14153. } // top is negative, bottom is positive
  14154. }
  14155. }
  14156. else {
  14157. for (var nodeId in this.nodes) {
  14158. if (this.nodes.hasOwnProperty(nodeId)) {
  14159. node = this.nodes[nodeId];
  14160. if (minX > (node.boundingBox.left)) {
  14161. minX = node.boundingBox.left;
  14162. }
  14163. if (maxX < (node.boundingBox.right)) {
  14164. maxX = node.boundingBox.right;
  14165. }
  14166. if (minY > (node.boundingBox.bottom)) {
  14167. minY = node.boundingBox.top;
  14168. } // top is negative, bottom is positive
  14169. if (maxY < (node.boundingBox.top)) {
  14170. maxY = node.boundingBox.bottom;
  14171. } // top is negative, bottom is positive
  14172. }
  14173. }
  14174. }
  14175. if (minX == 1e9 && maxX == -1e9 && minY == 1e9 && maxY == -1e9) {
  14176. minY = 0, maxY = 0, minX = 0, maxX = 0;
  14177. }
  14178. return {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  14179. };
  14180. /**
  14181. * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  14182. * @returns {{x: number, y: number}}
  14183. * @private
  14184. */
  14185. Network.prototype._findCenter = function(range) {
  14186. return {x: (0.5 * (range.maxX + range.minX)),
  14187. y: (0.5 * (range.maxY + range.minY))};
  14188. };
  14189. /**
  14190. * This function zooms out to fit all data on screen based on amount of nodes
  14191. *
  14192. * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
  14193. * @param {Boolean} [disableStart] | If true, start is not called.
  14194. */
  14195. Network.prototype.zoomExtent = function(options, initialZoom, disableStart) {
  14196. this._redraw(true);
  14197. if (initialZoom === undefined) {initialZoom = false;}
  14198. if (disableStart === undefined) {disableStart = false;}
  14199. if (options === undefined) {options = {nodes:[]};}
  14200. if (options.nodes === undefined) {
  14201. options.nodes = [];
  14202. }
  14203. var range;
  14204. var zoomLevel;
  14205. if (initialZoom == true) {
  14206. // check if more than half of the nodes have a predefined position. If so, we use the range, not the approximation.
  14207. var positionDefined = 0;
  14208. for (var nodeId in this.nodes) {
  14209. if (this.nodes.hasOwnProperty(nodeId)) {
  14210. var node = this.nodes[nodeId];
  14211. if (node.predefinedPosition == true) {
  14212. positionDefined += 1;
  14213. }
  14214. }
  14215. }
  14216. if (positionDefined > 0.5 * this.nodeIndices.length) {
  14217. this.zoomExtent(options,false,disableStart);
  14218. return;
  14219. }
  14220. range = this._getRange(options.nodes);
  14221. var numberOfNodes = this.nodeIndices.length;
  14222. if (this.constants.smoothCurves == true) {
  14223. if (this.constants.clustering.enabled == true &&
  14224. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  14225. 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.
  14226. }
  14227. else {
  14228. zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  14229. }
  14230. }
  14231. else {
  14232. if (this.constants.clustering.enabled == true &&
  14233. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  14234. 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.
  14235. }
  14236. else {
  14237. zoomLevel = 30.5062972 / (numberOfNodes + 19.93597763) + 0.08413486; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  14238. }
  14239. }
  14240. // correct for larger canvasses.
  14241. var factor = Math.min(this.frame.canvas.clientWidth / 600, this.frame.canvas.clientHeight / 600);
  14242. zoomLevel *= factor;
  14243. }
  14244. else {
  14245. range = this._getRange(options.nodes);
  14246. var xDistance = Math.abs(range.maxX - range.minX) * 1.1;
  14247. var yDistance = Math.abs(range.maxY - range.minY) * 1.1;
  14248. var xZoomLevel = this.frame.canvas.clientWidth / xDistance;
  14249. var yZoomLevel = this.frame.canvas.clientHeight / yDistance;
  14250. zoomLevel = (xZoomLevel <= yZoomLevel) ? xZoomLevel : yZoomLevel;
  14251. }
  14252. if (zoomLevel > 1.0) {
  14253. zoomLevel = 1.0;
  14254. }
  14255. var center = this._findCenter(range);
  14256. if (disableStart == false) {
  14257. var options = {position: center, scale: zoomLevel, animation: options};
  14258. this.moveTo(options);
  14259. this.moving = true;
  14260. this.start();
  14261. }
  14262. else {
  14263. center.x *= zoomLevel;
  14264. center.y *= zoomLevel;
  14265. center.x -= 0.5 * this.frame.canvas.clientWidth;
  14266. center.y -= 0.5 * this.frame.canvas.clientHeight;
  14267. this._setScale(zoomLevel);
  14268. this._setTranslation(-center.x,-center.y);
  14269. }
  14270. };
  14271. /**
  14272. * Update the this.nodeIndices with the most recent node index list
  14273. * @private
  14274. */
  14275. Network.prototype._updateNodeIndexList = function() {
  14276. this._clearNodeIndexList();
  14277. for (var idx in this.nodes) {
  14278. if (this.nodes.hasOwnProperty(idx)) {
  14279. this.nodeIndices.push(idx);
  14280. }
  14281. }
  14282. };
  14283. /**
  14284. * Set nodes and edges, and optionally options as well.
  14285. *
  14286. * @param {Object} data Object containing parameters:
  14287. * {Array | DataSet | DataView} [nodes] Array with nodes
  14288. * {Array | DataSet | DataView} [edges] Array with edges
  14289. * {String} [dot] String containing data in DOT format
  14290. * {String} [gephi] String containing data in gephi JSON format
  14291. * {Options} [options] Object with options
  14292. * @param {Boolean} [disableStart] | optional: disable the calling of the start function.
  14293. */
  14294. Network.prototype.setData = function(data, disableStart) {
  14295. if (disableStart === undefined) {
  14296. disableStart = false;
  14297. }
  14298. // unselect all to ensure no selections from old data are carried over.
  14299. this._unselectAll(true);
  14300. // we set initializing to true to ensure that the hierarchical layout is not performed until both nodes and edges are added.
  14301. this.initializing = true;
  14302. if (data && data.dot && (data.nodes || data.edges)) {
  14303. throw new SyntaxError('Data must contain either parameter "dot" or ' +
  14304. ' parameter pair "nodes" and "edges", but not both.');
  14305. }
  14306. // 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.
  14307. if (this.constants.dataManipulation.enabled == true) {
  14308. this._createManipulatorBar();
  14309. }
  14310. // set options
  14311. this.setOptions(data && data.options);
  14312. // set all data
  14313. if (data && data.dot) {
  14314. // parse DOT file
  14315. if(data && data.dot) {
  14316. var dotData = dotparser.DOTToGraph(data.dot);
  14317. this.setData(dotData);
  14318. return;
  14319. }
  14320. }
  14321. else if (data && data.gephi) {
  14322. // parse DOT file
  14323. if(data && data.gephi) {
  14324. var gephiData = gephiParser.parseGephi(data.gephi);
  14325. this.setData(gephiData);
  14326. return;
  14327. }
  14328. }
  14329. else {
  14330. this._setNodes(data && data.nodes);
  14331. this._setEdges(data && data.edges);
  14332. }
  14333. this._putDataInSector();
  14334. if (disableStart == false) {
  14335. if (this.constants.hierarchicalLayout.enabled == true) {
  14336. this._resetLevels();
  14337. this._setupHierarchicalLayout();
  14338. }
  14339. else {
  14340. // find a stable position or start animating to a stable position
  14341. if (this.constants.stabilize == true) {
  14342. this._stabilize();
  14343. }
  14344. }
  14345. this.start();
  14346. }
  14347. this.initializing = false;
  14348. };
  14349. /**
  14350. * Set options
  14351. * @param {Object} options
  14352. */
  14353. Network.prototype.setOptions = function (options) {
  14354. if (options) {
  14355. var prop;
  14356. var fields = ['nodes','edges','smoothCurves','hierarchicalLayout','clustering','navigation',
  14357. 'keyboard','dataManipulation','onAdd','onEdit','onEditEdge','onConnect','onDelete','clickToUse'
  14358. ];
  14359. // extend all but the values in fields
  14360. util.selectiveNotDeepExtend(fields,this.constants, options);
  14361. util.selectiveNotDeepExtend(['color'],this.constants.nodes, options.nodes);
  14362. util.selectiveNotDeepExtend(['color','length'],this.constants.edges, options.edges);
  14363. this.groups.useDefaultGroups = this.constants.useDefaultGroups;
  14364. if (options.physics) {
  14365. util.mergeOptions(this.constants.physics, options.physics,'barnesHut');
  14366. util.mergeOptions(this.constants.physics, options.physics,'repulsion');
  14367. if (options.physics.hierarchicalRepulsion) {
  14368. this.constants.hierarchicalLayout.enabled = true;
  14369. this.constants.physics.hierarchicalRepulsion.enabled = true;
  14370. this.constants.physics.barnesHut.enabled = false;
  14371. for (prop in options.physics.hierarchicalRepulsion) {
  14372. if (options.physics.hierarchicalRepulsion.hasOwnProperty(prop)) {
  14373. this.constants.physics.hierarchicalRepulsion[prop] = options.physics.hierarchicalRepulsion[prop];
  14374. }
  14375. }
  14376. }
  14377. }
  14378. if (options.onAdd) {this.triggerFunctions.add = options.onAdd;}
  14379. if (options.onEdit) {this.triggerFunctions.edit = options.onEdit;}
  14380. if (options.onEditEdge) {this.triggerFunctions.editEdge = options.onEditEdge;}
  14381. if (options.onConnect) {this.triggerFunctions.connect = options.onConnect;}
  14382. if (options.onDelete) {this.triggerFunctions.del = options.onDelete;}
  14383. util.mergeOptions(this.constants, options,'smoothCurves');
  14384. util.mergeOptions(this.constants, options,'hierarchicalLayout');
  14385. util.mergeOptions(this.constants, options,'clustering');
  14386. util.mergeOptions(this.constants, options,'navigation');
  14387. util.mergeOptions(this.constants, options,'keyboard');
  14388. util.mergeOptions(this.constants, options,'dataManipulation');
  14389. if (options.dataManipulation) {
  14390. this.editMode = this.constants.dataManipulation.initiallyVisible;
  14391. }
  14392. // TODO: work out these options and document them
  14393. if (options.edges) {
  14394. if (options.edges.color !== undefined) {
  14395. if (util.isString(options.edges.color)) {
  14396. this.constants.edges.color = {};
  14397. this.constants.edges.color.color = options.edges.color;
  14398. this.constants.edges.color.highlight = options.edges.color;
  14399. this.constants.edges.color.hover = options.edges.color;
  14400. }
  14401. else {
  14402. if (options.edges.color.color !== undefined) {this.constants.edges.color.color = options.edges.color.color;}
  14403. if (options.edges.color.highlight !== undefined) {this.constants.edges.color.highlight = options.edges.color.highlight;}
  14404. if (options.edges.color.hover !== undefined) {this.constants.edges.color.hover = options.edges.color.hover;}
  14405. }
  14406. this.constants.edges.inheritColor = false;
  14407. }
  14408. if (!options.edges.fontColor) {
  14409. if (options.edges.color !== undefined) {
  14410. if (util.isString(options.edges.color)) {this.constants.edges.fontColor = options.edges.color;}
  14411. else if (options.edges.color.color !== undefined) {this.constants.edges.fontColor = options.edges.color.color;}
  14412. }
  14413. }
  14414. }
  14415. if (options.nodes) {
  14416. if (options.nodes.color) {
  14417. var newColorObj = util.parseColor(options.nodes.color);
  14418. this.constants.nodes.color.background = newColorObj.background;
  14419. this.constants.nodes.color.border = newColorObj.border;
  14420. this.constants.nodes.color.highlight.background = newColorObj.highlight.background;
  14421. this.constants.nodes.color.highlight.border = newColorObj.highlight.border;
  14422. this.constants.nodes.color.hover.background = newColorObj.hover.background;
  14423. this.constants.nodes.color.hover.border = newColorObj.hover.border;
  14424. }
  14425. }
  14426. if (options.groups) {
  14427. for (var groupname in options.groups) {
  14428. if (options.groups.hasOwnProperty(groupname)) {
  14429. var group = options.groups[groupname];
  14430. this.groups.add(groupname, group);
  14431. }
  14432. }
  14433. }
  14434. if (options.tooltip) {
  14435. for (prop in options.tooltip) {
  14436. if (options.tooltip.hasOwnProperty(prop)) {
  14437. this.constants.tooltip[prop] = options.tooltip[prop];
  14438. }
  14439. }
  14440. if (options.tooltip.color) {
  14441. this.constants.tooltip.color = util.parseColor(options.tooltip.color);
  14442. }
  14443. }
  14444. if ('clickToUse' in options) {
  14445. if (options.clickToUse) {
  14446. if (!this.activator) {
  14447. this.activator = new Activator(this.frame);
  14448. this.activator.on('change', this._createKeyBinds.bind(this));
  14449. }
  14450. }
  14451. else {
  14452. if (this.activator) {
  14453. this.activator.destroy();
  14454. delete this.activator;
  14455. }
  14456. }
  14457. }
  14458. if (options.labels) {
  14459. throw new Error('Option "labels" is deprecated. Use options "locale" and "locales" instead.');
  14460. }
  14461. // (Re)loading the mixins that can be enabled or disabled in the options.
  14462. // load the force calculation functions, grouped under the physics system.
  14463. this._loadPhysicsSystem();
  14464. // load the navigation system.
  14465. this._loadNavigationControls();
  14466. // load the data manipulation system
  14467. this._loadManipulationSystem();
  14468. // configure the smooth curves
  14469. this._configureSmoothCurves();
  14470. // bind hammer
  14471. this._bindHammer();
  14472. // bind keys. If disabled, this will not do anything;
  14473. this._createKeyBinds();
  14474. this._markAllEdgesAsDirty();
  14475. this.setSize(this.constants.width, this.constants.height);
  14476. this.moving = true;
  14477. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  14478. this._resetLevels();
  14479. this._setupHierarchicalLayout();
  14480. }
  14481. this.start();
  14482. }
  14483. };
  14484. /**
  14485. * Create the main frame for the Network.
  14486. * This function is executed once when a Network object is created. The frame
  14487. * contains a canvas, and this canvas contains all objects like the axis and
  14488. * nodes.
  14489. * @private
  14490. */
  14491. Network.prototype._create = function () {
  14492. // remove all elements from the container element.
  14493. while (this.containerElement.hasChildNodes()) {
  14494. this.containerElement.removeChild(this.containerElement.firstChild);
  14495. }
  14496. this.frame = document.createElement('div');
  14497. this.frame.className = 'vis network-frame';
  14498. this.frame.style.position = 'relative';
  14499. this.frame.style.overflow = 'hidden';
  14500. this.frame.tabIndex = 900;
  14501. //////////////////////////////////////////////////////////////////
  14502. this.frame.canvas = document.createElement("canvas");
  14503. this.frame.canvas.style.position = 'relative';
  14504. this.frame.appendChild(this.frame.canvas);
  14505. if (!this.frame.canvas.getContext) {
  14506. var noCanvas = document.createElement( 'DIV' );
  14507. noCanvas.style.color = 'red';
  14508. noCanvas.style.fontWeight = 'bold' ;
  14509. noCanvas.style.padding = '10px';
  14510. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  14511. this.frame.canvas.appendChild(noCanvas);
  14512. }
  14513. else {
  14514. var ctx = this.frame.canvas.getContext("2d");
  14515. this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio ||
  14516. ctx.mozBackingStorePixelRatio ||
  14517. ctx.msBackingStorePixelRatio ||
  14518. ctx.oBackingStorePixelRatio ||
  14519. ctx.backingStorePixelRatio || 1);
  14520. //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.
  14521. this.frame.canvas.getContext("2d").setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
  14522. }
  14523. this._bindHammer();
  14524. };
  14525. /**
  14526. * This function binds hammer, it can be repeated over and over due to the uniqueness check.
  14527. * @private
  14528. */
  14529. Network.prototype._bindHammer = function() {
  14530. var me = this;
  14531. if (this.hammer !== undefined) {
  14532. this.hammer.dispose();
  14533. }
  14534. this.drag = {};
  14535. this.pinch = {};
  14536. this.hammer = Hammer(this.frame.canvas, {
  14537. prevent_default: true
  14538. });
  14539. this.hammer.on('tap', me._onTap.bind(me) );
  14540. this.hammer.on('doubletap', me._onDoubleTap.bind(me) );
  14541. this.hammer.on('hold', me._onHold.bind(me) );
  14542. this.hammer.on('touch', me._onTouch.bind(me) );
  14543. this.hammer.on('dragstart', me._onDragStart.bind(me) );
  14544. this.hammer.on('drag', me._onDrag.bind(me) );
  14545. this.hammer.on('dragend', me._onDragEnd.bind(me) );
  14546. if (this.constants.zoomable == true) {
  14547. this.hammer.on('mousewheel', me._onMouseWheel.bind(me));
  14548. this.hammer.on('DOMMouseScroll', me._onMouseWheel.bind(me)); // for FF
  14549. this.hammer.on('pinch', me._onPinch.bind(me) );
  14550. }
  14551. this.hammer.on('mousemove', me._onMouseMoveTitle.bind(me) );
  14552. this.hammerFrame = Hammer(this.frame, {
  14553. prevent_default: true
  14554. });
  14555. this.hammerFrame.on('release', me._onRelease.bind(me) );
  14556. // add the frame to the container element
  14557. this.containerElement.appendChild(this.frame);
  14558. }
  14559. /**
  14560. * Binding the keys for keyboard navigation. These functions are defined in the NavigationMixin
  14561. * @private
  14562. */
  14563. Network.prototype._createKeyBinds = function() {
  14564. var me = this;
  14565. if (this.keycharm !== undefined) {
  14566. this.keycharm.destroy();
  14567. }
  14568. if (this.constants.keyboard.bindToWindow == true) {
  14569. this.keycharm = keycharm({container: window, preventDefault: false});
  14570. }
  14571. else {
  14572. this.keycharm = keycharm({container: this.frame, preventDefault: false});
  14573. }
  14574. this.keycharm.reset();
  14575. if (this.constants.keyboard.enabled && this.isActive()) {
  14576. this.keycharm.bind("up", this._moveUp.bind(me) , "keydown");
  14577. this.keycharm.bind("up", this._yStopMoving.bind(me), "keyup");
  14578. this.keycharm.bind("down", this._moveDown.bind(me) , "keydown");
  14579. this.keycharm.bind("down", this._yStopMoving.bind(me), "keyup");
  14580. this.keycharm.bind("left", this._moveLeft.bind(me) , "keydown");
  14581. this.keycharm.bind("left", this._xStopMoving.bind(me), "keyup");
  14582. this.keycharm.bind("right",this._moveRight.bind(me), "keydown");
  14583. this.keycharm.bind("right",this._xStopMoving.bind(me), "keyup");
  14584. this.keycharm.bind("=", this._zoomIn.bind(me), "keydown");
  14585. this.keycharm.bind("=", this._stopZoom.bind(me), "keyup");
  14586. this.keycharm.bind("num+", this._zoomIn.bind(me), "keydown");
  14587. this.keycharm.bind("num+", this._stopZoom.bind(me), "keyup");
  14588. this.keycharm.bind("num-", this._zoomOut.bind(me), "keydown");
  14589. this.keycharm.bind("num-", this._stopZoom.bind(me), "keyup");
  14590. this.keycharm.bind("-", this._zoomOut.bind(me), "keydown");
  14591. this.keycharm.bind("-", this._stopZoom.bind(me), "keyup");
  14592. this.keycharm.bind("[", this._zoomIn.bind(me), "keydown");
  14593. this.keycharm.bind("[", this._stopZoom.bind(me), "keyup");
  14594. this.keycharm.bind("]", this._zoomOut.bind(me), "keydown");
  14595. this.keycharm.bind("]", this._stopZoom.bind(me), "keyup");
  14596. this.keycharm.bind("pageup",this._zoomIn.bind(me), "keydown");
  14597. this.keycharm.bind("pageup",this._stopZoom.bind(me), "keyup");
  14598. this.keycharm.bind("pagedown",this._zoomOut.bind(me),"keydown");
  14599. this.keycharm.bind("pagedown",this._stopZoom.bind(me), "keyup");
  14600. }
  14601. if (this.constants.dataManipulation.enabled == true) {
  14602. this.keycharm.bind("esc",this._createManipulatorBar.bind(me));
  14603. this.keycharm.bind("delete",this._deleteSelected.bind(me));
  14604. }
  14605. };
  14606. /**
  14607. * Cleans up all bindings of the network, removing it fully from the memory IF the variable is set to null after calling this function.
  14608. * var network = new vis.Network(..);
  14609. * network.destroy();
  14610. * network = null;
  14611. */
  14612. Network.prototype.destroy = function() {
  14613. this.start = function () {};
  14614. this.redraw = function () {};
  14615. this.timer = false;
  14616. // cleanup physicsConfiguration if it exists
  14617. this._cleanupPhysicsConfiguration();
  14618. // remove keybindings
  14619. this.keycharm.reset();
  14620. // clear hammer bindings
  14621. this.hammer.dispose();
  14622. // clear events
  14623. this.off();
  14624. this._recursiveDOMDelete(this.containerElement);
  14625. }
  14626. Network.prototype._recursiveDOMDelete = function(DOMobject) {
  14627. while (DOMobject.hasChildNodes() == true) {
  14628. this._recursiveDOMDelete(DOMobject.firstChild);
  14629. DOMobject.removeChild(DOMobject.firstChild);
  14630. }
  14631. }
  14632. /**
  14633. * Get the pointer location from a touch location
  14634. * @param {{pageX: Number, pageY: Number}} touch
  14635. * @return {{x: Number, y: Number}} pointer
  14636. * @private
  14637. */
  14638. Network.prototype._getPointer = function (touch) {
  14639. return {
  14640. x: touch.pageX - util.getAbsoluteLeft(this.frame.canvas),
  14641. y: touch.pageY - util.getAbsoluteTop(this.frame.canvas)
  14642. };
  14643. };
  14644. /**
  14645. * On start of a touch gesture, store the pointer
  14646. * @param event
  14647. * @private
  14648. */
  14649. Network.prototype._onTouch = function (event) {
  14650. if (new Date().valueOf() - this.touchTime > 100) {
  14651. this.drag.pointer = this._getPointer(event.gesture.center);
  14652. this.drag.pinched = false;
  14653. this.pinch.scale = this._getScale();
  14654. // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame)
  14655. this.touchTime = new Date().valueOf();
  14656. this._handleTouch(this.drag.pointer);
  14657. }
  14658. };
  14659. /**
  14660. * handle drag start event
  14661. * @private
  14662. */
  14663. Network.prototype._onDragStart = function (event) {
  14664. this._handleDragStart(event);
  14665. };
  14666. /**
  14667. * This function is called by _onDragStart.
  14668. * It is separated out because we can then overload it for the datamanipulation system.
  14669. *
  14670. * @private
  14671. */
  14672. Network.prototype._handleDragStart = function(event) {
  14673. // in case the touch event was triggered on an external div, do the initial touch now.
  14674. if (this.drag.pointer === undefined) {
  14675. this._onTouch(event);
  14676. }
  14677. var node = this._getNodeAt(this.drag.pointer);
  14678. // note: drag.pointer is set in _onTouch to get the initial touch location
  14679. this.drag.dragging = true;
  14680. this.drag.selection = [];
  14681. this.drag.translation = this._getTranslation();
  14682. this.drag.nodeId = null;
  14683. this.draggingNodes = false;
  14684. if (node != null && this.constants.dragNodes == true) {
  14685. this.draggingNodes = true;
  14686. this.drag.nodeId = node.id;
  14687. // select the clicked node if not yet selected
  14688. if (!node.isSelected()) {
  14689. this._selectObject(node,false);
  14690. }
  14691. this.emit("dragStart",{nodeIds:this.getSelection().nodes});
  14692. // create an array with the selected nodes and their original location and status
  14693. for (var objectId in this.selectionObj.nodes) {
  14694. if (this.selectionObj.nodes.hasOwnProperty(objectId)) {
  14695. var object = this.selectionObj.nodes[objectId];
  14696. var s = {
  14697. id: object.id,
  14698. node: object,
  14699. // store original x, y, xFixed and yFixed, make the node temporarily Fixed
  14700. x: object.x,
  14701. y: object.y,
  14702. xFixed: object.xFixed,
  14703. yFixed: object.yFixed
  14704. };
  14705. object.xFixed = true;
  14706. object.yFixed = true;
  14707. this.drag.selection.push(s);
  14708. }
  14709. }
  14710. }
  14711. };
  14712. /**
  14713. * handle drag event
  14714. * @private
  14715. */
  14716. Network.prototype._onDrag = function (event) {
  14717. this._handleOnDrag(event)
  14718. };
  14719. /**
  14720. * This function is called by _onDrag.
  14721. * It is separated out because we can then overload it for the datamanipulation system.
  14722. *
  14723. * @private
  14724. */
  14725. Network.prototype._handleOnDrag = function(event) {
  14726. if (this.drag.pinched) {
  14727. return;
  14728. }
  14729. // remove the focus on node if it is focussed on by the focusOnNode
  14730. this.releaseNode();
  14731. var pointer = this._getPointer(event.gesture.center);
  14732. var me = this;
  14733. var drag = this.drag;
  14734. var selection = drag.selection;
  14735. if (selection && selection.length && this.constants.dragNodes == true) {
  14736. // calculate delta's and new location
  14737. var deltaX = pointer.x - drag.pointer.x;
  14738. var deltaY = pointer.y - drag.pointer.y;
  14739. // update position of all selected nodes
  14740. selection.forEach(function (s) {
  14741. var node = s.node;
  14742. if (!s.xFixed) {
  14743. node.x = me._XconvertDOMtoCanvas(me._XconvertCanvasToDOM(s.x) + deltaX);
  14744. }
  14745. if (!s.yFixed) {
  14746. node.y = me._YconvertDOMtoCanvas(me._YconvertCanvasToDOM(s.y) + deltaY);
  14747. }
  14748. });
  14749. // start _animationStep if not yet running
  14750. if (!this.moving) {
  14751. this.moving = true;
  14752. this.start();
  14753. }
  14754. }
  14755. else {
  14756. // move the network
  14757. if (this.constants.dragNetwork == true) {
  14758. // if the drag was not started properly because the click started outside the network div, start it now.
  14759. if (this.drag.pointer === undefined) {
  14760. this._handleDragStart(event);
  14761. return;
  14762. }
  14763. var diffX = pointer.x - this.drag.pointer.x;
  14764. var diffY = pointer.y - this.drag.pointer.y;
  14765. this._setTranslation(
  14766. this.drag.translation.x + diffX,
  14767. this.drag.translation.y + diffY
  14768. );
  14769. this._redraw();
  14770. }
  14771. }
  14772. };
  14773. /**
  14774. * handle drag start event
  14775. * @private
  14776. */
  14777. Network.prototype._onDragEnd = function (event) {
  14778. this._handleDragEnd(event);
  14779. };
  14780. Network.prototype._handleDragEnd = function(event) {
  14781. this.drag.dragging = false;
  14782. var selection = this.drag.selection;
  14783. if (selection && selection.length) {
  14784. selection.forEach(function (s) {
  14785. // restore original xFixed and yFixed
  14786. s.node.xFixed = s.xFixed;
  14787. s.node.yFixed = s.yFixed;
  14788. });
  14789. this.moving = true;
  14790. this.start();
  14791. }
  14792. else {
  14793. this._redraw();
  14794. }
  14795. if (this.draggingNodes == false) {
  14796. this.emit("dragEnd",{nodeIds:[]});
  14797. }
  14798. else {
  14799. this.emit("dragEnd",{nodeIds:this.getSelection().nodes});
  14800. }
  14801. }
  14802. /**
  14803. * handle tap/click event: select/unselect a node
  14804. * @private
  14805. */
  14806. Network.prototype._onTap = function (event) {
  14807. var pointer = this._getPointer(event.gesture.center);
  14808. this.pointerPosition = pointer;
  14809. this._handleTap(pointer);
  14810. };
  14811. /**
  14812. * handle doubletap event
  14813. * @private
  14814. */
  14815. Network.prototype._onDoubleTap = function (event) {
  14816. var pointer = this._getPointer(event.gesture.center);
  14817. this._handleDoubleTap(pointer);
  14818. };
  14819. /**
  14820. * handle long tap event: multi select nodes
  14821. * @private
  14822. */
  14823. Network.prototype._onHold = function (event) {
  14824. var pointer = this._getPointer(event.gesture.center);
  14825. this.pointerPosition = pointer;
  14826. this._handleOnHold(pointer);
  14827. };
  14828. /**
  14829. * handle the release of the screen
  14830. *
  14831. * @private
  14832. */
  14833. Network.prototype._onRelease = function (event) {
  14834. var pointer = this._getPointer(event.gesture.center);
  14835. this._handleOnRelease(pointer);
  14836. };
  14837. /**
  14838. * Handle pinch event
  14839. * @param event
  14840. * @private
  14841. */
  14842. Network.prototype._onPinch = function (event) {
  14843. var pointer = this._getPointer(event.gesture.center);
  14844. this.drag.pinched = true;
  14845. if (!('scale' in this.pinch)) {
  14846. this.pinch.scale = 1;
  14847. }
  14848. // TODO: enabled moving while pinching?
  14849. var scale = this.pinch.scale * event.gesture.scale;
  14850. this._zoom(scale, pointer)
  14851. };
  14852. /**
  14853. * Zoom the network in or out
  14854. * @param {Number} scale a number around 1, and between 0.01 and 10
  14855. * @param {{x: Number, y: Number}} pointer Position on screen
  14856. * @return {Number} appliedScale scale is limited within the boundaries
  14857. * @private
  14858. */
  14859. Network.prototype._zoom = function(scale, pointer) {
  14860. if (this.constants.zoomable == true) {
  14861. var scaleOld = this._getScale();
  14862. if (scale < 0.00001) {
  14863. scale = 0.00001;
  14864. }
  14865. if (scale > 10) {
  14866. scale = 10;
  14867. }
  14868. var preScaleDragPointer = null;
  14869. if (this.drag !== undefined) {
  14870. if (this.drag.dragging == true) {
  14871. preScaleDragPointer = this.DOMtoCanvas(this.drag.pointer);
  14872. }
  14873. }
  14874. // + this.frame.canvas.clientHeight / 2
  14875. var translation = this._getTranslation();
  14876. var scaleFrac = scale / scaleOld;
  14877. var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
  14878. var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
  14879. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  14880. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  14881. this._setScale(scale);
  14882. this._setTranslation(tx, ty);
  14883. this.updateClustersDefault();
  14884. if (preScaleDragPointer != null) {
  14885. var postScaleDragPointer = this.canvasToDOM(preScaleDragPointer);
  14886. this.drag.pointer.x = postScaleDragPointer.x;
  14887. this.drag.pointer.y = postScaleDragPointer.y;
  14888. }
  14889. this._redraw();
  14890. if (scaleOld < scale) {
  14891. this.emit("zoom", {direction:"+"});
  14892. }
  14893. else {
  14894. this.emit("zoom", {direction:"-"});
  14895. }
  14896. return scale;
  14897. }
  14898. };
  14899. /**
  14900. * Event handler for mouse wheel event, used to zoom the timeline
  14901. * See http://adomas.org/javascript-mouse-wheel/
  14902. * https://github.com/EightMedia/hammer.js/issues/256
  14903. * @param {MouseEvent} event
  14904. * @private
  14905. */
  14906. Network.prototype._onMouseWheel = function(event) {
  14907. // retrieve delta
  14908. var delta = 0;
  14909. if (event.wheelDelta) { /* IE/Opera. */
  14910. delta = event.wheelDelta/120;
  14911. } else if (event.detail) { /* Mozilla case. */
  14912. // In Mozilla, sign of delta is different than in IE.
  14913. // Also, delta is multiple of 3.
  14914. delta = -event.detail/3;
  14915. }
  14916. // If delta is nonzero, handle it.
  14917. // Basically, delta is now positive if wheel was scrolled up,
  14918. // and negative, if wheel was scrolled down.
  14919. if (delta) {
  14920. // calculate the new scale
  14921. var scale = this._getScale();
  14922. var zoom = delta / 10;
  14923. if (delta < 0) {
  14924. zoom = zoom / (1 - zoom);
  14925. }
  14926. scale *= (1 + zoom);
  14927. // calculate the pointer location
  14928. var gesture = hammerUtil.fakeGesture(this, event);
  14929. var pointer = this._getPointer(gesture.center);
  14930. // apply the new scale
  14931. this._zoom(scale, pointer);
  14932. }
  14933. // Prevent default actions caused by mouse wheel.
  14934. event.preventDefault();
  14935. };
  14936. /**
  14937. * Mouse move handler for checking whether the title moves over a node with a title.
  14938. * @param {Event} event
  14939. * @private
  14940. */
  14941. Network.prototype._onMouseMoveTitle = function (event) {
  14942. var gesture = hammerUtil.fakeGesture(this, event);
  14943. var pointer = this._getPointer(gesture.center);
  14944. var popupVisible = false;
  14945. // check if the previously selected node is still selected
  14946. if (this.popup !== undefined) {
  14947. if (this.popup.hidden === false) {
  14948. this._checkHidePopup(pointer);
  14949. }
  14950. // if the popup was not hidden above
  14951. if (this.popup.hidden === false) {
  14952. popupVisible = true;
  14953. this.popup.setPosition(pointer.x + 3,pointer.y - 5)
  14954. this.popup.show();
  14955. }
  14956. }
  14957. // if we bind the keyboard to the div, we have to highlight it to use it. This highlights it on mouse over
  14958. if (this.constants.keyboard.bindToWindow == false && this.constants.keyboard.enabled == true) {
  14959. this.frame.focus();
  14960. }
  14961. // start a timeout that will check if the mouse is positioned above an element
  14962. if (popupVisible === false) {
  14963. var me = this;
  14964. var checkShow = function () {
  14965. me._checkShowPopup(pointer);
  14966. };
  14967. if (this.popupTimer) {
  14968. clearInterval(this.popupTimer); // stop any running calculationTimer
  14969. }
  14970. if (!this.drag.dragging) {
  14971. this.popupTimer = setTimeout(checkShow, this.constants.tooltip.delay);
  14972. }
  14973. }
  14974. /**
  14975. * Adding hover highlights
  14976. */
  14977. if (this.constants.hover == true) {
  14978. // removing all hover highlights
  14979. for (var edgeId in this.hoverObj.edges) {
  14980. if (this.hoverObj.edges.hasOwnProperty(edgeId)) {
  14981. this.hoverObj.edges[edgeId].hover = false;
  14982. delete this.hoverObj.edges[edgeId];
  14983. }
  14984. }
  14985. // adding hover highlights
  14986. var obj = this._getNodeAt(pointer);
  14987. if (obj == null) {
  14988. obj = this._getEdgeAt(pointer);
  14989. }
  14990. if (obj != null) {
  14991. this._hoverObject(obj);
  14992. }
  14993. // removing all node hover highlights except for the selected one.
  14994. for (var nodeId in this.hoverObj.nodes) {
  14995. if (this.hoverObj.nodes.hasOwnProperty(nodeId)) {
  14996. if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj == null) {
  14997. this._blurObject(this.hoverObj.nodes[nodeId]);
  14998. delete this.hoverObj.nodes[nodeId];
  14999. }
  15000. }
  15001. }
  15002. this.redraw();
  15003. }
  15004. };
  15005. /**
  15006. * Check if there is an element on the given position in the network
  15007. * (a node or edge). If so, and if this element has a title,
  15008. * show a popup window with its title.
  15009. *
  15010. * @param {{x:Number, y:Number}} pointer
  15011. * @private
  15012. */
  15013. Network.prototype._checkShowPopup = function (pointer) {
  15014. var obj = {
  15015. left: this._XconvertDOMtoCanvas(pointer.x),
  15016. top: this._YconvertDOMtoCanvas(pointer.y),
  15017. right: this._XconvertDOMtoCanvas(pointer.x),
  15018. bottom: this._YconvertDOMtoCanvas(pointer.y)
  15019. };
  15020. var id;
  15021. var previousPopupObjId = this.popupObj === undefined ? "" : this.popupObj.id;
  15022. var nodeUnderCursor = false;
  15023. var popupType = "node";
  15024. if (this.popupObj == undefined) {
  15025. // search the nodes for overlap, select the top one in case of multiple nodes
  15026. var nodes = this.nodes;
  15027. var overlappingNodes = [];
  15028. for (id in nodes) {
  15029. if (nodes.hasOwnProperty(id)) {
  15030. var node = nodes[id];
  15031. if (node.isOverlappingWith(obj)) {
  15032. if (node.getTitle() !== undefined) {
  15033. overlappingNodes.push(id);
  15034. }
  15035. }
  15036. }
  15037. }
  15038. if (overlappingNodes.length > 0) {
  15039. // if there are overlapping nodes, select the last one, this is the
  15040. // one which is drawn on top of the others
  15041. this.popupObj = this.nodes[overlappingNodes[overlappingNodes.length - 1]];
  15042. // if you hover over a node, the title of the edge is not supposed to be shown.
  15043. nodeUnderCursor = true;
  15044. }
  15045. }
  15046. if (this.popupObj === undefined && nodeUnderCursor == false) {
  15047. // search the edges for overlap
  15048. var edges = this.edges;
  15049. var overlappingEdges = [];
  15050. for (id in edges) {
  15051. if (edges.hasOwnProperty(id)) {
  15052. var edge = edges[id];
  15053. if (edge.connected && (edge.getTitle() !== undefined) &&
  15054. edge.isOverlappingWith(obj)) {
  15055. overlappingEdges.push(id);
  15056. }
  15057. }
  15058. }
  15059. if (overlappingEdges.length > 0) {
  15060. this.popupObj = this.edges[overlappingEdges[overlappingEdges.length - 1]];
  15061. popupType = "edge";
  15062. }
  15063. }
  15064. if (this.popupObj) {
  15065. // show popup message window
  15066. if (this.popupObj.id != previousPopupObjId) {
  15067. if (this.popup === undefined) {
  15068. this.popup = new Popup(this.frame, this.constants.tooltip);
  15069. }
  15070. this.popup.popupTargetType = popupType;
  15071. this.popup.popupTargetId = this.popupObj.id;
  15072. // adjust a small offset such that the mouse cursor is located in the
  15073. // bottom left location of the popup, and you can easily move over the
  15074. // popup area
  15075. this.popup.setPosition(pointer.x + 3, pointer.y - 5);
  15076. this.popup.setText(this.popupObj.getTitle());
  15077. this.popup.show();
  15078. }
  15079. }
  15080. else {
  15081. if (this.popup) {
  15082. this.popup.hide();
  15083. }
  15084. }
  15085. };
  15086. /**
  15087. * Check if the popup must be hidden, which is the case when the mouse is no
  15088. * longer hovering on the object
  15089. * @param {{x:Number, y:Number}} pointer
  15090. * @private
  15091. */
  15092. Network.prototype._checkHidePopup = function (pointer) {
  15093. var pointerObj = {
  15094. left: this._XconvertDOMtoCanvas(pointer.x),
  15095. top: this._YconvertDOMtoCanvas(pointer.y),
  15096. right: this._XconvertDOMtoCanvas(pointer.x),
  15097. bottom: this._YconvertDOMtoCanvas(pointer.y)
  15098. };
  15099. var stillOnObj = false;
  15100. if (this.popup.popupTargetType == 'node') {
  15101. stillOnObj = this.nodes[this.popup.popupTargetId].isOverlappingWith(pointerObj);
  15102. if (stillOnObj === true) {
  15103. var overNode = this._getNodeAt(pointer);
  15104. stillOnObj = overNode.id == this.popup.popupTargetId;
  15105. }
  15106. }
  15107. else {
  15108. if (this._getNodeAt(pointer) === null) {
  15109. stillOnObj = this.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj);
  15110. }
  15111. }
  15112. if (stillOnObj === false) {
  15113. this.popupObj = undefined;
  15114. this.popup.hide();
  15115. }
  15116. };
  15117. /**
  15118. * Set a new size for the network
  15119. * @param {string} width Width in pixels or percentage (for example '800px'
  15120. * or '50%')
  15121. * @param {string} height Height in pixels or percentage (for example '400px'
  15122. * or '30%')
  15123. */
  15124. Network.prototype.setSize = function(width, height) {
  15125. var emitEvent = false;
  15126. var oldWidth = this.frame.canvas.width;
  15127. var oldHeight = this.frame.canvas.height;
  15128. if (width != this.constants.width || height != this.constants.height || this.frame.style.width != width || this.frame.style.height != height) {
  15129. this.frame.style.width = width;
  15130. this.frame.style.height = height;
  15131. this.frame.canvas.style.width = '100%';
  15132. this.frame.canvas.style.height = '100%';
  15133. this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio;
  15134. this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio;
  15135. this.constants.width = width;
  15136. this.constants.height = height;
  15137. emitEvent = true;
  15138. }
  15139. else {
  15140. // this would adapt the width of the canvas to the width from 100% if and only if
  15141. // there is a change.
  15142. if (this.frame.canvas.width != this.frame.canvas.clientWidth * this.pixelRatio) {
  15143. this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio;
  15144. emitEvent = true;
  15145. }
  15146. if (this.frame.canvas.height != this.frame.canvas.clientHeight * this.pixelRatio) {
  15147. this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio;
  15148. emitEvent = true;
  15149. }
  15150. }
  15151. if (emitEvent == true) {
  15152. 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});
  15153. }
  15154. };
  15155. /**
  15156. * Set a data set with nodes for the network
  15157. * @param {Array | DataSet | DataView} nodes The data containing the nodes.
  15158. * @private
  15159. */
  15160. Network.prototype._setNodes = function(nodes) {
  15161. var oldNodesData = this.nodesData;
  15162. if (nodes instanceof DataSet || nodes instanceof DataView) {
  15163. this.nodesData = nodes;
  15164. }
  15165. else if (Array.isArray(nodes)) {
  15166. this.nodesData = new DataSet();
  15167. this.nodesData.add(nodes);
  15168. }
  15169. else if (!nodes) {
  15170. this.nodesData = new DataSet();
  15171. }
  15172. else {
  15173. throw new TypeError('Array or DataSet expected');
  15174. }
  15175. if (oldNodesData) {
  15176. // unsubscribe from old dataset
  15177. util.forEach(this.nodesListeners, function (callback, event) {
  15178. oldNodesData.off(event, callback);
  15179. });
  15180. }
  15181. // remove drawn nodes
  15182. this.nodes = {};
  15183. if (this.nodesData) {
  15184. // subscribe to new dataset
  15185. var me = this;
  15186. util.forEach(this.nodesListeners, function (callback, event) {
  15187. me.nodesData.on(event, callback);
  15188. });
  15189. // draw all new nodes
  15190. var ids = this.nodesData.getIds();
  15191. this._addNodes(ids);
  15192. }
  15193. this._updateSelection();
  15194. };
  15195. /**
  15196. * Add nodes
  15197. * @param {Number[] | String[]} ids
  15198. * @private
  15199. */
  15200. Network.prototype._addNodes = function(ids) {
  15201. var id;
  15202. for (var i = 0, len = ids.length; i < len; i++) {
  15203. id = ids[i];
  15204. var data = this.nodesData.get(id);
  15205. var node = new Node(data, this.images, this.groups, this.constants);
  15206. this.nodes[id] = node; // note: this may replace an existing node
  15207. if ((node.xFixed == false || node.yFixed == false) && (node.x === null || node.y === null)) {
  15208. var radius = 10 * 0.1*ids.length + 10;
  15209. var angle = 2 * Math.PI * Math.random();
  15210. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  15211. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  15212. }
  15213. this.moving = true;
  15214. }
  15215. this._updateNodeIndexList();
  15216. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  15217. this._resetLevels();
  15218. this._setupHierarchicalLayout();
  15219. }
  15220. this._updateCalculationNodes();
  15221. this._reconnectEdges();
  15222. this._updateValueRange(this.nodes);
  15223. this.updateLabels();
  15224. };
  15225. /**
  15226. * Update existing nodes, or create them when not yet existing
  15227. * @param {Number[] | String[]} ids
  15228. * @private
  15229. */
  15230. Network.prototype._updateNodes = function(ids) {
  15231. var nodesData = this.nodesData.get(ids);
  15232. var nodes = this.nodes;
  15233. for (var i = 0, len = ids.length; i < len; i++) {
  15234. var id = ids[i];
  15235. var node = nodes[id];
  15236. var data = nodesData[i];
  15237. if (node) {
  15238. // update node
  15239. node.setProperties(data, this.constants);
  15240. }
  15241. else {
  15242. // create node
  15243. node = new Node(properties, this.images, this.groups, this.constants);
  15244. nodes[id] = node;
  15245. }
  15246. }
  15247. this.moving = true;
  15248. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  15249. this._resetLevels();
  15250. this._setupHierarchicalLayout();
  15251. }
  15252. this._updateNodeIndexList();
  15253. this._updateValueRange(nodes);
  15254. this._markAllEdgesAsDirty();
  15255. };
  15256. Network.prototype._markAllEdgesAsDirty = function() {
  15257. for (var edgeId in this.edges) {
  15258. this.edges[edgeId].colorDirty = true;
  15259. }
  15260. }
  15261. /**
  15262. * Remove existing nodes. If nodes do not exist, the method will just ignore it.
  15263. * @param {Number[] | String[]} ids
  15264. * @private
  15265. */
  15266. Network.prototype._removeNodes = function(ids) {
  15267. var nodes = this.nodes;
  15268. // remove from selection
  15269. for (var i = 0, len = ids.length; i < len; i++) {
  15270. if (this.selectionObj.nodes[ids[i]] !== undefined) {
  15271. this.nodes[ids[i]].unselect();
  15272. this._removeFromSelection(this.nodes[ids[i]]);
  15273. }
  15274. }
  15275. for (var i = 0, len = ids.length; i < len; i++) {
  15276. var id = ids[i];
  15277. delete nodes[id];
  15278. }
  15279. this._updateNodeIndexList();
  15280. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  15281. this._resetLevels();
  15282. this._setupHierarchicalLayout();
  15283. }
  15284. this._updateCalculationNodes();
  15285. this._reconnectEdges();
  15286. this._updateSelection();
  15287. this._updateValueRange(nodes);
  15288. };
  15289. /**
  15290. * Load edges by reading the data table
  15291. * @param {Array | DataSet | DataView} edges The data containing the edges.
  15292. * @private
  15293. * @private
  15294. */
  15295. Network.prototype._setEdges = function(edges) {
  15296. var oldEdgesData = this.edgesData;
  15297. if (edges instanceof DataSet || edges instanceof DataView) {
  15298. this.edgesData = edges;
  15299. }
  15300. else if (Array.isArray(edges)) {
  15301. this.edgesData = new DataSet();
  15302. this.edgesData.add(edges);
  15303. }
  15304. else if (!edges) {
  15305. this.edgesData = new DataSet();
  15306. }
  15307. else {
  15308. throw new TypeError('Array or DataSet expected');
  15309. }
  15310. if (oldEdgesData) {
  15311. // unsubscribe from old dataset
  15312. util.forEach(this.edgesListeners, function (callback, event) {
  15313. oldEdgesData.off(event, callback);
  15314. });
  15315. }
  15316. // remove drawn edges
  15317. this.edges = {};
  15318. if (this.edgesData) {
  15319. // subscribe to new dataset
  15320. var me = this;
  15321. util.forEach(this.edgesListeners, function (callback, event) {
  15322. me.edgesData.on(event, callback);
  15323. });
  15324. // draw all new nodes
  15325. var ids = this.edgesData.getIds();
  15326. this._addEdges(ids);
  15327. }
  15328. this._reconnectEdges();
  15329. };
  15330. /**
  15331. * Add edges
  15332. * @param {Number[] | String[]} ids
  15333. * @private
  15334. */
  15335. Network.prototype._addEdges = function (ids) {
  15336. var edges = this.edges,
  15337. edgesData = this.edgesData;
  15338. for (var i = 0, len = ids.length; i < len; i++) {
  15339. var id = ids[i];
  15340. var oldEdge = edges[id];
  15341. if (oldEdge) {
  15342. oldEdge.disconnect();
  15343. }
  15344. var data = edgesData.get(id, {"showInternalIds" : true});
  15345. edges[id] = new Edge(data, this, this.constants);
  15346. }
  15347. this.moving = true;
  15348. this._updateValueRange(edges);
  15349. this._createBezierNodes();
  15350. this._updateCalculationNodes();
  15351. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  15352. this._resetLevels();
  15353. this._setupHierarchicalLayout();
  15354. }
  15355. };
  15356. /**
  15357. * Update existing edges, or create them when not yet existing
  15358. * @param {Number[] | String[]} ids
  15359. * @private
  15360. */
  15361. Network.prototype._updateEdges = function (ids) {
  15362. var edges = this.edges,
  15363. edgesData = this.edgesData;
  15364. for (var i = 0, len = ids.length; i < len; i++) {
  15365. var id = ids[i];
  15366. var data = edgesData.get(id);
  15367. var edge = edges[id];
  15368. if (edge) {
  15369. // update edge
  15370. edge.disconnect();
  15371. edge.setProperties(data, this.constants);
  15372. edge.connect();
  15373. }
  15374. else {
  15375. // create edge
  15376. edge = new Edge(data, this, this.constants);
  15377. this.edges[id] = edge;
  15378. }
  15379. }
  15380. this._createBezierNodes();
  15381. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  15382. this._resetLevels();
  15383. this._setupHierarchicalLayout();
  15384. }
  15385. this.moving = true;
  15386. this._updateValueRange(edges);
  15387. };
  15388. /**
  15389. * Remove existing edges. Non existing ids will be ignored
  15390. * @param {Number[] | String[]} ids
  15391. * @private
  15392. */
  15393. Network.prototype._removeEdges = function (ids) {
  15394. var edges = this.edges;
  15395. // remove from selection
  15396. for (var i = 0, len = ids.length; i < len; i++) {
  15397. if (this.selectionObj.edges[ids[i]] !== undefined) {
  15398. edges[ids[i]].unselect();
  15399. this._removeFromSelection(edges[ids[i]]);
  15400. }
  15401. }
  15402. for (var i = 0, len = ids.length; i < len; i++) {
  15403. var id = ids[i];
  15404. var edge = edges[id];
  15405. if (edge) {
  15406. if (edge.via != null) {
  15407. delete this.sectors['support']['nodes'][edge.via.id];
  15408. }
  15409. edge.disconnect();
  15410. delete edges[id];
  15411. }
  15412. }
  15413. this.moving = true;
  15414. this._updateValueRange(edges);
  15415. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  15416. this._resetLevels();
  15417. this._setupHierarchicalLayout();
  15418. }
  15419. this._updateCalculationNodes();
  15420. };
  15421. /**
  15422. * Reconnect all edges
  15423. * @private
  15424. */
  15425. Network.prototype._reconnectEdges = function() {
  15426. var id,
  15427. nodes = this.nodes,
  15428. edges = this.edges;
  15429. for (id in nodes) {
  15430. if (nodes.hasOwnProperty(id)) {
  15431. nodes[id].edges = [];
  15432. nodes[id].dynamicEdges = [];
  15433. }
  15434. }
  15435. for (id in edges) {
  15436. if (edges.hasOwnProperty(id)) {
  15437. var edge = edges[id];
  15438. edge.from = null;
  15439. edge.to = null;
  15440. edge.connect();
  15441. }
  15442. }
  15443. };
  15444. /**
  15445. * Update the values of all object in the given array according to the current
  15446. * value range of the objects in the array.
  15447. * @param {Object} obj An object containing a set of Edges or Nodes
  15448. * The objects must have a method getValue() and
  15449. * setValueRange(min, max).
  15450. * @private
  15451. */
  15452. Network.prototype._updateValueRange = function(obj) {
  15453. var id;
  15454. // determine the range of the objects
  15455. var valueMin = undefined;
  15456. var valueMax = undefined;
  15457. var valueTotal = 0;
  15458. for (id in obj) {
  15459. if (obj.hasOwnProperty(id)) {
  15460. var value = obj[id].getValue();
  15461. if (value !== undefined) {
  15462. valueMin = (valueMin === undefined) ? value : Math.min(value, valueMin);
  15463. valueMax = (valueMax === undefined) ? value : Math.max(value, valueMax);
  15464. valueTotal += value;
  15465. }
  15466. }
  15467. }
  15468. // adjust the range of all objects
  15469. if (valueMin !== undefined && valueMax !== undefined) {
  15470. for (id in obj) {
  15471. if (obj.hasOwnProperty(id)) {
  15472. obj[id].setValueRange(valueMin, valueMax, valueTotal);
  15473. }
  15474. }
  15475. }
  15476. };
  15477. /**
  15478. * Redraw the network with the current data
  15479. * chart will be resized too.
  15480. */
  15481. Network.prototype.redraw = function() {
  15482. this.setSize(this.constants.width, this.constants.height);
  15483. this._redraw();
  15484. };
  15485. /**
  15486. * Redraw the network with the current data
  15487. * @param hidden | used to get the first estimate of the node sizes. only the nodes are drawn after which they are quickly drawn over.
  15488. * @private
  15489. */
  15490. Network.prototype._requestRedraw = function(hidden) {
  15491. if (this.redrawRequested !== true) {
  15492. this.redrawRequested = true;
  15493. if (this.requiresTimeout === true) {
  15494. window.setTimeout(this._redraw.bind(this, hidden),0);
  15495. }
  15496. else {
  15497. window.requestAnimationFrame(this._redraw.bind(this, hidden, true));
  15498. }
  15499. }
  15500. };
  15501. Network.prototype._redraw = function(hidden, requested) {
  15502. if (hidden === undefined) {
  15503. hidden = false;
  15504. }
  15505. this.redrawRequested = false;
  15506. var ctx = this.frame.canvas.getContext('2d');
  15507. ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
  15508. // clear the canvas
  15509. var w = this.frame.canvas.clientWidth;
  15510. var h = this.frame.canvas.clientHeight;
  15511. ctx.clearRect(0, 0, w, h);
  15512. // set scaling and translation
  15513. ctx.save();
  15514. ctx.translate(this.translation.x, this.translation.y);
  15515. ctx.scale(this.scale, this.scale);
  15516. this.canvasTopLeft = {
  15517. "x": this._XconvertDOMtoCanvas(0),
  15518. "y": this._YconvertDOMtoCanvas(0)
  15519. };
  15520. this.canvasBottomRight = {
  15521. "x": this._XconvertDOMtoCanvas(this.frame.canvas.clientWidth),
  15522. "y": this._YconvertDOMtoCanvas(this.frame.canvas.clientHeight)
  15523. };
  15524. if (hidden === false) {
  15525. this._doInAllSectors("_drawAllSectorNodes", ctx);
  15526. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideEdgesOnDrag == false) {
  15527. this._doInAllSectors("_drawEdges", ctx);
  15528. }
  15529. }
  15530. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideNodesOnDrag == false) {
  15531. this._doInAllSectors("_drawNodes",ctx,false);
  15532. }
  15533. if (hidden === false) {
  15534. if (this.controlNodesActive == true) {
  15535. this._doInAllSectors("_drawControlNodes", ctx);
  15536. }
  15537. }
  15538. // this._doInSupportSector("_drawNodes",ctx,true);
  15539. // this._drawTree(ctx,"#F00F0F");
  15540. // restore original scaling and translation
  15541. ctx.restore();
  15542. if (hidden === true) {
  15543. ctx.clearRect(0, 0, w, h);
  15544. }
  15545. }
  15546. /**
  15547. * Set the translation of the network
  15548. * @param {Number} offsetX Horizontal offset
  15549. * @param {Number} offsetY Vertical offset
  15550. * @private
  15551. */
  15552. Network.prototype._setTranslation = function(offsetX, offsetY) {
  15553. if (this.translation === undefined) {
  15554. this.translation = {
  15555. x: 0,
  15556. y: 0
  15557. };
  15558. }
  15559. if (offsetX !== undefined) {
  15560. this.translation.x = offsetX;
  15561. }
  15562. if (offsetY !== undefined) {
  15563. this.translation.y = offsetY;
  15564. }
  15565. this.emit('viewChanged');
  15566. };
  15567. /**
  15568. * Get the translation of the network
  15569. * @return {Object} translation An object with parameters x and y, both a number
  15570. * @private
  15571. */
  15572. Network.prototype._getTranslation = function() {
  15573. return {
  15574. x: this.translation.x,
  15575. y: this.translation.y
  15576. };
  15577. };
  15578. /**
  15579. * Scale the network
  15580. * @param {Number} scale Scaling factor 1.0 is unscaled
  15581. * @private
  15582. */
  15583. Network.prototype._setScale = function(scale) {
  15584. this.scale = scale;
  15585. };
  15586. /**
  15587. * Get the current scale of the network
  15588. * @return {Number} scale Scaling factor 1.0 is unscaled
  15589. * @private
  15590. */
  15591. Network.prototype._getScale = function() {
  15592. return this.scale;
  15593. };
  15594. /**
  15595. * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to
  15596. * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  15597. * @param {number} x
  15598. * @returns {number}
  15599. * @private
  15600. */
  15601. Network.prototype._XconvertDOMtoCanvas = function(x) {
  15602. return (x - this.translation.x) / this.scale;
  15603. };
  15604. /**
  15605. * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  15606. * the X coordinate in DOM-space (coordinate point in browser relative to the container div)
  15607. * @param {number} x
  15608. * @returns {number}
  15609. * @private
  15610. */
  15611. Network.prototype._XconvertCanvasToDOM = function(x) {
  15612. return x * this.scale + this.translation.x;
  15613. };
  15614. /**
  15615. * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to
  15616. * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  15617. * @param {number} y
  15618. * @returns {number}
  15619. * @private
  15620. */
  15621. Network.prototype._YconvertDOMtoCanvas = function(y) {
  15622. return (y - this.translation.y) / this.scale;
  15623. };
  15624. /**
  15625. * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  15626. * the Y coordinate in DOM-space (coordinate point in browser relative to the container div)
  15627. * @param {number} y
  15628. * @returns {number}
  15629. * @private
  15630. */
  15631. Network.prototype._YconvertCanvasToDOM = function(y) {
  15632. return y * this.scale + this.translation.y ;
  15633. };
  15634. /**
  15635. *
  15636. * @param {object} pos = {x: number, y: number}
  15637. * @returns {{x: number, y: number}}
  15638. * @constructor
  15639. */
  15640. Network.prototype.canvasToDOM = function (pos) {
  15641. return {x: this._XconvertCanvasToDOM(pos.x), y: this._YconvertCanvasToDOM(pos.y)};
  15642. };
  15643. /**
  15644. *
  15645. * @param {object} pos = {x: number, y: number}
  15646. * @returns {{x: number, y: number}}
  15647. * @constructor
  15648. */
  15649. Network.prototype.DOMtoCanvas = function (pos) {
  15650. return {x: this._XconvertDOMtoCanvas(pos.x), y: this._YconvertDOMtoCanvas(pos.y)};
  15651. };
  15652. /**
  15653. * Redraw all nodes
  15654. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  15655. * @param {CanvasRenderingContext2D} ctx
  15656. * @param {Boolean} [alwaysShow]
  15657. * @private
  15658. */
  15659. Network.prototype._drawNodes = function(ctx,alwaysShow) {
  15660. if (alwaysShow === undefined) {
  15661. alwaysShow = false;
  15662. }
  15663. // first draw the unselected nodes
  15664. var nodes = this.nodes;
  15665. var selected = [];
  15666. for (var id in nodes) {
  15667. if (nodes.hasOwnProperty(id)) {
  15668. nodes[id].setScaleAndPos(this.scale,this.canvasTopLeft,this.canvasBottomRight);
  15669. if (nodes[id].isSelected()) {
  15670. selected.push(id);
  15671. }
  15672. else {
  15673. if (nodes[id].inArea() || alwaysShow) {
  15674. nodes[id].draw(ctx);
  15675. }
  15676. }
  15677. }
  15678. }
  15679. // draw the selected nodes on top
  15680. for (var s = 0, sMax = selected.length; s < sMax; s++) {
  15681. if (nodes[selected[s]].inArea() || alwaysShow) {
  15682. nodes[selected[s]].draw(ctx);
  15683. }
  15684. }
  15685. };
  15686. /**
  15687. * Redraw all edges
  15688. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  15689. * @param {CanvasRenderingContext2D} ctx
  15690. * @private
  15691. */
  15692. Network.prototype._drawEdges = function(ctx) {
  15693. var edges = this.edges;
  15694. for (var id in edges) {
  15695. if (edges.hasOwnProperty(id)) {
  15696. var edge = edges[id];
  15697. edge.setScale(this.scale);
  15698. if (edge.connected) {
  15699. edges[id].draw(ctx);
  15700. }
  15701. }
  15702. }
  15703. };
  15704. /**
  15705. * Redraw all edges
  15706. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  15707. * @param {CanvasRenderingContext2D} ctx
  15708. * @private
  15709. */
  15710. Network.prototype._drawControlNodes = function(ctx) {
  15711. var edges = this.edges;
  15712. for (var id in edges) {
  15713. if (edges.hasOwnProperty(id)) {
  15714. edges[id]._drawControlNodes(ctx);
  15715. }
  15716. }
  15717. };
  15718. /**
  15719. * Find a stable position for all nodes
  15720. * @private
  15721. */
  15722. Network.prototype._stabilize = function() {
  15723. if (this.constants.freezeForStabilization == true) {
  15724. this._freezeDefinedNodes();
  15725. }
  15726. // find stable position
  15727. var count = 0;
  15728. while (this.moving && count < this.constants.stabilizationIterations) {
  15729. this._physicsTick();
  15730. count++;
  15731. }
  15732. if (this.constants.zoomExtentOnStabilize == true) {
  15733. this.zoomExtent({duration:0}, false, true);
  15734. }
  15735. if (this.constants.freezeForStabilization == true) {
  15736. this._restoreFrozenNodes();
  15737. }
  15738. this.emit("stabilizationIterationsDone");
  15739. };
  15740. /**
  15741. * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization
  15742. * because only the supportnodes for the smoothCurves have to settle.
  15743. *
  15744. * @private
  15745. */
  15746. Network.prototype._freezeDefinedNodes = function() {
  15747. var nodes = this.nodes;
  15748. for (var id in nodes) {
  15749. if (nodes.hasOwnProperty(id)) {
  15750. if (nodes[id].x != null && nodes[id].y != null) {
  15751. nodes[id].fixedData.x = nodes[id].xFixed;
  15752. nodes[id].fixedData.y = nodes[id].yFixed;
  15753. nodes[id].xFixed = true;
  15754. nodes[id].yFixed = true;
  15755. }
  15756. }
  15757. }
  15758. };
  15759. /**
  15760. * Unfreezes the nodes that have been frozen by _freezeDefinedNodes.
  15761. *
  15762. * @private
  15763. */
  15764. Network.prototype._restoreFrozenNodes = function() {
  15765. var nodes = this.nodes;
  15766. for (var id in nodes) {
  15767. if (nodes.hasOwnProperty(id)) {
  15768. if (nodes[id].fixedData.x != null) {
  15769. nodes[id].xFixed = nodes[id].fixedData.x;
  15770. nodes[id].yFixed = nodes[id].fixedData.y;
  15771. }
  15772. }
  15773. }
  15774. };
  15775. /**
  15776. * Check if any of the nodes is still moving
  15777. * @param {number} vmin the minimum velocity considered as 'moving'
  15778. * @return {boolean} true if moving, false if non of the nodes is moving
  15779. * @private
  15780. */
  15781. Network.prototype._isMoving = function(vmin) {
  15782. var nodes = this.nodes;
  15783. for (var id in nodes) {
  15784. if (nodes[id] !== undefined) {
  15785. if (nodes[id].isMoving(vmin) == true) {
  15786. return true;
  15787. }
  15788. }
  15789. }
  15790. return false;
  15791. };
  15792. /**
  15793. * /**
  15794. * Perform one discrete step for all nodes
  15795. *
  15796. * @private
  15797. */
  15798. Network.prototype._discreteStepNodes = function() {
  15799. var interval = this.physicsDiscreteStepsize;
  15800. var nodes = this.nodes;
  15801. var nodeId;
  15802. var nodesPresent = false;
  15803. if (this.constants.maxVelocity > 0) {
  15804. for (nodeId in nodes) {
  15805. if (nodes.hasOwnProperty(nodeId)) {
  15806. nodes[nodeId].discreteStepLimited(interval, this.constants.maxVelocity);
  15807. nodesPresent = true;
  15808. }
  15809. }
  15810. }
  15811. else {
  15812. for (nodeId in nodes) {
  15813. if (nodes.hasOwnProperty(nodeId)) {
  15814. nodes[nodeId].discreteStep(interval);
  15815. nodesPresent = true;
  15816. }
  15817. }
  15818. }
  15819. if (nodesPresent == true) {
  15820. var vminCorrected = this.constants.minVelocity / Math.max(this.scale,0.05);
  15821. if (vminCorrected > 0.5*this.constants.maxVelocity) {
  15822. return true;
  15823. }
  15824. else {
  15825. return this._isMoving(vminCorrected);
  15826. }
  15827. }
  15828. return false;
  15829. };
  15830. Network.prototype._revertPhysicsState = function() {
  15831. var nodes = this.nodes;
  15832. for (var nodeId in nodes) {
  15833. if (nodes.hasOwnProperty(nodeId)) {
  15834. nodes[nodeId].revertPosition();
  15835. }
  15836. }
  15837. }
  15838. Network.prototype._revertPhysicsTick = function() {
  15839. this._doInAllActiveSectors("_revertPhysicsState");
  15840. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  15841. this._doInSupportSector("_revertPhysicsState");
  15842. }
  15843. }
  15844. /**
  15845. * A single simulation step (or "tick") in the physics simulation
  15846. *
  15847. * @private
  15848. */
  15849. Network.prototype._physicsTick = function() {
  15850. if (!this.freezeSimulationEnabled) {
  15851. if (this.moving == true) {
  15852. var mainMovingStatus = false;
  15853. var supportMovingStatus = false;
  15854. this._doInAllActiveSectors("_initializeForceCalculation");
  15855. var mainMoving = this._doInAllActiveSectors("_discreteStepNodes");
  15856. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  15857. supportMovingStatus = this._doInSupportSector("_discreteStepNodes");
  15858. }
  15859. // gather movement data from all sectors, if one moves, we are NOT stabilzied
  15860. for (var i = 0; i < mainMoving.length; i++) {
  15861. mainMovingStatus = mainMoving[i] || mainMovingStatus;
  15862. }
  15863. // determine if the network has stabilzied
  15864. this.moving = mainMovingStatus || supportMovingStatus;
  15865. if (this.moving == false) {
  15866. this._revertPhysicsTick();
  15867. }
  15868. else {
  15869. // this is here to ensure that there is no start event when the network is already stable.
  15870. if (this.startedStabilization == false) {
  15871. this.emit("startStabilization");
  15872. this.startedStabilization = true;
  15873. }
  15874. }
  15875. this.stabilizationIterations++;
  15876. }
  15877. }
  15878. };
  15879. /**
  15880. * This function runs one step of the animation. It calls an x amount of physics ticks and one render tick.
  15881. * It reschedules itself at the beginning of the function
  15882. *
  15883. * @private
  15884. */
  15885. Network.prototype._animationStep = function() {
  15886. // reset the timer so a new scheduled animation step can be set
  15887. this.timer = undefined;
  15888. if (this.requiresTimeout == true) {
  15889. // this schedules a new animation step
  15890. this.start();
  15891. }
  15892. // handle the keyboad movement
  15893. this._handleNavigation();
  15894. // check if the physics have settled
  15895. if (this.moving == true) {
  15896. var startTime = Date.now();
  15897. this._physicsTick();
  15898. var physicsTime = Date.now() - startTime;
  15899. // run double speed if it is a little graph
  15900. if ((this.renderTimestep - this.renderTime > 2 * physicsTime || this.runDoubleSpeed == true) && this.moving == true) {
  15901. this._physicsTick();
  15902. // this makes sure there is no jitter. The decision is taken once to run it at double speed.
  15903. if (this.renderTime != 0) {
  15904. this.runDoubleSpeed = true
  15905. }
  15906. }
  15907. }
  15908. var renderStartTime = Date.now();
  15909. this._redraw();
  15910. this.renderTime = Date.now() - renderStartTime;
  15911. if (this.requiresTimeout == false) {
  15912. // this schedules a new animation step
  15913. this.start();
  15914. }
  15915. };
  15916. if (typeof window !== 'undefined') {
  15917. window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
  15918. window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  15919. }
  15920. /**
  15921. * Schedule a animation step with the refreshrate interval.
  15922. */
  15923. Network.prototype.start = function() {
  15924. if (this.freezeSimulationEnabled == true) {
  15925. this.moving = false;
  15926. }
  15927. if (this.moving == true || this.xIncrement != 0 || this.yIncrement != 0 || this.zoomIncrement != 0 || this.animating == true) {
  15928. if (!this.timer) {
  15929. if (this.requiresTimeout == true) {
  15930. this.timer = window.setTimeout(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
  15931. }
  15932. else {
  15933. this.timer = window.requestAnimationFrame(this._animationStep.bind(this)); // wait this.renderTimeStep milliseconds and perform the animation step function
  15934. }
  15935. }
  15936. }
  15937. else {
  15938. this._requestRedraw();
  15939. // 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())
  15940. if (this.stabilizationIterations > 1) {
  15941. // trigger the "stabilized" event.
  15942. // The event is triggered on the next tick, to prevent the case that
  15943. // it is fired while initializing the Network, in which case you would not
  15944. // be able to catch it
  15945. var me = this;
  15946. var params = {
  15947. iterations: me.stabilizationIterations
  15948. };
  15949. this.stabilizationIterations = 0;
  15950. this.startedStabilization = false;
  15951. setTimeout(function () {
  15952. me.emit("stabilized", params);
  15953. }, 0);
  15954. }
  15955. else {
  15956. this.stabilizationIterations = 0;
  15957. }
  15958. }
  15959. };
  15960. /**
  15961. * Move the network according to the keyboard presses.
  15962. *
  15963. * @private
  15964. */
  15965. Network.prototype._handleNavigation = function() {
  15966. if (this.xIncrement != 0 || this.yIncrement != 0) {
  15967. var translation = this._getTranslation();
  15968. this._setTranslation(translation.x+this.xIncrement, translation.y+this.yIncrement);
  15969. }
  15970. if (this.zoomIncrement != 0) {
  15971. var center = {
  15972. x: this.frame.canvas.clientWidth / 2,
  15973. y: this.frame.canvas.clientHeight / 2
  15974. };
  15975. this._zoom(this.scale*(1 + this.zoomIncrement), center);
  15976. }
  15977. };
  15978. /**
  15979. * Freeze the _animationStep
  15980. */
  15981. Network.prototype.freezeSimulation = function(freeze) {
  15982. if (freeze == true) {
  15983. this.freezeSimulationEnabled = true;
  15984. this.moving = false;
  15985. }
  15986. else {
  15987. this.freezeSimulationEnabled = false;
  15988. this.moving = true;
  15989. this.start();
  15990. }
  15991. };
  15992. /**
  15993. * This function cleans the support nodes if they are not needed and adds them when they are.
  15994. *
  15995. * @param {boolean} [disableStart]
  15996. * @private
  15997. */
  15998. Network.prototype._configureSmoothCurves = function(disableStart) {
  15999. if (disableStart === undefined) {
  16000. disableStart = true;
  16001. }
  16002. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  16003. this._createBezierNodes();
  16004. // cleanup unused support nodes
  16005. for (var nodeId in this.sectors['support']['nodes']) {
  16006. if (this.sectors['support']['nodes'].hasOwnProperty(nodeId)) {
  16007. if (this.edges[this.sectors['support']['nodes'][nodeId].parentEdgeId] === undefined) {
  16008. delete this.sectors['support']['nodes'][nodeId];
  16009. }
  16010. }
  16011. }
  16012. }
  16013. else {
  16014. // delete the support nodes
  16015. this.sectors['support']['nodes'] = {};
  16016. for (var edgeId in this.edges) {
  16017. if (this.edges.hasOwnProperty(edgeId)) {
  16018. this.edges[edgeId].via = null;
  16019. }
  16020. }
  16021. }
  16022. this._updateCalculationNodes();
  16023. if (!disableStart) {
  16024. this.moving = true;
  16025. this.start();
  16026. }
  16027. };
  16028. /**
  16029. * Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
  16030. * are used for the force calculation.
  16031. *
  16032. * @private
  16033. */
  16034. Network.prototype._createBezierNodes = function() {
  16035. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  16036. for (var edgeId in this.edges) {
  16037. if (this.edges.hasOwnProperty(edgeId)) {
  16038. var edge = this.edges[edgeId];
  16039. if (edge.via == null) {
  16040. var nodeId = "edgeId:".concat(edge.id);
  16041. this.sectors['support']['nodes'][nodeId] = new Node(
  16042. {id:nodeId,
  16043. mass:1,
  16044. shape:'circle',
  16045. image:"",
  16046. internalMultiplier:1
  16047. },{},{},this.constants);
  16048. edge.via = this.sectors['support']['nodes'][nodeId];
  16049. edge.via.parentEdgeId = edge.id;
  16050. edge.positionBezierNode();
  16051. }
  16052. }
  16053. }
  16054. }
  16055. };
  16056. /**
  16057. * load the functions that load the mixins into the prototype.
  16058. *
  16059. * @private
  16060. */
  16061. Network.prototype._initializeMixinLoaders = function () {
  16062. for (var mixin in MixinLoader) {
  16063. if (MixinLoader.hasOwnProperty(mixin)) {
  16064. Network.prototype[mixin] = MixinLoader[mixin];
  16065. }
  16066. }
  16067. };
  16068. /**
  16069. * Load the XY positions of the nodes into the dataset.
  16070. */
  16071. Network.prototype.storePosition = function() {
  16072. console.log("storePosition is depricated: use .storePositions() from now on.")
  16073. this.storePositions();
  16074. };
  16075. /**
  16076. * Load the XY positions of the nodes into the dataset.
  16077. */
  16078. Network.prototype.storePositions = function() {
  16079. var dataArray = [];
  16080. for (var nodeId in this.nodes) {
  16081. if (this.nodes.hasOwnProperty(nodeId)) {
  16082. var node = this.nodes[nodeId];
  16083. var allowedToMoveX = !this.nodes.xFixed;
  16084. var allowedToMoveY = !this.nodes.yFixed;
  16085. if (this.nodesData._data[nodeId].x != Math.round(node.x) || this.nodesData._data[nodeId].y != Math.round(node.y)) {
  16086. dataArray.push({id:nodeId,x:Math.round(node.x),y:Math.round(node.y),allowedToMoveX:allowedToMoveX,allowedToMoveY:allowedToMoveY});
  16087. }
  16088. }
  16089. }
  16090. this.nodesData.update(dataArray);
  16091. };
  16092. /**
  16093. * Return the positions of the nodes.
  16094. */
  16095. Network.prototype.getPositions = function(ids) {
  16096. var dataArray = {};
  16097. if (ids !== undefined) {
  16098. if (Array.isArray(ids) == true) {
  16099. for (var i = 0; i < ids.length; i++) {
  16100. if (this.nodes[ids[i]] !== undefined) {
  16101. var node = this.nodes[ids[i]];
  16102. dataArray[ids[i]] = {x: Math.round(node.x), y: Math.round(node.y)};
  16103. }
  16104. }
  16105. }
  16106. else {
  16107. if (this.nodes[ids] !== undefined) {
  16108. var node = this.nodes[ids];
  16109. dataArray[ids] = {x: Math.round(node.x), y: Math.round(node.y)};
  16110. }
  16111. }
  16112. }
  16113. else {
  16114. for (var nodeId in this.nodes) {
  16115. if (this.nodes.hasOwnProperty(nodeId)) {
  16116. var node = this.nodes[nodeId];
  16117. dataArray[nodeId] = {x: Math.round(node.x), y: Math.round(node.y)};
  16118. }
  16119. }
  16120. }
  16121. return dataArray;
  16122. };
  16123. /**
  16124. * Center a node in view.
  16125. *
  16126. * @param {Number} nodeId
  16127. * @param {Number} [options]
  16128. */
  16129. Network.prototype.focusOnNode = function (nodeId, options) {
  16130. if (this.nodes.hasOwnProperty(nodeId)) {
  16131. if (options === undefined) {
  16132. options = {};
  16133. }
  16134. var nodePosition = {x: this.nodes[nodeId].x, y: this.nodes[nodeId].y};
  16135. options.position = nodePosition;
  16136. options.lockedOnNode = nodeId;
  16137. this.moveTo(options)
  16138. }
  16139. else {
  16140. console.log("This nodeId cannot be found.");
  16141. }
  16142. };
  16143. /**
  16144. *
  16145. * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels
  16146. * | options.scale = Number // scale to move to
  16147. * | options.position = {x:Number, y:Number} // position to move to
  16148. * | options.animation = {duration:Number, easingFunction:String} || Boolean // position to move to
  16149. */
  16150. Network.prototype.moveTo = function (options) {
  16151. if (options === undefined) {
  16152. options = {};
  16153. return;
  16154. }
  16155. if (options.offset === undefined) {options.offset = {x: 0, y: 0}; }
  16156. if (options.offset.x === undefined) {options.offset.x = 0; }
  16157. if (options.offset.y === undefined) {options.offset.y = 0; }
  16158. if (options.scale === undefined) {options.scale = this._getScale(); }
  16159. if (options.position === undefined) {options.position = this._getTranslation();}
  16160. if (options.animation === undefined) {options.animation = {duration:0}; }
  16161. if (options.animation === false ) {options.animation = {duration:0}; }
  16162. if (options.animation === true ) {options.animation = {}; }
  16163. if (options.animation.duration === undefined) {options.animation.duration = 1000; } // default duration
  16164. if (options.animation.easingFunction === undefined) {options.animation.easingFunction = "easeInOutQuad"; } // default easing function
  16165. this.animateView(options);
  16166. };
  16167. /**
  16168. *
  16169. * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels
  16170. * | options.time = Number // animation time in milliseconds
  16171. * | options.scale = Number // scale to animate to
  16172. * | options.position = {x:Number, y:Number} // position to animate to
  16173. * | options.easingFunction = String // linear, easeInQuad, easeOutQuad, easeInOutQuad,
  16174. * // easeInCubic, easeOutCubic, easeInOutCubic,
  16175. * // easeInQuart, easeOutQuart, easeInOutQuart,
  16176. * // easeInQuint, easeOutQuint, easeInOutQuint
  16177. */
  16178. Network.prototype.animateView = function (options) {
  16179. if (options === undefined) {
  16180. options = {};
  16181. return;
  16182. }
  16183. // release if something focussed on the node
  16184. this.releaseNode();
  16185. if (options.locked == true) {
  16186. this.lockedOnNodeId = options.lockedOnNode;
  16187. this.lockedOnNodeOffset = options.offset;
  16188. }
  16189. // forcefully complete the old animation if it was still running
  16190. if (this.easingTime != 0) {
  16191. this._transitionRedraw(1); // by setting easingtime to 1, we finish the animation.
  16192. }
  16193. this.sourceScale = this._getScale();
  16194. this.sourceTranslation = this._getTranslation();
  16195. this.targetScale = options.scale;
  16196. // set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw
  16197. // but at least then we'll have the target transition
  16198. this._setScale(this.targetScale);
  16199. var viewCenter = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  16200. var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
  16201. x: viewCenter.x - options.position.x,
  16202. y: viewCenter.y - options.position.y
  16203. };
  16204. this.targetTranslation = {
  16205. x: this.sourceTranslation.x + distanceFromCenter.x * this.targetScale + options.offset.x,
  16206. y: this.sourceTranslation.y + distanceFromCenter.y * this.targetScale + options.offset.y
  16207. };
  16208. // if the time is set to 0, don't do an animation
  16209. if (options.animation.duration == 0) {
  16210. if (this.lockedOnNodeId != null) {
  16211. this._classicRedraw = this._redraw;
  16212. this._redraw = this._lockedRedraw;
  16213. }
  16214. else {
  16215. this._setScale(this.targetScale);
  16216. this._setTranslation(this.targetTranslation.x, this.targetTranslation.y);
  16217. this._redraw();
  16218. }
  16219. }
  16220. else {
  16221. this.animating = true;
  16222. this.animationSpeed = 1 / (this.renderRefreshRate * options.animation.duration * 0.001) || 1 / this.renderRefreshRate;
  16223. this.animationEasingFunction = options.animation.easingFunction;
  16224. this._classicRedraw = this._redraw;
  16225. this._redraw = this._transitionRedraw;
  16226. this._redraw();
  16227. this.start();
  16228. }
  16229. };
  16230. /**
  16231. * used to animate smoothly by hijacking the redraw function.
  16232. * @private
  16233. */
  16234. Network.prototype._lockedRedraw = function () {
  16235. var nodePosition = {x: this.nodes[this.lockedOnNodeId].x, y: this.nodes[this.lockedOnNodeId].y};
  16236. var viewCenter = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  16237. var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
  16238. x: viewCenter.x - nodePosition.x,
  16239. y: viewCenter.y - nodePosition.y
  16240. };
  16241. var sourceTranslation = this._getTranslation();
  16242. var targetTranslation = {
  16243. x: sourceTranslation.x + distanceFromCenter.x * this.scale + this.lockedOnNodeOffset.x,
  16244. y: sourceTranslation.y + distanceFromCenter.y * this.scale + this.lockedOnNodeOffset.y
  16245. };
  16246. this._setTranslation(targetTranslation.x,targetTranslation.y);
  16247. this._classicRedraw();
  16248. }
  16249. Network.prototype.releaseNode = function () {
  16250. if (this.lockedOnNodeId != null) {
  16251. this._redraw = this._classicRedraw;
  16252. this.lockedOnNodeId = null;
  16253. this.lockedOnNodeOffset = null;
  16254. }
  16255. }
  16256. /**
  16257. *
  16258. * @param easingTime
  16259. * @private
  16260. */
  16261. Network.prototype._transitionRedraw = function (easingTime) {
  16262. this.easingTime = easingTime || this.easingTime + this.animationSpeed;
  16263. this.easingTime += this.animationSpeed;
  16264. var progress = util.easingFunctions[this.animationEasingFunction](this.easingTime);
  16265. this._setScale(this.sourceScale + (this.targetScale - this.sourceScale) * progress);
  16266. this._setTranslation(
  16267. this.sourceTranslation.x + (this.targetTranslation.x - this.sourceTranslation.x) * progress,
  16268. this.sourceTranslation.y + (this.targetTranslation.y - this.sourceTranslation.y) * progress
  16269. );
  16270. this._classicRedraw();
  16271. // cleanup
  16272. if (this.easingTime >= 1.0) {
  16273. this.animating = false;
  16274. this.easingTime = 0;
  16275. if (this.lockedOnNodeId != null) {
  16276. this._redraw = this._lockedRedraw;
  16277. }
  16278. else {
  16279. this._redraw = this._classicRedraw;
  16280. }
  16281. this.emit("animationFinished");
  16282. }
  16283. };
  16284. Network.prototype._classicRedraw = function () {
  16285. // placeholder function to be overloaded by animations;
  16286. };
  16287. /**
  16288. * Returns true when the Network is active.
  16289. * @returns {boolean}
  16290. */
  16291. Network.prototype.isActive = function () {
  16292. return !this.activator || this.activator.active;
  16293. };
  16294. /**
  16295. * Sets the scale
  16296. * @returns {Number}
  16297. */
  16298. Network.prototype.setScale = function () {
  16299. return this._setScale();
  16300. };
  16301. /**
  16302. * Returns the scale
  16303. * @returns {Number}
  16304. */
  16305. Network.prototype.getScale = function () {
  16306. return this._getScale();
  16307. };
  16308. /**
  16309. * Returns the scale
  16310. * @returns {Number}
  16311. */
  16312. Network.prototype.getCenterCoordinates = function () {
  16313. return this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  16314. };
  16315. Network.prototype.getBoundingBox = function(nodeId) {
  16316. if (this.nodes[nodeId] !== undefined) {
  16317. return this.nodes[nodeId].boundingBox;
  16318. }
  16319. }
  16320. Network.prototype.getConnectedNodes = function(nodeId) {
  16321. var nodeList = [];
  16322. if (this.nodes[nodeId] !== undefined) {
  16323. var node = this.nodes[nodeId];
  16324. var nodeObj = {nodeId : true}; // used to quickly check if node already exists
  16325. for (var i = 0; i < node.edges.length; i++) {
  16326. var edge = node.edges[i];
  16327. if (edge.toId == nodeId) {
  16328. if (nodeObj[edge.fromId] === undefined) {
  16329. nodeList.push(edge.fromId);
  16330. nodeObj[edge.fromId] = true;
  16331. }
  16332. }
  16333. else if (edge.fromId == nodeId) {
  16334. if (nodeObj[edge.toId] === undefined) {
  16335. nodeList.push(edge.toId)
  16336. nodeObj[edge.toId] = true;
  16337. }
  16338. }
  16339. }
  16340. }
  16341. return nodeList;
  16342. }
  16343. Network.prototype.getEdgesFromNode = function(nodeId) {
  16344. var edgesList = [];
  16345. if (this.nodes[nodeId] !== undefined) {
  16346. var node = this.nodes[nodeId];
  16347. for (var i = 0; i < node.edges.length; i++) {
  16348. edgesList.push(node.edges[i].id);
  16349. }
  16350. }
  16351. return edgesList;
  16352. }
  16353. Network.prototype.generateColorObject = function(color) {
  16354. return util.parseColor(color);
  16355. }
  16356. module.exports = Network;
  16357. /***/ },
  16358. /* 37 */
  16359. /***/ function(module, exports, __webpack_require__) {
  16360. var util = __webpack_require__(1);
  16361. var Node = __webpack_require__(40);
  16362. /**
  16363. * @class Edge
  16364. *
  16365. * A edge connects two nodes
  16366. * @param {Object} properties Object with properties. Must contain
  16367. * At least properties from and to.
  16368. * Available properties: from (number),
  16369. * to (number), label (string, color (string),
  16370. * width (number), style (string),
  16371. * length (number), title (string)
  16372. * @param {Network} network A Network object, used to find and edge to
  16373. * nodes.
  16374. * @param {Object} constants An object with default values for
  16375. * example for the color
  16376. */
  16377. function Edge (properties, network, networkConstants) {
  16378. if (!network) {
  16379. throw "No network provided";
  16380. }
  16381. var fields = ['edges','physics'];
  16382. var constants = util.selectiveBridgeObject(fields,networkConstants);
  16383. this.options = constants.edges;
  16384. this.physics = constants.physics;
  16385. this.options['smoothCurves'] = networkConstants['smoothCurves'];
  16386. this.network = network;
  16387. // initialize variables
  16388. this.id = undefined;
  16389. this.fromId = undefined;
  16390. this.toId = undefined;
  16391. this.title = undefined;
  16392. this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
  16393. this.value = undefined;
  16394. this.selected = false;
  16395. this.hover = false;
  16396. this.labelDimensions = {top:0,left:0,width:0,height:0,yLine:0}; // could be cached
  16397. this.dirtyLabel = true;
  16398. this.colorDirty = true;
  16399. this.from = null; // a node
  16400. this.to = null; // a node
  16401. this.via = null; // a temp node
  16402. this.fromBackup = null; // used to clean up after reconnect
  16403. this.toBackup = null;; // used to clean up after reconnect
  16404. // we use this to be able to reconnect the edge to a cluster if its node is put into a cluster
  16405. // by storing the original information we can revert to the original connection when the cluser is opened.
  16406. this.originalFromId = [];
  16407. this.originalToId = [];
  16408. this.connected = false;
  16409. this.widthFixed = false;
  16410. this.lengthFixed = false;
  16411. this.setProperties(properties);
  16412. this.controlNodesEnabled = false;
  16413. this.controlNodes = {from:null, to:null, positions:{}};
  16414. this.connectedNode = null;
  16415. }
  16416. /**
  16417. * Set or overwrite properties for the edge
  16418. * @param {Object} properties an object with properties
  16419. * @param {Object} constants and object with default, global properties
  16420. */
  16421. Edge.prototype.setProperties = function(properties) {
  16422. this.colorDirty = true;
  16423. if (!properties) {
  16424. return;
  16425. }
  16426. var fields = ['style','fontSize','fontFace','fontColor','fontFill','fontStrokeWidth','fontStrokeColor','width',
  16427. 'widthSelectionMultiplier','hoverWidth','arrowScaleFactor','dash','inheritColor','labelAlignment', 'opacity',
  16428. 'customScalingFunction','useGradients'
  16429. ];
  16430. util.selectiveDeepExtend(fields, this.options, properties);
  16431. if (properties.from !== undefined) {this.fromId = properties.from;}
  16432. if (properties.to !== undefined) {this.toId = properties.to;}
  16433. if (properties.id !== undefined) {this.id = properties.id;}
  16434. if (properties.label !== undefined) {this.label = properties.label; this.dirtyLabel = true;}
  16435. if (properties.title !== undefined) {this.title = properties.title;}
  16436. if (properties.value !== undefined) {this.value = properties.value;}
  16437. if (properties.length !== undefined) {this.physics.springLength = properties.length;}
  16438. if (properties.color !== undefined) {
  16439. this.options.inheritColor = false;
  16440. if (util.isString(properties.color)) {
  16441. this.options.color.color = properties.color;
  16442. this.options.color.highlight = properties.color;
  16443. }
  16444. else {
  16445. if (properties.color.color !== undefined) {this.options.color.color = properties.color.color;}
  16446. if (properties.color.highlight !== undefined) {this.options.color.highlight = properties.color.highlight;}
  16447. if (properties.color.hover !== undefined) {this.options.color.hover = properties.color.hover;}
  16448. }
  16449. }
  16450. // A node is connected when it has a from and to node.
  16451. this.connect();
  16452. this.widthFixed = this.widthFixed || (properties.width !== undefined);
  16453. this.lengthFixed = this.lengthFixed || (properties.length !== undefined);
  16454. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  16455. // set draw method based on style
  16456. switch (this.options.style) {
  16457. case 'line': this.draw = this._drawLine; break;
  16458. case 'arrow': this.draw = this._drawArrow; break;
  16459. case 'arrow-center': this.draw = this._drawArrowCenter; break;
  16460. case 'dash-line': this.draw = this._drawDashLine; break;
  16461. default: this.draw = this._drawLine; break;
  16462. }
  16463. };
  16464. /**
  16465. * Connect an edge to its nodes
  16466. */
  16467. Edge.prototype.connect = function () {
  16468. this.disconnect();
  16469. this.from = this.network.nodes[this.fromId] || null;
  16470. this.to = this.network.nodes[this.toId] || null;
  16471. this.connected = (this.from && this.to);
  16472. if (this.connected) {
  16473. this.from.attachEdge(this);
  16474. this.to.attachEdge(this);
  16475. }
  16476. else {
  16477. if (this.from) {
  16478. this.from.detachEdge(this);
  16479. }
  16480. if (this.to) {
  16481. this.to.detachEdge(this);
  16482. }
  16483. }
  16484. };
  16485. /**
  16486. * Disconnect an edge from its nodes
  16487. */
  16488. Edge.prototype.disconnect = function () {
  16489. if (this.from) {
  16490. this.from.detachEdge(this);
  16491. this.from = null;
  16492. }
  16493. if (this.to) {
  16494. this.to.detachEdge(this);
  16495. this.to = null;
  16496. }
  16497. this.connected = false;
  16498. };
  16499. /**
  16500. * get the title of this edge.
  16501. * @return {string} title The title of the edge, or undefined when no title
  16502. * has been set.
  16503. */
  16504. Edge.prototype.getTitle = function() {
  16505. return typeof this.title === "function" ? this.title() : this.title;
  16506. };
  16507. /**
  16508. * Retrieve the value of the edge. Can be undefined
  16509. * @return {Number} value
  16510. */
  16511. Edge.prototype.getValue = function() {
  16512. return this.value;
  16513. };
  16514. /**
  16515. * Adjust the value range of the edge. The edge will adjust it's width
  16516. * based on its value.
  16517. * @param {Number} min
  16518. * @param {Number} max
  16519. */
  16520. Edge.prototype.setValueRange = function(min, max, total) {
  16521. if (!this.widthFixed && this.value !== undefined) {
  16522. var scale = this.options.customScalingFunction(min, max, total, this.value);
  16523. var widthDiff = this.options.widthMax - this.options.widthMin;
  16524. this.options.width = this.options.widthMin + scale * widthDiff;
  16525. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  16526. }
  16527. };
  16528. /**
  16529. * Redraw a edge
  16530. * Draw this edge in the given canvas
  16531. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  16532. * @param {CanvasRenderingContext2D} ctx
  16533. */
  16534. Edge.prototype.draw = function(ctx) {
  16535. throw "Method draw not initialized in edge";
  16536. };
  16537. /**
  16538. * Check if this object is overlapping with the provided object
  16539. * @param {Object} obj an object with parameters left, top
  16540. * @return {boolean} True if location is located on the edge
  16541. */
  16542. Edge.prototype.isOverlappingWith = function(obj) {
  16543. if (this.connected) {
  16544. var distMax = 10;
  16545. var xFrom = this.from.x;
  16546. var yFrom = this.from.y;
  16547. var xTo = this.to.x;
  16548. var yTo = this.to.y;
  16549. var xObj = obj.left;
  16550. var yObj = obj.top;
  16551. var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  16552. return (dist < distMax);
  16553. }
  16554. else {
  16555. return false
  16556. }
  16557. };
  16558. Edge.prototype._getColor = function(ctx) {
  16559. var colorObj = this.options.color;
  16560. if (this.options.useGradients == true) {
  16561. var grd = ctx.createLinearGradient(this.from.x, this.from.y, this.to.x, this.to.y);
  16562. var fromColor, toColor;
  16563. fromColor = this.from.options.color.highlight.border;
  16564. toColor = this.to.options.color.highlight.border;
  16565. if (this.from.selected == false && this.to.selected == false) {
  16566. fromColor = util.overrideOpacity(this.from.options.color.border, this.options.opacity);
  16567. toColor = util.overrideOpacity(this.to.options.color.border, this.options.opacity);
  16568. }
  16569. else if (this.from.selected == true && this.to.selected == false) {
  16570. toColor = this.to.options.color.border;
  16571. }
  16572. else if (this.from.selected == false && this.to.selected == true) {
  16573. fromColor = this.from.options.color.border;
  16574. }
  16575. grd.addColorStop(0, fromColor);
  16576. grd.addColorStop(1, toColor);
  16577. return grd;
  16578. }
  16579. if (this.colorDirty === true) {
  16580. if (this.options.inheritColor == "to") {
  16581. colorObj = {
  16582. highlight: this.to.options.color.highlight.border,
  16583. hover: this.to.options.color.hover.border,
  16584. color: util.overrideOpacity(this.from.options.color.border, this.options.opacity)
  16585. };
  16586. }
  16587. else if (this.options.inheritColor == "from" || this.options.inheritColor == true) {
  16588. colorObj = {
  16589. highlight: this.from.options.color.highlight.border,
  16590. hover: this.from.options.color.hover.border,
  16591. color: util.overrideOpacity(this.from.options.color.border, this.options.opacity)
  16592. };
  16593. }
  16594. this.options.color = colorObj;
  16595. this.colorDirty = false;
  16596. }
  16597. if (this.selected == true) {return colorObj.highlight;}
  16598. else if (this.hover == true) {return colorObj.hover;}
  16599. else {return colorObj.color;}
  16600. };
  16601. /**
  16602. * Redraw a edge as a line
  16603. * Draw this edge in the given canvas
  16604. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  16605. * @param {CanvasRenderingContext2D} ctx
  16606. * @private
  16607. */
  16608. Edge.prototype._drawLine = function(ctx) {
  16609. // set style
  16610. ctx.strokeStyle = this._getColor(ctx);
  16611. ctx.lineWidth = this._getLineWidth();
  16612. if (this.from != this.to) {
  16613. // draw line
  16614. var via = this._line(ctx);
  16615. // draw label
  16616. var point;
  16617. if (this.label) {
  16618. if (this.options.smoothCurves.enabled == true && via != null) {
  16619. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  16620. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  16621. point = {x:midpointX, y:midpointY};
  16622. }
  16623. else {
  16624. point = this._pointOnLine(0.5);
  16625. }
  16626. this._label(ctx, this.label, point.x, point.y);
  16627. }
  16628. }
  16629. else {
  16630. var x, y;
  16631. var radius = this.physics.springLength / 4;
  16632. var node = this.from;
  16633. if (!node.width) {
  16634. node.resize(ctx);
  16635. }
  16636. if (node.width > node.height) {
  16637. x = node.x + node.width / 2;
  16638. y = node.y - radius;
  16639. }
  16640. else {
  16641. x = node.x + radius;
  16642. y = node.y - node.height / 2;
  16643. }
  16644. this._circle(ctx, x, y, radius);
  16645. point = this._pointOnCircle(x, y, radius, 0.5);
  16646. this._label(ctx, this.label, point.x, point.y);
  16647. }
  16648. };
  16649. /**
  16650. * Get the line width of the edge. Depends on width and whether one of the
  16651. * connected nodes is selected.
  16652. * @return {Number} width
  16653. * @private
  16654. */
  16655. Edge.prototype._getLineWidth = function() {
  16656. if (this.selected == true) {
  16657. return Math.max(Math.min(this.widthSelected, this.options.widthMax), 0.3*this.networkScaleInv);
  16658. }
  16659. else {
  16660. if (this.hover == true) {
  16661. return Math.max(Math.min(this.options.hoverWidth, this.options.widthMax), 0.3*this.networkScaleInv);
  16662. }
  16663. else {
  16664. return Math.max(this.options.width, 0.3*this.networkScaleInv);
  16665. }
  16666. }
  16667. };
  16668. Edge.prototype._getViaCoordinates = function () {
  16669. if (this.options.smoothCurves.dynamic == true && this.options.smoothCurves.enabled == true ) {
  16670. return this.via;
  16671. }
  16672. else if (this.options.smoothCurves.enabled == false) {
  16673. return {x:0,y:0};
  16674. }
  16675. else {
  16676. var xVia = null;
  16677. var yVia = null;
  16678. var factor = this.options.smoothCurves.roundness;
  16679. var type = this.options.smoothCurves.type;
  16680. var dx = Math.abs(this.from.x - this.to.x);
  16681. var dy = Math.abs(this.from.y - this.to.y);
  16682. if (type == 'discrete' || type == 'diagonalCross') {
  16683. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  16684. if (this.from.y > this.to.y) {
  16685. if (this.from.x < this.to.x) {
  16686. xVia = this.from.x + factor * dy;
  16687. yVia = this.from.y - factor * dy;
  16688. }
  16689. else if (this.from.x > this.to.x) {
  16690. xVia = this.from.x - factor * dy;
  16691. yVia = this.from.y - factor * dy;
  16692. }
  16693. }
  16694. else if (this.from.y < this.to.y) {
  16695. if (this.from.x < this.to.x) {
  16696. xVia = this.from.x + factor * dy;
  16697. yVia = this.from.y + factor * dy;
  16698. }
  16699. else if (this.from.x > this.to.x) {
  16700. xVia = this.from.x - factor * dy;
  16701. yVia = this.from.y + factor * dy;
  16702. }
  16703. }
  16704. if (type == "discrete") {
  16705. xVia = dx < factor * dy ? this.from.x : xVia;
  16706. }
  16707. }
  16708. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  16709. if (this.from.y > this.to.y) {
  16710. if (this.from.x < this.to.x) {
  16711. xVia = this.from.x + factor * dx;
  16712. yVia = this.from.y - factor * dx;
  16713. }
  16714. else if (this.from.x > this.to.x) {
  16715. xVia = this.from.x - factor * dx;
  16716. yVia = this.from.y - factor * dx;
  16717. }
  16718. }
  16719. else if (this.from.y < this.to.y) {
  16720. if (this.from.x < this.to.x) {
  16721. xVia = this.from.x + factor * dx;
  16722. yVia = this.from.y + factor * dx;
  16723. }
  16724. else if (this.from.x > this.to.x) {
  16725. xVia = this.from.x - factor * dx;
  16726. yVia = this.from.y + factor * dx;
  16727. }
  16728. }
  16729. if (type == "discrete") {
  16730. yVia = dy < factor * dx ? this.from.y : yVia;
  16731. }
  16732. }
  16733. }
  16734. else if (type == "straightCross") {
  16735. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { // up - down
  16736. xVia = this.from.x;
  16737. if (this.from.y < this.to.y) {
  16738. yVia = this.to.y - (1 - factor) * dy;
  16739. }
  16740. else {
  16741. yVia = this.to.y + (1 - factor) * dy;
  16742. }
  16743. }
  16744. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { // left - right
  16745. if (this.from.x < this.to.x) {
  16746. xVia = this.to.x - (1 - factor) * dx;
  16747. }
  16748. else {
  16749. xVia = this.to.x + (1 - factor) * dx;
  16750. }
  16751. yVia = this.from.y;
  16752. }
  16753. }
  16754. else if (type == 'horizontal') {
  16755. if (this.from.x < this.to.x) {
  16756. xVia = this.to.x - (1 - factor) * dx;
  16757. }
  16758. else {
  16759. xVia = this.to.x + (1 - factor) * dx;
  16760. }
  16761. yVia = this.from.y;
  16762. }
  16763. else if (type == 'vertical') {
  16764. xVia = this.from.x;
  16765. if (this.from.y < this.to.y) {
  16766. yVia = this.to.y - (1 - factor) * dy;
  16767. }
  16768. else {
  16769. yVia = this.to.y + (1 - factor) * dy;
  16770. }
  16771. }
  16772. else if (type == 'curvedCW') {
  16773. var dx = this.to.x - this.from.x;
  16774. var dy = this.from.y - this.to.y;
  16775. var radius = Math.sqrt(dx*dx + dy*dy);
  16776. var pi = Math.PI;
  16777. var originalAngle = Math.atan2(dy,dx);
  16778. var myAngle = (originalAngle + ((factor * 0.5) + 0.5) * pi) % (2 * pi);
  16779. xVia = this.from.x + (factor*0.5 + 0.5)*radius*Math.sin(myAngle);
  16780. yVia = this.from.y + (factor*0.5 + 0.5)*radius*Math.cos(myAngle);
  16781. }
  16782. else if (type == 'curvedCCW') {
  16783. var dx = this.to.x - this.from.x;
  16784. var dy = this.from.y - this.to.y;
  16785. var radius = Math.sqrt(dx*dx + dy*dy);
  16786. var pi = Math.PI;
  16787. var originalAngle = Math.atan2(dy,dx);
  16788. var myAngle = (originalAngle + ((-factor * 0.5) + 0.5) * pi) % (2 * pi);
  16789. xVia = this.from.x + (factor*0.5 + 0.5)*radius*Math.sin(myAngle);
  16790. yVia = this.from.y + (factor*0.5 + 0.5)*radius*Math.cos(myAngle);
  16791. }
  16792. else { // continuous
  16793. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  16794. if (this.from.y > this.to.y) {
  16795. if (this.from.x < this.to.x) {
  16796. xVia = this.from.x + factor * dy;
  16797. yVia = this.from.y - factor * dy;
  16798. xVia = this.to.x < xVia ? this.to.x : xVia;
  16799. }
  16800. else if (this.from.x > this.to.x) {
  16801. xVia = this.from.x - factor * dy;
  16802. yVia = this.from.y - factor * dy;
  16803. xVia = this.to.x > xVia ? this.to.x : xVia;
  16804. }
  16805. }
  16806. else if (this.from.y < this.to.y) {
  16807. if (this.from.x < this.to.x) {
  16808. xVia = this.from.x + factor * dy;
  16809. yVia = this.from.y + factor * dy;
  16810. xVia = this.to.x < xVia ? this.to.x : xVia;
  16811. }
  16812. else if (this.from.x > this.to.x) {
  16813. xVia = this.from.x - factor * dy;
  16814. yVia = this.from.y + factor * dy;
  16815. xVia = this.to.x > xVia ? this.to.x : xVia;
  16816. }
  16817. }
  16818. }
  16819. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  16820. if (this.from.y > this.to.y) {
  16821. if (this.from.x < this.to.x) {
  16822. xVia = this.from.x + factor * dx;
  16823. yVia = this.from.y - factor * dx;
  16824. yVia = this.to.y > yVia ? this.to.y : yVia;
  16825. }
  16826. else if (this.from.x > this.to.x) {
  16827. xVia = this.from.x - factor * dx;
  16828. yVia = this.from.y - factor * dx;
  16829. yVia = this.to.y > yVia ? this.to.y : yVia;
  16830. }
  16831. }
  16832. else if (this.from.y < this.to.y) {
  16833. if (this.from.x < this.to.x) {
  16834. xVia = this.from.x + factor * dx;
  16835. yVia = this.from.y + factor * dx;
  16836. yVia = this.to.y < yVia ? this.to.y : yVia;
  16837. }
  16838. else if (this.from.x > this.to.x) {
  16839. xVia = this.from.x - factor * dx;
  16840. yVia = this.from.y + factor * dx;
  16841. yVia = this.to.y < yVia ? this.to.y : yVia;
  16842. }
  16843. }
  16844. }
  16845. }
  16846. return {x: xVia, y: yVia};
  16847. }
  16848. };
  16849. /**
  16850. * Draw a line between two nodes
  16851. * @param {CanvasRenderingContext2D} ctx
  16852. * @private
  16853. */
  16854. Edge.prototype._line = function (ctx) {
  16855. // draw a straight line
  16856. ctx.beginPath();
  16857. ctx.moveTo(this.from.x, this.from.y);
  16858. if (this.options.smoothCurves.enabled == true) {
  16859. if (this.options.smoothCurves.dynamic == false) {
  16860. var via = this._getViaCoordinates();
  16861. if (via.x == null) {
  16862. ctx.lineTo(this.to.x, this.to.y);
  16863. ctx.stroke();
  16864. return null;
  16865. }
  16866. else {
  16867. // this.via.x = via.x;
  16868. // this.via.y = via.y;
  16869. ctx.quadraticCurveTo(via.x,via.y,this.to.x, this.to.y);
  16870. ctx.stroke();
  16871. //ctx.circle(via.x,via.y,2)
  16872. //ctx.stroke();
  16873. return via;
  16874. }
  16875. }
  16876. else {
  16877. ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
  16878. ctx.stroke();
  16879. return this.via;
  16880. }
  16881. }
  16882. else {
  16883. ctx.lineTo(this.to.x, this.to.y);
  16884. ctx.stroke();
  16885. return null;
  16886. }
  16887. };
  16888. /**
  16889. * Draw a line from a node to itself, a circle
  16890. * @param {CanvasRenderingContext2D} ctx
  16891. * @param {Number} x
  16892. * @param {Number} y
  16893. * @param {Number} radius
  16894. * @private
  16895. */
  16896. Edge.prototype._circle = function (ctx, x, y, radius) {
  16897. // draw a circle
  16898. ctx.beginPath();
  16899. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  16900. ctx.stroke();
  16901. };
  16902. /**
  16903. * Draw label with white background and with the middle at (x, y)
  16904. * @param {CanvasRenderingContext2D} ctx
  16905. * @param {String} text
  16906. * @param {Number} x
  16907. * @param {Number} y
  16908. * @private
  16909. */
  16910. Edge.prototype._label = function (ctx, text, x, y) {
  16911. if (text) {
  16912. ctx.font = ((this.from.selected || this.to.selected) ? "bold " : "") +
  16913. this.options.fontSize + "px " + this.options.fontFace;
  16914. var yLine;
  16915. if (this.dirtyLabel == true) {
  16916. var lines = String(text).split('\n');
  16917. var lineCount = lines.length;
  16918. var fontSize = Number(this.options.fontSize);
  16919. yLine = y + (1 - lineCount) / 2 * fontSize;
  16920. var width = ctx.measureText(lines[0]).width;
  16921. for (var i = 1; i < lineCount; i++) {
  16922. var lineWidth = ctx.measureText(lines[i]).width;
  16923. width = lineWidth > width ? lineWidth : width;
  16924. }
  16925. var height = this.options.fontSize * lineCount;
  16926. var left = x - width / 2;
  16927. var top = y - height / 2;
  16928. // cache
  16929. this.labelDimensions = {top:top,left:left,width:width,height:height,yLine:yLine};
  16930. }
  16931. var yLine = this.labelDimensions.yLine;
  16932. ctx.save();
  16933. if (this.options.labelAlignment != "horizontal"){
  16934. ctx.translate(x, yLine);
  16935. this._rotateForLabelAlignment(ctx);
  16936. x = 0;
  16937. yLine = 0;
  16938. }
  16939. this._drawLabelRect(ctx);
  16940. this._drawLabelText(ctx,x,yLine, lines, lineCount, fontSize);
  16941. ctx.restore();
  16942. }
  16943. };
  16944. /**
  16945. * Rotates the canvas so the text is most readable
  16946. * @param {CanvasRenderingContext2D} ctx
  16947. * @private
  16948. */
  16949. Edge.prototype._rotateForLabelAlignment = function(ctx) {
  16950. var dy = this.from.y - this.to.y;
  16951. var dx = this.from.x - this.to.x;
  16952. var angleInDegrees = Math.atan2(dy, dx);
  16953. // rotate so label it is readable
  16954. if((angleInDegrees < -1 && dx < 0) || (angleInDegrees > 0 && dx < 0)){
  16955. angleInDegrees = angleInDegrees + Math.PI;
  16956. }
  16957. ctx.rotate(angleInDegrees);
  16958. };
  16959. /**
  16960. * Draws the label rectangle
  16961. * @param {CanvasRenderingContext2D} ctx
  16962. * @param {String} labelAlignment
  16963. * @private
  16964. */
  16965. Edge.prototype._drawLabelRect = function(ctx) {
  16966. if (this.options.fontFill !== undefined && this.options.fontFill !== null && this.options.fontFill !== "none") {
  16967. ctx.fillStyle = this.options.fontFill;
  16968. var lineMargin = 2;
  16969. if (this.options.labelAlignment == 'line-center') {
  16970. ctx.fillRect(-this.labelDimensions.width * 0.5, -this.labelDimensions.height * 0.5, this.labelDimensions.width, this.labelDimensions.height);
  16971. }
  16972. else if (this.options.labelAlignment == 'line-above') {
  16973. ctx.fillRect(-this.labelDimensions.width * 0.5, -(this.labelDimensions.height + lineMargin), this.labelDimensions.width, this.labelDimensions.height);
  16974. }
  16975. else if (this.options.labelAlignment == 'line-below') {
  16976. ctx.fillRect(-this.labelDimensions.width * 0.5, lineMargin, this.labelDimensions.width, this.labelDimensions.height);
  16977. }
  16978. else {
  16979. ctx.fillRect(this.labelDimensions.left, this.labelDimensions.top, this.labelDimensions.width, this.labelDimensions.height);
  16980. }
  16981. }
  16982. };
  16983. /**
  16984. * Draws the label text
  16985. * @param {CanvasRenderingContext2D} ctx
  16986. * @param {Number} x
  16987. * @param {Number} yLine
  16988. * @param {Array} lines
  16989. * @param {Number} lineCount
  16990. * @param {Number} fontSize
  16991. * @private
  16992. */
  16993. Edge.prototype._drawLabelText = function(ctx, x, yLine, lines, lineCount, fontSize) {
  16994. // draw text
  16995. ctx.fillStyle = this.options.fontColor || "black";
  16996. ctx.textAlign = "center";
  16997. // check for label alignment
  16998. if (this.options.labelAlignment != 'horizontal') {
  16999. var lineMargin = 2;
  17000. if (this.options.labelAlignment == 'line-above') {
  17001. ctx.textBaseline = "alphabetic";
  17002. yLine -= 2 * lineMargin; // distance from edge, required because we use alphabetic. Alphabetic has less difference between browsers
  17003. }
  17004. else if (this.options.labelAlignment == 'line-below') {
  17005. ctx.textBaseline = "hanging";
  17006. yLine += 2 * lineMargin;// distance from edge, required because we use hanging. Hanging has less difference between browsers
  17007. }
  17008. else {
  17009. ctx.textBaseline = "middle";
  17010. }
  17011. }
  17012. else {
  17013. ctx.textBaseline = "middle";
  17014. }
  17015. // check for strokeWidth
  17016. if (this.options.fontStrokeWidth > 0){
  17017. ctx.lineWidth = this.options.fontStrokeWidth;
  17018. ctx.strokeStyle = this.options.fontStrokeColor;
  17019. ctx.lineJoin = 'round';
  17020. }
  17021. for (var i = 0; i < lineCount; i++) {
  17022. if(this.options.fontStrokeWidth > 0){
  17023. ctx.strokeText(lines[i], x, yLine);
  17024. }
  17025. ctx.fillText(lines[i], x, yLine);
  17026. yLine += fontSize;
  17027. }
  17028. };
  17029. /**
  17030. * Redraw a edge as a dashed line
  17031. * Draw this edge in the given canvas
  17032. * @author David Jordan
  17033. * @date 2012-08-08
  17034. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  17035. * @param {CanvasRenderingContext2D} ctx
  17036. * @private
  17037. */
  17038. Edge.prototype._drawDashLine = function(ctx) {
  17039. // set style
  17040. ctx.strokeStyle = this._getColor(ctx);
  17041. ctx.lineWidth = this._getLineWidth();
  17042. var via = null;
  17043. // only firefox and chrome support this method, else we use the legacy one.
  17044. if (ctx.setLineDash !== undefined) {
  17045. ctx.save();
  17046. // configure the dash pattern
  17047. var pattern = [0];
  17048. if (this.options.dash.length !== undefined && this.options.dash.gap !== undefined) {
  17049. pattern = [this.options.dash.length,this.options.dash.gap];
  17050. }
  17051. else {
  17052. pattern = [5,5];
  17053. }
  17054. // set dash settings for chrome or firefox
  17055. ctx.setLineDash(pattern);
  17056. ctx.lineDashOffset = 0;
  17057. // draw the line
  17058. via = this._line(ctx);
  17059. // restore the dash settings.
  17060. ctx.setLineDash([0]);
  17061. ctx.lineDashOffset = 0;
  17062. ctx.restore();
  17063. }
  17064. else { // unsupporting smooth lines
  17065. // draw dashed line
  17066. ctx.beginPath();
  17067. ctx.lineCap = 'round';
  17068. if (this.options.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
  17069. {
  17070. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  17071. [this.options.dash.length,this.options.dash.gap,this.options.dash.altLength,this.options.dash.gap]);
  17072. }
  17073. 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
  17074. {
  17075. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  17076. [this.options.dash.length,this.options.dash.gap]);
  17077. }
  17078. else //If all else fails draw a line
  17079. {
  17080. ctx.moveTo(this.from.x, this.from.y);
  17081. ctx.lineTo(this.to.x, this.to.y);
  17082. }
  17083. ctx.stroke();
  17084. }
  17085. // draw label
  17086. if (this.label) {
  17087. var point;
  17088. if (this.options.smoothCurves.enabled == true && via != null) {
  17089. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  17090. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  17091. point = {x:midpointX, y:midpointY};
  17092. }
  17093. else {
  17094. point = this._pointOnLine(0.5);
  17095. }
  17096. this._label(ctx, this.label, point.x, point.y);
  17097. }
  17098. };
  17099. /**
  17100. * Get a point on a line
  17101. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  17102. * @return {Object} point
  17103. * @private
  17104. */
  17105. Edge.prototype._pointOnLine = function (percentage) {
  17106. return {
  17107. x: (1 - percentage) * this.from.x + percentage * this.to.x,
  17108. y: (1 - percentage) * this.from.y + percentage * this.to.y
  17109. }
  17110. };
  17111. /**
  17112. * Get a point on a circle
  17113. * @param {Number} x
  17114. * @param {Number} y
  17115. * @param {Number} radius
  17116. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  17117. * @return {Object} point
  17118. * @private
  17119. */
  17120. Edge.prototype._pointOnCircle = function (x, y, radius, percentage) {
  17121. var angle = (percentage - 3/8) * 2 * Math.PI;
  17122. return {
  17123. x: x + radius * Math.cos(angle),
  17124. y: y - radius * Math.sin(angle)
  17125. }
  17126. };
  17127. /**
  17128. * Redraw a edge as a line with an arrow halfway the line
  17129. * Draw this edge in the given canvas
  17130. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  17131. * @param {CanvasRenderingContext2D} ctx
  17132. * @private
  17133. */
  17134. Edge.prototype._drawArrowCenter = function(ctx) {
  17135. var point;
  17136. // set style
  17137. ctx.strokeStyle = this._getColor(ctx);
  17138. ctx.fillStyle = ctx.strokeStyle;
  17139. ctx.lineWidth = this._getLineWidth();
  17140. if (this.from != this.to) {
  17141. // draw line
  17142. var via = this._line(ctx);
  17143. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  17144. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  17145. // draw an arrow halfway the line
  17146. if (this.options.smoothCurves.enabled == true && via != null) {
  17147. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  17148. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  17149. point = {x:midpointX, y:midpointY};
  17150. }
  17151. else {
  17152. point = this._pointOnLine(0.5);
  17153. }
  17154. ctx.arrow(point.x, point.y, angle, length);
  17155. ctx.fill();
  17156. ctx.stroke();
  17157. // draw label
  17158. if (this.label) {
  17159. this._label(ctx, this.label, point.x, point.y);
  17160. }
  17161. }
  17162. else {
  17163. // draw circle
  17164. var x, y;
  17165. var radius = 0.25 * Math.max(100,this.physics.springLength);
  17166. var node = this.from;
  17167. if (!node.width) {
  17168. node.resize(ctx);
  17169. }
  17170. if (node.width > node.height) {
  17171. x = node.x + node.width * 0.5;
  17172. y = node.y - radius;
  17173. }
  17174. else {
  17175. x = node.x + radius;
  17176. y = node.y - node.height * 0.5;
  17177. }
  17178. this._circle(ctx, x, y, radius);
  17179. // draw all arrows
  17180. var angle = 0.2 * Math.PI;
  17181. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  17182. point = this._pointOnCircle(x, y, radius, 0.5);
  17183. ctx.arrow(point.x, point.y, angle, length);
  17184. ctx.fill();
  17185. ctx.stroke();
  17186. // draw label
  17187. if (this.label) {
  17188. point = this._pointOnCircle(x, y, radius, 0.5);
  17189. this._label(ctx, this.label, point.x, point.y);
  17190. }
  17191. }
  17192. };
  17193. Edge.prototype._pointOnBezier = function(t) {
  17194. var via = this._getViaCoordinates();
  17195. var x = Math.pow(1-t,2)*this.from.x + (2*t*(1 - t))*via.x + Math.pow(t,2)*this.to.x;
  17196. var y = Math.pow(1-t,2)*this.from.y + (2*t*(1 - t))*via.y + Math.pow(t,2)*this.to.y;
  17197. return {x:x,y:y};
  17198. }
  17199. /**
  17200. * This function uses binary search to look for the point where the bezier curve crosses the border of the node.
  17201. *
  17202. * @param from
  17203. * @param ctx
  17204. * @returns {*}
  17205. * @private
  17206. */
  17207. Edge.prototype._findBorderPosition = function(from,ctx) {
  17208. var maxIterations = 10;
  17209. var iteration = 0;
  17210. var low = 0;
  17211. var high = 1;
  17212. var pos,angle,distanceToBorder, distanceToNodes, difference;
  17213. var threshold = 0.2;
  17214. var node = this.to;
  17215. if (from == true) {
  17216. node = this.from;
  17217. }
  17218. while (low <= high && iteration < maxIterations) {
  17219. var middle = (low + high) * 0.5;
  17220. pos = this._pointOnBezier(middle);
  17221. angle = Math.atan2((node.y - pos.y), (node.x - pos.x));
  17222. distanceToBorder = node.distanceToBorder(ctx,angle);
  17223. distanceToNodes = Math.sqrt(Math.pow(pos.x-node.x,2) + Math.pow(pos.y-node.y,2));
  17224. difference = distanceToBorder - distanceToNodes;
  17225. if (Math.abs(difference) < threshold) {
  17226. break; // found
  17227. }
  17228. 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.
  17229. if (from == false) {
  17230. low = middle;
  17231. }
  17232. else {
  17233. high = middle;
  17234. }
  17235. }
  17236. else {
  17237. if (from == false) {
  17238. high = middle;
  17239. }
  17240. else {
  17241. low = middle;
  17242. }
  17243. }
  17244. iteration++;
  17245. }
  17246. pos.t = middle;
  17247. return pos;
  17248. };
  17249. /**
  17250. * Redraw a edge as a line with an arrow
  17251. * Draw this edge in the given canvas
  17252. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  17253. * @param {CanvasRenderingContext2D} ctx
  17254. * @private
  17255. */
  17256. Edge.prototype._drawArrow = function(ctx) {
  17257. // set style
  17258. ctx.strokeStyle = this._getColor(ctx);
  17259. ctx.fillStyle = ctx.strokeStyle;
  17260. ctx.lineWidth = this._getLineWidth();
  17261. // set vars
  17262. var angle, length, arrowPos;
  17263. // if not connected to itself
  17264. if (this.from != this.to) {
  17265. // draw line
  17266. this._line(ctx);
  17267. // draw arrow head
  17268. if (this.options.smoothCurves.enabled == true) {
  17269. var via = this._getViaCoordinates();
  17270. arrowPos = this._findBorderPosition(false, ctx);
  17271. var guidePos = this._pointOnBezier(Math.max(0.0, arrowPos.t - 0.1))
  17272. angle = Math.atan2((arrowPos.y - guidePos.y), (arrowPos.x - guidePos.x));
  17273. }
  17274. else {
  17275. angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  17276. var dx = (this.to.x - this.from.x);
  17277. var dy = (this.to.y - this.from.y);
  17278. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  17279. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  17280. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  17281. arrowPos = {};
  17282. arrowPos.x = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  17283. arrowPos.y = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  17284. }
  17285. // draw arrow at the end of the line
  17286. length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  17287. ctx.arrow(arrowPos.x,arrowPos.y, angle, length);
  17288. ctx.fill();
  17289. ctx.stroke();
  17290. // draw label
  17291. if (this.label) {
  17292. var point;
  17293. if (this.options.smoothCurves.enabled == true && via != null) {
  17294. point = this._pointOnBezier(0.5);
  17295. }
  17296. else {
  17297. point = this._pointOnLine(0.5);
  17298. }
  17299. this._label(ctx, this.label, point.x, point.y);
  17300. }
  17301. }
  17302. else {
  17303. // draw circle
  17304. var node = this.from;
  17305. var x, y, arrow;
  17306. var radius = 0.25 * Math.max(100,this.physics.springLength);
  17307. if (!node.width) {
  17308. node.resize(ctx);
  17309. }
  17310. if (node.width > node.height) {
  17311. x = node.x + node.width * 0.5;
  17312. y = node.y - radius;
  17313. arrow = {
  17314. x: x,
  17315. y: node.y,
  17316. angle: 0.9 * Math.PI
  17317. };
  17318. }
  17319. else {
  17320. x = node.x + radius;
  17321. y = node.y - node.height * 0.5;
  17322. arrow = {
  17323. x: node.x,
  17324. y: y,
  17325. angle: 0.6 * Math.PI
  17326. };
  17327. }
  17328. ctx.beginPath();
  17329. // TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
  17330. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  17331. ctx.stroke();
  17332. // draw all arrows
  17333. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  17334. ctx.arrow(arrow.x, arrow.y, arrow.angle, length);
  17335. ctx.fill();
  17336. ctx.stroke();
  17337. // draw label
  17338. if (this.label) {
  17339. point = this._pointOnCircle(x, y, radius, 0.5);
  17340. this._label(ctx, this.label, point.x, point.y);
  17341. }
  17342. }
  17343. };
  17344. /**
  17345. * Calculate the distance between a point (x3,y3) and a line segment from
  17346. * (x1,y1) to (x2,y2).
  17347. * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
  17348. * @param {number} x1
  17349. * @param {number} y1
  17350. * @param {number} x2
  17351. * @param {number} y2
  17352. * @param {number} x3
  17353. * @param {number} y3
  17354. * @private
  17355. */
  17356. Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
  17357. var returnValue = 0;
  17358. if (this.from != this.to) {
  17359. if (this.options.smoothCurves.enabled == true) {
  17360. var xVia, yVia;
  17361. if (this.options.smoothCurves.enabled == true && this.options.smoothCurves.dynamic == true) {
  17362. xVia = this.via.x;
  17363. yVia = this.via.y;
  17364. }
  17365. else {
  17366. var via = this._getViaCoordinates();
  17367. xVia = via.x;
  17368. yVia = via.y;
  17369. }
  17370. var minDistance = 1e9;
  17371. var distance;
  17372. var i,t,x,y, lastX, lastY;
  17373. for (i = 0; i < 10; i++) {
  17374. t = 0.1*i;
  17375. x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*xVia + Math.pow(t,2)*x2;
  17376. y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*yVia + Math.pow(t,2)*y2;
  17377. if (i > 0) {
  17378. distance = this._getDistanceToLine(lastX,lastY,x,y, x3,y3);
  17379. minDistance = distance < minDistance ? distance : minDistance;
  17380. }
  17381. lastX = x; lastY = y;
  17382. }
  17383. returnValue = minDistance;
  17384. }
  17385. else {
  17386. returnValue = this._getDistanceToLine(x1,y1,x2,y2,x3,y3);
  17387. }
  17388. }
  17389. else {
  17390. var x, y, dx, dy;
  17391. var radius = 0.25 * this.physics.springLength;
  17392. var node = this.from;
  17393. if (node.width > node.height) {
  17394. x = node.x + 0.5 * node.width;
  17395. y = node.y - radius;
  17396. }
  17397. else {
  17398. x = node.x + radius;
  17399. y = node.y - 0.5 * node.height;
  17400. }
  17401. dx = x - x3;
  17402. dy = y - y3;
  17403. returnValue = Math.abs(Math.sqrt(dx*dx + dy*dy) - radius);
  17404. }
  17405. if (this.labelDimensions.left < x3 &&
  17406. this.labelDimensions.left + this.labelDimensions.width > x3 &&
  17407. this.labelDimensions.top < y3 &&
  17408. this.labelDimensions.top + this.labelDimensions.height > y3) {
  17409. return 0;
  17410. }
  17411. else {
  17412. return returnValue;
  17413. }
  17414. };
  17415. Edge.prototype._getDistanceToLine = function(x1,y1,x2,y2,x3,y3) {
  17416. var px = x2-x1,
  17417. py = y2-y1,
  17418. something = px*px + py*py,
  17419. u = ((x3 - x1) * px + (y3 - y1) * py) / something;
  17420. if (u > 1) {
  17421. u = 1;
  17422. }
  17423. else if (u < 0) {
  17424. u = 0;
  17425. }
  17426. var x = x1 + u * px,
  17427. y = y1 + u * py,
  17428. dx = x - x3,
  17429. dy = y - y3;
  17430. //# Note: If the actual distance does not matter,
  17431. //# if you only want to compare what this function
  17432. //# returns to other results of this function, you
  17433. //# can just return the squared distance instead
  17434. //# (i.e. remove the sqrt) to gain a little performance
  17435. return Math.sqrt(dx*dx + dy*dy);
  17436. };
  17437. /**
  17438. * This allows the zoom level of the network to influence the rendering
  17439. *
  17440. * @param scale
  17441. */
  17442. Edge.prototype.setScale = function(scale) {
  17443. this.networkScaleInv = 1.0/scale;
  17444. };
  17445. Edge.prototype.select = function() {
  17446. this.selected = true;
  17447. };
  17448. Edge.prototype.unselect = function() {
  17449. this.selected = false;
  17450. };
  17451. Edge.prototype.positionBezierNode = function() {
  17452. if (this.via !== null && this.from !== null && this.to !== null) {
  17453. this.via.x = 0.5 * (this.from.x + this.to.x);
  17454. this.via.y = 0.5 * (this.from.y + this.to.y);
  17455. }
  17456. else if (this.via !== null) {
  17457. this.via.x = 0;
  17458. this.via.y = 0;
  17459. }
  17460. };
  17461. /**
  17462. * This function draws the control nodes for the manipulator.
  17463. * In order to enable this, only set the this.controlNodesEnabled to true.
  17464. * @param ctx
  17465. */
  17466. Edge.prototype._drawControlNodes = function(ctx) {
  17467. if (this.controlNodesEnabled == true) {
  17468. if (this.controlNodes.from === null && this.controlNodes.to === null) {
  17469. var nodeIdFrom = "edgeIdFrom:".concat(this.id);
  17470. var nodeIdTo = "edgeIdTo:".concat(this.id);
  17471. var constants = {
  17472. nodes:{group:'', radius:7, borderWidth:2, borderWidthSelected: 2},
  17473. physics:{damping:0},
  17474. clustering: {maxNodeSizeIncrements: 0 ,nodeScaling: {width:0, height: 0, radius:0}}
  17475. };
  17476. this.controlNodes.from = new Node(
  17477. {id:nodeIdFrom,
  17478. shape:'dot',
  17479. color:{background:'#ff0000', border:'#3c3c3c', highlight: {background:'#07f968'}}
  17480. },{},{},constants);
  17481. this.controlNodes.to = new Node(
  17482. {id:nodeIdTo,
  17483. shape:'dot',
  17484. color:{background:'#ff0000', border:'#3c3c3c', highlight: {background:'#07f968'}}
  17485. },{},{},constants);
  17486. }
  17487. this.controlNodes.positions = {};
  17488. if (this.controlNodes.from.selected == false) {
  17489. this.controlNodes.positions.from = this.getControlNodeFromPosition(ctx);
  17490. this.controlNodes.from.x = this.controlNodes.positions.from.x;
  17491. this.controlNodes.from.y = this.controlNodes.positions.from.y;
  17492. }
  17493. if (this.controlNodes.to.selected == false) {
  17494. this.controlNodes.positions.to = this.getControlNodeToPosition(ctx);
  17495. this.controlNodes.to.x = this.controlNodes.positions.to.x;
  17496. this.controlNodes.to.y = this.controlNodes.positions.to.y;
  17497. }
  17498. this.controlNodes.from.draw(ctx);
  17499. this.controlNodes.to.draw(ctx);
  17500. }
  17501. else {
  17502. this.controlNodes = {from:null, to:null, positions:{}};
  17503. }
  17504. };
  17505. /**
  17506. * Enable control nodes.
  17507. * @private
  17508. */
  17509. Edge.prototype._enableControlNodes = function() {
  17510. this.fromBackup = this.from;
  17511. this.toBackup = this.to;
  17512. this.controlNodesEnabled = true;
  17513. };
  17514. /**
  17515. * disable control nodes and remove from dynamicEdges from old node
  17516. * @private
  17517. */
  17518. Edge.prototype._disableControlNodes = function() {
  17519. this.fromId = this.from.id;
  17520. this.toId = this.to.id;
  17521. if (this.fromId != this.fromBackup.id) { // from was changed, remove edge from old 'from' node dynamic edges
  17522. this.fromBackup.detachEdge(this);
  17523. }
  17524. else if (this.toId != this.toBackup.id) { // to was changed, remove edge from old 'to' node dynamic edges
  17525. this.toBackup.detachEdge(this);
  17526. }
  17527. this.fromBackup = null;
  17528. this.toBackup = null;
  17529. this.controlNodesEnabled = false;
  17530. };
  17531. /**
  17532. * This checks if one of the control nodes is selected and if so, returns the control node object. Else it returns null.
  17533. * @param x
  17534. * @param y
  17535. * @returns {null}
  17536. * @private
  17537. */
  17538. Edge.prototype._getSelectedControlNode = function(x,y) {
  17539. var positions = this.controlNodes.positions;
  17540. var fromDistance = Math.sqrt(Math.pow(x - positions.from.x,2) + Math.pow(y - positions.from.y,2));
  17541. var toDistance = Math.sqrt(Math.pow(x - positions.to.x ,2) + Math.pow(y - positions.to.y ,2));
  17542. if (fromDistance < 15) {
  17543. this.connectedNode = this.from;
  17544. this.from = this.controlNodes.from;
  17545. return this.controlNodes.from;
  17546. }
  17547. else if (toDistance < 15) {
  17548. this.connectedNode = this.to;
  17549. this.to = this.controlNodes.to;
  17550. return this.controlNodes.to;
  17551. }
  17552. else {
  17553. return null;
  17554. }
  17555. };
  17556. /**
  17557. * this resets the control nodes to their original position.
  17558. * @private
  17559. */
  17560. Edge.prototype._restoreControlNodes = function() {
  17561. if (this.controlNodes.from.selected == true) {
  17562. this.from = this.connectedNode;
  17563. this.connectedNode = null;
  17564. this.controlNodes.from.unselect();
  17565. }
  17566. else if (this.controlNodes.to.selected == true) {
  17567. this.to = this.connectedNode;
  17568. this.connectedNode = null;
  17569. this.controlNodes.to.unselect();
  17570. }
  17571. };
  17572. /**
  17573. * this calculates the position of the control nodes on the edges of the parent nodes.
  17574. *
  17575. * @param ctx
  17576. * @returns {x: *, y: *}
  17577. */
  17578. Edge.prototype.getControlNodeFromPosition = function(ctx) {
  17579. // draw arrow head
  17580. var controlnodeFromPos;
  17581. if (this.options.smoothCurves.enabled == true) {
  17582. controlnodeFromPos = this._findBorderPosition(true, ctx);
  17583. }
  17584. else {
  17585. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  17586. var dx = (this.to.x - this.from.x);
  17587. var dy = (this.to.y - this.from.y);
  17588. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  17589. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  17590. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  17591. controlnodeFromPos = {};
  17592. controlnodeFromPos.x = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  17593. controlnodeFromPos.y = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  17594. }
  17595. return controlnodeFromPos;
  17596. };
  17597. /**
  17598. * this calculates the position of the control nodes on the edges of the parent nodes.
  17599. *
  17600. * @param ctx
  17601. * @returns {{from: {x: number, y: number}, to: {x: *, y: *}}}
  17602. */
  17603. Edge.prototype.getControlNodeToPosition = function(ctx) {
  17604. // draw arrow head
  17605. var controlnodeFromPos,controlnodeToPos;
  17606. if (this.options.smoothCurves.enabled == true) {
  17607. controlnodeToPos = this._findBorderPosition(false, ctx);
  17608. }
  17609. else {
  17610. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  17611. var dx = (this.to.x - this.from.x);
  17612. var dy = (this.to.y - this.from.y);
  17613. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  17614. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  17615. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  17616. controlnodeToPos = {};
  17617. controlnodeToPos.x = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  17618. controlnodeToPos.y = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  17619. }
  17620. return controlnodeToPos;
  17621. };
  17622. module.exports = Edge;
  17623. /***/ },
  17624. /* 38 */
  17625. /***/ function(module, exports, __webpack_require__) {
  17626. var util = __webpack_require__(1);
  17627. /**
  17628. * @class Groups
  17629. * This class can store groups and properties specific for groups.
  17630. */
  17631. function Groups() {
  17632. this.clear();
  17633. this.defaultIndex = 0;
  17634. this.groupsArray = [];
  17635. this.groupIndex = 0;
  17636. this.useDefaultGroups = true;
  17637. }
  17638. /**
  17639. * default constants for group colors
  17640. */
  17641. Groups.DEFAULT = [
  17642. {border: "#2B7CE9", background: "#97C2FC", highlight: {border: "#2B7CE9", background: "#D2E5FF"}, hover: {border: "#2B7CE9", background: "#D2E5FF"}}, // 0: blue
  17643. {border: "#FFA500", background: "#FFFF00", highlight: {border: "#FFA500", background: "#FFFFA3"}, hover: {border: "#FFA500", background: "#FFFFA3"}}, // 1: yellow
  17644. {border: "#FA0A10", background: "#FB7E81", highlight: {border: "#FA0A10", background: "#FFAFB1"}, hover: {border: "#FA0A10", background: "#FFAFB1"}}, // 2: red
  17645. {border: "#41A906", background: "#7BE141", highlight: {border: "#41A906", background: "#A1EC76"}, hover: {border: "#41A906", background: "#A1EC76"}}, // 3: green
  17646. {border: "#E129F0", background: "#EB7DF4", highlight: {border: "#E129F0", background: "#F0B3F5"}, hover: {border: "#E129F0", background: "#F0B3F5"}}, // 4: magenta
  17647. {border: "#7C29F0", background: "#AD85E4", highlight: {border: "#7C29F0", background: "#D3BDF0"}, hover: {border: "#7C29F0", background: "#D3BDF0"}}, // 5: purple
  17648. {border: "#C37F00", background: "#FFA807", highlight: {border: "#C37F00", background: "#FFCA66"}, hover: {border: "#C37F00", background: "#FFCA66"}}, // 6: orange
  17649. {border: "#4220FB", background: "#6E6EFD", highlight: {border: "#4220FB", background: "#9B9BFD"}, hover: {border: "#4220FB", background: "#9B9BFD"}}, // 7: darkblue
  17650. {border: "#FD5A77", background: "#FFC0CB", highlight: {border: "#FD5A77", background: "#FFD1D9"}, hover: {border: "#FD5A77", background: "#FFD1D9"}}, // 8: pink
  17651. {border: "#4AD63A", background: "#C2FABC", highlight: {border: "#4AD63A", background: "#E6FFE3"}, hover: {border: "#4AD63A", background: "#E6FFE3"}}, // 9: mint
  17652. {border: "#990000", background: "#EE0000", highlight: {border: "#BB0000", background: "#FF3333"}, hover: {border: "#BB0000", background: "#FF3333"}}, // 10:bright red
  17653. {border: "#FF6000", background: "#FF6000", highlight: {border: "#FF6000", background: "#FF6000"}, hover: {border: "#FF6000", background: "#FF6000"}}, // 12: real orange
  17654. {border: "#97C2FC", background: "#2B7CE9", highlight: {border: "#D2E5FF", background: "#2B7CE9"}, hover: {border: "#D2E5FF", background: "#2B7CE9"}}, // 13: blue
  17655. {border: "#399605", background: "#255C03", highlight: {border: "#399605", background: "#255C03"}, hover: {border: "#399605", background: "#255C03"}}, // 14: green
  17656. {border: "#B70054", background: "#FF007E", highlight: {border: "#B70054", background: "#FF007E"}, hover: {border: "#B70054", background: "#FF007E"}}, // 15: magenta
  17657. {border: "#AD85E4", background: "#7C29F0", highlight: {border: "#D3BDF0", background: "#7C29F0"}, hover: {border: "#D3BDF0", background: "#7C29F0"}}, // 16: purple
  17658. {border: "#4557FA", background: "#000EA1", highlight: {border: "#6E6EFD", background: "#000EA1"}, hover: {border: "#6E6EFD", background: "#000EA1"}}, // 17: darkblue
  17659. {border: "#FFC0CB", background: "#FD5A77", highlight: {border: "#FFD1D9", background: "#FD5A77"}, hover: {border: "#FFD1D9", background: "#FD5A77"}}, // 18: pink
  17660. {border: "#C2FABC", background: "#74D66A", highlight: {border: "#E6FFE3", background: "#74D66A"}, hover: {border: "#E6FFE3", background: "#74D66A"}}, // 19: mint
  17661. {border: "#EE0000", background: "#990000", highlight: {border: "#FF3333", background: "#BB0000"}, hover: {border: "#FF3333", background: "#BB0000"}}, // 20:bright red
  17662. ];
  17663. /**
  17664. * Clear all groups
  17665. */
  17666. Groups.prototype.clear = function () {
  17667. this.groups = {};
  17668. this.groups.length = function()
  17669. {
  17670. var i = 0;
  17671. for ( var p in this ) {
  17672. if (this.hasOwnProperty(p)) {
  17673. i++;
  17674. }
  17675. }
  17676. return i;
  17677. }
  17678. };
  17679. /**
  17680. * get group properties of a groupname. If groupname is not found, a new group
  17681. * is added.
  17682. * @param {*} groupname Can be a number, string, Date, etc.
  17683. * @return {Object} group The created group, containing all group properties
  17684. */
  17685. Groups.prototype.get = function (groupname) {
  17686. var group = this.groups[groupname];
  17687. if (group == undefined) {
  17688. if (this.useDefaultGroups === false && this.groupsArray.length > 0) {
  17689. // create new group
  17690. var index = this.groupIndex % this.groupsArray.length;
  17691. this.groupIndex++;
  17692. group = {};
  17693. group.color = this.groups[this.groupsArray[index]];
  17694. this.groups[groupname] = group;
  17695. }
  17696. else {
  17697. // create new group
  17698. var index = this.defaultIndex % Groups.DEFAULT.length;
  17699. this.defaultIndex++;
  17700. group = {};
  17701. group.color = Groups.DEFAULT[index];
  17702. this.groups[groupname] = group;
  17703. }
  17704. }
  17705. return group;
  17706. };
  17707. /**
  17708. * Add a custom group style
  17709. * @param {String} groupName
  17710. * @param {Object} style An object containing borderColor,
  17711. * backgroundColor, etc.
  17712. * @return {Object} group The created group object
  17713. */
  17714. Groups.prototype.add = function (groupName, style) {
  17715. this.groups[groupName] = style;
  17716. this.groupsArray.push(groupName);
  17717. return style;
  17718. };
  17719. module.exports = Groups;
  17720. /***/ },
  17721. /* 39 */
  17722. /***/ function(module, exports, __webpack_require__) {
  17723. /**
  17724. * @class Images
  17725. * This class loads images and keeps them stored.
  17726. */
  17727. function Images() {
  17728. this.images = {};
  17729. this.imageBroken = {};
  17730. this.callback = undefined;
  17731. }
  17732. /**
  17733. * Set an onload callback function. This will be called each time an image
  17734. * is loaded
  17735. * @param {function} callback
  17736. */
  17737. Images.prototype.setOnloadCallback = function(callback) {
  17738. this.callback = callback;
  17739. };
  17740. /**
  17741. *
  17742. * @param {string} url Url of the image
  17743. * @param {string} url Url of an image to use if the url image is not found
  17744. * @return {Image} img The image object
  17745. */
  17746. Images.prototype.load = function(url, brokenUrl) {
  17747. var img = this.images[url]; // make a pointer
  17748. if (img === undefined) {
  17749. // create the image
  17750. var me = this;
  17751. img = new Image();
  17752. img.onload = function () {
  17753. // IE11 fix -- thanks dponch!
  17754. if (this.width == 0) {
  17755. document.body.appendChild(this);
  17756. this.width = this.offsetWidth;
  17757. this.height = this.offsetHeight;
  17758. document.body.removeChild(this);
  17759. }
  17760. if (me.callback) {
  17761. me.images[url] = img;
  17762. me.callback(this);
  17763. }
  17764. };
  17765. img.onerror = function () {
  17766. if (brokenUrl === undefined) {
  17767. console.error("Could not load image:", url);
  17768. delete this.src;
  17769. if (me.callback) {
  17770. me.callback(this);
  17771. }
  17772. }
  17773. else {
  17774. if (me.imageBroken[url] === true) {
  17775. if (this.src == brokenUrl) {
  17776. console.error("Could not load brokenImage:", brokenUrl);
  17777. delete this.src;
  17778. if (me.callback) {
  17779. me.callback(this);
  17780. }
  17781. }
  17782. else {
  17783. console.error("Could not load image:", url);
  17784. this.src = brokenUrl;
  17785. }
  17786. }
  17787. else {
  17788. console.error("Could not load image:", url);
  17789. this.src = brokenUrl;
  17790. me.imageBroken[url] = true;
  17791. }
  17792. }
  17793. };
  17794. img.src = url;
  17795. }
  17796. return img;
  17797. };
  17798. module.exports = Images;
  17799. /***/ },
  17800. /* 40 */
  17801. /***/ function(module, exports, __webpack_require__) {
  17802. var util = __webpack_require__(1);
  17803. /**
  17804. * @class Node
  17805. * A node. A node can be connected to other nodes via one or multiple edges.
  17806. * @param {object} properties An object containing properties for the node. All
  17807. * properties are optional, except for the id.
  17808. * {number} id Id of the node. Required
  17809. * {string} label Text label for the node
  17810. * {number} x Horizontal position of the node
  17811. * {number} y Vertical position of the node
  17812. * {string} shape Node shape, available:
  17813. * "database", "circle", "ellipse",
  17814. * "box", "image", "text", "dot",
  17815. * "star", "triangle", "triangleDown",
  17816. * "square", "icon"
  17817. * {string} image An image url
  17818. * {string} title An title text, can be HTML
  17819. * {anytype} group A group name or number
  17820. * @param {Network.Images} imagelist A list with images. Only needed
  17821. * when the node has an image
  17822. * @param {Network.Groups} grouplist A list with groups. Needed for
  17823. * retrieving group properties
  17824. * @param {Object} constants An object with default values for
  17825. * example for the color
  17826. *
  17827. */
  17828. function Node(properties, imagelist, grouplist, networkConstants) {
  17829. var constants = util.selectiveBridgeObject(['nodes'],networkConstants);
  17830. this.options = constants.nodes;
  17831. this.selected = false;
  17832. this.hover = false;
  17833. this.edges = []; // all edges connected to this node
  17834. this.dynamicEdges = [];
  17835. this.reroutedEdges = {};
  17836. // set defaults for the properties
  17837. this.id = undefined;
  17838. this.allowedToMoveX = false;
  17839. this.allowedToMoveY = false;
  17840. this.xFixed = false;
  17841. this.yFixed = false;
  17842. this.horizontalAlignLeft = true; // these are for the navigation controls
  17843. this.verticalAlignTop = true; // these are for the navigation controls
  17844. this.baseRadiusValue = networkConstants.nodes.radius;
  17845. this.radiusFixed = false;
  17846. this.level = -1;
  17847. this.preassignedLevel = false;
  17848. this.hierarchyEnumerated = false;
  17849. this.labelDimensions = {top:0, left:0, width:0, height:0, yLine:0}; // could be cached
  17850. this.boundingBox = {top:0, left:0, right:0, bottom:0};
  17851. this.imagelist = imagelist;
  17852. this.grouplist = grouplist;
  17853. // physics properties
  17854. this.fx = 0.0; // external force x
  17855. this.fy = 0.0; // external force y
  17856. this.vx = 0.0; // velocity x
  17857. this.vy = 0.0; // velocity y
  17858. this.x = null;
  17859. this.y = null;
  17860. this.predefinedPosition = false; // used to check if initial zoomExtent should just take the range or approximate
  17861. // used for reverting to previous position on stabilization
  17862. this.previousState = {vx:0,vy:0,x:0,y:0};
  17863. this.damping = networkConstants.physics.damping; // written every time gravity is calculated
  17864. this.fixedData = {x:null,y:null};
  17865. this.setProperties(properties, constants);
  17866. // creating the variables for clustering
  17867. this.resetCluster();
  17868. this.clusterSession = 0;
  17869. this.clusterSizeWidthFactor = networkConstants.clustering.nodeScaling.width;
  17870. this.clusterSizeHeightFactor = networkConstants.clustering.nodeScaling.height;
  17871. this.clusterSizeRadiusFactor = networkConstants.clustering.nodeScaling.radius;
  17872. this.maxNodeSizeIncrements = networkConstants.clustering.maxNodeSizeIncrements;
  17873. this.growthIndicator = 0;
  17874. // variables to tell the node about the network.
  17875. this.networkScaleInv = 1;
  17876. this.networkScale = 1;
  17877. this.canvasTopLeft = {"x": -300, "y": -300};
  17878. this.canvasBottomRight = {"x": 300, "y": 300};
  17879. this.parentEdgeId = null;
  17880. }
  17881. /**
  17882. * Revert the position and velocity of the previous step.
  17883. */
  17884. Node.prototype.revertPosition = function() {
  17885. this.x = this.previousState.x;
  17886. this.y = this.previousState.y;
  17887. this.vx = this.previousState.vx;
  17888. this.vy = this.previousState.vy;
  17889. }
  17890. /**
  17891. * (re)setting the clustering variables and objects
  17892. */
  17893. Node.prototype.resetCluster = function() {
  17894. // clustering variables
  17895. this.formationScale = undefined; // this is used to determine when to open the cluster
  17896. this.clusterSize = 1; // this signifies the total amount of nodes in this cluster
  17897. this.containedNodes = {};
  17898. this.containedEdges = {};
  17899. this.clusterSessions = [];
  17900. };
  17901. /**
  17902. * Attach a edge to the node
  17903. * @param {Edge} edge
  17904. */
  17905. Node.prototype.attachEdge = function(edge) {
  17906. if (this.edges.indexOf(edge) == -1) {
  17907. this.edges.push(edge);
  17908. }
  17909. if (this.dynamicEdges.indexOf(edge) == -1) {
  17910. this.dynamicEdges.push(edge);
  17911. }
  17912. };
  17913. /**
  17914. * Detach a edge from the node
  17915. * @param {Edge} edge
  17916. */
  17917. Node.prototype.detachEdge = function(edge) {
  17918. var index = this.edges.indexOf(edge);
  17919. if (index != -1) {
  17920. this.edges.splice(index, 1);
  17921. }
  17922. index = this.dynamicEdges.indexOf(edge);
  17923. if (index != -1) {
  17924. this.dynamicEdges.splice(index, 1);
  17925. }
  17926. };
  17927. /**
  17928. * Set or overwrite properties for the node
  17929. * @param {Object} properties an object with properties
  17930. * @param {Object} constants and object with default, global properties
  17931. */
  17932. Node.prototype.setProperties = function(properties, constants) {
  17933. if (!properties) {
  17934. return;
  17935. }
  17936. var fields = ['borderWidth','borderWidthSelected','shape','image','brokenImage','radius','fontColor',
  17937. 'fontSize','fontFace','fontFill','fontStrokeWidth','fontStrokeColor','group','mass','fontDrawThreshold',
  17938. 'scaleFontWithValue','fontSizeMaxVisible','customScalingFunction','iconFontFace', 'icon', 'iconColor', 'iconSize'
  17939. ];
  17940. util.selectiveDeepExtend(fields, this.options, properties);
  17941. // basic properties
  17942. if (properties.id !== undefined) {this.id = properties.id;}
  17943. if (properties.label !== undefined) {this.label = properties.label; this.originalLabel = properties.label;}
  17944. if (properties.title !== undefined) {this.title = properties.title;}
  17945. if (properties.x !== undefined) {this.x = properties.x; this.predefinedPosition = true;}
  17946. if (properties.y !== undefined) {this.y = properties.y; this.predefinedPosition = true;}
  17947. if (properties.value !== undefined) {this.value = properties.value;}
  17948. if (properties.level !== undefined) {this.level = properties.level; this.preassignedLevel = true;}
  17949. // navigation controls properties
  17950. if (properties.horizontalAlignLeft !== undefined) {this.horizontalAlignLeft = properties.horizontalAlignLeft;}
  17951. if (properties.verticalAlignTop !== undefined) {this.verticalAlignTop = properties.verticalAlignTop;}
  17952. if (properties.triggerFunction !== undefined) {this.triggerFunction = properties.triggerFunction;}
  17953. if (this.id === undefined) {
  17954. throw "Node must have an id";
  17955. }
  17956. // copy group properties
  17957. if (typeof properties.group === 'number' || (typeof properties.group === 'string' && properties.group != '')) {
  17958. var groupObj = this.grouplist.get(properties.group);
  17959. util.deepExtend(this.options, groupObj);
  17960. // the color object needs to be completely defined. Since groups can partially overwrite the colors, we parse it again, just in case.
  17961. this.options.color = util.parseColor(this.options.color);
  17962. }
  17963. // individual shape properties
  17964. if (properties.radius !== undefined) {this.baseRadiusValue = this.options.radius;}
  17965. if (properties.color !== undefined) {this.options.color = util.parseColor(properties.color);}
  17966. if (this.options.image !== undefined && this.options.image!= "") {
  17967. if (this.imagelist) {
  17968. this.imageObj = this.imagelist.load(this.options.image, this.options.brokenImage);
  17969. }
  17970. else {
  17971. throw "No imagelist provided";
  17972. }
  17973. }
  17974. if (properties.allowedToMoveX !== undefined) {
  17975. this.xFixed = !properties.allowedToMoveX;
  17976. this.allowedToMoveX = properties.allowedToMoveX;
  17977. }
  17978. else if (properties.x !== undefined && this.allowedToMoveX == false) {
  17979. this.xFixed = true;
  17980. }
  17981. if (properties.allowedToMoveY !== undefined) {
  17982. this.yFixed = !properties.allowedToMoveY;
  17983. this.allowedToMoveY = properties.allowedToMoveY;
  17984. }
  17985. else if (properties.y !== undefined && this.allowedToMoveY == false) {
  17986. this.yFixed = true;
  17987. }
  17988. this.radiusFixed = this.radiusFixed || (properties.radius !== undefined);
  17989. if (this.options.shape === 'image' || this.options.shape === 'circularImage') {
  17990. this.options.radiusMin = constants.nodes.widthMin;
  17991. this.options.radiusMax = constants.nodes.widthMax;
  17992. }
  17993. // choose draw method depending on the shape
  17994. switch (this.options.shape) {
  17995. case 'database': this.draw = this._drawDatabase; this.resize = this._resizeDatabase; break;
  17996. case 'box': this.draw = this._drawBox; this.resize = this._resizeBox; break;
  17997. case 'circle': this.draw = this._drawCircle; this.resize = this._resizeCircle; break;
  17998. case 'ellipse': this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
  17999. // TODO: add diamond shape
  18000. case 'image': this.draw = this._drawImage; this.resize = this._resizeImage; break;
  18001. case 'circularImage': this.draw = this._drawCircularImage; this.resize = this._resizeCircularImage; break;
  18002. case 'text': this.draw = this._drawText; this.resize = this._resizeText; break;
  18003. case 'dot': this.draw = this._drawDot; this.resize = this._resizeShape; break;
  18004. case 'square': this.draw = this._drawSquare; this.resize = this._resizeShape; break;
  18005. case 'triangle': this.draw = this._drawTriangle; this.resize = this._resizeShape; break;
  18006. case 'triangleDown': this.draw = this._drawTriangleDown; this.resize = this._resizeShape; break;
  18007. case 'star': this.draw = this._drawStar; this.resize = this._resizeShape; break;
  18008. case 'icon': this.draw = this._drawIcon; this.resize = this._resizeIcon; break;
  18009. default: this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
  18010. }
  18011. // reset the size of the node, this can be changed
  18012. this._reset();
  18013. };
  18014. /**
  18015. * select this node
  18016. */
  18017. Node.prototype.select = function() {
  18018. this.selected = true;
  18019. this._reset();
  18020. };
  18021. /**
  18022. * unselect this node
  18023. */
  18024. Node.prototype.unselect = function() {
  18025. this.selected = false;
  18026. this._reset();
  18027. };
  18028. /**
  18029. * Reset the calculated size of the node, forces it to recalculate its size
  18030. */
  18031. Node.prototype.clearSizeCache = function() {
  18032. this._reset();
  18033. };
  18034. /**
  18035. * Reset the calculated size of the node, forces it to recalculate its size
  18036. * @private
  18037. */
  18038. Node.prototype._reset = function() {
  18039. this.width = undefined;
  18040. this.height = undefined;
  18041. };
  18042. /**
  18043. * get the title of this node.
  18044. * @return {string} title The title of the node, or undefined when no title
  18045. * has been set.
  18046. */
  18047. Node.prototype.getTitle = function() {
  18048. return typeof this.title === "function" ? this.title() : this.title;
  18049. };
  18050. /**
  18051. * Calculate the distance to the border of the Node
  18052. * @param {CanvasRenderingContext2D} ctx
  18053. * @param {Number} angle Angle in radians
  18054. * @returns {number} distance Distance to the border in pixels
  18055. */
  18056. Node.prototype.distanceToBorder = function (ctx, angle) {
  18057. var borderWidth = 1;
  18058. if (!this.width) {
  18059. this.resize(ctx);
  18060. }
  18061. switch (this.options.shape) {
  18062. case 'circle':
  18063. case 'dot':
  18064. return this.options.radius+ borderWidth;
  18065. case 'ellipse':
  18066. var a = this.width / 2;
  18067. var b = this.height / 2;
  18068. var w = (Math.sin(angle) * a);
  18069. var h = (Math.cos(angle) * b);
  18070. return a * b / Math.sqrt(w * w + h * h);
  18071. // TODO: implement distanceToBorder for database
  18072. // TODO: implement distanceToBorder for triangle
  18073. // TODO: implement distanceToBorder for triangleDown
  18074. case 'box':
  18075. case 'image':
  18076. case 'text':
  18077. default:
  18078. if (this.width) {
  18079. return Math.min(
  18080. Math.abs(this.width / 2 / Math.cos(angle)),
  18081. Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth;
  18082. // TODO: reckon with border radius too in case of box
  18083. }
  18084. else {
  18085. return 0;
  18086. }
  18087. }
  18088. // TODO: implement calculation of distance to border for all shapes
  18089. };
  18090. /**
  18091. * Set forces acting on the node
  18092. * @param {number} fx Force in horizontal direction
  18093. * @param {number} fy Force in vertical direction
  18094. */
  18095. Node.prototype._setForce = function(fx, fy) {
  18096. this.fx = fx;
  18097. this.fy = fy;
  18098. };
  18099. /**
  18100. * Add forces acting on the node
  18101. * @param {number} fx Force in horizontal direction
  18102. * @param {number} fy Force in vertical direction
  18103. * @private
  18104. */
  18105. Node.prototype._addForce = function(fx, fy) {
  18106. this.fx += fx;
  18107. this.fy += fy;
  18108. };
  18109. /**
  18110. * Store the state before the next step
  18111. */
  18112. Node.prototype.storeState = function() {
  18113. this.previousState.x = this.x;
  18114. this.previousState.y = this.y;
  18115. this.previousState.vx = this.vx;
  18116. this.previousState.vy = this.vy;
  18117. }
  18118. /**
  18119. * Perform one discrete step for the node
  18120. * @param {number} interval Time interval in seconds
  18121. */
  18122. Node.prototype.discreteStep = function(interval) {
  18123. this.storeState();
  18124. if (!this.xFixed) {
  18125. var dx = this.damping * this.vx; // damping force
  18126. var ax = (this.fx - dx) / this.options.mass; // acceleration
  18127. this.vx += ax * interval; // velocity
  18128. this.x += this.vx * interval; // position
  18129. }
  18130. else {
  18131. this.fx = 0;
  18132. this.vx = 0;
  18133. }
  18134. if (!this.yFixed) {
  18135. var dy = this.damping * this.vy; // damping force
  18136. var ay = (this.fy - dy) / this.options.mass; // acceleration
  18137. this.vy += ay * interval; // velocity
  18138. this.y += this.vy * interval; // position
  18139. }
  18140. else {
  18141. this.fy = 0;
  18142. this.vy = 0;
  18143. }
  18144. };
  18145. /**
  18146. * Perform one discrete step for the node
  18147. * @param {number} interval Time interval in seconds
  18148. * @param {number} maxVelocity The speed limit imposed on the velocity
  18149. */
  18150. Node.prototype.discreteStepLimited = function(interval, maxVelocity) {
  18151. this.storeState();
  18152. if (!this.xFixed) {
  18153. var dx = this.damping * this.vx; // damping force
  18154. var ax = (this.fx - dx) / this.options.mass; // acceleration
  18155. this.vx += ax * interval; // velocity
  18156. this.vx = (Math.abs(this.vx) > maxVelocity) ? ((this.vx > 0) ? maxVelocity : -maxVelocity) : this.vx;
  18157. this.x += this.vx * interval; // position
  18158. }
  18159. else {
  18160. this.fx = 0;
  18161. this.vx = 0;
  18162. }
  18163. if (!this.yFixed) {
  18164. var dy = this.damping * this.vy; // damping force
  18165. var ay = (this.fy - dy) / this.options.mass; // acceleration
  18166. this.vy += ay * interval; // velocity
  18167. this.vy = (Math.abs(this.vy) > maxVelocity) ? ((this.vy > 0) ? maxVelocity : -maxVelocity) : this.vy;
  18168. this.y += this.vy * interval; // position
  18169. }
  18170. else {
  18171. this.fy = 0;
  18172. this.vy = 0;
  18173. }
  18174. };
  18175. /**
  18176. * Check if this node has a fixed x and y position
  18177. * @return {boolean} true if fixed, false if not
  18178. */
  18179. Node.prototype.isFixed = function() {
  18180. return (this.xFixed && this.yFixed);
  18181. };
  18182. /**
  18183. * Check if this node is moving
  18184. * @param {number} vmin the minimum velocity considered as "moving"
  18185. * @return {boolean} true if moving, false if it has no velocity
  18186. */
  18187. Node.prototype.isMoving = function(vmin) {
  18188. var velocity = Math.sqrt(Math.pow(this.vx,2) + Math.pow(this.vy,2));
  18189. // this.velocity = Math.sqrt(Math.pow(this.vx,2) + Math.pow(this.vy,2))
  18190. return (velocity > vmin);
  18191. };
  18192. /**
  18193. * check if this node is selecte
  18194. * @return {boolean} selected True if node is selected, else false
  18195. */
  18196. Node.prototype.isSelected = function() {
  18197. return this.selected;
  18198. };
  18199. /**
  18200. * Retrieve the value of the node. Can be undefined
  18201. * @return {Number} value
  18202. */
  18203. Node.prototype.getValue = function() {
  18204. return this.value;
  18205. };
  18206. /**
  18207. * Calculate the distance from the nodes location to the given location (x,y)
  18208. * @param {Number} x
  18209. * @param {Number} y
  18210. * @return {Number} value
  18211. */
  18212. Node.prototype.getDistance = function(x, y) {
  18213. var dx = this.x - x,
  18214. dy = this.y - y;
  18215. return Math.sqrt(dx * dx + dy * dy);
  18216. };
  18217. /**
  18218. * Adjust the value range of the node. The node will adjust it's radius
  18219. * based on its value.
  18220. * @param {Number} min
  18221. * @param {Number} max
  18222. */
  18223. Node.prototype.setValueRange = function(min, max, total) {
  18224. if (!this.radiusFixed && this.value !== undefined) {
  18225. var scale = this.options.customScalingFunction(min, max, total, this.value);
  18226. var radiusDiff = this.options.radiusMax - this.options.radiusMin;
  18227. if (this.options.scaleFontWithValue == true) {
  18228. var fontDiff = this.options.fontSizeMax - this.options.fontSizeMin;
  18229. this.options.fontSize = this.options.fontSizeMin + scale * fontDiff;
  18230. }
  18231. this.options.radius = this.options.radiusMin + scale * radiusDiff;
  18232. }
  18233. this.baseRadiusValue = this.options.radius;
  18234. };
  18235. /**
  18236. * Draw this node in the given canvas
  18237. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  18238. * @param {CanvasRenderingContext2D} ctx
  18239. */
  18240. Node.prototype.draw = function(ctx) {
  18241. throw "Draw method not initialized for node";
  18242. };
  18243. /**
  18244. * Recalculate the size of this node in the given canvas
  18245. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  18246. * @param {CanvasRenderingContext2D} ctx
  18247. */
  18248. Node.prototype.resize = function(ctx) {
  18249. throw "Resize method not initialized for node";
  18250. };
  18251. /**
  18252. * Check if this object is overlapping with the provided object
  18253. * @param {Object} obj an object with parameters left, top, right, bottom
  18254. * @return {boolean} True if location is located on node
  18255. */
  18256. Node.prototype.isOverlappingWith = function(obj) {
  18257. return (this.left < obj.right &&
  18258. this.left + this.width > obj.left &&
  18259. this.top < obj.bottom &&
  18260. this.top + this.height > obj.top);
  18261. };
  18262. Node.prototype._resizeImage = function (ctx) {
  18263. // TODO: pre calculate the image size
  18264. if (!this.width || !this.height) { // undefined or 0
  18265. var width, height;
  18266. if (this.value) {
  18267. this.options.radius= this.baseRadiusValue;
  18268. var scale = this.imageObj.height / this.imageObj.width;
  18269. if (scale !== undefined) {
  18270. width = this.options.radius|| this.imageObj.width;
  18271. height = this.options.radius* scale || this.imageObj.height;
  18272. }
  18273. else {
  18274. width = 0;
  18275. height = 0;
  18276. }
  18277. }
  18278. else {
  18279. width = this.imageObj.width;
  18280. height = this.imageObj.height;
  18281. }
  18282. this.width = width;
  18283. this.height = height;
  18284. this.growthIndicator = 0;
  18285. if (this.width > 0 && this.height > 0) {
  18286. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  18287. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  18288. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  18289. this.growthIndicator = this.width - width;
  18290. }
  18291. }
  18292. };
  18293. Node.prototype._drawImageAtPosition = function (ctx) {
  18294. if (this.imageObj.width != 0 ) {
  18295. // draw the shade
  18296. if (this.clusterSize > 1) {
  18297. var lineWidth = ((this.clusterSize > 1) ? 10 : 0.0);
  18298. lineWidth *= this.networkScaleInv;
  18299. lineWidth = Math.min(0.2 * this.width,lineWidth);
  18300. ctx.globalAlpha = 0.5;
  18301. ctx.drawImage(this.imageObj, this.left - lineWidth, this.top - lineWidth, this.width + 2*lineWidth, this.height + 2*lineWidth);
  18302. }
  18303. // draw the image
  18304. ctx.globalAlpha = 1.0;
  18305. ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height);
  18306. }
  18307. };
  18308. Node.prototype._drawImageLabel = function (ctx) {
  18309. var yLabel;
  18310. var offset = 0;
  18311. if (this.height){
  18312. offset = this.height / 2;
  18313. var labelDimensions = this.getTextSize(ctx);
  18314. if (labelDimensions.lineCount >= 1){
  18315. offset += labelDimensions.height / 2;
  18316. offset += 3;
  18317. }
  18318. }
  18319. yLabel = this.y + offset;
  18320. this._label(ctx, this.label, this.x, yLabel, undefined);
  18321. };
  18322. Node.prototype._drawImage = function (ctx) {
  18323. this._resizeImage(ctx);
  18324. this.left = this.x - this.width / 2;
  18325. this.top = this.y - this.height / 2;
  18326. this._drawImageAtPosition(ctx);
  18327. this.boundingBox.top = this.top;
  18328. this.boundingBox.left = this.left;
  18329. this.boundingBox.right = this.left + this.width;
  18330. this.boundingBox.bottom = this.top + this.height;
  18331. this._drawImageLabel(ctx);
  18332. this.boundingBox.left = Math.min(this.boundingBox.left, this.labelDimensions.left);
  18333. this.boundingBox.right = Math.max(this.boundingBox.right, this.labelDimensions.left + this.labelDimensions.width);
  18334. this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelDimensions.height);
  18335. };
  18336. Node.prototype._resizeCircularImage = function (ctx) {
  18337. if(!this.imageObj.src || !this.imageObj.width || !this.imageObj.height){
  18338. if (!this.width) {
  18339. var diameter = this.options.radius * 2;
  18340. this.width = diameter;
  18341. this.height = diameter;
  18342. // scaling used for clustering
  18343. //this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  18344. //this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  18345. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  18346. this.growthIndicator = this.options.radius- 0.5*diameter;
  18347. this._swapToImageResizeWhenImageLoaded = true;
  18348. }
  18349. }
  18350. else {
  18351. if (this._swapToImageResizeWhenImageLoaded) {
  18352. this.width = 0;
  18353. this.height = 0;
  18354. delete this._swapToImageResizeWhenImageLoaded;
  18355. }
  18356. this._resizeImage(ctx);
  18357. }
  18358. };
  18359. Node.prototype._drawCircularImage = function (ctx) {
  18360. this._resizeCircularImage(ctx);
  18361. this.left = this.x - this.width / 2;
  18362. this.top = this.y - this.height / 2;
  18363. var centerX = this.left + (this.width / 2);
  18364. var centerY = this.top + (this.height / 2);
  18365. var radius = Math.abs(this.height / 2);
  18366. this._drawRawCircle(ctx, centerX, centerY, radius);
  18367. ctx.save();
  18368. ctx.circle(this.x, this.y, radius);
  18369. ctx.stroke();
  18370. ctx.clip();
  18371. this._drawImageAtPosition(ctx);
  18372. ctx.restore();
  18373. this.boundingBox.top = this.y - this.options.radius;
  18374. this.boundingBox.left = this.x - this.options.radius;
  18375. this.boundingBox.right = this.x + this.options.radius;
  18376. this.boundingBox.bottom = this.y + this.options.radius;
  18377. this._drawImageLabel(ctx);
  18378. this.boundingBox.left = Math.min(this.boundingBox.left, this.labelDimensions.left);
  18379. this.boundingBox.right = Math.max(this.boundingBox.right, this.labelDimensions.left + this.labelDimensions.width);
  18380. this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelDimensions.height);
  18381. };
  18382. Node.prototype._resizeBox = function (ctx) {
  18383. if (!this.width) {
  18384. var margin = 5;
  18385. var textSize = this.getTextSize(ctx);
  18386. this.width = textSize.width + 2 * margin;
  18387. this.height = textSize.height + 2 * margin;
  18388. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  18389. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  18390. this.growthIndicator = this.width - (textSize.width + 2 * margin);
  18391. // this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  18392. }
  18393. };
  18394. Node.prototype._drawBox = function (ctx) {
  18395. this._resizeBox(ctx);
  18396. this.left = this.x - this.width / 2;
  18397. this.top = this.y - this.height / 2;
  18398. var clusterLineWidth = 2.5;
  18399. var borderWidth = this.options.borderWidth;
  18400. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  18401. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  18402. // draw the outer border
  18403. if (this.clusterSize > 1) {
  18404. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18405. ctx.lineWidth *= this.networkScaleInv;
  18406. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18407. 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);
  18408. ctx.stroke();
  18409. }
  18410. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18411. ctx.lineWidth *= this.networkScaleInv;
  18412. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18413. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  18414. ctx.roundRect(this.left, this.top, this.width, this.height, this.options.radius);
  18415. ctx.fill();
  18416. ctx.stroke();
  18417. this.boundingBox.top = this.top;
  18418. this.boundingBox.left = this.left;
  18419. this.boundingBox.right = this.left + this.width;
  18420. this.boundingBox.bottom = this.top + this.height;
  18421. this._label(ctx, this.label, this.x, this.y);
  18422. };
  18423. Node.prototype._resizeDatabase = function (ctx) {
  18424. if (!this.width) {
  18425. var margin = 5;
  18426. var textSize = this.getTextSize(ctx);
  18427. var size = textSize.width + 2 * margin;
  18428. this.width = size;
  18429. this.height = size;
  18430. // scaling used for clustering
  18431. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  18432. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  18433. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  18434. this.growthIndicator = this.width - size;
  18435. }
  18436. };
  18437. Node.prototype._drawDatabase = function (ctx) {
  18438. this._resizeDatabase(ctx);
  18439. this.left = this.x - this.width / 2;
  18440. this.top = this.y - this.height / 2;
  18441. var clusterLineWidth = 2.5;
  18442. var borderWidth = this.options.borderWidth;
  18443. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  18444. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  18445. // draw the outer border
  18446. if (this.clusterSize > 1) {
  18447. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18448. ctx.lineWidth *= this.networkScaleInv;
  18449. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18450. 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);
  18451. ctx.stroke();
  18452. }
  18453. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18454. ctx.lineWidth *= this.networkScaleInv;
  18455. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18456. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  18457. ctx.database(this.x - this.width/2, this.y - this.height*0.5, this.width, this.height);
  18458. ctx.fill();
  18459. ctx.stroke();
  18460. this.boundingBox.top = this.top;
  18461. this.boundingBox.left = this.left;
  18462. this.boundingBox.right = this.left + this.width;
  18463. this.boundingBox.bottom = this.top + this.height;
  18464. this._label(ctx, this.label, this.x, this.y);
  18465. };
  18466. Node.prototype._resizeCircle = function (ctx) {
  18467. if (!this.width) {
  18468. var margin = 5;
  18469. var textSize = this.getTextSize(ctx);
  18470. var diameter = Math.max(textSize.width, textSize.height) + 2 * margin;
  18471. this.options.radius = diameter / 2;
  18472. this.width = diameter;
  18473. this.height = diameter;
  18474. // scaling used for clustering
  18475. // this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  18476. // this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  18477. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  18478. this.growthIndicator = this.options.radius- 0.5*diameter;
  18479. }
  18480. };
  18481. Node.prototype._drawRawCircle = function (ctx, x, y, radius) {
  18482. var clusterLineWidth = 2.5;
  18483. var borderWidth = this.options.borderWidth;
  18484. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  18485. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  18486. // draw the outer border
  18487. if (this.clusterSize > 1) {
  18488. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18489. ctx.lineWidth *= this.networkScaleInv;
  18490. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18491. ctx.circle(x, y, radius+2*ctx.lineWidth);
  18492. ctx.stroke();
  18493. }
  18494. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18495. ctx.lineWidth *= this.networkScaleInv;
  18496. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18497. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  18498. ctx.circle(this.x, this.y, radius);
  18499. ctx.fill();
  18500. ctx.stroke();
  18501. };
  18502. Node.prototype._drawCircle = function (ctx) {
  18503. this._resizeCircle(ctx);
  18504. this.left = this.x - this.width / 2;
  18505. this.top = this.y - this.height / 2;
  18506. this._drawRawCircle(ctx, this.x, this.y, this.options.radius);
  18507. this.boundingBox.top = this.y - this.options.radius;
  18508. this.boundingBox.left = this.x - this.options.radius;
  18509. this.boundingBox.right = this.x + this.options.radius;
  18510. this.boundingBox.bottom = this.y + this.options.radius;
  18511. this._label(ctx, this.label, this.x, this.y);
  18512. };
  18513. Node.prototype._resizeEllipse = function (ctx) {
  18514. if (!this.width) {
  18515. var textSize = this.getTextSize(ctx);
  18516. this.width = textSize.width * 1.5;
  18517. this.height = textSize.height * 2;
  18518. if (this.width < this.height) {
  18519. this.width = this.height;
  18520. }
  18521. var defaultSize = this.width;
  18522. // scaling used for clustering
  18523. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  18524. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  18525. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  18526. this.growthIndicator = this.width - defaultSize;
  18527. }
  18528. };
  18529. Node.prototype._drawEllipse = function (ctx) {
  18530. this._resizeEllipse(ctx);
  18531. this.left = this.x - this.width / 2;
  18532. this.top = this.y - this.height / 2;
  18533. var clusterLineWidth = 2.5;
  18534. var borderWidth = this.options.borderWidth;
  18535. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  18536. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  18537. // draw the outer border
  18538. if (this.clusterSize > 1) {
  18539. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18540. ctx.lineWidth *= this.networkScaleInv;
  18541. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18542. ctx.ellipse(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth);
  18543. ctx.stroke();
  18544. }
  18545. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18546. ctx.lineWidth *= this.networkScaleInv;
  18547. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18548. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  18549. ctx.ellipse(this.left, this.top, this.width, this.height);
  18550. ctx.fill();
  18551. ctx.stroke();
  18552. this.boundingBox.top = this.top;
  18553. this.boundingBox.left = this.left;
  18554. this.boundingBox.right = this.left + this.width;
  18555. this.boundingBox.bottom = this.top + this.height;
  18556. this._label(ctx, this.label, this.x, this.y);
  18557. };
  18558. Node.prototype._drawDot = function (ctx) {
  18559. this._drawShape(ctx, 'circle');
  18560. };
  18561. Node.prototype._drawTriangle = function (ctx) {
  18562. this._drawShape(ctx, 'triangle');
  18563. };
  18564. Node.prototype._drawTriangleDown = function (ctx) {
  18565. this._drawShape(ctx, 'triangleDown');
  18566. };
  18567. Node.prototype._drawSquare = function (ctx) {
  18568. this._drawShape(ctx, 'square');
  18569. };
  18570. Node.prototype._drawStar = function (ctx) {
  18571. this._drawShape(ctx, 'star');
  18572. };
  18573. Node.prototype._resizeShape = function (ctx) {
  18574. if (!this.width) {
  18575. this.options.radius= this.baseRadiusValue;
  18576. var size = 2 * this.options.radius;
  18577. this.width = size;
  18578. this.height = size;
  18579. // scaling used for clustering
  18580. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  18581. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  18582. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  18583. this.growthIndicator = this.width - size;
  18584. }
  18585. };
  18586. Node.prototype._drawShape = function (ctx, shape) {
  18587. this._resizeShape(ctx);
  18588. this.left = this.x - this.width / 2;
  18589. this.top = this.y - this.height / 2;
  18590. var clusterLineWidth = 2.5;
  18591. var borderWidth = this.options.borderWidth;
  18592. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  18593. var radiusMultiplier = 2;
  18594. // choose draw method depending on the shape
  18595. switch (shape) {
  18596. case 'dot': radiusMultiplier = 2; break;
  18597. case 'square': radiusMultiplier = 2; break;
  18598. case 'triangle': radiusMultiplier = 3; break;
  18599. case 'triangleDown': radiusMultiplier = 3; break;
  18600. case 'star': radiusMultiplier = 4; break;
  18601. }
  18602. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  18603. // draw the outer border
  18604. if (this.clusterSize > 1) {
  18605. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18606. ctx.lineWidth *= this.networkScaleInv;
  18607. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18608. ctx[shape](this.x, this.y, this.options.radius+ radiusMultiplier * ctx.lineWidth);
  18609. ctx.stroke();
  18610. }
  18611. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  18612. ctx.lineWidth *= this.networkScaleInv;
  18613. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  18614. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  18615. ctx[shape](this.x, this.y, this.options.radius);
  18616. ctx.fill();
  18617. ctx.stroke();
  18618. this.boundingBox.top = this.y - this.options.radius;
  18619. this.boundingBox.left = this.x - this.options.radius;
  18620. this.boundingBox.right = this.x + this.options.radius;
  18621. this.boundingBox.bottom = this.y + this.options.radius;
  18622. if (this.label) {
  18623. this._label(ctx, this.label, this.x, this.y + this.height / 2, undefined, 'hanging',true);
  18624. this.boundingBox.left = Math.min(this.boundingBox.left, this.labelDimensions.left);
  18625. this.boundingBox.right = Math.max(this.boundingBox.right, this.labelDimensions.left + this.labelDimensions.width);
  18626. this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelDimensions.height);
  18627. }
  18628. };
  18629. Node.prototype._resizeText = function (ctx) {
  18630. if (!this.width) {
  18631. var margin = 5;
  18632. var textSize = this.getTextSize(ctx);
  18633. this.width = textSize.width + 2 * margin;
  18634. this.height = textSize.height + 2 * margin;
  18635. // scaling used for clustering
  18636. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  18637. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  18638. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  18639. this.growthIndicator = this.width - (textSize.width + 2 * margin);
  18640. }
  18641. };
  18642. Node.prototype._drawText = function (ctx) {
  18643. this._resizeText(ctx);
  18644. this.left = this.x - this.width / 2;
  18645. this.top = this.y - this.height / 2;
  18646. this._label(ctx, this.label, this.x, this.y);
  18647. this.boundingBox.top = this.top;
  18648. this.boundingBox.left = this.left;
  18649. this.boundingBox.right = this.left + this.width;
  18650. this.boundingBox.bottom = this.top + this.height;
  18651. };
  18652. Node.prototype._resizeIcon = function (ctx) {
  18653. if (!this.width) {
  18654. var margin = 5;
  18655. var iconSize =
  18656. {
  18657. width: Number(this.options.iconSize),
  18658. height: Number(this.options.iconSize)
  18659. };
  18660. this.width = iconSize.width + 2 * margin;
  18661. this.height = iconSize.height + 2 * margin;
  18662. // scaling used for clustering
  18663. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  18664. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  18665. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  18666. this.growthIndicator = this.width - (iconSize.width + 2 * margin);
  18667. }
  18668. };
  18669. Node.prototype._drawIcon = function (ctx) {
  18670. this._resizeIcon(ctx);
  18671. this.options.iconSize = this.options.iconSize || 50;
  18672. this.left = this.x - this.width / 2;
  18673. this.top = this.y - this.height / 2;
  18674. this._icon(ctx);
  18675. this.boundingBox.top = this.y - this.options.iconSize/2;
  18676. this.boundingBox.left = this.x - this.options.iconSize/2;
  18677. this.boundingBox.right = this.x + this.options.iconSize/2;
  18678. this.boundingBox.bottom = this.y + this.options.iconSize/2;
  18679. if (this.label) {
  18680. var iconTextSpacing = 5;
  18681. this._label(ctx, this.label, this.x, this.y + this.height / 2 + iconTextSpacing, 'top', true);
  18682. this.boundingBox.left = Math.min(this.boundingBox.left, this.labelDimensions.left);
  18683. this.boundingBox.right = Math.max(this.boundingBox.right, this.labelDimensions.left + this.labelDimensions.width);
  18684. this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelDimensions.height);
  18685. }
  18686. };
  18687. Node.prototype._icon = function (ctx) {
  18688. var relativeIconSize = Number(this.options.iconSize) * this.networkScale;
  18689. if (this.options.icon && relativeIconSize > this.options.fontDrawThreshold - 1) {
  18690. var iconSize = Number(this.options.iconSize);
  18691. ctx.font = (this.selected ? "bold " : "") + iconSize + "px " + this.options.iconFontFace;
  18692. // draw icon
  18693. ctx.fillStyle = this.options.iconColor || "black";
  18694. ctx.textAlign = "center";
  18695. ctx.textBaseline = "middle";
  18696. ctx.fillText(this.options.icon, this.x, this.y);
  18697. }
  18698. };
  18699. Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNode) {
  18700. var relativeFontSize = Number(this.options.fontSize) * this.networkScale;
  18701. if (text && relativeFontSize >= this.options.fontDrawThreshold - 1) {
  18702. var fontSize = Number(this.options.fontSize);
  18703. // this ensures that there will not be HUGE letters on screen by setting an upper limit on the visible text size (regardless of zoomLevel)
  18704. if (relativeFontSize >= this.options.fontSizeMaxVisible) {
  18705. fontSize = Number(this.options.fontSizeMaxVisible) * this.networkScaleInv;
  18706. }
  18707. // fade in when relative scale is between threshold and threshold - 1
  18708. var fontColor = this.options.fontColor || "#000000";
  18709. var strokecolor = this.options.fontStrokeColor;
  18710. if (relativeFontSize <= this.options.fontDrawThreshold) {
  18711. var opacity = Math.max(0,Math.min(1,1 - (this.options.fontDrawThreshold - relativeFontSize)));
  18712. fontColor = util.overrideOpacity(fontColor, opacity);
  18713. strokecolor = util.overrideOpacity(strokecolor, opacity);
  18714. }
  18715. ctx.font = (this.selected ? "bold " : "") + fontSize + "px " + this.options.fontFace;
  18716. var lines = text.split('\n');
  18717. var lineCount = lines.length;
  18718. var yLine = y + (1 - lineCount) / 2 * fontSize;
  18719. if (labelUnderNode == true) {
  18720. yLine = y + (1 - lineCount) / (2 * fontSize);
  18721. }
  18722. // font fill from edges now for nodes!
  18723. var width = ctx.measureText(lines[0]).width;
  18724. for (var i = 1; i < lineCount; i++) {
  18725. var lineWidth = ctx.measureText(lines[i]).width;
  18726. width = lineWidth > width ? lineWidth : width;
  18727. }
  18728. var height = fontSize * lineCount;
  18729. var left = x - width / 2;
  18730. var top = y - height / 2;
  18731. if (baseline == "hanging") {
  18732. top += 0.5 * fontSize;
  18733. top += 4; // distance from node, required because we use hanging. Hanging has less difference between browsers
  18734. yLine += 4; // distance from node
  18735. }
  18736. this.labelDimensions = {top:top,left:left,width:width,height:height,yLine:yLine};
  18737. // create the fontfill background
  18738. if (this.options.fontFill !== undefined && this.options.fontFill !== null && this.options.fontFill !== "none") {
  18739. ctx.fillStyle = this.options.fontFill;
  18740. ctx.fillRect(left, top, width, height);
  18741. }
  18742. // draw text
  18743. ctx.fillStyle = fontColor;
  18744. ctx.textAlign = align || "center";
  18745. ctx.textBaseline = baseline || "middle";
  18746. if (this.options.fontStrokeWidth > 0){
  18747. ctx.lineWidth = this.options.fontStrokeWidth;
  18748. ctx.strokeStyle = strokecolor;
  18749. ctx.lineJoin = 'round';
  18750. }
  18751. for (var i = 0; i < lineCount; i++) {
  18752. if(this.options.fontStrokeWidth){
  18753. ctx.strokeText(lines[i], x, yLine);
  18754. }
  18755. ctx.fillText(lines[i], x, yLine);
  18756. yLine += fontSize;
  18757. }
  18758. }
  18759. };
  18760. Node.prototype.getTextSize = function(ctx) {
  18761. if (this.label !== undefined) {
  18762. var fontSize = Number(this.options.fontSize);
  18763. if (fontSize * this.networkScale > this.options.fontSizeMaxVisible) {
  18764. fontSize = Number(this.options.fontSizeMaxVisible) * this.networkScaleInv;
  18765. }
  18766. ctx.font = (this.selected ? "bold " : "") + fontSize + "px " + this.options.fontFace;
  18767. var lines = this.label.split('\n'),
  18768. height = (fontSize + 4) * lines.length,
  18769. width = 0;
  18770. for (var i = 0, iMax = lines.length; i < iMax; i++) {
  18771. width = Math.max(width, ctx.measureText(lines[i]).width);
  18772. }
  18773. return {"width": width, "height": height, lineCount: lines.length};
  18774. }
  18775. else {
  18776. return {"width": 0, "height": 0, lineCount: 0};
  18777. }
  18778. };
  18779. /**
  18780. * this is used to determine if a node is visible at all. this is used to determine when it needs to be drawn.
  18781. * there is a safety margin of 0.3 * width;
  18782. *
  18783. * @returns {boolean}
  18784. */
  18785. Node.prototype.inArea = function() {
  18786. if (this.width !== undefined) {
  18787. return (this.x + this.width *this.networkScaleInv >= this.canvasTopLeft.x &&
  18788. this.x - this.width *this.networkScaleInv < this.canvasBottomRight.x &&
  18789. this.y + this.height*this.networkScaleInv >= this.canvasTopLeft.y &&
  18790. this.y - this.height*this.networkScaleInv < this.canvasBottomRight.y);
  18791. }
  18792. else {
  18793. return true;
  18794. }
  18795. };
  18796. /**
  18797. * checks if the core of the node is in the display area, this is used for opening clusters around zoom
  18798. * @returns {boolean}
  18799. */
  18800. Node.prototype.inView = function() {
  18801. return (this.x >= this.canvasTopLeft.x &&
  18802. this.x < this.canvasBottomRight.x &&
  18803. this.y >= this.canvasTopLeft.y &&
  18804. this.y < this.canvasBottomRight.y);
  18805. };
  18806. /**
  18807. * This allows the zoom level of the network to influence the rendering
  18808. * We store the inverted scale and the coordinates of the top left, and bottom right points of the canvas
  18809. *
  18810. * @param scale
  18811. * @param canvasTopLeft
  18812. * @param canvasBottomRight
  18813. */
  18814. Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight) {
  18815. this.networkScaleInv = 1.0/scale;
  18816. this.networkScale = scale;
  18817. this.canvasTopLeft = canvasTopLeft;
  18818. this.canvasBottomRight = canvasBottomRight;
  18819. };
  18820. /**
  18821. * This allows the zoom level of the network to influence the rendering
  18822. *
  18823. * @param scale
  18824. */
  18825. Node.prototype.setScale = function(scale) {
  18826. this.networkScaleInv = 1.0/scale;
  18827. this.networkScale = scale;
  18828. };
  18829. /**
  18830. * set the velocity at 0. Is called when this node is contained in another during clustering
  18831. */
  18832. Node.prototype.clearVelocity = function() {
  18833. this.vx = 0;
  18834. this.vy = 0;
  18835. };
  18836. /**
  18837. * Basic preservation of (kinectic) energy
  18838. *
  18839. * @param massBeforeClustering
  18840. */
  18841. Node.prototype.updateVelocity = function(massBeforeClustering) {
  18842. var energyBefore = this.vx * this.vx * massBeforeClustering;
  18843. //this.vx = (this.vx < 0) ? -Math.sqrt(energyBefore/this.options.mass) : Math.sqrt(energyBefore/this.options.mass);
  18844. this.vx = Math.sqrt(energyBefore/this.options.mass);
  18845. energyBefore = this.vy * this.vy * massBeforeClustering;
  18846. //this.vy = (this.vy < 0) ? -Math.sqrt(energyBefore/this.options.mass) : Math.sqrt(energyBefore/this.options.mass);
  18847. this.vy = Math.sqrt(energyBefore/this.options.mass);
  18848. };
  18849. module.exports = Node;
  18850. /***/ },
  18851. /* 41 */
  18852. /***/ function(module, exports, __webpack_require__) {
  18853. /**
  18854. * Popup is a class to create a popup window with some text
  18855. * @param {Element} container The container object.
  18856. * @param {Number} [x]
  18857. * @param {Number} [y]
  18858. * @param {String} [text]
  18859. * @param {Object} [style] An object containing borderColor,
  18860. * backgroundColor, etc.
  18861. */
  18862. function Popup(container, x, y, text, style) {
  18863. if (container) {
  18864. this.container = container;
  18865. }
  18866. else {
  18867. this.container = document.body;
  18868. }
  18869. // x, y and text are optional, see if a style object was passed in their place
  18870. if (style === undefined) {
  18871. if (typeof x === "object") {
  18872. style = x;
  18873. x = undefined;
  18874. } else if (typeof text === "object") {
  18875. style = text;
  18876. text = undefined;
  18877. } else {
  18878. // for backwards compatibility, in case clients other than Network are creating Popup directly
  18879. style = {
  18880. fontColor: 'black',
  18881. fontSize: 14, // px
  18882. fontFace: 'verdana',
  18883. color: {
  18884. border: '#666',
  18885. background: '#FFFFC6'
  18886. }
  18887. }
  18888. }
  18889. }
  18890. this.x = 0;
  18891. this.y = 0;
  18892. this.padding = 5;
  18893. this.hidden = false;
  18894. if (x !== undefined && y !== undefined) {
  18895. this.setPosition(x, y);
  18896. }
  18897. if (text !== undefined) {
  18898. this.setText(text);
  18899. }
  18900. // create the frame
  18901. this.frame = document.createElement('div');
  18902. this.frame.className = 'network-tooltip';
  18903. this.frame.style.color = style.fontColor;
  18904. this.frame.style.backgroundColor = style.color.background;
  18905. this.frame.style.borderColor = style.color.border;
  18906. this.frame.style.fontSize = style.fontSize + 'px';
  18907. this.frame.style.fontFamily = style.fontFace;
  18908. this.container.appendChild(this.frame);
  18909. }
  18910. /**
  18911. * @param {number} x Horizontal position of the popup window
  18912. * @param {number} y Vertical position of the popup window
  18913. */
  18914. Popup.prototype.setPosition = function(x, y) {
  18915. this.x = parseInt(x);
  18916. this.y = parseInt(y);
  18917. };
  18918. /**
  18919. * Set the content for the popup window. This can be HTML code or text.
  18920. * @param {string | Element} content
  18921. */
  18922. Popup.prototype.setText = function(content) {
  18923. if (content instanceof Element) {
  18924. this.frame.innerHTML = '';
  18925. this.frame.appendChild(content);
  18926. }
  18927. else {
  18928. this.frame.innerHTML = content; // string containing text or HTML
  18929. }
  18930. };
  18931. /**
  18932. * Show the popup window
  18933. * @param {boolean} show Optional. Show or hide the window
  18934. */
  18935. Popup.prototype.show = function (show) {
  18936. if (show === undefined) {
  18937. show = true;
  18938. }
  18939. if (show) {
  18940. var height = this.frame.clientHeight;
  18941. var width = this.frame.clientWidth;
  18942. var maxHeight = this.frame.parentNode.clientHeight;
  18943. var maxWidth = this.frame.parentNode.clientWidth;
  18944. var top = (this.y - height);
  18945. if (top + height + this.padding > maxHeight) {
  18946. top = maxHeight - height - this.padding;
  18947. }
  18948. if (top < this.padding) {
  18949. top = this.padding;
  18950. }
  18951. var left = this.x;
  18952. if (left + width + this.padding > maxWidth) {
  18953. left = maxWidth - width - this.padding;
  18954. }
  18955. if (left < this.padding) {
  18956. left = this.padding;
  18957. }
  18958. this.frame.style.left = left + "px";
  18959. this.frame.style.top = top + "px";
  18960. this.frame.style.visibility = "visible";
  18961. this.hidden = false;
  18962. }
  18963. else {
  18964. this.hide();
  18965. }
  18966. };
  18967. /**
  18968. * Hide the popup window
  18969. */
  18970. Popup.prototype.hide = function () {
  18971. this.hidden = true;
  18972. this.frame.style.visibility = "hidden";
  18973. };
  18974. module.exports = Popup;
  18975. /***/ },
  18976. /* 42 */
  18977. /***/ function(module, exports, __webpack_require__) {
  18978. /**
  18979. * Parse a text source containing data in DOT language into a JSON object.
  18980. * The object contains two lists: one with nodes and one with edges.
  18981. *
  18982. * DOT language reference: http://www.graphviz.org/doc/info/lang.html
  18983. *
  18984. * @param {String} data Text containing a graph in DOT-notation
  18985. * @return {Object} graph An object containing two parameters:
  18986. * {Object[]} nodes
  18987. * {Object[]} edges
  18988. */
  18989. function parseDOT (data) {
  18990. dot = data;
  18991. return parseGraph();
  18992. }
  18993. // token types enumeration
  18994. var TOKENTYPE = {
  18995. NULL : 0,
  18996. DELIMITER : 1,
  18997. IDENTIFIER: 2,
  18998. UNKNOWN : 3
  18999. };
  19000. // map with all delimiters
  19001. var DELIMITERS = {
  19002. '{': true,
  19003. '}': true,
  19004. '[': true,
  19005. ']': true,
  19006. ';': true,
  19007. '=': true,
  19008. ',': true,
  19009. '->': true,
  19010. '--': true
  19011. };
  19012. var dot = ''; // current dot file
  19013. var index = 0; // current index in dot file
  19014. var c = ''; // current token character in expr
  19015. var token = ''; // current token
  19016. var tokenType = TOKENTYPE.NULL; // type of the token
  19017. /**
  19018. * Get the first character from the dot file.
  19019. * The character is stored into the char c. If the end of the dot file is
  19020. * reached, the function puts an empty string in c.
  19021. */
  19022. function first() {
  19023. index = 0;
  19024. c = dot.charAt(0);
  19025. }
  19026. /**
  19027. * Get the next character from the dot file.
  19028. * The character is stored into the char c. If the end of the dot file is
  19029. * reached, the function puts an empty string in c.
  19030. */
  19031. function next() {
  19032. index++;
  19033. c = dot.charAt(index);
  19034. }
  19035. /**
  19036. * Preview the next character from the dot file.
  19037. * @return {String} cNext
  19038. */
  19039. function nextPreview() {
  19040. return dot.charAt(index + 1);
  19041. }
  19042. /**
  19043. * Test whether given character is alphabetic or numeric
  19044. * @param {String} c
  19045. * @return {Boolean} isAlphaNumeric
  19046. */
  19047. var regexAlphaNumeric = /[a-zA-Z_0-9.:#]/;
  19048. function isAlphaNumeric(c) {
  19049. return regexAlphaNumeric.test(c);
  19050. }
  19051. /**
  19052. * Merge all properties of object b into object b
  19053. * @param {Object} a
  19054. * @param {Object} b
  19055. * @return {Object} a
  19056. */
  19057. function merge (a, b) {
  19058. if (!a) {
  19059. a = {};
  19060. }
  19061. if (b) {
  19062. for (var name in b) {
  19063. if (b.hasOwnProperty(name)) {
  19064. a[name] = b[name];
  19065. }
  19066. }
  19067. }
  19068. return a;
  19069. }
  19070. /**
  19071. * Set a value in an object, where the provided parameter name can be a
  19072. * path with nested parameters. For example:
  19073. *
  19074. * var obj = {a: 2};
  19075. * setValue(obj, 'b.c', 3); // obj = {a: 2, b: {c: 3}}
  19076. *
  19077. * @param {Object} obj
  19078. * @param {String} path A parameter name or dot-separated parameter path,
  19079. * like "color.highlight.border".
  19080. * @param {*} value
  19081. */
  19082. function setValue(obj, path, value) {
  19083. var keys = path.split('.');
  19084. var o = obj;
  19085. while (keys.length) {
  19086. var key = keys.shift();
  19087. if (keys.length) {
  19088. // this isn't the end point
  19089. if (!o[key]) {
  19090. o[key] = {};
  19091. }
  19092. o = o[key];
  19093. }
  19094. else {
  19095. // this is the end point
  19096. o[key] = value;
  19097. }
  19098. }
  19099. }
  19100. /**
  19101. * Add a node to a graph object. If there is already a node with
  19102. * the same id, their attributes will be merged.
  19103. * @param {Object} graph
  19104. * @param {Object} node
  19105. */
  19106. function addNode(graph, node) {
  19107. var i, len;
  19108. var current = null;
  19109. // find root graph (in case of subgraph)
  19110. var graphs = [graph]; // list with all graphs from current graph to root graph
  19111. var root = graph;
  19112. while (root.parent) {
  19113. graphs.push(root.parent);
  19114. root = root.parent;
  19115. }
  19116. // find existing node (at root level) by its id
  19117. if (root.nodes) {
  19118. for (i = 0, len = root.nodes.length; i < len; i++) {
  19119. if (node.id === root.nodes[i].id) {
  19120. current = root.nodes[i];
  19121. break;
  19122. }
  19123. }
  19124. }
  19125. if (!current) {
  19126. // this is a new node
  19127. current = {
  19128. id: node.id
  19129. };
  19130. if (graph.node) {
  19131. // clone default attributes
  19132. current.attr = merge(current.attr, graph.node);
  19133. }
  19134. }
  19135. // add node to this (sub)graph and all its parent graphs
  19136. for (i = graphs.length - 1; i >= 0; i--) {
  19137. var g = graphs[i];
  19138. if (!g.nodes) {
  19139. g.nodes = [];
  19140. }
  19141. if (g.nodes.indexOf(current) == -1) {
  19142. g.nodes.push(current);
  19143. }
  19144. }
  19145. // merge attributes
  19146. if (node.attr) {
  19147. current.attr = merge(current.attr, node.attr);
  19148. }
  19149. }
  19150. /**
  19151. * Add an edge to a graph object
  19152. * @param {Object} graph
  19153. * @param {Object} edge
  19154. */
  19155. function addEdge(graph, edge) {
  19156. if (!graph.edges) {
  19157. graph.edges = [];
  19158. }
  19159. graph.edges.push(edge);
  19160. if (graph.edge) {
  19161. var attr = merge({}, graph.edge); // clone default attributes
  19162. edge.attr = merge(attr, edge.attr); // merge attributes
  19163. }
  19164. }
  19165. /**
  19166. * Create an edge to a graph object
  19167. * @param {Object} graph
  19168. * @param {String | Number | Object} from
  19169. * @param {String | Number | Object} to
  19170. * @param {String} type
  19171. * @param {Object | null} attr
  19172. * @return {Object} edge
  19173. */
  19174. function createEdge(graph, from, to, type, attr) {
  19175. var edge = {
  19176. from: from,
  19177. to: to,
  19178. type: type
  19179. };
  19180. if (graph.edge) {
  19181. edge.attr = merge({}, graph.edge); // clone default attributes
  19182. }
  19183. edge.attr = merge(edge.attr || {}, attr); // merge attributes
  19184. return edge;
  19185. }
  19186. /**
  19187. * Get next token in the current dot file.
  19188. * The token and token type are available as token and tokenType
  19189. */
  19190. function getToken() {
  19191. tokenType = TOKENTYPE.NULL;
  19192. token = '';
  19193. // skip over whitespaces
  19194. while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter
  19195. next();
  19196. }
  19197. do {
  19198. var isComment = false;
  19199. // skip comment
  19200. if (c == '#') {
  19201. // find the previous non-space character
  19202. var i = index - 1;
  19203. while (dot.charAt(i) == ' ' || dot.charAt(i) == '\t') {
  19204. i--;
  19205. }
  19206. if (dot.charAt(i) == '\n' || dot.charAt(i) == '') {
  19207. // the # is at the start of a line, this is indeed a line comment
  19208. while (c != '' && c != '\n') {
  19209. next();
  19210. }
  19211. isComment = true;
  19212. }
  19213. }
  19214. if (c == '/' && nextPreview() == '/') {
  19215. // skip line comment
  19216. while (c != '' && c != '\n') {
  19217. next();
  19218. }
  19219. isComment = true;
  19220. }
  19221. if (c == '/' && nextPreview() == '*') {
  19222. // skip block comment
  19223. while (c != '') {
  19224. if (c == '*' && nextPreview() == '/') {
  19225. // end of block comment found. skip these last two characters
  19226. next();
  19227. next();
  19228. break;
  19229. }
  19230. else {
  19231. next();
  19232. }
  19233. }
  19234. isComment = true;
  19235. }
  19236. // skip over whitespaces
  19237. while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter
  19238. next();
  19239. }
  19240. }
  19241. while (isComment);
  19242. // check for end of dot file
  19243. if (c == '') {
  19244. // token is still empty
  19245. tokenType = TOKENTYPE.DELIMITER;
  19246. return;
  19247. }
  19248. // check for delimiters consisting of 2 characters
  19249. var c2 = c + nextPreview();
  19250. if (DELIMITERS[c2]) {
  19251. tokenType = TOKENTYPE.DELIMITER;
  19252. token = c2;
  19253. next();
  19254. next();
  19255. return;
  19256. }
  19257. // check for delimiters consisting of 1 character
  19258. if (DELIMITERS[c]) {
  19259. tokenType = TOKENTYPE.DELIMITER;
  19260. token = c;
  19261. next();
  19262. return;
  19263. }
  19264. // check for an identifier (number or string)
  19265. // TODO: more precise parsing of numbers/strings (and the port separator ':')
  19266. if (isAlphaNumeric(c) || c == '-') {
  19267. token += c;
  19268. next();
  19269. while (isAlphaNumeric(c)) {
  19270. token += c;
  19271. next();
  19272. }
  19273. if (token == 'false') {
  19274. token = false; // convert to boolean
  19275. }
  19276. else if (token == 'true') {
  19277. token = true; // convert to boolean
  19278. }
  19279. else if (!isNaN(Number(token))) {
  19280. token = Number(token); // convert to number
  19281. }
  19282. tokenType = TOKENTYPE.IDENTIFIER;
  19283. return;
  19284. }
  19285. // check for a string enclosed by double quotes
  19286. if (c == '"') {
  19287. next();
  19288. while (c != '' && (c != '"' || (c == '"' && nextPreview() == '"'))) {
  19289. token += c;
  19290. if (c == '"') { // skip the escape character
  19291. next();
  19292. }
  19293. next();
  19294. }
  19295. if (c != '"') {
  19296. throw newSyntaxError('End of string " expected');
  19297. }
  19298. next();
  19299. tokenType = TOKENTYPE.IDENTIFIER;
  19300. return;
  19301. }
  19302. // something unknown is found, wrong characters, a syntax error
  19303. tokenType = TOKENTYPE.UNKNOWN;
  19304. while (c != '') {
  19305. token += c;
  19306. next();
  19307. }
  19308. throw new SyntaxError('Syntax error in part "' + chop(token, 30) + '"');
  19309. }
  19310. /**
  19311. * Parse a graph.
  19312. * @returns {Object} graph
  19313. */
  19314. function parseGraph() {
  19315. var graph = {};
  19316. first();
  19317. getToken();
  19318. // optional strict keyword
  19319. if (token == 'strict') {
  19320. graph.strict = true;
  19321. getToken();
  19322. }
  19323. // graph or digraph keyword
  19324. if (token == 'graph' || token == 'digraph') {
  19325. graph.type = token;
  19326. getToken();
  19327. }
  19328. // optional graph id
  19329. if (tokenType == TOKENTYPE.IDENTIFIER) {
  19330. graph.id = token;
  19331. getToken();
  19332. }
  19333. // open angle bracket
  19334. if (token != '{') {
  19335. throw newSyntaxError('Angle bracket { expected');
  19336. }
  19337. getToken();
  19338. // statements
  19339. parseStatements(graph);
  19340. // close angle bracket
  19341. if (token != '}') {
  19342. throw newSyntaxError('Angle bracket } expected');
  19343. }
  19344. getToken();
  19345. // end of file
  19346. if (token !== '') {
  19347. throw newSyntaxError('End of file expected');
  19348. }
  19349. getToken();
  19350. // remove temporary default properties
  19351. delete graph.node;
  19352. delete graph.edge;
  19353. delete graph.graph;
  19354. return graph;
  19355. }
  19356. /**
  19357. * Parse a list with statements.
  19358. * @param {Object} graph
  19359. */
  19360. function parseStatements (graph) {
  19361. while (token !== '' && token != '}') {
  19362. parseStatement(graph);
  19363. if (token == ';') {
  19364. getToken();
  19365. }
  19366. }
  19367. }
  19368. /**
  19369. * Parse a single statement. Can be a an attribute statement, node
  19370. * statement, a series of node statements and edge statements, or a
  19371. * parameter.
  19372. * @param {Object} graph
  19373. */
  19374. function parseStatement(graph) {
  19375. // parse subgraph
  19376. var subgraph = parseSubgraph(graph);
  19377. if (subgraph) {
  19378. // edge statements
  19379. parseEdge(graph, subgraph);
  19380. return;
  19381. }
  19382. // parse an attribute statement
  19383. var attr = parseAttributeStatement(graph);
  19384. if (attr) {
  19385. return;
  19386. }
  19387. // parse node
  19388. if (tokenType != TOKENTYPE.IDENTIFIER) {
  19389. throw newSyntaxError('Identifier expected');
  19390. }
  19391. var id = token; // id can be a string or a number
  19392. getToken();
  19393. if (token == '=') {
  19394. // id statement
  19395. getToken();
  19396. if (tokenType != TOKENTYPE.IDENTIFIER) {
  19397. throw newSyntaxError('Identifier expected');
  19398. }
  19399. graph[id] = token;
  19400. getToken();
  19401. // TODO: implement comma separated list with "a_list: ID=ID [','] [a_list] "
  19402. }
  19403. else {
  19404. parseNodeStatement(graph, id);
  19405. }
  19406. }
  19407. /**
  19408. * Parse a subgraph
  19409. * @param {Object} graph parent graph object
  19410. * @return {Object | null} subgraph
  19411. */
  19412. function parseSubgraph (graph) {
  19413. var subgraph = null;
  19414. // optional subgraph keyword
  19415. if (token == 'subgraph') {
  19416. subgraph = {};
  19417. subgraph.type = 'subgraph';
  19418. getToken();
  19419. // optional graph id
  19420. if (tokenType == TOKENTYPE.IDENTIFIER) {
  19421. subgraph.id = token;
  19422. getToken();
  19423. }
  19424. }
  19425. // open angle bracket
  19426. if (token == '{') {
  19427. getToken();
  19428. if (!subgraph) {
  19429. subgraph = {};
  19430. }
  19431. subgraph.parent = graph;
  19432. subgraph.node = graph.node;
  19433. subgraph.edge = graph.edge;
  19434. subgraph.graph = graph.graph;
  19435. // statements
  19436. parseStatements(subgraph);
  19437. // close angle bracket
  19438. if (token != '}') {
  19439. throw newSyntaxError('Angle bracket } expected');
  19440. }
  19441. getToken();
  19442. // remove temporary default properties
  19443. delete subgraph.node;
  19444. delete subgraph.edge;
  19445. delete subgraph.graph;
  19446. delete subgraph.parent;
  19447. // register at the parent graph
  19448. if (!graph.subgraphs) {
  19449. graph.subgraphs = [];
  19450. }
  19451. graph.subgraphs.push(subgraph);
  19452. }
  19453. return subgraph;
  19454. }
  19455. /**
  19456. * parse an attribute statement like "node [shape=circle fontSize=16]".
  19457. * Available keywords are 'node', 'edge', 'graph'.
  19458. * The previous list with default attributes will be replaced
  19459. * @param {Object} graph
  19460. * @returns {String | null} keyword Returns the name of the parsed attribute
  19461. * (node, edge, graph), or null if nothing
  19462. * is parsed.
  19463. */
  19464. function parseAttributeStatement (graph) {
  19465. // attribute statements
  19466. if (token == 'node') {
  19467. getToken();
  19468. // node attributes
  19469. graph.node = parseAttributeList();
  19470. return 'node';
  19471. }
  19472. else if (token == 'edge') {
  19473. getToken();
  19474. // edge attributes
  19475. graph.edge = parseAttributeList();
  19476. return 'edge';
  19477. }
  19478. else if (token == 'graph') {
  19479. getToken();
  19480. // graph attributes
  19481. graph.graph = parseAttributeList();
  19482. return 'graph';
  19483. }
  19484. return null;
  19485. }
  19486. /**
  19487. * parse a node statement
  19488. * @param {Object} graph
  19489. * @param {String | Number} id
  19490. */
  19491. function parseNodeStatement(graph, id) {
  19492. // node statement
  19493. var node = {
  19494. id: id
  19495. };
  19496. var attr = parseAttributeList();
  19497. if (attr) {
  19498. node.attr = attr;
  19499. }
  19500. addNode(graph, node);
  19501. // edge statements
  19502. parseEdge(graph, id);
  19503. }
  19504. /**
  19505. * Parse an edge or a series of edges
  19506. * @param {Object} graph
  19507. * @param {String | Number} from Id of the from node
  19508. */
  19509. function parseEdge(graph, from) {
  19510. while (token == '->' || token == '--') {
  19511. var to;
  19512. var type = token;
  19513. getToken();
  19514. var subgraph = parseSubgraph(graph);
  19515. if (subgraph) {
  19516. to = subgraph;
  19517. }
  19518. else {
  19519. if (tokenType != TOKENTYPE.IDENTIFIER) {
  19520. throw newSyntaxError('Identifier or subgraph expected');
  19521. }
  19522. to = token;
  19523. addNode(graph, {
  19524. id: to
  19525. });
  19526. getToken();
  19527. }
  19528. // parse edge attributes
  19529. var attr = parseAttributeList();
  19530. // create edge
  19531. var edge = createEdge(graph, from, to, type, attr);
  19532. addEdge(graph, edge);
  19533. from = to;
  19534. }
  19535. }
  19536. /**
  19537. * Parse a set with attributes,
  19538. * for example [label="1.000", shape=solid]
  19539. * @return {Object | null} attr
  19540. */
  19541. function parseAttributeList() {
  19542. var attr = null;
  19543. while (token == '[') {
  19544. getToken();
  19545. attr = {};
  19546. while (token !== '' && token != ']') {
  19547. if (tokenType != TOKENTYPE.IDENTIFIER) {
  19548. throw newSyntaxError('Attribute name expected');
  19549. }
  19550. var name = token;
  19551. getToken();
  19552. if (token != '=') {
  19553. throw newSyntaxError('Equal sign = expected');
  19554. }
  19555. getToken();
  19556. if (tokenType != TOKENTYPE.IDENTIFIER) {
  19557. throw newSyntaxError('Attribute value expected');
  19558. }
  19559. var value = token;
  19560. setValue(attr, name, value); // name can be a path
  19561. getToken();
  19562. if (token ==',') {
  19563. getToken();
  19564. }
  19565. }
  19566. if (token != ']') {
  19567. throw newSyntaxError('Bracket ] expected');
  19568. }
  19569. getToken();
  19570. }
  19571. return attr;
  19572. }
  19573. /**
  19574. * Create a syntax error with extra information on current token and index.
  19575. * @param {String} message
  19576. * @returns {SyntaxError} err
  19577. */
  19578. function newSyntaxError(message) {
  19579. return new SyntaxError(message + ', got "' + chop(token, 30) + '" (char ' + index + ')');
  19580. }
  19581. /**
  19582. * Chop off text after a maximum length
  19583. * @param {String} text
  19584. * @param {Number} maxLength
  19585. * @returns {String}
  19586. */
  19587. function chop (text, maxLength) {
  19588. return (text.length <= maxLength) ? text : (text.substr(0, 27) + '...');
  19589. }
  19590. /**
  19591. * Execute a function fn for each pair of elements in two arrays
  19592. * @param {Array | *} array1
  19593. * @param {Array | *} array2
  19594. * @param {function} fn
  19595. */
  19596. function forEach2(array1, array2, fn) {
  19597. if (Array.isArray(array1)) {
  19598. array1.forEach(function (elem1) {
  19599. if (Array.isArray(array2)) {
  19600. array2.forEach(function (elem2) {
  19601. fn(elem1, elem2);
  19602. });
  19603. }
  19604. else {
  19605. fn(elem1, array2);
  19606. }
  19607. });
  19608. }
  19609. else {
  19610. if (Array.isArray(array2)) {
  19611. array2.forEach(function (elem2) {
  19612. fn(array1, elem2);
  19613. });
  19614. }
  19615. else {
  19616. fn(array1, array2);
  19617. }
  19618. }
  19619. }
  19620. /**
  19621. * Convert a string containing a graph in DOT language into a map containing
  19622. * with nodes and edges in the format of graph.
  19623. * @param {String} data Text containing a graph in DOT-notation
  19624. * @return {Object} graphData
  19625. */
  19626. function DOTToGraph (data) {
  19627. // parse the DOT file
  19628. var dotData = parseDOT(data);
  19629. var graphData = {
  19630. nodes: [],
  19631. edges: [],
  19632. options: {}
  19633. };
  19634. // copy the nodes
  19635. if (dotData.nodes) {
  19636. dotData.nodes.forEach(function (dotNode) {
  19637. var graphNode = {
  19638. id: dotNode.id,
  19639. label: String(dotNode.label || dotNode.id)
  19640. };
  19641. merge(graphNode, dotNode.attr);
  19642. if (graphNode.image) {
  19643. graphNode.shape = 'image';
  19644. }
  19645. graphData.nodes.push(graphNode);
  19646. });
  19647. }
  19648. // copy the edges
  19649. if (dotData.edges) {
  19650. /**
  19651. * Convert an edge in DOT format to an edge with VisGraph format
  19652. * @param {Object} dotEdge
  19653. * @returns {Object} graphEdge
  19654. */
  19655. var convertEdge = function (dotEdge) {
  19656. var graphEdge = {
  19657. from: dotEdge.from,
  19658. to: dotEdge.to
  19659. };
  19660. merge(graphEdge, dotEdge.attr);
  19661. graphEdge.style = (dotEdge.type == '->') ? 'arrow' : 'line';
  19662. return graphEdge;
  19663. }
  19664. dotData.edges.forEach(function (dotEdge) {
  19665. var from, to;
  19666. if (dotEdge.from instanceof Object) {
  19667. from = dotEdge.from.nodes;
  19668. }
  19669. else {
  19670. from = {
  19671. id: dotEdge.from
  19672. }
  19673. }
  19674. if (dotEdge.to instanceof Object) {
  19675. to = dotEdge.to.nodes;
  19676. }
  19677. else {
  19678. to = {
  19679. id: dotEdge.to
  19680. }
  19681. }
  19682. if (dotEdge.from instanceof Object && dotEdge.from.edges) {
  19683. dotEdge.from.edges.forEach(function (subEdge) {
  19684. var graphEdge = convertEdge(subEdge);
  19685. graphData.edges.push(graphEdge);
  19686. });
  19687. }
  19688. forEach2(from, to, function (from, to) {
  19689. var subEdge = createEdge(graphData, from.id, to.id, dotEdge.type, dotEdge.attr);
  19690. var graphEdge = convertEdge(subEdge);
  19691. graphData.edges.push(graphEdge);
  19692. });
  19693. if (dotEdge.to instanceof Object && dotEdge.to.edges) {
  19694. dotEdge.to.edges.forEach(function (subEdge) {
  19695. var graphEdge = convertEdge(subEdge);
  19696. graphData.edges.push(graphEdge);
  19697. });
  19698. }
  19699. });
  19700. }
  19701. // copy the options
  19702. if (dotData.attr) {
  19703. graphData.options = dotData.attr;
  19704. }
  19705. return graphData;
  19706. }
  19707. // exports
  19708. exports.parseDOT = parseDOT;
  19709. exports.DOTToGraph = DOTToGraph;
  19710. /***/ },
  19711. /* 43 */
  19712. /***/ function(module, exports, __webpack_require__) {
  19713. function parseGephi(gephiJSON, options) {
  19714. var edges = [];
  19715. var nodes = [];
  19716. this.options = {
  19717. edges: {
  19718. inheritColor: true
  19719. },
  19720. nodes: {
  19721. allowedToMove: false,
  19722. parseColor: false
  19723. }
  19724. };
  19725. if (options !== undefined) {
  19726. this.options.nodes['allowedToMove'] = options.allowedToMove | false;
  19727. this.options.nodes['parseColor'] = options.parseColor | false;
  19728. this.options.edges['inheritColor'] = options.inheritColor | true;
  19729. }
  19730. var gEdges = gephiJSON.edges;
  19731. var gNodes = gephiJSON.nodes;
  19732. for (var i = 0; i < gEdges.length; i++) {
  19733. var edge = {};
  19734. var gEdge = gEdges[i];
  19735. edge['id'] = gEdge.id;
  19736. edge['from'] = gEdge.source;
  19737. edge['to'] = gEdge.target;
  19738. edge['attributes'] = gEdge.attributes;
  19739. // edge['value'] = gEdge.attributes !== undefined ? gEdge.attributes.Weight : undefined;
  19740. // edge['width'] = edge['value'] !== undefined ? undefined : edgegEdge.size;
  19741. edge['color'] = gEdge.color;
  19742. edge['inheritColor'] = edge['color'] !== undefined ? false : this.options.inheritColor;
  19743. edges.push(edge);
  19744. }
  19745. for (var i = 0; i < gNodes.length; i++) {
  19746. var node = {};
  19747. var gNode = gNodes[i];
  19748. node['id'] = gNode.id;
  19749. node['attributes'] = gNode.attributes;
  19750. node['x'] = gNode.x;
  19751. node['y'] = gNode.y;
  19752. node['label'] = gNode.label;
  19753. if (this.options.nodes.parseColor == true) {
  19754. node['color'] = gNode.color;
  19755. }
  19756. else {
  19757. node['color'] = gNode.color !== undefined ? {background:gNode.color, border:gNode.color} : undefined;
  19758. }
  19759. node['radius'] = gNode.size;
  19760. node['allowedToMoveX'] = this.options.nodes.allowedToMove;
  19761. node['allowedToMoveY'] = this.options.nodes.allowedToMove;
  19762. nodes.push(node);
  19763. }
  19764. return {nodes:nodes, edges:edges};
  19765. }
  19766. exports.parseGephi = parseGephi;
  19767. /***/ },
  19768. /* 44 */
  19769. /***/ function(module, exports, __webpack_require__) {
  19770. // first check if moment.js is already loaded in the browser window, if so,
  19771. // use this instance. Else, load via commonjs.
  19772. module.exports = (typeof window !== 'undefined') && window['moment'] || __webpack_require__(58);
  19773. /***/ },
  19774. /* 45 */
  19775. /***/ function(module, exports, __webpack_require__) {
  19776. // Only load hammer.js when in a browser environment
  19777. // (loading hammer.js in a node.js environment gives errors)
  19778. if (typeof window !== 'undefined') {
  19779. module.exports = window['Hammer'] || __webpack_require__(59);
  19780. }
  19781. else {
  19782. module.exports = function () {
  19783. throw Error('hammer.js is only available in a browser, not in node.js.');
  19784. }
  19785. }
  19786. /***/ },
  19787. /* 46 */
  19788. /***/ function(module, exports, __webpack_require__) {
  19789. var Emitter = __webpack_require__(56);
  19790. var Hammer = __webpack_require__(45);
  19791. var util = __webpack_require__(1);
  19792. var DataSet = __webpack_require__(3);
  19793. var DataView = __webpack_require__(4);
  19794. var Range = __webpack_require__(17);
  19795. var ItemSet = __webpack_require__(32);
  19796. var TimeAxis = __webpack_require__(35);
  19797. var Activator = __webpack_require__(53);
  19798. var DateUtil = __webpack_require__(15);
  19799. var CustomTime = __webpack_require__(27);
  19800. /**
  19801. * Create a timeline visualization
  19802. * @param {HTMLElement} container
  19803. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  19804. * @param {Object} [options] See Core.setOptions for the available options.
  19805. * @constructor
  19806. */
  19807. function Core () {}
  19808. // turn Core into an event emitter
  19809. Emitter(Core.prototype);
  19810. /**
  19811. * Create the main DOM for the Core: a root panel containing left, right,
  19812. * top, bottom, content, and background panel.
  19813. * @param {Element} container The container element where the Core will
  19814. * be attached.
  19815. * @protected
  19816. */
  19817. Core.prototype._create = function (container) {
  19818. this.dom = {};
  19819. this.dom.root = document.createElement('div');
  19820. this.dom.background = document.createElement('div');
  19821. this.dom.backgroundVertical = document.createElement('div');
  19822. this.dom.backgroundHorizontal = document.createElement('div');
  19823. this.dom.centerContainer = document.createElement('div');
  19824. this.dom.leftContainer = document.createElement('div');
  19825. this.dom.rightContainer = document.createElement('div');
  19826. this.dom.center = document.createElement('div');
  19827. this.dom.left = document.createElement('div');
  19828. this.dom.right = document.createElement('div');
  19829. this.dom.top = document.createElement('div');
  19830. this.dom.bottom = document.createElement('div');
  19831. this.dom.shadowTop = document.createElement('div');
  19832. this.dom.shadowBottom = document.createElement('div');
  19833. this.dom.shadowTopLeft = document.createElement('div');
  19834. this.dom.shadowBottomLeft = document.createElement('div');
  19835. this.dom.shadowTopRight = document.createElement('div');
  19836. this.dom.shadowBottomRight = document.createElement('div');
  19837. this.dom.root.className = 'vis timeline root';
  19838. this.dom.background.className = 'vispanel background';
  19839. this.dom.backgroundVertical.className = 'vispanel background vertical';
  19840. this.dom.backgroundHorizontal.className = 'vispanel background horizontal';
  19841. this.dom.centerContainer.className = 'vispanel center jooo';
  19842. this.dom.leftContainer.className = 'vispanel left';
  19843. this.dom.rightContainer.className = 'vispanel right';
  19844. this.dom.top.className = 'vispanel top';
  19845. this.dom.bottom.className = 'vispanel bottom';
  19846. this.dom.left.className = 'content';
  19847. this.dom.center.className = 'content';
  19848. this.dom.right.className = 'content';
  19849. this.dom.shadowTop.className = 'shadow top';
  19850. this.dom.shadowBottom.className = 'shadow bottom';
  19851. this.dom.shadowTopLeft.className = 'shadow top';
  19852. this.dom.shadowBottomLeft.className = 'shadow bottom';
  19853. this.dom.shadowTopRight.className = 'shadow top';
  19854. this.dom.shadowBottomRight.className = 'shadow bottom';
  19855. this.dom.root.appendChild(this.dom.background);
  19856. this.dom.root.appendChild(this.dom.backgroundVertical);
  19857. this.dom.root.appendChild(this.dom.backgroundHorizontal);
  19858. this.dom.root.appendChild(this.dom.centerContainer);
  19859. this.dom.root.appendChild(this.dom.leftContainer);
  19860. this.dom.root.appendChild(this.dom.rightContainer);
  19861. this.dom.root.appendChild(this.dom.top);
  19862. this.dom.root.appendChild(this.dom.bottom);
  19863. this.dom.centerContainer.appendChild(this.dom.center);
  19864. this.dom.leftContainer.appendChild(this.dom.left);
  19865. this.dom.rightContainer.appendChild(this.dom.right);
  19866. this.dom.centerContainer.appendChild(this.dom.shadowTop);
  19867. this.dom.centerContainer.appendChild(this.dom.shadowBottom);
  19868. this.dom.leftContainer.appendChild(this.dom.shadowTopLeft);
  19869. this.dom.leftContainer.appendChild(this.dom.shadowBottomLeft);
  19870. this.dom.rightContainer.appendChild(this.dom.shadowTopRight);
  19871. this.dom.rightContainer.appendChild(this.dom.shadowBottomRight);
  19872. this.on('rangechange', this._redraw.bind(this));
  19873. this.on('touch', this._onTouch.bind(this));
  19874. this.on('pinch', this._onPinch.bind(this));
  19875. this.on('dragstart', this._onDragStart.bind(this));
  19876. this.on('drag', this._onDrag.bind(this));
  19877. var me = this;
  19878. this.on('change', function (properties) {
  19879. if (properties && properties.queue == true) {
  19880. // redraw once on next tick
  19881. if (!me._redrawTimer) {
  19882. me._redrawTimer = setTimeout(function () {
  19883. me._redrawTimer = null;
  19884. me._redraw();
  19885. }, 0)
  19886. }
  19887. }
  19888. else {
  19889. // redraw immediately
  19890. me._redraw();
  19891. }
  19892. });
  19893. // create event listeners for all interesting events, these events will be
  19894. // emitted via emitter
  19895. this.hammer = Hammer(this.dom.root, {
  19896. preventDefault: true
  19897. });
  19898. this.listeners = {};
  19899. var events = [
  19900. 'touch', 'pinch',
  19901. 'tap', 'doubletap', 'hold',
  19902. 'dragstart', 'drag', 'dragend',
  19903. 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
  19904. ];
  19905. events.forEach(function (event) {
  19906. var listener = function () {
  19907. var args = [event].concat(Array.prototype.slice.call(arguments, 0));
  19908. if (me.isActive()) {
  19909. me.emit.apply(me, args);
  19910. }
  19911. };
  19912. me.hammer.on(event, listener);
  19913. me.listeners[event] = listener;
  19914. });
  19915. // size properties of each of the panels
  19916. this.props = {
  19917. root: {},
  19918. background: {},
  19919. centerContainer: {},
  19920. leftContainer: {},
  19921. rightContainer: {},
  19922. center: {},
  19923. left: {},
  19924. right: {},
  19925. top: {},
  19926. bottom: {},
  19927. border: {},
  19928. scrollTop: 0,
  19929. scrollTopMin: 0
  19930. };
  19931. this.touch = {}; // store state information needed for touch events
  19932. this.redrawCount = 0;
  19933. // attach the root panel to the provided container
  19934. if (!container) throw new Error('No container provided');
  19935. container.appendChild(this.dom.root);
  19936. };
  19937. /**
  19938. * Set options. Options will be passed to all components loaded in the Timeline.
  19939. * @param {Object} [options]
  19940. * {String} orientation
  19941. * Vertical orientation for the Timeline,
  19942. * can be 'bottom' (default) or 'top'.
  19943. * {String | Number} width
  19944. * Width for the timeline, a number in pixels or
  19945. * a css string like '1000px' or '75%'. '100%' by default.
  19946. * {String | Number} height
  19947. * Fixed height for the Timeline, a number in pixels or
  19948. * a css string like '400px' or '75%'. If undefined,
  19949. * The Timeline will automatically size such that
  19950. * its contents fit.
  19951. * {String | Number} minHeight
  19952. * Minimum height for the Timeline, a number in pixels or
  19953. * a css string like '400px' or '75%'.
  19954. * {String | Number} maxHeight
  19955. * Maximum height for the Timeline, a number in pixels or
  19956. * a css string like '400px' or '75%'.
  19957. * {Number | Date | String} start
  19958. * Start date for the visible window
  19959. * {Number | Date | String} end
  19960. * End date for the visible window
  19961. */
  19962. Core.prototype.setOptions = function (options) {
  19963. if (options) {
  19964. // copy the known options
  19965. var fields = ['width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'start', 'end', 'orientation', 'clickToUse', 'dataAttributes', 'hiddenDates'];
  19966. util.selectiveExtend(fields, this.options, options);
  19967. if (this.options.orientation === 'both') {
  19968. if (!this.timeAxis2) {
  19969. var timeAxis2 = this.timeAxis2 = new TimeAxis(this.body);
  19970. timeAxis2.setOptions = function (options) {
  19971. var _options = options ? util.extend({}, options) : {};
  19972. _options.orientation = 'top'; // override the orientation option, always top
  19973. TimeAxis.prototype.setOptions.call(timeAxis2, _options);
  19974. };
  19975. this.components.push(timeAxis2);
  19976. }
  19977. }
  19978. else {
  19979. if (this.timeAxis2) {
  19980. var index = this.components.indexOf(this.timeAxis2);
  19981. if (index !== -1) {
  19982. this.components.splice(index, 1);
  19983. }
  19984. this.timeAxis2.destroy();
  19985. this.timeAxis2 = null;
  19986. }
  19987. }
  19988. if ('hiddenDates' in this.options) {
  19989. DateUtil.convertHiddenOptions(this.body, this.options.hiddenDates);
  19990. }
  19991. if ('clickToUse' in options) {
  19992. if (options.clickToUse) {
  19993. if (!this.activator) {
  19994. this.activator = new Activator(this.dom.root);
  19995. }
  19996. }
  19997. else {
  19998. if (this.activator) {
  19999. this.activator.destroy();
  20000. delete this.activator;
  20001. }
  20002. }
  20003. }
  20004. // enable/disable autoResize
  20005. this._initAutoResize();
  20006. }
  20007. // propagate options to all components
  20008. this.components.forEach(function (component) {
  20009. component.setOptions(options);
  20010. });
  20011. // redraw everything
  20012. this._redraw();
  20013. };
  20014. /**
  20015. * Returns true when the Timeline is active.
  20016. * @returns {boolean}
  20017. */
  20018. Core.prototype.isActive = function () {
  20019. return !this.activator || this.activator.active;
  20020. };
  20021. /**
  20022. * Destroy the Core, clean up all DOM elements and event listeners.
  20023. */
  20024. Core.prototype.destroy = function () {
  20025. // unbind datasets
  20026. this.clear();
  20027. // remove all event listeners
  20028. this.off();
  20029. // stop checking for changed size
  20030. this._stopAutoResize();
  20031. // remove from DOM
  20032. if (this.dom.root.parentNode) {
  20033. this.dom.root.parentNode.removeChild(this.dom.root);
  20034. }
  20035. this.dom = null;
  20036. // remove Activator
  20037. if (this.activator) {
  20038. this.activator.destroy();
  20039. delete this.activator;
  20040. }
  20041. // cleanup hammer touch events
  20042. for (var event in this.listeners) {
  20043. if (this.listeners.hasOwnProperty(event)) {
  20044. delete this.listeners[event];
  20045. }
  20046. }
  20047. this.listeners = null;
  20048. this.hammer = null;
  20049. // give all components the opportunity to cleanup
  20050. this.components.forEach(function (component) {
  20051. component.destroy();
  20052. });
  20053. this.body = null;
  20054. };
  20055. /**
  20056. * Set a custom time bar
  20057. * @param {Date} time
  20058. * @param {int} id
  20059. */
  20060. Core.prototype.setCustomTime = function (time, id) {
  20061. if (!this.customTime) {
  20062. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  20063. }
  20064. var barId = id || 0;
  20065. this.components.forEach(function (element, index, components) {
  20066. if (element instanceof CustomTime && element.options.id === barId) {
  20067. element.setCustomTime(time);
  20068. }
  20069. });
  20070. };
  20071. /**
  20072. * Retrieve the current custom time.
  20073. * @return {Date} customTime
  20074. * @param {int} id
  20075. */
  20076. Core.prototype.getCustomTime = function(id) {
  20077. if (!this.customTime) {
  20078. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  20079. }
  20080. var barId = id || 0,
  20081. customTime = this.customTime.getCustomTime();
  20082. this.components.forEach(function (element, index, components) {
  20083. if (element instanceof CustomTime && element.options.id === barId) {
  20084. customTime = element.getCustomTime();
  20085. }
  20086. });
  20087. return customTime;
  20088. };
  20089. /**
  20090. * Add custom vertical bar
  20091. * @param {Date | String | Number} time A Date, unix timestamp, or
  20092. * ISO date string. Time point where the new bar should be placed
  20093. * @param {Number | String} ID of the new bar
  20094. * @return {Number | String} ID of the new bar
  20095. */
  20096. Core.prototype.addCustomTime = function (time, id) {
  20097. if (!this.currentTime) {
  20098. throw new Error('Option showCurrentTime must be true');
  20099. }
  20100. if (time === undefined) {
  20101. throw new Error('Time parameter for the custom bar must be provided');
  20102. }
  20103. var ts = util.convert(time, 'Date').valueOf(),
  20104. numIds, customTime, customBarId;
  20105. // All bar IDs are kept in 1 array, mixed types
  20106. // Bar with ID 0 is the default bar.
  20107. if (!this.customBarIds || this.customBarIds.constructor !== Array) {
  20108. this.customBarIds = [0];
  20109. }
  20110. // If the ID is not provided, generate one, otherwise just use it
  20111. if (id === undefined) {
  20112. numIds = this.customBarIds.filter(function (element) {
  20113. return util.isNumber(element);
  20114. });
  20115. customBarId = numIds.length > 0 ? Math.max.apply(null, numIds) + 1 : 1;
  20116. } else {
  20117. // Check for duplicates
  20118. this.customBarIds.forEach(function (element) {
  20119. if (element === id) {
  20120. throw new Error('Custom time ID already exists');
  20121. }
  20122. });
  20123. customBarId = id;
  20124. }
  20125. this.customBarIds.push(customBarId);
  20126. customTime = new CustomTime(this.body, {
  20127. showCustomTime : true,
  20128. time : ts,
  20129. id : customBarId
  20130. });
  20131. this.components.push(customTime);
  20132. this.redraw();
  20133. return customBarId;
  20134. };
  20135. /**
  20136. * Remove previously added custom bar
  20137. * @param {int} id ID of the custom bar to be removed
  20138. * @return {boolean} True if the bar exists and is removed, false otherwise
  20139. */
  20140. Core.prototype.removeCustomTime = function (id) {
  20141. var me = this;
  20142. this.components.forEach(function (bar, index, components) {
  20143. if (bar instanceof CustomTime && bar.options.id === id) {
  20144. // Only the lines added by the user will be removed
  20145. if (bar.options.id !== 0) {
  20146. me.customBarIds.splice(me.customBarIds.indexOf(id), 1);
  20147. components.splice(index, 1);
  20148. bar.destroy();
  20149. }
  20150. }
  20151. });
  20152. };
  20153. /**
  20154. * Get the id's of the currently visible items.
  20155. * @returns {Array} The ids of the visible items
  20156. */
  20157. Core.prototype.getVisibleItems = function() {
  20158. return this.itemSet && this.itemSet.getVisibleItems() || [];
  20159. };
  20160. /**
  20161. * Clear the Core. By Default, items, groups and options are cleared.
  20162. * Example usage:
  20163. *
  20164. * timeline.clear(); // clear items, groups, and options
  20165. * timeline.clear({options: true}); // clear options only
  20166. *
  20167. * @param {Object} [what] Optionally specify what to clear. By default:
  20168. * {items: true, groups: true, options: true}
  20169. */
  20170. Core.prototype.clear = function(what) {
  20171. // clear items
  20172. if (!what || what.items) {
  20173. this.setItems(null);
  20174. }
  20175. // clear groups
  20176. if (!what || what.groups) {
  20177. this.setGroups(null);
  20178. }
  20179. // clear options of timeline and of each of the components
  20180. if (!what || what.options) {
  20181. this.components.forEach(function (component) {
  20182. component.setOptions(component.defaultOptions);
  20183. });
  20184. this.setOptions(this.defaultOptions); // this will also do a redraw
  20185. }
  20186. };
  20187. /**
  20188. * Set Core window such that it fits all items
  20189. * @param {Object} [options] Available options:
  20190. * `animate: boolean | number`
  20191. * If true (default), the range is animated
  20192. * smoothly to the new window.
  20193. * If a number, the number is taken as duration
  20194. * for the animation. Default duration is 500 ms.
  20195. */
  20196. Core.prototype.fit = function(options) {
  20197. var range = this._getDataRange();
  20198. // skip range set if there is no start and end date
  20199. if (range.start === null && range.end === null) {
  20200. return;
  20201. }
  20202. var animate = (options && options.animate !== undefined) ? options.animate : true;
  20203. this.range.setRange(range.start, range.end, animate);
  20204. };
  20205. /**
  20206. * Calculate the data range of the items and applies a 5% window around it.
  20207. * @returns {{start: Date | null, end: Date | null}}
  20208. * @protected
  20209. */
  20210. Core.prototype._getDataRange = function() {
  20211. // apply the data range as range
  20212. var dataRange = this.getItemRange();
  20213. // add 5% space on both sides
  20214. var start = dataRange.min;
  20215. var end = dataRange.max;
  20216. if (start != null && end != null) {
  20217. var interval = (end.valueOf() - start.valueOf());
  20218. if (interval <= 0) {
  20219. // prevent an empty interval
  20220. interval = 24 * 60 * 60 * 1000; // 1 day
  20221. }
  20222. start = new Date(start.valueOf() - interval * 0.05);
  20223. end = new Date(end.valueOf() + interval * 0.05);
  20224. }
  20225. return {
  20226. start: start,
  20227. end: end
  20228. }
  20229. };
  20230. /**
  20231. * Set the visible window. Both parameters are optional, you can change only
  20232. * start or only end. Syntax:
  20233. *
  20234. * TimeLine.setWindow(start, end)
  20235. * TimeLine.setWindow(start, end, options)
  20236. * TimeLine.setWindow(range)
  20237. *
  20238. * Where start and end can be a Date, number, or string, and range is an
  20239. * object with properties start and end.
  20240. *
  20241. * @param {Date | Number | String | Object} [start] Start date of visible window
  20242. * @param {Date | Number | String} [end] End date of visible window
  20243. * @param {Object} [options] Available options:
  20244. * `animate: boolean | number`
  20245. * If true (default), the range is animated
  20246. * smoothly to the new window.
  20247. * If a number, the number is taken as duration
  20248. * for the animation. Default duration is 500 ms.
  20249. */
  20250. Core.prototype.setWindow = function(start, end, options) {
  20251. var animate;
  20252. if (arguments.length == 1) {
  20253. var range = arguments[0];
  20254. animate = (range.animate !== undefined) ? range.animate : true;
  20255. this.range.setRange(range.start, range.end, animate);
  20256. }
  20257. else {
  20258. animate = (options && options.animate !== undefined) ? options.animate : true;
  20259. this.range.setRange(start, end, animate);
  20260. }
  20261. };
  20262. /**
  20263. * Move the window such that given time is centered on screen.
  20264. * @param {Date | Number | String} time
  20265. * @param {Object} [options] Available options:
  20266. * `animate: boolean | number`
  20267. * If true (default), the range is animated
  20268. * smoothly to the new window.
  20269. * If a number, the number is taken as duration
  20270. * for the animation. Default duration is 500 ms.
  20271. */
  20272. Core.prototype.moveTo = function(time, options) {
  20273. var interval = this.range.end - this.range.start;
  20274. var t = util.convert(time, 'Date').valueOf();
  20275. var start = t - interval / 2;
  20276. var end = t + interval / 2;
  20277. var animate = (options && options.animate !== undefined) ? options.animate : true;
  20278. this.range.setRange(start, end, animate);
  20279. };
  20280. /**
  20281. * Get the visible window
  20282. * @return {{start: Date, end: Date}} Visible range
  20283. */
  20284. Core.prototype.getWindow = function() {
  20285. var range = this.range.getRange();
  20286. return {
  20287. start: new Date(range.start),
  20288. end: new Date(range.end)
  20289. };
  20290. };
  20291. /**
  20292. * Force a redraw. Can be overridden by implementations of Core
  20293. */
  20294. Core.prototype.redraw = function() {
  20295. this._redraw();
  20296. };
  20297. /**
  20298. * Redraw for internal use. Redraws all components. See also the public
  20299. * method redraw.
  20300. * @protected
  20301. */
  20302. Core.prototype._redraw = function() {
  20303. var resized = false;
  20304. var options = this.options;
  20305. var props = this.props;
  20306. var dom = this.dom;
  20307. if (!dom) return; // when destroyed
  20308. DateUtil.updateHiddenDates(this.body, this.options.hiddenDates);
  20309. // update class names
  20310. if (options.orientation == 'top') {
  20311. util.addClassName(dom.root, 'top');
  20312. util.removeClassName(dom.root, 'bottom');
  20313. }
  20314. else {
  20315. util.removeClassName(dom.root, 'top');
  20316. util.addClassName(dom.root, 'bottom');
  20317. }
  20318. // update root width and height options
  20319. dom.root.style.maxHeight = util.option.asSize(options.maxHeight, '');
  20320. dom.root.style.minHeight = util.option.asSize(options.minHeight, '');
  20321. dom.root.style.width = util.option.asSize(options.width, '');
  20322. // calculate border widths
  20323. props.border.left = (dom.centerContainer.offsetWidth - dom.centerContainer.clientWidth) / 2;
  20324. props.border.right = props.border.left;
  20325. props.border.top = (dom.centerContainer.offsetHeight - dom.centerContainer.clientHeight) / 2;
  20326. props.border.bottom = props.border.top;
  20327. var borderRootHeight= dom.root.offsetHeight - dom.root.clientHeight;
  20328. var borderRootWidth = dom.root.offsetWidth - dom.root.clientWidth;
  20329. // workaround for a bug in IE: the clientWidth of an element with
  20330. // a height:0px and overflow:hidden is not calculated and always has value 0
  20331. if (dom.centerContainer.clientHeight === 0) {
  20332. props.border.left = props.border.top;
  20333. props.border.right = props.border.left;
  20334. }
  20335. if (dom.root.clientHeight === 0) {
  20336. borderRootWidth = borderRootHeight;
  20337. }
  20338. // calculate the heights. If any of the side panels is empty, we set the height to
  20339. // minus the border width, such that the border will be invisible
  20340. props.center.height = dom.center.offsetHeight;
  20341. props.left.height = dom.left.offsetHeight;
  20342. props.right.height = dom.right.offsetHeight;
  20343. props.top.height = dom.top.clientHeight || -props.border.top;
  20344. props.bottom.height = dom.bottom.clientHeight || -props.border.bottom;
  20345. // TODO: compensate borders when any of the panels is empty.
  20346. // apply auto height
  20347. // TODO: only calculate autoHeight when needed (else we cause an extra reflow/repaint of the DOM)
  20348. var contentHeight = Math.max(props.left.height, props.center.height, props.right.height);
  20349. var autoHeight = props.top.height + contentHeight + props.bottom.height +
  20350. borderRootHeight + props.border.top + props.border.bottom;
  20351. dom.root.style.height = util.option.asSize(options.height, autoHeight + 'px');
  20352. // calculate heights of the content panels
  20353. props.root.height = dom.root.offsetHeight;
  20354. props.background.height = props.root.height - borderRootHeight;
  20355. var containerHeight = props.root.height - props.top.height - props.bottom.height -
  20356. borderRootHeight;
  20357. props.centerContainer.height = containerHeight;
  20358. props.leftContainer.height = containerHeight;
  20359. props.rightContainer.height = props.leftContainer.height;
  20360. // calculate the widths of the panels
  20361. props.root.width = dom.root.offsetWidth;
  20362. props.background.width = props.root.width - borderRootWidth;
  20363. props.left.width = dom.leftContainer.clientWidth || -props.border.left;
  20364. props.leftContainer.width = props.left.width;
  20365. props.right.width = dom.rightContainer.clientWidth || -props.border.right;
  20366. props.rightContainer.width = props.right.width;
  20367. var centerWidth = props.root.width - props.left.width - props.right.width - borderRootWidth;
  20368. props.center.width = centerWidth;
  20369. props.centerContainer.width = centerWidth;
  20370. props.top.width = centerWidth;
  20371. props.bottom.width = centerWidth;
  20372. // resize the panels
  20373. dom.background.style.height = props.background.height + 'px';
  20374. dom.backgroundVertical.style.height = props.background.height + 'px';
  20375. dom.backgroundHorizontal.style.height = props.centerContainer.height + 'px';
  20376. dom.centerContainer.style.height = props.centerContainer.height + 'px';
  20377. dom.leftContainer.style.height = props.leftContainer.height + 'px';
  20378. dom.rightContainer.style.height = props.rightContainer.height + 'px';
  20379. dom.background.style.width = props.background.width + 'px';
  20380. dom.backgroundVertical.style.width = props.centerContainer.width + 'px';
  20381. dom.backgroundHorizontal.style.width = props.background.width + 'px';
  20382. dom.centerContainer.style.width = props.center.width + 'px';
  20383. dom.top.style.width = props.top.width + 'px';
  20384. dom.bottom.style.width = props.bottom.width + 'px';
  20385. // reposition the panels
  20386. dom.background.style.left = '0';
  20387. dom.background.style.top = '0';
  20388. dom.backgroundVertical.style.left = (props.left.width + props.border.left) + 'px';
  20389. dom.backgroundVertical.style.top = '0';
  20390. dom.backgroundHorizontal.style.left = '0';
  20391. dom.backgroundHorizontal.style.top = props.top.height + 'px';
  20392. dom.centerContainer.style.left = props.left.width + 'px';
  20393. dom.centerContainer.style.top = props.top.height + 'px';
  20394. dom.leftContainer.style.left = '0';
  20395. dom.leftContainer.style.top = props.top.height + 'px';
  20396. dom.rightContainer.style.left = (props.left.width + props.center.width) + 'px';
  20397. dom.rightContainer.style.top = props.top.height + 'px';
  20398. dom.top.style.left = props.left.width + 'px';
  20399. dom.top.style.top = '0';
  20400. dom.bottom.style.left = props.left.width + 'px';
  20401. dom.bottom.style.top = (props.top.height + props.centerContainer.height) + 'px';
  20402. // update the scrollTop, feasible range for the offset can be changed
  20403. // when the height of the Core or of the contents of the center changed
  20404. this._updateScrollTop();
  20405. // reposition the scrollable contents
  20406. var offset = this.props.scrollTop;
  20407. if (options.orientation == 'bottom') {
  20408. offset += Math.max(this.props.centerContainer.height - this.props.center.height -
  20409. this.props.border.top - this.props.border.bottom, 0);
  20410. }
  20411. dom.center.style.left = '0';
  20412. dom.center.style.top = offset + 'px';
  20413. dom.left.style.left = '0';
  20414. dom.left.style.top = offset + 'px';
  20415. dom.right.style.left = '0';
  20416. dom.right.style.top = offset + 'px';
  20417. // show shadows when vertical scrolling is available
  20418. var visibilityTop = this.props.scrollTop == 0 ? 'hidden' : '';
  20419. var visibilityBottom = this.props.scrollTop == this.props.scrollTopMin ? 'hidden' : '';
  20420. dom.shadowTop.style.visibility = visibilityTop;
  20421. dom.shadowBottom.style.visibility = visibilityBottom;
  20422. dom.shadowTopLeft.style.visibility = visibilityTop;
  20423. dom.shadowBottomLeft.style.visibility = visibilityBottom;
  20424. dom.shadowTopRight.style.visibility = visibilityTop;
  20425. dom.shadowBottomRight.style.visibility = visibilityBottom;
  20426. // redraw all components
  20427. this.components.forEach(function (component) {
  20428. resized = component.redraw() || resized;
  20429. });
  20430. if (resized) {
  20431. // keep repainting until all sizes are settled
  20432. var MAX_REDRAWS = 3; // maximum number of consecutive redraws
  20433. if (this.redrawCount < MAX_REDRAWS) {
  20434. this.redrawCount++;
  20435. this._redraw();
  20436. }
  20437. else {
  20438. console.log('WARNING: infinite loop in redraw?');
  20439. }
  20440. this.redrawCount = 0;
  20441. }
  20442. this.emit("finishedRedraw");
  20443. };
  20444. // TODO: deprecated since version 1.1.0, remove some day
  20445. Core.prototype.repaint = function () {
  20446. throw new Error('Function repaint is deprecated. Use redraw instead.');
  20447. };
  20448. /**
  20449. * Set a current time. This can be used for example to ensure that a client's
  20450. * time is synchronized with a shared server time.
  20451. * Only applicable when option `showCurrentTime` is true.
  20452. * @param {Date | String | Number} time A Date, unix timestamp, or
  20453. * ISO date string.
  20454. */
  20455. Core.prototype.setCurrentTime = function(time) {
  20456. if (!this.currentTime) {
  20457. throw new Error('Option showCurrentTime must be true');
  20458. }
  20459. this.currentTime.setCurrentTime(time);
  20460. };
  20461. /**
  20462. * Get the current time.
  20463. * Only applicable when option `showCurrentTime` is true.
  20464. * @return {Date} Returns the current time.
  20465. */
  20466. Core.prototype.getCurrentTime = function() {
  20467. if (!this.currentTime) {
  20468. throw new Error('Option showCurrentTime must be true');
  20469. }
  20470. return this.currentTime.getCurrentTime();
  20471. };
  20472. /**
  20473. * Convert a position on screen (pixels) to a datetime
  20474. * @param {int} x Position on the screen in pixels
  20475. * @return {Date} time The datetime the corresponds with given position x
  20476. * @protected
  20477. */
  20478. // TODO: move this function to Range
  20479. Core.prototype._toTime = function(x) {
  20480. return DateUtil.toTime(this, x, this.props.center.width);
  20481. };
  20482. /**
  20483. * Convert a position on the global screen (pixels) to a datetime
  20484. * @param {int} x Position on the screen in pixels
  20485. * @return {Date} time The datetime the corresponds with given position x
  20486. * @protected
  20487. */
  20488. // TODO: move this function to Range
  20489. Core.prototype._toGlobalTime = function(x) {
  20490. return DateUtil.toTime(this, x, this.props.root.width);
  20491. //var conversion = this.range.conversion(this.props.root.width);
  20492. //return new Date(x / conversion.scale + conversion.offset);
  20493. };
  20494. /**
  20495. * Convert a datetime (Date object) into a position on the screen
  20496. * @param {Date} time A date
  20497. * @return {int} x The position on the screen in pixels which corresponds
  20498. * with the given date.
  20499. * @protected
  20500. */
  20501. // TODO: move this function to Range
  20502. Core.prototype._toScreen = function(time) {
  20503. return DateUtil.toScreen(this, time, this.props.center.width);
  20504. };
  20505. /**
  20506. * Convert a datetime (Date object) into a position on the root
  20507. * This is used to get the pixel density estimate for the screen, not the center panel
  20508. * @param {Date} time A date
  20509. * @return {int} x The position on root in pixels which corresponds
  20510. * with the given date.
  20511. * @protected
  20512. */
  20513. // TODO: move this function to Range
  20514. Core.prototype._toGlobalScreen = function(time) {
  20515. return DateUtil.toScreen(this, time, this.props.root.width);
  20516. //var conversion = this.range.conversion(this.props.root.width);
  20517. //return (time.valueOf() - conversion.offset) * conversion.scale;
  20518. };
  20519. /**
  20520. * Initialize watching when option autoResize is true
  20521. * @private
  20522. */
  20523. Core.prototype._initAutoResize = function () {
  20524. if (this.options.autoResize == true) {
  20525. this._startAutoResize();
  20526. }
  20527. else {
  20528. this._stopAutoResize();
  20529. }
  20530. };
  20531. /**
  20532. * Watch for changes in the size of the container. On resize, the Panel will
  20533. * automatically redraw itself.
  20534. * @private
  20535. */
  20536. Core.prototype._startAutoResize = function () {
  20537. var me = this;
  20538. this._stopAutoResize();
  20539. this._onResize = function() {
  20540. if (me.options.autoResize != true) {
  20541. // stop watching when the option autoResize is changed to false
  20542. me._stopAutoResize();
  20543. return;
  20544. }
  20545. if (me.dom.root) {
  20546. // check whether the frame is resized
  20547. // Note: we compare offsetWidth here, not clientWidth. For some reason,
  20548. // IE does not restore the clientWidth from 0 to the actual width after
  20549. // changing the timeline's container display style from none to visible
  20550. if ((me.dom.root.offsetWidth != me.props.lastWidth) ||
  20551. (me.dom.root.offsetHeight != me.props.lastHeight)) {
  20552. me.props.lastWidth = me.dom.root.offsetWidth;
  20553. me.props.lastHeight = me.dom.root.offsetHeight;
  20554. me.emit('change');
  20555. }
  20556. }
  20557. };
  20558. // add event listener to window resize
  20559. util.addEventListener(window, 'resize', this._onResize);
  20560. this.watchTimer = setInterval(this._onResize, 1000);
  20561. };
  20562. /**
  20563. * Stop watching for a resize of the frame.
  20564. * @private
  20565. */
  20566. Core.prototype._stopAutoResize = function () {
  20567. if (this.watchTimer) {
  20568. clearInterval(this.watchTimer);
  20569. this.watchTimer = undefined;
  20570. }
  20571. // remove event listener on window.resize
  20572. util.removeEventListener(window, 'resize', this._onResize);
  20573. this._onResize = null;
  20574. };
  20575. /**
  20576. * Start moving the timeline vertically
  20577. * @param {Event} event
  20578. * @private
  20579. */
  20580. Core.prototype._onTouch = function (event) {
  20581. this.touch.allowDragging = true;
  20582. };
  20583. /**
  20584. * Start moving the timeline vertically
  20585. * @param {Event} event
  20586. * @private
  20587. */
  20588. Core.prototype._onPinch = function (event) {
  20589. this.touch.allowDragging = false;
  20590. };
  20591. /**
  20592. * Start moving the timeline vertically
  20593. * @param {Event} event
  20594. * @private
  20595. */
  20596. Core.prototype._onDragStart = function (event) {
  20597. this.touch.initialScrollTop = this.props.scrollTop;
  20598. };
  20599. /**
  20600. * Move the timeline vertically
  20601. * @param {Event} event
  20602. * @private
  20603. */
  20604. Core.prototype._onDrag = function (event) {
  20605. // refuse to drag when we where pinching to prevent the timeline make a jump
  20606. // when releasing the fingers in opposite order from the touch screen
  20607. if (!this.touch.allowDragging) return;
  20608. var delta = event.gesture.deltaY;
  20609. var oldScrollTop = this._getScrollTop();
  20610. var newScrollTop = this._setScrollTop(this.touch.initialScrollTop + delta);
  20611. if (newScrollTop != oldScrollTop) {
  20612. this._redraw(); // TODO: this causes two redraws when dragging, the other is triggered by rangechange already
  20613. this.emit("verticalDrag");
  20614. }
  20615. };
  20616. /**
  20617. * Apply a scrollTop
  20618. * @param {Number} scrollTop
  20619. * @returns {Number} scrollTop Returns the applied scrollTop
  20620. * @private
  20621. */
  20622. Core.prototype._setScrollTop = function (scrollTop) {
  20623. this.props.scrollTop = scrollTop;
  20624. this._updateScrollTop();
  20625. return this.props.scrollTop;
  20626. };
  20627. /**
  20628. * Update the current scrollTop when the height of the containers has been changed
  20629. * @returns {Number} scrollTop Returns the applied scrollTop
  20630. * @private
  20631. */
  20632. Core.prototype._updateScrollTop = function () {
  20633. // recalculate the scrollTopMin
  20634. var scrollTopMin = Math.min(this.props.centerContainer.height - this.props.center.height, 0); // is negative or zero
  20635. if (scrollTopMin != this.props.scrollTopMin) {
  20636. // in case of bottom orientation, change the scrollTop such that the contents
  20637. // do not move relative to the time axis at the bottom
  20638. if (this.options.orientation == 'bottom') {
  20639. this.props.scrollTop += (scrollTopMin - this.props.scrollTopMin);
  20640. }
  20641. this.props.scrollTopMin = scrollTopMin;
  20642. }
  20643. // limit the scrollTop to the feasible scroll range
  20644. if (this.props.scrollTop > 0) this.props.scrollTop = 0;
  20645. if (this.props.scrollTop < scrollTopMin) this.props.scrollTop = scrollTopMin;
  20646. return this.props.scrollTop;
  20647. };
  20648. /**
  20649. * Get the current scrollTop
  20650. * @returns {number} scrollTop
  20651. * @private
  20652. */
  20653. Core.prototype._getScrollTop = function () {
  20654. return this.props.scrollTop;
  20655. };
  20656. module.exports = Core;
  20657. /***/ },
  20658. /* 47 */
  20659. /***/ function(module, exports, __webpack_require__) {
  20660. var Hammer = __webpack_require__(45);
  20661. /**
  20662. * Fake a hammer.js gesture. Event can be a ScrollEvent or MouseMoveEvent
  20663. * @param {Element} element
  20664. * @param {Event} event
  20665. */
  20666. exports.fakeGesture = function(element, event) {
  20667. var eventType = null;
  20668. // for hammer.js 1.0.5
  20669. // var gesture = Hammer.event.collectEventData(this, eventType, event);
  20670. // for hammer.js 1.0.6+
  20671. var touches = Hammer.event.getTouchList(event, eventType);
  20672. var gesture = Hammer.event.collectEventData(this, eventType, touches, event);
  20673. // on IE in standards mode, no touches are recognized by hammer.js,
  20674. // resulting in NaN values for center.pageX and center.pageY
  20675. if (isNaN(gesture.center.pageX)) {
  20676. gesture.center.pageX = event.pageX;
  20677. }
  20678. if (isNaN(gesture.center.pageY)) {
  20679. gesture.center.pageY = event.pageY;
  20680. }
  20681. return gesture;
  20682. };
  20683. /***/ },
  20684. /* 48 */
  20685. /***/ function(module, exports, __webpack_require__) {
  20686. // English
  20687. exports['en'] = {
  20688. current: 'current',
  20689. time: 'time'
  20690. };
  20691. exports['en_EN'] = exports['en'];
  20692. exports['en_US'] = exports['en'];
  20693. // Dutch
  20694. exports['nl'] = {
  20695. custom: 'aangepaste',
  20696. time: 'tijd'
  20697. };
  20698. exports['nl_NL'] = exports['nl'];
  20699. exports['nl_BE'] = exports['nl'];
  20700. /***/ },
  20701. /* 49 */
  20702. /***/ function(module, exports, __webpack_require__) {
  20703. /**
  20704. * Created by Alex on 11/11/2014.
  20705. */
  20706. var DOMutil = __webpack_require__(2);
  20707. var Points = __webpack_require__(51);
  20708. function Line(groupId, options) {
  20709. this.groupId = groupId;
  20710. this.options = options;
  20711. }
  20712. Line.prototype.getYRange = function(groupData) {
  20713. var yMin = groupData[0].y;
  20714. var yMax = groupData[0].y;
  20715. for (var j = 0; j < groupData.length; j++) {
  20716. yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
  20717. yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
  20718. }
  20719. return {min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation};
  20720. };
  20721. /**
  20722. * draw a line graph
  20723. *
  20724. * @param dataset
  20725. * @param group
  20726. */
  20727. Line.prototype.draw = function (dataset, group, framework) {
  20728. if (dataset != null) {
  20729. if (dataset.length > 0) {
  20730. var path, d;
  20731. var svgHeight = Number(framework.svg.style.height.replace('px',''));
  20732. path = DOMutil.getSVGElement('path', framework.svgElements, framework.svg);
  20733. path.setAttributeNS(null, "class", group.className);
  20734. if(group.style !== undefined) {
  20735. path.setAttributeNS(null, "style", group.style);
  20736. }
  20737. // construct path from dataset
  20738. if (group.options.catmullRom.enabled == true) {
  20739. d = Line._catmullRom(dataset, group);
  20740. }
  20741. else {
  20742. d = Line._linear(dataset);
  20743. }
  20744. // append with points for fill and finalize the path
  20745. if (group.options.shaded.enabled == true) {
  20746. var fillPath = DOMutil.getSVGElement('path', framework.svgElements, framework.svg);
  20747. var dFill;
  20748. if (group.options.shaded.orientation == 'top') {
  20749. dFill = 'M' + dataset[0].x + ',' + 0 + ' ' + d + 'L' + dataset[dataset.length - 1].x + ',' + 0;
  20750. }
  20751. else {
  20752. dFill = 'M' + dataset[0].x + ',' + svgHeight + ' ' + d + 'L' + dataset[dataset.length - 1].x + ',' + svgHeight;
  20753. }
  20754. fillPath.setAttributeNS(null, "class", group.className + " fill");
  20755. if(group.options.shaded.style !== undefined) {
  20756. fillPath.setAttributeNS(null, "style", group.options.shaded.style);
  20757. }
  20758. fillPath.setAttributeNS(null, "d", dFill);
  20759. }
  20760. // copy properties to path for drawing.
  20761. path.setAttributeNS(null, 'd', 'M' + d);
  20762. // draw points
  20763. if (group.options.drawPoints.enabled == true) {
  20764. Points.draw(dataset, group, framework);
  20765. }
  20766. }
  20767. }
  20768. };
  20769. /**
  20770. * This uses an uniform parametrization of the CatmullRom algorithm:
  20771. * 'On the Parameterization of Catmull-Rom Curves' by Cem Yuksel et al.
  20772. * @param data
  20773. * @returns {string}
  20774. * @private
  20775. */
  20776. Line._catmullRomUniform = function(data) {
  20777. // catmull rom
  20778. var p0, p1, p2, p3, bp1, bp2;
  20779. var d = Math.round(data[0].x) + ',' + Math.round(data[0].y) + ' ';
  20780. var normalization = 1/6;
  20781. var length = data.length;
  20782. for (var i = 0; i < length - 1; i++) {
  20783. p0 = (i == 0) ? data[0] : data[i-1];
  20784. p1 = data[i];
  20785. p2 = data[i+1];
  20786. p3 = (i + 2 < length) ? data[i+2] : p2;
  20787. // Catmull-Rom to Cubic Bezier conversion matrix
  20788. // 0 1 0 0
  20789. // -1/6 1 1/6 0
  20790. // 0 1/6 1 -1/6
  20791. // 0 0 1 0
  20792. // bp0 = { x: p1.x, y: p1.y };
  20793. bp1 = { x: ((-p0.x + 6*p1.x + p2.x) *normalization), y: ((-p0.y + 6*p1.y + p2.y) *normalization)};
  20794. bp2 = { x: (( p1.x + 6*p2.x - p3.x) *normalization), y: (( p1.y + 6*p2.y - p3.y) *normalization)};
  20795. // bp0 = { x: p2.x, y: p2.y };
  20796. d += 'C' +
  20797. bp1.x + ',' +
  20798. bp1.y + ' ' +
  20799. bp2.x + ',' +
  20800. bp2.y + ' ' +
  20801. p2.x + ',' +
  20802. p2.y + ' ';
  20803. }
  20804. return d;
  20805. };
  20806. /**
  20807. * This uses either the chordal or centripetal parameterization of the catmull-rom algorithm.
  20808. * By default, the centripetal parameterization is used because this gives the nicest results.
  20809. * These parameterizations are relatively heavy because the distance between 4 points have to be calculated.
  20810. *
  20811. * One optimization can be used to reuse distances since this is a sliding window approach.
  20812. * @param data
  20813. * @param group
  20814. * @returns {string}
  20815. * @private
  20816. */
  20817. Line._catmullRom = function(data, group) {
  20818. var alpha = group.options.catmullRom.alpha;
  20819. if (alpha == 0 || alpha === undefined) {
  20820. return this._catmullRomUniform(data);
  20821. }
  20822. else {
  20823. var p0, p1, p2, p3, bp1, bp2, d1,d2,d3, A, B, N, M;
  20824. var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA;
  20825. var d = Math.round(data[0].x) + ',' + Math.round(data[0].y) + ' ';
  20826. var length = data.length;
  20827. for (var i = 0; i < length - 1; i++) {
  20828. p0 = (i == 0) ? data[0] : data[i-1];
  20829. p1 = data[i];
  20830. p2 = data[i+1];
  20831. p3 = (i + 2 < length) ? data[i+2] : p2;
  20832. d1 = Math.sqrt(Math.pow(p0.x - p1.x,2) + Math.pow(p0.y - p1.y,2));
  20833. d2 = Math.sqrt(Math.pow(p1.x - p2.x,2) + Math.pow(p1.y - p2.y,2));
  20834. d3 = Math.sqrt(Math.pow(p2.x - p3.x,2) + Math.pow(p2.y - p3.y,2));
  20835. // Catmull-Rom to Cubic Bezier conversion matrix
  20836. // A = 2d1^2a + 3d1^a * d2^a + d3^2a
  20837. // B = 2d3^2a + 3d3^a * d2^a + d2^2a
  20838. // [ 0 1 0 0 ]
  20839. // [ -d2^2a /N A/N d1^2a /N 0 ]
  20840. // [ 0 d3^2a /M B/M -d2^2a /M ]
  20841. // [ 0 0 1 0 ]
  20842. d3powA = Math.pow(d3, alpha);
  20843. d3pow2A = Math.pow(d3,2*alpha);
  20844. d2powA = Math.pow(d2, alpha);
  20845. d2pow2A = Math.pow(d2,2*alpha);
  20846. d1powA = Math.pow(d1, alpha);
  20847. d1pow2A = Math.pow(d1,2*alpha);
  20848. A = 2*d1pow2A + 3*d1powA * d2powA + d2pow2A;
  20849. B = 2*d3pow2A + 3*d3powA * d2powA + d2pow2A;
  20850. N = 3*d1powA * (d1powA + d2powA);
  20851. if (N > 0) {N = 1 / N;}
  20852. M = 3*d3powA * (d3powA + d2powA);
  20853. if (M > 0) {M = 1 / M;}
  20854. bp1 = { x: ((-d2pow2A * p0.x + A*p1.x + d1pow2A * p2.x) * N),
  20855. y: ((-d2pow2A * p0.y + A*p1.y + d1pow2A * p2.y) * N)};
  20856. bp2 = { x: (( d3pow2A * p1.x + B*p2.x - d2pow2A * p3.x) * M),
  20857. y: (( d3pow2A * p1.y + B*p2.y - d2pow2A * p3.y) * M)};
  20858. if (bp1.x == 0 && bp1.y == 0) {bp1 = p1;}
  20859. if (bp2.x == 0 && bp2.y == 0) {bp2 = p2;}
  20860. d += 'C' +
  20861. bp1.x + ',' +
  20862. bp1.y + ' ' +
  20863. bp2.x + ',' +
  20864. bp2.y + ' ' +
  20865. p2.x + ',' +
  20866. p2.y + ' ';
  20867. }
  20868. return d;
  20869. }
  20870. };
  20871. /**
  20872. * this generates the SVG path for a linear drawing between datapoints.
  20873. * @param data
  20874. * @returns {string}
  20875. * @private
  20876. */
  20877. Line._linear = function(data) {
  20878. // linear
  20879. var d = '';
  20880. for (var i = 0; i < data.length; i++) {
  20881. if (i == 0) {
  20882. d += data[i].x + ',' + data[i].y;
  20883. }
  20884. else {
  20885. d += ' ' + data[i].x + ',' + data[i].y;
  20886. }
  20887. }
  20888. return d;
  20889. };
  20890. module.exports = Line;
  20891. /***/ },
  20892. /* 50 */
  20893. /***/ function(module, exports, __webpack_require__) {
  20894. /**
  20895. * Created by Alex on 11/11/2014.
  20896. */
  20897. var DOMutil = __webpack_require__(2);
  20898. var Points = __webpack_require__(51);
  20899. function Bargraph(groupId, options) {
  20900. this.groupId = groupId;
  20901. this.options = options;
  20902. }
  20903. Bargraph.prototype.getYRange = function(groupData) {
  20904. if (this.options.barChart.handleOverlap != 'stack') {
  20905. var yMin = groupData[0].y;
  20906. var yMax = groupData[0].y;
  20907. for (var j = 0; j < groupData.length; j++) {
  20908. yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
  20909. yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
  20910. }
  20911. return {min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation};
  20912. }
  20913. else {
  20914. var barCombinedData = [];
  20915. for (var j = 0; j < groupData.length; j++) {
  20916. barCombinedData.push({
  20917. x: groupData[j].x,
  20918. y: groupData[j].y,
  20919. groupId: this.groupId
  20920. });
  20921. }
  20922. return barCombinedData;
  20923. }
  20924. };
  20925. /**
  20926. * draw a bar graph
  20927. *
  20928. * @param groupIds
  20929. * @param processedGroupData
  20930. */
  20931. Bargraph.draw = function (groupIds, processedGroupData, framework) {
  20932. var combinedData = [];
  20933. var intersections = {};
  20934. var coreDistance;
  20935. var key, drawData;
  20936. var group;
  20937. var i,j;
  20938. var barPoints = 0;
  20939. // combine all barchart data
  20940. for (i = 0; i < groupIds.length; i++) {
  20941. group = framework.groups[groupIds[i]];
  20942. if (group.options.style == 'bar') {
  20943. if (group.visible == true && (framework.options.groups.visibility[groupIds[i]] === undefined || framework.options.groups.visibility[groupIds[i]] == true)) {
  20944. for (j = 0; j < processedGroupData[groupIds[i]].length; j++) {
  20945. combinedData.push({
  20946. x: processedGroupData[groupIds[i]][j].x,
  20947. y: processedGroupData[groupIds[i]][j].y,
  20948. groupId: groupIds[i]
  20949. });
  20950. barPoints += 1;
  20951. }
  20952. }
  20953. }
  20954. }
  20955. if (barPoints == 0) {return;}
  20956. // sort by time and by group
  20957. combinedData.sort(function (a, b) {
  20958. if (a.x == b.x) {
  20959. return a.groupId - b.groupId;
  20960. } else {
  20961. return a.x - b.x;
  20962. }
  20963. });
  20964. // get intersections
  20965. Bargraph._getDataIntersections(intersections, combinedData);
  20966. // plot barchart
  20967. for (i = 0; i < combinedData.length; i++) {
  20968. group = framework.groups[combinedData[i].groupId];
  20969. var minWidth = 0.1 * group.options.barChart.width;
  20970. key = combinedData[i].x;
  20971. var heightOffset = 0;
  20972. if (intersections[key] === undefined) {
  20973. if (i+1 < combinedData.length) {coreDistance = Math.abs(combinedData[i+1].x - key);}
  20974. if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[i-1].x - key));}
  20975. drawData = Bargraph._getSafeDrawData(coreDistance, group, minWidth);
  20976. }
  20977. else {
  20978. var nextKey = i + (intersections[key].amount - intersections[key].resolved);
  20979. var prevKey = i - (intersections[key].resolved + 1);
  20980. if (nextKey < combinedData.length) {coreDistance = Math.abs(combinedData[nextKey].x - key);}
  20981. if (prevKey > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[prevKey].x - key));}
  20982. drawData = Bargraph._getSafeDrawData(coreDistance, group, minWidth);
  20983. intersections[key].resolved += 1;
  20984. if (group.options.barChart.handleOverlap == 'stack') {
  20985. heightOffset = intersections[key].accumulated;
  20986. intersections[key].accumulated += group.zeroPosition - combinedData[i].y;
  20987. }
  20988. else if (group.options.barChart.handleOverlap == 'sideBySide') {
  20989. drawData.width = drawData.width / intersections[key].amount;
  20990. drawData.offset += (intersections[key].resolved) * drawData.width - (0.5*drawData.width * (intersections[key].amount+1));
  20991. if (group.options.barChart.align == 'left') {drawData.offset -= 0.5*drawData.width;}
  20992. else if (group.options.barChart.align == 'right') {drawData.offset += 0.5*drawData.width;}
  20993. }
  20994. }
  20995. 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);
  20996. // draw points
  20997. if (group.options.drawPoints.enabled == true) {
  20998. DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y, group, framework.svgElements, framework.svg);
  20999. }
  21000. }
  21001. };
  21002. /**
  21003. * Fill the intersections object with counters of how many datapoints share the same x coordinates
  21004. * @param intersections
  21005. * @param combinedData
  21006. * @private
  21007. */
  21008. Bargraph._getDataIntersections = function (intersections, combinedData) {
  21009. // get intersections
  21010. var coreDistance;
  21011. for (var i = 0; i < combinedData.length; i++) {
  21012. if (i + 1 < combinedData.length) {
  21013. coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x);
  21014. }
  21015. if (i > 0) {
  21016. coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x));
  21017. }
  21018. if (coreDistance == 0) {
  21019. if (intersections[combinedData[i].x] === undefined) {
  21020. intersections[combinedData[i].x] = {amount: 0, resolved: 0, accumulated: 0};
  21021. }
  21022. intersections[combinedData[i].x].amount += 1;
  21023. }
  21024. }
  21025. };
  21026. /**
  21027. * Get the width and offset for bargraphs based on the coredistance between datapoints
  21028. *
  21029. * @param coreDistance
  21030. * @param group
  21031. * @param minWidth
  21032. * @returns {{width: Number, offset: Number}}
  21033. * @private
  21034. */
  21035. Bargraph._getSafeDrawData = function (coreDistance, group, minWidth) {
  21036. var width, offset;
  21037. if (coreDistance < group.options.barChart.width && coreDistance > 0) {
  21038. width = coreDistance < minWidth ? minWidth : coreDistance;
  21039. offset = 0; // recalculate offset with the new width;
  21040. if (group.options.barChart.align == 'left') {
  21041. offset -= 0.5 * coreDistance;
  21042. }
  21043. else if (group.options.barChart.align == 'right') {
  21044. offset += 0.5 * coreDistance;
  21045. }
  21046. }
  21047. else {
  21048. // default settings
  21049. width = group.options.barChart.width;
  21050. offset = 0;
  21051. if (group.options.barChart.align == 'left') {
  21052. offset -= 0.5 * group.options.barChart.width;
  21053. }
  21054. else if (group.options.barChart.align == 'right') {
  21055. offset += 0.5 * group.options.barChart.width;
  21056. }
  21057. }
  21058. return {width: width, offset: offset};
  21059. };
  21060. Bargraph.getStackedBarYRange = function(barCombinedData, groupRanges, groupIds, groupLabel, orientation) {
  21061. if (barCombinedData.length > 0) {
  21062. // sort by time and by group
  21063. barCombinedData.sort(function (a, b) {
  21064. if (a.x == b.x) {
  21065. return a.groupId - b.groupId;
  21066. } else {
  21067. return a.x - b.x;
  21068. }
  21069. });
  21070. var intersections = {};
  21071. Bargraph._getDataIntersections(intersections, barCombinedData);
  21072. groupRanges[groupLabel] = Bargraph._getStackedBarYRange(intersections, barCombinedData);
  21073. groupRanges[groupLabel].yAxisOrientation = orientation;
  21074. groupIds.push(groupLabel);
  21075. }
  21076. }
  21077. Bargraph._getStackedBarYRange = function (intersections, combinedData) {
  21078. var key;
  21079. var yMin = combinedData[0].y;
  21080. var yMax = combinedData[0].y;
  21081. for (var i = 0; i < combinedData.length; i++) {
  21082. key = combinedData[i].x;
  21083. if (intersections[key] === undefined) {
  21084. yMin = yMin > combinedData[i].y ? combinedData[i].y : yMin;
  21085. yMax = yMax < combinedData[i].y ? combinedData[i].y : yMax;
  21086. }
  21087. else {
  21088. intersections[key].accumulated += combinedData[i].y;
  21089. }
  21090. }
  21091. for (var xpos in intersections) {
  21092. if (intersections.hasOwnProperty(xpos)) {
  21093. yMin = yMin > intersections[xpos].accumulated ? intersections[xpos].accumulated : yMin;
  21094. yMax = yMax < intersections[xpos].accumulated ? intersections[xpos].accumulated : yMax;
  21095. }
  21096. }
  21097. return {min: yMin, max: yMax};
  21098. };
  21099. module.exports = Bargraph;
  21100. /***/ },
  21101. /* 51 */
  21102. /***/ function(module, exports, __webpack_require__) {
  21103. /**
  21104. * Created by Alex on 11/11/2014.
  21105. */
  21106. var DOMutil = __webpack_require__(2);
  21107. function Points(groupId, options) {
  21108. this.groupId = groupId;
  21109. this.options = options;
  21110. }
  21111. Points.prototype.getYRange = function(groupData) {
  21112. var yMin = groupData[0].y;
  21113. var yMax = groupData[0].y;
  21114. for (var j = 0; j < groupData.length; j++) {
  21115. yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
  21116. yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
  21117. }
  21118. return {min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation};
  21119. };
  21120. Points.prototype.draw = function(dataset, group, framework, offset) {
  21121. Points.draw(dataset, group, framework, offset);
  21122. }
  21123. /**
  21124. * draw the data points
  21125. *
  21126. * @param {Array} dataset
  21127. * @param {Object} JSONcontainer
  21128. * @param {Object} svg | SVG DOM element
  21129. * @param {GraphGroup} group
  21130. * @param {Number} [offset]
  21131. */
  21132. Points.draw = function (dataset, group, framework, offset) {
  21133. if (offset === undefined) {offset = 0;}
  21134. for (var i = 0; i < dataset.length; i++) {
  21135. DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, group, framework.svgElements, framework.svg, dataset[i].label);
  21136. }
  21137. };
  21138. module.exports = Points;
  21139. /***/ },
  21140. /* 52 */
  21141. /***/ function(module, exports, __webpack_require__) {
  21142. var PhysicsMixin = __webpack_require__(60);
  21143. var ClusterMixin = __webpack_require__(61);
  21144. var SectorsMixin = __webpack_require__(62);
  21145. var SelectionMixin = __webpack_require__(63);
  21146. var ManipulationMixin = __webpack_require__(64);
  21147. var NavigationMixin = __webpack_require__(65);
  21148. var HierarchicalLayoutMixin = __webpack_require__(66);
  21149. /**
  21150. * Load a mixin into the network object
  21151. *
  21152. * @param {Object} sourceVariable | this object has to contain functions.
  21153. * @private
  21154. */
  21155. exports._loadMixin = function (sourceVariable) {
  21156. for (var mixinFunction in sourceVariable) {
  21157. if (sourceVariable.hasOwnProperty(mixinFunction)) {
  21158. this[mixinFunction] = sourceVariable[mixinFunction];
  21159. }
  21160. }
  21161. };
  21162. /**
  21163. * removes a mixin from the network object.
  21164. *
  21165. * @param {Object} sourceVariable | this object has to contain functions.
  21166. * @private
  21167. */
  21168. exports._clearMixin = function (sourceVariable) {
  21169. for (var mixinFunction in sourceVariable) {
  21170. if (sourceVariable.hasOwnProperty(mixinFunction)) {
  21171. this[mixinFunction] = undefined;
  21172. }
  21173. }
  21174. };
  21175. /**
  21176. * Mixin the physics system and initialize the parameters required.
  21177. *
  21178. * @private
  21179. */
  21180. exports._loadPhysicsSystem = function () {
  21181. this._loadMixin(PhysicsMixin);
  21182. this._loadSelectedForceSolver();
  21183. if (this.constants.configurePhysics == true) {
  21184. this._loadPhysicsConfiguration();
  21185. }
  21186. else {
  21187. this._cleanupPhysicsConfiguration();
  21188. }
  21189. };
  21190. /**
  21191. * Mixin the cluster system and initialize the parameters required.
  21192. *
  21193. * @private
  21194. */
  21195. exports._loadClusterSystem = function () {
  21196. this.clusterSession = 0;
  21197. this.hubThreshold = 5;
  21198. this._loadMixin(ClusterMixin);
  21199. };
  21200. /**
  21201. * Mixin the sector system and initialize the parameters required
  21202. *
  21203. * @private
  21204. */
  21205. exports._loadSectorSystem = function () {
  21206. this.sectors = {};
  21207. this.activeSector = ["default"];
  21208. this.sectors["active"] = {};
  21209. this.sectors["active"]["default"] = {"nodes": {},
  21210. "edges": {},
  21211. "nodeIndices": [],
  21212. "formationScale": 1.0,
  21213. "drawingNode": undefined };
  21214. this.sectors["frozen"] = {};
  21215. this.sectors["support"] = {"nodes": {},
  21216. "edges": {},
  21217. "nodeIndices": [],
  21218. "formationScale": 1.0,
  21219. "drawingNode": undefined };
  21220. this.nodeIndices = this.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields
  21221. this._loadMixin(SectorsMixin);
  21222. };
  21223. /**
  21224. * Mixin the selection system and initialize the parameters required
  21225. *
  21226. * @private
  21227. */
  21228. exports._loadSelectionSystem = function () {
  21229. this.selectionObj = {nodes: {}, edges: {}};
  21230. this._loadMixin(SelectionMixin);
  21231. };
  21232. /**
  21233. * Mixin the navigationUI (User Interface) system and initialize the parameters required
  21234. *
  21235. * @private
  21236. */
  21237. exports._loadManipulationSystem = function () {
  21238. // reset global variables -- these are used by the selection of nodes and edges.
  21239. this.blockConnectingEdgeSelection = false;
  21240. this.forceAppendSelection = false;
  21241. if (this.constants.dataManipulation.enabled == true) {
  21242. // load the manipulator HTML elements. All styling done in css.
  21243. if (this.manipulationDiv === undefined) {
  21244. this.manipulationDiv = document.createElement('div');
  21245. this.manipulationDiv.className = 'network-manipulationDiv';
  21246. if (this.editMode == true) {
  21247. this.manipulationDiv.style.display = "block";
  21248. }
  21249. else {
  21250. this.manipulationDiv.style.display = "none";
  21251. }
  21252. this.frame.appendChild(this.manipulationDiv);
  21253. }
  21254. if (this.editModeDiv === undefined) {
  21255. this.editModeDiv = document.createElement('div');
  21256. this.editModeDiv.className = 'network-manipulation-editMode';
  21257. if (this.editMode == true) {
  21258. this.editModeDiv.style.display = "none";
  21259. }
  21260. else {
  21261. this.editModeDiv.style.display = "block";
  21262. }
  21263. this.frame.appendChild(this.editModeDiv);
  21264. }
  21265. if (this.closeDiv === undefined) {
  21266. this.closeDiv = document.createElement('div');
  21267. this.closeDiv.className = 'network-manipulation-closeDiv';
  21268. this.closeDiv.style.display = this.manipulationDiv.style.display;
  21269. this.frame.appendChild(this.closeDiv);
  21270. }
  21271. // load the manipulation functions
  21272. this._loadMixin(ManipulationMixin);
  21273. // create the manipulator toolbar
  21274. this._createManipulatorBar();
  21275. }
  21276. else {
  21277. if (this.manipulationDiv !== undefined) {
  21278. // removes all the bindings and overloads
  21279. this._createManipulatorBar();
  21280. // remove the manipulation divs
  21281. this.frame.removeChild(this.manipulationDiv);
  21282. this.frame.removeChild(this.editModeDiv);
  21283. this.frame.removeChild(this.closeDiv);
  21284. this.manipulationDiv = undefined;
  21285. this.editModeDiv = undefined;
  21286. this.closeDiv = undefined;
  21287. // remove the mixin functions
  21288. this._clearMixin(ManipulationMixin);
  21289. }
  21290. }
  21291. };
  21292. /**
  21293. * Mixin the navigation (User Interface) system and initialize the parameters required
  21294. *
  21295. * @private
  21296. */
  21297. exports._loadNavigationControls = function () {
  21298. this._loadMixin(NavigationMixin);
  21299. // the clean function removes the button divs, this is done to remove the bindings.
  21300. this._cleanNavigation();
  21301. if (this.constants.navigation.enabled == true) {
  21302. this._loadNavigationElements();
  21303. }
  21304. };
  21305. /**
  21306. * Mixin the hierarchical layout system.
  21307. *
  21308. * @private
  21309. */
  21310. exports._loadHierarchySystem = function () {
  21311. this._loadMixin(HierarchicalLayoutMixin);
  21312. };
  21313. /***/ },
  21314. /* 53 */
  21315. /***/ function(module, exports, __webpack_require__) {
  21316. var keycharm = __webpack_require__(57);
  21317. var Emitter = __webpack_require__(56);
  21318. var Hammer = __webpack_require__(45);
  21319. var util = __webpack_require__(1);
  21320. /**
  21321. * Turn an element into an clickToUse element.
  21322. * When not active, the element has a transparent overlay. When the overlay is
  21323. * clicked, the mode is changed to active.
  21324. * When active, the element is displayed with a blue border around it, and
  21325. * the interactive contents of the element can be used. When clicked outside
  21326. * the element, the elements mode is changed to inactive.
  21327. * @param {Element} container
  21328. * @constructor
  21329. */
  21330. function Activator(container) {
  21331. this.active = false;
  21332. this.dom = {
  21333. container: container
  21334. };
  21335. this.dom.overlay = document.createElement('div');
  21336. this.dom.overlay.className = 'overlay';
  21337. this.dom.container.appendChild(this.dom.overlay);
  21338. this.hammer = Hammer(this.dom.overlay, {prevent_default: false});
  21339. this.hammer.on('tap', this._onTapOverlay.bind(this));
  21340. // block all touch events (except tap)
  21341. var me = this;
  21342. var events = [
  21343. 'touch', 'pinch',
  21344. 'doubletap', 'hold',
  21345. 'dragstart', 'drag', 'dragend',
  21346. 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
  21347. ];
  21348. events.forEach(function (event) {
  21349. me.hammer.on(event, function (event) {
  21350. event.stopPropagation();
  21351. });
  21352. });
  21353. // attach a tap event to the window, in order to deactivate when clicking outside the timeline
  21354. this.windowHammer = Hammer(window, {prevent_default: false});
  21355. this.windowHammer.on('tap', function (event) {
  21356. // deactivate when clicked outside the container
  21357. if (!_hasParent(event.target, container)) {
  21358. me.deactivate();
  21359. }
  21360. });
  21361. if (this.keycharm !== undefined) {
  21362. this.keycharm.destroy();
  21363. }
  21364. this.keycharm = keycharm();
  21365. // keycharm listener only bounded when active)
  21366. this.escListener = this.deactivate.bind(this);
  21367. }
  21368. // turn into an event emitter
  21369. Emitter(Activator.prototype);
  21370. // The currently active activator
  21371. Activator.current = null;
  21372. /**
  21373. * Destroy the activator. Cleans up all created DOM and event listeners
  21374. */
  21375. Activator.prototype.destroy = function () {
  21376. this.deactivate();
  21377. // remove dom
  21378. this.dom.overlay.parentNode.removeChild(this.dom.overlay);
  21379. // cleanup hammer instances
  21380. this.hammer = null;
  21381. this.windowHammer = null;
  21382. // FIXME: cleaning up hammer instances doesn't work (Timeline not removed from memory)
  21383. };
  21384. /**
  21385. * Activate the element
  21386. * Overlay is hidden, element is decorated with a blue shadow border
  21387. */
  21388. Activator.prototype.activate = function () {
  21389. // we allow only one active activator at a time
  21390. if (Activator.current) {
  21391. Activator.current.deactivate();
  21392. }
  21393. Activator.current = this;
  21394. this.active = true;
  21395. this.dom.overlay.style.display = 'none';
  21396. util.addClassName(this.dom.container, 'vis-active');
  21397. this.emit('change');
  21398. this.emit('activate');
  21399. // ugly hack: bind ESC after emitting the events, as the Network rebinds all
  21400. // keyboard events on a 'change' event
  21401. this.keycharm.bind('esc', this.escListener);
  21402. };
  21403. /**
  21404. * Deactivate the element
  21405. * Overlay is displayed on top of the element
  21406. */
  21407. Activator.prototype.deactivate = function () {
  21408. this.active = false;
  21409. this.dom.overlay.style.display = '';
  21410. util.removeClassName(this.dom.container, 'vis-active');
  21411. this.keycharm.unbind('esc', this.escListener);
  21412. this.emit('change');
  21413. this.emit('deactivate');
  21414. };
  21415. /**
  21416. * Handle a tap event: activate the container
  21417. * @param event
  21418. * @private
  21419. */
  21420. Activator.prototype._onTapOverlay = function (event) {
  21421. // activate the container
  21422. this.activate();
  21423. event.stopPropagation();
  21424. };
  21425. /**
  21426. * Test whether the element has the requested parent element somewhere in
  21427. * its chain of parent nodes.
  21428. * @param {HTMLElement} element
  21429. * @param {HTMLElement} parent
  21430. * @returns {boolean} Returns true when the parent is found somewhere in the
  21431. * chain of parent nodes.
  21432. * @private
  21433. */
  21434. function _hasParent(element, parent) {
  21435. while (element) {
  21436. if (element === parent) {
  21437. return true
  21438. }
  21439. element = element.parentNode;
  21440. }
  21441. return false;
  21442. }
  21443. module.exports = Activator;
  21444. /***/ },
  21445. /* 54 */
  21446. /***/ function(module, exports, __webpack_require__) {
  21447. // English
  21448. exports['en'] = {
  21449. edit: 'Edit',
  21450. del: 'Delete selected',
  21451. back: 'Back',
  21452. addNode: 'Add Node',
  21453. addEdge: 'Add Edge',
  21454. editNode: 'Edit Node',
  21455. editEdge: 'Edit Edge',
  21456. addDescription: 'Click in an empty space to place a new node.',
  21457. edgeDescription: 'Click on a node and drag the edge to another node to connect them.',
  21458. editEdgeDescription: 'Click on the control points and drag them to a node to connect to it.',
  21459. createEdgeError: 'Cannot link edges to a cluster.',
  21460. deleteClusterError: 'Clusters cannot be deleted.'
  21461. };
  21462. exports['en_EN'] = exports['en'];
  21463. exports['en_US'] = exports['en'];
  21464. // Dutch
  21465. exports['nl'] = {
  21466. edit: 'Wijzigen',
  21467. del: 'Selectie verwijderen',
  21468. back: 'Terug',
  21469. addNode: 'Node toevoegen',
  21470. addEdge: 'Link toevoegen',
  21471. editNode: 'Node wijzigen',
  21472. editEdge: 'Link wijzigen',
  21473. addDescription: 'Klik op een leeg gebied om een nieuwe node te maken.',
  21474. edgeDescription: 'Klik op een node en sleep de link naar een andere node om ze te verbinden.',
  21475. editEdgeDescription: 'Klik op de verbindingspunten en sleep ze naar een node om daarmee te verbinden.',
  21476. createEdgeError: 'Kan geen link maken naar een cluster.',
  21477. deleteClusterError: 'Clusters kunnen niet worden verwijderd.'
  21478. };
  21479. exports['nl_NL'] = exports['nl'];
  21480. exports['nl_BE'] = exports['nl'];
  21481. /***/ },
  21482. /* 55 */
  21483. /***/ function(module, exports, __webpack_require__) {
  21484. /**
  21485. * Canvas shapes used by Network
  21486. */
  21487. if (typeof CanvasRenderingContext2D !== 'undefined') {
  21488. /**
  21489. * Draw a circle shape
  21490. */
  21491. CanvasRenderingContext2D.prototype.circle = function(x, y, r) {
  21492. this.beginPath();
  21493. this.arc(x, y, r, 0, 2*Math.PI, false);
  21494. };
  21495. /**
  21496. * Draw a square shape
  21497. * @param {Number} x horizontal center
  21498. * @param {Number} y vertical center
  21499. * @param {Number} r size, width and height of the square
  21500. */
  21501. CanvasRenderingContext2D.prototype.square = function(x, y, r) {
  21502. this.beginPath();
  21503. this.rect(x - r, y - r, r * 2, r * 2);
  21504. };
  21505. /**
  21506. * Draw a triangle shape
  21507. * @param {Number} x horizontal center
  21508. * @param {Number} y vertical center
  21509. * @param {Number} r radius, half the length of the sides of the triangle
  21510. */
  21511. CanvasRenderingContext2D.prototype.triangle = function(x, y, r) {
  21512. // http://en.wikipedia.org/wiki/Equilateral_triangle
  21513. this.beginPath();
  21514. var s = r * 2;
  21515. var s2 = s / 2;
  21516. var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
  21517. var h = Math.sqrt(s * s - s2 * s2); // height
  21518. this.moveTo(x, y - (h - ir));
  21519. this.lineTo(x + s2, y + ir);
  21520. this.lineTo(x - s2, y + ir);
  21521. this.lineTo(x, y - (h - ir));
  21522. this.closePath();
  21523. };
  21524. /**
  21525. * Draw a triangle shape in downward orientation
  21526. * @param {Number} x horizontal center
  21527. * @param {Number} y vertical center
  21528. * @param {Number} r radius
  21529. */
  21530. CanvasRenderingContext2D.prototype.triangleDown = function(x, y, r) {
  21531. // http://en.wikipedia.org/wiki/Equilateral_triangle
  21532. this.beginPath();
  21533. var s = r * 2;
  21534. var s2 = s / 2;
  21535. var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
  21536. var h = Math.sqrt(s * s - s2 * s2); // height
  21537. this.moveTo(x, y + (h - ir));
  21538. this.lineTo(x + s2, y - ir);
  21539. this.lineTo(x - s2, y - ir);
  21540. this.lineTo(x, y + (h - ir));
  21541. this.closePath();
  21542. };
  21543. /**
  21544. * Draw a star shape, a star with 5 points
  21545. * @param {Number} x horizontal center
  21546. * @param {Number} y vertical center
  21547. * @param {Number} r radius, half the length of the sides of the triangle
  21548. */
  21549. CanvasRenderingContext2D.prototype.star = function(x, y, r) {
  21550. // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/
  21551. this.beginPath();
  21552. for (var n = 0; n < 10; n++) {
  21553. var radius = (n % 2 === 0) ? r * 1.3 : r * 0.5;
  21554. this.lineTo(
  21555. x + radius * Math.sin(n * 2 * Math.PI / 10),
  21556. y - radius * Math.cos(n * 2 * Math.PI / 10)
  21557. );
  21558. }
  21559. this.closePath();
  21560. };
  21561. /**
  21562. * http://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas
  21563. */
  21564. CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
  21565. var r2d = Math.PI/180;
  21566. if( w - ( 2 * r ) < 0 ) { r = ( w / 2 ); } //ensure that the radius isn't too large for x
  21567. if( h - ( 2 * r ) < 0 ) { r = ( h / 2 ); } //ensure that the radius isn't too large for y
  21568. this.beginPath();
  21569. this.moveTo(x+r,y);
  21570. this.lineTo(x+w-r,y);
  21571. this.arc(x+w-r,y+r,r,r2d*270,r2d*360,false);
  21572. this.lineTo(x+w,y+h-r);
  21573. this.arc(x+w-r,y+h-r,r,0,r2d*90,false);
  21574. this.lineTo(x+r,y+h);
  21575. this.arc(x+r,y+h-r,r,r2d*90,r2d*180,false);
  21576. this.lineTo(x,y+r);
  21577. this.arc(x+r,y+r,r,r2d*180,r2d*270,false);
  21578. };
  21579. /**
  21580. * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  21581. */
  21582. CanvasRenderingContext2D.prototype.ellipse = function(x, y, w, h) {
  21583. var kappa = .5522848,
  21584. ox = (w / 2) * kappa, // control point offset horizontal
  21585. oy = (h / 2) * kappa, // control point offset vertical
  21586. xe = x + w, // x-end
  21587. ye = y + h, // y-end
  21588. xm = x + w / 2, // x-middle
  21589. ym = y + h / 2; // y-middle
  21590. this.beginPath();
  21591. this.moveTo(x, ym);
  21592. this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  21593. this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  21594. this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  21595. this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  21596. };
  21597. /**
  21598. * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  21599. */
  21600. CanvasRenderingContext2D.prototype.database = function(x, y, w, h) {
  21601. var f = 1/3;
  21602. var wEllipse = w;
  21603. var hEllipse = h * f;
  21604. var kappa = .5522848,
  21605. ox = (wEllipse / 2) * kappa, // control point offset horizontal
  21606. oy = (hEllipse / 2) * kappa, // control point offset vertical
  21607. xe = x + wEllipse, // x-end
  21608. ye = y + hEllipse, // y-end
  21609. xm = x + wEllipse / 2, // x-middle
  21610. ym = y + hEllipse / 2, // y-middle
  21611. ymb = y + (h - hEllipse/2), // y-midlle, bottom ellipse
  21612. yeb = y + h; // y-end, bottom ellipse
  21613. this.beginPath();
  21614. this.moveTo(xe, ym);
  21615. this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  21616. this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  21617. this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  21618. this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  21619. this.lineTo(xe, ymb);
  21620. this.bezierCurveTo(xe, ymb + oy, xm + ox, yeb, xm, yeb);
  21621. this.bezierCurveTo(xm - ox, yeb, x, ymb + oy, x, ymb);
  21622. this.lineTo(x, ym);
  21623. };
  21624. /**
  21625. * Draw an arrow point (no line)
  21626. */
  21627. CanvasRenderingContext2D.prototype.arrow = function(x, y, angle, length) {
  21628. // tail
  21629. var xt = x - length * Math.cos(angle);
  21630. var yt = y - length * Math.sin(angle);
  21631. // inner tail
  21632. // TODO: allow to customize different shapes
  21633. var xi = x - length * 0.9 * Math.cos(angle);
  21634. var yi = y - length * 0.9 * Math.sin(angle);
  21635. // left
  21636. var xl = xt + length / 3 * Math.cos(angle + 0.5 * Math.PI);
  21637. var yl = yt + length / 3 * Math.sin(angle + 0.5 * Math.PI);
  21638. // right
  21639. var xr = xt + length / 3 * Math.cos(angle - 0.5 * Math.PI);
  21640. var yr = yt + length / 3 * Math.sin(angle - 0.5 * Math.PI);
  21641. this.beginPath();
  21642. this.moveTo(x, y);
  21643. this.lineTo(xl, yl);
  21644. this.lineTo(xi, yi);
  21645. this.lineTo(xr, yr);
  21646. this.closePath();
  21647. };
  21648. /**
  21649. * Sets up the dashedLine functionality for drawing
  21650. * Original code came from http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
  21651. * @author David Jordan
  21652. * @date 2012-08-08
  21653. */
  21654. CanvasRenderingContext2D.prototype.dashedLine = function(x,y,x2,y2,dashArray){
  21655. if (!dashArray) dashArray=[10,5];
  21656. if (dashLength==0) dashLength = 0.001; // Hack for Safari
  21657. var dashCount = dashArray.length;
  21658. this.moveTo(x, y);
  21659. var dx = (x2-x), dy = (y2-y);
  21660. var slope = dy/dx;
  21661. var distRemaining = Math.sqrt( dx*dx + dy*dy );
  21662. var dashIndex=0, draw=true;
  21663. while (distRemaining>=0.1){
  21664. var dashLength = dashArray[dashIndex++%dashCount];
  21665. if (dashLength > distRemaining) dashLength = distRemaining;
  21666. var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) );
  21667. if (dx<0) xStep = -xStep;
  21668. x += xStep;
  21669. y += slope*xStep;
  21670. this[draw ? 'lineTo' : 'moveTo'](x,y);
  21671. distRemaining -= dashLength;
  21672. draw = !draw;
  21673. }
  21674. };
  21675. // TODO: add diamond shape
  21676. }
  21677. /***/ },
  21678. /* 56 */
  21679. /***/ function(module, exports, __webpack_require__) {
  21680. /**
  21681. * Expose `Emitter`.
  21682. */
  21683. module.exports = Emitter;
  21684. /**
  21685. * Initialize a new `Emitter`.
  21686. *
  21687. * @api public
  21688. */
  21689. function Emitter(obj) {
  21690. if (obj) return mixin(obj);
  21691. };
  21692. /**
  21693. * Mixin the emitter properties.
  21694. *
  21695. * @param {Object} obj
  21696. * @return {Object}
  21697. * @api private
  21698. */
  21699. function mixin(obj) {
  21700. for (var key in Emitter.prototype) {
  21701. obj[key] = Emitter.prototype[key];
  21702. }
  21703. return obj;
  21704. }
  21705. /**
  21706. * Listen on the given `event` with `fn`.
  21707. *
  21708. * @param {String} event
  21709. * @param {Function} fn
  21710. * @return {Emitter}
  21711. * @api public
  21712. */
  21713. Emitter.prototype.on =
  21714. Emitter.prototype.addEventListener = function(event, fn){
  21715. this._callbacks = this._callbacks || {};
  21716. (this._callbacks[event] = this._callbacks[event] || [])
  21717. .push(fn);
  21718. return this;
  21719. };
  21720. /**
  21721. * Adds an `event` listener that will be invoked a single
  21722. * time then automatically removed.
  21723. *
  21724. * @param {String} event
  21725. * @param {Function} fn
  21726. * @return {Emitter}
  21727. * @api public
  21728. */
  21729. Emitter.prototype.once = function(event, fn){
  21730. var self = this;
  21731. this._callbacks = this._callbacks || {};
  21732. function on() {
  21733. self.off(event, on);
  21734. fn.apply(this, arguments);
  21735. }
  21736. on.fn = fn;
  21737. this.on(event, on);
  21738. return this;
  21739. };
  21740. /**
  21741. * Remove the given callback for `event` or all
  21742. * registered callbacks.
  21743. *
  21744. * @param {String} event
  21745. * @param {Function} fn
  21746. * @return {Emitter}
  21747. * @api public
  21748. */
  21749. Emitter.prototype.off =
  21750. Emitter.prototype.removeListener =
  21751. Emitter.prototype.removeAllListeners =
  21752. Emitter.prototype.removeEventListener = function(event, fn){
  21753. this._callbacks = this._callbacks || {};
  21754. // all
  21755. if (0 == arguments.length) {
  21756. this._callbacks = {};
  21757. return this;
  21758. }
  21759. // specific event
  21760. var callbacks = this._callbacks[event];
  21761. if (!callbacks) return this;
  21762. // remove all handlers
  21763. if (1 == arguments.length) {
  21764. delete this._callbacks[event];
  21765. return this;
  21766. }
  21767. // remove specific handler
  21768. var cb;
  21769. for (var i = 0; i < callbacks.length; i++) {
  21770. cb = callbacks[i];
  21771. if (cb === fn || cb.fn === fn) {
  21772. callbacks.splice(i, 1);
  21773. break;
  21774. }
  21775. }
  21776. return this;
  21777. };
  21778. /**
  21779. * Emit `event` with the given args.
  21780. *
  21781. * @param {String} event
  21782. * @param {Mixed} ...
  21783. * @return {Emitter}
  21784. */
  21785. Emitter.prototype.emit = function(event){
  21786. this._callbacks = this._callbacks || {};
  21787. var args = [].slice.call(arguments, 1)
  21788. , callbacks = this._callbacks[event];
  21789. if (callbacks) {
  21790. callbacks = callbacks.slice(0);
  21791. for (var i = 0, len = callbacks.length; i < len; ++i) {
  21792. callbacks[i].apply(this, args);
  21793. }
  21794. }
  21795. return this;
  21796. };
  21797. /**
  21798. * Return array of callbacks for `event`.
  21799. *
  21800. * @param {String} event
  21801. * @return {Array}
  21802. * @api public
  21803. */
  21804. Emitter.prototype.listeners = function(event){
  21805. this._callbacks = this._callbacks || {};
  21806. return this._callbacks[event] || [];
  21807. };
  21808. /**
  21809. * Check if this emitter has `event` handlers.
  21810. *
  21811. * @param {String} event
  21812. * @return {Boolean}
  21813. * @api public
  21814. */
  21815. Emitter.prototype.hasListeners = function(event){
  21816. return !! this.listeners(event).length;
  21817. };
  21818. /***/ },
  21819. /* 57 */
  21820. /***/ function(module, exports, __webpack_require__) {
  21821. var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;"use strict";
  21822. /**
  21823. * Created by Alex on 11/6/2014.
  21824. */
  21825. // https://github.com/umdjs/umd/blob/master/returnExports.js#L40-L60
  21826. // if the module has no dependencies, the above pattern can be simplified to
  21827. (function (root, factory) {
  21828. if (true) {
  21829. // AMD. Register as an anonymous module.
  21830. !(__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__));
  21831. } else if (typeof exports === 'object') {
  21832. // Node. Does not work with strict CommonJS, but
  21833. // only CommonJS-like environments that support module.exports,
  21834. // like Node.
  21835. module.exports = factory();
  21836. } else {
  21837. // Browser globals (root is window)
  21838. root.keycharm = factory();
  21839. }
  21840. }(this, function () {
  21841. function keycharm(options) {
  21842. var preventDefault = options && options.preventDefault || false;
  21843. var container = options && options.container || window;
  21844. var _exportFunctions = {};
  21845. var _bound = {keydown:{}, keyup:{}};
  21846. var _keys = {};
  21847. var i;
  21848. // a - z
  21849. for (i = 97; i <= 122; i++) {_keys[String.fromCharCode(i)] = {code:65 + (i - 97), shift: false};}
  21850. // A - Z
  21851. for (i = 65; i <= 90; i++) {_keys[String.fromCharCode(i)] = {code:i, shift: true};}
  21852. // 0 - 9
  21853. for (i = 0; i <= 9; i++) {_keys['' + i] = {code:48 + i, shift: false};}
  21854. // F1 - F12
  21855. for (i = 1; i <= 12; i++) {_keys['F' + i] = {code:111 + i, shift: false};}
  21856. // num0 - num9
  21857. for (i = 0; i <= 9; i++) {_keys['num' + i] = {code:96 + i, shift: false};}
  21858. // numpad misc
  21859. _keys['num*'] = {code:106, shift: false};
  21860. _keys['num+'] = {code:107, shift: false};
  21861. _keys['num-'] = {code:109, shift: false};
  21862. _keys['num/'] = {code:111, shift: false};
  21863. _keys['num.'] = {code:110, shift: false};
  21864. // arrows
  21865. _keys['left'] = {code:37, shift: false};
  21866. _keys['up'] = {code:38, shift: false};
  21867. _keys['right'] = {code:39, shift: false};
  21868. _keys['down'] = {code:40, shift: false};
  21869. // extra keys
  21870. _keys['space'] = {code:32, shift: false};
  21871. _keys['enter'] = {code:13, shift: false};
  21872. _keys['shift'] = {code:16, shift: undefined};
  21873. _keys['esc'] = {code:27, shift: false};
  21874. _keys['backspace'] = {code:8, shift: false};
  21875. _keys['tab'] = {code:9, shift: false};
  21876. _keys['ctrl'] = {code:17, shift: false};
  21877. _keys['alt'] = {code:18, shift: false};
  21878. _keys['delete'] = {code:46, shift: false};
  21879. _keys['pageup'] = {code:33, shift: false};
  21880. _keys['pagedown'] = {code:34, shift: false};
  21881. // symbols
  21882. _keys['='] = {code:187, shift: false};
  21883. _keys['-'] = {code:189, shift: false};
  21884. _keys[']'] = {code:221, shift: false};
  21885. _keys['['] = {code:219, shift: false};
  21886. var down = function(event) {handleEvent(event,'keydown');};
  21887. var up = function(event) {handleEvent(event,'keyup');};
  21888. // handle the actualy bound key with the event
  21889. var handleEvent = function(event,type) {
  21890. if (_bound[type][event.keyCode] !== undefined) {
  21891. var bound = _bound[type][event.keyCode];
  21892. for (var i = 0; i < bound.length; i++) {
  21893. if (bound[i].shift === undefined) {
  21894. bound[i].fn(event);
  21895. }
  21896. else if (bound[i].shift == true && event.shiftKey == true) {
  21897. bound[i].fn(event);
  21898. }
  21899. else if (bound[i].shift == false && event.shiftKey == false) {
  21900. bound[i].fn(event);
  21901. }
  21902. }
  21903. if (preventDefault == true) {
  21904. event.preventDefault();
  21905. }
  21906. }
  21907. };
  21908. // bind a key to a callback
  21909. _exportFunctions.bind = function(key, callback, type) {
  21910. if (type === undefined) {
  21911. type = 'keydown';
  21912. }
  21913. if (_keys[key] === undefined) {
  21914. throw new Error("unsupported key: " + key);
  21915. }
  21916. if (_bound[type][_keys[key].code] === undefined) {
  21917. _bound[type][_keys[key].code] = [];
  21918. }
  21919. _bound[type][_keys[key].code].push({fn:callback, shift:_keys[key].shift});
  21920. };
  21921. // bind all keys to a call back (demo purposes)
  21922. _exportFunctions.bindAll = function(callback, type) {
  21923. if (type === undefined) {
  21924. type = 'keydown';
  21925. }
  21926. for (var key in _keys) {
  21927. if (_keys.hasOwnProperty(key)) {
  21928. _exportFunctions.bind(key,callback,type);
  21929. }
  21930. }
  21931. };
  21932. // get the key label from an event
  21933. _exportFunctions.getKey = function(event) {
  21934. for (var key in _keys) {
  21935. if (_keys.hasOwnProperty(key)) {
  21936. if (event.shiftKey == true && _keys[key].shift == true && event.keyCode == _keys[key].code) {
  21937. return key;
  21938. }
  21939. else if (event.shiftKey == false && _keys[key].shift == false && event.keyCode == _keys[key].code) {
  21940. return key;
  21941. }
  21942. else if (event.keyCode == _keys[key].code && key == 'shift') {
  21943. return key;
  21944. }
  21945. }
  21946. }
  21947. return "unknown key, currently not supported";
  21948. };
  21949. // unbind either a specific callback from a key or all of them (by leaving callback undefined)
  21950. _exportFunctions.unbind = function(key, callback, type) {
  21951. if (type === undefined) {
  21952. type = 'keydown';
  21953. }
  21954. if (_keys[key] === undefined) {
  21955. throw new Error("unsupported key: " + key);
  21956. }
  21957. if (callback !== undefined) {
  21958. var newBindings = [];
  21959. var bound = _bound[type][_keys[key].code];
  21960. if (bound !== undefined) {
  21961. for (var i = 0; i < bound.length; i++) {
  21962. if (!(bound[i].fn == callback && bound[i].shift == _keys[key].shift)) {
  21963. newBindings.push(_bound[type][_keys[key].code][i]);
  21964. }
  21965. }
  21966. }
  21967. _bound[type][_keys[key].code] = newBindings;
  21968. }
  21969. else {
  21970. _bound[type][_keys[key].code] = [];
  21971. }
  21972. };
  21973. // reset all bound variables.
  21974. _exportFunctions.reset = function() {
  21975. _bound = {keydown:{}, keyup:{}};
  21976. };
  21977. // unbind all listeners and reset all variables.
  21978. _exportFunctions.destroy = function() {
  21979. _bound = {keydown:{}, keyup:{}};
  21980. container.removeEventListener('keydown', down, true);
  21981. container.removeEventListener('keyup', up, true);
  21982. };
  21983. // create listeners.
  21984. container.addEventListener('keydown',down,true);
  21985. container.addEventListener('keyup',up,true);
  21986. // return the public functions.
  21987. return _exportFunctions;
  21988. }
  21989. return keycharm;
  21990. }));
  21991. /***/ },
  21992. /* 58 */
  21993. /***/ function(module, exports, __webpack_require__) {
  21994. var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(global, module) {//! moment.js
  21995. //! version : 2.9.0
  21996. //! authors : Tim Wood, Iskren Chernev, Moment.js contributors
  21997. //! license : MIT
  21998. //! momentjs.com
  21999. (function (undefined) {
  22000. /************************************
  22001. Constants
  22002. ************************************/
  22003. var moment,
  22004. VERSION = '2.9.0',
  22005. // the global-scope this is NOT the global object in Node.js
  22006. globalScope = (typeof global !== 'undefined' && (typeof window === 'undefined' || window === global.window)) ? global : this,
  22007. oldGlobalMoment,
  22008. round = Math.round,
  22009. hasOwnProperty = Object.prototype.hasOwnProperty,
  22010. i,
  22011. YEAR = 0,
  22012. MONTH = 1,
  22013. DATE = 2,
  22014. HOUR = 3,
  22015. MINUTE = 4,
  22016. SECOND = 5,
  22017. MILLISECOND = 6,
  22018. // internal storage for locale config files
  22019. locales = {},
  22020. // extra moment internal properties (plugins register props here)
  22021. momentProperties = [],
  22022. // check for nodeJS
  22023. hasModule = (typeof module !== 'undefined' && module && module.exports),
  22024. // ASP.NET json date format regex
  22025. aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
  22026. aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,
  22027. // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
  22028. // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
  22029. isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,
  22030. // format tokens
  22031. 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,
  22032. localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,
  22033. // parsing token regexes
  22034. parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
  22035. parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
  22036. parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999
  22037. parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
  22038. parseTokenDigits = /\d+/, // nonzero number of digits
  22039. parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic.
  22040. parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
  22041. parseTokenT = /T/i, // T (ISO separator)
  22042. parseTokenOffsetMs = /[\+\-]?\d+/, // 1234567890123
  22043. parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
  22044. //strict parsing regexes
  22045. parseTokenOneDigit = /\d/, // 0 - 9
  22046. parseTokenTwoDigits = /\d\d/, // 00 - 99
  22047. parseTokenThreeDigits = /\d{3}/, // 000 - 999
  22048. parseTokenFourDigits = /\d{4}/, // 0000 - 9999
  22049. parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999
  22050. parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf
  22051. // iso 8601 regex
  22052. // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
  22053. 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)?)?$/,
  22054. isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
  22055. isoDates = [
  22056. ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/],
  22057. ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/],
  22058. ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/],
  22059. ['GGGG-[W]WW', /\d{4}-W\d{2}/],
  22060. ['YYYY-DDD', /\d{4}-\d{3}/]
  22061. ],
  22062. // iso time formats and regexes
  22063. isoTimes = [
  22064. ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/],
  22065. ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
  22066. ['HH:mm', /(T| )\d\d:\d\d/],
  22067. ['HH', /(T| )\d\d/]
  22068. ],
  22069. // timezone chunker '+10:00' > ['10', '00'] or '-1530' > ['-', '15', '30']
  22070. parseTimezoneChunker = /([\+\-]|\d\d)/gi,
  22071. // getter and setter names
  22072. proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
  22073. unitMillisecondFactors = {
  22074. 'Milliseconds' : 1,
  22075. 'Seconds' : 1e3,
  22076. 'Minutes' : 6e4,
  22077. 'Hours' : 36e5,
  22078. 'Days' : 864e5,
  22079. 'Months' : 2592e6,
  22080. 'Years' : 31536e6
  22081. },
  22082. unitAliases = {
  22083. ms : 'millisecond',
  22084. s : 'second',
  22085. m : 'minute',
  22086. h : 'hour',
  22087. d : 'day',
  22088. D : 'date',
  22089. w : 'week',
  22090. W : 'isoWeek',
  22091. M : 'month',
  22092. Q : 'quarter',
  22093. y : 'year',
  22094. DDD : 'dayOfYear',
  22095. e : 'weekday',
  22096. E : 'isoWeekday',
  22097. gg: 'weekYear',
  22098. GG: 'isoWeekYear'
  22099. },
  22100. camelFunctions = {
  22101. dayofyear : 'dayOfYear',
  22102. isoweekday : 'isoWeekday',
  22103. isoweek : 'isoWeek',
  22104. weekyear : 'weekYear',
  22105. isoweekyear : 'isoWeekYear'
  22106. },
  22107. // format function strings
  22108. formatFunctions = {},
  22109. // default relative time thresholds
  22110. relativeTimeThresholds = {
  22111. s: 45, // seconds to minute
  22112. m: 45, // minutes to hour
  22113. h: 22, // hours to day
  22114. d: 26, // days to month
  22115. M: 11 // months to year
  22116. },
  22117. // tokens to ordinalize and pad
  22118. ordinalizeTokens = 'DDD w W M D d'.split(' '),
  22119. paddedTokens = 'M D H h m s w W'.split(' '),
  22120. formatTokenFunctions = {
  22121. M : function () {
  22122. return this.month() + 1;
  22123. },
  22124. MMM : function (format) {
  22125. return this.localeData().monthsShort(this, format);
  22126. },
  22127. MMMM : function (format) {
  22128. return this.localeData().months(this, format);
  22129. },
  22130. D : function () {
  22131. return this.date();
  22132. },
  22133. DDD : function () {
  22134. return this.dayOfYear();
  22135. },
  22136. d : function () {
  22137. return this.day();
  22138. },
  22139. dd : function (format) {
  22140. return this.localeData().weekdaysMin(this, format);
  22141. },
  22142. ddd : function (format) {
  22143. return this.localeData().weekdaysShort(this, format);
  22144. },
  22145. dddd : function (format) {
  22146. return this.localeData().weekdays(this, format);
  22147. },
  22148. w : function () {
  22149. return this.week();
  22150. },
  22151. W : function () {
  22152. return this.isoWeek();
  22153. },
  22154. YY : function () {
  22155. return leftZeroFill(this.year() % 100, 2);
  22156. },
  22157. YYYY : function () {
  22158. return leftZeroFill(this.year(), 4);
  22159. },
  22160. YYYYY : function () {
  22161. return leftZeroFill(this.year(), 5);
  22162. },
  22163. YYYYYY : function () {
  22164. var y = this.year(), sign = y >= 0 ? '+' : '-';
  22165. return sign + leftZeroFill(Math.abs(y), 6);
  22166. },
  22167. gg : function () {
  22168. return leftZeroFill(this.weekYear() % 100, 2);
  22169. },
  22170. gggg : function () {
  22171. return leftZeroFill(this.weekYear(), 4);
  22172. },
  22173. ggggg : function () {
  22174. return leftZeroFill(this.weekYear(), 5);
  22175. },
  22176. GG : function () {
  22177. return leftZeroFill(this.isoWeekYear() % 100, 2);
  22178. },
  22179. GGGG : function () {
  22180. return leftZeroFill(this.isoWeekYear(), 4);
  22181. },
  22182. GGGGG : function () {
  22183. return leftZeroFill(this.isoWeekYear(), 5);
  22184. },
  22185. e : function () {
  22186. return this.weekday();
  22187. },
  22188. E : function () {
  22189. return this.isoWeekday();
  22190. },
  22191. a : function () {
  22192. return this.localeData().meridiem(this.hours(), this.minutes(), true);
  22193. },
  22194. A : function () {
  22195. return this.localeData().meridiem(this.hours(), this.minutes(), false);
  22196. },
  22197. H : function () {
  22198. return this.hours();
  22199. },
  22200. h : function () {
  22201. return this.hours() % 12 || 12;
  22202. },
  22203. m : function () {
  22204. return this.minutes();
  22205. },
  22206. s : function () {
  22207. return this.seconds();
  22208. },
  22209. S : function () {
  22210. return toInt(this.milliseconds() / 100);
  22211. },
  22212. SS : function () {
  22213. return leftZeroFill(toInt(this.milliseconds() / 10), 2);
  22214. },
  22215. SSS : function () {
  22216. return leftZeroFill(this.milliseconds(), 3);
  22217. },
  22218. SSSS : function () {
  22219. return leftZeroFill(this.milliseconds(), 3);
  22220. },
  22221. Z : function () {
  22222. var a = this.utcOffset(),
  22223. b = '+';
  22224. if (a < 0) {
  22225. a = -a;
  22226. b = '-';
  22227. }
  22228. return b + leftZeroFill(toInt(a / 60), 2) + ':' + leftZeroFill(toInt(a) % 60, 2);
  22229. },
  22230. ZZ : function () {
  22231. var a = this.utcOffset(),
  22232. b = '+';
  22233. if (a < 0) {
  22234. a = -a;
  22235. b = '-';
  22236. }
  22237. return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2);
  22238. },
  22239. z : function () {
  22240. return this.zoneAbbr();
  22241. },
  22242. zz : function () {
  22243. return this.zoneName();
  22244. },
  22245. x : function () {
  22246. return this.valueOf();
  22247. },
  22248. X : function () {
  22249. return this.unix();
  22250. },
  22251. Q : function () {
  22252. return this.quarter();
  22253. }
  22254. },
  22255. deprecations = {},
  22256. lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'],
  22257. updateInProgress = false;
  22258. // Pick the first defined of two or three arguments. dfl comes from
  22259. // default.
  22260. function dfl(a, b, c) {
  22261. switch (arguments.length) {
  22262. case 2: return a != null ? a : b;
  22263. case 3: return a != null ? a : b != null ? b : c;
  22264. default: throw new Error('Implement me');
  22265. }
  22266. }
  22267. function hasOwnProp(a, b) {
  22268. return hasOwnProperty.call(a, b);
  22269. }
  22270. function defaultParsingFlags() {
  22271. // We need to deep clone this object, and es5 standard is not very
  22272. // helpful.
  22273. return {
  22274. empty : false,
  22275. unusedTokens : [],
  22276. unusedInput : [],
  22277. overflow : -2,
  22278. charsLeftOver : 0,
  22279. nullInput : false,
  22280. invalidMonth : null,
  22281. invalidFormat : false,
  22282. userInvalidated : false,
  22283. iso: false
  22284. };
  22285. }
  22286. function printMsg(msg) {
  22287. if (moment.suppressDeprecationWarnings === false &&
  22288. typeof console !== 'undefined' && console.warn) {
  22289. console.warn('Deprecation warning: ' + msg);
  22290. }
  22291. }
  22292. function deprecate(msg, fn) {
  22293. var firstTime = true;
  22294. return extend(function () {
  22295. if (firstTime) {
  22296. printMsg(msg);
  22297. firstTime = false;
  22298. }
  22299. return fn.apply(this, arguments);
  22300. }, fn);
  22301. }
  22302. function deprecateSimple(name, msg) {
  22303. if (!deprecations[name]) {
  22304. printMsg(msg);
  22305. deprecations[name] = true;
  22306. }
  22307. }
  22308. function padToken(func, count) {
  22309. return function (a) {
  22310. return leftZeroFill(func.call(this, a), count);
  22311. };
  22312. }
  22313. function ordinalizeToken(func, period) {
  22314. return function (a) {
  22315. return this.localeData().ordinal(func.call(this, a), period);
  22316. };
  22317. }
  22318. function monthDiff(a, b) {
  22319. // difference in months
  22320. var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
  22321. // b is in (anchor - 1 month, anchor + 1 month)
  22322. anchor = a.clone().add(wholeMonthDiff, 'months'),
  22323. anchor2, adjust;
  22324. if (b - anchor < 0) {
  22325. anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
  22326. // linear across the month
  22327. adjust = (b - anchor) / (anchor - anchor2);
  22328. } else {
  22329. anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
  22330. // linear across the month
  22331. adjust = (b - anchor) / (anchor2 - anchor);
  22332. }
  22333. return -(wholeMonthDiff + adjust);
  22334. }
  22335. while (ordinalizeTokens.length) {
  22336. i = ordinalizeTokens.pop();
  22337. formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i);
  22338. }
  22339. while (paddedTokens.length) {
  22340. i = paddedTokens.pop();
  22341. formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2);
  22342. }
  22343. formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3);
  22344. function meridiemFixWrap(locale, hour, meridiem) {
  22345. var isPm;
  22346. if (meridiem == null) {
  22347. // nothing to do
  22348. return hour;
  22349. }
  22350. if (locale.meridiemHour != null) {
  22351. return locale.meridiemHour(hour, meridiem);
  22352. } else if (locale.isPM != null) {
  22353. // Fallback
  22354. isPm = locale.isPM(meridiem);
  22355. if (isPm && hour < 12) {
  22356. hour += 12;
  22357. }
  22358. if (!isPm && hour === 12) {
  22359. hour = 0;
  22360. }
  22361. return hour;
  22362. } else {
  22363. // thie is not supposed to happen
  22364. return hour;
  22365. }
  22366. }
  22367. /************************************
  22368. Constructors
  22369. ************************************/
  22370. function Locale() {
  22371. }
  22372. // Moment prototype object
  22373. function Moment(config, skipOverflow) {
  22374. if (skipOverflow !== false) {
  22375. checkOverflow(config);
  22376. }
  22377. copyConfig(this, config);
  22378. this._d = new Date(+config._d);
  22379. // Prevent infinite loop in case updateOffset creates new moment
  22380. // objects.
  22381. if (updateInProgress === false) {
  22382. updateInProgress = true;
  22383. moment.updateOffset(this);
  22384. updateInProgress = false;
  22385. }
  22386. }
  22387. // Duration Constructor
  22388. function Duration(duration) {
  22389. var normalizedInput = normalizeObjectUnits(duration),
  22390. years = normalizedInput.year || 0,
  22391. quarters = normalizedInput.quarter || 0,
  22392. months = normalizedInput.month || 0,
  22393. weeks = normalizedInput.week || 0,
  22394. days = normalizedInput.day || 0,
  22395. hours = normalizedInput.hour || 0,
  22396. minutes = normalizedInput.minute || 0,
  22397. seconds = normalizedInput.second || 0,
  22398. milliseconds = normalizedInput.millisecond || 0;
  22399. // representation for dateAddRemove
  22400. this._milliseconds = +milliseconds +
  22401. seconds * 1e3 + // 1000
  22402. minutes * 6e4 + // 1000 * 60
  22403. hours * 36e5; // 1000 * 60 * 60
  22404. // Because of dateAddRemove treats 24 hours as different from a
  22405. // day when working around DST, we need to store them separately
  22406. this._days = +days +
  22407. weeks * 7;
  22408. // It is impossible translate months into days without knowing
  22409. // which months you are are talking about, so we have to store
  22410. // it separately.
  22411. this._months = +months +
  22412. quarters * 3 +
  22413. years * 12;
  22414. this._data = {};
  22415. this._locale = moment.localeData();
  22416. this._bubble();
  22417. }
  22418. /************************************
  22419. Helpers
  22420. ************************************/
  22421. function extend(a, b) {
  22422. for (var i in b) {
  22423. if (hasOwnProp(b, i)) {
  22424. a[i] = b[i];
  22425. }
  22426. }
  22427. if (hasOwnProp(b, 'toString')) {
  22428. a.toString = b.toString;
  22429. }
  22430. if (hasOwnProp(b, 'valueOf')) {
  22431. a.valueOf = b.valueOf;
  22432. }
  22433. return a;
  22434. }
  22435. function copyConfig(to, from) {
  22436. var i, prop, val;
  22437. if (typeof from._isAMomentObject !== 'undefined') {
  22438. to._isAMomentObject = from._isAMomentObject;
  22439. }
  22440. if (typeof from._i !== 'undefined') {
  22441. to._i = from._i;
  22442. }
  22443. if (typeof from._f !== 'undefined') {
  22444. to._f = from._f;
  22445. }
  22446. if (typeof from._l !== 'undefined') {
  22447. to._l = from._l;
  22448. }
  22449. if (typeof from._strict !== 'undefined') {
  22450. to._strict = from._strict;
  22451. }
  22452. if (typeof from._tzm !== 'undefined') {
  22453. to._tzm = from._tzm;
  22454. }
  22455. if (typeof from._isUTC !== 'undefined') {
  22456. to._isUTC = from._isUTC;
  22457. }
  22458. if (typeof from._offset !== 'undefined') {
  22459. to._offset = from._offset;
  22460. }
  22461. if (typeof from._pf !== 'undefined') {
  22462. to._pf = from._pf;
  22463. }
  22464. if (typeof from._locale !== 'undefined') {
  22465. to._locale = from._locale;
  22466. }
  22467. if (momentProperties.length > 0) {
  22468. for (i in momentProperties) {
  22469. prop = momentProperties[i];
  22470. val = from[prop];
  22471. if (typeof val !== 'undefined') {
  22472. to[prop] = val;
  22473. }
  22474. }
  22475. }
  22476. return to;
  22477. }
  22478. function absRound(number) {
  22479. if (number < 0) {
  22480. return Math.ceil(number);
  22481. } else {
  22482. return Math.floor(number);
  22483. }
  22484. }
  22485. // left zero fill a number
  22486. // see http://jsperf.com/left-zero-filling for performance comparison
  22487. function leftZeroFill(number, targetLength, forceSign) {
  22488. var output = '' + Math.abs(number),
  22489. sign = number >= 0;
  22490. while (output.length < targetLength) {
  22491. output = '0' + output;
  22492. }
  22493. return (sign ? (forceSign ? '+' : '') : '-') + output;
  22494. }
  22495. function positiveMomentsDifference(base, other) {
  22496. var res = {milliseconds: 0, months: 0};
  22497. res.months = other.month() - base.month() +
  22498. (other.year() - base.year()) * 12;
  22499. if (base.clone().add(res.months, 'M').isAfter(other)) {
  22500. --res.months;
  22501. }
  22502. res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
  22503. return res;
  22504. }
  22505. function momentsDifference(base, other) {
  22506. var res;
  22507. other = makeAs(other, base);
  22508. if (base.isBefore(other)) {
  22509. res = positiveMomentsDifference(base, other);
  22510. } else {
  22511. res = positiveMomentsDifference(other, base);
  22512. res.milliseconds = -res.milliseconds;
  22513. res.months = -res.months;
  22514. }
  22515. return res;
  22516. }
  22517. // TODO: remove 'name' arg after deprecation is removed
  22518. function createAdder(direction, name) {
  22519. return function (val, period) {
  22520. var dur, tmp;
  22521. //invert the arguments, but complain about it
  22522. if (period !== null && !isNaN(+period)) {
  22523. deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).');
  22524. tmp = val; val = period; period = tmp;
  22525. }
  22526. val = typeof val === 'string' ? +val : val;
  22527. dur = moment.duration(val, period);
  22528. addOrSubtractDurationFromMoment(this, dur, direction);
  22529. return this;
  22530. };
  22531. }
  22532. function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) {
  22533. var milliseconds = duration._milliseconds,
  22534. days = duration._days,
  22535. months = duration._months;
  22536. updateOffset = updateOffset == null ? true : updateOffset;
  22537. if (milliseconds) {
  22538. mom._d.setTime(+mom._d + milliseconds * isAdding);
  22539. }
  22540. if (days) {
  22541. rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding);
  22542. }
  22543. if (months) {
  22544. rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding);
  22545. }
  22546. if (updateOffset) {
  22547. moment.updateOffset(mom, days || months);
  22548. }
  22549. }
  22550. // check if is an array
  22551. function isArray(input) {
  22552. return Object.prototype.toString.call(input) === '[object Array]';
  22553. }
  22554. function isDate(input) {
  22555. return Object.prototype.toString.call(input) === '[object Date]' ||
  22556. input instanceof Date;
  22557. }
  22558. // compare two arrays, return the number of differences
  22559. function compareArrays(array1, array2, dontConvert) {
  22560. var len = Math.min(array1.length, array2.length),
  22561. lengthDiff = Math.abs(array1.length - array2.length),
  22562. diffs = 0,
  22563. i;
  22564. for (i = 0; i < len; i++) {
  22565. if ((dontConvert && array1[i] !== array2[i]) ||
  22566. (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
  22567. diffs++;
  22568. }
  22569. }
  22570. return diffs + lengthDiff;
  22571. }
  22572. function normalizeUnits(units) {
  22573. if (units) {
  22574. var lowered = units.toLowerCase().replace(/(.)s$/, '$1');
  22575. units = unitAliases[units] || camelFunctions[lowered] || lowered;
  22576. }
  22577. return units;
  22578. }
  22579. function normalizeObjectUnits(inputObject) {
  22580. var normalizedInput = {},
  22581. normalizedProp,
  22582. prop;
  22583. for (prop in inputObject) {
  22584. if (hasOwnProp(inputObject, prop)) {
  22585. normalizedProp = normalizeUnits(prop);
  22586. if (normalizedProp) {
  22587. normalizedInput[normalizedProp] = inputObject[prop];
  22588. }
  22589. }
  22590. }
  22591. return normalizedInput;
  22592. }
  22593. function makeList(field) {
  22594. var count, setter;
  22595. if (field.indexOf('week') === 0) {
  22596. count = 7;
  22597. setter = 'day';
  22598. }
  22599. else if (field.indexOf('month') === 0) {
  22600. count = 12;
  22601. setter = 'month';
  22602. }
  22603. else {
  22604. return;
  22605. }
  22606. moment[field] = function (format, index) {
  22607. var i, getter,
  22608. method = moment._locale[field],
  22609. results = [];
  22610. if (typeof format === 'number') {
  22611. index = format;
  22612. format = undefined;
  22613. }
  22614. getter = function (i) {
  22615. var m = moment().utc().set(setter, i);
  22616. return method.call(moment._locale, m, format || '');
  22617. };
  22618. if (index != null) {
  22619. return getter(index);
  22620. }
  22621. else {
  22622. for (i = 0; i < count; i++) {
  22623. results.push(getter(i));
  22624. }
  22625. return results;
  22626. }
  22627. };
  22628. }
  22629. function toInt(argumentForCoercion) {
  22630. var coercedNumber = +argumentForCoercion,
  22631. value = 0;
  22632. if (coercedNumber !== 0 && isFinite(coercedNumber)) {
  22633. if (coercedNumber >= 0) {
  22634. value = Math.floor(coercedNumber);
  22635. } else {
  22636. value = Math.ceil(coercedNumber);
  22637. }
  22638. }
  22639. return value;
  22640. }
  22641. function daysInMonth(year, month) {
  22642. return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
  22643. }
  22644. function weeksInYear(year, dow, doy) {
  22645. return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week;
  22646. }
  22647. function daysInYear(year) {
  22648. return isLeapYear(year) ? 366 : 365;
  22649. }
  22650. function isLeapYear(year) {
  22651. return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
  22652. }
  22653. function checkOverflow(m) {
  22654. var overflow;
  22655. if (m._a && m._pf.overflow === -2) {
  22656. overflow =
  22657. m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH :
  22658. m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE :
  22659. m._a[HOUR] < 0 || m._a[HOUR] > 24 ||
  22660. (m._a[HOUR] === 24 && (m._a[MINUTE] !== 0 ||
  22661. m._a[SECOND] !== 0 ||
  22662. m._a[MILLISECOND] !== 0)) ? HOUR :
  22663. m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE :
  22664. m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND :
  22665. m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND :
  22666. -1;
  22667. if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
  22668. overflow = DATE;
  22669. }
  22670. m._pf.overflow = overflow;
  22671. }
  22672. }
  22673. function isValid(m) {
  22674. if (m._isValid == null) {
  22675. m._isValid = !isNaN(m._d.getTime()) &&
  22676. m._pf.overflow < 0 &&
  22677. !m._pf.empty &&
  22678. !m._pf.invalidMonth &&
  22679. !m._pf.nullInput &&
  22680. !m._pf.invalidFormat &&
  22681. !m._pf.userInvalidated;
  22682. if (m._strict) {
  22683. m._isValid = m._isValid &&
  22684. m._pf.charsLeftOver === 0 &&
  22685. m._pf.unusedTokens.length === 0 &&
  22686. m._pf.bigHour === undefined;
  22687. }
  22688. }
  22689. return m._isValid;
  22690. }
  22691. function normalizeLocale(key) {
  22692. return key ? key.toLowerCase().replace('_', '-') : key;
  22693. }
  22694. // pick the locale from the array
  22695. // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
  22696. // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
  22697. function chooseLocale(names) {
  22698. var i = 0, j, next, locale, split;
  22699. while (i < names.length) {
  22700. split = normalizeLocale(names[i]).split('-');
  22701. j = split.length;
  22702. next = normalizeLocale(names[i + 1]);
  22703. next = next ? next.split('-') : null;
  22704. while (j > 0) {
  22705. locale = loadLocale(split.slice(0, j).join('-'));
  22706. if (locale) {
  22707. return locale;
  22708. }
  22709. if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
  22710. //the next array item is better than a shallower substring of this one
  22711. break;
  22712. }
  22713. j--;
  22714. }
  22715. i++;
  22716. }
  22717. return null;
  22718. }
  22719. function loadLocale(name) {
  22720. var oldLocale = null;
  22721. if (!locales[name] && hasModule) {
  22722. try {
  22723. oldLocale = moment.locale();
  22724. !(function webpackMissingModule() { var e = new Error("Cannot find module \"./locale\""); e.code = 'MODULE_NOT_FOUND'; throw e; }());
  22725. // because defineLocale currently also sets the global locale, we want to undo that for lazy loaded locales
  22726. moment.locale(oldLocale);
  22727. } catch (e) { }
  22728. }
  22729. return locales[name];
  22730. }
  22731. // Return a moment from input, that is local/utc/utcOffset equivalent to
  22732. // model.
  22733. function makeAs(input, model) {
  22734. var res, diff;
  22735. if (model._isUTC) {
  22736. res = model.clone();
  22737. diff = (moment.isMoment(input) || isDate(input) ?
  22738. +input : +moment(input)) - (+res);
  22739. // Use low-level api, because this fn is low-level api.
  22740. res._d.setTime(+res._d + diff);
  22741. moment.updateOffset(res, false);
  22742. return res;
  22743. } else {
  22744. return moment(input).local();
  22745. }
  22746. }
  22747. /************************************
  22748. Locale
  22749. ************************************/
  22750. extend(Locale.prototype, {
  22751. set : function (config) {
  22752. var prop, i;
  22753. for (i in config) {
  22754. prop = config[i];
  22755. if (typeof prop === 'function') {
  22756. this[i] = prop;
  22757. } else {
  22758. this['_' + i] = prop;
  22759. }
  22760. }
  22761. // Lenient ordinal parsing accepts just a number in addition to
  22762. // number + (possibly) stuff coming from _ordinalParseLenient.
  22763. this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + /\d{1,2}/.source);
  22764. },
  22765. _months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
  22766. months : function (m) {
  22767. return this._months[m.month()];
  22768. },
  22769. _monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
  22770. monthsShort : function (m) {
  22771. return this._monthsShort[m.month()];
  22772. },
  22773. monthsParse : function (monthName, format, strict) {
  22774. var i, mom, regex;
  22775. if (!this._monthsParse) {
  22776. this._monthsParse = [];
  22777. this._longMonthsParse = [];
  22778. this._shortMonthsParse = [];
  22779. }
  22780. for (i = 0; i < 12; i++) {
  22781. // make the regex if we don't have it already
  22782. mom = moment.utc([2000, i]);
  22783. if (strict && !this._longMonthsParse[i]) {
  22784. this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
  22785. this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
  22786. }
  22787. if (!strict && !this._monthsParse[i]) {
  22788. regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
  22789. this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
  22790. }
  22791. // test the regex
  22792. if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
  22793. return i;
  22794. } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
  22795. return i;
  22796. } else if (!strict && this._monthsParse[i].test(monthName)) {
  22797. return i;
  22798. }
  22799. }
  22800. },
  22801. _weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
  22802. weekdays : function (m) {
  22803. return this._weekdays[m.day()];
  22804. },
  22805. _weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
  22806. weekdaysShort : function (m) {
  22807. return this._weekdaysShort[m.day()];
  22808. },
  22809. _weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
  22810. weekdaysMin : function (m) {
  22811. return this._weekdaysMin[m.day()];
  22812. },
  22813. weekdaysParse : function (weekdayName) {
  22814. var i, mom, regex;
  22815. if (!this._weekdaysParse) {
  22816. this._weekdaysParse = [];
  22817. }
  22818. for (i = 0; i < 7; i++) {
  22819. // make the regex if we don't have it already
  22820. if (!this._weekdaysParse[i]) {
  22821. mom = moment([2000, 1]).day(i);
  22822. regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
  22823. this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
  22824. }
  22825. // test the regex
  22826. if (this._weekdaysParse[i].test(weekdayName)) {
  22827. return i;
  22828. }
  22829. }
  22830. },
  22831. _longDateFormat : {
  22832. LTS : 'h:mm:ss A',
  22833. LT : 'h:mm A',
  22834. L : 'MM/DD/YYYY',
  22835. LL : 'MMMM D, YYYY',
  22836. LLL : 'MMMM D, YYYY LT',
  22837. LLLL : 'dddd, MMMM D, YYYY LT'
  22838. },
  22839. longDateFormat : function (key) {
  22840. var output = this._longDateFormat[key];
  22841. if (!output && this._longDateFormat[key.toUpperCase()]) {
  22842. output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
  22843. return val.slice(1);
  22844. });
  22845. this._longDateFormat[key] = output;
  22846. }
  22847. return output;
  22848. },
  22849. isPM : function (input) {
  22850. // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
  22851. // Using charAt should be more compatible.
  22852. return ((input + '').toLowerCase().charAt(0) === 'p');
  22853. },
  22854. _meridiemParse : /[ap]\.?m?\.?/i,
  22855. meridiem : function (hours, minutes, isLower) {
  22856. if (hours > 11) {
  22857. return isLower ? 'pm' : 'PM';
  22858. } else {
  22859. return isLower ? 'am' : 'AM';
  22860. }
  22861. },
  22862. _calendar : {
  22863. sameDay : '[Today at] LT',
  22864. nextDay : '[Tomorrow at] LT',
  22865. nextWeek : 'dddd [at] LT',
  22866. lastDay : '[Yesterday at] LT',
  22867. lastWeek : '[Last] dddd [at] LT',
  22868. sameElse : 'L'
  22869. },
  22870. calendar : function (key, mom, now) {
  22871. var output = this._calendar[key];
  22872. return typeof output === 'function' ? output.apply(mom, [now]) : output;
  22873. },
  22874. _relativeTime : {
  22875. future : 'in %s',
  22876. past : '%s ago',
  22877. s : 'a few seconds',
  22878. m : 'a minute',
  22879. mm : '%d minutes',
  22880. h : 'an hour',
  22881. hh : '%d hours',
  22882. d : 'a day',
  22883. dd : '%d days',
  22884. M : 'a month',
  22885. MM : '%d months',
  22886. y : 'a year',
  22887. yy : '%d years'
  22888. },
  22889. relativeTime : function (number, withoutSuffix, string, isFuture) {
  22890. var output = this._relativeTime[string];
  22891. return (typeof output === 'function') ?
  22892. output(number, withoutSuffix, string, isFuture) :
  22893. output.replace(/%d/i, number);
  22894. },
  22895. pastFuture : function (diff, output) {
  22896. var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
  22897. return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
  22898. },
  22899. ordinal : function (number) {
  22900. return this._ordinal.replace('%d', number);
  22901. },
  22902. _ordinal : '%d',
  22903. _ordinalParse : /\d{1,2}/,
  22904. preparse : function (string) {
  22905. return string;
  22906. },
  22907. postformat : function (string) {
  22908. return string;
  22909. },
  22910. week : function (mom) {
  22911. return weekOfYear(mom, this._week.dow, this._week.doy).week;
  22912. },
  22913. _week : {
  22914. dow : 0, // Sunday is the first day of the week.
  22915. doy : 6 // The week that contains Jan 1st is the first week of the year.
  22916. },
  22917. firstDayOfWeek : function () {
  22918. return this._week.dow;
  22919. },
  22920. firstDayOfYear : function () {
  22921. return this._week.doy;
  22922. },
  22923. _invalidDate: 'Invalid date',
  22924. invalidDate: function () {
  22925. return this._invalidDate;
  22926. }
  22927. });
  22928. /************************************
  22929. Formatting
  22930. ************************************/
  22931. function removeFormattingTokens(input) {
  22932. if (input.match(/\[[\s\S]/)) {
  22933. return input.replace(/^\[|\]$/g, '');
  22934. }
  22935. return input.replace(/\\/g, '');
  22936. }
  22937. function makeFormatFunction(format) {
  22938. var array = format.match(formattingTokens), i, length;
  22939. for (i = 0, length = array.length; i < length; i++) {
  22940. if (formatTokenFunctions[array[i]]) {
  22941. array[i] = formatTokenFunctions[array[i]];
  22942. } else {
  22943. array[i] = removeFormattingTokens(array[i]);
  22944. }
  22945. }
  22946. return function (mom) {
  22947. var output = '';
  22948. for (i = 0; i < length; i++) {
  22949. output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
  22950. }
  22951. return output;
  22952. };
  22953. }
  22954. // format date using native date object
  22955. function formatMoment(m, format) {
  22956. if (!m.isValid()) {
  22957. return m.localeData().invalidDate();
  22958. }
  22959. format = expandFormat(format, m.localeData());
  22960. if (!formatFunctions[format]) {
  22961. formatFunctions[format] = makeFormatFunction(format);
  22962. }
  22963. return formatFunctions[format](m);
  22964. }
  22965. function expandFormat(format, locale) {
  22966. var i = 5;
  22967. function replaceLongDateFormatTokens(input) {
  22968. return locale.longDateFormat(input) || input;
  22969. }
  22970. localFormattingTokens.lastIndex = 0;
  22971. while (i >= 0 && localFormattingTokens.test(format)) {
  22972. format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
  22973. localFormattingTokens.lastIndex = 0;
  22974. i -= 1;
  22975. }
  22976. return format;
  22977. }
  22978. /************************************
  22979. Parsing
  22980. ************************************/
  22981. // get the regex to find the next token
  22982. function getParseRegexForToken(token, config) {
  22983. var a, strict = config._strict;
  22984. switch (token) {
  22985. case 'Q':
  22986. return parseTokenOneDigit;
  22987. case 'DDDD':
  22988. return parseTokenThreeDigits;
  22989. case 'YYYY':
  22990. case 'GGGG':
  22991. case 'gggg':
  22992. return strict ? parseTokenFourDigits : parseTokenOneToFourDigits;
  22993. case 'Y':
  22994. case 'G':
  22995. case 'g':
  22996. return parseTokenSignedNumber;
  22997. case 'YYYYYY':
  22998. case 'YYYYY':
  22999. case 'GGGGG':
  23000. case 'ggggg':
  23001. return strict ? parseTokenSixDigits : parseTokenOneToSixDigits;
  23002. case 'S':
  23003. if (strict) {
  23004. return parseTokenOneDigit;
  23005. }
  23006. /* falls through */
  23007. case 'SS':
  23008. if (strict) {
  23009. return parseTokenTwoDigits;
  23010. }
  23011. /* falls through */
  23012. case 'SSS':
  23013. if (strict) {
  23014. return parseTokenThreeDigits;
  23015. }
  23016. /* falls through */
  23017. case 'DDD':
  23018. return parseTokenOneToThreeDigits;
  23019. case 'MMM':
  23020. case 'MMMM':
  23021. case 'dd':
  23022. case 'ddd':
  23023. case 'dddd':
  23024. return parseTokenWord;
  23025. case 'a':
  23026. case 'A':
  23027. return config._locale._meridiemParse;
  23028. case 'x':
  23029. return parseTokenOffsetMs;
  23030. case 'X':
  23031. return parseTokenTimestampMs;
  23032. case 'Z':
  23033. case 'ZZ':
  23034. return parseTokenTimezone;
  23035. case 'T':
  23036. return parseTokenT;
  23037. case 'SSSS':
  23038. return parseTokenDigits;
  23039. case 'MM':
  23040. case 'DD':
  23041. case 'YY':
  23042. case 'GG':
  23043. case 'gg':
  23044. case 'HH':
  23045. case 'hh':
  23046. case 'mm':
  23047. case 'ss':
  23048. case 'ww':
  23049. case 'WW':
  23050. return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits;
  23051. case 'M':
  23052. case 'D':
  23053. case 'd':
  23054. case 'H':
  23055. case 'h':
  23056. case 'm':
  23057. case 's':
  23058. case 'w':
  23059. case 'W':
  23060. case 'e':
  23061. case 'E':
  23062. return parseTokenOneOrTwoDigits;
  23063. case 'Do':
  23064. return strict ? config._locale._ordinalParse : config._locale._ordinalParseLenient;
  23065. default :
  23066. a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), 'i'));
  23067. return a;
  23068. }
  23069. }
  23070. function utcOffsetFromString(string) {
  23071. string = string || '';
  23072. var possibleTzMatches = (string.match(parseTokenTimezone) || []),
  23073. tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [],
  23074. parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
  23075. minutes = +(parts[1] * 60) + toInt(parts[2]);
  23076. return parts[0] === '+' ? minutes : -minutes;
  23077. }
  23078. // function to convert string input to date
  23079. function addTimeToArrayFromToken(token, input, config) {
  23080. var a, datePartArray = config._a;
  23081. switch (token) {
  23082. // QUARTER
  23083. case 'Q':
  23084. if (input != null) {
  23085. datePartArray[MONTH] = (toInt(input) - 1) * 3;
  23086. }
  23087. break;
  23088. // MONTH
  23089. case 'M' : // fall through to MM
  23090. case 'MM' :
  23091. if (input != null) {
  23092. datePartArray[MONTH] = toInt(input) - 1;
  23093. }
  23094. break;
  23095. case 'MMM' : // fall through to MMMM
  23096. case 'MMMM' :
  23097. a = config._locale.monthsParse(input, token, config._strict);
  23098. // if we didn't find a month name, mark the date as invalid.
  23099. if (a != null) {
  23100. datePartArray[MONTH] = a;
  23101. } else {
  23102. config._pf.invalidMonth = input;
  23103. }
  23104. break;
  23105. // DAY OF MONTH
  23106. case 'D' : // fall through to DD
  23107. case 'DD' :
  23108. if (input != null) {
  23109. datePartArray[DATE] = toInt(input);
  23110. }
  23111. break;
  23112. case 'Do' :
  23113. if (input != null) {
  23114. datePartArray[DATE] = toInt(parseInt(
  23115. input.match(/\d{1,2}/)[0], 10));
  23116. }
  23117. break;
  23118. // DAY OF YEAR
  23119. case 'DDD' : // fall through to DDDD
  23120. case 'DDDD' :
  23121. if (input != null) {
  23122. config._dayOfYear = toInt(input);
  23123. }
  23124. break;
  23125. // YEAR
  23126. case 'YY' :
  23127. datePartArray[YEAR] = moment.parseTwoDigitYear(input);
  23128. break;
  23129. case 'YYYY' :
  23130. case 'YYYYY' :
  23131. case 'YYYYYY' :
  23132. datePartArray[YEAR] = toInt(input);
  23133. break;
  23134. // AM / PM
  23135. case 'a' : // fall through to A
  23136. case 'A' :
  23137. config._meridiem = input;
  23138. // config._isPm = config._locale.isPM(input);
  23139. break;
  23140. // HOUR
  23141. case 'h' : // fall through to hh
  23142. case 'hh' :
  23143. config._pf.bigHour = true;
  23144. /* falls through */
  23145. case 'H' : // fall through to HH
  23146. case 'HH' :
  23147. datePartArray[HOUR] = toInt(input);
  23148. break;
  23149. // MINUTE
  23150. case 'm' : // fall through to mm
  23151. case 'mm' :
  23152. datePartArray[MINUTE] = toInt(input);
  23153. break;
  23154. // SECOND
  23155. case 's' : // fall through to ss
  23156. case 'ss' :
  23157. datePartArray[SECOND] = toInt(input);
  23158. break;
  23159. // MILLISECOND
  23160. case 'S' :
  23161. case 'SS' :
  23162. case 'SSS' :
  23163. case 'SSSS' :
  23164. datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000);
  23165. break;
  23166. // UNIX OFFSET (MILLISECONDS)
  23167. case 'x':
  23168. config._d = new Date(toInt(input));
  23169. break;
  23170. // UNIX TIMESTAMP WITH MS
  23171. case 'X':
  23172. config._d = new Date(parseFloat(input) * 1000);
  23173. break;
  23174. // TIMEZONE
  23175. case 'Z' : // fall through to ZZ
  23176. case 'ZZ' :
  23177. config._useUTC = true;
  23178. config._tzm = utcOffsetFromString(input);
  23179. break;
  23180. // WEEKDAY - human
  23181. case 'dd':
  23182. case 'ddd':
  23183. case 'dddd':
  23184. a = config._locale.weekdaysParse(input);
  23185. // if we didn't get a weekday name, mark the date as invalid
  23186. if (a != null) {
  23187. config._w = config._w || {};
  23188. config._w['d'] = a;
  23189. } else {
  23190. config._pf.invalidWeekday = input;
  23191. }
  23192. break;
  23193. // WEEK, WEEK DAY - numeric
  23194. case 'w':
  23195. case 'ww':
  23196. case 'W':
  23197. case 'WW':
  23198. case 'd':
  23199. case 'e':
  23200. case 'E':
  23201. token = token.substr(0, 1);
  23202. /* falls through */
  23203. case 'gggg':
  23204. case 'GGGG':
  23205. case 'GGGGG':
  23206. token = token.substr(0, 2);
  23207. if (input) {
  23208. config._w = config._w || {};
  23209. config._w[token] = toInt(input);
  23210. }
  23211. break;
  23212. case 'gg':
  23213. case 'GG':
  23214. config._w = config._w || {};
  23215. config._w[token] = moment.parseTwoDigitYear(input);
  23216. }
  23217. }
  23218. function dayOfYearFromWeekInfo(config) {
  23219. var w, weekYear, week, weekday, dow, doy, temp;
  23220. w = config._w;
  23221. if (w.GG != null || w.W != null || w.E != null) {
  23222. dow = 1;
  23223. doy = 4;
  23224. // TODO: We need to take the current isoWeekYear, but that depends on
  23225. // how we interpret now (local, utc, fixed offset). So create
  23226. // a now version of current config (take local/utc/offset flags, and
  23227. // create now).
  23228. weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year);
  23229. week = dfl(w.W, 1);
  23230. weekday = dfl(w.E, 1);
  23231. } else {
  23232. dow = config._locale._week.dow;
  23233. doy = config._locale._week.doy;
  23234. weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year);
  23235. week = dfl(w.w, 1);
  23236. if (w.d != null) {
  23237. // weekday -- low day numbers are considered next week
  23238. weekday = w.d;
  23239. if (weekday < dow) {
  23240. ++week;
  23241. }
  23242. } else if (w.e != null) {
  23243. // local weekday -- counting starts from begining of week
  23244. weekday = w.e + dow;
  23245. } else {
  23246. // default to begining of week
  23247. weekday = dow;
  23248. }
  23249. }
  23250. temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow);
  23251. config._a[YEAR] = temp.year;
  23252. config._dayOfYear = temp.dayOfYear;
  23253. }
  23254. // convert an array to a date.
  23255. // the array should mirror the parameters below
  23256. // note: all values past the year are optional and will default to the lowest possible value.
  23257. // [year, month, day , hour, minute, second, millisecond]
  23258. function dateFromConfig(config) {
  23259. var i, date, input = [], currentDate, yearToUse;
  23260. if (config._d) {
  23261. return;
  23262. }
  23263. currentDate = currentDateArray(config);
  23264. //compute day of the year from weeks and weekdays
  23265. if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
  23266. dayOfYearFromWeekInfo(config);
  23267. }
  23268. //if the day of the year is set, figure out what it is
  23269. if (config._dayOfYear) {
  23270. yearToUse = dfl(config._a[YEAR], currentDate[YEAR]);
  23271. if (config._dayOfYear > daysInYear(yearToUse)) {
  23272. config._pf._overflowDayOfYear = true;
  23273. }
  23274. date = makeUTCDate(yearToUse, 0, config._dayOfYear);
  23275. config._a[MONTH] = date.getUTCMonth();
  23276. config._a[DATE] = date.getUTCDate();
  23277. }
  23278. // Default to current date.
  23279. // * if no year, month, day of month are given, default to today
  23280. // * if day of month is given, default month and year
  23281. // * if month is given, default only year
  23282. // * if year is given, don't default anything
  23283. for (i = 0; i < 3 && config._a[i] == null; ++i) {
  23284. config._a[i] = input[i] = currentDate[i];
  23285. }
  23286. // Zero out whatever was not defaulted, including time
  23287. for (; i < 7; i++) {
  23288. config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
  23289. }
  23290. // Check for 24:00:00.000
  23291. if (config._a[HOUR] === 24 &&
  23292. config._a[MINUTE] === 0 &&
  23293. config._a[SECOND] === 0 &&
  23294. config._a[MILLISECOND] === 0) {
  23295. config._nextDay = true;
  23296. config._a[HOUR] = 0;
  23297. }
  23298. config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input);
  23299. // Apply timezone offset from input. The actual utcOffset can be changed
  23300. // with parseZone.
  23301. if (config._tzm != null) {
  23302. config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
  23303. }
  23304. if (config._nextDay) {
  23305. config._a[HOUR] = 24;
  23306. }
  23307. }
  23308. function dateFromObject(config) {
  23309. var normalizedInput;
  23310. if (config._d) {
  23311. return;
  23312. }
  23313. normalizedInput = normalizeObjectUnits(config._i);
  23314. config._a = [
  23315. normalizedInput.year,
  23316. normalizedInput.month,
  23317. normalizedInput.day || normalizedInput.date,
  23318. normalizedInput.hour,
  23319. normalizedInput.minute,
  23320. normalizedInput.second,
  23321. normalizedInput.millisecond
  23322. ];
  23323. dateFromConfig(config);
  23324. }
  23325. function currentDateArray(config) {
  23326. var now = new Date();
  23327. if (config._useUTC) {
  23328. return [
  23329. now.getUTCFullYear(),
  23330. now.getUTCMonth(),
  23331. now.getUTCDate()
  23332. ];
  23333. } else {
  23334. return [now.getFullYear(), now.getMonth(), now.getDate()];
  23335. }
  23336. }
  23337. // date from string and format string
  23338. function makeDateFromStringAndFormat(config) {
  23339. if (config._f === moment.ISO_8601) {
  23340. parseISO(config);
  23341. return;
  23342. }
  23343. config._a = [];
  23344. config._pf.empty = true;
  23345. // This array is used to make a Date, either with `new Date` or `Date.UTC`
  23346. var string = '' + config._i,
  23347. i, parsedInput, tokens, token, skipped,
  23348. stringLength = string.length,
  23349. totalParsedInputLength = 0;
  23350. tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
  23351. for (i = 0; i < tokens.length; i++) {
  23352. token = tokens[i];
  23353. parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
  23354. if (parsedInput) {
  23355. skipped = string.substr(0, string.indexOf(parsedInput));
  23356. if (skipped.length > 0) {
  23357. config._pf.unusedInput.push(skipped);
  23358. }
  23359. string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
  23360. totalParsedInputLength += parsedInput.length;
  23361. }
  23362. // don't parse if it's not a known token
  23363. if (formatTokenFunctions[token]) {
  23364. if (parsedInput) {
  23365. config._pf.empty = false;
  23366. }
  23367. else {
  23368. config._pf.unusedTokens.push(token);
  23369. }
  23370. addTimeToArrayFromToken(token, parsedInput, config);
  23371. }
  23372. else if (config._strict && !parsedInput) {
  23373. config._pf.unusedTokens.push(token);
  23374. }
  23375. }
  23376. // add remaining unparsed input length to the string
  23377. config._pf.charsLeftOver = stringLength - totalParsedInputLength;
  23378. if (string.length > 0) {
  23379. config._pf.unusedInput.push(string);
  23380. }
  23381. // clear _12h flag if hour is <= 12
  23382. if (config._pf.bigHour === true && config._a[HOUR] <= 12) {
  23383. config._pf.bigHour = undefined;
  23384. }
  23385. // handle meridiem
  23386. config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR],
  23387. config._meridiem);
  23388. dateFromConfig(config);
  23389. checkOverflow(config);
  23390. }
  23391. function unescapeFormat(s) {
  23392. return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
  23393. return p1 || p2 || p3 || p4;
  23394. });
  23395. }
  23396. // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
  23397. function regexpEscape(s) {
  23398. return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
  23399. }
  23400. // date from string and array of format strings
  23401. function makeDateFromStringAndArray(config) {
  23402. var tempConfig,
  23403. bestMoment,
  23404. scoreToBeat,
  23405. i,
  23406. currentScore;
  23407. if (config._f.length === 0) {
  23408. config._pf.invalidFormat = true;
  23409. config._d = new Date(NaN);
  23410. return;
  23411. }
  23412. for (i = 0; i < config._f.length; i++) {
  23413. currentScore = 0;
  23414. tempConfig = copyConfig({}, config);
  23415. if (config._useUTC != null) {
  23416. tempConfig._useUTC = config._useUTC;
  23417. }
  23418. tempConfig._pf = defaultParsingFlags();
  23419. tempConfig._f = config._f[i];
  23420. makeDateFromStringAndFormat(tempConfig);
  23421. if (!isValid(tempConfig)) {
  23422. continue;
  23423. }
  23424. // if there is any input that was not parsed add a penalty for that format
  23425. currentScore += tempConfig._pf.charsLeftOver;
  23426. //or tokens
  23427. currentScore += tempConfig._pf.unusedTokens.length * 10;
  23428. tempConfig._pf.score = currentScore;
  23429. if (scoreToBeat == null || currentScore < scoreToBeat) {
  23430. scoreToBeat = currentScore;
  23431. bestMoment = tempConfig;
  23432. }
  23433. }
  23434. extend(config, bestMoment || tempConfig);
  23435. }
  23436. // date from iso format
  23437. function parseISO(config) {
  23438. var i, l,
  23439. string = config._i,
  23440. match = isoRegex.exec(string);
  23441. if (match) {
  23442. config._pf.iso = true;
  23443. for (i = 0, l = isoDates.length; i < l; i++) {
  23444. if (isoDates[i][1].exec(string)) {
  23445. // match[5] should be 'T' or undefined
  23446. config._f = isoDates[i][0] + (match[6] || ' ');
  23447. break;
  23448. }
  23449. }
  23450. for (i = 0, l = isoTimes.length; i < l; i++) {
  23451. if (isoTimes[i][1].exec(string)) {
  23452. config._f += isoTimes[i][0];
  23453. break;
  23454. }
  23455. }
  23456. if (string.match(parseTokenTimezone)) {
  23457. config._f += 'Z';
  23458. }
  23459. makeDateFromStringAndFormat(config);
  23460. } else {
  23461. config._isValid = false;
  23462. }
  23463. }
  23464. // date from iso format or fallback
  23465. function makeDateFromString(config) {
  23466. parseISO(config);
  23467. if (config._isValid === false) {
  23468. delete config._isValid;
  23469. moment.createFromInputFallback(config);
  23470. }
  23471. }
  23472. function map(arr, fn) {
  23473. var res = [], i;
  23474. for (i = 0; i < arr.length; ++i) {
  23475. res.push(fn(arr[i], i));
  23476. }
  23477. return res;
  23478. }
  23479. function makeDateFromInput(config) {
  23480. var input = config._i, matched;
  23481. if (input === undefined) {
  23482. config._d = new Date();
  23483. } else if (isDate(input)) {
  23484. config._d = new Date(+input);
  23485. } else if ((matched = aspNetJsonRegex.exec(input)) !== null) {
  23486. config._d = new Date(+matched[1]);
  23487. } else if (typeof input === 'string') {
  23488. makeDateFromString(config);
  23489. } else if (isArray(input)) {
  23490. config._a = map(input.slice(0), function (obj) {
  23491. return parseInt(obj, 10);
  23492. });
  23493. dateFromConfig(config);
  23494. } else if (typeof(input) === 'object') {
  23495. dateFromObject(config);
  23496. } else if (typeof(input) === 'number') {
  23497. // from milliseconds
  23498. config._d = new Date(input);
  23499. } else {
  23500. moment.createFromInputFallback(config);
  23501. }
  23502. }
  23503. function makeDate(y, m, d, h, M, s, ms) {
  23504. //can't just apply() to create a date:
  23505. //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
  23506. var date = new Date(y, m, d, h, M, s, ms);
  23507. //the date constructor doesn't accept years < 1970
  23508. if (y < 1970) {
  23509. date.setFullYear(y);
  23510. }
  23511. return date;
  23512. }
  23513. function makeUTCDate(y) {
  23514. var date = new Date(Date.UTC.apply(null, arguments));
  23515. if (y < 1970) {
  23516. date.setUTCFullYear(y);
  23517. }
  23518. return date;
  23519. }
  23520. function parseWeekday(input, locale) {
  23521. if (typeof input === 'string') {
  23522. if (!isNaN(input)) {
  23523. input = parseInt(input, 10);
  23524. }
  23525. else {
  23526. input = locale.weekdaysParse(input);
  23527. if (typeof input !== 'number') {
  23528. return null;
  23529. }
  23530. }
  23531. }
  23532. return input;
  23533. }
  23534. /************************************
  23535. Relative Time
  23536. ************************************/
  23537. // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
  23538. function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
  23539. return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
  23540. }
  23541. function relativeTime(posNegDuration, withoutSuffix, locale) {
  23542. var duration = moment.duration(posNegDuration).abs(),
  23543. seconds = round(duration.as('s')),
  23544. minutes = round(duration.as('m')),
  23545. hours = round(duration.as('h')),
  23546. days = round(duration.as('d')),
  23547. months = round(duration.as('M')),
  23548. years = round(duration.as('y')),
  23549. args = seconds < relativeTimeThresholds.s && ['s', seconds] ||
  23550. minutes === 1 && ['m'] ||
  23551. minutes < relativeTimeThresholds.m && ['mm', minutes] ||
  23552. hours === 1 && ['h'] ||
  23553. hours < relativeTimeThresholds.h && ['hh', hours] ||
  23554. days === 1 && ['d'] ||
  23555. days < relativeTimeThresholds.d && ['dd', days] ||
  23556. months === 1 && ['M'] ||
  23557. months < relativeTimeThresholds.M && ['MM', months] ||
  23558. years === 1 && ['y'] || ['yy', years];
  23559. args[2] = withoutSuffix;
  23560. args[3] = +posNegDuration > 0;
  23561. args[4] = locale;
  23562. return substituteTimeAgo.apply({}, args);
  23563. }
  23564. /************************************
  23565. Week of Year
  23566. ************************************/
  23567. // firstDayOfWeek 0 = sun, 6 = sat
  23568. // the day of the week that starts the week
  23569. // (usually sunday or monday)
  23570. // firstDayOfWeekOfYear 0 = sun, 6 = sat
  23571. // the first week is the week that contains the first
  23572. // of this day of the week
  23573. // (eg. ISO weeks use thursday (4))
  23574. function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
  23575. var end = firstDayOfWeekOfYear - firstDayOfWeek,
  23576. daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(),
  23577. adjustedMoment;
  23578. if (daysToDayOfWeek > end) {
  23579. daysToDayOfWeek -= 7;
  23580. }
  23581. if (daysToDayOfWeek < end - 7) {
  23582. daysToDayOfWeek += 7;
  23583. }
  23584. adjustedMoment = moment(mom).add(daysToDayOfWeek, 'd');
  23585. return {
  23586. week: Math.ceil(adjustedMoment.dayOfYear() / 7),
  23587. year: adjustedMoment.year()
  23588. };
  23589. }
  23590. //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
  23591. function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) {
  23592. var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear;
  23593. d = d === 0 ? 7 : d;
  23594. weekday = weekday != null ? weekday : firstDayOfWeek;
  23595. daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0);
  23596. dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1;
  23597. return {
  23598. year: dayOfYear > 0 ? year : year - 1,
  23599. dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear
  23600. };
  23601. }
  23602. /************************************
  23603. Top Level Functions
  23604. ************************************/
  23605. function makeMoment(config) {
  23606. var input = config._i,
  23607. format = config._f,
  23608. res;
  23609. config._locale = config._locale || moment.localeData(config._l);
  23610. if (input === null || (format === undefined && input === '')) {
  23611. return moment.invalid({nullInput: true});
  23612. }
  23613. if (typeof input === 'string') {
  23614. config._i = input = config._locale.preparse(input);
  23615. }
  23616. if (moment.isMoment(input)) {
  23617. return new Moment(input, true);
  23618. } else if (format) {
  23619. if (isArray(format)) {
  23620. makeDateFromStringAndArray(config);
  23621. } else {
  23622. makeDateFromStringAndFormat(config);
  23623. }
  23624. } else {
  23625. makeDateFromInput(config);
  23626. }
  23627. res = new Moment(config);
  23628. if (res._nextDay) {
  23629. // Adding is smart enough around DST
  23630. res.add(1, 'd');
  23631. res._nextDay = undefined;
  23632. }
  23633. return res;
  23634. }
  23635. moment = function (input, format, locale, strict) {
  23636. var c;
  23637. if (typeof(locale) === 'boolean') {
  23638. strict = locale;
  23639. locale = undefined;
  23640. }
  23641. // object construction must be done this way.
  23642. // https://github.com/moment/moment/issues/1423
  23643. c = {};
  23644. c._isAMomentObject = true;
  23645. c._i = input;
  23646. c._f = format;
  23647. c._l = locale;
  23648. c._strict = strict;
  23649. c._isUTC = false;
  23650. c._pf = defaultParsingFlags();
  23651. return makeMoment(c);
  23652. };
  23653. moment.suppressDeprecationWarnings = false;
  23654. moment.createFromInputFallback = deprecate(
  23655. 'moment construction falls back to js Date. This is ' +
  23656. 'discouraged and will be removed in upcoming major ' +
  23657. 'release. Please refer to ' +
  23658. 'https://github.com/moment/moment/issues/1407 for more info.',
  23659. function (config) {
  23660. config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
  23661. }
  23662. );
  23663. // Pick a moment m from moments so that m[fn](other) is true for all
  23664. // other. This relies on the function fn to be transitive.
  23665. //
  23666. // moments should either be an array of moment objects or an array, whose
  23667. // first element is an array of moment objects.
  23668. function pickBy(fn, moments) {
  23669. var res, i;
  23670. if (moments.length === 1 && isArray(moments[0])) {
  23671. moments = moments[0];
  23672. }
  23673. if (!moments.length) {
  23674. return moment();
  23675. }
  23676. res = moments[0];
  23677. for (i = 1; i < moments.length; ++i) {
  23678. if (moments[i][fn](res)) {
  23679. res = moments[i];
  23680. }
  23681. }
  23682. return res;
  23683. }
  23684. moment.min = function () {
  23685. var args = [].slice.call(arguments, 0);
  23686. return pickBy('isBefore', args);
  23687. };
  23688. moment.max = function () {
  23689. var args = [].slice.call(arguments, 0);
  23690. return pickBy('isAfter', args);
  23691. };
  23692. // creating with utc
  23693. moment.utc = function (input, format, locale, strict) {
  23694. var c;
  23695. if (typeof(locale) === 'boolean') {
  23696. strict = locale;
  23697. locale = undefined;
  23698. }
  23699. // object construction must be done this way.
  23700. // https://github.com/moment/moment/issues/1423
  23701. c = {};
  23702. c._isAMomentObject = true;
  23703. c._useUTC = true;
  23704. c._isUTC = true;
  23705. c._l = locale;
  23706. c._i = input;
  23707. c._f = format;
  23708. c._strict = strict;
  23709. c._pf = defaultParsingFlags();
  23710. return makeMoment(c).utc();
  23711. };
  23712. // creating with unix timestamp (in seconds)
  23713. moment.unix = function (input) {
  23714. return moment(input * 1000);
  23715. };
  23716. // duration
  23717. moment.duration = function (input, key) {
  23718. var duration = input,
  23719. // matching against regexp is expensive, do it on demand
  23720. match = null,
  23721. sign,
  23722. ret,
  23723. parseIso,
  23724. diffRes;
  23725. if (moment.isDuration(input)) {
  23726. duration = {
  23727. ms: input._milliseconds,
  23728. d: input._days,
  23729. M: input._months
  23730. };
  23731. } else if (typeof input === 'number') {
  23732. duration = {};
  23733. if (key) {
  23734. duration[key] = input;
  23735. } else {
  23736. duration.milliseconds = input;
  23737. }
  23738. } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) {
  23739. sign = (match[1] === '-') ? -1 : 1;
  23740. duration = {
  23741. y: 0,
  23742. d: toInt(match[DATE]) * sign,
  23743. h: toInt(match[HOUR]) * sign,
  23744. m: toInt(match[MINUTE]) * sign,
  23745. s: toInt(match[SECOND]) * sign,
  23746. ms: toInt(match[MILLISECOND]) * sign
  23747. };
  23748. } else if (!!(match = isoDurationRegex.exec(input))) {
  23749. sign = (match[1] === '-') ? -1 : 1;
  23750. parseIso = function (inp) {
  23751. // We'd normally use ~~inp for this, but unfortunately it also
  23752. // converts floats to ints.
  23753. // inp may be undefined, so careful calling replace on it.
  23754. var res = inp && parseFloat(inp.replace(',', '.'));
  23755. // apply sign while we're at it
  23756. return (isNaN(res) ? 0 : res) * sign;
  23757. };
  23758. duration = {
  23759. y: parseIso(match[2]),
  23760. M: parseIso(match[3]),
  23761. d: parseIso(match[4]),
  23762. h: parseIso(match[5]),
  23763. m: parseIso(match[6]),
  23764. s: parseIso(match[7]),
  23765. w: parseIso(match[8])
  23766. };
  23767. } else if (duration == null) {// checks for null or undefined
  23768. duration = {};
  23769. } else if (typeof duration === 'object' &&
  23770. ('from' in duration || 'to' in duration)) {
  23771. diffRes = momentsDifference(moment(duration.from), moment(duration.to));
  23772. duration = {};
  23773. duration.ms = diffRes.milliseconds;
  23774. duration.M = diffRes.months;
  23775. }
  23776. ret = new Duration(duration);
  23777. if (moment.isDuration(input) && hasOwnProp(input, '_locale')) {
  23778. ret._locale = input._locale;
  23779. }
  23780. return ret;
  23781. };
  23782. // version number
  23783. moment.version = VERSION;
  23784. // default format
  23785. moment.defaultFormat = isoFormat;
  23786. // constant that refers to the ISO standard
  23787. moment.ISO_8601 = function () {};
  23788. // Plugins that add properties should also add the key here (null value),
  23789. // so we can properly clone ourselves.
  23790. moment.momentProperties = momentProperties;
  23791. // This function will be called whenever a moment is mutated.
  23792. // It is intended to keep the offset in sync with the timezone.
  23793. moment.updateOffset = function () {};
  23794. // This function allows you to set a threshold for relative time strings
  23795. moment.relativeTimeThreshold = function (threshold, limit) {
  23796. if (relativeTimeThresholds[threshold] === undefined) {
  23797. return false;
  23798. }
  23799. if (limit === undefined) {
  23800. return relativeTimeThresholds[threshold];
  23801. }
  23802. relativeTimeThresholds[threshold] = limit;
  23803. return true;
  23804. };
  23805. moment.lang = deprecate(
  23806. 'moment.lang is deprecated. Use moment.locale instead.',
  23807. function (key, value) {
  23808. return moment.locale(key, value);
  23809. }
  23810. );
  23811. // This function will load locale and then set the global locale. If
  23812. // no arguments are passed in, it will simply return the current global
  23813. // locale key.
  23814. moment.locale = function (key, values) {
  23815. var data;
  23816. if (key) {
  23817. if (typeof(values) !== 'undefined') {
  23818. data = moment.defineLocale(key, values);
  23819. }
  23820. else {
  23821. data = moment.localeData(key);
  23822. }
  23823. if (data) {
  23824. moment.duration._locale = moment._locale = data;
  23825. }
  23826. }
  23827. return moment._locale._abbr;
  23828. };
  23829. moment.defineLocale = function (name, values) {
  23830. if (values !== null) {
  23831. values.abbr = name;
  23832. if (!locales[name]) {
  23833. locales[name] = new Locale();
  23834. }
  23835. locales[name].set(values);
  23836. // backwards compat for now: also set the locale
  23837. moment.locale(name);
  23838. return locales[name];
  23839. } else {
  23840. // useful for testing
  23841. delete locales[name];
  23842. return null;
  23843. }
  23844. };
  23845. moment.langData = deprecate(
  23846. 'moment.langData is deprecated. Use moment.localeData instead.',
  23847. function (key) {
  23848. return moment.localeData(key);
  23849. }
  23850. );
  23851. // returns locale data
  23852. moment.localeData = function (key) {
  23853. var locale;
  23854. if (key && key._locale && key._locale._abbr) {
  23855. key = key._locale._abbr;
  23856. }
  23857. if (!key) {
  23858. return moment._locale;
  23859. }
  23860. if (!isArray(key)) {
  23861. //short-circuit everything else
  23862. locale = loadLocale(key);
  23863. if (locale) {
  23864. return locale;
  23865. }
  23866. key = [key];
  23867. }
  23868. return chooseLocale(key);
  23869. };
  23870. // compare moment object
  23871. moment.isMoment = function (obj) {
  23872. return obj instanceof Moment ||
  23873. (obj != null && hasOwnProp(obj, '_isAMomentObject'));
  23874. };
  23875. // for typechecking Duration objects
  23876. moment.isDuration = function (obj) {
  23877. return obj instanceof Duration;
  23878. };
  23879. for (i = lists.length - 1; i >= 0; --i) {
  23880. makeList(lists[i]);
  23881. }
  23882. moment.normalizeUnits = function (units) {
  23883. return normalizeUnits(units);
  23884. };
  23885. moment.invalid = function (flags) {
  23886. var m = moment.utc(NaN);
  23887. if (flags != null) {
  23888. extend(m._pf, flags);
  23889. }
  23890. else {
  23891. m._pf.userInvalidated = true;
  23892. }
  23893. return m;
  23894. };
  23895. moment.parseZone = function () {
  23896. return moment.apply(null, arguments).parseZone();
  23897. };
  23898. moment.parseTwoDigitYear = function (input) {
  23899. return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
  23900. };
  23901. moment.isDate = isDate;
  23902. /************************************
  23903. Moment Prototype
  23904. ************************************/
  23905. extend(moment.fn = Moment.prototype, {
  23906. clone : function () {
  23907. return moment(this);
  23908. },
  23909. valueOf : function () {
  23910. return +this._d - ((this._offset || 0) * 60000);
  23911. },
  23912. unix : function () {
  23913. return Math.floor(+this / 1000);
  23914. },
  23915. toString : function () {
  23916. return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
  23917. },
  23918. toDate : function () {
  23919. return this._offset ? new Date(+this) : this._d;
  23920. },
  23921. toISOString : function () {
  23922. var m = moment(this).utc();
  23923. if (0 < m.year() && m.year() <= 9999) {
  23924. if ('function' === typeof Date.prototype.toISOString) {
  23925. // native implementation is ~50x faster, use it when we can
  23926. return this.toDate().toISOString();
  23927. } else {
  23928. return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  23929. }
  23930. } else {
  23931. return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  23932. }
  23933. },
  23934. toArray : function () {
  23935. var m = this;
  23936. return [
  23937. m.year(),
  23938. m.month(),
  23939. m.date(),
  23940. m.hours(),
  23941. m.minutes(),
  23942. m.seconds(),
  23943. m.milliseconds()
  23944. ];
  23945. },
  23946. isValid : function () {
  23947. return isValid(this);
  23948. },
  23949. isDSTShifted : function () {
  23950. if (this._a) {
  23951. return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0;
  23952. }
  23953. return false;
  23954. },
  23955. parsingFlags : function () {
  23956. return extend({}, this._pf);
  23957. },
  23958. invalidAt: function () {
  23959. return this._pf.overflow;
  23960. },
  23961. utc : function (keepLocalTime) {
  23962. return this.utcOffset(0, keepLocalTime);
  23963. },
  23964. local : function (keepLocalTime) {
  23965. if (this._isUTC) {
  23966. this.utcOffset(0, keepLocalTime);
  23967. this._isUTC = false;
  23968. if (keepLocalTime) {
  23969. this.subtract(this._dateUtcOffset(), 'm');
  23970. }
  23971. }
  23972. return this;
  23973. },
  23974. format : function (inputString) {
  23975. var output = formatMoment(this, inputString || moment.defaultFormat);
  23976. return this.localeData().postformat(output);
  23977. },
  23978. add : createAdder(1, 'add'),
  23979. subtract : createAdder(-1, 'subtract'),
  23980. diff : function (input, units, asFloat) {
  23981. var that = makeAs(input, this),
  23982. zoneDiff = (that.utcOffset() - this.utcOffset()) * 6e4,
  23983. anchor, diff, output, daysAdjust;
  23984. units = normalizeUnits(units);
  23985. if (units === 'year' || units === 'month' || units === 'quarter') {
  23986. output = monthDiff(this, that);
  23987. if (units === 'quarter') {
  23988. output = output / 3;
  23989. } else if (units === 'year') {
  23990. output = output / 12;
  23991. }
  23992. } else {
  23993. diff = this - that;
  23994. output = units === 'second' ? diff / 1e3 : // 1000
  23995. units === 'minute' ? diff / 6e4 : // 1000 * 60
  23996. units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
  23997. units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
  23998. units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
  23999. diff;
  24000. }
  24001. return asFloat ? output : absRound(output);
  24002. },
  24003. from : function (time, withoutSuffix) {
  24004. return moment.duration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
  24005. },
  24006. fromNow : function (withoutSuffix) {
  24007. return this.from(moment(), withoutSuffix);
  24008. },
  24009. calendar : function (time) {
  24010. // We want to compare the start of today, vs this.
  24011. // Getting start-of-today depends on whether we're locat/utc/offset
  24012. // or not.
  24013. var now = time || moment(),
  24014. sod = makeAs(now, this).startOf('day'),
  24015. diff = this.diff(sod, 'days', true),
  24016. format = diff < -6 ? 'sameElse' :
  24017. diff < -1 ? 'lastWeek' :
  24018. diff < 0 ? 'lastDay' :
  24019. diff < 1 ? 'sameDay' :
  24020. diff < 2 ? 'nextDay' :
  24021. diff < 7 ? 'nextWeek' : 'sameElse';
  24022. return this.format(this.localeData().calendar(format, this, moment(now)));
  24023. },
  24024. isLeapYear : function () {
  24025. return isLeapYear(this.year());
  24026. },
  24027. isDST : function () {
  24028. return (this.utcOffset() > this.clone().month(0).utcOffset() ||
  24029. this.utcOffset() > this.clone().month(5).utcOffset());
  24030. },
  24031. day : function (input) {
  24032. var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
  24033. if (input != null) {
  24034. input = parseWeekday(input, this.localeData());
  24035. return this.add(input - day, 'd');
  24036. } else {
  24037. return day;
  24038. }
  24039. },
  24040. month : makeAccessor('Month', true),
  24041. startOf : function (units) {
  24042. units = normalizeUnits(units);
  24043. // the following switch intentionally omits break keywords
  24044. // to utilize falling through the cases.
  24045. switch (units) {
  24046. case 'year':
  24047. this.month(0);
  24048. /* falls through */
  24049. case 'quarter':
  24050. case 'month':
  24051. this.date(1);
  24052. /* falls through */
  24053. case 'week':
  24054. case 'isoWeek':
  24055. case 'day':
  24056. this.hours(0);
  24057. /* falls through */
  24058. case 'hour':
  24059. this.minutes(0);
  24060. /* falls through */
  24061. case 'minute':
  24062. this.seconds(0);
  24063. /* falls through */
  24064. case 'second':
  24065. this.milliseconds(0);
  24066. /* falls through */
  24067. }
  24068. // weeks are a special case
  24069. if (units === 'week') {
  24070. this.weekday(0);
  24071. } else if (units === 'isoWeek') {
  24072. this.isoWeekday(1);
  24073. }
  24074. // quarters are also special
  24075. if (units === 'quarter') {
  24076. this.month(Math.floor(this.month() / 3) * 3);
  24077. }
  24078. return this;
  24079. },
  24080. endOf: function (units) {
  24081. units = normalizeUnits(units);
  24082. if (units === undefined || units === 'millisecond') {
  24083. return this;
  24084. }
  24085. return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
  24086. },
  24087. isAfter: function (input, units) {
  24088. var inputMs;
  24089. units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond');
  24090. if (units === 'millisecond') {
  24091. input = moment.isMoment(input) ? input : moment(input);
  24092. return +this > +input;
  24093. } else {
  24094. inputMs = moment.isMoment(input) ? +input : +moment(input);
  24095. return inputMs < +this.clone().startOf(units);
  24096. }
  24097. },
  24098. isBefore: function (input, units) {
  24099. var inputMs;
  24100. units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond');
  24101. if (units === 'millisecond') {
  24102. input = moment.isMoment(input) ? input : moment(input);
  24103. return +this < +input;
  24104. } else {
  24105. inputMs = moment.isMoment(input) ? +input : +moment(input);
  24106. return +this.clone().endOf(units) < inputMs;
  24107. }
  24108. },
  24109. isBetween: function (from, to, units) {
  24110. return this.isAfter(from, units) && this.isBefore(to, units);
  24111. },
  24112. isSame: function (input, units) {
  24113. var inputMs;
  24114. units = normalizeUnits(units || 'millisecond');
  24115. if (units === 'millisecond') {
  24116. input = moment.isMoment(input) ? input : moment(input);
  24117. return +this === +input;
  24118. } else {
  24119. inputMs = +moment(input);
  24120. return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units));
  24121. }
  24122. },
  24123. min: deprecate(
  24124. 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548',
  24125. function (other) {
  24126. other = moment.apply(null, arguments);
  24127. return other < this ? this : other;
  24128. }
  24129. ),
  24130. max: deprecate(
  24131. 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548',
  24132. function (other) {
  24133. other = moment.apply(null, arguments);
  24134. return other > this ? this : other;
  24135. }
  24136. ),
  24137. zone : deprecate(
  24138. 'moment().zone is deprecated, use moment().utcOffset instead. ' +
  24139. 'https://github.com/moment/moment/issues/1779',
  24140. function (input, keepLocalTime) {
  24141. if (input != null) {
  24142. if (typeof input !== 'string') {
  24143. input = -input;
  24144. }
  24145. this.utcOffset(input, keepLocalTime);
  24146. return this;
  24147. } else {
  24148. return -this.utcOffset();
  24149. }
  24150. }
  24151. ),
  24152. // keepLocalTime = true means only change the timezone, without
  24153. // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
  24154. // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
  24155. // +0200, so we adjust the time as needed, to be valid.
  24156. //
  24157. // Keeping the time actually adds/subtracts (one hour)
  24158. // from the actual represented time. That is why we call updateOffset
  24159. // a second time. In case it wants us to change the offset again
  24160. // _changeInProgress == true case, then we have to adjust, because
  24161. // there is no such time in the given timezone.
  24162. utcOffset : function (input, keepLocalTime) {
  24163. var offset = this._offset || 0,
  24164. localAdjust;
  24165. if (input != null) {
  24166. if (typeof input === 'string') {
  24167. input = utcOffsetFromString(input);
  24168. }
  24169. if (Math.abs(input) < 16) {
  24170. input = input * 60;
  24171. }
  24172. if (!this._isUTC && keepLocalTime) {
  24173. localAdjust = this._dateUtcOffset();
  24174. }
  24175. this._offset = input;
  24176. this._isUTC = true;
  24177. if (localAdjust != null) {
  24178. this.add(localAdjust, 'm');
  24179. }
  24180. if (offset !== input) {
  24181. if (!keepLocalTime || this._changeInProgress) {
  24182. addOrSubtractDurationFromMoment(this,
  24183. moment.duration(input - offset, 'm'), 1, false);
  24184. } else if (!this._changeInProgress) {
  24185. this._changeInProgress = true;
  24186. moment.updateOffset(this, true);
  24187. this._changeInProgress = null;
  24188. }
  24189. }
  24190. return this;
  24191. } else {
  24192. return this._isUTC ? offset : this._dateUtcOffset();
  24193. }
  24194. },
  24195. isLocal : function () {
  24196. return !this._isUTC;
  24197. },
  24198. isUtcOffset : function () {
  24199. return this._isUTC;
  24200. },
  24201. isUtc : function () {
  24202. return this._isUTC && this._offset === 0;
  24203. },
  24204. zoneAbbr : function () {
  24205. return this._isUTC ? 'UTC' : '';
  24206. },
  24207. zoneName : function () {
  24208. return this._isUTC ? 'Coordinated Universal Time' : '';
  24209. },
  24210. parseZone : function () {
  24211. if (this._tzm) {
  24212. this.utcOffset(this._tzm);
  24213. } else if (typeof this._i === 'string') {
  24214. this.utcOffset(utcOffsetFromString(this._i));
  24215. }
  24216. return this;
  24217. },
  24218. hasAlignedHourOffset : function (input) {
  24219. if (!input) {
  24220. input = 0;
  24221. }
  24222. else {
  24223. input = moment(input).utcOffset();
  24224. }
  24225. return (this.utcOffset() - input) % 60 === 0;
  24226. },
  24227. daysInMonth : function () {
  24228. return daysInMonth(this.year(), this.month());
  24229. },
  24230. dayOfYear : function (input) {
  24231. var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1;
  24232. return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
  24233. },
  24234. quarter : function (input) {
  24235. return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
  24236. },
  24237. weekYear : function (input) {
  24238. var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year;
  24239. return input == null ? year : this.add((input - year), 'y');
  24240. },
  24241. isoWeekYear : function (input) {
  24242. var year = weekOfYear(this, 1, 4).year;
  24243. return input == null ? year : this.add((input - year), 'y');
  24244. },
  24245. week : function (input) {
  24246. var week = this.localeData().week(this);
  24247. return input == null ? week : this.add((input - week) * 7, 'd');
  24248. },
  24249. isoWeek : function (input) {
  24250. var week = weekOfYear(this, 1, 4).week;
  24251. return input == null ? week : this.add((input - week) * 7, 'd');
  24252. },
  24253. weekday : function (input) {
  24254. var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
  24255. return input == null ? weekday : this.add(input - weekday, 'd');
  24256. },
  24257. isoWeekday : function (input) {
  24258. // behaves the same as moment#day except
  24259. // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
  24260. // as a setter, sunday should belong to the previous week.
  24261. return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
  24262. },
  24263. isoWeeksInYear : function () {
  24264. return weeksInYear(this.year(), 1, 4);
  24265. },
  24266. weeksInYear : function () {
  24267. var weekInfo = this.localeData()._week;
  24268. return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
  24269. },
  24270. get : function (units) {
  24271. units = normalizeUnits(units);
  24272. return this[units]();
  24273. },
  24274. set : function (units, value) {
  24275. var unit;
  24276. if (typeof units === 'object') {
  24277. for (unit in units) {
  24278. this.set(unit, units[unit]);
  24279. }
  24280. }
  24281. else {
  24282. units = normalizeUnits(units);
  24283. if (typeof this[units] === 'function') {
  24284. this[units](value);
  24285. }
  24286. }
  24287. return this;
  24288. },
  24289. // If passed a locale key, it will set the locale for this
  24290. // instance. Otherwise, it will return the locale configuration
  24291. // variables for this instance.
  24292. locale : function (key) {
  24293. var newLocaleData;
  24294. if (key === undefined) {
  24295. return this._locale._abbr;
  24296. } else {
  24297. newLocaleData = moment.localeData(key);
  24298. if (newLocaleData != null) {
  24299. this._locale = newLocaleData;
  24300. }
  24301. return this;
  24302. }
  24303. },
  24304. lang : deprecate(
  24305. 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
  24306. function (key) {
  24307. if (key === undefined) {
  24308. return this.localeData();
  24309. } else {
  24310. return this.locale(key);
  24311. }
  24312. }
  24313. ),
  24314. localeData : function () {
  24315. return this._locale;
  24316. },
  24317. _dateUtcOffset : function () {
  24318. // On Firefox.24 Date#getTimezoneOffset returns a floating point.
  24319. // https://github.com/moment/moment/pull/1871
  24320. return -Math.round(this._d.getTimezoneOffset() / 15) * 15;
  24321. }
  24322. });
  24323. function rawMonthSetter(mom, value) {
  24324. var dayOfMonth;
  24325. // TODO: Move this out of here!
  24326. if (typeof value === 'string') {
  24327. value = mom.localeData().monthsParse(value);
  24328. // TODO: Another silent failure?
  24329. if (typeof value !== 'number') {
  24330. return mom;
  24331. }
  24332. }
  24333. dayOfMonth = Math.min(mom.date(),
  24334. daysInMonth(mom.year(), value));
  24335. mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
  24336. return mom;
  24337. }
  24338. function rawGetter(mom, unit) {
  24339. return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]();
  24340. }
  24341. function rawSetter(mom, unit, value) {
  24342. if (unit === 'Month') {
  24343. return rawMonthSetter(mom, value);
  24344. } else {
  24345. return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
  24346. }
  24347. }
  24348. function makeAccessor(unit, keepTime) {
  24349. return function (value) {
  24350. if (value != null) {
  24351. rawSetter(this, unit, value);
  24352. moment.updateOffset(this, keepTime);
  24353. return this;
  24354. } else {
  24355. return rawGetter(this, unit);
  24356. }
  24357. };
  24358. }
  24359. moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false);
  24360. moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false);
  24361. moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false);
  24362. // Setting the hour should keep the time, because the user explicitly
  24363. // specified which hour he wants. So trying to maintain the same hour (in
  24364. // a new timezone) makes sense. Adding/subtracting hours does not follow
  24365. // this rule.
  24366. moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true);
  24367. // moment.fn.month is defined separately
  24368. moment.fn.date = makeAccessor('Date', true);
  24369. moment.fn.dates = deprecate('dates accessor is deprecated. Use date instead.', makeAccessor('Date', true));
  24370. moment.fn.year = makeAccessor('FullYear', true);
  24371. moment.fn.years = deprecate('years accessor is deprecated. Use year instead.', makeAccessor('FullYear', true));
  24372. // add plural methods
  24373. moment.fn.days = moment.fn.day;
  24374. moment.fn.months = moment.fn.month;
  24375. moment.fn.weeks = moment.fn.week;
  24376. moment.fn.isoWeeks = moment.fn.isoWeek;
  24377. moment.fn.quarters = moment.fn.quarter;
  24378. // add aliased format methods
  24379. moment.fn.toJSON = moment.fn.toISOString;
  24380. // alias isUtc for dev-friendliness
  24381. moment.fn.isUTC = moment.fn.isUtc;
  24382. /************************************
  24383. Duration Prototype
  24384. ************************************/
  24385. function daysToYears (days) {
  24386. // 400 years have 146097 days (taking into account leap year rules)
  24387. return days * 400 / 146097;
  24388. }
  24389. function yearsToDays (years) {
  24390. // years * 365 + absRound(years / 4) -
  24391. // absRound(years / 100) + absRound(years / 400);
  24392. return years * 146097 / 400;
  24393. }
  24394. extend(moment.duration.fn = Duration.prototype, {
  24395. _bubble : function () {
  24396. var milliseconds = this._milliseconds,
  24397. days = this._days,
  24398. months = this._months,
  24399. data = this._data,
  24400. seconds, minutes, hours, years = 0;
  24401. // The following code bubbles up values, see the tests for
  24402. // examples of what that means.
  24403. data.milliseconds = milliseconds % 1000;
  24404. seconds = absRound(milliseconds / 1000);
  24405. data.seconds = seconds % 60;
  24406. minutes = absRound(seconds / 60);
  24407. data.minutes = minutes % 60;
  24408. hours = absRound(minutes / 60);
  24409. data.hours = hours % 24;
  24410. days += absRound(hours / 24);
  24411. // Accurately convert days to years, assume start from year 0.
  24412. years = absRound(daysToYears(days));
  24413. days -= absRound(yearsToDays(years));
  24414. // 30 days to a month
  24415. // TODO (iskren): Use anchor date (like 1st Jan) to compute this.
  24416. months += absRound(days / 30);
  24417. days %= 30;
  24418. // 12 months -> 1 year
  24419. years += absRound(months / 12);
  24420. months %= 12;
  24421. data.days = days;
  24422. data.months = months;
  24423. data.years = years;
  24424. },
  24425. abs : function () {
  24426. this._milliseconds = Math.abs(this._milliseconds);
  24427. this._days = Math.abs(this._days);
  24428. this._months = Math.abs(this._months);
  24429. this._data.milliseconds = Math.abs(this._data.milliseconds);
  24430. this._data.seconds = Math.abs(this._data.seconds);
  24431. this._data.minutes = Math.abs(this._data.minutes);
  24432. this._data.hours = Math.abs(this._data.hours);
  24433. this._data.months = Math.abs(this._data.months);
  24434. this._data.years = Math.abs(this._data.years);
  24435. return this;
  24436. },
  24437. weeks : function () {
  24438. return absRound(this.days() / 7);
  24439. },
  24440. valueOf : function () {
  24441. return this._milliseconds +
  24442. this._days * 864e5 +
  24443. (this._months % 12) * 2592e6 +
  24444. toInt(this._months / 12) * 31536e6;
  24445. },
  24446. humanize : function (withSuffix) {
  24447. var output = relativeTime(this, !withSuffix, this.localeData());
  24448. if (withSuffix) {
  24449. output = this.localeData().pastFuture(+this, output);
  24450. }
  24451. return this.localeData().postformat(output);
  24452. },
  24453. add : function (input, val) {
  24454. // supports only 2.0-style add(1, 's') or add(moment)
  24455. var dur = moment.duration(input, val);
  24456. this._milliseconds += dur._milliseconds;
  24457. this._days += dur._days;
  24458. this._months += dur._months;
  24459. this._bubble();
  24460. return this;
  24461. },
  24462. subtract : function (input, val) {
  24463. var dur = moment.duration(input, val);
  24464. this._milliseconds -= dur._milliseconds;
  24465. this._days -= dur._days;
  24466. this._months -= dur._months;
  24467. this._bubble();
  24468. return this;
  24469. },
  24470. get : function (units) {
  24471. units = normalizeUnits(units);
  24472. return this[units.toLowerCase() + 's']();
  24473. },
  24474. as : function (units) {
  24475. var days, months;
  24476. units = normalizeUnits(units);
  24477. if (units === 'month' || units === 'year') {
  24478. days = this._days + this._milliseconds / 864e5;
  24479. months = this._months + daysToYears(days) * 12;
  24480. return units === 'month' ? months : months / 12;
  24481. } else {
  24482. // handle milliseconds separately because of floating point math errors (issue #1867)
  24483. days = this._days + Math.round(yearsToDays(this._months / 12));
  24484. switch (units) {
  24485. case 'week': return days / 7 + this._milliseconds / 6048e5;
  24486. case 'day': return days + this._milliseconds / 864e5;
  24487. case 'hour': return days * 24 + this._milliseconds / 36e5;
  24488. case 'minute': return days * 24 * 60 + this._milliseconds / 6e4;
  24489. case 'second': return days * 24 * 60 * 60 + this._milliseconds / 1000;
  24490. // Math.floor prevents floating point math errors here
  24491. case 'millisecond': return Math.floor(days * 24 * 60 * 60 * 1000) + this._milliseconds;
  24492. default: throw new Error('Unknown unit ' + units);
  24493. }
  24494. }
  24495. },
  24496. lang : moment.fn.lang,
  24497. locale : moment.fn.locale,
  24498. toIsoString : deprecate(
  24499. 'toIsoString() is deprecated. Please use toISOString() instead ' +
  24500. '(notice the capitals)',
  24501. function () {
  24502. return this.toISOString();
  24503. }
  24504. ),
  24505. toISOString : function () {
  24506. // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
  24507. var years = Math.abs(this.years()),
  24508. months = Math.abs(this.months()),
  24509. days = Math.abs(this.days()),
  24510. hours = Math.abs(this.hours()),
  24511. minutes = Math.abs(this.minutes()),
  24512. seconds = Math.abs(this.seconds() + this.milliseconds() / 1000);
  24513. if (!this.asSeconds()) {
  24514. // this is the same as C#'s (Noda) and python (isodate)...
  24515. // but not other JS (goog.date)
  24516. return 'P0D';
  24517. }
  24518. return (this.asSeconds() < 0 ? '-' : '') +
  24519. 'P' +
  24520. (years ? years + 'Y' : '') +
  24521. (months ? months + 'M' : '') +
  24522. (days ? days + 'D' : '') +
  24523. ((hours || minutes || seconds) ? 'T' : '') +
  24524. (hours ? hours + 'H' : '') +
  24525. (minutes ? minutes + 'M' : '') +
  24526. (seconds ? seconds + 'S' : '');
  24527. },
  24528. localeData : function () {
  24529. return this._locale;
  24530. },
  24531. toJSON : function () {
  24532. return this.toISOString();
  24533. }
  24534. });
  24535. moment.duration.fn.toString = moment.duration.fn.toISOString;
  24536. function makeDurationGetter(name) {
  24537. moment.duration.fn[name] = function () {
  24538. return this._data[name];
  24539. };
  24540. }
  24541. for (i in unitMillisecondFactors) {
  24542. if (hasOwnProp(unitMillisecondFactors, i)) {
  24543. makeDurationGetter(i.toLowerCase());
  24544. }
  24545. }
  24546. moment.duration.fn.asMilliseconds = function () {
  24547. return this.as('ms');
  24548. };
  24549. moment.duration.fn.asSeconds = function () {
  24550. return this.as('s');
  24551. };
  24552. moment.duration.fn.asMinutes = function () {
  24553. return this.as('m');
  24554. };
  24555. moment.duration.fn.asHours = function () {
  24556. return this.as('h');
  24557. };
  24558. moment.duration.fn.asDays = function () {
  24559. return this.as('d');
  24560. };
  24561. moment.duration.fn.asWeeks = function () {
  24562. return this.as('weeks');
  24563. };
  24564. moment.duration.fn.asMonths = function () {
  24565. return this.as('M');
  24566. };
  24567. moment.duration.fn.asYears = function () {
  24568. return this.as('y');
  24569. };
  24570. /************************************
  24571. Default Locale
  24572. ************************************/
  24573. // Set default locale, other locale will inherit from English.
  24574. moment.locale('en', {
  24575. ordinalParse: /\d{1,2}(th|st|nd|rd)/,
  24576. ordinal : function (number) {
  24577. var b = number % 10,
  24578. output = (toInt(number % 100 / 10) === 1) ? 'th' :
  24579. (b === 1) ? 'st' :
  24580. (b === 2) ? 'nd' :
  24581. (b === 3) ? 'rd' : 'th';
  24582. return number + output;
  24583. }
  24584. });
  24585. /* EMBED_LOCALES */
  24586. /************************************
  24587. Exposing Moment
  24588. ************************************/
  24589. function makeGlobal(shouldDeprecate) {
  24590. /*global ender:false */
  24591. if (typeof ender !== 'undefined') {
  24592. return;
  24593. }
  24594. oldGlobalMoment = globalScope.moment;
  24595. if (shouldDeprecate) {
  24596. globalScope.moment = deprecate(
  24597. 'Accessing Moment through the global scope is ' +
  24598. 'deprecated, and will be removed in an upcoming ' +
  24599. 'release.',
  24600. moment);
  24601. } else {
  24602. globalScope.moment = moment;
  24603. }
  24604. }
  24605. // CommonJS module is defined
  24606. if (hasModule) {
  24607. module.exports = moment;
  24608. } else if (true) {
  24609. !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
  24610. if (module.config && module.config() && module.config().noGlobal === true) {
  24611. // release the global variable
  24612. globalScope.moment = oldGlobalMoment;
  24613. }
  24614. return moment;
  24615. }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  24616. makeGlobal(true);
  24617. } else {
  24618. makeGlobal();
  24619. }
  24620. }).call(this);
  24621. /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()), __webpack_require__(71)(module)))
  24622. /***/ },
  24623. /* 59 */
  24624. /***/ function(module, exports, __webpack_require__) {
  24625. var __WEBPACK_AMD_DEFINE_RESULT__;/*! Hammer.JS - v1.1.3 - 2014-05-20
  24626. * http://eightmedia.github.io/hammer.js
  24627. *
  24628. * Copyright (c) 2014 Jorik Tangelder <j.tangelder@gmail.com>;
  24629. * Licensed under the MIT license */
  24630. (function(window, undefined) {
  24631. 'use strict';
  24632. /**
  24633. * @main
  24634. * @module hammer
  24635. *
  24636. * @class Hammer
  24637. * @static
  24638. */
  24639. /**
  24640. * Hammer, use this to create instances
  24641. * ````
  24642. * var hammertime = new Hammer(myElement);
  24643. * ````
  24644. *
  24645. * @method Hammer
  24646. * @param {HTMLElement} element
  24647. * @param {Object} [options={}]
  24648. * @return {Hammer.Instance}
  24649. */
  24650. var Hammer = function Hammer(element, options) {
  24651. return new Hammer.Instance(element, options || {});
  24652. };
  24653. /**
  24654. * version, as defined in package.json
  24655. * the value will be set at each build
  24656. * @property VERSION
  24657. * @final
  24658. * @type {String}
  24659. */
  24660. Hammer.VERSION = '1.1.3';
  24661. /**
  24662. * default settings.
  24663. * more settings are defined per gesture at `/gestures`. Each gesture can be disabled/enabled
  24664. * by setting it's name (like `swipe`) to false.
  24665. * You can set the defaults for all instances by changing this object before creating an instance.
  24666. * @example
  24667. * ````
  24668. * Hammer.defaults.drag = false;
  24669. * Hammer.defaults.behavior.touchAction = 'pan-y';
  24670. * delete Hammer.defaults.behavior.userSelect;
  24671. * ````
  24672. * @property defaults
  24673. * @type {Object}
  24674. */
  24675. Hammer.defaults = {
  24676. /**
  24677. * this setting object adds styles and attributes to the element to prevent the browser from doing
  24678. * its native behavior. The css properties are auto prefixed for the browsers when needed.
  24679. * @property defaults.behavior
  24680. * @type {Object}
  24681. */
  24682. behavior: {
  24683. /**
  24684. * Disables text selection to improve the dragging gesture. When the value is `none` it also sets
  24685. * `onselectstart=false` for IE on the element. Mainly for desktop browsers.
  24686. * @property defaults.behavior.userSelect
  24687. * @type {String}
  24688. * @default 'none'
  24689. */
  24690. userSelect: 'none',
  24691. /**
  24692. * Specifies whether and how a given region can be manipulated by the user (for instance, by panning or zooming).
  24693. * Used by Chrome 35> and IE10>. By default this makes the element blocking any touch event.
  24694. * @property defaults.behavior.touchAction
  24695. * @type {String}
  24696. * @default: 'pan-y'
  24697. */
  24698. touchAction: 'pan-y',
  24699. /**
  24700. * Disables the default callout shown when you touch and hold a touch target.
  24701. * On iOS, when you touch and hold a touch target such as a link, Safari displays
  24702. * a callout containing information about the link. This property allows you to disable that callout.
  24703. * @property defaults.behavior.touchCallout
  24704. * @type {String}
  24705. * @default 'none'
  24706. */
  24707. touchCallout: 'none',
  24708. /**
  24709. * Specifies whether zooming is enabled. Used by IE10>
  24710. * @property defaults.behavior.contentZooming
  24711. * @type {String}
  24712. * @default 'none'
  24713. */
  24714. contentZooming: 'none',
  24715. /**
  24716. * Specifies that an entire element should be draggable instead of its contents.
  24717. * Mainly for desktop browsers.
  24718. * @property defaults.behavior.userDrag
  24719. * @type {String}
  24720. * @default 'none'
  24721. */
  24722. userDrag: 'none',
  24723. /**
  24724. * Overrides the highlight color shown when the user taps a link or a JavaScript
  24725. * clickable element in Safari on iPhone. This property obeys the alpha value, if specified.
  24726. *
  24727. * If you don't specify an alpha value, Safari on iPhone applies a default alpha value
  24728. * to the color. To disable tap highlighting, set the alpha value to 0 (invisible).
  24729. * If you set the alpha value to 1.0 (opaque), the element is not visible when tapped.
  24730. * @property defaults.behavior.tapHighlightColor
  24731. * @type {String}
  24732. * @default 'rgba(0,0,0,0)'
  24733. */
  24734. tapHighlightColor: 'rgba(0,0,0,0)'
  24735. }
  24736. };
  24737. /**
  24738. * hammer document where the base events are added at
  24739. * @property DOCUMENT
  24740. * @type {HTMLElement}
  24741. * @default window.document
  24742. */
  24743. Hammer.DOCUMENT = document;
  24744. /**
  24745. * detect support for pointer events
  24746. * @property HAS_POINTEREVENTS
  24747. * @type {Boolean}
  24748. */
  24749. Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;
  24750. /**
  24751. * detect support for touch events
  24752. * @property HAS_TOUCHEVENTS
  24753. * @type {Boolean}
  24754. */
  24755. Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);
  24756. /**
  24757. * detect mobile browsers
  24758. * @property IS_MOBILE
  24759. * @type {Boolean}
  24760. */
  24761. Hammer.IS_MOBILE = /mobile|tablet|ip(ad|hone|od)|android|silk/i.test(navigator.userAgent);
  24762. /**
  24763. * detect if we want to support mouseevents at all
  24764. * @property NO_MOUSEEVENTS
  24765. * @type {Boolean}
  24766. */
  24767. Hammer.NO_MOUSEEVENTS = (Hammer.HAS_TOUCHEVENTS && Hammer.IS_MOBILE) || Hammer.HAS_POINTEREVENTS;
  24768. /**
  24769. * interval in which Hammer recalculates current velocity/direction/angle in ms
  24770. * @property CALCULATE_INTERVAL
  24771. * @type {Number}
  24772. * @default 25
  24773. */
  24774. Hammer.CALCULATE_INTERVAL = 25;
  24775. /**
  24776. * eventtypes per touchevent (start, move, end) are filled by `Event.determineEventTypes` on `setup`
  24777. * the object contains the DOM event names per type (`EVENT_START`, `EVENT_MOVE`, `EVENT_END`)
  24778. * @property EVENT_TYPES
  24779. * @private
  24780. * @writeOnce
  24781. * @type {Object}
  24782. */
  24783. var EVENT_TYPES = {};
  24784. /**
  24785. * direction strings, for safe comparisons
  24786. * @property DIRECTION_DOWN|LEFT|UP|RIGHT
  24787. * @final
  24788. * @type {String}
  24789. * @default 'down' 'left' 'up' 'right'
  24790. */
  24791. var DIRECTION_DOWN = Hammer.DIRECTION_DOWN = 'down';
  24792. var DIRECTION_LEFT = Hammer.DIRECTION_LEFT = 'left';
  24793. var DIRECTION_UP = Hammer.DIRECTION_UP = 'up';
  24794. var DIRECTION_RIGHT = Hammer.DIRECTION_RIGHT = 'right';
  24795. /**
  24796. * pointertype strings, for safe comparisons
  24797. * @property POINTER_MOUSE|TOUCH|PEN
  24798. * @final
  24799. * @type {String}
  24800. * @default 'mouse' 'touch' 'pen'
  24801. */
  24802. var POINTER_MOUSE = Hammer.POINTER_MOUSE = 'mouse';
  24803. var POINTER_TOUCH = Hammer.POINTER_TOUCH = 'touch';
  24804. var POINTER_PEN = Hammer.POINTER_PEN = 'pen';
  24805. /**
  24806. * eventtypes
  24807. * @property EVENT_START|MOVE|END|RELEASE|TOUCH
  24808. * @final
  24809. * @type {String}
  24810. * @default 'start' 'change' 'move' 'end' 'release' 'touch'
  24811. */
  24812. var EVENT_START = Hammer.EVENT_START = 'start';
  24813. var EVENT_MOVE = Hammer.EVENT_MOVE = 'move';
  24814. var EVENT_END = Hammer.EVENT_END = 'end';
  24815. var EVENT_RELEASE = Hammer.EVENT_RELEASE = 'release';
  24816. var EVENT_TOUCH = Hammer.EVENT_TOUCH = 'touch';
  24817. /**
  24818. * if the window events are set...
  24819. * @property READY
  24820. * @writeOnce
  24821. * @type {Boolean}
  24822. * @default false
  24823. */
  24824. Hammer.READY = false;
  24825. /**
  24826. * plugins namespace
  24827. * @property plugins
  24828. * @type {Object}
  24829. */
  24830. Hammer.plugins = Hammer.plugins || {};
  24831. /**
  24832. * gestures namespace
  24833. * see `/gestures` for the definitions
  24834. * @property gestures
  24835. * @type {Object}
  24836. */
  24837. Hammer.gestures = Hammer.gestures || {};
  24838. /**
  24839. * setup events to detect gestures on the document
  24840. * this function is called when creating an new instance
  24841. * @private
  24842. */
  24843. function setup() {
  24844. if(Hammer.READY) {
  24845. return;
  24846. }
  24847. // find what eventtypes we add listeners to
  24848. Event.determineEventTypes();
  24849. // Register all gestures inside Hammer.gestures
  24850. Utils.each(Hammer.gestures, function(gesture) {
  24851. Detection.register(gesture);
  24852. });
  24853. // Add touch events on the document
  24854. Event.onTouch(Hammer.DOCUMENT, EVENT_MOVE, Detection.detect);
  24855. Event.onTouch(Hammer.DOCUMENT, EVENT_END, Detection.detect);
  24856. // Hammer is ready...!
  24857. Hammer.READY = true;
  24858. }
  24859. /**
  24860. * @module hammer
  24861. *
  24862. * @class Utils
  24863. * @static
  24864. */
  24865. var Utils = Hammer.utils = {
  24866. /**
  24867. * extend method, could also be used for cloning when `dest` is an empty object.
  24868. * changes the dest object
  24869. * @method extend
  24870. * @param {Object} dest
  24871. * @param {Object} src
  24872. * @param {Boolean} [merge=false] do a merge
  24873. * @return {Object} dest
  24874. */
  24875. extend: function extend(dest, src, merge) {
  24876. for(var key in src) {
  24877. if(!src.hasOwnProperty(key) || (dest[key] !== undefined && merge)) {
  24878. continue;
  24879. }
  24880. dest[key] = src[key];
  24881. }
  24882. return dest;
  24883. },
  24884. /**
  24885. * simple addEventListener wrapper
  24886. * @method on
  24887. * @param {HTMLElement} element
  24888. * @param {String} type
  24889. * @param {Function} handler
  24890. */
  24891. on: function on(element, type, handler) {
  24892. element.addEventListener(type, handler, false);
  24893. },
  24894. /**
  24895. * simple removeEventListener wrapper
  24896. * @method off
  24897. * @param {HTMLElement} element
  24898. * @param {String} type
  24899. * @param {Function} handler
  24900. */
  24901. off: function off(element, type, handler) {
  24902. element.removeEventListener(type, handler, false);
  24903. },
  24904. /**
  24905. * forEach over arrays and objects
  24906. * @method each
  24907. * @param {Object|Array} obj
  24908. * @param {Function} iterator
  24909. * @param {any} iterator.item
  24910. * @param {Number} iterator.index
  24911. * @param {Object|Array} iterator.obj the source object
  24912. * @param {Object} context value to use as `this` in the iterator
  24913. */
  24914. each: function each(obj, iterator, context) {
  24915. var i, len;
  24916. // native forEach on arrays
  24917. if('forEach' in obj) {
  24918. obj.forEach(iterator, context);
  24919. // arrays
  24920. } else if(obj.length !== undefined) {
  24921. for(i = 0, len = obj.length; i < len; i++) {
  24922. if(iterator.call(context, obj[i], i, obj) === false) {
  24923. return;
  24924. }
  24925. }
  24926. // objects
  24927. } else {
  24928. for(i in obj) {
  24929. if(obj.hasOwnProperty(i) &&
  24930. iterator.call(context, obj[i], i, obj) === false) {
  24931. return;
  24932. }
  24933. }
  24934. }
  24935. },
  24936. /**
  24937. * find if a string contains the string using indexOf
  24938. * @method inStr
  24939. * @param {String} src
  24940. * @param {String} find
  24941. * @return {Boolean} found
  24942. */
  24943. inStr: function inStr(src, find) {
  24944. return src.indexOf(find) > -1;
  24945. },
  24946. /**
  24947. * find if a array contains the object using indexOf or a simple polyfill
  24948. * @method inArray
  24949. * @param {String} src
  24950. * @param {String} find
  24951. * @return {Boolean|Number} false when not found, or the index
  24952. */
  24953. inArray: function inArray(src, find) {
  24954. if(src.indexOf) {
  24955. var index = src.indexOf(find);
  24956. return (index === -1) ? false : index;
  24957. } else {
  24958. for(var i = 0, len = src.length; i < len; i++) {
  24959. if(src[i] === find) {
  24960. return i;
  24961. }
  24962. }
  24963. return false;
  24964. }
  24965. },
  24966. /**
  24967. * convert an array-like object (`arguments`, `touchlist`) to an array
  24968. * @method toArray
  24969. * @param {Object} obj
  24970. * @return {Array}
  24971. */
  24972. toArray: function toArray(obj) {
  24973. return Array.prototype.slice.call(obj, 0);
  24974. },
  24975. /**
  24976. * find if a node is in the given parent
  24977. * @method hasParent
  24978. * @param {HTMLElement} node
  24979. * @param {HTMLElement} parent
  24980. * @return {Boolean} found
  24981. */
  24982. hasParent: function hasParent(node, parent) {
  24983. while(node) {
  24984. if(node == parent) {
  24985. return true;
  24986. }
  24987. node = node.parentNode;
  24988. }
  24989. return false;
  24990. },
  24991. /**
  24992. * get the center of all the touches
  24993. * @method getCenter
  24994. * @param {Array} touches
  24995. * @return {Object} center contains `pageX`, `pageY`, `clientX` and `clientY` properties
  24996. */
  24997. getCenter: function getCenter(touches) {
  24998. var pageX = [],
  24999. pageY = [],
  25000. clientX = [],
  25001. clientY = [],
  25002. min = Math.min,
  25003. max = Math.max;
  25004. // no need to loop when only one touch
  25005. if(touches.length === 1) {
  25006. return {
  25007. pageX: touches[0].pageX,
  25008. pageY: touches[0].pageY,
  25009. clientX: touches[0].clientX,
  25010. clientY: touches[0].clientY
  25011. };
  25012. }
  25013. Utils.each(touches, function(touch) {
  25014. pageX.push(touch.pageX);
  25015. pageY.push(touch.pageY);
  25016. clientX.push(touch.clientX);
  25017. clientY.push(touch.clientY);
  25018. });
  25019. return {
  25020. pageX: (min.apply(Math, pageX) + max.apply(Math, pageX)) / 2,
  25021. pageY: (min.apply(Math, pageY) + max.apply(Math, pageY)) / 2,
  25022. clientX: (min.apply(Math, clientX) + max.apply(Math, clientX)) / 2,
  25023. clientY: (min.apply(Math, clientY) + max.apply(Math, clientY)) / 2
  25024. };
  25025. },
  25026. /**
  25027. * calculate the velocity between two points. unit is in px per ms.
  25028. * @method getVelocity
  25029. * @param {Number} deltaTime
  25030. * @param {Number} deltaX
  25031. * @param {Number} deltaY
  25032. * @return {Object} velocity `x` and `y`
  25033. */
  25034. getVelocity: function getVelocity(deltaTime, deltaX, deltaY) {
  25035. return {
  25036. x: Math.abs(deltaX / deltaTime) || 0,
  25037. y: Math.abs(deltaY / deltaTime) || 0
  25038. };
  25039. },
  25040. /**
  25041. * calculate the angle between two coordinates
  25042. * @method getAngle
  25043. * @param {Touch} touch1
  25044. * @param {Touch} touch2
  25045. * @return {Number} angle
  25046. */
  25047. getAngle: function getAngle(touch1, touch2) {
  25048. var x = touch2.clientX - touch1.clientX,
  25049. y = touch2.clientY - touch1.clientY;
  25050. return Math.atan2(y, x) * 180 / Math.PI;
  25051. },
  25052. /**
  25053. * do a small comparision to get the direction between two touches.
  25054. * @method getDirection
  25055. * @param {Touch} touch1
  25056. * @param {Touch} touch2
  25057. * @return {String} direction matches `DIRECTION_LEFT|RIGHT|UP|DOWN`
  25058. */
  25059. getDirection: function getDirection(touch1, touch2) {
  25060. var x = Math.abs(touch1.clientX - touch2.clientX),
  25061. y = Math.abs(touch1.clientY - touch2.clientY);
  25062. if(x >= y) {
  25063. return touch1.clientX - touch2.clientX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
  25064. }
  25065. return touch1.clientY - touch2.clientY > 0 ? DIRECTION_UP : DIRECTION_DOWN;
  25066. },
  25067. /**
  25068. * calculate the distance between two touches
  25069. * @method getDistance
  25070. * @param {Touch}touch1
  25071. * @param {Touch} touch2
  25072. * @return {Number} distance
  25073. */
  25074. getDistance: function getDistance(touch1, touch2) {
  25075. var x = touch2.clientX - touch1.clientX,
  25076. y = touch2.clientY - touch1.clientY;
  25077. return Math.sqrt((x * x) + (y * y));
  25078. },
  25079. /**
  25080. * calculate the scale factor between two touchLists
  25081. * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
  25082. * @method getScale
  25083. * @param {Array} start array of touches
  25084. * @param {Array} end array of touches
  25085. * @return {Number} scale
  25086. */
  25087. getScale: function getScale(start, end) {
  25088. // need two fingers...
  25089. if(start.length >= 2 && end.length >= 2) {
  25090. return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]);
  25091. }
  25092. return 1;
  25093. },
  25094. /**
  25095. * calculate the rotation degrees between two touchLists
  25096. * @method getRotation
  25097. * @param {Array} start array of touches
  25098. * @param {Array} end array of touches
  25099. * @return {Number} rotation
  25100. */
  25101. getRotation: function getRotation(start, end) {
  25102. // need two fingers
  25103. if(start.length >= 2 && end.length >= 2) {
  25104. return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]);
  25105. }
  25106. return 0;
  25107. },
  25108. /**
  25109. * find out if the direction is vertical *
  25110. * @method isVertical
  25111. * @param {String} direction matches `DIRECTION_UP|DOWN`
  25112. * @return {Boolean} is_vertical
  25113. */
  25114. isVertical: function isVertical(direction) {
  25115. return direction == DIRECTION_UP || direction == DIRECTION_DOWN;
  25116. },
  25117. /**
  25118. * set css properties with their prefixes
  25119. * @param {HTMLElement} element
  25120. * @param {String} prop
  25121. * @param {String} value
  25122. * @param {Boolean} [toggle=true]
  25123. * @return {Boolean}
  25124. */
  25125. setPrefixedCss: function setPrefixedCss(element, prop, value, toggle) {
  25126. var prefixes = ['', 'Webkit', 'Moz', 'O', 'ms'];
  25127. prop = Utils.toCamelCase(prop);
  25128. for(var i = 0; i < prefixes.length; i++) {
  25129. var p = prop;
  25130. // prefixes
  25131. if(prefixes[i]) {
  25132. p = prefixes[i] + p.slice(0, 1).toUpperCase() + p.slice(1);
  25133. }
  25134. // test the style
  25135. if(p in element.style) {
  25136. element.style[p] = (toggle == null || toggle) && value || '';
  25137. break;
  25138. }
  25139. }
  25140. },
  25141. /**
  25142. * toggle browser default behavior by setting css properties.
  25143. * `userSelect='none'` also sets `element.onselectstart` to false
  25144. * `userDrag='none'` also sets `element.ondragstart` to false
  25145. *
  25146. * @method toggleBehavior
  25147. * @param {HtmlElement} element
  25148. * @param {Object} props
  25149. * @param {Boolean} [toggle=true]
  25150. */
  25151. toggleBehavior: function toggleBehavior(element, props, toggle) {
  25152. if(!props || !element || !element.style) {
  25153. return;
  25154. }
  25155. // set the css properties
  25156. Utils.each(props, function(value, prop) {
  25157. Utils.setPrefixedCss(element, prop, value, toggle);
  25158. });
  25159. var falseFn = toggle && function() {
  25160. return false;
  25161. };
  25162. // also the disable onselectstart
  25163. if(props.userSelect == 'none') {
  25164. element.onselectstart = falseFn;
  25165. }
  25166. // and disable ondragstart
  25167. if(props.userDrag == 'none') {
  25168. element.ondragstart = falseFn;
  25169. }
  25170. },
  25171. /**
  25172. * convert a string with underscores to camelCase
  25173. * so prevent_default becomes preventDefault
  25174. * @param {String} str
  25175. * @return {String} camelCaseStr
  25176. */
  25177. toCamelCase: function toCamelCase(str) {
  25178. return str.replace(/[_-]([a-z])/g, function(s) {
  25179. return s[1].toUpperCase();
  25180. });
  25181. }
  25182. };
  25183. /**
  25184. * @module hammer
  25185. */
  25186. /**
  25187. * @class Event
  25188. * @static
  25189. */
  25190. var Event = Hammer.event = {
  25191. /**
  25192. * when touch events have been fired, this is true
  25193. * this is used to stop mouse events
  25194. * @property prevent_mouseevents
  25195. * @private
  25196. * @type {Boolean}
  25197. */
  25198. preventMouseEvents: false,
  25199. /**
  25200. * if EVENT_START has been fired
  25201. * @property started
  25202. * @private
  25203. * @type {Boolean}
  25204. */
  25205. started: false,
  25206. /**
  25207. * when the mouse is hold down, this is true
  25208. * @property should_detect
  25209. * @private
  25210. * @type {Boolean}
  25211. */
  25212. shouldDetect: false,
  25213. /**
  25214. * simple event binder with a hook and support for multiple types
  25215. * @method on
  25216. * @param {HTMLElement} element
  25217. * @param {String} type
  25218. * @param {Function} handler
  25219. * @param {Function} [hook]
  25220. * @param {Object} hook.type
  25221. */
  25222. on: function on(element, type, handler, hook) {
  25223. var types = type.split(' ');
  25224. Utils.each(types, function(type) {
  25225. Utils.on(element, type, handler);
  25226. hook && hook(type);
  25227. });
  25228. },
  25229. /**
  25230. * simple event unbinder with a hook and support for multiple types
  25231. * @method off
  25232. * @param {HTMLElement} element
  25233. * @param {String} type
  25234. * @param {Function} handler
  25235. * @param {Function} [hook]
  25236. * @param {Object} hook.type
  25237. */
  25238. off: function off(element, type, handler, hook) {
  25239. var types = type.split(' ');
  25240. Utils.each(types, function(type) {
  25241. Utils.off(element, type, handler);
  25242. hook && hook(type);
  25243. });
  25244. },
  25245. /**
  25246. * the core touch event handler.
  25247. * this finds out if we should to detect gestures
  25248. * @method onTouch
  25249. * @param {HTMLElement} element
  25250. * @param {String} eventType matches `EVENT_START|MOVE|END`
  25251. * @param {Function} handler
  25252. * @return onTouchHandler {Function} the core event handler
  25253. */
  25254. onTouch: function onTouch(element, eventType, handler) {
  25255. var self = this;
  25256. var onTouchHandler = function onTouchHandler(ev) {
  25257. var srcType = ev.type.toLowerCase(),
  25258. isPointer = Hammer.HAS_POINTEREVENTS,
  25259. isMouse = Utils.inStr(srcType, 'mouse'),
  25260. triggerType;
  25261. // if we are in a mouseevent, but there has been a touchevent triggered in this session
  25262. // we want to do nothing. simply break out of the event.
  25263. if(isMouse && self.preventMouseEvents) {
  25264. return;
  25265. // mousebutton must be down
  25266. } else if(isMouse && eventType == EVENT_START && ev.button === 0) {
  25267. self.preventMouseEvents = false;
  25268. self.shouldDetect = true;
  25269. } else if(isPointer && eventType == EVENT_START) {
  25270. self.shouldDetect = (ev.buttons === 1 || PointerEvent.matchType(POINTER_TOUCH, ev));
  25271. // just a valid start event, but no mouse
  25272. } else if(!isMouse && eventType == EVENT_START) {
  25273. self.preventMouseEvents = true;
  25274. self.shouldDetect = true;
  25275. }
  25276. // update the pointer event before entering the detection
  25277. if(isPointer && eventType != EVENT_END) {
  25278. PointerEvent.updatePointer(eventType, ev);
  25279. }
  25280. // we are in a touch/down state, so allowed detection of gestures
  25281. if(self.shouldDetect) {
  25282. triggerType = self.doDetect.call(self, ev, eventType, element, handler);
  25283. }
  25284. // ...and we are done with the detection
  25285. // so reset everything to start each detection totally fresh
  25286. if(triggerType == EVENT_END) {
  25287. self.preventMouseEvents = false;
  25288. self.shouldDetect = false;
  25289. PointerEvent.reset();
  25290. // update the pointerevent object after the detection
  25291. }
  25292. if(isPointer && eventType == EVENT_END) {
  25293. PointerEvent.updatePointer(eventType, ev);
  25294. }
  25295. };
  25296. this.on(element, EVENT_TYPES[eventType], onTouchHandler);
  25297. return onTouchHandler;
  25298. },
  25299. /**
  25300. * the core detection method
  25301. * this finds out what hammer-touch-events to trigger
  25302. * @method doDetect
  25303. * @param {Object} ev
  25304. * @param {String} eventType matches `EVENT_START|MOVE|END`
  25305. * @param {HTMLElement} element
  25306. * @param {Function} handler
  25307. * @return {String} triggerType matches `EVENT_START|MOVE|END`
  25308. */
  25309. doDetect: function doDetect(ev, eventType, element, handler) {
  25310. var touchList = this.getTouchList(ev, eventType);
  25311. var touchListLength = touchList.length;
  25312. var triggerType = eventType;
  25313. var triggerChange = touchList.trigger; // used by fakeMultitouch plugin
  25314. var changedLength = touchListLength;
  25315. // at each touchstart-like event we want also want to trigger a TOUCH event...
  25316. if(eventType == EVENT_START) {
  25317. triggerChange = EVENT_TOUCH;
  25318. // ...the same for a touchend-like event
  25319. } else if(eventType == EVENT_END) {
  25320. triggerChange = EVENT_RELEASE;
  25321. // keep track of how many touches have been removed
  25322. changedLength = touchList.length - ((ev.changedTouches) ? ev.changedTouches.length : 1);
  25323. }
  25324. // after there are still touches on the screen,
  25325. // we just want to trigger a MOVE event. so change the START or END to a MOVE
  25326. // but only after detection has been started, the first time we actualy want a START
  25327. if(changedLength > 0 && this.started) {
  25328. triggerType = EVENT_MOVE;
  25329. }
  25330. // detection has been started, we keep track of this, see above
  25331. this.started = true;
  25332. // generate some event data, some basic information
  25333. var evData = this.collectEventData(element, triggerType, touchList, ev);
  25334. // trigger the triggerType event before the change (TOUCH, RELEASE) events
  25335. // but the END event should be at last
  25336. if(eventType != EVENT_END) {
  25337. handler.call(Detection, evData);
  25338. }
  25339. // trigger a change (TOUCH, RELEASE) event, this means the length of the touches changed
  25340. if(triggerChange) {
  25341. evData.changedLength = changedLength;
  25342. evData.eventType = triggerChange;
  25343. handler.call(Detection, evData);
  25344. evData.eventType = triggerType;
  25345. delete evData.changedLength;
  25346. }
  25347. // trigger the END event
  25348. if(triggerType == EVENT_END) {
  25349. handler.call(Detection, evData);
  25350. // ...and we are done with the detection
  25351. // so reset everything to start each detection totally fresh
  25352. this.started = false;
  25353. }
  25354. return triggerType;
  25355. },
  25356. /**
  25357. * we have different events for each device/browser
  25358. * determine what we need and set them in the EVENT_TYPES constant
  25359. * the `onTouch` method is bind to these properties.
  25360. * @method determineEventTypes
  25361. * @return {Object} events
  25362. */
  25363. determineEventTypes: function determineEventTypes() {
  25364. var types;
  25365. if(Hammer.HAS_POINTEREVENTS) {
  25366. if(window.PointerEvent) {
  25367. types = [
  25368. 'pointerdown',
  25369. 'pointermove',
  25370. 'pointerup pointercancel lostpointercapture'
  25371. ];
  25372. } else {
  25373. types = [
  25374. 'MSPointerDown',
  25375. 'MSPointerMove',
  25376. 'MSPointerUp MSPointerCancel MSLostPointerCapture'
  25377. ];
  25378. }
  25379. } else if(Hammer.NO_MOUSEEVENTS) {
  25380. types = [
  25381. 'touchstart',
  25382. 'touchmove',
  25383. 'touchend touchcancel'
  25384. ];
  25385. } else {
  25386. types = [
  25387. 'touchstart mousedown',
  25388. 'touchmove mousemove',
  25389. 'touchend touchcancel mouseup'
  25390. ];
  25391. }
  25392. EVENT_TYPES[EVENT_START] = types[0];
  25393. EVENT_TYPES[EVENT_MOVE] = types[1];
  25394. EVENT_TYPES[EVENT_END] = types[2];
  25395. return EVENT_TYPES;
  25396. },
  25397. /**
  25398. * create touchList depending on the event
  25399. * @method getTouchList
  25400. * @param {Object} ev
  25401. * @param {String} eventType
  25402. * @return {Array} touches
  25403. */
  25404. getTouchList: function getTouchList(ev, eventType) {
  25405. // get the fake pointerEvent touchlist
  25406. if(Hammer.HAS_POINTEREVENTS) {
  25407. return PointerEvent.getTouchList();
  25408. }
  25409. // get the touchlist
  25410. if(ev.touches) {
  25411. if(eventType == EVENT_MOVE) {
  25412. return ev.touches;
  25413. }
  25414. var identifiers = [];
  25415. var concat = [].concat(Utils.toArray(ev.touches), Utils.toArray(ev.changedTouches));
  25416. var touchList = [];
  25417. Utils.each(concat, function(touch) {
  25418. if(Utils.inArray(identifiers, touch.identifier) === false) {
  25419. touchList.push(touch);
  25420. }
  25421. identifiers.push(touch.identifier);
  25422. });
  25423. return touchList;
  25424. }
  25425. // make fake touchList from mouse position
  25426. ev.identifier = 1;
  25427. return [ev];
  25428. },
  25429. /**
  25430. * collect basic event data
  25431. * @method collectEventData
  25432. * @param {HTMLElement} element
  25433. * @param {String} eventType matches `EVENT_START|MOVE|END`
  25434. * @param {Array} touches
  25435. * @param {Object} ev
  25436. * @return {Object} ev
  25437. */
  25438. collectEventData: function collectEventData(element, eventType, touches, ev) {
  25439. // find out pointerType
  25440. var pointerType = POINTER_TOUCH;
  25441. if(Utils.inStr(ev.type, 'mouse') || PointerEvent.matchType(POINTER_MOUSE, ev)) {
  25442. pointerType = POINTER_MOUSE;
  25443. } else if(PointerEvent.matchType(POINTER_PEN, ev)) {
  25444. pointerType = POINTER_PEN;
  25445. }
  25446. return {
  25447. center: Utils.getCenter(touches),
  25448. timeStamp: Date.now(),
  25449. target: ev.target,
  25450. touches: touches,
  25451. eventType: eventType,
  25452. pointerType: pointerType,
  25453. srcEvent: ev,
  25454. /**
  25455. * prevent the browser default actions
  25456. * mostly used to disable scrolling of the browser
  25457. */
  25458. preventDefault: function() {
  25459. var srcEvent = this.srcEvent;
  25460. srcEvent.preventManipulation && srcEvent.preventManipulation();
  25461. srcEvent.preventDefault && srcEvent.preventDefault();
  25462. },
  25463. /**
  25464. * stop bubbling the event up to its parents
  25465. */
  25466. stopPropagation: function() {
  25467. this.srcEvent.stopPropagation();
  25468. },
  25469. /**
  25470. * immediately stop gesture detection
  25471. * might be useful after a swipe was detected
  25472. * @return {*}
  25473. */
  25474. stopDetect: function() {
  25475. return Detection.stopDetect();
  25476. }
  25477. };
  25478. }
  25479. };
  25480. /**
  25481. * @module hammer
  25482. *
  25483. * @class PointerEvent
  25484. * @static
  25485. */
  25486. var PointerEvent = Hammer.PointerEvent = {
  25487. /**
  25488. * holds all pointers, by `identifier`
  25489. * @property pointers
  25490. * @type {Object}
  25491. */
  25492. pointers: {},
  25493. /**
  25494. * get the pointers as an array
  25495. * @method getTouchList
  25496. * @return {Array} touchlist
  25497. */
  25498. getTouchList: function getTouchList() {
  25499. var touchlist = [];
  25500. // we can use forEach since pointerEvents only is in IE10
  25501. Utils.each(this.pointers, function(pointer) {
  25502. touchlist.push(pointer);
  25503. });
  25504. return touchlist;
  25505. },
  25506. /**
  25507. * update the position of a pointer
  25508. * @method updatePointer
  25509. * @param {String} eventType matches `EVENT_START|MOVE|END`
  25510. * @param {Object} pointerEvent
  25511. */
  25512. updatePointer: function updatePointer(eventType, pointerEvent) {
  25513. if(eventType == EVENT_END || (eventType != EVENT_END && pointerEvent.buttons !== 1)) {
  25514. delete this.pointers[pointerEvent.pointerId];
  25515. } else {
  25516. pointerEvent.identifier = pointerEvent.pointerId;
  25517. this.pointers[pointerEvent.pointerId] = pointerEvent;
  25518. }
  25519. },
  25520. /**
  25521. * check if ev matches pointertype
  25522. * @method matchType
  25523. * @param {String} pointerType matches `POINTER_MOUSE|TOUCH|PEN`
  25524. * @param {PointerEvent} ev
  25525. */
  25526. matchType: function matchType(pointerType, ev) {
  25527. if(!ev.pointerType) {
  25528. return false;
  25529. }
  25530. var pt = ev.pointerType,
  25531. types = {};
  25532. types[POINTER_MOUSE] = (pt === (ev.MSPOINTER_TYPE_MOUSE || POINTER_MOUSE));
  25533. types[POINTER_TOUCH] = (pt === (ev.MSPOINTER_TYPE_TOUCH || POINTER_TOUCH));
  25534. types[POINTER_PEN] = (pt === (ev.MSPOINTER_TYPE_PEN || POINTER_PEN));
  25535. return types[pointerType];
  25536. },
  25537. /**
  25538. * reset the stored pointers
  25539. * @method reset
  25540. */
  25541. reset: function resetList() {
  25542. this.pointers = {};
  25543. }
  25544. };
  25545. /**
  25546. * @module hammer
  25547. *
  25548. * @class Detection
  25549. * @static
  25550. */
  25551. var Detection = Hammer.detection = {
  25552. // contains all registred Hammer.gestures in the correct order
  25553. gestures: [],
  25554. // data of the current Hammer.gesture detection session
  25555. current: null,
  25556. // the previous Hammer.gesture session data
  25557. // is a full clone of the previous gesture.current object
  25558. previous: null,
  25559. // when this becomes true, no gestures are fired
  25560. stopped: false,
  25561. /**
  25562. * start Hammer.gesture detection
  25563. * @method startDetect
  25564. * @param {Hammer.Instance} inst
  25565. * @param {Object} eventData
  25566. */
  25567. startDetect: function startDetect(inst, eventData) {
  25568. // already busy with a Hammer.gesture detection on an element
  25569. if(this.current) {
  25570. return;
  25571. }
  25572. this.stopped = false;
  25573. // holds current session
  25574. this.current = {
  25575. inst: inst, // reference to HammerInstance we're working for
  25576. startEvent: Utils.extend({}, eventData), // start eventData for distances, timing etc
  25577. lastEvent: false, // last eventData
  25578. lastCalcEvent: false, // last eventData for calculations.
  25579. futureCalcEvent: false, // last eventData for calculations.
  25580. lastCalcData: {}, // last lastCalcData
  25581. name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
  25582. };
  25583. this.detect(eventData);
  25584. },
  25585. /**
  25586. * Hammer.gesture detection
  25587. * @method detect
  25588. * @param {Object} eventData
  25589. * @return {any}
  25590. */
  25591. detect: function detect(eventData) {
  25592. if(!this.current || this.stopped) {
  25593. return;
  25594. }
  25595. // extend event data with calculations about scale, distance etc
  25596. eventData = this.extendEventData(eventData);
  25597. // hammer instance and instance options
  25598. var inst = this.current.inst,
  25599. instOptions = inst.options;
  25600. // call Hammer.gesture handlers
  25601. Utils.each(this.gestures, function triggerGesture(gesture) {
  25602. // only when the instance options have enabled this gesture
  25603. if(!this.stopped && inst.enabled && instOptions[gesture.name]) {
  25604. gesture.handler.call(gesture, eventData, inst);
  25605. }
  25606. }, this);
  25607. // store as previous event event
  25608. if(this.current) {
  25609. this.current.lastEvent = eventData;
  25610. }
  25611. if(eventData.eventType == EVENT_END) {
  25612. this.stopDetect();
  25613. }
  25614. return eventData;
  25615. },
  25616. /**
  25617. * clear the Hammer.gesture vars
  25618. * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
  25619. * to stop other Hammer.gestures from being fired
  25620. * @method stopDetect
  25621. */
  25622. stopDetect: function stopDetect() {
  25623. // clone current data to the store as the previous gesture
  25624. // used for the double tap gesture, since this is an other gesture detect session
  25625. this.previous = Utils.extend({}, this.current);
  25626. // reset the current
  25627. this.current = null;
  25628. this.stopped = true;
  25629. },
  25630. /**
  25631. * calculate velocity, angle and direction
  25632. * @method getVelocityData
  25633. * @param {Object} ev
  25634. * @param {Object} center
  25635. * @param {Number} deltaTime
  25636. * @param {Number} deltaX
  25637. * @param {Number} deltaY
  25638. */
  25639. getCalculatedData: function getCalculatedData(ev, center, deltaTime, deltaX, deltaY) {
  25640. var cur = this.current,
  25641. recalc = false,
  25642. calcEv = cur.lastCalcEvent,
  25643. calcData = cur.lastCalcData;
  25644. if(calcEv && ev.timeStamp - calcEv.timeStamp > Hammer.CALCULATE_INTERVAL) {
  25645. center = calcEv.center;
  25646. deltaTime = ev.timeStamp - calcEv.timeStamp;
  25647. deltaX = ev.center.clientX - calcEv.center.clientX;
  25648. deltaY = ev.center.clientY - calcEv.center.clientY;
  25649. recalc = true;
  25650. }
  25651. if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
  25652. cur.futureCalcEvent = ev;
  25653. }
  25654. if(!cur.lastCalcEvent || recalc) {
  25655. calcData.velocity = Utils.getVelocity(deltaTime, deltaX, deltaY);
  25656. calcData.angle = Utils.getAngle(center, ev.center);
  25657. calcData.direction = Utils.getDirection(center, ev.center);
  25658. cur.lastCalcEvent = cur.futureCalcEvent || ev;
  25659. cur.futureCalcEvent = ev;
  25660. }
  25661. ev.velocityX = calcData.velocity.x;
  25662. ev.velocityY = calcData.velocity.y;
  25663. ev.interimAngle = calcData.angle;
  25664. ev.interimDirection = calcData.direction;
  25665. },
  25666. /**
  25667. * extend eventData for Hammer.gestures
  25668. * @method extendEventData
  25669. * @param {Object} ev
  25670. * @return {Object} ev
  25671. */
  25672. extendEventData: function extendEventData(ev) {
  25673. var cur = this.current,
  25674. startEv = cur.startEvent,
  25675. lastEv = cur.lastEvent || startEv;
  25676. // update the start touchlist to calculate the scale/rotation
  25677. if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
  25678. startEv.touches = [];
  25679. Utils.each(ev.touches, function(touch) {
  25680. startEv.touches.push({
  25681. clientX: touch.clientX,
  25682. clientY: touch.clientY
  25683. });
  25684. });
  25685. }
  25686. var deltaTime = ev.timeStamp - startEv.timeStamp,
  25687. deltaX = ev.center.clientX - startEv.center.clientX,
  25688. deltaY = ev.center.clientY - startEv.center.clientY;
  25689. this.getCalculatedData(ev, lastEv.center, deltaTime, deltaX, deltaY);
  25690. Utils.extend(ev, {
  25691. startEvent: startEv,
  25692. deltaTime: deltaTime,
  25693. deltaX: deltaX,
  25694. deltaY: deltaY,
  25695. distance: Utils.getDistance(startEv.center, ev.center),
  25696. angle: Utils.getAngle(startEv.center, ev.center),
  25697. direction: Utils.getDirection(startEv.center, ev.center),
  25698. scale: Utils.getScale(startEv.touches, ev.touches),
  25699. rotation: Utils.getRotation(startEv.touches, ev.touches)
  25700. });
  25701. return ev;
  25702. },
  25703. /**
  25704. * register new gesture
  25705. * @method register
  25706. * @param {Object} gesture object, see `gestures/` for documentation
  25707. * @return {Array} gestures
  25708. */
  25709. register: function register(gesture) {
  25710. // add an enable gesture options if there is no given
  25711. var options = gesture.defaults || {};
  25712. if(options[gesture.name] === undefined) {
  25713. options[gesture.name] = true;
  25714. }
  25715. // extend Hammer default options with the Hammer.gesture options
  25716. Utils.extend(Hammer.defaults, options, true);
  25717. // set its index
  25718. gesture.index = gesture.index || 1000;
  25719. // add Hammer.gesture to the list
  25720. this.gestures.push(gesture);
  25721. // sort the list by index
  25722. this.gestures.sort(function(a, b) {
  25723. if(a.index < b.index) {
  25724. return -1;
  25725. }
  25726. if(a.index > b.index) {
  25727. return 1;
  25728. }
  25729. return 0;
  25730. });
  25731. return this.gestures;
  25732. }
  25733. };
  25734. /**
  25735. * @module hammer
  25736. */
  25737. /**
  25738. * create new hammer instance
  25739. * all methods should return the instance itself, so it is chainable.
  25740. *
  25741. * @class Instance
  25742. * @constructor
  25743. * @param {HTMLElement} element
  25744. * @param {Object} [options={}] options are merged with `Hammer.defaults`
  25745. * @return {Hammer.Instance}
  25746. */
  25747. Hammer.Instance = function(element, options) {
  25748. var self = this;
  25749. // setup HammerJS window events and register all gestures
  25750. // this also sets up the default options
  25751. setup();
  25752. /**
  25753. * @property element
  25754. * @type {HTMLElement}
  25755. */
  25756. this.element = element;
  25757. /**
  25758. * @property enabled
  25759. * @type {Boolean}
  25760. * @protected
  25761. */
  25762. this.enabled = true;
  25763. /**
  25764. * options, merged with the defaults
  25765. * options with an _ are converted to camelCase
  25766. * @property options
  25767. * @type {Object}
  25768. */
  25769. Utils.each(options, function(value, name) {
  25770. delete options[name];
  25771. options[Utils.toCamelCase(name)] = value;
  25772. });
  25773. this.options = Utils.extend(Utils.extend({}, Hammer.defaults), options || {});
  25774. // add some css to the element to prevent the browser from doing its native behavoir
  25775. if(this.options.behavior) {
  25776. Utils.toggleBehavior(this.element, this.options.behavior, true);
  25777. }
  25778. /**
  25779. * event start handler on the element to start the detection
  25780. * @property eventStartHandler
  25781. * @type {Object}
  25782. */
  25783. this.eventStartHandler = Event.onTouch(element, EVENT_START, function(ev) {
  25784. if(self.enabled && ev.eventType == EVENT_START) {
  25785. Detection.startDetect(self, ev);
  25786. } else if(ev.eventType == EVENT_TOUCH) {
  25787. Detection.detect(ev);
  25788. }
  25789. });
  25790. /**
  25791. * keep a list of user event handlers which needs to be removed when calling 'dispose'
  25792. * @property eventHandlers
  25793. * @type {Array}
  25794. */
  25795. this.eventHandlers = [];
  25796. };
  25797. Hammer.Instance.prototype = {
  25798. /**
  25799. * bind events to the instance
  25800. * @method on
  25801. * @chainable
  25802. * @param {String} gestures multiple gestures by splitting with a space
  25803. * @param {Function} handler
  25804. * @param {Object} handler.ev event object
  25805. */
  25806. on: function onEvent(gestures, handler) {
  25807. var self = this;
  25808. Event.on(self.element, gestures, handler, function(type) {
  25809. self.eventHandlers.push({ gesture: type, handler: handler });
  25810. });
  25811. return self;
  25812. },
  25813. /**
  25814. * unbind events to the instance
  25815. * @method off
  25816. * @chainable
  25817. * @param {String} gestures
  25818. * @param {Function} handler
  25819. */
  25820. off: function offEvent(gestures, handler) {
  25821. var self = this;
  25822. Event.off(self.element, gestures, handler, function(type) {
  25823. var index = Utils.inArray({ gesture: type, handler: handler });
  25824. if(index !== false) {
  25825. self.eventHandlers.splice(index, 1);
  25826. }
  25827. });
  25828. return self;
  25829. },
  25830. /**
  25831. * trigger gesture event
  25832. * @method trigger
  25833. * @chainable
  25834. * @param {String} gesture
  25835. * @param {Object} [eventData]
  25836. */
  25837. trigger: function triggerEvent(gesture, eventData) {
  25838. // optional
  25839. if(!eventData) {
  25840. eventData = {};
  25841. }
  25842. // create DOM event
  25843. var event = Hammer.DOCUMENT.createEvent('Event');
  25844. event.initEvent(gesture, true, true);
  25845. event.gesture = eventData;
  25846. // trigger on the target if it is in the instance element,
  25847. // this is for event delegation tricks
  25848. var element = this.element;
  25849. if(Utils.hasParent(eventData.target, element)) {
  25850. element = eventData.target;
  25851. }
  25852. element.dispatchEvent(event);
  25853. return this;
  25854. },
  25855. /**
  25856. * enable of disable hammer.js detection
  25857. * @method enable
  25858. * @chainable
  25859. * @param {Boolean} state
  25860. */
  25861. enable: function enable(state) {
  25862. this.enabled = state;
  25863. return this;
  25864. },
  25865. /**
  25866. * dispose this hammer instance
  25867. * @method dispose
  25868. * @return {Null}
  25869. */
  25870. dispose: function dispose() {
  25871. var i, eh;
  25872. // undo all changes made by stop_browser_behavior
  25873. Utils.toggleBehavior(this.element, this.options.behavior, false);
  25874. // unbind all custom event handlers
  25875. for(i = -1; (eh = this.eventHandlers[++i]);) {
  25876. Utils.off(this.element, eh.gesture, eh.handler);
  25877. }
  25878. this.eventHandlers = [];
  25879. // unbind the start event listener
  25880. Event.off(this.element, EVENT_TYPES[EVENT_START], this.eventStartHandler);
  25881. return null;
  25882. }
  25883. };
  25884. /**
  25885. * @module gestures
  25886. */
  25887. /**
  25888. * Move with x fingers (default 1) around on the page.
  25889. * Preventing the default browser behavior is a good way to improve feel and working.
  25890. * ````
  25891. * hammertime.on("drag", function(ev) {
  25892. * console.log(ev);
  25893. * ev.gesture.preventDefault();
  25894. * });
  25895. * ````
  25896. *
  25897. * @class Drag
  25898. * @static
  25899. */
  25900. /**
  25901. * @event drag
  25902. * @param {Object} ev
  25903. */
  25904. /**
  25905. * @event dragstart
  25906. * @param {Object} ev
  25907. */
  25908. /**
  25909. * @event dragend
  25910. * @param {Object} ev
  25911. */
  25912. /**
  25913. * @event drapleft
  25914. * @param {Object} ev
  25915. */
  25916. /**
  25917. * @event dragright
  25918. * @param {Object} ev
  25919. */
  25920. /**
  25921. * @event dragup
  25922. * @param {Object} ev
  25923. */
  25924. /**
  25925. * @event dragdown
  25926. * @param {Object} ev
  25927. */
  25928. /**
  25929. * @param {String} name
  25930. */
  25931. (function(name) {
  25932. var triggered = false;
  25933. function dragGesture(ev, inst) {
  25934. var cur = Detection.current;
  25935. // max touches
  25936. if(inst.options.dragMaxTouches > 0 &&
  25937. ev.touches.length > inst.options.dragMaxTouches) {
  25938. return;
  25939. }
  25940. switch(ev.eventType) {
  25941. case EVENT_START:
  25942. triggered = false;
  25943. break;
  25944. case EVENT_MOVE:
  25945. // when the distance we moved is too small we skip this gesture
  25946. // or we can be already in dragging
  25947. if(ev.distance < inst.options.dragMinDistance &&
  25948. cur.name != name) {
  25949. return;
  25950. }
  25951. var startCenter = cur.startEvent.center;
  25952. // we are dragging!
  25953. if(cur.name != name) {
  25954. cur.name = name;
  25955. if(inst.options.dragDistanceCorrection && ev.distance > 0) {
  25956. // When a drag is triggered, set the event center to dragMinDistance pixels from the original event center.
  25957. // Without this correction, the dragged distance would jumpstart at dragMinDistance pixels instead of at 0.
  25958. // It might be useful to save the original start point somewhere
  25959. var factor = Math.abs(inst.options.dragMinDistance / ev.distance);
  25960. startCenter.pageX += ev.deltaX * factor;
  25961. startCenter.pageY += ev.deltaY * factor;
  25962. startCenter.clientX += ev.deltaX * factor;
  25963. startCenter.clientY += ev.deltaY * factor;
  25964. // recalculate event data using new start point
  25965. ev = Detection.extendEventData(ev);
  25966. }
  25967. }
  25968. // lock drag to axis?
  25969. if(cur.lastEvent.dragLockToAxis ||
  25970. ( inst.options.dragLockToAxis &&
  25971. inst.options.dragLockMinDistance <= ev.distance
  25972. )) {
  25973. ev.dragLockToAxis = true;
  25974. }
  25975. // keep direction on the axis that the drag gesture started on
  25976. var lastDirection = cur.lastEvent.direction;
  25977. if(ev.dragLockToAxis && lastDirection !== ev.direction) {
  25978. if(Utils.isVertical(lastDirection)) {
  25979. ev.direction = (ev.deltaY < 0) ? DIRECTION_UP : DIRECTION_DOWN;
  25980. } else {
  25981. ev.direction = (ev.deltaX < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
  25982. }
  25983. }
  25984. // first time, trigger dragstart event
  25985. if(!triggered) {
  25986. inst.trigger(name + 'start', ev);
  25987. triggered = true;
  25988. }
  25989. // trigger events
  25990. inst.trigger(name, ev);
  25991. inst.trigger(name + ev.direction, ev);
  25992. var isVertical = Utils.isVertical(ev.direction);
  25993. // block the browser events
  25994. if((inst.options.dragBlockVertical && isVertical) ||
  25995. (inst.options.dragBlockHorizontal && !isVertical)) {
  25996. ev.preventDefault();
  25997. }
  25998. break;
  25999. case EVENT_RELEASE:
  26000. if(triggered && ev.changedLength <= inst.options.dragMaxTouches) {
  26001. inst.trigger(name + 'end', ev);
  26002. triggered = false;
  26003. }
  26004. break;
  26005. case EVENT_END:
  26006. triggered = false;
  26007. break;
  26008. }
  26009. }
  26010. Hammer.gestures.Drag = {
  26011. name: name,
  26012. index: 50,
  26013. handler: dragGesture,
  26014. defaults: {
  26015. /**
  26016. * minimal movement that have to be made before the drag event gets triggered
  26017. * @property dragMinDistance
  26018. * @type {Number}
  26019. * @default 10
  26020. */
  26021. dragMinDistance: 10,
  26022. /**
  26023. * Set dragDistanceCorrection to true to make the starting point of the drag
  26024. * be calculated from where the drag was triggered, not from where the touch started.
  26025. * Useful to avoid a jerk-starting drag, which can make fine-adjustments
  26026. * through dragging difficult, and be visually unappealing.
  26027. * @property dragDistanceCorrection
  26028. * @type {Boolean}
  26029. * @default true
  26030. */
  26031. dragDistanceCorrection: true,
  26032. /**
  26033. * set 0 for unlimited, but this can conflict with transform
  26034. * @property dragMaxTouches
  26035. * @type {Number}
  26036. * @default 1
  26037. */
  26038. dragMaxTouches: 1,
  26039. /**
  26040. * prevent default browser behavior when dragging occurs
  26041. * be careful with it, it makes the element a blocking element
  26042. * when you are using the drag gesture, it is a good practice to set this true
  26043. * @property dragBlockHorizontal
  26044. * @type {Boolean}
  26045. * @default false
  26046. */
  26047. dragBlockHorizontal: false,
  26048. /**
  26049. * same as `dragBlockHorizontal`, but for vertical movement
  26050. * @property dragBlockVertical
  26051. * @type {Boolean}
  26052. * @default false
  26053. */
  26054. dragBlockVertical: false,
  26055. /**
  26056. * dragLockToAxis keeps the drag gesture on the axis that it started on,
  26057. * It disallows vertical directions if the initial direction was horizontal, and vice versa.
  26058. * @property dragLockToAxis
  26059. * @type {Boolean}
  26060. * @default false
  26061. */
  26062. dragLockToAxis: false,
  26063. /**
  26064. * drag lock only kicks in when distance > dragLockMinDistance
  26065. * This way, locking occurs only when the distance has become large enough to reliably determine the direction
  26066. * @property dragLockMinDistance
  26067. * @type {Number}
  26068. * @default 25
  26069. */
  26070. dragLockMinDistance: 25
  26071. }
  26072. };
  26073. })('drag');
  26074. /**
  26075. * @module gestures
  26076. */
  26077. /**
  26078. * trigger a simple gesture event, so you can do anything in your handler.
  26079. * only usable if you know what your doing...
  26080. *
  26081. * @class Gesture
  26082. * @static
  26083. */
  26084. /**
  26085. * @event gesture
  26086. * @param {Object} ev
  26087. */
  26088. Hammer.gestures.Gesture = {
  26089. name: 'gesture',
  26090. index: 1337,
  26091. handler: function releaseGesture(ev, inst) {
  26092. inst.trigger(this.name, ev);
  26093. }
  26094. };
  26095. /**
  26096. * @module gestures
  26097. */
  26098. /**
  26099. * Touch stays at the same place for x time
  26100. *
  26101. * @class Hold
  26102. * @static
  26103. */
  26104. /**
  26105. * @event hold
  26106. * @param {Object} ev
  26107. */
  26108. /**
  26109. * @param {String} name
  26110. */
  26111. (function(name) {
  26112. var timer;
  26113. function holdGesture(ev, inst) {
  26114. var options = inst.options,
  26115. current = Detection.current;
  26116. switch(ev.eventType) {
  26117. case EVENT_START:
  26118. clearTimeout(timer);
  26119. // set the gesture so we can check in the timeout if it still is
  26120. current.name = name;
  26121. // set timer and if after the timeout it still is hold,
  26122. // we trigger the hold event
  26123. timer = setTimeout(function() {
  26124. if(current && current.name == name) {
  26125. inst.trigger(name, ev);
  26126. }
  26127. }, options.holdTimeout);
  26128. break;
  26129. case EVENT_MOVE:
  26130. if(ev.distance > options.holdThreshold) {
  26131. clearTimeout(timer);
  26132. }
  26133. break;
  26134. case EVENT_RELEASE:
  26135. clearTimeout(timer);
  26136. break;
  26137. }
  26138. }
  26139. Hammer.gestures.Hold = {
  26140. name: name,
  26141. index: 10,
  26142. defaults: {
  26143. /**
  26144. * @property holdTimeout
  26145. * @type {Number}
  26146. * @default 500
  26147. */
  26148. holdTimeout: 500,
  26149. /**
  26150. * movement allowed while holding
  26151. * @property holdThreshold
  26152. * @type {Number}
  26153. * @default 2
  26154. */
  26155. holdThreshold: 2
  26156. },
  26157. handler: holdGesture
  26158. };
  26159. })('hold');
  26160. /**
  26161. * @module gestures
  26162. */
  26163. /**
  26164. * when a touch is being released from the page
  26165. *
  26166. * @class Release
  26167. * @static
  26168. */
  26169. /**
  26170. * @event release
  26171. * @param {Object} ev
  26172. */
  26173. Hammer.gestures.Release = {
  26174. name: 'release',
  26175. index: Infinity,
  26176. handler: function releaseGesture(ev, inst) {
  26177. if(ev.eventType == EVENT_RELEASE) {
  26178. inst.trigger(this.name, ev);
  26179. }
  26180. }
  26181. };
  26182. /**
  26183. * @module gestures
  26184. */
  26185. /**
  26186. * triggers swipe events when the end velocity is above the threshold
  26187. * for best usage, set `preventDefault` (on the drag gesture) to `true`
  26188. * ````
  26189. * hammertime.on("dragleft swipeleft", function(ev) {
  26190. * console.log(ev);
  26191. * ev.gesture.preventDefault();
  26192. * });
  26193. * ````
  26194. *
  26195. * @class Swipe
  26196. * @static
  26197. */
  26198. /**
  26199. * @event swipe
  26200. * @param {Object} ev
  26201. */
  26202. /**
  26203. * @event swipeleft
  26204. * @param {Object} ev
  26205. */
  26206. /**
  26207. * @event swiperight
  26208. * @param {Object} ev
  26209. */
  26210. /**
  26211. * @event swipeup
  26212. * @param {Object} ev
  26213. */
  26214. /**
  26215. * @event swipedown
  26216. * @param {Object} ev
  26217. */
  26218. Hammer.gestures.Swipe = {
  26219. name: 'swipe',
  26220. index: 40,
  26221. defaults: {
  26222. /**
  26223. * @property swipeMinTouches
  26224. * @type {Number}
  26225. * @default 1
  26226. */
  26227. swipeMinTouches: 1,
  26228. /**
  26229. * @property swipeMaxTouches
  26230. * @type {Number}
  26231. * @default 1
  26232. */
  26233. swipeMaxTouches: 1,
  26234. /**
  26235. * horizontal swipe velocity
  26236. * @property swipeVelocityX
  26237. * @type {Number}
  26238. * @default 0.6
  26239. */
  26240. swipeVelocityX: 0.6,
  26241. /**
  26242. * vertical swipe velocity
  26243. * @property swipeVelocityY
  26244. * @type {Number}
  26245. * @default 0.6
  26246. */
  26247. swipeVelocityY: 0.6
  26248. },
  26249. handler: function swipeGesture(ev, inst) {
  26250. if(ev.eventType == EVENT_RELEASE) {
  26251. var touches = ev.touches.length,
  26252. options = inst.options;
  26253. // max touches
  26254. if(touches < options.swipeMinTouches ||
  26255. touches > options.swipeMaxTouches) {
  26256. return;
  26257. }
  26258. // when the distance we moved is too small we skip this gesture
  26259. // or we can be already in dragging
  26260. if(ev.velocityX > options.swipeVelocityX ||
  26261. ev.velocityY > options.swipeVelocityY) {
  26262. // trigger swipe events
  26263. inst.trigger(this.name, ev);
  26264. inst.trigger(this.name + ev.direction, ev);
  26265. }
  26266. }
  26267. }
  26268. };
  26269. /**
  26270. * @module gestures
  26271. */
  26272. /**
  26273. * Single tap and a double tap on a place
  26274. *
  26275. * @class Tap
  26276. * @static
  26277. */
  26278. /**
  26279. * @event tap
  26280. * @param {Object} ev
  26281. */
  26282. /**
  26283. * @event doubletap
  26284. * @param {Object} ev
  26285. */
  26286. /**
  26287. * @param {String} name
  26288. */
  26289. (function(name) {
  26290. var hasMoved = false;
  26291. function tapGesture(ev, inst) {
  26292. var options = inst.options,
  26293. current = Detection.current,
  26294. prev = Detection.previous,
  26295. sincePrev,
  26296. didDoubleTap;
  26297. switch(ev.eventType) {
  26298. case EVENT_START:
  26299. hasMoved = false;
  26300. break;
  26301. case EVENT_MOVE:
  26302. hasMoved = hasMoved || (ev.distance > options.tapMaxDistance);
  26303. break;
  26304. case EVENT_END:
  26305. if(!Utils.inStr(ev.srcEvent.type, 'cancel') && ev.deltaTime < options.tapMaxTime && !hasMoved) {
  26306. // previous gesture, for the double tap since these are two different gesture detections
  26307. sincePrev = prev && prev.lastEvent && ev.timeStamp - prev.lastEvent.timeStamp;
  26308. didDoubleTap = false;
  26309. // check if double tap
  26310. if(prev && prev.name == name &&
  26311. (sincePrev && sincePrev < options.doubleTapInterval) &&
  26312. ev.distance < options.doubleTapDistance) {
  26313. inst.trigger('doubletap', ev);
  26314. didDoubleTap = true;
  26315. }
  26316. // do a single tap
  26317. if(!didDoubleTap || options.tapAlways) {
  26318. current.name = name;
  26319. inst.trigger(current.name, ev);
  26320. }
  26321. }
  26322. break;
  26323. }
  26324. }
  26325. Hammer.gestures.Tap = {
  26326. name: name,
  26327. index: 100,
  26328. handler: tapGesture,
  26329. defaults: {
  26330. /**
  26331. * max time of a tap, this is for the slow tappers
  26332. * @property tapMaxTime
  26333. * @type {Number}
  26334. * @default 250
  26335. */
  26336. tapMaxTime: 250,
  26337. /**
  26338. * max distance of movement of a tap, this is for the slow tappers
  26339. * @property tapMaxDistance
  26340. * @type {Number}
  26341. * @default 10
  26342. */
  26343. tapMaxDistance: 10,
  26344. /**
  26345. * always trigger the `tap` event, even while double-tapping
  26346. * @property tapAlways
  26347. * @type {Boolean}
  26348. * @default true
  26349. */
  26350. tapAlways: true,
  26351. /**
  26352. * max distance between two taps
  26353. * @property doubleTapDistance
  26354. * @type {Number}
  26355. * @default 20
  26356. */
  26357. doubleTapDistance: 20,
  26358. /**
  26359. * max time between two taps
  26360. * @property doubleTapInterval
  26361. * @type {Number}
  26362. * @default 300
  26363. */
  26364. doubleTapInterval: 300
  26365. }
  26366. };
  26367. })('tap');
  26368. /**
  26369. * @module gestures
  26370. */
  26371. /**
  26372. * when a touch is being touched at the page
  26373. *
  26374. * @class Touch
  26375. * @static
  26376. */
  26377. /**
  26378. * @event touch
  26379. * @param {Object} ev
  26380. */
  26381. Hammer.gestures.Touch = {
  26382. name: 'touch',
  26383. index: -Infinity,
  26384. defaults: {
  26385. /**
  26386. * call preventDefault at touchstart, and makes the element blocking by disabling the scrolling of the page,
  26387. * but it improves gestures like transforming and dragging.
  26388. * be careful with using this, it can be very annoying for users to be stuck on the page
  26389. * @property preventDefault
  26390. * @type {Boolean}
  26391. * @default false
  26392. */
  26393. preventDefault: false,
  26394. /**
  26395. * disable mouse events, so only touch (or pen!) input triggers events
  26396. * @property preventMouse
  26397. * @type {Boolean}
  26398. * @default false
  26399. */
  26400. preventMouse: false
  26401. },
  26402. handler: function touchGesture(ev, inst) {
  26403. if(inst.options.preventMouse && ev.pointerType == POINTER_MOUSE) {
  26404. ev.stopDetect();
  26405. return;
  26406. }
  26407. if(inst.options.preventDefault) {
  26408. ev.preventDefault();
  26409. }
  26410. if(ev.eventType == EVENT_TOUCH) {
  26411. inst.trigger('touch', ev);
  26412. }
  26413. }
  26414. };
  26415. /**
  26416. * @module gestures
  26417. */
  26418. /**
  26419. * User want to scale or rotate with 2 fingers
  26420. * Preventing the default browser behavior is a good way to improve feel and working. This can be done with the
  26421. * `preventDefault` option.
  26422. *
  26423. * @class Transform
  26424. * @static
  26425. */
  26426. /**
  26427. * @event transform
  26428. * @param {Object} ev
  26429. */
  26430. /**
  26431. * @event transformstart
  26432. * @param {Object} ev
  26433. */
  26434. /**
  26435. * @event transformend
  26436. * @param {Object} ev
  26437. */
  26438. /**
  26439. * @event pinchin
  26440. * @param {Object} ev
  26441. */
  26442. /**
  26443. * @event pinchout
  26444. * @param {Object} ev
  26445. */
  26446. /**
  26447. * @event rotate
  26448. * @param {Object} ev
  26449. */
  26450. /**
  26451. * @param {String} name
  26452. */
  26453. (function(name) {
  26454. var triggered = false;
  26455. function transformGesture(ev, inst) {
  26456. switch(ev.eventType) {
  26457. case EVENT_START:
  26458. triggered = false;
  26459. break;
  26460. case EVENT_MOVE:
  26461. // at least multitouch
  26462. if(ev.touches.length < 2) {
  26463. return;
  26464. }
  26465. var scaleThreshold = Math.abs(1 - ev.scale);
  26466. var rotationThreshold = Math.abs(ev.rotation);
  26467. // when the distance we moved is too small we skip this gesture
  26468. // or we can be already in dragging
  26469. if(scaleThreshold < inst.options.transformMinScale &&
  26470. rotationThreshold < inst.options.transformMinRotation) {
  26471. return;
  26472. }
  26473. // we are transforming!
  26474. Detection.current.name = name;
  26475. // first time, trigger dragstart event
  26476. if(!triggered) {
  26477. inst.trigger(name + 'start', ev);
  26478. triggered = true;
  26479. }
  26480. inst.trigger(name, ev); // basic transform event
  26481. // trigger rotate event
  26482. if(rotationThreshold > inst.options.transformMinRotation) {
  26483. inst.trigger('rotate', ev);
  26484. }
  26485. // trigger pinch event
  26486. if(scaleThreshold > inst.options.transformMinScale) {
  26487. inst.trigger('pinch', ev);
  26488. inst.trigger('pinch' + (ev.scale < 1 ? 'in' : 'out'), ev);
  26489. }
  26490. break;
  26491. case EVENT_RELEASE:
  26492. if(triggered && ev.changedLength < 2) {
  26493. inst.trigger(name + 'end', ev);
  26494. triggered = false;
  26495. }
  26496. break;
  26497. }
  26498. }
  26499. Hammer.gestures.Transform = {
  26500. name: name,
  26501. index: 45,
  26502. defaults: {
  26503. /**
  26504. * minimal scale factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
  26505. * @property transformMinScale
  26506. * @type {Number}
  26507. * @default 0.01
  26508. */
  26509. transformMinScale: 0.01,
  26510. /**
  26511. * rotation in degrees
  26512. * @property transformMinRotation
  26513. * @type {Number}
  26514. * @default 1
  26515. */
  26516. transformMinRotation: 1
  26517. },
  26518. handler: transformGesture
  26519. };
  26520. })('transform');
  26521. /**
  26522. * @module hammer
  26523. */
  26524. // AMD export
  26525. if(true) {
  26526. !(__WEBPACK_AMD_DEFINE_RESULT__ = function() {
  26527. return Hammer;
  26528. }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  26529. // commonjs export
  26530. } else if(typeof module !== 'undefined' && module.exports) {
  26531. module.exports = Hammer;
  26532. // browser export
  26533. } else {
  26534. window.Hammer = Hammer;
  26535. }
  26536. })(window);
  26537. /***/ },
  26538. /* 60 */
  26539. /***/ function(module, exports, __webpack_require__) {
  26540. var util = __webpack_require__(1);
  26541. var RepulsionMixin = __webpack_require__(67);
  26542. var HierarchialRepulsionMixin = __webpack_require__(68);
  26543. var BarnesHutMixin = __webpack_require__(69);
  26544. /**
  26545. * Toggling barnes Hut calculation on and off.
  26546. *
  26547. * @private
  26548. */
  26549. exports._toggleBarnesHut = function () {
  26550. this.constants.physics.barnesHut.enabled = !this.constants.physics.barnesHut.enabled;
  26551. this._loadSelectedForceSolver();
  26552. this.moving = true;
  26553. this.start();
  26554. };
  26555. /**
  26556. * This loads the node force solver based on the barnes hut or repulsion algorithm
  26557. *
  26558. * @private
  26559. */
  26560. exports._loadSelectedForceSolver = function () {
  26561. // this overloads the this._calculateNodeForces
  26562. if (this.constants.physics.barnesHut.enabled == true) {
  26563. this._clearMixin(RepulsionMixin);
  26564. this._clearMixin(HierarchialRepulsionMixin);
  26565. this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity;
  26566. this.constants.physics.springLength = this.constants.physics.barnesHut.springLength;
  26567. this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant;
  26568. this.constants.physics.damping = this.constants.physics.barnesHut.damping;
  26569. this._loadMixin(BarnesHutMixin);
  26570. }
  26571. else if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  26572. this._clearMixin(BarnesHutMixin);
  26573. this._clearMixin(RepulsionMixin);
  26574. this.constants.physics.centralGravity = this.constants.physics.hierarchicalRepulsion.centralGravity;
  26575. this.constants.physics.springLength = this.constants.physics.hierarchicalRepulsion.springLength;
  26576. this.constants.physics.springConstant = this.constants.physics.hierarchicalRepulsion.springConstant;
  26577. this.constants.physics.damping = this.constants.physics.hierarchicalRepulsion.damping;
  26578. this._loadMixin(HierarchialRepulsionMixin);
  26579. }
  26580. else {
  26581. this._clearMixin(BarnesHutMixin);
  26582. this._clearMixin(HierarchialRepulsionMixin);
  26583. this.barnesHutTree = undefined;
  26584. this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity;
  26585. this.constants.physics.springLength = this.constants.physics.repulsion.springLength;
  26586. this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant;
  26587. this.constants.physics.damping = this.constants.physics.repulsion.damping;
  26588. this._loadMixin(RepulsionMixin);
  26589. }
  26590. };
  26591. /**
  26592. * Before calculating the forces, we check if we need to cluster to keep up performance and we check
  26593. * if there is more than one node. If it is just one node, we dont calculate anything.
  26594. *
  26595. * @private
  26596. */
  26597. exports._initializeForceCalculation = function () {
  26598. // stop calculation if there is only one node
  26599. if (this.nodeIndices.length == 1) {
  26600. this.nodes[this.nodeIndices[0]]._setForce(0, 0);
  26601. }
  26602. else {
  26603. // if there are too many nodes on screen, we cluster without repositioning
  26604. if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
  26605. this.clusterToFit(this.constants.clustering.reduceToNodes, false);
  26606. }
  26607. // we now start the force calculation
  26608. this._calculateForces();
  26609. }
  26610. };
  26611. /**
  26612. * Calculate the external forces acting on the nodes
  26613. * Forces are caused by: edges, repulsing forces between nodes, gravity
  26614. * @private
  26615. */
  26616. exports._calculateForces = function () {
  26617. // Gravity is required to keep separated groups from floating off
  26618. // the forces are reset to zero in this loop by using _setForce instead
  26619. // of _addForce
  26620. this._calculateGravitationalForces();
  26621. this._calculateNodeForces();
  26622. if (this.constants.physics.springConstant > 0) {
  26623. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  26624. this._calculateSpringForcesWithSupport();
  26625. }
  26626. else {
  26627. if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  26628. this._calculateHierarchicalSpringForces();
  26629. }
  26630. else {
  26631. this._calculateSpringForces();
  26632. }
  26633. }
  26634. }
  26635. };
  26636. /**
  26637. * Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also
  26638. * handled in the calculateForces function. We then use a quadratic curve with the center node as control.
  26639. * This function joins the datanodes and invisible (called support) nodes into one object.
  26640. * We do this so we do not contaminate this.nodes with the support nodes.
  26641. *
  26642. * @private
  26643. */
  26644. exports._updateCalculationNodes = function () {
  26645. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  26646. this.calculationNodes = {};
  26647. this.calculationNodeIndices = [];
  26648. for (var nodeId in this.nodes) {
  26649. if (this.nodes.hasOwnProperty(nodeId)) {
  26650. this.calculationNodes[nodeId] = this.nodes[nodeId];
  26651. }
  26652. }
  26653. var supportNodes = this.sectors['support']['nodes'];
  26654. for (var supportNodeId in supportNodes) {
  26655. if (supportNodes.hasOwnProperty(supportNodeId)) {
  26656. if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) {
  26657. this.calculationNodes[supportNodeId] = supportNodes[supportNodeId];
  26658. }
  26659. else {
  26660. supportNodes[supportNodeId]._setForce(0, 0);
  26661. }
  26662. }
  26663. }
  26664. for (var idx in this.calculationNodes) {
  26665. if (this.calculationNodes.hasOwnProperty(idx)) {
  26666. this.calculationNodeIndices.push(idx);
  26667. }
  26668. }
  26669. }
  26670. else {
  26671. this.calculationNodes = this.nodes;
  26672. this.calculationNodeIndices = this.nodeIndices;
  26673. }
  26674. };
  26675. /**
  26676. * this function applies the central gravity effect to keep groups from floating off
  26677. *
  26678. * @private
  26679. */
  26680. exports._calculateGravitationalForces = function () {
  26681. var dx, dy, distance, node, i;
  26682. var nodes = this.calculationNodes;
  26683. var gravity = this.constants.physics.centralGravity;
  26684. var gravityForce = 0;
  26685. for (i = 0; i < this.calculationNodeIndices.length; i++) {
  26686. node = nodes[this.calculationNodeIndices[i]];
  26687. node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters.
  26688. // gravity does not apply when we are in a pocket sector
  26689. if (this._sector() == "default" && gravity != 0) {
  26690. dx = -node.x;
  26691. dy = -node.y;
  26692. distance = Math.sqrt(dx * dx + dy * dy);
  26693. gravityForce = (distance == 0) ? 0 : (gravity / distance);
  26694. node.fx = dx * gravityForce;
  26695. node.fy = dy * gravityForce;
  26696. }
  26697. else {
  26698. node.fx = 0;
  26699. node.fy = 0;
  26700. }
  26701. }
  26702. };
  26703. /**
  26704. * this function calculates the effects of the springs in the case of unsmooth curves.
  26705. *
  26706. * @private
  26707. */
  26708. exports._calculateSpringForces = function () {
  26709. var edgeLength, edge, edgeId;
  26710. var dx, dy, fx, fy, springForce, distance;
  26711. var edges = this.edges;
  26712. // forces caused by the edges, modelled as springs
  26713. for (edgeId in edges) {
  26714. if (edges.hasOwnProperty(edgeId)) {
  26715. edge = edges[edgeId];
  26716. if (edge.connected) {
  26717. // only calculate forces if nodes are in the same sector
  26718. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  26719. edgeLength = edge.physics.springLength;
  26720. // this implies that the edges between big clusters are longer
  26721. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  26722. dx = (edge.from.x - edge.to.x);
  26723. dy = (edge.from.y - edge.to.y);
  26724. distance = Math.sqrt(dx * dx + dy * dy);
  26725. if (distance == 0) {
  26726. distance = 0.01;
  26727. }
  26728. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  26729. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  26730. fx = dx * springForce;
  26731. fy = dy * springForce;
  26732. edge.from.fx += fx;
  26733. edge.from.fy += fy;
  26734. edge.to.fx -= fx;
  26735. edge.to.fy -= fy;
  26736. }
  26737. }
  26738. }
  26739. }
  26740. };
  26741. /**
  26742. * This function calculates the springforces on the nodes, accounting for the support nodes.
  26743. *
  26744. * @private
  26745. */
  26746. exports._calculateSpringForcesWithSupport = function () {
  26747. var edgeLength, edge, edgeId, combinedClusterSize;
  26748. var edges = this.edges;
  26749. // forces caused by the edges, modelled as springs
  26750. for (edgeId in edges) {
  26751. if (edges.hasOwnProperty(edgeId)) {
  26752. edge = edges[edgeId];
  26753. if (edge.connected) {
  26754. // only calculate forces if nodes are in the same sector
  26755. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  26756. if (edge.via != null) {
  26757. var node1 = edge.to;
  26758. var node2 = edge.via;
  26759. var node3 = edge.from;
  26760. edgeLength = edge.physics.springLength;
  26761. combinedClusterSize = node1.clusterSize + node3.clusterSize - 2;
  26762. // this implies that the edges between big clusters are longer
  26763. edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth;
  26764. this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
  26765. this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
  26766. }
  26767. }
  26768. }
  26769. }
  26770. }
  26771. };
  26772. /**
  26773. * This is the code actually performing the calculation for the function above. It is split out to avoid repetition.
  26774. *
  26775. * @param node1
  26776. * @param node2
  26777. * @param edgeLength
  26778. * @private
  26779. */
  26780. exports._calculateSpringForce = function (node1, node2, edgeLength) {
  26781. var dx, dy, fx, fy, springForce, distance;
  26782. dx = (node1.x - node2.x);
  26783. dy = (node1.y - node2.y);
  26784. distance = Math.sqrt(dx * dx + dy * dy);
  26785. if (distance == 0) {
  26786. distance = 0.01;
  26787. }
  26788. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  26789. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  26790. fx = dx * springForce;
  26791. fy = dy * springForce;
  26792. node1.fx += fx;
  26793. node1.fy += fy;
  26794. node2.fx -= fx;
  26795. node2.fy -= fy;
  26796. };
  26797. exports._cleanupPhysicsConfiguration = function() {
  26798. if (this.physicsConfiguration !== undefined) {
  26799. while (this.physicsConfiguration.hasChildNodes()) {
  26800. this.physicsConfiguration.removeChild(this.physicsConfiguration.firstChild);
  26801. }
  26802. this.physicsConfiguration.parentNode.removeChild(this.physicsConfiguration);
  26803. this.physicsConfiguration = undefined;
  26804. }
  26805. }
  26806. /**
  26807. * Load the HTML for the physics config and bind it
  26808. * @private
  26809. */
  26810. exports._loadPhysicsConfiguration = function () {
  26811. if (this.physicsConfiguration === undefined) {
  26812. this.backupConstants = {};
  26813. util.deepExtend(this.backupConstants,this.constants);
  26814. var maxGravitational = Math.max(20000, (-1 * this.constants.physics.barnesHut.gravitationalConstant) * 10);
  26815. var maxSpring = Math.min(0.05, this.constants.physics.barnesHut.springConstant * 10)
  26816. var hierarchicalLayoutDirections = ["LR", "RL", "UD", "DU"];
  26817. this.physicsConfiguration = document.createElement('div');
  26818. this.physicsConfiguration.className = "PhysicsConfiguration";
  26819. this.physicsConfiguration.innerHTML = '' +
  26820. '<table><tr><td><b>Simulation Mode:</b></td></tr>' +
  26821. '<tr>' +
  26822. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod1" value="BH" checked="checked">Barnes Hut</td>' +
  26823. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod2" value="R">Repulsion</td>' +
  26824. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod3" value="H">Hierarchical</td>' +
  26825. '</tr>' +
  26826. '</table>' +
  26827. '<table id="graph_BH_table" style="display:none">' +
  26828. '<tr><td><b>Barnes Hut</b></td></tr>' +
  26829. '<tr>' +
  26830. '<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>' +
  26831. '</tr>' +
  26832. '<tr>' +
  26833. '<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>' +
  26834. '</tr>' +
  26835. '<tr>' +
  26836. '<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>' +
  26837. '</tr>' +
  26838. '<tr>' +
  26839. '<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>' +
  26840. '</tr>' +
  26841. '<tr>' +
  26842. '<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>' +
  26843. '</tr>' +
  26844. '</table>' +
  26845. '<table id="graph_R_table" style="display:none">' +
  26846. '<tr><td><b>Repulsion</b></td></tr>' +
  26847. '<tr>' +
  26848. '<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>' +
  26849. '</tr>' +
  26850. '<tr>' +
  26851. '<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>' +
  26852. '</tr>' +
  26853. '<tr>' +
  26854. '<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>' +
  26855. '</tr>' +
  26856. '<tr>' +
  26857. '<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>' +
  26858. '</tr>' +
  26859. '<tr>' +
  26860. '<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>' +
  26861. '</tr>' +
  26862. '</table>' +
  26863. '<table id="graph_H_table" style="display:none">' +
  26864. '<tr><td width="150"><b>Hierarchical</b></td></tr>' +
  26865. '<tr>' +
  26866. '<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>' +
  26867. '</tr>' +
  26868. '<tr>' +
  26869. '<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>' +
  26870. '</tr>' +
  26871. '<tr>' +
  26872. '<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>' +
  26873. '</tr>' +
  26874. '<tr>' +
  26875. '<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>' +
  26876. '</tr>' +
  26877. '<tr>' +
  26878. '<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>' +
  26879. '</tr>' +
  26880. '<tr>' +
  26881. '<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>' +
  26882. '</tr>' +
  26883. '<tr>' +
  26884. '<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>' +
  26885. '</tr>' +
  26886. '<tr>' +
  26887. '<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>' +
  26888. '</tr>' +
  26889. '</table>' +
  26890. '<table><tr><td><b>Options:</b></td></tr>' +
  26891. '<tr>' +
  26892. '<td width="180px"><input type="button" id="graph_toggleSmooth" value="Toggle smoothCurves" style="width:150px"></td>' +
  26893. '<td width="180px"><input type="button" id="graph_repositionNodes" value="Reinitialize" style="width:150px"></td>' +
  26894. '<td width="180px"><input type="button" id="graph_generateOptions" value="Generate Options" style="width:150px"></td>' +
  26895. '</tr>' +
  26896. '</table>'
  26897. this.containerElement.parentElement.insertBefore(this.physicsConfiguration, this.containerElement);
  26898. this.optionsDiv = document.createElement("div");
  26899. this.optionsDiv.style.fontSize = "14px";
  26900. this.optionsDiv.style.fontFamily = "verdana";
  26901. this.containerElement.parentElement.insertBefore(this.optionsDiv, this.containerElement);
  26902. var rangeElement;
  26903. rangeElement = document.getElementById('graph_BH_gc');
  26904. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_gc', -1, "physics_barnesHut_gravitationalConstant");
  26905. rangeElement = document.getElementById('graph_BH_cg');
  26906. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_cg', 1, "physics_centralGravity");
  26907. rangeElement = document.getElementById('graph_BH_sc');
  26908. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sc', 1, "physics_springConstant");
  26909. rangeElement = document.getElementById('graph_BH_sl');
  26910. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sl', 1, "physics_springLength");
  26911. rangeElement = document.getElementById('graph_BH_damp');
  26912. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_damp', 1, "physics_damping");
  26913. rangeElement = document.getElementById('graph_R_nd');
  26914. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_nd', 1, "physics_repulsion_nodeDistance");
  26915. rangeElement = document.getElementById('graph_R_cg');
  26916. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_cg', 1, "physics_centralGravity");
  26917. rangeElement = document.getElementById('graph_R_sc');
  26918. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sc', 1, "physics_springConstant");
  26919. rangeElement = document.getElementById('graph_R_sl');
  26920. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sl', 1, "physics_springLength");
  26921. rangeElement = document.getElementById('graph_R_damp');
  26922. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_damp', 1, "physics_damping");
  26923. rangeElement = document.getElementById('graph_H_nd');
  26924. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  26925. rangeElement = document.getElementById('graph_H_cg');
  26926. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_cg', 1, "physics_centralGravity");
  26927. rangeElement = document.getElementById('graph_H_sc');
  26928. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sc', 1, "physics_springConstant");
  26929. rangeElement = document.getElementById('graph_H_sl');
  26930. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sl', 1, "physics_springLength");
  26931. rangeElement = document.getElementById('graph_H_damp');
  26932. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_damp', 1, "physics_damping");
  26933. rangeElement = document.getElementById('graph_H_direction');
  26934. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_direction', hierarchicalLayoutDirections, "hierarchicalLayout_direction");
  26935. rangeElement = document.getElementById('graph_H_levsep');
  26936. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_levsep', 1, "hierarchicalLayout_levelSeparation");
  26937. rangeElement = document.getElementById('graph_H_nspac');
  26938. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nspac', 1, "hierarchicalLayout_nodeSpacing");
  26939. var radioButton1 = document.getElementById("graph_physicsMethod1");
  26940. var radioButton2 = document.getElementById("graph_physicsMethod2");
  26941. var radioButton3 = document.getElementById("graph_physicsMethod3");
  26942. radioButton2.checked = true;
  26943. if (this.constants.physics.barnesHut.enabled) {
  26944. radioButton1.checked = true;
  26945. }
  26946. if (this.constants.hierarchicalLayout.enabled) {
  26947. radioButton3.checked = true;
  26948. }
  26949. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  26950. var graph_repositionNodes = document.getElementById("graph_repositionNodes");
  26951. var graph_generateOptions = document.getElementById("graph_generateOptions");
  26952. graph_toggleSmooth.onclick = graphToggleSmoothCurves.bind(this);
  26953. graph_repositionNodes.onclick = graphRepositionNodes.bind(this);
  26954. graph_generateOptions.onclick = graphGenerateOptions.bind(this);
  26955. if (this.constants.smoothCurves == true && this.constants.dynamicSmoothCurves == false) {
  26956. graph_toggleSmooth.style.background = "#A4FF56";
  26957. }
  26958. else {
  26959. graph_toggleSmooth.style.background = "#FF8532";
  26960. }
  26961. switchConfigurations.apply(this);
  26962. radioButton1.onchange = switchConfigurations.bind(this);
  26963. radioButton2.onchange = switchConfigurations.bind(this);
  26964. radioButton3.onchange = switchConfigurations.bind(this);
  26965. }
  26966. };
  26967. /**
  26968. * This overwrites the this.constants.
  26969. *
  26970. * @param constantsVariableName
  26971. * @param value
  26972. * @private
  26973. */
  26974. exports._overWriteGraphConstants = function (constantsVariableName, value) {
  26975. var nameArray = constantsVariableName.split("_");
  26976. if (nameArray.length == 1) {
  26977. this.constants[nameArray[0]] = value;
  26978. }
  26979. else if (nameArray.length == 2) {
  26980. this.constants[nameArray[0]][nameArray[1]] = value;
  26981. }
  26982. else if (nameArray.length == 3) {
  26983. this.constants[nameArray[0]][nameArray[1]][nameArray[2]] = value;
  26984. }
  26985. };
  26986. /**
  26987. * this function is bound to the toggle smooth curves button. That is also why it is not in the prototype.
  26988. */
  26989. function graphToggleSmoothCurves () {
  26990. this.constants.smoothCurves.enabled = !this.constants.smoothCurves.enabled;
  26991. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  26992. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  26993. else {graph_toggleSmooth.style.background = "#FF8532";}
  26994. this._configureSmoothCurves(false);
  26995. }
  26996. /**
  26997. * this function is used to scramble the nodes
  26998. *
  26999. */
  27000. function graphRepositionNodes () {
  27001. for (var nodeId in this.calculationNodes) {
  27002. if (this.calculationNodes.hasOwnProperty(nodeId)) {
  27003. this.calculationNodes[nodeId].vx = 0; this.calculationNodes[nodeId].vy = 0;
  27004. this.calculationNodes[nodeId].fx = 0; this.calculationNodes[nodeId].fy = 0;
  27005. }
  27006. }
  27007. if (this.constants.hierarchicalLayout.enabled == true) {
  27008. this._setupHierarchicalLayout();
  27009. showValueOfRange.call(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  27010. showValueOfRange.call(this, 'graph_H_cg', 1, "physics_centralGravity");
  27011. showValueOfRange.call(this, 'graph_H_sc', 1, "physics_springConstant");
  27012. showValueOfRange.call(this, 'graph_H_sl', 1, "physics_springLength");
  27013. showValueOfRange.call(this, 'graph_H_damp', 1, "physics_damping");
  27014. }
  27015. else {
  27016. this.repositionNodes();
  27017. }
  27018. this.moving = true;
  27019. this.start();
  27020. }
  27021. /**
  27022. * this is used to generate an options file from the playing with physics system.
  27023. */
  27024. function graphGenerateOptions () {
  27025. var options = "No options are required, default values used.";
  27026. var optionsSpecific = [];
  27027. var radioButton1 = document.getElementById("graph_physicsMethod1");
  27028. var radioButton2 = document.getElementById("graph_physicsMethod2");
  27029. if (radioButton1.checked == true) {
  27030. if (this.constants.physics.barnesHut.gravitationalConstant != this.backupConstants.physics.barnesHut.gravitationalConstant) {optionsSpecific.push("gravitationalConstant: " + this.constants.physics.barnesHut.gravitationalConstant);}
  27031. if (this.constants.physics.centralGravity != this.backupConstants.physics.barnesHut.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  27032. if (this.constants.physics.springLength != this.backupConstants.physics.barnesHut.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  27033. if (this.constants.physics.springConstant != this.backupConstants.physics.barnesHut.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  27034. if (this.constants.physics.damping != this.backupConstants.physics.barnesHut.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  27035. if (optionsSpecific.length != 0) {
  27036. options = "var options = {";
  27037. options += "physics: {barnesHut: {";
  27038. for (var i = 0; i < optionsSpecific.length; i++) {
  27039. options += optionsSpecific[i];
  27040. if (i < optionsSpecific.length - 1) {
  27041. options += ", "
  27042. }
  27043. }
  27044. options += '}}'
  27045. }
  27046. if (this.constants.smoothCurves.enabled != this.backupConstants.smoothCurves.enabled) {
  27047. if (optionsSpecific.length == 0) {options = "var options = {";}
  27048. else {options += ", "}
  27049. options += "smoothCurves: " + this.constants.smoothCurves.enabled;
  27050. }
  27051. if (options != "No options are required, default values used.") {
  27052. options += '};'
  27053. }
  27054. }
  27055. else if (radioButton2.checked == true) {
  27056. options = "var options = {";
  27057. options += "physics: {barnesHut: {enabled: false}";
  27058. if (this.constants.physics.repulsion.nodeDistance != this.backupConstants.physics.repulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.repulsion.nodeDistance);}
  27059. if (this.constants.physics.centralGravity != this.backupConstants.physics.repulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  27060. if (this.constants.physics.springLength != this.backupConstants.physics.repulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  27061. if (this.constants.physics.springConstant != this.backupConstants.physics.repulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  27062. if (this.constants.physics.damping != this.backupConstants.physics.repulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  27063. if (optionsSpecific.length != 0) {
  27064. options += ", repulsion: {";
  27065. for (var i = 0; i < optionsSpecific.length; i++) {
  27066. options += optionsSpecific[i];
  27067. if (i < optionsSpecific.length - 1) {
  27068. options += ", "
  27069. }
  27070. }
  27071. options += '}}'
  27072. }
  27073. if (optionsSpecific.length == 0) {options += "}"}
  27074. if (this.constants.smoothCurves != this.backupConstants.smoothCurves) {
  27075. options += ", smoothCurves: " + this.constants.smoothCurves;
  27076. }
  27077. options += '};'
  27078. }
  27079. else {
  27080. options = "var options = {";
  27081. if (this.constants.physics.hierarchicalRepulsion.nodeDistance != this.backupConstants.physics.hierarchicalRepulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.hierarchicalRepulsion.nodeDistance);}
  27082. if (this.constants.physics.centralGravity != this.backupConstants.physics.hierarchicalRepulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  27083. if (this.constants.physics.springLength != this.backupConstants.physics.hierarchicalRepulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  27084. if (this.constants.physics.springConstant != this.backupConstants.physics.hierarchicalRepulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  27085. if (this.constants.physics.damping != this.backupConstants.physics.hierarchicalRepulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  27086. if (optionsSpecific.length != 0) {
  27087. options += "physics: {hierarchicalRepulsion: {";
  27088. for (var i = 0; i < optionsSpecific.length; i++) {
  27089. options += optionsSpecific[i];
  27090. if (i < optionsSpecific.length - 1) {
  27091. options += ", ";
  27092. }
  27093. }
  27094. options += '}},';
  27095. }
  27096. options += 'hierarchicalLayout: {';
  27097. optionsSpecific = [];
  27098. if (this.constants.hierarchicalLayout.direction != this.backupConstants.hierarchicalLayout.direction) {optionsSpecific.push("direction: " + this.constants.hierarchicalLayout.direction);}
  27099. if (Math.abs(this.constants.hierarchicalLayout.levelSeparation) != this.backupConstants.hierarchicalLayout.levelSeparation) {optionsSpecific.push("levelSeparation: " + this.constants.hierarchicalLayout.levelSeparation);}
  27100. if (this.constants.hierarchicalLayout.nodeSpacing != this.backupConstants.hierarchicalLayout.nodeSpacing) {optionsSpecific.push("nodeSpacing: " + this.constants.hierarchicalLayout.nodeSpacing);}
  27101. if (optionsSpecific.length != 0) {
  27102. for (var i = 0; i < optionsSpecific.length; i++) {
  27103. options += optionsSpecific[i];
  27104. if (i < optionsSpecific.length - 1) {
  27105. options += ", "
  27106. }
  27107. }
  27108. options += '}'
  27109. }
  27110. else {
  27111. options += "enabled:true}";
  27112. }
  27113. options += '};'
  27114. }
  27115. this.optionsDiv.innerHTML = options;
  27116. }
  27117. /**
  27118. * this is used to switch between barnesHut, repulsion and hierarchical.
  27119. *
  27120. */
  27121. function switchConfigurations () {
  27122. var ids = ["graph_BH_table", "graph_R_table", "graph_H_table"];
  27123. var radioButton = document.querySelector('input[name="graph_physicsMethod"]:checked').value;
  27124. var tableId = "graph_" + radioButton + "_table";
  27125. var table = document.getElementById(tableId);
  27126. table.style.display = "block";
  27127. for (var i = 0; i < ids.length; i++) {
  27128. if (ids[i] != tableId) {
  27129. table = document.getElementById(ids[i]);
  27130. table.style.display = "none";
  27131. }
  27132. }
  27133. this._restoreNodes();
  27134. if (radioButton == "R") {
  27135. this.constants.hierarchicalLayout.enabled = false;
  27136. this.constants.physics.hierarchicalRepulsion.enabled = false;
  27137. this.constants.physics.barnesHut.enabled = false;
  27138. }
  27139. else if (radioButton == "H") {
  27140. if (this.constants.hierarchicalLayout.enabled == false) {
  27141. this.constants.hierarchicalLayout.enabled = true;
  27142. this.constants.physics.hierarchicalRepulsion.enabled = true;
  27143. this.constants.physics.barnesHut.enabled = false;
  27144. this.constants.smoothCurves.enabled = false;
  27145. this._setupHierarchicalLayout();
  27146. }
  27147. }
  27148. else {
  27149. this.constants.hierarchicalLayout.enabled = false;
  27150. this.constants.physics.hierarchicalRepulsion.enabled = false;
  27151. this.constants.physics.barnesHut.enabled = true;
  27152. }
  27153. this._loadSelectedForceSolver();
  27154. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  27155. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  27156. else {graph_toggleSmooth.style.background = "#FF8532";}
  27157. this.moving = true;
  27158. this.start();
  27159. }
  27160. /**
  27161. * this generates the ranges depending on the iniital values.
  27162. *
  27163. * @param id
  27164. * @param map
  27165. * @param constantsVariableName
  27166. */
  27167. function showValueOfRange (id,map,constantsVariableName) {
  27168. var valueId = id + "_value";
  27169. var rangeValue = document.getElementById(id).value;
  27170. if (Array.isArray(map)) {
  27171. document.getElementById(valueId).value = map[parseInt(rangeValue)];
  27172. this._overWriteGraphConstants(constantsVariableName,map[parseInt(rangeValue)]);
  27173. }
  27174. else {
  27175. document.getElementById(valueId).value = parseInt(map) * parseFloat(rangeValue);
  27176. this._overWriteGraphConstants(constantsVariableName, parseInt(map) * parseFloat(rangeValue));
  27177. }
  27178. if (constantsVariableName == "hierarchicalLayout_direction" ||
  27179. constantsVariableName == "hierarchicalLayout_levelSeparation" ||
  27180. constantsVariableName == "hierarchicalLayout_nodeSpacing") {
  27181. this._setupHierarchicalLayout();
  27182. }
  27183. this.moving = true;
  27184. this.start();
  27185. }
  27186. /***/ },
  27187. /* 61 */
  27188. /***/ function(module, exports, __webpack_require__) {
  27189. /**
  27190. * Creation of the ClusterMixin var.
  27191. *
  27192. * This contains all the functions the Network object can use to employ clustering
  27193. */
  27194. /**
  27195. * This is only called in the constructor of the network object
  27196. *
  27197. */
  27198. exports.startWithClustering = function() {
  27199. // cluster if the data set is big
  27200. this.clusterToFit(this.constants.clustering.initialMaxNodes, true);
  27201. // updates the lables after clustering
  27202. this.updateLabels();
  27203. // this is called here because if clusterin is disabled, the start and stabilize are called in
  27204. // the setData function.
  27205. if (this.constants.stabilize == true) {
  27206. this._stabilize();
  27207. }
  27208. this.start();
  27209. };
  27210. /**
  27211. * This function clusters until the initialMaxNodes has been reached
  27212. *
  27213. * @param {Number} maxNumberOfNodes
  27214. * @param {Boolean} reposition
  27215. */
  27216. exports.clusterToFit = function(maxNumberOfNodes, reposition) {
  27217. var numberOfNodes = this.nodeIndices.length;
  27218. var maxLevels = 50;
  27219. var level = 0;
  27220. // we first cluster the hubs, then we pull in the outliers, repeat
  27221. while (numberOfNodes > maxNumberOfNodes && level < maxLevels) {
  27222. if (level % 3 == 0.0) {
  27223. this.forceAggregateHubs(true);
  27224. this.normalizeClusterLevels();
  27225. }
  27226. else {
  27227. this.increaseClusterLevel(); // this also includes a cluster normalization
  27228. }
  27229. this.forceAggregateHubs(true);
  27230. numberOfNodes = this.nodeIndices.length;
  27231. level += 1;
  27232. }
  27233. // after the clustering we reposition the nodes to reduce the initial chaos
  27234. if (level > 0 && reposition == true) {
  27235. this.repositionNodes();
  27236. }
  27237. this._updateCalculationNodes();
  27238. };
  27239. /**
  27240. * This function can be called to open up a specific cluster.
  27241. * It will unpack the cluster back one level.
  27242. *
  27243. * @param node | Node object: cluster to open.
  27244. */
  27245. exports.openCluster = function(node) {
  27246. var isMovingBeforeClustering = this.moving;
  27247. if (node.clusterSize > this.constants.clustering.sectorThreshold && this._nodeInActiveArea(node) &&
  27248. !(this._sector() == "default" && this.nodeIndices.length == 1)) {
  27249. // this loads a new sector, loads the nodes and edges and nodeIndices of it.
  27250. this._addSector(node);
  27251. var level = 0;
  27252. // we decluster until we reach a decent number of nodes
  27253. while ((this.nodeIndices.length < this.constants.clustering.initialMaxNodes) && (level < 10)) {
  27254. this.decreaseClusterLevel();
  27255. level += 1;
  27256. }
  27257. }
  27258. else {
  27259. this._expandClusterNode(node,false,true);
  27260. // update the index list and labels
  27261. this._updateNodeIndexList();
  27262. this._updateCalculationNodes();
  27263. this.updateLabels();
  27264. }
  27265. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  27266. if (this.moving != isMovingBeforeClustering) {
  27267. this.start();
  27268. }
  27269. };
  27270. /**
  27271. * This calls the updateClustes with default arguments
  27272. */
  27273. exports.updateClustersDefault = function() {
  27274. if (this.constants.clustering.enabled == true && this.constants.clustering.clusterByZoom == true) {
  27275. this.updateClusters(0,false,false);
  27276. }
  27277. };
  27278. /**
  27279. * This function can be called to increase the cluster level. This means that the nodes with only one edge connection will
  27280. * be clustered with their connected node. This can be repeated as many times as needed.
  27281. * This can be called externally (by a keybind for instance) to reduce the complexity of big datasets.
  27282. */
  27283. exports.increaseClusterLevel = function() {
  27284. this.updateClusters(-1,false,true);
  27285. };
  27286. /**
  27287. * This function can be called to decrease the cluster level. This means that the nodes with only one edge connection will
  27288. * be unpacked if they are a cluster. This can be repeated as many times as needed.
  27289. * This can be called externally (by a key-bind for instance) to look into clusters without zooming.
  27290. */
  27291. exports.decreaseClusterLevel = function() {
  27292. this.updateClusters(1,false,true);
  27293. };
  27294. /**
  27295. * This is the main clustering function. It clusters and declusters on zoom or forced
  27296. * This function clusters on zoom, it can be called with a predefined zoom direction
  27297. * If out, check if we can form clusters, if in, check if we can open clusters.
  27298. * This function is only called from _zoom()
  27299. *
  27300. * @param {Number} zoomDirection | -1 / 0 / +1 for zoomOut / determineByZoom / zoomIn
  27301. * @param {Boolean} recursive | enabled or disable recursive calling of the opening of clusters
  27302. * @param {Boolean} force | enabled or disable forcing
  27303. * @param {Boolean} doNotStart | if true do not call start
  27304. *
  27305. */
  27306. exports.updateClusters = function(zoomDirection,recursive,force,doNotStart) {
  27307. var isMovingBeforeClustering = this.moving;
  27308. var amountOfNodes = this.nodeIndices.length;
  27309. var detectedZoomingIn = (this.previousScale < this.scale && zoomDirection == 0);
  27310. var detectedZoomingOut = (this.previousScale > this.scale && zoomDirection == 0);
  27311. // on zoom out collapse the sector if the scale is at the level the sector was made
  27312. if (detectedZoomingOut == true) {
  27313. this._collapseSector();
  27314. }
  27315. // check if we zoom in or out
  27316. if (detectedZoomingOut == true || zoomDirection == -1) { // zoom out
  27317. // forming clusters when forced pulls outliers in. When not forced, the edge length of the
  27318. // outer nodes determines if it is being clustered
  27319. this._formClusters(force);
  27320. }
  27321. else if (detectedZoomingIn == true || zoomDirection == 1) { // zoom in
  27322. if (force == true) {
  27323. // _openClusters checks for each node if the formationScale of the cluster is smaller than
  27324. // the current scale and if so, declusters. When forced, all clusters are reduced by one step
  27325. this._openClusters(recursive,force);
  27326. }
  27327. else {
  27328. // if a cluster takes up a set percentage of the active window
  27329. //this._openClustersBySize();
  27330. this._openClusters(recursive, false);
  27331. }
  27332. }
  27333. this._updateNodeIndexList();
  27334. // if a cluster was NOT formed and the user zoomed out, we try clustering by hubs
  27335. if (this.nodeIndices.length == amountOfNodes && (detectedZoomingOut == true || zoomDirection == -1)) {
  27336. this._aggregateHubs(force);
  27337. this._updateNodeIndexList();
  27338. }
  27339. // we now reduce chains.
  27340. if (detectedZoomingOut == true || zoomDirection == -1) { // zoom out
  27341. this.handleChains();
  27342. this._updateNodeIndexList();
  27343. }
  27344. this.previousScale = this.scale;
  27345. // update labels
  27346. this.updateLabels();
  27347. // if a cluster was formed, we increase the clusterSession
  27348. if (this.nodeIndices.length < amountOfNodes) { // this means a clustering operation has taken place
  27349. this.clusterSession += 1;
  27350. // if clusters have been made, we normalize the cluster level
  27351. this.normalizeClusterLevels();
  27352. }
  27353. if (doNotStart == false || doNotStart === undefined) {
  27354. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  27355. if (this.moving != isMovingBeforeClustering) {
  27356. this.start();
  27357. }
  27358. }
  27359. this._updateCalculationNodes();
  27360. };
  27361. /**
  27362. * This function handles the chains. It is called on every updateClusters().
  27363. */
  27364. exports.handleChains = function() {
  27365. // after clustering we check how many chains there are
  27366. var chainPercentage = this._getChainFraction();
  27367. if (chainPercentage > this.constants.clustering.chainThreshold) {
  27368. this._reduceAmountOfChains(1 - this.constants.clustering.chainThreshold / chainPercentage)
  27369. }
  27370. };
  27371. /**
  27372. * this functions starts clustering by hubs
  27373. * The minimum hub threshold is set globally
  27374. *
  27375. * @private
  27376. */
  27377. exports._aggregateHubs = function(force) {
  27378. this._getHubSize();
  27379. this._formClustersByHub(force,false);
  27380. };
  27381. /**
  27382. * This function forces hubs to form.
  27383. *
  27384. */
  27385. exports.forceAggregateHubs = function(doNotStart) {
  27386. var isMovingBeforeClustering = this.moving;
  27387. var amountOfNodes = this.nodeIndices.length;
  27388. this._aggregateHubs(true);
  27389. // update the index list, dynamic edges and labels
  27390. this._updateNodeIndexList();
  27391. this.updateLabels();
  27392. this._updateCalculationNodes();
  27393. // if a cluster was formed, we increase the clusterSession
  27394. if (this.nodeIndices.length != amountOfNodes) {
  27395. this.clusterSession += 1;
  27396. }
  27397. if (doNotStart == false || doNotStart === undefined) {
  27398. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  27399. if (this.moving != isMovingBeforeClustering) {
  27400. this.start();
  27401. }
  27402. }
  27403. };
  27404. /**
  27405. * If a cluster takes up more than a set percentage of the screen, open the cluster
  27406. *
  27407. * @private
  27408. */
  27409. exports._openClustersBySize = function() {
  27410. if (this.constants.clustering.clusterByZoom == true) {
  27411. for (var nodeId in this.nodes) {
  27412. if (this.nodes.hasOwnProperty(nodeId)) {
  27413. var node = this.nodes[nodeId];
  27414. if (node.inView() == true) {
  27415. if ((node.width * this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  27416. (node.height * this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  27417. this.openCluster(node);
  27418. }
  27419. }
  27420. }
  27421. }
  27422. }
  27423. };
  27424. /**
  27425. * This function loops over all nodes in the nodeIndices list. For each node it checks if it is a cluster and if it
  27426. * has to be opened based on the current zoom level.
  27427. *
  27428. * @private
  27429. */
  27430. exports._openClusters = function(recursive,force) {
  27431. for (var i = 0; i < this.nodeIndices.length; i++) {
  27432. var node = this.nodes[this.nodeIndices[i]];
  27433. this._expandClusterNode(node,recursive,force);
  27434. this._updateCalculationNodes();
  27435. }
  27436. };
  27437. /**
  27438. * This function checks if a node has to be opened. This is done by checking the zoom level.
  27439. * If the node contains child nodes, this function is recursively called on the child nodes as well.
  27440. * This recursive behaviour is optional and can be set by the recursive argument.
  27441. *
  27442. * @param {Node} parentNode | to check for cluster and expand
  27443. * @param {Boolean} recursive | enabled or disable recursive calling
  27444. * @param {Boolean} force | enabled or disable forcing
  27445. * @param {Boolean} [openAll] | This will recursively force all nodes in the parent to be released
  27446. * @private
  27447. */
  27448. exports._expandClusterNode = function(parentNode, recursive, force, openAll) {
  27449. // first check if node is a cluster
  27450. if (parentNode.clusterSize > 1) {
  27451. if (openAll === undefined) {
  27452. openAll = false;
  27453. }
  27454. // this means that on a double tap event or a zoom event, the cluster fully unpacks if it is smaller than 20
  27455. recursive = openAll || recursive;
  27456. // if the last child has been added on a smaller scale than current scale decluster
  27457. if (parentNode.formationScale < this.scale || force == true) {
  27458. // we will check if any of the contained child nodes should be removed from the cluster
  27459. for (var containedNodeId in parentNode.containedNodes) {
  27460. if (parentNode.containedNodes.hasOwnProperty(containedNodeId)) {
  27461. var childNode = parentNode.containedNodes[containedNodeId];
  27462. // force expand will expand the largest cluster size clusters. Since we cluster from outside in, we assume that
  27463. // the largest cluster is the one that comes from outside
  27464. if (force == true) {
  27465. if (childNode.clusterSession == parentNode.clusterSessions[parentNode.clusterSessions.length-1]
  27466. || openAll) {
  27467. this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
  27468. }
  27469. }
  27470. else {
  27471. if (this._nodeInActiveArea(parentNode)) {
  27472. this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
  27473. }
  27474. }
  27475. }
  27476. }
  27477. }
  27478. }
  27479. };
  27480. /**
  27481. * ONLY CALLED FROM _expandClusterNode
  27482. *
  27483. * This function will expel a child_node from a parent_node. This is to de-cluster the node. This function will remove
  27484. * the child node from the parent contained_node object and put it back into the global nodes object.
  27485. * The same holds for the edge that was connected to the child node. It is moved back into the global edges object.
  27486. *
  27487. * @param {Node} parentNode | the parent node
  27488. * @param {String} containedNodeId | child_node id as it is contained in the containedNodes object of the parent node
  27489. * @param {Boolean} recursive | This will also check if the child needs to be expanded.
  27490. * With force and recursive both true, the entire cluster is unpacked
  27491. * @param {Boolean} force | This will disregard the zoom level and will expel this child from the parent
  27492. * @param {Boolean} openAll | This will recursively force all nodes in the parent to be released
  27493. * @private
  27494. */
  27495. exports._expelChildFromParent = function(parentNode, containedNodeId, recursive, force, openAll) {
  27496. var childNode = parentNode.containedNodes[containedNodeId]
  27497. // if child node has been added on smaller scale than current, kick out
  27498. if (childNode.formationScale < this.scale || force == true) {
  27499. // unselect all selected items
  27500. this._unselectAll();
  27501. // put the child node back in the global nodes object
  27502. this.nodes[containedNodeId] = childNode;
  27503. // release the contained edges from this childNode back into the global edges
  27504. this._releaseContainedEdges(parentNode,childNode);
  27505. // reconnect rerouted edges to the childNode
  27506. this._connectEdgeBackToChild(parentNode,childNode);
  27507. // validate all edges in dynamicEdges
  27508. this._validateEdges(parentNode);
  27509. // undo the changes from the clustering operation on the parent node
  27510. parentNode.options.mass -= childNode.options.mass;
  27511. parentNode.clusterSize -= childNode.clusterSize;
  27512. parentNode.options.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*(parentNode.clusterSize-1));
  27513. // place the child node near the parent, not at the exact same location to avoid chaos in the system
  27514. childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random());
  27515. childNode.y = parentNode.y + parentNode.growthIndicator * (0.5 - Math.random());
  27516. // remove node from the list
  27517. delete parentNode.containedNodes[containedNodeId];
  27518. // check if there are other childs with this clusterSession in the parent.
  27519. var othersPresent = false;
  27520. for (var childNodeId in parentNode.containedNodes) {
  27521. if (parentNode.containedNodes.hasOwnProperty(childNodeId)) {
  27522. if (parentNode.containedNodes[childNodeId].clusterSession == childNode.clusterSession) {
  27523. othersPresent = true;
  27524. break;
  27525. }
  27526. }
  27527. }
  27528. // if there are no others, remove the cluster session from the list
  27529. if (othersPresent == false) {
  27530. parentNode.clusterSessions.pop();
  27531. }
  27532. this._repositionBezierNodes(childNode);
  27533. // this._repositionBezierNodes(parentNode);
  27534. // remove the clusterSession from the child node
  27535. childNode.clusterSession = 0;
  27536. // recalculate the size of the node on the next time the node is rendered
  27537. parentNode.clearSizeCache();
  27538. // restart the simulation to reorganise all nodes
  27539. this.moving = true;
  27540. }
  27541. // check if a further expansion step is possible if recursivity is enabled
  27542. if (recursive == true) {
  27543. this._expandClusterNode(childNode,recursive,force,openAll);
  27544. }
  27545. };
  27546. /**
  27547. * position the bezier nodes at the center of the edges
  27548. *
  27549. * @param node
  27550. * @private
  27551. */
  27552. exports._repositionBezierNodes = function(node) {
  27553. for (var i = 0; i < node.dynamicEdges.length; i++) {
  27554. node.dynamicEdges[i].positionBezierNode();
  27555. }
  27556. };
  27557. /**
  27558. * This function checks if any nodes at the end of their trees have edges below a threshold length
  27559. * This function is called only from updateClusters()
  27560. * forceLevelCollapse ignores the length of the edge and collapses one level
  27561. * This means that a node with only one edge will be clustered with its connected node
  27562. *
  27563. * @private
  27564. * @param {Boolean} force
  27565. */
  27566. exports._formClusters = function(force) {
  27567. if (force == false) {
  27568. if (this.constants.clustering.clusterByZoom == true) {
  27569. this._formClustersByZoom();
  27570. }
  27571. }
  27572. else {
  27573. this._forceClustersByZoom();
  27574. }
  27575. };
  27576. /**
  27577. * This function handles the clustering by zooming out, this is based on a minimum edge distance
  27578. *
  27579. * @private
  27580. */
  27581. exports._formClustersByZoom = function() {
  27582. var dx,dy,length;
  27583. var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
  27584. // check if any edges are shorter than minLength and start the clustering
  27585. // the clustering favours the node with the larger mass
  27586. for (var edgeId in this.edges) {
  27587. if (this.edges.hasOwnProperty(edgeId)) {
  27588. var edge = this.edges[edgeId];
  27589. if (edge.connected) {
  27590. if (edge.toId != edge.fromId) {
  27591. dx = (edge.to.x - edge.from.x);
  27592. dy = (edge.to.y - edge.from.y);
  27593. length = Math.sqrt(dx * dx + dy * dy);
  27594. if (length < minLength) {
  27595. // first check which node is larger
  27596. var parentNode = edge.from;
  27597. var childNode = edge.to;
  27598. if (edge.to.options.mass > edge.from.options.mass) {
  27599. parentNode = edge.to;
  27600. childNode = edge.from;
  27601. }
  27602. if (childNode.dynamicEdges.length == 1) {
  27603. this._addToCluster(parentNode,childNode,false);
  27604. }
  27605. else if (parentNode.dynamicEdges.length == 1) {
  27606. this._addToCluster(childNode,parentNode,false);
  27607. }
  27608. }
  27609. }
  27610. }
  27611. }
  27612. }
  27613. };
  27614. /**
  27615. * This function forces the network to cluster all nodes with only one connecting edge to their
  27616. * connected node.
  27617. *
  27618. * @private
  27619. */
  27620. exports._forceClustersByZoom = function() {
  27621. for (var nodeId in this.nodes) {
  27622. // another node could have absorbed this child.
  27623. if (this.nodes.hasOwnProperty(nodeId)) {
  27624. var childNode = this.nodes[nodeId];
  27625. // the edges can be swallowed by another decrease
  27626. if (childNode.dynamicEdges.length == 1) {
  27627. var edge = childNode.dynamicEdges[0];
  27628. var parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId];
  27629. // group to the largest node
  27630. if (childNode.id != parentNode.id) {
  27631. if (parentNode.options.mass > childNode.options.mass) {
  27632. this._addToCluster(parentNode,childNode,true);
  27633. }
  27634. else {
  27635. this._addToCluster(childNode,parentNode,true);
  27636. }
  27637. }
  27638. }
  27639. }
  27640. }
  27641. };
  27642. /**
  27643. * To keep the nodes of roughly equal size we normalize the cluster levels.
  27644. * This function clusters a node to its smallest connected neighbour.
  27645. *
  27646. * @param node
  27647. * @private
  27648. */
  27649. exports._clusterToSmallestNeighbour = function(node) {
  27650. var smallestNeighbour = -1;
  27651. var smallestNeighbourNode = null;
  27652. for (var i = 0; i < node.dynamicEdges.length; i++) {
  27653. if (node.dynamicEdges[i] !== undefined) {
  27654. var neighbour = null;
  27655. if (node.dynamicEdges[i].fromId != node.id) {
  27656. neighbour = node.dynamicEdges[i].from;
  27657. }
  27658. else if (node.dynamicEdges[i].toId != node.id) {
  27659. neighbour = node.dynamicEdges[i].to;
  27660. }
  27661. if (neighbour != null && smallestNeighbour > neighbour.clusterSessions.length) {
  27662. smallestNeighbour = neighbour.clusterSessions.length;
  27663. smallestNeighbourNode = neighbour;
  27664. }
  27665. }
  27666. }
  27667. if (neighbour != null && this.nodes[neighbour.id] !== undefined) {
  27668. this._addToCluster(neighbour, node, true);
  27669. }
  27670. };
  27671. /**
  27672. * This function forms clusters from hubs, it loops over all nodes
  27673. *
  27674. * @param {Boolean} force | Disregard zoom level
  27675. * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
  27676. * @private
  27677. */
  27678. exports._formClustersByHub = function(force, onlyEqual) {
  27679. // we loop over all nodes in the list
  27680. for (var nodeId in this.nodes) {
  27681. // we check if it is still available since it can be used by the clustering in this loop
  27682. if (this.nodes.hasOwnProperty(nodeId)) {
  27683. this._formClusterFromHub(this.nodes[nodeId],force,onlyEqual);
  27684. }
  27685. }
  27686. };
  27687. /**
  27688. * This function forms a cluster from a specific preselected hub node
  27689. *
  27690. * @param {Node} hubNode | the node we will cluster as a hub
  27691. * @param {Boolean} force | Disregard zoom level
  27692. * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
  27693. * @param {Number} [absorptionSizeOffset] |
  27694. * @private
  27695. */
  27696. exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSizeOffset) {
  27697. if (absorptionSizeOffset === undefined) {
  27698. absorptionSizeOffset = 0;
  27699. }
  27700. //this.hubThreshold = 43
  27701. //if (hubNode.dynamicEdgesLength < 0) {
  27702. // console.error(hubNode.dynamicEdgesLength, this.hubThreshold, onlyEqual)
  27703. //}
  27704. // we decide if the node is a hub
  27705. if ((hubNode.dynamicEdges.length >= this.hubThreshold && onlyEqual == false) ||
  27706. (hubNode.dynamicEdges.length == this.hubThreshold && onlyEqual == true)) {
  27707. // initialize variables
  27708. var dx,dy,length;
  27709. var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
  27710. var allowCluster = false;
  27711. // we create a list of edges because the dynamicEdges change over the course of this loop
  27712. var edgesIdarray = [];
  27713. var amountOfInitialEdges = hubNode.dynamicEdges.length;
  27714. for (var j = 0; j < amountOfInitialEdges; j++) {
  27715. edgesIdarray.push(hubNode.dynamicEdges[j].id);
  27716. }
  27717. // if the hub clustering is not forced, we check if one of the edges connected
  27718. // to a cluster is small enough based on the constants.clustering.clusterEdgeThreshold
  27719. if (force == false) {
  27720. allowCluster = false;
  27721. for (j = 0; j < amountOfInitialEdges; j++) {
  27722. var edge = this.edges[edgesIdarray[j]];
  27723. if (edge !== undefined) {
  27724. if (edge.connected) {
  27725. if (edge.toId != edge.fromId) {
  27726. dx = (edge.to.x - edge.from.x);
  27727. dy = (edge.to.y - edge.from.y);
  27728. length = Math.sqrt(dx * dx + dy * dy);
  27729. if (length < minLength) {
  27730. allowCluster = true;
  27731. break;
  27732. }
  27733. }
  27734. }
  27735. }
  27736. }
  27737. }
  27738. // start the clustering if allowed
  27739. if ((!force && allowCluster) || force) {
  27740. var children = [];
  27741. var childrenIds = {};
  27742. // we loop over all edges INITIALLY connected to this hub to get a list of the childNodes
  27743. for (j = 0; j < amountOfInitialEdges; j++) {
  27744. edge = this.edges[edgesIdarray[j]];
  27745. var childNode = this.nodes[(edge.fromId == hubNode.id) ? edge.toId : edge.fromId];
  27746. if (childrenIds[childNode.id] === undefined) {
  27747. childrenIds[childNode.id] = true;
  27748. children.push(childNode);
  27749. }
  27750. }
  27751. for (j = 0; j < children.length; j++) {
  27752. var childNode = children[j];
  27753. // we do not want hubs to merge with other hubs nor do we want to cluster itself.
  27754. if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) &&
  27755. (childNode.id != hubNode.id)) {
  27756. this._addToCluster(hubNode,childNode,force);
  27757. }
  27758. else {
  27759. //console.log("WILL NOT MERGE:",childNode.dynamicEdges.length , (this.hubThreshold + absorptionSizeOffset))
  27760. }
  27761. }
  27762. }
  27763. }
  27764. };
  27765. /**
  27766. * This function adds the child node to the parent node, creating a cluster if it is not already.
  27767. *
  27768. * @param {Node} parentNode | this is the node that will house the child node
  27769. * @param {Node} childNode | this node will be deleted from the global this.nodes and stored in the parent node
  27770. * @param {Boolean} force | true will only update the remainingEdges at the very end of the clustering, ensuring single level collapse
  27771. * @private
  27772. */
  27773. exports._addToCluster = function(parentNode, childNode, force) {
  27774. // join child node in the parent node
  27775. parentNode.containedNodes[childNode.id] = childNode;
  27776. //console.log(parentNode.id, childNode.id)
  27777. // manage all the edges connected to the child and parent nodes
  27778. for (var i = 0; i < childNode.dynamicEdges.length; i++) {
  27779. var edge = childNode.dynamicEdges[i];
  27780. if (edge.toId == parentNode.id || edge.fromId == parentNode.id) { // edge connected to parentNode
  27781. //console.log("COLLECT",parentNode.id, childNode.id, edge.toId, edge.fromId)
  27782. this._addToContainedEdges(parentNode,childNode,edge);
  27783. }
  27784. else {
  27785. //console.log("REWIRE",parentNode.id, childNode.id, edge.toId, edge.fromId)
  27786. this._connectEdgeToCluster(parentNode,childNode,edge);
  27787. }
  27788. }
  27789. // a contained node has no dynamic edges.
  27790. childNode.dynamicEdges = [];
  27791. // remove circular edges from clusters
  27792. this._containCircularEdgesFromNode(parentNode,childNode);
  27793. // remove the childNode from the global nodes object
  27794. delete this.nodes[childNode.id];
  27795. // update the properties of the child and parent
  27796. var massBefore = parentNode.options.mass;
  27797. childNode.clusterSession = this.clusterSession;
  27798. parentNode.options.mass += childNode.options.mass;
  27799. parentNode.clusterSize += childNode.clusterSize;
  27800. parentNode.options.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
  27801. // keep track of the clustersessions so we can open the cluster up as it has been formed.
  27802. if (parentNode.clusterSessions[parentNode.clusterSessions.length - 1] != this.clusterSession) {
  27803. parentNode.clusterSessions.push(this.clusterSession);
  27804. }
  27805. // forced clusters only open from screen size and double tap
  27806. if (force == true) {
  27807. parentNode.formationScale = 0;
  27808. }
  27809. else {
  27810. parentNode.formationScale = this.scale; // The latest child has been added on this scale
  27811. }
  27812. // recalculate the size of the node on the next time the node is rendered
  27813. parentNode.clearSizeCache();
  27814. // set the pop-out scale for the childnode
  27815. parentNode.containedNodes[childNode.id].formationScale = parentNode.formationScale;
  27816. // nullify the movement velocity of the child, this is to avoid hectic behaviour
  27817. childNode.clearVelocity();
  27818. // the mass has altered, preservation of energy dictates the velocity to be updated
  27819. parentNode.updateVelocity(massBefore);
  27820. // restart the simulation to reorganise all nodes
  27821. this.moving = true;
  27822. };
  27823. /**
  27824. * This adds an edge from the childNode to the contained edges of the parent node
  27825. *
  27826. * @param parentNode | Node object
  27827. * @param childNode | Node object
  27828. * @param edge | Edge object
  27829. * @private
  27830. */
  27831. exports._addToContainedEdges = function(parentNode, childNode, edge) {
  27832. // create an array object if it does not yet exist for this childNode
  27833. if (parentNode.containedEdges[childNode.id] === undefined) {
  27834. parentNode.containedEdges[childNode.id] = []
  27835. }
  27836. // add this edge to the list
  27837. parentNode.containedEdges[childNode.id].push(edge);
  27838. // remove the edge from the global edges object
  27839. delete this.edges[edge.id];
  27840. // remove the edge from the parent object
  27841. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  27842. if (parentNode.dynamicEdges[i].id == edge.id) {
  27843. parentNode.dynamicEdges.splice(i,1);
  27844. break;
  27845. }
  27846. }
  27847. };
  27848. /**
  27849. * This function connects an edge that was connected to a child node to the parent node.
  27850. * It keeps track of which nodes it has been connected to with the originalId array.
  27851. *
  27852. * @param {Node} parentNode | Node object
  27853. * @param {Node} childNode | Node object
  27854. * @param {Edge} edge | Edge object
  27855. * @private
  27856. */
  27857. exports._connectEdgeToCluster = function(parentNode, childNode, edge) {
  27858. // handle circular edges
  27859. if (edge.toId == edge.fromId) {
  27860. this._addToContainedEdges(parentNode, childNode, edge);
  27861. }
  27862. else {
  27863. if (edge.toId == childNode.id) { // edge connected to other node on the "to" side
  27864. edge.originalToId.push(childNode.id);
  27865. edge.to = parentNode;
  27866. edge.toId = parentNode.id;
  27867. }
  27868. else { // edge connected to other node with the "from" side
  27869. edge.originalFromId.push(childNode.id);
  27870. edge.from = parentNode;
  27871. edge.fromId = parentNode.id;
  27872. }
  27873. this._addToReroutedEdges(parentNode,childNode,edge);
  27874. }
  27875. };
  27876. /**
  27877. * If a node is connected to itself, a circular edge is drawn. When clustering we want to contain
  27878. * these edges inside of the cluster.
  27879. *
  27880. * @param parentNode
  27881. * @param childNode
  27882. * @private
  27883. */
  27884. exports._containCircularEdgesFromNode = function(parentNode, childNode) {
  27885. // manage all the edges connected to the child and parent nodes
  27886. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  27887. var edge = parentNode.dynamicEdges[i];
  27888. // handle circular edges
  27889. if (edge.toId == edge.fromId) {
  27890. this._addToContainedEdges(parentNode, childNode, edge);
  27891. }
  27892. }
  27893. };
  27894. /**
  27895. * This adds an edge from the childNode to the rerouted edges of the parent node
  27896. *
  27897. * @param parentNode | Node object
  27898. * @param childNode | Node object
  27899. * @param edge | Edge object
  27900. * @private
  27901. */
  27902. exports._addToReroutedEdges = function(parentNode, childNode, edge) {
  27903. // create an array object if it does not yet exist for this childNode
  27904. // we store the edge in the rerouted edges so we can restore it when the cluster pops open
  27905. if (!(parentNode.reroutedEdges.hasOwnProperty(childNode.id))) {
  27906. parentNode.reroutedEdges[childNode.id] = [];
  27907. }
  27908. parentNode.reroutedEdges[childNode.id].push(edge);
  27909. // this edge becomes part of the dynamicEdges of the cluster node
  27910. parentNode.dynamicEdges.push(edge);
  27911. };
  27912. /**
  27913. * This function connects an edge that was connected to a cluster node back to the child node.
  27914. *
  27915. * @param parentNode | Node object
  27916. * @param childNode | Node object
  27917. * @private
  27918. */
  27919. exports._connectEdgeBackToChild = function(parentNode, childNode) {
  27920. if (parentNode.reroutedEdges.hasOwnProperty(childNode.id)) {
  27921. for (var i = 0; i < parentNode.reroutedEdges[childNode.id].length; i++) {
  27922. var edge = parentNode.reroutedEdges[childNode.id][i];
  27923. if (edge.originalFromId[edge.originalFromId.length-1] == childNode.id) {
  27924. edge.originalFromId.pop();
  27925. edge.fromId = childNode.id;
  27926. edge.from = childNode;
  27927. }
  27928. else {
  27929. edge.originalToId.pop();
  27930. edge.toId = childNode.id;
  27931. edge.to = childNode;
  27932. }
  27933. // append this edge to the list of edges connecting to the childnode
  27934. childNode.dynamicEdges.push(edge);
  27935. // remove the edge from the parent object
  27936. for (var j = 0; j < parentNode.dynamicEdges.length; j++) {
  27937. if (parentNode.dynamicEdges[j].id == edge.id) {
  27938. parentNode.dynamicEdges.splice(j,1);
  27939. break;
  27940. }
  27941. }
  27942. }
  27943. // remove the entry from the rerouted edges
  27944. delete parentNode.reroutedEdges[childNode.id];
  27945. }
  27946. };
  27947. /**
  27948. * When loops are clustered, an edge can be both in the rerouted array and the contained array.
  27949. * This function is called last to verify that all edges in dynamicEdges are in fact connected to the
  27950. * parentNode
  27951. *
  27952. * @param parentNode | Node object
  27953. * @private
  27954. */
  27955. exports._validateEdges = function(parentNode) {
  27956. var dynamicEdges = []
  27957. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  27958. var edge = parentNode.dynamicEdges[i];
  27959. if (parentNode.id == edge.toId || parentNode.id == edge.fromId) {
  27960. dynamicEdges.push(edge);
  27961. }
  27962. }
  27963. parentNode.dynamicEdges = dynamicEdges;
  27964. };
  27965. /**
  27966. * This function released the contained edges back into the global domain and puts them back into the
  27967. * dynamic edges of both parent and child.
  27968. *
  27969. * @param {Node} parentNode |
  27970. * @param {Node} childNode |
  27971. * @private
  27972. */
  27973. exports._releaseContainedEdges = function(parentNode, childNode) {
  27974. for (var i = 0; i < parentNode.containedEdges[childNode.id].length; i++) {
  27975. var edge = parentNode.containedEdges[childNode.id][i];
  27976. // put the edge back in the global edges object
  27977. this.edges[edge.id] = edge;
  27978. // put the edge back in the dynamic edges of the child and parent
  27979. childNode.dynamicEdges.push(edge);
  27980. parentNode.dynamicEdges.push(edge);
  27981. }
  27982. // remove the entry from the contained edges
  27983. delete parentNode.containedEdges[childNode.id];
  27984. };
  27985. // ------------------- UTILITY FUNCTIONS ---------------------------- //
  27986. /**
  27987. * This updates the node labels for all nodes (for debugging purposes)
  27988. */
  27989. exports.updateLabels = function() {
  27990. var nodeId;
  27991. // update node labels
  27992. for (nodeId in this.nodes) {
  27993. if (this.nodes.hasOwnProperty(nodeId)) {
  27994. var node = this.nodes[nodeId];
  27995. if (node.clusterSize > 1) {
  27996. node.label = "[".concat(String(node.clusterSize),"]");
  27997. }
  27998. }
  27999. }
  28000. // update node labels
  28001. for (nodeId in this.nodes) {
  28002. if (this.nodes.hasOwnProperty(nodeId)) {
  28003. node = this.nodes[nodeId];
  28004. if (node.clusterSize == 1) {
  28005. if (node.originalLabel !== undefined) {
  28006. node.label = node.originalLabel;
  28007. }
  28008. else {
  28009. node.label = String(node.id);
  28010. }
  28011. }
  28012. }
  28013. }
  28014. // /* Debug Override */
  28015. // for (nodeId in this.nodes) {
  28016. // if (this.nodes.hasOwnProperty(nodeId)) {
  28017. // node = this.nodes[nodeId];
  28018. // node.label = String(node.clusterSize + ":" + node.dynamicEdges.length);
  28019. // }
  28020. // }
  28021. };
  28022. /**
  28023. * We want to keep the cluster level distribution rather small. This means we do not want unclustered nodes
  28024. * if the rest of the nodes are already a few cluster levels in.
  28025. * To fix this we use this function. It determines the min and max cluster level and sends nodes that have not
  28026. * clustered enough to the clusterToSmallestNeighbours function.
  28027. */
  28028. exports.normalizeClusterLevels = function() {
  28029. var maxLevel = 0;
  28030. var minLevel = 1e9;
  28031. var clusterLevel = 0;
  28032. var nodeId;
  28033. // we loop over all nodes in the list
  28034. for (nodeId in this.nodes) {
  28035. if (this.nodes.hasOwnProperty(nodeId)) {
  28036. clusterLevel = this.nodes[nodeId].clusterSessions.length;
  28037. if (maxLevel < clusterLevel) {maxLevel = clusterLevel;}
  28038. if (minLevel > clusterLevel) {minLevel = clusterLevel;}
  28039. }
  28040. }
  28041. if (maxLevel - minLevel > this.constants.clustering.clusterLevelDifference) {
  28042. var amountOfNodes = this.nodeIndices.length;
  28043. var targetLevel = maxLevel - this.constants.clustering.clusterLevelDifference;
  28044. // we loop over all nodes in the list
  28045. for (nodeId in this.nodes) {
  28046. if (this.nodes.hasOwnProperty(nodeId)) {
  28047. if (this.nodes[nodeId].clusterSessions.length < targetLevel) {
  28048. this._clusterToSmallestNeighbour(this.nodes[nodeId]);
  28049. }
  28050. }
  28051. }
  28052. this._updateNodeIndexList();
  28053. // if a cluster was formed, we increase the clusterSession
  28054. if (this.nodeIndices.length != amountOfNodes) {
  28055. this.clusterSession += 1;
  28056. }
  28057. }
  28058. };
  28059. /**
  28060. * This function determines if the cluster we want to decluster is in the active area
  28061. * this means around the zoom center
  28062. *
  28063. * @param {Node} node
  28064. * @returns {boolean}
  28065. * @private
  28066. */
  28067. exports._nodeInActiveArea = function(node) {
  28068. return (
  28069. Math.abs(node.x - this.areaCenter.x) <= this.constants.clustering.activeAreaBoxSize/this.scale
  28070. &&
  28071. Math.abs(node.y - this.areaCenter.y) <= this.constants.clustering.activeAreaBoxSize/this.scale
  28072. )
  28073. };
  28074. /**
  28075. * This is an adaptation of the original repositioning function. This is called if the system is clustered initially
  28076. * It puts large clusters away from the center and randomizes the order.
  28077. *
  28078. */
  28079. exports.repositionNodes = function() {
  28080. for (var i = 0; i < this.nodeIndices.length; i++) {
  28081. var node = this.nodes[this.nodeIndices[i]];
  28082. if ((node.xFixed == false || node.yFixed == false)) {
  28083. var radius = 10 * 0.1*this.nodeIndices.length * Math.min(100,node.options.mass);
  28084. var angle = 2 * Math.PI * Math.random();
  28085. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  28086. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  28087. this._repositionBezierNodes(node);
  28088. }
  28089. }
  28090. };
  28091. /**
  28092. * We determine how many connections denote an important hub.
  28093. * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
  28094. *
  28095. * @private
  28096. */
  28097. exports._getHubSize = function() {
  28098. var average = 0;
  28099. var averageSquared = 0;
  28100. var hubCounter = 0;
  28101. var largestHub = 0;
  28102. for (var i = 0; i < this.nodeIndices.length; i++) {
  28103. var node = this.nodes[this.nodeIndices[i]];
  28104. if (node.dynamicEdges.length > largestHub) {
  28105. largestHub = node.dynamicEdges.length;
  28106. }
  28107. average += node.dynamicEdges.length;
  28108. averageSquared += Math.pow(node.dynamicEdges.length,2);
  28109. hubCounter += 1;
  28110. }
  28111. average = average / hubCounter;
  28112. averageSquared = averageSquared / hubCounter;
  28113. var variance = averageSquared - Math.pow(average,2);
  28114. var standardDeviation = Math.sqrt(variance);
  28115. this.hubThreshold = Math.floor(average + 2*standardDeviation);
  28116. // always have at least one to cluster
  28117. if (this.hubThreshold > largestHub) {
  28118. this.hubThreshold = largestHub;
  28119. }
  28120. // console.log("average",average,"averageSQ",averageSquared,"var",variance,"std",standardDeviation);
  28121. // console.log("hubThreshold:",this.hubThreshold);
  28122. };
  28123. /**
  28124. * We reduce the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
  28125. * with this amount we can cluster specifically on these chains.
  28126. *
  28127. * @param {Number} fraction | between 0 and 1, the percentage of chains to reduce
  28128. * @private
  28129. */
  28130. exports._reduceAmountOfChains = function(fraction) {
  28131. this.hubThreshold = 2;
  28132. var reduceAmount = Math.floor(this.nodeIndices.length * fraction);
  28133. for (var nodeId in this.nodes) {
  28134. if (this.nodes.hasOwnProperty(nodeId)) {
  28135. if (this.nodes[nodeId].dynamicEdges.length == 2) {
  28136. if (reduceAmount > 0) {
  28137. this._formClusterFromHub(this.nodes[nodeId],true,true,1);
  28138. reduceAmount -= 1;
  28139. }
  28140. }
  28141. }
  28142. }
  28143. };
  28144. /**
  28145. * We get the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
  28146. * with this amount we can cluster specifically on these chains.
  28147. *
  28148. * @private
  28149. */
  28150. exports._getChainFraction = function() {
  28151. var chains = 0;
  28152. var total = 0;
  28153. for (var nodeId in this.nodes) {
  28154. if (this.nodes.hasOwnProperty(nodeId)) {
  28155. if (this.nodes[nodeId].dynamicEdges.length == 2) {
  28156. chains += 1;
  28157. }
  28158. total += 1;
  28159. }
  28160. }
  28161. return chains/total;
  28162. };
  28163. /***/ },
  28164. /* 62 */
  28165. /***/ function(module, exports, __webpack_require__) {
  28166. var util = __webpack_require__(1);
  28167. var Node = __webpack_require__(40);
  28168. /**
  28169. * Creation of the SectorMixin var.
  28170. *
  28171. * This contains all the functions the Network object can use to employ the sector system.
  28172. * The sector system is always used by Network, though the benefits only apply to the use of clustering.
  28173. * If clustering is not used, there is no overhead except for a duplicate object with references to nodes and edges.
  28174. */
  28175. /**
  28176. * This function is only called by the setData function of the Network object.
  28177. * This loads the global references into the active sector. This initializes the sector.
  28178. *
  28179. * @private
  28180. */
  28181. exports._putDataInSector = function() {
  28182. this.sectors["active"][this._sector()].nodes = this.nodes;
  28183. this.sectors["active"][this._sector()].edges = this.edges;
  28184. this.sectors["active"][this._sector()].nodeIndices = this.nodeIndices;
  28185. };
  28186. /**
  28187. * /**
  28188. * This function sets the global references to nodes, edges and nodeIndices back to
  28189. * those of the supplied (active) sector. If a type is defined, do the specific type
  28190. *
  28191. * @param {String} sectorId
  28192. * @param {String} [sectorType] | "active" or "frozen"
  28193. * @private
  28194. */
  28195. exports._switchToSector = function(sectorId, sectorType) {
  28196. if (sectorType === undefined || sectorType == "active") {
  28197. this._switchToActiveSector(sectorId);
  28198. }
  28199. else {
  28200. this._switchToFrozenSector(sectorId);
  28201. }
  28202. };
  28203. /**
  28204. * This function sets the global references to nodes, edges and nodeIndices back to
  28205. * those of the supplied active sector.
  28206. *
  28207. * @param sectorId
  28208. * @private
  28209. */
  28210. exports._switchToActiveSector = function(sectorId) {
  28211. this.nodeIndices = this.sectors["active"][sectorId]["nodeIndices"];
  28212. this.nodes = this.sectors["active"][sectorId]["nodes"];
  28213. this.edges = this.sectors["active"][sectorId]["edges"];
  28214. };
  28215. /**
  28216. * This function sets the global references to nodes, edges and nodeIndices back to
  28217. * those of the supplied active sector.
  28218. *
  28219. * @private
  28220. */
  28221. exports._switchToSupportSector = function() {
  28222. this.nodeIndices = this.sectors["support"]["nodeIndices"];
  28223. this.nodes = this.sectors["support"]["nodes"];
  28224. this.edges = this.sectors["support"]["edges"];
  28225. };
  28226. /**
  28227. * This function sets the global references to nodes, edges and nodeIndices back to
  28228. * those of the supplied frozen sector.
  28229. *
  28230. * @param sectorId
  28231. * @private
  28232. */
  28233. exports._switchToFrozenSector = function(sectorId) {
  28234. this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"];
  28235. this.nodes = this.sectors["frozen"][sectorId]["nodes"];
  28236. this.edges = this.sectors["frozen"][sectorId]["edges"];
  28237. };
  28238. /**
  28239. * This function sets the global references to nodes, edges and nodeIndices back to
  28240. * those of the currently active sector.
  28241. *
  28242. * @private
  28243. */
  28244. exports._loadLatestSector = function() {
  28245. this._switchToSector(this._sector());
  28246. };
  28247. /**
  28248. * This function returns the currently active sector Id
  28249. *
  28250. * @returns {String}
  28251. * @private
  28252. */
  28253. exports._sector = function() {
  28254. return this.activeSector[this.activeSector.length-1];
  28255. };
  28256. /**
  28257. * This function returns the previously active sector Id
  28258. *
  28259. * @returns {String}
  28260. * @private
  28261. */
  28262. exports._previousSector = function() {
  28263. if (this.activeSector.length > 1) {
  28264. return this.activeSector[this.activeSector.length-2];
  28265. }
  28266. else {
  28267. throw new TypeError('there are not enough sectors in the this.activeSector array.');
  28268. }
  28269. };
  28270. /**
  28271. * We add the active sector at the end of the this.activeSector array
  28272. * This ensures it is the currently active sector returned by _sector() and it reaches the top
  28273. * of the activeSector stack. When we reverse our steps we move from the end to the beginning of this stack.
  28274. *
  28275. * @param newId
  28276. * @private
  28277. */
  28278. exports._setActiveSector = function(newId) {
  28279. this.activeSector.push(newId);
  28280. };
  28281. /**
  28282. * We remove the currently active sector id from the active sector stack. This happens when
  28283. * we reactivate the previously active sector
  28284. *
  28285. * @private
  28286. */
  28287. exports._forgetLastSector = function() {
  28288. this.activeSector.pop();
  28289. };
  28290. /**
  28291. * This function creates a new active sector with the supplied newId. This newId
  28292. * is the expanding node id.
  28293. *
  28294. * @param {String} newId | Id of the new active sector
  28295. * @private
  28296. */
  28297. exports._createNewSector = function(newId) {
  28298. // create the new sector
  28299. this.sectors["active"][newId] = {"nodes":{},
  28300. "edges":{},
  28301. "nodeIndices":[],
  28302. "formationScale": this.scale,
  28303. "drawingNode": undefined};
  28304. // create the new sector render node. This gives visual feedback that you are in a new sector.
  28305. this.sectors["active"][newId]['drawingNode'] = new Node(
  28306. {id:newId,
  28307. color: {
  28308. background: "#eaefef",
  28309. border: "495c5e"
  28310. }
  28311. },{},{},this.constants);
  28312. this.sectors["active"][newId]['drawingNode'].clusterSize = 2;
  28313. };
  28314. /**
  28315. * This function removes the currently active sector. This is called when we create a new
  28316. * active sector.
  28317. *
  28318. * @param {String} sectorId | Id of the active sector that will be removed
  28319. * @private
  28320. */
  28321. exports._deleteActiveSector = function(sectorId) {
  28322. delete this.sectors["active"][sectorId];
  28323. };
  28324. /**
  28325. * This function removes the currently active sector. This is called when we reactivate
  28326. * the previously active sector.
  28327. *
  28328. * @param {String} sectorId | Id of the active sector that will be removed
  28329. * @private
  28330. */
  28331. exports._deleteFrozenSector = function(sectorId) {
  28332. delete this.sectors["frozen"][sectorId];
  28333. };
  28334. /**
  28335. * Freezing an active sector means moving it from the "active" object to the "frozen" object.
  28336. * We copy the references, then delete the active entree.
  28337. *
  28338. * @param sectorId
  28339. * @private
  28340. */
  28341. exports._freezeSector = function(sectorId) {
  28342. // we move the set references from the active to the frozen stack.
  28343. this.sectors["frozen"][sectorId] = this.sectors["active"][sectorId];
  28344. // we have moved the sector data into the frozen set, we now remove it from the active set
  28345. this._deleteActiveSector(sectorId);
  28346. };
  28347. /**
  28348. * This is the reverse operation of _freezeSector. Activating means moving the sector from the "frozen"
  28349. * object to the "active" object.
  28350. *
  28351. * @param sectorId
  28352. * @private
  28353. */
  28354. exports._activateSector = function(sectorId) {
  28355. // we move the set references from the frozen to the active stack.
  28356. this.sectors["active"][sectorId] = this.sectors["frozen"][sectorId];
  28357. // we have moved the sector data into the active set, we now remove it from the frozen stack
  28358. this._deleteFrozenSector(sectorId);
  28359. };
  28360. /**
  28361. * This function merges the data from the currently active sector with a frozen sector. This is used
  28362. * in the process of reverting back to the previously active sector.
  28363. * The data that is placed in the frozen (the previously active) sector is the node that has been removed from it
  28364. * upon the creation of a new active sector.
  28365. *
  28366. * @param sectorId
  28367. * @private
  28368. */
  28369. exports._mergeThisWithFrozen = function(sectorId) {
  28370. // copy all nodes
  28371. for (var nodeId in this.nodes) {
  28372. if (this.nodes.hasOwnProperty(nodeId)) {
  28373. this.sectors["frozen"][sectorId]["nodes"][nodeId] = this.nodes[nodeId];
  28374. }
  28375. }
  28376. // copy all edges (if not fully clustered, else there are no edges)
  28377. for (var edgeId in this.edges) {
  28378. if (this.edges.hasOwnProperty(edgeId)) {
  28379. this.sectors["frozen"][sectorId]["edges"][edgeId] = this.edges[edgeId];
  28380. }
  28381. }
  28382. // merge the nodeIndices
  28383. for (var i = 0; i < this.nodeIndices.length; i++) {
  28384. this.sectors["frozen"][sectorId]["nodeIndices"].push(this.nodeIndices[i]);
  28385. }
  28386. };
  28387. /**
  28388. * This clusters the sector to one cluster. It was a single cluster before this process started so
  28389. * we revert to that state. The clusterToFit function with a maximum size of 1 node does this.
  28390. *
  28391. * @private
  28392. */
  28393. exports._collapseThisToSingleCluster = function() {
  28394. this.clusterToFit(1,false);
  28395. };
  28396. /**
  28397. * We create a new active sector from the node that we want to open.
  28398. *
  28399. * @param node
  28400. * @private
  28401. */
  28402. exports._addSector = function(node) {
  28403. // this is the currently active sector
  28404. var sector = this._sector();
  28405. // // this should allow me to select nodes from a frozen set.
  28406. // if (this.sectors['active'][sector]["nodes"].hasOwnProperty(node.id)) {
  28407. // console.log("the node is part of the active sector");
  28408. // }
  28409. // else {
  28410. // console.log("I dont know what the fuck happened!!");
  28411. // }
  28412. // when we switch to a new sector, we remove the node that will be expanded from the current nodes list.
  28413. delete this.nodes[node.id];
  28414. var unqiueIdentifier = util.randomUUID();
  28415. // we fully freeze the currently active sector
  28416. this._freezeSector(sector);
  28417. // we create a new active sector. This sector has the Id of the node to ensure uniqueness
  28418. this._createNewSector(unqiueIdentifier);
  28419. // we add the active sector to the sectors array to be able to revert these steps later on
  28420. this._setActiveSector(unqiueIdentifier);
  28421. // we redirect the global references to the new sector's references. this._sector() now returns unqiueIdentifier
  28422. this._switchToSector(this._sector());
  28423. // finally we add the node we removed from our previous active sector to the new active sector
  28424. this.nodes[node.id] = node;
  28425. };
  28426. /**
  28427. * We close the sector that is currently open and revert back to the one before.
  28428. * If the active sector is the "default" sector, nothing happens.
  28429. *
  28430. * @private
  28431. */
  28432. exports._collapseSector = function() {
  28433. // the currently active sector
  28434. var sector = this._sector();
  28435. // we cannot collapse the default sector
  28436. if (sector != "default") {
  28437. if ((this.nodeIndices.length == 1) ||
  28438. (this.sectors["active"][sector]["drawingNode"].width*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  28439. (this.sectors["active"][sector]["drawingNode"].height*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  28440. var previousSector = this._previousSector();
  28441. // we collapse the sector back to a single cluster
  28442. this._collapseThisToSingleCluster();
  28443. // we move the remaining nodes, edges and nodeIndices to the previous sector.
  28444. // This previous sector is the one we will reactivate
  28445. this._mergeThisWithFrozen(previousSector);
  28446. // the previously active (frozen) sector now has all the data from the currently active sector.
  28447. // we can now delete the active sector.
  28448. this._deleteActiveSector(sector);
  28449. // we activate the previously active (and currently frozen) sector.
  28450. this._activateSector(previousSector);
  28451. // we load the references from the newly active sector into the global references
  28452. this._switchToSector(previousSector);
  28453. // we forget the previously active sector because we reverted to the one before
  28454. this._forgetLastSector();
  28455. // finally, we update the node index list.
  28456. this._updateNodeIndexList();
  28457. // we refresh the list with calulation nodes and calculation node indices.
  28458. this._updateCalculationNodes();
  28459. }
  28460. }
  28461. };
  28462. /**
  28463. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  28464. *
  28465. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  28466. * | we dont pass the function itself because then the "this" is the window object
  28467. * | instead of the Network object
  28468. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  28469. * @private
  28470. */
  28471. exports._doInAllActiveSectors = function(runFunction,argument) {
  28472. var returnValues = [];
  28473. if (argument === undefined) {
  28474. for (var sector in this.sectors["active"]) {
  28475. if (this.sectors["active"].hasOwnProperty(sector)) {
  28476. // switch the global references to those of this sector
  28477. this._switchToActiveSector(sector);
  28478. returnValues.push( this[runFunction]() );
  28479. }
  28480. }
  28481. }
  28482. else {
  28483. for (var sector in this.sectors["active"]) {
  28484. if (this.sectors["active"].hasOwnProperty(sector)) {
  28485. // switch the global references to those of this sector
  28486. this._switchToActiveSector(sector);
  28487. var args = Array.prototype.splice.call(arguments, 1);
  28488. if (args.length > 1) {
  28489. returnValues.push( this[runFunction](args[0],args[1]) );
  28490. }
  28491. else {
  28492. returnValues.push( this[runFunction](argument) );
  28493. }
  28494. }
  28495. }
  28496. }
  28497. // we revert the global references back to our active sector
  28498. this._loadLatestSector();
  28499. return returnValues;
  28500. };
  28501. /**
  28502. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  28503. *
  28504. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  28505. * | we dont pass the function itself because then the "this" is the window object
  28506. * | instead of the Network object
  28507. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  28508. * @private
  28509. */
  28510. exports._doInSupportSector = function(runFunction,argument) {
  28511. var returnValues = false;
  28512. if (argument === undefined) {
  28513. this._switchToSupportSector();
  28514. returnValues = this[runFunction]();
  28515. }
  28516. else {
  28517. this._switchToSupportSector();
  28518. var args = Array.prototype.splice.call(arguments, 1);
  28519. if (args.length > 1) {
  28520. returnValues = this[runFunction](args[0],args[1]);
  28521. }
  28522. else {
  28523. returnValues = this[runFunction](argument);
  28524. }
  28525. }
  28526. // we revert the global references back to our active sector
  28527. this._loadLatestSector();
  28528. return returnValues;
  28529. };
  28530. /**
  28531. * This runs a function in all frozen sectors. This is used in the _redraw().
  28532. *
  28533. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  28534. * | we don't pass the function itself because then the "this" is the window object
  28535. * | instead of the Network object
  28536. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  28537. * @private
  28538. */
  28539. exports._doInAllFrozenSectors = function(runFunction,argument) {
  28540. if (argument === undefined) {
  28541. for (var sector in this.sectors["frozen"]) {
  28542. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  28543. // switch the global references to those of this sector
  28544. this._switchToFrozenSector(sector);
  28545. this[runFunction]();
  28546. }
  28547. }
  28548. }
  28549. else {
  28550. for (var sector in this.sectors["frozen"]) {
  28551. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  28552. // switch the global references to those of this sector
  28553. this._switchToFrozenSector(sector);
  28554. var args = Array.prototype.splice.call(arguments, 1);
  28555. if (args.length > 1) {
  28556. this[runFunction](args[0],args[1]);
  28557. }
  28558. else {
  28559. this[runFunction](argument);
  28560. }
  28561. }
  28562. }
  28563. }
  28564. this._loadLatestSector();
  28565. };
  28566. /**
  28567. * This runs a function in all sectors. This is used in the _redraw().
  28568. *
  28569. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  28570. * | we don't pass the function itself because then the "this" is the window object
  28571. * | instead of the Network object
  28572. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  28573. * @private
  28574. */
  28575. exports._doInAllSectors = function(runFunction,argument) {
  28576. var args = Array.prototype.splice.call(arguments, 1);
  28577. if (argument === undefined) {
  28578. this._doInAllActiveSectors(runFunction);
  28579. this._doInAllFrozenSectors(runFunction);
  28580. }
  28581. else {
  28582. if (args.length > 1) {
  28583. this._doInAllActiveSectors(runFunction,args[0],args[1]);
  28584. this._doInAllFrozenSectors(runFunction,args[0],args[1]);
  28585. }
  28586. else {
  28587. this._doInAllActiveSectors(runFunction,argument);
  28588. this._doInAllFrozenSectors(runFunction,argument);
  28589. }
  28590. }
  28591. };
  28592. /**
  28593. * This clears the nodeIndices list. We cannot use this.nodeIndices = [] because we would break the link with the
  28594. * active sector. Thus we clear the nodeIndices in the active sector, then reconnect the this.nodeIndices to it.
  28595. *
  28596. * @private
  28597. */
  28598. exports._clearNodeIndexList = function() {
  28599. var sector = this._sector();
  28600. this.sectors["active"][sector]["nodeIndices"] = [];
  28601. this.nodeIndices = this.sectors["active"][sector]["nodeIndices"];
  28602. };
  28603. /**
  28604. * Draw the encompassing sector node
  28605. *
  28606. * @param ctx
  28607. * @param sectorType
  28608. * @private
  28609. */
  28610. exports._drawSectorNodes = function(ctx,sectorType) {
  28611. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  28612. for (var sector in this.sectors[sectorType]) {
  28613. if (this.sectors[sectorType].hasOwnProperty(sector)) {
  28614. if (this.sectors[sectorType][sector]["drawingNode"] !== undefined) {
  28615. this._switchToSector(sector,sectorType);
  28616. minY = 1e9; maxY = -1e9; minX = 1e9; maxX = -1e9;
  28617. for (var nodeId in this.nodes) {
  28618. if (this.nodes.hasOwnProperty(nodeId)) {
  28619. node = this.nodes[nodeId];
  28620. node.resize(ctx);
  28621. if (minX > node.x - 0.5 * node.width) {minX = node.x - 0.5 * node.width;}
  28622. if (maxX < node.x + 0.5 * node.width) {maxX = node.x + 0.5 * node.width;}
  28623. if (minY > node.y - 0.5 * node.height) {minY = node.y - 0.5 * node.height;}
  28624. if (maxY < node.y + 0.5 * node.height) {maxY = node.y + 0.5 * node.height;}
  28625. }
  28626. }
  28627. node = this.sectors[sectorType][sector]["drawingNode"];
  28628. node.x = 0.5 * (maxX + minX);
  28629. node.y = 0.5 * (maxY + minY);
  28630. node.width = 2 * (node.x - minX);
  28631. node.height = 2 * (node.y - minY);
  28632. node.options.radius = Math.sqrt(Math.pow(0.5*node.width,2) + Math.pow(0.5*node.height,2));
  28633. node.setScale(this.scale);
  28634. node._drawCircle(ctx);
  28635. }
  28636. }
  28637. }
  28638. };
  28639. exports._drawAllSectorNodes = function(ctx) {
  28640. this._drawSectorNodes(ctx,"frozen");
  28641. this._drawSectorNodes(ctx,"active");
  28642. this._loadLatestSector();
  28643. };
  28644. /***/ },
  28645. /* 63 */
  28646. /***/ function(module, exports, __webpack_require__) {
  28647. var Node = __webpack_require__(40);
  28648. /**
  28649. * This function can be called from the _doInAllSectors function
  28650. *
  28651. * @param object
  28652. * @param overlappingNodes
  28653. * @private
  28654. */
  28655. exports._getNodesOverlappingWith = function(object, overlappingNodes) {
  28656. var nodes = this.nodes;
  28657. for (var nodeId in nodes) {
  28658. if (nodes.hasOwnProperty(nodeId)) {
  28659. if (nodes[nodeId].isOverlappingWith(object)) {
  28660. overlappingNodes.push(nodeId);
  28661. }
  28662. }
  28663. }
  28664. };
  28665. /**
  28666. * retrieve all nodes overlapping with given object
  28667. * @param {Object} object An object with parameters left, top, right, bottom
  28668. * @return {Number[]} An array with id's of the overlapping nodes
  28669. * @private
  28670. */
  28671. exports._getAllNodesOverlappingWith = function (object) {
  28672. var overlappingNodes = [];
  28673. this._doInAllActiveSectors("_getNodesOverlappingWith",object,overlappingNodes);
  28674. return overlappingNodes;
  28675. };
  28676. /**
  28677. * Return a position object in canvasspace from a single point in screenspace
  28678. *
  28679. * @param pointer
  28680. * @returns {{left: number, top: number, right: number, bottom: number}}
  28681. * @private
  28682. */
  28683. exports._pointerToPositionObject = function(pointer) {
  28684. var x = this._XconvertDOMtoCanvas(pointer.x);
  28685. var y = this._YconvertDOMtoCanvas(pointer.y);
  28686. return {
  28687. left: x,
  28688. top: y,
  28689. right: x,
  28690. bottom: y
  28691. };
  28692. };
  28693. /**
  28694. * Get the top node at the a specific point (like a click)
  28695. *
  28696. * @param {{x: Number, y: Number}} pointer
  28697. * @return {Node | null} node
  28698. * @private
  28699. */
  28700. exports._getNodeAt = function (pointer) {
  28701. // we first check if this is an navigation controls element
  28702. var positionObject = this._pointerToPositionObject(pointer);
  28703. var overlappingNodes = this._getAllNodesOverlappingWith(positionObject);
  28704. // if there are overlapping nodes, select the last one, this is the
  28705. // one which is drawn on top of the others
  28706. if (overlappingNodes.length > 0) {
  28707. return this.nodes[overlappingNodes[overlappingNodes.length - 1]];
  28708. }
  28709. else {
  28710. return null;
  28711. }
  28712. };
  28713. /**
  28714. * retrieve all edges overlapping with given object, selector is around center
  28715. * @param {Object} object An object with parameters left, top, right, bottom
  28716. * @return {Number[]} An array with id's of the overlapping nodes
  28717. * @private
  28718. */
  28719. exports._getEdgesOverlappingWith = function (object, overlappingEdges) {
  28720. var edges = this.edges;
  28721. for (var edgeId in edges) {
  28722. if (edges.hasOwnProperty(edgeId)) {
  28723. if (edges[edgeId].isOverlappingWith(object)) {
  28724. overlappingEdges.push(edgeId);
  28725. }
  28726. }
  28727. }
  28728. };
  28729. /**
  28730. * retrieve all nodes overlapping with given object
  28731. * @param {Object} object An object with parameters left, top, right, bottom
  28732. * @return {Number[]} An array with id's of the overlapping nodes
  28733. * @private
  28734. */
  28735. exports._getAllEdgesOverlappingWith = function (object) {
  28736. var overlappingEdges = [];
  28737. this._doInAllActiveSectors("_getEdgesOverlappingWith",object,overlappingEdges);
  28738. return overlappingEdges;
  28739. };
  28740. /**
  28741. * Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call
  28742. * _getNodeAt and _getEdgesAt, then priortize the selection to user preferences.
  28743. *
  28744. * @param pointer
  28745. * @returns {null}
  28746. * @private
  28747. */
  28748. exports._getEdgeAt = function(pointer) {
  28749. var positionObject = this._pointerToPositionObject(pointer);
  28750. var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject);
  28751. if (overlappingEdges.length > 0) {
  28752. return this.edges[overlappingEdges[overlappingEdges.length - 1]];
  28753. }
  28754. else {
  28755. return null;
  28756. }
  28757. };
  28758. /**
  28759. * Add object to the selection array.
  28760. *
  28761. * @param obj
  28762. * @private
  28763. */
  28764. exports._addToSelection = function(obj) {
  28765. if (obj instanceof Node) {
  28766. this.selectionObj.nodes[obj.id] = obj;
  28767. }
  28768. else {
  28769. this.selectionObj.edges[obj.id] = obj;
  28770. }
  28771. };
  28772. /**
  28773. * Add object to the selection array.
  28774. *
  28775. * @param obj
  28776. * @private
  28777. */
  28778. exports._addToHover = function(obj) {
  28779. if (obj instanceof Node) {
  28780. this.hoverObj.nodes[obj.id] = obj;
  28781. }
  28782. else {
  28783. this.hoverObj.edges[obj.id] = obj;
  28784. }
  28785. };
  28786. /**
  28787. * Remove a single option from selection.
  28788. *
  28789. * @param {Object} obj
  28790. * @private
  28791. */
  28792. exports._removeFromSelection = function(obj) {
  28793. if (obj instanceof Node) {
  28794. delete this.selectionObj.nodes[obj.id];
  28795. }
  28796. else {
  28797. delete this.selectionObj.edges[obj.id];
  28798. }
  28799. };
  28800. /**
  28801. * Unselect all. The selectionObj is useful for this.
  28802. *
  28803. * @param {Boolean} [doNotTrigger] | ignore trigger
  28804. * @private
  28805. */
  28806. exports._unselectAll = function(doNotTrigger) {
  28807. if (doNotTrigger === undefined) {
  28808. doNotTrigger = false;
  28809. }
  28810. for(var nodeId in this.selectionObj.nodes) {
  28811. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28812. this.selectionObj.nodes[nodeId].unselect();
  28813. }
  28814. }
  28815. for(var edgeId in this.selectionObj.edges) {
  28816. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  28817. this.selectionObj.edges[edgeId].unselect();
  28818. }
  28819. }
  28820. this.selectionObj = {nodes:{},edges:{}};
  28821. if (doNotTrigger == false) {
  28822. this.emit('select', this.getSelection());
  28823. }
  28824. };
  28825. /**
  28826. * Unselect all clusters. The selectionObj is useful for this.
  28827. *
  28828. * @param {Boolean} [doNotTrigger] | ignore trigger
  28829. * @private
  28830. */
  28831. exports._unselectClusters = function(doNotTrigger) {
  28832. if (doNotTrigger === undefined) {
  28833. doNotTrigger = false;
  28834. }
  28835. for (var nodeId in this.selectionObj.nodes) {
  28836. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28837. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  28838. this.selectionObj.nodes[nodeId].unselect();
  28839. this._removeFromSelection(this.selectionObj.nodes[nodeId]);
  28840. }
  28841. }
  28842. }
  28843. if (doNotTrigger == false) {
  28844. this.emit('select', this.getSelection());
  28845. }
  28846. };
  28847. /**
  28848. * return the number of selected nodes
  28849. *
  28850. * @returns {number}
  28851. * @private
  28852. */
  28853. exports._getSelectedNodeCount = function() {
  28854. var count = 0;
  28855. for (var nodeId in this.selectionObj.nodes) {
  28856. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28857. count += 1;
  28858. }
  28859. }
  28860. return count;
  28861. };
  28862. /**
  28863. * return the selected node
  28864. *
  28865. * @returns {number}
  28866. * @private
  28867. */
  28868. exports._getSelectedNode = function() {
  28869. for (var nodeId in this.selectionObj.nodes) {
  28870. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28871. return this.selectionObj.nodes[nodeId];
  28872. }
  28873. }
  28874. return null;
  28875. };
  28876. /**
  28877. * return the selected edge
  28878. *
  28879. * @returns {number}
  28880. * @private
  28881. */
  28882. exports._getSelectedEdge = function() {
  28883. for (var edgeId in this.selectionObj.edges) {
  28884. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  28885. return this.selectionObj.edges[edgeId];
  28886. }
  28887. }
  28888. return null;
  28889. };
  28890. /**
  28891. * return the number of selected edges
  28892. *
  28893. * @returns {number}
  28894. * @private
  28895. */
  28896. exports._getSelectedEdgeCount = function() {
  28897. var count = 0;
  28898. for (var edgeId in this.selectionObj.edges) {
  28899. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  28900. count += 1;
  28901. }
  28902. }
  28903. return count;
  28904. };
  28905. /**
  28906. * return the number of selected objects.
  28907. *
  28908. * @returns {number}
  28909. * @private
  28910. */
  28911. exports._getSelectedObjectCount = function() {
  28912. var count = 0;
  28913. for(var nodeId in this.selectionObj.nodes) {
  28914. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28915. count += 1;
  28916. }
  28917. }
  28918. for(var edgeId in this.selectionObj.edges) {
  28919. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  28920. count += 1;
  28921. }
  28922. }
  28923. return count;
  28924. };
  28925. /**
  28926. * Check if anything is selected
  28927. *
  28928. * @returns {boolean}
  28929. * @private
  28930. */
  28931. exports._selectionIsEmpty = function() {
  28932. for(var nodeId in this.selectionObj.nodes) {
  28933. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28934. return false;
  28935. }
  28936. }
  28937. for(var edgeId in this.selectionObj.edges) {
  28938. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  28939. return false;
  28940. }
  28941. }
  28942. return true;
  28943. };
  28944. /**
  28945. * check if one of the selected nodes is a cluster.
  28946. *
  28947. * @returns {boolean}
  28948. * @private
  28949. */
  28950. exports._clusterInSelection = function() {
  28951. for(var nodeId in this.selectionObj.nodes) {
  28952. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28953. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  28954. return true;
  28955. }
  28956. }
  28957. }
  28958. return false;
  28959. };
  28960. /**
  28961. * select the edges connected to the node that is being selected
  28962. *
  28963. * @param {Node} node
  28964. * @private
  28965. */
  28966. exports._selectConnectedEdges = function(node) {
  28967. for (var i = 0; i < node.dynamicEdges.length; i++) {
  28968. var edge = node.dynamicEdges[i];
  28969. edge.select();
  28970. this._addToSelection(edge);
  28971. }
  28972. };
  28973. /**
  28974. * select the edges connected to the node that is being selected
  28975. *
  28976. * @param {Node} node
  28977. * @private
  28978. */
  28979. exports._hoverConnectedEdges = function(node) {
  28980. for (var i = 0; i < node.dynamicEdges.length; i++) {
  28981. var edge = node.dynamicEdges[i];
  28982. edge.hover = true;
  28983. this._addToHover(edge);
  28984. }
  28985. };
  28986. /**
  28987. * unselect the edges connected to the node that is being selected
  28988. *
  28989. * @param {Node} node
  28990. * @private
  28991. */
  28992. exports._unselectConnectedEdges = function(node) {
  28993. for (var i = 0; i < node.dynamicEdges.length; i++) {
  28994. var edge = node.dynamicEdges[i];
  28995. edge.unselect();
  28996. this._removeFromSelection(edge);
  28997. }
  28998. };
  28999. /**
  29000. * This is called when someone clicks on a node. either select or deselect it.
  29001. * If there is an existing selection and we don't want to append to it, clear the existing selection
  29002. *
  29003. * @param {Node || Edge} object
  29004. * @param {Boolean} append
  29005. * @param {Boolean} [doNotTrigger] | ignore trigger
  29006. * @private
  29007. */
  29008. exports._selectObject = function(object, append, doNotTrigger, highlightEdges, overrideSelectable) {
  29009. if (doNotTrigger === undefined) {
  29010. doNotTrigger = false;
  29011. }
  29012. if (highlightEdges === undefined) {
  29013. highlightEdges = true;
  29014. }
  29015. if (this._selectionIsEmpty() == false && append == false && this.forceAppendSelection == false) {
  29016. this._unselectAll(true);
  29017. }
  29018. // selectable allows the object to be selected. Override can be used if needed to bypass this.
  29019. if (object.selected == false && (this.constants.selectable == true || overrideSelectable)) {
  29020. object.select();
  29021. this._addToSelection(object);
  29022. if (object instanceof Node && this.blockConnectingEdgeSelection == false && highlightEdges == true) {
  29023. this._selectConnectedEdges(object);
  29024. }
  29025. }
  29026. // do not select the object if selectable is false, only add it to selection to allow drag to work
  29027. else if (object.selected == false) {
  29028. this._addToSelection(object);
  29029. doNotTrigger = true;
  29030. }
  29031. else {
  29032. object.unselect();
  29033. this._removeFromSelection(object);
  29034. }
  29035. if (doNotTrigger == false) {
  29036. this.emit('select', this.getSelection());
  29037. }
  29038. };
  29039. /**
  29040. * This is called when someone clicks on a node. either select or deselect it.
  29041. * If there is an existing selection and we don't want to append to it, clear the existing selection
  29042. *
  29043. * @param {Node || Edge} object
  29044. * @private
  29045. */
  29046. exports._blurObject = function(object) {
  29047. if (object.hover == true) {
  29048. object.hover = false;
  29049. this.emit("blurNode",{node:object.id});
  29050. }
  29051. };
  29052. /**
  29053. * This is called when someone clicks on a node. either select or deselect it.
  29054. * If there is an existing selection and we don't want to append to it, clear the existing selection
  29055. *
  29056. * @param {Node || Edge} object
  29057. * @private
  29058. */
  29059. exports._hoverObject = function(object) {
  29060. if (object.hover == false) {
  29061. object.hover = true;
  29062. this._addToHover(object);
  29063. if (object instanceof Node) {
  29064. this.emit("hoverNode",{node:object.id});
  29065. }
  29066. }
  29067. if (object instanceof Node) {
  29068. this._hoverConnectedEdges(object);
  29069. }
  29070. };
  29071. /**
  29072. * handles the selection part of the touch, only for navigation controls elements;
  29073. * Touch is triggered before tap, also before hold. Hold triggers after a while.
  29074. * This is the most responsive solution
  29075. *
  29076. * @param {Object} pointer
  29077. * @private
  29078. */
  29079. exports._handleTouch = function(pointer) {
  29080. };
  29081. /**
  29082. * handles the selection part of the tap;
  29083. *
  29084. * @param {Object} pointer
  29085. * @private
  29086. */
  29087. exports._handleTap = function(pointer) {
  29088. var node = this._getNodeAt(pointer);
  29089. if (node != null) {
  29090. this._selectObject(node, false);
  29091. }
  29092. else {
  29093. var edge = this._getEdgeAt(pointer);
  29094. if (edge != null) {
  29095. this._selectObject(edge, false);
  29096. }
  29097. else {
  29098. this._unselectAll();
  29099. }
  29100. }
  29101. var properties = this.getSelection();
  29102. properties['pointer'] = {
  29103. DOM: {x: pointer.x, y: pointer.y},
  29104. canvas: {x: this._XconvertDOMtoCanvas(pointer.x), y: this._YconvertDOMtoCanvas(pointer.y)}
  29105. }
  29106. this.emit("click", properties);
  29107. this._requestRedraw();
  29108. };
  29109. /**
  29110. * handles the selection part of the double tap and opens a cluster if needed
  29111. *
  29112. * @param {Object} pointer
  29113. * @private
  29114. */
  29115. exports._handleDoubleTap = function(pointer) {
  29116. var node = this._getNodeAt(pointer);
  29117. if (node != null && node !== undefined) {
  29118. // we reset the areaCenter here so the opening of the node will occur
  29119. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  29120. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  29121. this.openCluster(node);
  29122. }
  29123. var properties = this.getSelection();
  29124. properties['pointer'] = {
  29125. DOM: {x: pointer.x, y: pointer.y},
  29126. canvas: {x: this._XconvertDOMtoCanvas(pointer.x), y: this._YconvertDOMtoCanvas(pointer.y)}
  29127. }
  29128. this.emit("doubleClick", properties);
  29129. };
  29130. /**
  29131. * Handle the onHold selection part
  29132. *
  29133. * @param pointer
  29134. * @private
  29135. */
  29136. exports._handleOnHold = function(pointer) {
  29137. var node = this._getNodeAt(pointer);
  29138. if (node != null) {
  29139. this._selectObject(node,true);
  29140. }
  29141. else {
  29142. var edge = this._getEdgeAt(pointer);
  29143. if (edge != null) {
  29144. this._selectObject(edge,true);
  29145. }
  29146. }
  29147. this._requestRedraw();
  29148. };
  29149. /**
  29150. * handle the onRelease event. These functions are here for the navigation controls module
  29151. * and data manipulation module.
  29152. *
  29153. * @private
  29154. */
  29155. exports._handleOnRelease = function(pointer) {
  29156. this._manipulationReleaseOverload(pointer);
  29157. this._navigationReleaseOverload(pointer);
  29158. };
  29159. exports._manipulationReleaseOverload = function (pointer) {};
  29160. exports._navigationReleaseOverload = function (pointer) {};
  29161. /**
  29162. *
  29163. * retrieve the currently selected objects
  29164. * @return {{nodes: Array.<String>, edges: Array.<String>}} selection
  29165. */
  29166. exports.getSelection = function() {
  29167. var nodeIds = this.getSelectedNodes();
  29168. var edgeIds = this.getSelectedEdges();
  29169. return {nodes:nodeIds, edges:edgeIds};
  29170. };
  29171. /**
  29172. *
  29173. * retrieve the currently selected nodes
  29174. * @return {String[]} selection An array with the ids of the
  29175. * selected nodes.
  29176. */
  29177. exports.getSelectedNodes = function() {
  29178. var idArray = [];
  29179. if (this.constants.selectable == true) {
  29180. for (var nodeId in this.selectionObj.nodes) {
  29181. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  29182. idArray.push(nodeId);
  29183. }
  29184. }
  29185. }
  29186. return idArray
  29187. };
  29188. /**
  29189. *
  29190. * retrieve the currently selected edges
  29191. * @return {Array} selection An array with the ids of the
  29192. * selected nodes.
  29193. */
  29194. exports.getSelectedEdges = function() {
  29195. var idArray = [];
  29196. if (this.constants.selectable == true) {
  29197. for (var edgeId in this.selectionObj.edges) {
  29198. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  29199. idArray.push(edgeId);
  29200. }
  29201. }
  29202. }
  29203. return idArray;
  29204. };
  29205. /**
  29206. * select zero or more nodes DEPRICATED
  29207. * @param {Number[] | String[]} selection An array with the ids of the
  29208. * selected nodes.
  29209. */
  29210. exports.setSelection = function() {
  29211. console.log("setSelection is deprecated. Please use selectNodes instead.")
  29212. };
  29213. /**
  29214. * select zero or more nodes with the option to highlight edges
  29215. * @param {Number[] | String[]} selection An array with the ids of the
  29216. * selected nodes.
  29217. * @param {boolean} [highlightEdges]
  29218. */
  29219. exports.selectNodes = function(selection, highlightEdges) {
  29220. var i, iMax, id;
  29221. if (!selection || (selection.length == undefined))
  29222. throw 'Selection must be an array with ids';
  29223. // first unselect any selected node
  29224. this._unselectAll(true);
  29225. for (i = 0, iMax = selection.length; i < iMax; i++) {
  29226. id = selection[i];
  29227. var node = this.nodes[id];
  29228. if (!node) {
  29229. throw new RangeError('Node with id "' + id + '" not found');
  29230. }
  29231. this._selectObject(node,true,true,highlightEdges,true);
  29232. }
  29233. this.redraw();
  29234. };
  29235. /**
  29236. * select zero or more edges
  29237. * @param {Number[] | String[]} selection An array with the ids of the
  29238. * selected nodes.
  29239. */
  29240. exports.selectEdges = function(selection) {
  29241. var i, iMax, id;
  29242. if (!selection || (selection.length == undefined))
  29243. throw 'Selection must be an array with ids';
  29244. // first unselect any selected node
  29245. this._unselectAll(true);
  29246. for (i = 0, iMax = selection.length; i < iMax; i++) {
  29247. id = selection[i];
  29248. var edge = this.edges[id];
  29249. if (!edge) {
  29250. throw new RangeError('Edge with id "' + id + '" not found');
  29251. }
  29252. this._selectObject(edge,true,true,false,true);
  29253. }
  29254. this.redraw();
  29255. };
  29256. /**
  29257. * Validate the selection: remove ids of nodes which no longer exist
  29258. * @private
  29259. */
  29260. exports._updateSelection = function () {
  29261. for(var nodeId in this.selectionObj.nodes) {
  29262. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  29263. if (!this.nodes.hasOwnProperty(nodeId)) {
  29264. delete this.selectionObj.nodes[nodeId];
  29265. }
  29266. }
  29267. }
  29268. for(var edgeId in this.selectionObj.edges) {
  29269. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  29270. if (!this.edges.hasOwnProperty(edgeId)) {
  29271. delete this.selectionObj.edges[edgeId];
  29272. }
  29273. }
  29274. }
  29275. };
  29276. /***/ },
  29277. /* 64 */
  29278. /***/ function(module, exports, __webpack_require__) {
  29279. var util = __webpack_require__(1);
  29280. var Node = __webpack_require__(40);
  29281. var Edge = __webpack_require__(37);
  29282. var Hammer = __webpack_require__(45);
  29283. /**
  29284. * clears the toolbar div element of children
  29285. *
  29286. * @private
  29287. */
  29288. exports._clearManipulatorBar = function() {
  29289. this._recursiveDOMDelete(this.manipulationDiv);
  29290. this.manipulationDOM = {};
  29291. this._cleanManipulatorHammers();
  29292. this._manipulationReleaseOverload = function () {};
  29293. delete this.sectors['support']['nodes']['targetNode'];
  29294. delete this.sectors['support']['nodes']['targetViaNode'];
  29295. this.controlNodesActive = false;
  29296. this.freezeSimulation(false);
  29297. };
  29298. exports._cleanManipulatorHammers = function() {
  29299. // clean hammer bindings
  29300. if (this.manipulationHammers.length != 0) {
  29301. for (var i = 0; i < this.manipulationHammers.length; i++) {
  29302. this.manipulationHammers[i].dispose();
  29303. }
  29304. this.manipulationHammers = [];
  29305. }
  29306. };
  29307. /**
  29308. * Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore
  29309. * these functions to their original functionality, we saved them in this.cachedFunctions.
  29310. * This function restores these functions to their original function.
  29311. *
  29312. * @private
  29313. */
  29314. exports._restoreOverloadedFunctions = function() {
  29315. for (var functionName in this.cachedFunctions) {
  29316. if (this.cachedFunctions.hasOwnProperty(functionName)) {
  29317. this[functionName] = this.cachedFunctions[functionName];
  29318. delete this.cachedFunctions[functionName];
  29319. }
  29320. }
  29321. };
  29322. /**
  29323. * Enable or disable edit-mode.
  29324. *
  29325. * @private
  29326. */
  29327. exports._toggleEditMode = function() {
  29328. this.editMode = !this.editMode;
  29329. var toolbar = this.manipulationDiv;
  29330. var closeDiv = this.closeDiv;
  29331. var editModeDiv = this.editModeDiv;
  29332. if (this.editMode == true) {
  29333. toolbar.style.display="block";
  29334. closeDiv.style.display="block";
  29335. editModeDiv.style.display="none";
  29336. this._bindHammerToDiv(closeDiv,'_toggleEditMode');
  29337. }
  29338. else {
  29339. toolbar.style.display="none";
  29340. closeDiv.style.display="none";
  29341. editModeDiv.style.display="block";
  29342. }
  29343. this._createManipulatorBar()
  29344. };
  29345. /**
  29346. * main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
  29347. *
  29348. * @private
  29349. */
  29350. exports._createManipulatorBar = function() {
  29351. // remove bound functions
  29352. if (this.boundFunction) {
  29353. this.off('select', this.boundFunction);
  29354. }
  29355. this._cleanManipulatorHammers();
  29356. var locale = this.constants.locales[this.constants.locale];
  29357. if (this.edgeBeingEdited !== undefined) {
  29358. this.edgeBeingEdited._disableControlNodes();
  29359. this.edgeBeingEdited = undefined;
  29360. this.selectedControlNode = null;
  29361. this.controlNodesActive = false;
  29362. this._redraw();
  29363. }
  29364. // restore overloaded functions
  29365. this._restoreOverloadedFunctions();
  29366. // resume calculation
  29367. this.freezeSimulation(false);
  29368. // reset global variables
  29369. this.blockConnectingEdgeSelection = false;
  29370. this.forceAppendSelection = false;
  29371. this.manipulationDOM = {};
  29372. if (this.editMode == true) {
  29373. while (this.manipulationDiv.hasChildNodes()) {
  29374. this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
  29375. }
  29376. this.manipulationDOM['addNodeSpan'] = document.createElement('div');
  29377. this.manipulationDOM['addNodeSpan'].className = 'network-manipulationUI add';
  29378. this.manipulationDOM['addNodeLabelSpan'] = document.createElement('div');
  29379. this.manipulationDOM['addNodeLabelSpan'].className = 'network-manipulationLabel';
  29380. this.manipulationDOM['addNodeLabelSpan'].innerHTML = locale['addNode'];
  29381. this.manipulationDOM['addNodeSpan'].appendChild(this.manipulationDOM['addNodeLabelSpan']);
  29382. this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
  29383. this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
  29384. this.manipulationDOM['addEdgeSpan'] = document.createElement('div');
  29385. this.manipulationDOM['addEdgeSpan'].className = 'network-manipulationUI connect';
  29386. this.manipulationDOM['addEdgeLabelSpan'] = document.createElement('div');
  29387. this.manipulationDOM['addEdgeLabelSpan'].className = 'network-manipulationLabel';
  29388. this.manipulationDOM['addEdgeLabelSpan'].innerHTML = locale['addEdge'];
  29389. this.manipulationDOM['addEdgeSpan'].appendChild(this.manipulationDOM['addEdgeLabelSpan']);
  29390. this.manipulationDiv.appendChild(this.manipulationDOM['addNodeSpan']);
  29391. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
  29392. this.manipulationDiv.appendChild(this.manipulationDOM['addEdgeSpan']);
  29393. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  29394. this.manipulationDOM['seperatorLineDiv2'] = document.createElement('div');
  29395. this.manipulationDOM['seperatorLineDiv2'].className = 'network-seperatorLine';
  29396. this.manipulationDOM['editNodeSpan'] = document.createElement('div');
  29397. this.manipulationDOM['editNodeSpan'].className = 'network-manipulationUI edit node';
  29398. this.manipulationDOM['editNodeLabelSpan'] = document.createElement('div');
  29399. this.manipulationDOM['editNodeLabelSpan'].className = 'network-manipulationLabel';
  29400. this.manipulationDOM['editNodeLabelSpan'].innerHTML = locale['editNode'];
  29401. this.manipulationDOM['editNodeSpan'].appendChild(this.manipulationDOM['editNodeLabelSpan']);
  29402. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv2']);
  29403. this.manipulationDiv.appendChild(this.manipulationDOM['editNodeSpan']);
  29404. }
  29405. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  29406. this.manipulationDOM['seperatorLineDiv3'] = document.createElement('div');
  29407. this.manipulationDOM['seperatorLineDiv3'].className = 'network-seperatorLine';
  29408. this.manipulationDOM['editEdgeSpan'] = document.createElement('div');
  29409. this.manipulationDOM['editEdgeSpan'].className = 'network-manipulationUI edit edge';
  29410. this.manipulationDOM['editEdgeLabelSpan'] = document.createElement('div');
  29411. this.manipulationDOM['editEdgeLabelSpan'].className = 'network-manipulationLabel';
  29412. this.manipulationDOM['editEdgeLabelSpan'].innerHTML = locale['editEdge'];
  29413. this.manipulationDOM['editEdgeSpan'].appendChild(this.manipulationDOM['editEdgeLabelSpan']);
  29414. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv3']);
  29415. this.manipulationDiv.appendChild(this.manipulationDOM['editEdgeSpan']);
  29416. }
  29417. if (this._selectionIsEmpty() == false) {
  29418. this.manipulationDOM['seperatorLineDiv4'] = document.createElement('div');
  29419. this.manipulationDOM['seperatorLineDiv4'].className = 'network-seperatorLine';
  29420. this.manipulationDOM['deleteSpan'] = document.createElement('div');
  29421. this.manipulationDOM['deleteSpan'].className = 'network-manipulationUI delete';
  29422. this.manipulationDOM['deleteLabelSpan'] = document.createElement('div');
  29423. this.manipulationDOM['deleteLabelSpan'].className = 'network-manipulationLabel';
  29424. this.manipulationDOM['deleteLabelSpan'].innerHTML = locale['del'];
  29425. this.manipulationDOM['deleteSpan'].appendChild(this.manipulationDOM['deleteLabelSpan']);
  29426. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv4']);
  29427. this.manipulationDiv.appendChild(this.manipulationDOM['deleteSpan']);
  29428. }
  29429. // bind the icons
  29430. this._bindHammerToDiv(this.manipulationDOM['addNodeSpan'],'_createAddNodeToolbar');
  29431. this._bindHammerToDiv(this.manipulationDOM['addEdgeSpan'],'_createAddEdgeToolbar');
  29432. this._bindHammerToDiv(this.closeDiv,'_toggleEditMode');
  29433. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  29434. this._bindHammerToDiv(this.manipulationDOM['editNodeSpan'],'_editNode');
  29435. }
  29436. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  29437. this._bindHammerToDiv(this.manipulationDOM['editEdgeSpan'],'_createEditEdgeToolbar');
  29438. }
  29439. if (this._selectionIsEmpty() == false) {
  29440. this._bindHammerToDiv(this.manipulationDOM['deleteSpan'],'_deleteSelected');
  29441. }
  29442. var me = this;
  29443. this.boundFunction = me._createManipulatorBar;
  29444. this.on('select', this.boundFunction);
  29445. }
  29446. else {
  29447. while (this.editModeDiv.hasChildNodes()) {
  29448. this.editModeDiv.removeChild(this.editModeDiv.firstChild);
  29449. }
  29450. this.manipulationDOM['editModeSpan'] = document.createElement('div');
  29451. this.manipulationDOM['editModeSpan'].className = 'network-manipulationUI edit editmode';
  29452. this.manipulationDOM['editModeLabelSpan'] = document.createElement('div');
  29453. this.manipulationDOM['editModeLabelSpan'].className = 'network-manipulationLabel';
  29454. this.manipulationDOM['editModeLabelSpan'].innerHTML = locale['edit'];
  29455. this.manipulationDOM['editModeSpan'].appendChild(this.manipulationDOM['editModeLabelSpan']);
  29456. this.editModeDiv.appendChild(this.manipulationDOM['editModeSpan']);
  29457. this._bindHammerToDiv(this.manipulationDOM['editModeSpan'],'_toggleEditMode');
  29458. }
  29459. };
  29460. exports._bindHammerToDiv = function(domElement, funct) {
  29461. var hammer = Hammer(domElement, {prevent_default: true});
  29462. hammer.on('touch', this[funct].bind(this));
  29463. this.manipulationHammers.push(hammer);
  29464. }
  29465. /**
  29466. * Create the toolbar for adding Nodes
  29467. *
  29468. * @private
  29469. */
  29470. exports._createAddNodeToolbar = function() {
  29471. // clear the toolbar
  29472. this._clearManipulatorBar();
  29473. if (this.boundFunction) {
  29474. this.off('select', this.boundFunction);
  29475. }
  29476. var locale = this.constants.locales[this.constants.locale];
  29477. this.manipulationDOM = {};
  29478. this.manipulationDOM['backSpan'] = document.createElement('div');
  29479. this.manipulationDOM['backSpan'].className = 'network-manipulationUI back';
  29480. this.manipulationDOM['backLabelSpan'] = document.createElement('div');
  29481. this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel';
  29482. this.manipulationDOM['backLabelSpan'].innerHTML = locale['back'];
  29483. this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']);
  29484. this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
  29485. this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
  29486. this.manipulationDOM['descriptionSpan'] = document.createElement('div');
  29487. this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none';
  29488. this.manipulationDOM['descriptionLabelSpan'] = document.createElement('div');
  29489. this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel';
  29490. this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['addDescription'];
  29491. this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']);
  29492. this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']);
  29493. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
  29494. this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']);
  29495. // bind the icon
  29496. this._bindHammerToDiv(this.manipulationDOM['backSpan'],'_createManipulatorBar');
  29497. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  29498. var me = this;
  29499. this.boundFunction = me._addNode;
  29500. this.on('select', this.boundFunction);
  29501. };
  29502. /**
  29503. * create the toolbar to connect nodes
  29504. *
  29505. * @private
  29506. */
  29507. exports._createAddEdgeToolbar = function() {
  29508. // clear the toolbar
  29509. this._clearManipulatorBar();
  29510. this._unselectAll(true);
  29511. this.freezeSimulation(true);
  29512. if (this.boundFunction) {
  29513. this.off('select', this.boundFunction);
  29514. }
  29515. var locale = this.constants.locales[this.constants.locale];
  29516. this._unselectAll();
  29517. this.forceAppendSelection = false;
  29518. this.blockConnectingEdgeSelection = true;
  29519. this.manipulationDOM = {};
  29520. this.manipulationDOM['backSpan'] = document.createElement('div');
  29521. this.manipulationDOM['backSpan'].className = 'network-manipulationUI back';
  29522. this.manipulationDOM['backLabelSpan'] = document.createElement('div');
  29523. this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel';
  29524. this.manipulationDOM['backLabelSpan'].innerHTML = locale['back'];
  29525. this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']);
  29526. this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
  29527. this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
  29528. this.manipulationDOM['descriptionSpan'] = document.createElement('div');
  29529. this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none';
  29530. this.manipulationDOM['descriptionLabelSpan'] = document.createElement('div');
  29531. this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel';
  29532. this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['edgeDescription'];
  29533. this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']);
  29534. this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']);
  29535. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
  29536. this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']);
  29537. // bind the icon
  29538. this._bindHammerToDiv(this.manipulationDOM['backSpan'],'_createManipulatorBar');
  29539. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  29540. var me = this;
  29541. this.boundFunction = me._handleConnect;
  29542. this.on('select', this.boundFunction);
  29543. // temporarily overload functions
  29544. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  29545. this.cachedFunctions["_manipulationReleaseOverload"] = this._manipulationReleaseOverload;
  29546. this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
  29547. this.cachedFunctions["_handleDragEnd"] = this._handleDragEnd;
  29548. this.cachedFunctions["_handleOnHold"] = this._handleOnHold;
  29549. this._handleTouch = this._handleConnect;
  29550. this._manipulationReleaseOverload = function () {};
  29551. this._handleOnHold = function () {};
  29552. this._handleDragStart = function () {};
  29553. this._handleDragEnd = this._finishConnect;
  29554. // redraw to show the unselect
  29555. this._redraw();
  29556. };
  29557. /**
  29558. * create the toolbar to edit edges
  29559. *
  29560. * @private
  29561. */
  29562. exports._createEditEdgeToolbar = function() {
  29563. // clear the toolbar
  29564. this._clearManipulatorBar();
  29565. this.controlNodesActive = true;
  29566. if (this.boundFunction) {
  29567. this.off('select', this.boundFunction);
  29568. }
  29569. this.edgeBeingEdited = this._getSelectedEdge();
  29570. this.edgeBeingEdited._enableControlNodes();
  29571. var locale = this.constants.locales[this.constants.locale];
  29572. this.manipulationDOM = {};
  29573. this.manipulationDOM['backSpan'] = document.createElement('div');
  29574. this.manipulationDOM['backSpan'].className = 'network-manipulationUI back';
  29575. this.manipulationDOM['backLabelSpan'] = document.createElement('div');
  29576. this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel';
  29577. this.manipulationDOM['backLabelSpan'].innerHTML = locale['back'];
  29578. this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']);
  29579. this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
  29580. this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
  29581. this.manipulationDOM['descriptionSpan'] = document.createElement('div');
  29582. this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none';
  29583. this.manipulationDOM['descriptionLabelSpan'] = document.createElement('div');
  29584. this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel';
  29585. this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['editEdgeDescription'];
  29586. this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']);
  29587. this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']);
  29588. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
  29589. this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']);
  29590. // bind the icon
  29591. this._bindHammerToDiv(this.manipulationDOM['backSpan'],'_createManipulatorBar');
  29592. // temporarily overload functions
  29593. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  29594. this.cachedFunctions["_manipulationReleaseOverload"] = this._manipulationReleaseOverload;
  29595. this.cachedFunctions["_handleTap"] = this._handleTap;
  29596. this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
  29597. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  29598. this._handleTouch = this._selectControlNode;
  29599. this._handleTap = function () {};
  29600. this._handleOnDrag = this._controlNodeDrag;
  29601. this._handleDragStart = function () {}
  29602. this._manipulationReleaseOverload = this._releaseControlNode;
  29603. // redraw to show the unselect
  29604. this._redraw();
  29605. };
  29606. /**
  29607. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  29608. * to walk the user through the process.
  29609. *
  29610. * @private
  29611. */
  29612. exports._selectControlNode = function(pointer) {
  29613. this.edgeBeingEdited.controlNodes.from.unselect();
  29614. this.edgeBeingEdited.controlNodes.to.unselect();
  29615. this.selectedControlNode = this.edgeBeingEdited._getSelectedControlNode(this._XconvertDOMtoCanvas(pointer.x),this._YconvertDOMtoCanvas(pointer.y));
  29616. if (this.selectedControlNode !== null) {
  29617. this.selectedControlNode.select();
  29618. this.freezeSimulation(true);
  29619. }
  29620. this._redraw();
  29621. };
  29622. /**
  29623. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  29624. * to walk the user through the process.
  29625. *
  29626. * @private
  29627. */
  29628. exports._controlNodeDrag = function(event) {
  29629. var pointer = this._getPointer(event.gesture.center);
  29630. if (this.selectedControlNode !== null && this.selectedControlNode !== undefined) {
  29631. this.selectedControlNode.x = this._XconvertDOMtoCanvas(pointer.x);
  29632. this.selectedControlNode.y = this._YconvertDOMtoCanvas(pointer.y);
  29633. }
  29634. this._redraw();
  29635. };
  29636. /**
  29637. *
  29638. * @param pointer
  29639. * @private
  29640. */
  29641. exports._releaseControlNode = function(pointer) {
  29642. var newNode = this._getNodeAt(pointer);
  29643. if (newNode !== null) {
  29644. if (this.edgeBeingEdited.controlNodes.from.selected == true) {
  29645. this.edgeBeingEdited._restoreControlNodes();
  29646. this._editEdge(newNode.id, this.edgeBeingEdited.to.id);
  29647. this.edgeBeingEdited.controlNodes.from.unselect();
  29648. }
  29649. if (this.edgeBeingEdited.controlNodes.to.selected == true) {
  29650. this.edgeBeingEdited._restoreControlNodes();
  29651. this._editEdge(this.edgeBeingEdited.from.id, newNode.id);
  29652. this.edgeBeingEdited.controlNodes.to.unselect();
  29653. }
  29654. }
  29655. else {
  29656. this.edgeBeingEdited._restoreControlNodes();
  29657. }
  29658. this.freezeSimulation(false);
  29659. this._redraw();
  29660. };
  29661. /**
  29662. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  29663. * to walk the user through the process.
  29664. *
  29665. * @private
  29666. */
  29667. exports._handleConnect = function(pointer) {
  29668. if (this._getSelectedNodeCount() == 0) {
  29669. var node = this._getNodeAt(pointer);
  29670. if (node != null) {
  29671. if (node.clusterSize > 1) {
  29672. alert(this.constants.locales[this.constants.locale]['createEdgeError'])
  29673. }
  29674. else {
  29675. this._selectObject(node,false);
  29676. var supportNodes = this.sectors['support']['nodes'];
  29677. // create a node the temporary line can look at
  29678. supportNodes['targetNode'] = new Node({id:'targetNode'},{},{},this.constants);
  29679. var targetNode = supportNodes['targetNode'];
  29680. targetNode.x = node.x;
  29681. targetNode.y = node.y;
  29682. // create a temporary edge
  29683. this.edges['connectionEdge'] = new Edge({id:"connectionEdge",from:node.id,to:targetNode.id}, this, this.constants);
  29684. var connectionEdge = this.edges['connectionEdge'];
  29685. connectionEdge.from = node;
  29686. connectionEdge.connected = true;
  29687. connectionEdge.options.smoothCurves = {enabled: true,
  29688. dynamic: false,
  29689. type: "continuous",
  29690. roundness: 0.5
  29691. };
  29692. connectionEdge.selected = true;
  29693. connectionEdge.to = targetNode;
  29694. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  29695. var me = this;
  29696. this._handleOnDrag = function(event) {
  29697. var pointer = this._getPointer(event.gesture.center);
  29698. var connectionEdge = me.edges['connectionEdge'];
  29699. connectionEdge.to.x = me._XconvertDOMtoCanvas(pointer.x);
  29700. connectionEdge.to.y = me._YconvertDOMtoCanvas(pointer.y);
  29701. me._redraw();
  29702. };
  29703. this.moving = true;
  29704. this.start();
  29705. }
  29706. }
  29707. }
  29708. };
  29709. exports._finishConnect = function(event) {
  29710. if (this._getSelectedNodeCount() == 1) {
  29711. var pointer = this._getPointer(event.gesture.center);
  29712. // restore the drag function
  29713. this._handleOnDrag = this.cachedFunctions["_handleOnDrag"];
  29714. delete this.cachedFunctions["_handleOnDrag"];
  29715. // remember the edge id
  29716. var connectFromId = this.edges['connectionEdge'].fromId;
  29717. // remove the temporary nodes and edge
  29718. delete this.edges['connectionEdge'];
  29719. delete this.sectors['support']['nodes']['targetNode'];
  29720. delete this.sectors['support']['nodes']['targetViaNode'];
  29721. var node = this._getNodeAt(pointer);
  29722. if (node != null) {
  29723. if (node.clusterSize > 1) {
  29724. alert(this.constants.locales[this.constants.locale]["createEdgeError"])
  29725. }
  29726. else {
  29727. this._createEdge(connectFromId,node.id);
  29728. this._createManipulatorBar();
  29729. }
  29730. }
  29731. this._unselectAll();
  29732. }
  29733. };
  29734. /**
  29735. * Adds a node on the specified location
  29736. */
  29737. exports._addNode = function() {
  29738. if (this._selectionIsEmpty() && this.editMode == true) {
  29739. var positionObject = this._pointerToPositionObject(this.pointerPosition);
  29740. var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",allowedToMoveX:true,allowedToMoveY:true};
  29741. if (this.triggerFunctions.add) {
  29742. if (this.triggerFunctions.add.length == 2) {
  29743. var me = this;
  29744. this.triggerFunctions.add(defaultData, function(finalizedData) {
  29745. me.nodesData.add(finalizedData);
  29746. me._createManipulatorBar();
  29747. me.moving = true;
  29748. me.start();
  29749. });
  29750. }
  29751. else {
  29752. throw new Error('The function for add does not support two arguments (data,callback)');
  29753. this._createManipulatorBar();
  29754. this.moving = true;
  29755. this.start();
  29756. }
  29757. }
  29758. else {
  29759. this.nodesData.add(defaultData);
  29760. this._createManipulatorBar();
  29761. this.moving = true;
  29762. this.start();
  29763. }
  29764. }
  29765. };
  29766. /**
  29767. * connect two nodes with a new edge.
  29768. *
  29769. * @private
  29770. */
  29771. exports._createEdge = function(sourceNodeId,targetNodeId) {
  29772. if (this.editMode == true) {
  29773. var defaultData = {from:sourceNodeId, to:targetNodeId};
  29774. if (this.triggerFunctions.connect) {
  29775. if (this.triggerFunctions.connect.length == 2) {
  29776. var me = this;
  29777. this.triggerFunctions.connect(defaultData, function(finalizedData) {
  29778. me.edgesData.add(finalizedData);
  29779. me.moving = true;
  29780. me.start();
  29781. });
  29782. }
  29783. else {
  29784. throw new Error('The function for connect does not support two arguments (data,callback)');
  29785. this.moving = true;
  29786. this.start();
  29787. }
  29788. }
  29789. else {
  29790. this.edgesData.add(defaultData);
  29791. this.moving = true;
  29792. this.start();
  29793. }
  29794. }
  29795. };
  29796. /**
  29797. * connect two nodes with a new edge.
  29798. *
  29799. * @private
  29800. */
  29801. exports._editEdge = function(sourceNodeId,targetNodeId) {
  29802. if (this.editMode == true) {
  29803. var defaultData = {id: this.edgeBeingEdited.id, from:sourceNodeId, to:targetNodeId};
  29804. if (this.triggerFunctions.editEdge) {
  29805. if (this.triggerFunctions.editEdge.length == 2) {
  29806. var me = this;
  29807. this.triggerFunctions.editEdge(defaultData, function(finalizedData) {
  29808. me.edgesData.update(finalizedData);
  29809. me.moving = true;
  29810. me.start();
  29811. });
  29812. }
  29813. else {
  29814. throw new Error('The function for edit does not support two arguments (data, callback)');
  29815. this.moving = true;
  29816. this.start();
  29817. }
  29818. }
  29819. else {
  29820. this.edgesData.update(defaultData);
  29821. this.moving = true;
  29822. this.start();
  29823. }
  29824. }
  29825. };
  29826. /**
  29827. * Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color.
  29828. *
  29829. * @private
  29830. */
  29831. exports._editNode = function() {
  29832. if (this.triggerFunctions.edit && this.editMode == true) {
  29833. var node = this._getSelectedNode();
  29834. var data = {id:node.id,
  29835. label: node.label,
  29836. group: node.options.group,
  29837. shape: node.options.shape,
  29838. color: {
  29839. background:node.options.color.background,
  29840. border:node.options.color.border,
  29841. highlight: {
  29842. background:node.options.color.highlight.background,
  29843. border:node.options.color.highlight.border
  29844. }
  29845. }};
  29846. if (this.triggerFunctions.edit.length == 2) {
  29847. var me = this;
  29848. this.triggerFunctions.edit(data, function (finalizedData) {
  29849. me.nodesData.update(finalizedData);
  29850. me._createManipulatorBar();
  29851. me.moving = true;
  29852. me.start();
  29853. });
  29854. }
  29855. else {
  29856. throw new Error('The function for edit does not support two arguments (data, callback)');
  29857. }
  29858. }
  29859. else {
  29860. throw new Error('No edit function has been bound to this button');
  29861. }
  29862. };
  29863. /**
  29864. * delete everything in the selection
  29865. *
  29866. * @private
  29867. */
  29868. exports._deleteSelected = function() {
  29869. if (!this._selectionIsEmpty() && this.editMode == true) {
  29870. if (!this._clusterInSelection()) {
  29871. var selectedNodes = this.getSelectedNodes();
  29872. var selectedEdges = this.getSelectedEdges();
  29873. if (this.triggerFunctions.del) {
  29874. var me = this;
  29875. var data = {nodes: selectedNodes, edges: selectedEdges};
  29876. if (this.triggerFunctions.del.length == 2) {
  29877. this.triggerFunctions.del(data, function (finalizedData) {
  29878. me.edgesData.remove(finalizedData.edges);
  29879. me.nodesData.remove(finalizedData.nodes);
  29880. me._unselectAll();
  29881. me.moving = true;
  29882. me.start();
  29883. });
  29884. }
  29885. else {
  29886. throw new Error('The function for delete does not support two arguments (data, callback)')
  29887. }
  29888. }
  29889. else {
  29890. this.edgesData.remove(selectedEdges);
  29891. this.nodesData.remove(selectedNodes);
  29892. this._unselectAll();
  29893. this.moving = true;
  29894. this.start();
  29895. }
  29896. }
  29897. else {
  29898. alert(this.constants.locales[this.constants.locale]["deleteClusterError"]);
  29899. }
  29900. }
  29901. };
  29902. /***/ },
  29903. /* 65 */
  29904. /***/ function(module, exports, __webpack_require__) {
  29905. var util = __webpack_require__(1);
  29906. var Hammer = __webpack_require__(45);
  29907. exports._cleanNavigation = function() {
  29908. // clean hammer bindings
  29909. if (this.navigationHammers.length != 0) {
  29910. for (var i = 0; i < this.navigationHammers.length; i++) {
  29911. this.navigationHammers[i].dispose();
  29912. }
  29913. this.navigationHammers = [];
  29914. }
  29915. this._navigationReleaseOverload = function () {};
  29916. // clean up previous navigation items
  29917. if (this.navigationDOM && this.navigationDOM['wrapper'] && this.navigationDOM['wrapper'].parentNode) {
  29918. this.navigationDOM['wrapper'].parentNode.removeChild(this.navigationDOM['wrapper']);
  29919. }
  29920. };
  29921. /**
  29922. * Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation
  29923. * they have a triggerFunction which is called on click. If the position of the navigation controls is dependent
  29924. * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false.
  29925. * This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas.
  29926. *
  29927. * @private
  29928. */
  29929. exports._loadNavigationElements = function() {
  29930. this._cleanNavigation();
  29931. this.navigationDOM = {};
  29932. var navigationDivs = ['up','down','left','right','zoomIn','zoomOut','zoomExtends'];
  29933. var navigationDivActions = ['_moveUp','_moveDown','_moveLeft','_moveRight','_zoomIn','_zoomOut','_zoomExtent'];
  29934. this.navigationDOM['wrapper'] = document.createElement('div');
  29935. this.frame.appendChild(this.navigationDOM['wrapper']);
  29936. for (var i = 0; i < navigationDivs.length; i++) {
  29937. this.navigationDOM[navigationDivs[i]] = document.createElement('div');
  29938. this.navigationDOM[navigationDivs[i]].className = 'network-navigation ' + navigationDivs[i];
  29939. this.navigationDOM['wrapper'].appendChild(this.navigationDOM[navigationDivs[i]]);
  29940. var hammer = Hammer(this.navigationDOM[navigationDivs[i]], {prevent_default: true});
  29941. hammer.on('touch', this[navigationDivActions[i]].bind(this));
  29942. this.navigationHammers.push(hammer);
  29943. }
  29944. this._navigationReleaseOverload = this._stopMovement;
  29945. };
  29946. /**
  29947. * this stops all movement induced by the navigation buttons
  29948. *
  29949. * @private
  29950. */
  29951. exports._zoomExtent = function(event) {
  29952. this.zoomExtent({duration:700});
  29953. event.stopPropagation();
  29954. };
  29955. /**
  29956. * this stops all movement induced by the navigation buttons
  29957. *
  29958. * @private
  29959. */
  29960. exports._stopMovement = function() {
  29961. this._xStopMoving();
  29962. this._yStopMoving();
  29963. this._stopZoom();
  29964. };
  29965. /**
  29966. * move the screen up
  29967. * By using the increments, instead of adding a fixed number to the translation, we keep fluent and
  29968. * instant movement. The onKeypress event triggers immediately, then pauses, then triggers frequently
  29969. * To avoid this behaviour, we do the translation in the start loop.
  29970. *
  29971. * @private
  29972. */
  29973. exports._moveUp = function(event) {
  29974. this.yIncrement = this.constants.keyboard.speed.y;
  29975. this.start(); // if there is no node movement, the calculation wont be done
  29976. event.preventDefault();
  29977. };
  29978. /**
  29979. * move the screen down
  29980. * @private
  29981. */
  29982. exports._moveDown = function(event) {
  29983. this.yIncrement = -this.constants.keyboard.speed.y;
  29984. this.start(); // if there is no node movement, the calculation wont be done
  29985. event.preventDefault();
  29986. };
  29987. /**
  29988. * move the screen left
  29989. * @private
  29990. */
  29991. exports._moveLeft = function(event) {
  29992. this.xIncrement = this.constants.keyboard.speed.x;
  29993. this.start(); // if there is no node movement, the calculation wont be done
  29994. event.preventDefault();
  29995. };
  29996. /**
  29997. * move the screen right
  29998. * @private
  29999. */
  30000. exports._moveRight = function(event) {
  30001. this.xIncrement = -this.constants.keyboard.speed.y;
  30002. this.start(); // if there is no node movement, the calculation wont be done
  30003. event.preventDefault();
  30004. };
  30005. /**
  30006. * Zoom in, using the same method as the movement.
  30007. * @private
  30008. */
  30009. exports._zoomIn = function(event) {
  30010. this.zoomIncrement = this.constants.keyboard.speed.zoom;
  30011. this.start(); // if there is no node movement, the calculation wont be done
  30012. event.preventDefault();
  30013. };
  30014. /**
  30015. * Zoom out
  30016. * @private
  30017. */
  30018. exports._zoomOut = function(event) {
  30019. this.zoomIncrement = -this.constants.keyboard.speed.zoom;
  30020. this.start(); // if there is no node movement, the calculation wont be done
  30021. event.preventDefault();
  30022. };
  30023. /**
  30024. * Stop zooming and unhighlight the zoom controls
  30025. * @private
  30026. */
  30027. exports._stopZoom = function(event) {
  30028. this.zoomIncrement = 0;
  30029. event && event.preventDefault();
  30030. };
  30031. /**
  30032. * Stop moving in the Y direction and unHighlight the up and down
  30033. * @private
  30034. */
  30035. exports._yStopMoving = function(event) {
  30036. this.yIncrement = 0;
  30037. event && event.preventDefault();
  30038. };
  30039. /**
  30040. * Stop moving in the X direction and unHighlight left and right.
  30041. * @private
  30042. */
  30043. exports._xStopMoving = function(event) {
  30044. this.xIncrement = 0;
  30045. event && event.preventDefault();
  30046. };
  30047. /***/ },
  30048. /* 66 */
  30049. /***/ function(module, exports, __webpack_require__) {
  30050. exports._resetLevels = function() {
  30051. for (var nodeId in this.nodes) {
  30052. if (this.nodes.hasOwnProperty(nodeId)) {
  30053. var node = this.nodes[nodeId];
  30054. if (node.preassignedLevel == false) {
  30055. node.level = -1;
  30056. node.hierarchyEnumerated = false;
  30057. }
  30058. }
  30059. }
  30060. };
  30061. /**
  30062. * This is the main function to layout the nodes in a hierarchical way.
  30063. * It checks if the node details are supplied correctly
  30064. *
  30065. * @private
  30066. */
  30067. exports._setupHierarchicalLayout = function() {
  30068. if (this.constants.hierarchicalLayout.enabled == true && this.nodeIndices.length > 0) {
  30069. // get the size of the largest hubs and check if the user has defined a level for a node.
  30070. var hubsize = 0;
  30071. var node, nodeId;
  30072. var definedLevel = false;
  30073. var undefinedLevel = false;
  30074. for (nodeId in this.nodes) {
  30075. if (this.nodes.hasOwnProperty(nodeId)) {
  30076. node = this.nodes[nodeId];
  30077. if (node.level != -1) {
  30078. definedLevel = true;
  30079. }
  30080. else {
  30081. undefinedLevel = true;
  30082. }
  30083. if (hubsize < node.edges.length) {
  30084. hubsize = node.edges.length;
  30085. }
  30086. }
  30087. }
  30088. // if the user defined some levels but not all, alert and run without hierarchical layout
  30089. if (undefinedLevel == true && definedLevel == true) {
  30090. throw new Error("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.");
  30091. this.zoomExtent({duration:0},true,this.constants.clustering.enabled);
  30092. if (!this.constants.clustering.enabled) {
  30093. this.start();
  30094. }
  30095. }
  30096. else {
  30097. // setup the system to use hierarchical method.
  30098. this._changeConstants();
  30099. // define levels if undefined by the users. Based on hubsize
  30100. if (undefinedLevel == true) {
  30101. if (this.constants.hierarchicalLayout.layout == "hubsize") {
  30102. this._determineLevels(hubsize);
  30103. }
  30104. else {
  30105. this._determineLevelsDirected(false);
  30106. }
  30107. }
  30108. // check the distribution of the nodes per level.
  30109. var distribution = this._getDistribution();
  30110. // place the nodes on the canvas. This also stablilizes the system.
  30111. this._placeNodesByHierarchy(distribution);
  30112. // start the simulation.
  30113. this.start();
  30114. }
  30115. }
  30116. };
  30117. /**
  30118. * This function places the nodes on the canvas based on the hierarchial distribution.
  30119. *
  30120. * @param {Object} distribution | obtained by the function this._getDistribution()
  30121. * @private
  30122. */
  30123. exports._placeNodesByHierarchy = function(distribution) {
  30124. var nodeId, node;
  30125. // start placing all the level 0 nodes first. Then recursively position their branches.
  30126. for (var level in distribution) {
  30127. if (distribution.hasOwnProperty(level)) {
  30128. for (nodeId in distribution[level].nodes) {
  30129. if (distribution[level].nodes.hasOwnProperty(nodeId)) {
  30130. node = distribution[level].nodes[nodeId];
  30131. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  30132. if (node.xFixed) {
  30133. node.x = distribution[level].minPos;
  30134. node.xFixed = false;
  30135. distribution[level].minPos += distribution[level].nodeSpacing;
  30136. }
  30137. }
  30138. else {
  30139. if (node.yFixed) {
  30140. node.y = distribution[level].minPos;
  30141. node.yFixed = false;
  30142. distribution[level].minPos += distribution[level].nodeSpacing;
  30143. }
  30144. }
  30145. this._placeBranchNodes(node.edges,node.id,distribution,node.level);
  30146. }
  30147. }
  30148. }
  30149. }
  30150. // stabilize the system after positioning. This function calls zoomExtent.
  30151. this._stabilize();
  30152. };
  30153. /**
  30154. * This function get the distribution of levels based on hubsize
  30155. *
  30156. * @returns {Object}
  30157. * @private
  30158. */
  30159. exports._getDistribution = function() {
  30160. var distribution = {};
  30161. var nodeId, node, level;
  30162. // 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.
  30163. // the fix of X is removed after the x value has been set.
  30164. for (nodeId in this.nodes) {
  30165. if (this.nodes.hasOwnProperty(nodeId)) {
  30166. node = this.nodes[nodeId];
  30167. node.xFixed = true;
  30168. node.yFixed = true;
  30169. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  30170. node.y = this.constants.hierarchicalLayout.levelSeparation*node.level;
  30171. }
  30172. else {
  30173. node.x = this.constants.hierarchicalLayout.levelSeparation*node.level;
  30174. }
  30175. if (distribution[node.level] === undefined) {
  30176. distribution[node.level] = {amount: 0, nodes: {}, minPos:0, nodeSpacing:0};
  30177. }
  30178. distribution[node.level].amount += 1;
  30179. distribution[node.level].nodes[nodeId] = node;
  30180. }
  30181. }
  30182. // determine the largest amount of nodes of all levels
  30183. var maxCount = 0;
  30184. for (level in distribution) {
  30185. if (distribution.hasOwnProperty(level)) {
  30186. if (maxCount < distribution[level].amount) {
  30187. maxCount = distribution[level].amount;
  30188. }
  30189. }
  30190. }
  30191. // set the initial position and spacing of each nodes accordingly
  30192. for (level in distribution) {
  30193. if (distribution.hasOwnProperty(level)) {
  30194. distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing;
  30195. distribution[level].nodeSpacing /= (distribution[level].amount + 1);
  30196. distribution[level].minPos = distribution[level].nodeSpacing - (0.5 * (distribution[level].amount + 1) * distribution[level].nodeSpacing);
  30197. }
  30198. }
  30199. return distribution;
  30200. };
  30201. /**
  30202. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  30203. *
  30204. * @param hubsize
  30205. * @private
  30206. */
  30207. exports._determineLevels = function(hubsize) {
  30208. var nodeId, node;
  30209. // determine hubs
  30210. for (nodeId in this.nodes) {
  30211. if (this.nodes.hasOwnProperty(nodeId)) {
  30212. node = this.nodes[nodeId];
  30213. if (node.edges.length == hubsize) {
  30214. node.level = 0;
  30215. }
  30216. }
  30217. }
  30218. // branch from hubs
  30219. for (nodeId in this.nodes) {
  30220. if (this.nodes.hasOwnProperty(nodeId)) {
  30221. node = this.nodes[nodeId];
  30222. if (node.level == 0) {
  30223. this._setLevel(1,node.edges,node.id);
  30224. }
  30225. }
  30226. }
  30227. };
  30228. /**
  30229. * this function allocates nodes in levels based on the direction of the edges
  30230. *
  30231. * @param hubsize
  30232. * @private
  30233. */
  30234. exports._determineLevelsDirected = function() {
  30235. var nodeId, node, firstNode;
  30236. var minLevel = 10000;
  30237. // set first node to source
  30238. firstNode = this.nodes[this.nodeIndices[0]];
  30239. firstNode.level = minLevel;
  30240. this._setLevelDirected(minLevel,firstNode.edges,firstNode.id);
  30241. // get the minimum level
  30242. for (nodeId in this.nodes) {
  30243. if (this.nodes.hasOwnProperty(nodeId)) {
  30244. node = this.nodes[nodeId];
  30245. minLevel = node.level < minLevel ? node.level : minLevel;
  30246. }
  30247. }
  30248. // subtract the minimum from the set so we have a range starting from 0
  30249. for (nodeId in this.nodes) {
  30250. if (this.nodes.hasOwnProperty(nodeId)) {
  30251. node = this.nodes[nodeId];
  30252. node.level -= minLevel;
  30253. }
  30254. }
  30255. };
  30256. /**
  30257. * Since hierarchical layout does not support:
  30258. * - smooth curves (based on the physics),
  30259. * - clustering (based on dynamic node counts)
  30260. *
  30261. * We disable both features so there will be no problems.
  30262. *
  30263. * @private
  30264. */
  30265. exports._changeConstants = function() {
  30266. this.constants.clustering.enabled = false;
  30267. this.constants.physics.barnesHut.enabled = false;
  30268. this.constants.physics.hierarchicalRepulsion.enabled = true;
  30269. this._loadSelectedForceSolver();
  30270. if (this.constants.smoothCurves.enabled == true) {
  30271. this.constants.smoothCurves.dynamic = false;
  30272. }
  30273. this._configureSmoothCurves();
  30274. var config = this.constants.hierarchicalLayout;
  30275. config.levelSeparation = Math.abs(config.levelSeparation);
  30276. if (config.direction == "RL" || config.direction == "DU") {
  30277. config.levelSeparation *= -1;
  30278. }
  30279. if (config.direction == "RL" || config.direction == "LR") {
  30280. if (this.constants.smoothCurves.enabled == true) {
  30281. this.constants.smoothCurves.type = "vertical";
  30282. }
  30283. }
  30284. else {
  30285. if (this.constants.smoothCurves.enabled == true) {
  30286. this.constants.smoothCurves.type = "horizontal";
  30287. }
  30288. }
  30289. };
  30290. /**
  30291. * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
  30292. * on a X position that ensures there will be no overlap.
  30293. *
  30294. * @param edges
  30295. * @param parentId
  30296. * @param distribution
  30297. * @param parentLevel
  30298. * @private
  30299. */
  30300. exports._placeBranchNodes = function(edges, parentId, distribution, parentLevel) {
  30301. for (var i = 0; i < edges.length; i++) {
  30302. var childNode = null;
  30303. if (edges[i].toId == parentId) {
  30304. childNode = edges[i].from;
  30305. }
  30306. else {
  30307. childNode = edges[i].to;
  30308. }
  30309. // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
  30310. var nodeMoved = false;
  30311. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  30312. if (childNode.xFixed && childNode.level > parentLevel) {
  30313. childNode.xFixed = false;
  30314. childNode.x = distribution[childNode.level].minPos;
  30315. nodeMoved = true;
  30316. }
  30317. }
  30318. else {
  30319. if (childNode.yFixed && childNode.level > parentLevel) {
  30320. childNode.yFixed = false;
  30321. childNode.y = distribution[childNode.level].minPos;
  30322. nodeMoved = true;
  30323. }
  30324. }
  30325. if (nodeMoved == true) {
  30326. distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing;
  30327. if (childNode.edges.length > 1) {
  30328. this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level);
  30329. }
  30330. }
  30331. }
  30332. };
  30333. /**
  30334. * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
  30335. *
  30336. * @param level
  30337. * @param edges
  30338. * @param parentId
  30339. * @private
  30340. */
  30341. exports._setLevel = function(level, edges, parentId) {
  30342. for (var i = 0; i < edges.length; i++) {
  30343. var childNode = null;
  30344. if (edges[i].toId == parentId) {
  30345. childNode = edges[i].from;
  30346. }
  30347. else {
  30348. childNode = edges[i].to;
  30349. }
  30350. if (childNode.level == -1 || childNode.level > level) {
  30351. childNode.level = level;
  30352. if (childNode.edges.length > 1) {
  30353. this._setLevel(level+1, childNode.edges, childNode.id);
  30354. }
  30355. }
  30356. }
  30357. };
  30358. /**
  30359. * this function is called recursively to enumerate the branched of the first node and give each node a level based on edge direction
  30360. *
  30361. * @param level
  30362. * @param edges
  30363. * @param parentId
  30364. * @private
  30365. */
  30366. exports._setLevelDirected = function(level, edges, parentId) {
  30367. this.nodes[parentId].hierarchyEnumerated = true;
  30368. var childNode, direction;
  30369. for (var i = 0; i < edges.length; i++) {
  30370. direction = 1;
  30371. if (edges[i].toId == parentId) {
  30372. childNode = edges[i].from;
  30373. direction = -1;
  30374. }
  30375. else {
  30376. childNode = edges[i].to;
  30377. }
  30378. if (childNode.level == -1) {
  30379. childNode.level = level + direction;
  30380. }
  30381. }
  30382. for (var i = 0; i < edges.length; i++) {
  30383. if (edges[i].toId == parentId) {childNode = edges[i].from;}
  30384. else {childNode = edges[i].to;}
  30385. if (childNode.edges.length > 1 && childNode.hierarchyEnumerated === false) {
  30386. this._setLevelDirected(childNode.level, childNode.edges, childNode.id);
  30387. }
  30388. }
  30389. };
  30390. /**
  30391. * Unfix nodes
  30392. *
  30393. * @private
  30394. */
  30395. exports._restoreNodes = function() {
  30396. for (var nodeId in this.nodes) {
  30397. if (this.nodes.hasOwnProperty(nodeId)) {
  30398. this.nodes[nodeId].xFixed = false;
  30399. this.nodes[nodeId].yFixed = false;
  30400. }
  30401. }
  30402. };
  30403. /***/ },
  30404. /* 67 */
  30405. /***/ function(module, exports, __webpack_require__) {
  30406. /**
  30407. * Calculate the forces the nodes apply on each other based on a repulsion field.
  30408. * This field is linearly approximated.
  30409. *
  30410. * @private
  30411. */
  30412. exports._calculateNodeForces = function () {
  30413. var dx, dy, angle, distance, fx, fy, combinedClusterSize,
  30414. repulsingForce, node1, node2, i, j;
  30415. var nodes = this.calculationNodes;
  30416. var nodeIndices = this.calculationNodeIndices;
  30417. // approximation constants
  30418. var a_base = -2 / 3;
  30419. var b = 4 / 3;
  30420. // repulsing forces between nodes
  30421. var nodeDistance = this.constants.physics.repulsion.nodeDistance;
  30422. var minimumDistance = nodeDistance;
  30423. // we loop from i over all but the last entree in the array
  30424. // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  30425. for (i = 0; i < nodeIndices.length - 1; i++) {
  30426. node1 = nodes[nodeIndices[i]];
  30427. for (j = i + 1; j < nodeIndices.length; j++) {
  30428. node2 = nodes[nodeIndices[j]];
  30429. combinedClusterSize = node1.clusterSize + node2.clusterSize - 2;
  30430. dx = node2.x - node1.x;
  30431. dy = node2.y - node1.y;
  30432. distance = Math.sqrt(dx * dx + dy * dy);
  30433. // same condition as BarnesHut, making sure nodes are never 100% overlapping.
  30434. if (distance == 0) {
  30435. distance = 0.1*Math.random();
  30436. dx = distance;
  30437. }
  30438. minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification));
  30439. var a = a_base / minimumDistance;
  30440. if (distance < 2 * minimumDistance) {
  30441. if (distance < 0.5 * minimumDistance) {
  30442. repulsingForce = 1.0;
  30443. }
  30444. else {
  30445. repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness))
  30446. }
  30447. // amplify the repulsion for clusters.
  30448. repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification;
  30449. repulsingForce = repulsingForce / Math.max(distance,0.01*minimumDistance);
  30450. fx = dx * repulsingForce;
  30451. fy = dy * repulsingForce;
  30452. node1.fx -= fx;
  30453. node1.fy -= fy;
  30454. node2.fx += fx;
  30455. node2.fy += fy;
  30456. }
  30457. }
  30458. }
  30459. };
  30460. /***/ },
  30461. /* 68 */
  30462. /***/ function(module, exports, __webpack_require__) {
  30463. /**
  30464. * Calculate the forces the nodes apply on eachother based on a repulsion field.
  30465. * This field is linearly approximated.
  30466. *
  30467. * @private
  30468. */
  30469. exports._calculateNodeForces = function () {
  30470. var dx, dy, distance, fx, fy,
  30471. repulsingForce, node1, node2, i, j;
  30472. var nodes = this.calculationNodes;
  30473. var nodeIndices = this.calculationNodeIndices;
  30474. // repulsing forces between nodes
  30475. var nodeDistance = this.constants.physics.hierarchicalRepulsion.nodeDistance;
  30476. // we loop from i over all but the last entree in the array
  30477. // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  30478. for (i = 0; i < nodeIndices.length - 1; i++) {
  30479. node1 = nodes[nodeIndices[i]];
  30480. for (j = i + 1; j < nodeIndices.length; j++) {
  30481. node2 = nodes[nodeIndices[j]];
  30482. // nodes only affect nodes on their level
  30483. if (node1.level == node2.level) {
  30484. dx = node2.x - node1.x;
  30485. dy = node2.y - node1.y;
  30486. distance = Math.sqrt(dx * dx + dy * dy);
  30487. var steepness = 0.05;
  30488. if (distance < nodeDistance) {
  30489. repulsingForce = -Math.pow(steepness*distance,2) + Math.pow(steepness*nodeDistance,2);
  30490. }
  30491. else {
  30492. repulsingForce = 0;
  30493. }
  30494. // normalize force with
  30495. if (distance == 0) {
  30496. distance = 0.01;
  30497. }
  30498. else {
  30499. repulsingForce = repulsingForce / distance;
  30500. }
  30501. fx = dx * repulsingForce;
  30502. fy = dy * repulsingForce;
  30503. node1.fx -= fx;
  30504. node1.fy -= fy;
  30505. node2.fx += fx;
  30506. node2.fy += fy;
  30507. }
  30508. }
  30509. }
  30510. };
  30511. /**
  30512. * this function calculates the effects of the springs in the case of unsmooth curves.
  30513. *
  30514. * @private
  30515. */
  30516. exports._calculateHierarchicalSpringForces = function () {
  30517. var edgeLength, edge, edgeId;
  30518. var dx, dy, fx, fy, springForce, distance;
  30519. var edges = this.edges;
  30520. var nodes = this.calculationNodes;
  30521. var nodeIndices = this.calculationNodeIndices;
  30522. for (var i = 0; i < nodeIndices.length; i++) {
  30523. var node1 = nodes[nodeIndices[i]];
  30524. node1.springFx = 0;
  30525. node1.springFy = 0;
  30526. }
  30527. // forces caused by the edges, modelled as springs
  30528. for (edgeId in edges) {
  30529. if (edges.hasOwnProperty(edgeId)) {
  30530. edge = edges[edgeId];
  30531. if (edge.connected) {
  30532. // only calculate forces if nodes are in the same sector
  30533. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  30534. edgeLength = edge.physics.springLength;
  30535. // this implies that the edges between big clusters are longer
  30536. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  30537. dx = (edge.from.x - edge.to.x);
  30538. dy = (edge.from.y - edge.to.y);
  30539. distance = Math.sqrt(dx * dx + dy * dy);
  30540. if (distance == 0) {
  30541. distance = 0.01;
  30542. }
  30543. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  30544. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  30545. fx = dx * springForce;
  30546. fy = dy * springForce;
  30547. if (edge.to.level != edge.from.level) {
  30548. edge.to.springFx -= fx;
  30549. edge.to.springFy -= fy;
  30550. edge.from.springFx += fx;
  30551. edge.from.springFy += fy;
  30552. }
  30553. else {
  30554. var factor = 0.5;
  30555. edge.to.fx -= factor*fx;
  30556. edge.to.fy -= factor*fy;
  30557. edge.from.fx += factor*fx;
  30558. edge.from.fy += factor*fy;
  30559. }
  30560. }
  30561. }
  30562. }
  30563. }
  30564. // normalize spring forces
  30565. var springForce = 1;
  30566. var springFx, springFy;
  30567. for (i = 0; i < nodeIndices.length; i++) {
  30568. var node = nodes[nodeIndices[i]];
  30569. springFx = Math.min(springForce,Math.max(-springForce,node.springFx));
  30570. springFy = Math.min(springForce,Math.max(-springForce,node.springFy));
  30571. node.fx += springFx;
  30572. node.fy += springFy;
  30573. }
  30574. // retain energy balance
  30575. var totalFx = 0;
  30576. var totalFy = 0;
  30577. for (i = 0; i < nodeIndices.length; i++) {
  30578. var node = nodes[nodeIndices[i]];
  30579. totalFx += node.fx;
  30580. totalFy += node.fy;
  30581. }
  30582. var correctionFx = totalFx / nodeIndices.length;
  30583. var correctionFy = totalFy / nodeIndices.length;
  30584. for (i = 0; i < nodeIndices.length; i++) {
  30585. var node = nodes[nodeIndices[i]];
  30586. node.fx -= correctionFx;
  30587. node.fy -= correctionFy;
  30588. }
  30589. };
  30590. /***/ },
  30591. /* 69 */
  30592. /***/ function(module, exports, __webpack_require__) {
  30593. /**
  30594. * This function calculates the forces the nodes apply on eachother based on a gravitational model.
  30595. * The Barnes Hut method is used to speed up this N-body simulation.
  30596. *
  30597. * @private
  30598. */
  30599. exports._calculateNodeForces = function() {
  30600. if (this.constants.physics.barnesHut.gravitationalConstant != 0) {
  30601. var node;
  30602. var nodes = this.calculationNodes;
  30603. var nodeIndices = this.calculationNodeIndices;
  30604. var nodeCount = nodeIndices.length;
  30605. this._formBarnesHutTree(nodes,nodeIndices);
  30606. var barnesHutTree = this.barnesHutTree;
  30607. // place the nodes one by one recursively
  30608. for (var i = 0; i < nodeCount; i++) {
  30609. node = nodes[nodeIndices[i]];
  30610. if (node.options.mass > 0) {
  30611. // starting with root is irrelevant, it never passes the BarnesHut condition
  30612. this._getForceContribution(barnesHutTree.root.children.NW,node);
  30613. this._getForceContribution(barnesHutTree.root.children.NE,node);
  30614. this._getForceContribution(barnesHutTree.root.children.SW,node);
  30615. this._getForceContribution(barnesHutTree.root.children.SE,node);
  30616. }
  30617. }
  30618. }
  30619. };
  30620. /**
  30621. * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass.
  30622. * If a region contains a single node, we check if it is not itself, then we apply the force.
  30623. *
  30624. * @param parentBranch
  30625. * @param node
  30626. * @private
  30627. */
  30628. exports._getForceContribution = function(parentBranch,node) {
  30629. // we get no force contribution from an empty region
  30630. if (parentBranch.childrenCount > 0) {
  30631. var dx,dy,distance;
  30632. // get the distance from the center of mass to the node.
  30633. dx = parentBranch.centerOfMass.x - node.x;
  30634. dy = parentBranch.centerOfMass.y - node.y;
  30635. distance = Math.sqrt(dx * dx + dy * dy);
  30636. // BarnesHut condition
  30637. // original condition : s/d < thetaInverted = passed === d/s > 1/theta = passed
  30638. // calcSize = 1/s --> d * 1/s > 1/theta = passed
  30639. if (distance * parentBranch.calcSize > this.constants.physics.barnesHut.thetaInverted) {
  30640. // duplicate code to reduce function calls to speed up program
  30641. if (distance == 0) {
  30642. distance = 0.1*Math.random();
  30643. dx = distance;
  30644. }
  30645. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
  30646. var fx = dx * gravityForce;
  30647. var fy = dy * gravityForce;
  30648. node.fx += fx;
  30649. node.fy += fy;
  30650. }
  30651. else {
  30652. // Did not pass the condition, go into children if available
  30653. if (parentBranch.childrenCount == 4) {
  30654. this._getForceContribution(parentBranch.children.NW,node);
  30655. this._getForceContribution(parentBranch.children.NE,node);
  30656. this._getForceContribution(parentBranch.children.SW,node);
  30657. this._getForceContribution(parentBranch.children.SE,node);
  30658. }
  30659. else { // parentBranch must have only one node, if it was empty we wouldnt be here
  30660. if (parentBranch.children.data.id != node.id) { // if it is not self
  30661. // duplicate code to reduce function calls to speed up program
  30662. if (distance == 0) {
  30663. distance = 0.5*Math.random();
  30664. dx = distance;
  30665. }
  30666. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
  30667. var fx = dx * gravityForce;
  30668. var fy = dy * gravityForce;
  30669. node.fx += fx;
  30670. node.fy += fy;
  30671. }
  30672. }
  30673. }
  30674. }
  30675. };
  30676. /**
  30677. * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes.
  30678. *
  30679. * @param nodes
  30680. * @param nodeIndices
  30681. * @private
  30682. */
  30683. exports._formBarnesHutTree = function(nodes,nodeIndices) {
  30684. var node;
  30685. var nodeCount = nodeIndices.length;
  30686. var minX = Number.MAX_VALUE,
  30687. minY = Number.MAX_VALUE,
  30688. maxX =-Number.MAX_VALUE,
  30689. maxY =-Number.MAX_VALUE;
  30690. // get the range of the nodes
  30691. for (var i = 0; i < nodeCount; i++) {
  30692. var x = nodes[nodeIndices[i]].x;
  30693. var y = nodes[nodeIndices[i]].y;
  30694. if (nodes[nodeIndices[i]].options.mass > 0) {
  30695. if (x < minX) { minX = x; }
  30696. if (x > maxX) { maxX = x; }
  30697. if (y < minY) { minY = y; }
  30698. if (y > maxY) { maxY = y; }
  30699. }
  30700. }
  30701. // make the range a square
  30702. var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
  30703. if (sizeDiff > 0) {minY -= 0.5 * sizeDiff; maxY += 0.5 * sizeDiff;} // xSize > ySize
  30704. else {minX += 0.5 * sizeDiff; maxX -= 0.5 * sizeDiff;} // xSize < ySize
  30705. var minimumTreeSize = 1e-5;
  30706. var rootSize = Math.max(minimumTreeSize,Math.abs(maxX - minX));
  30707. var halfRootSize = 0.5 * rootSize;
  30708. var centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY);
  30709. // construct the barnesHutTree
  30710. var barnesHutTree = {
  30711. root:{
  30712. centerOfMass: {x:0, y:0},
  30713. mass:0,
  30714. range: {
  30715. minX: centerX-halfRootSize,maxX:centerX+halfRootSize,
  30716. minY: centerY-halfRootSize,maxY:centerY+halfRootSize
  30717. },
  30718. size: rootSize,
  30719. calcSize: 1 / rootSize,
  30720. children: { data:null},
  30721. maxWidth: 0,
  30722. level: 0,
  30723. childrenCount: 4
  30724. }
  30725. };
  30726. this._splitBranch(barnesHutTree.root);
  30727. // place the nodes one by one recursively
  30728. for (i = 0; i < nodeCount; i++) {
  30729. node = nodes[nodeIndices[i]];
  30730. if (node.options.mass > 0) {
  30731. this._placeInTree(barnesHutTree.root,node);
  30732. }
  30733. }
  30734. // make global
  30735. this.barnesHutTree = barnesHutTree
  30736. };
  30737. /**
  30738. * this updates the mass of a branch. this is increased by adding a node.
  30739. *
  30740. * @param parentBranch
  30741. * @param node
  30742. * @private
  30743. */
  30744. exports._updateBranchMass = function(parentBranch, node) {
  30745. var totalMass = parentBranch.mass + node.options.mass;
  30746. var totalMassInv = 1/totalMass;
  30747. parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass;
  30748. parentBranch.centerOfMass.x *= totalMassInv;
  30749. parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass;
  30750. parentBranch.centerOfMass.y *= totalMassInv;
  30751. parentBranch.mass = totalMass;
  30752. var biggestSize = Math.max(Math.max(node.height,node.radius),node.width);
  30753. parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth;
  30754. };
  30755. /**
  30756. * determine in which branch the node will be placed.
  30757. *
  30758. * @param parentBranch
  30759. * @param node
  30760. * @param skipMassUpdate
  30761. * @private
  30762. */
  30763. exports._placeInTree = function(parentBranch,node,skipMassUpdate) {
  30764. if (skipMassUpdate != true || skipMassUpdate === undefined) {
  30765. // update the mass of the branch.
  30766. this._updateBranchMass(parentBranch,node);
  30767. }
  30768. if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW
  30769. if (parentBranch.children.NW.range.maxY > node.y) { // in NW
  30770. this._placeInRegion(parentBranch,node,"NW");
  30771. }
  30772. else { // in SW
  30773. this._placeInRegion(parentBranch,node,"SW");
  30774. }
  30775. }
  30776. else { // in NE or SE
  30777. if (parentBranch.children.NW.range.maxY > node.y) { // in NE
  30778. this._placeInRegion(parentBranch,node,"NE");
  30779. }
  30780. else { // in SE
  30781. this._placeInRegion(parentBranch,node,"SE");
  30782. }
  30783. }
  30784. };
  30785. /**
  30786. * actually place the node in a region (or branch)
  30787. *
  30788. * @param parentBranch
  30789. * @param node
  30790. * @param region
  30791. * @private
  30792. */
  30793. exports._placeInRegion = function(parentBranch,node,region) {
  30794. switch (parentBranch.children[region].childrenCount) {
  30795. case 0: // place node here
  30796. parentBranch.children[region].children.data = node;
  30797. parentBranch.children[region].childrenCount = 1;
  30798. this._updateBranchMass(parentBranch.children[region],node);
  30799. break;
  30800. case 1: // convert into children
  30801. // if there are two nodes exactly overlapping (on init, on opening of cluster etc.)
  30802. // we move one node a pixel and we do not put it in the tree.
  30803. if (parentBranch.children[region].children.data.x == node.x &&
  30804. parentBranch.children[region].children.data.y == node.y) {
  30805. node.x += Math.random();
  30806. node.y += Math.random();
  30807. }
  30808. else {
  30809. this._splitBranch(parentBranch.children[region]);
  30810. this._placeInTree(parentBranch.children[region],node);
  30811. }
  30812. break;
  30813. case 4: // place in branch
  30814. this._placeInTree(parentBranch.children[region],node);
  30815. break;
  30816. }
  30817. };
  30818. /**
  30819. * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch
  30820. * after the split is complete.
  30821. *
  30822. * @param parentBranch
  30823. * @private
  30824. */
  30825. exports._splitBranch = function(parentBranch) {
  30826. // if the branch is shaded with a node, replace the node in the new subset.
  30827. var containedNode = null;
  30828. if (parentBranch.childrenCount == 1) {
  30829. containedNode = parentBranch.children.data;
  30830. parentBranch.mass = 0; parentBranch.centerOfMass.x = 0; parentBranch.centerOfMass.y = 0;
  30831. }
  30832. parentBranch.childrenCount = 4;
  30833. parentBranch.children.data = null;
  30834. this._insertRegion(parentBranch,"NW");
  30835. this._insertRegion(parentBranch,"NE");
  30836. this._insertRegion(parentBranch,"SW");
  30837. this._insertRegion(parentBranch,"SE");
  30838. if (containedNode != null) {
  30839. this._placeInTree(parentBranch,containedNode);
  30840. }
  30841. };
  30842. /**
  30843. * This function subdivides the region into four new segments.
  30844. * Specifically, this inserts a single new segment.
  30845. * It fills the children section of the parentBranch
  30846. *
  30847. * @param parentBranch
  30848. * @param region
  30849. * @param parentRange
  30850. * @private
  30851. */
  30852. exports._insertRegion = function(parentBranch, region) {
  30853. var minX,maxX,minY,maxY;
  30854. var childSize = 0.5 * parentBranch.size;
  30855. switch (region) {
  30856. case "NW":
  30857. minX = parentBranch.range.minX;
  30858. maxX = parentBranch.range.minX + childSize;
  30859. minY = parentBranch.range.minY;
  30860. maxY = parentBranch.range.minY + childSize;
  30861. break;
  30862. case "NE":
  30863. minX = parentBranch.range.minX + childSize;
  30864. maxX = parentBranch.range.maxX;
  30865. minY = parentBranch.range.minY;
  30866. maxY = parentBranch.range.minY + childSize;
  30867. break;
  30868. case "SW":
  30869. minX = parentBranch.range.minX;
  30870. maxX = parentBranch.range.minX + childSize;
  30871. minY = parentBranch.range.minY + childSize;
  30872. maxY = parentBranch.range.maxY;
  30873. break;
  30874. case "SE":
  30875. minX = parentBranch.range.minX + childSize;
  30876. maxX = parentBranch.range.maxX;
  30877. minY = parentBranch.range.minY + childSize;
  30878. maxY = parentBranch.range.maxY;
  30879. break;
  30880. }
  30881. parentBranch.children[region] = {
  30882. centerOfMass:{x:0,y:0},
  30883. mass:0,
  30884. range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY},
  30885. size: 0.5 * parentBranch.size,
  30886. calcSize: 2 * parentBranch.calcSize,
  30887. children: {data:null},
  30888. maxWidth: 0,
  30889. level: parentBranch.level+1,
  30890. childrenCount: 0
  30891. };
  30892. };
  30893. /**
  30894. * This function is for debugging purposed, it draws the tree.
  30895. *
  30896. * @param ctx
  30897. * @param color
  30898. * @private
  30899. */
  30900. exports._drawTree = function(ctx,color) {
  30901. if (this.barnesHutTree !== undefined) {
  30902. ctx.lineWidth = 1;
  30903. this._drawBranch(this.barnesHutTree.root,ctx,color);
  30904. }
  30905. };
  30906. /**
  30907. * This function is for debugging purposes. It draws the branches recursively.
  30908. *
  30909. * @param branch
  30910. * @param ctx
  30911. * @param color
  30912. * @private
  30913. */
  30914. exports._drawBranch = function(branch,ctx,color) {
  30915. if (color === undefined) {
  30916. color = "#FF0000";
  30917. }
  30918. if (branch.childrenCount == 4) {
  30919. this._drawBranch(branch.children.NW,ctx);
  30920. this._drawBranch(branch.children.NE,ctx);
  30921. this._drawBranch(branch.children.SE,ctx);
  30922. this._drawBranch(branch.children.SW,ctx);
  30923. }
  30924. ctx.strokeStyle = color;
  30925. ctx.beginPath();
  30926. ctx.moveTo(branch.range.minX,branch.range.minY);
  30927. ctx.lineTo(branch.range.maxX,branch.range.minY);
  30928. ctx.stroke();
  30929. ctx.beginPath();
  30930. ctx.moveTo(branch.range.maxX,branch.range.minY);
  30931. ctx.lineTo(branch.range.maxX,branch.range.maxY);
  30932. ctx.stroke();
  30933. ctx.beginPath();
  30934. ctx.moveTo(branch.range.maxX,branch.range.maxY);
  30935. ctx.lineTo(branch.range.minX,branch.range.maxY);
  30936. ctx.stroke();
  30937. ctx.beginPath();
  30938. ctx.moveTo(branch.range.minX,branch.range.maxY);
  30939. ctx.lineTo(branch.range.minX,branch.range.minY);
  30940. ctx.stroke();
  30941. /*
  30942. if (branch.mass > 0) {
  30943. ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass);
  30944. ctx.stroke();
  30945. }
  30946. */
  30947. };
  30948. /***/ },
  30949. /* 70 */
  30950. /***/ function(module, exports, __webpack_require__) {
  30951. function webpackContext(req) {
  30952. throw new Error("Cannot find module '" + req + "'.");
  30953. }
  30954. webpackContext.keys = function() { return []; };
  30955. webpackContext.resolve = webpackContext;
  30956. module.exports = webpackContext;
  30957. webpackContext.id = 70;
  30958. /***/ },
  30959. /* 71 */
  30960. /***/ function(module, exports, __webpack_require__) {
  30961. module.exports = function(module) {
  30962. if(!module.webpackPolyfill) {
  30963. module.deprecate = function() {};
  30964. module.paths = [];
  30965. // module.parent = undefined by default
  30966. module.children = [];
  30967. module.webpackPolyfill = 1;
  30968. }
  30969. return module;
  30970. }
  30971. /***/ }
  30972. /******/ ])
  30973. });
  30974. ;