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.

34916 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.9.2-SNAPSHOT
  8. * @date 2015-02-06
  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. /******/
  40. /******/ // The require function
  41. /******/ function __webpack_require__(moduleId) {
  42. /******/
  43. /******/ // Check if module is in cache
  44. /******/ if(installedModules[moduleId])
  45. /******/ return installedModules[moduleId].exports;
  46. /******/
  47. /******/ // Create a new module (and put it into the cache)
  48. /******/ var module = installedModules[moduleId] = {
  49. /******/ exports: {},
  50. /******/ id: moduleId,
  51. /******/ loaded: false
  52. /******/ };
  53. /******/
  54. /******/ // Execute the module function
  55. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  56. /******/
  57. /******/ // Flag the module as loaded
  58. /******/ module.loaded = true;
  59. /******/
  60. /******/ // Return the exports of the module
  61. /******/ return module.exports;
  62. /******/ }
  63. /******/
  64. /******/
  65. /******/ // expose the modules object (__webpack_modules__)
  66. /******/ __webpack_require__.m = modules;
  67. /******/
  68. /******/ // expose the module cache
  69. /******/ __webpack_require__.c = installedModules;
  70. /******/
  71. /******/ // __webpack_public_path__
  72. /******/ __webpack_require__.p = "";
  73. /******/
  74. /******/ // Load entry module and return exports
  75. /******/ return __webpack_require__(0);
  76. /******/ })
  77. /************************************************************************/
  78. /******/ ([
  79. /* 0 */
  80. /***/ function(module, exports, __webpack_require__) {
  81. // utils
  82. exports.util = __webpack_require__(1);
  83. exports.DOMutil = __webpack_require__(6);
  84. // data
  85. exports.DataSet = __webpack_require__(7);
  86. exports.DataView = __webpack_require__(9);
  87. exports.Queue = __webpack_require__(8);
  88. // Graph3d
  89. exports.Graph3d = __webpack_require__(10);
  90. exports.graph3d = {
  91. Camera: __webpack_require__(14),
  92. Filter: __webpack_require__(15),
  93. Point2d: __webpack_require__(13),
  94. Point3d: __webpack_require__(12),
  95. Slider: __webpack_require__(16),
  96. StepNumber: __webpack_require__(17)
  97. };
  98. // Timeline
  99. exports.Timeline = __webpack_require__(18);
  100. exports.Graph2d = __webpack_require__(42);
  101. exports.timeline = {
  102. DateUtil: __webpack_require__(24),
  103. DataStep: __webpack_require__(45),
  104. Range: __webpack_require__(21),
  105. stack: __webpack_require__(28),
  106. TimeStep: __webpack_require__(38),
  107. components: {
  108. items: {
  109. Item: __webpack_require__(30),
  110. BackgroundItem: __webpack_require__(34),
  111. BoxItem: __webpack_require__(32),
  112. PointItem: __webpack_require__(33),
  113. RangeItem: __webpack_require__(29)
  114. },
  115. Component: __webpack_require__(23),
  116. CurrentTime: __webpack_require__(39),
  117. CustomTime: __webpack_require__(41),
  118. DataAxis: __webpack_require__(44),
  119. GraphGroup: __webpack_require__(46),
  120. Group: __webpack_require__(27),
  121. BackgroundGroup: __webpack_require__(31),
  122. ItemSet: __webpack_require__(26),
  123. Legend: __webpack_require__(50),
  124. LineGraph: __webpack_require__(43),
  125. TimeAxis: __webpack_require__(37)
  126. }
  127. };
  128. // Network
  129. exports.Network = __webpack_require__(51);
  130. exports.network = {
  131. Edge: __webpack_require__(57),
  132. Groups: __webpack_require__(54),
  133. Images: __webpack_require__(55),
  134. Node: __webpack_require__(56),
  135. Popup: __webpack_require__(58),
  136. dotparser: __webpack_require__(52),
  137. gephiParser: __webpack_require__(53)
  138. };
  139. // Deprecated since v3.0.0
  140. exports.Graph = function () {
  141. throw new Error('Graph is renamed to Network. Please create a graph as new vis.Network(...)');
  142. };
  143. // bundled external libraries
  144. exports.moment = __webpack_require__(2);
  145. exports.hammer = __webpack_require__(19);
  146. /***/ },
  147. /* 1 */
  148. /***/ function(module, exports, __webpack_require__) {
  149. // utility functions
  150. // first check if moment.js is already loaded in the browser window, if so,
  151. // use this instance. Else, load via commonjs.
  152. var moment = __webpack_require__(2);
  153. /**
  154. * Test whether given object is a number
  155. * @param {*} object
  156. * @return {Boolean} isNumber
  157. */
  158. exports.isNumber = function(object) {
  159. return (object instanceof Number || typeof object == 'number');
  160. };
  161. /**
  162. * 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.
  163. *
  164. * @param min
  165. * @param max
  166. * @param total
  167. * @param value
  168. * @returns {number}
  169. */
  170. exports.giveRange = function(min,max,total,value) {
  171. if (max == min) {
  172. return 0.5;
  173. }
  174. else {
  175. var scale = 1 / (max - min);
  176. return Math.max(0,(value - min)*scale);
  177. }
  178. }
  179. /**
  180. * Test whether given object is a string
  181. * @param {*} object
  182. * @return {Boolean} isString
  183. */
  184. exports.isString = function(object) {
  185. return (object instanceof String || typeof object == 'string');
  186. };
  187. /**
  188. * Test whether given object is a Date, or a String containing a Date
  189. * @param {Date | String} object
  190. * @return {Boolean} isDate
  191. */
  192. exports.isDate = function(object) {
  193. if (object instanceof Date) {
  194. return true;
  195. }
  196. else if (exports.isString(object)) {
  197. // test whether this string contains a date
  198. var match = ASPDateRegex.exec(object);
  199. if (match) {
  200. return true;
  201. }
  202. else if (!isNaN(Date.parse(object))) {
  203. return true;
  204. }
  205. }
  206. return false;
  207. };
  208. /**
  209. * Test whether given object is an instance of google.visualization.DataTable
  210. * @param {*} object
  211. * @return {Boolean} isDataTable
  212. */
  213. exports.isDataTable = function(object) {
  214. return (typeof (google) !== 'undefined') &&
  215. (google.visualization) &&
  216. (google.visualization.DataTable) &&
  217. (object instanceof google.visualization.DataTable);
  218. };
  219. /**
  220. * Create a semi UUID
  221. * source: http://stackoverflow.com/a/105074/1262753
  222. * @return {String} uuid
  223. */
  224. exports.randomUUID = function() {
  225. var S4 = function () {
  226. return Math.floor(
  227. Math.random() * 0x10000 /* 65536 */
  228. ).toString(16);
  229. };
  230. return (
  231. S4() + S4() + '-' +
  232. S4() + '-' +
  233. S4() + '-' +
  234. S4() + '-' +
  235. S4() + S4() + S4()
  236. );
  237. };
  238. /**
  239. * Extend object a with the properties of object b or a series of objects
  240. * Only properties with defined values are copied
  241. * @param {Object} a
  242. * @param {... Object} b
  243. * @return {Object} a
  244. */
  245. exports.extend = function (a, b) {
  246. for (var i = 1, len = arguments.length; i < len; i++) {
  247. var other = arguments[i];
  248. for (var prop in other) {
  249. if (other.hasOwnProperty(prop)) {
  250. a[prop] = other[prop];
  251. }
  252. }
  253. }
  254. return a;
  255. };
  256. /**
  257. * Extend object a with selected properties of object b or a series of objects
  258. * Only properties with defined values are copied
  259. * @param {Array.<String>} props
  260. * @param {Object} a
  261. * @param {... Object} b
  262. * @return {Object} a
  263. */
  264. exports.selectiveExtend = function (props, a, b) {
  265. if (!Array.isArray(props)) {
  266. throw new Error('Array with property names expected as first argument');
  267. }
  268. for (var i = 2; i < arguments.length; i++) {
  269. var other = arguments[i];
  270. for (var p = 0; p < props.length; p++) {
  271. var prop = props[p];
  272. if (other.hasOwnProperty(prop)) {
  273. a[prop] = other[prop];
  274. }
  275. }
  276. }
  277. return a;
  278. };
  279. /**
  280. * Extend object a with selected properties of object b or a series of objects
  281. * Only properties with defined values are copied
  282. * @param {Array.<String>} props
  283. * @param {Object} a
  284. * @param {... Object} b
  285. * @return {Object} a
  286. */
  287. exports.selectiveDeepExtend = function (props, a, b) {
  288. // TODO: add support for Arrays to deepExtend
  289. if (Array.isArray(b)) {
  290. throw new TypeError('Arrays are not supported by deepExtend');
  291. }
  292. for (var i = 2; i < arguments.length; i++) {
  293. var other = arguments[i];
  294. for (var p = 0; p < props.length; p++) {
  295. var prop = props[p];
  296. if (other.hasOwnProperty(prop)) {
  297. if (b[prop] && b[prop].constructor === Object) {
  298. if (a[prop] === undefined) {
  299. a[prop] = {};
  300. }
  301. if (a[prop].constructor === Object) {
  302. exports.deepExtend(a[prop], b[prop]);
  303. }
  304. else {
  305. a[prop] = b[prop];
  306. }
  307. } else if (Array.isArray(b[prop])) {
  308. throw new TypeError('Arrays are not supported by deepExtend');
  309. } else {
  310. a[prop] = b[prop];
  311. }
  312. }
  313. }
  314. }
  315. return a;
  316. };
  317. /**
  318. * Extend object a with selected properties of object b or a series of objects
  319. * Only properties with defined values are copied
  320. * @param {Array.<String>} props
  321. * @param {Object} a
  322. * @param {... Object} b
  323. * @return {Object} a
  324. */
  325. exports.selectiveNotDeepExtend = function (props, a, b) {
  326. // TODO: add support for Arrays to deepExtend
  327. if (Array.isArray(b)) {
  328. throw new TypeError('Arrays are not supported by deepExtend');
  329. }
  330. for (var prop in b) {
  331. if (b.hasOwnProperty(prop)) {
  332. if (props.indexOf(prop) == -1) {
  333. if (b[prop] && b[prop].constructor === Object) {
  334. if (a[prop] === undefined) {
  335. a[prop] = {};
  336. }
  337. if (a[prop].constructor === Object) {
  338. exports.deepExtend(a[prop], b[prop]);
  339. }
  340. else {
  341. a[prop] = b[prop];
  342. }
  343. } else if (Array.isArray(b[prop])) {
  344. throw new TypeError('Arrays are not supported by deepExtend');
  345. } else {
  346. a[prop] = b[prop];
  347. }
  348. }
  349. }
  350. }
  351. return a;
  352. };
  353. /**
  354. * Deep extend an object a with the properties of object b
  355. * @param {Object} a
  356. * @param {Object} b
  357. * @returns {Object}
  358. */
  359. exports.deepExtend = function(a, b) {
  360. // TODO: add support for Arrays to deepExtend
  361. if (Array.isArray(b)) {
  362. throw new TypeError('Arrays are not supported by deepExtend');
  363. }
  364. for (var prop in b) {
  365. if (b.hasOwnProperty(prop)) {
  366. if (b[prop] && b[prop].constructor === Object) {
  367. if (a[prop] === undefined) {
  368. a[prop] = {};
  369. }
  370. if (a[prop].constructor === Object) {
  371. exports.deepExtend(a[prop], b[prop]);
  372. }
  373. else {
  374. a[prop] = b[prop];
  375. }
  376. } else if (Array.isArray(b[prop])) {
  377. throw new TypeError('Arrays are not supported by deepExtend');
  378. } else {
  379. a[prop] = b[prop];
  380. }
  381. }
  382. }
  383. return a;
  384. };
  385. /**
  386. * Test whether all elements in two arrays are equal.
  387. * @param {Array} a
  388. * @param {Array} b
  389. * @return {boolean} Returns true if both arrays have the same length and same
  390. * elements.
  391. */
  392. exports.equalArray = function (a, b) {
  393. if (a.length != b.length) return false;
  394. for (var i = 0, len = a.length; i < len; i++) {
  395. if (a[i] != b[i]) return false;
  396. }
  397. return true;
  398. };
  399. /**
  400. * Convert an object to another type
  401. * @param {Boolean | Number | String | Date | Moment | Null | undefined} object
  402. * @param {String | undefined} type Name of the type. Available types:
  403. * 'Boolean', 'Number', 'String',
  404. * 'Date', 'Moment', ISODate', 'ASPDate'.
  405. * @return {*} object
  406. * @throws Error
  407. */
  408. exports.convert = function(object, type) {
  409. var match;
  410. if (object === undefined) {
  411. return undefined;
  412. }
  413. if (object === null) {
  414. return null;
  415. }
  416. if (!type) {
  417. return object;
  418. }
  419. if (!(typeof type === 'string') && !(type instanceof String)) {
  420. throw new Error('Type must be a string');
  421. }
  422. //noinspection FallthroughInSwitchStatementJS
  423. switch (type) {
  424. case 'boolean':
  425. case 'Boolean':
  426. return Boolean(object);
  427. case 'number':
  428. case 'Number':
  429. return Number(object.valueOf());
  430. case 'string':
  431. case 'String':
  432. return String(object);
  433. case 'Date':
  434. if (exports.isNumber(object)) {
  435. return new Date(object);
  436. }
  437. if (object instanceof Date) {
  438. return new Date(object.valueOf());
  439. }
  440. else if (moment.isMoment(object)) {
  441. return new Date(object.valueOf());
  442. }
  443. if (exports.isString(object)) {
  444. match = ASPDateRegex.exec(object);
  445. if (match) {
  446. // object is an ASP date
  447. return new Date(Number(match[1])); // parse number
  448. }
  449. else {
  450. return moment(object).toDate(); // parse string
  451. }
  452. }
  453. else {
  454. throw new Error(
  455. 'Cannot convert object of type ' + exports.getType(object) +
  456. ' to type Date');
  457. }
  458. case 'Moment':
  459. if (exports.isNumber(object)) {
  460. return moment(object);
  461. }
  462. if (object instanceof Date) {
  463. return moment(object.valueOf());
  464. }
  465. else if (moment.isMoment(object)) {
  466. return moment(object);
  467. }
  468. if (exports.isString(object)) {
  469. match = ASPDateRegex.exec(object);
  470. if (match) {
  471. // object is an ASP date
  472. return moment(Number(match[1])); // parse number
  473. }
  474. else {
  475. return moment(object); // parse string
  476. }
  477. }
  478. else {
  479. throw new Error(
  480. 'Cannot convert object of type ' + exports.getType(object) +
  481. ' to type Date');
  482. }
  483. case 'ISODate':
  484. if (exports.isNumber(object)) {
  485. return new Date(object);
  486. }
  487. else if (object instanceof Date) {
  488. return object.toISOString();
  489. }
  490. else if (moment.isMoment(object)) {
  491. return object.toDate().toISOString();
  492. }
  493. else if (exports.isString(object)) {
  494. match = ASPDateRegex.exec(object);
  495. if (match) {
  496. // object is an ASP date
  497. return new Date(Number(match[1])).toISOString(); // parse number
  498. }
  499. else {
  500. return new Date(object).toISOString(); // parse string
  501. }
  502. }
  503. else {
  504. throw new Error(
  505. 'Cannot convert object of type ' + exports.getType(object) +
  506. ' to type ISODate');
  507. }
  508. case 'ASPDate':
  509. if (exports.isNumber(object)) {
  510. return '/Date(' + object + ')/';
  511. }
  512. else if (object instanceof Date) {
  513. return '/Date(' + object.valueOf() + ')/';
  514. }
  515. else if (exports.isString(object)) {
  516. match = ASPDateRegex.exec(object);
  517. var value;
  518. if (match) {
  519. // object is an ASP date
  520. value = new Date(Number(match[1])).valueOf(); // parse number
  521. }
  522. else {
  523. value = new Date(object).valueOf(); // parse string
  524. }
  525. return '/Date(' + value + ')/';
  526. }
  527. else {
  528. throw new Error(
  529. 'Cannot convert object of type ' + exports.getType(object) +
  530. ' to type ASPDate');
  531. }
  532. default:
  533. throw new Error('Unknown type "' + type + '"');
  534. }
  535. };
  536. // parse ASP.Net Date pattern,
  537. // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
  538. // code from http://momentjs.com/
  539. var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
  540. /**
  541. * Get the type of an object, for example exports.getType([]) returns 'Array'
  542. * @param {*} object
  543. * @return {String} type
  544. */
  545. exports.getType = function(object) {
  546. var type = typeof object;
  547. if (type == 'object') {
  548. if (object == null) {
  549. return 'null';
  550. }
  551. if (object instanceof Boolean) {
  552. return 'Boolean';
  553. }
  554. if (object instanceof Number) {
  555. return 'Number';
  556. }
  557. if (object instanceof String) {
  558. return 'String';
  559. }
  560. if (Array.isArray(object)) {
  561. return 'Array';
  562. }
  563. if (object instanceof Date) {
  564. return 'Date';
  565. }
  566. return 'Object';
  567. }
  568. else if (type == 'number') {
  569. return 'Number';
  570. }
  571. else if (type == 'boolean') {
  572. return 'Boolean';
  573. }
  574. else if (type == 'string') {
  575. return 'String';
  576. }
  577. return type;
  578. };
  579. /**
  580. * Retrieve the absolute left value of a DOM element
  581. * @param {Element} elem A dom element, for example a div
  582. * @return {number} left The absolute left position of this element
  583. * in the browser page.
  584. */
  585. exports.getAbsoluteLeft = function(elem) {
  586. return elem.getBoundingClientRect().left + window.pageXOffset;
  587. };
  588. /**
  589. * Retrieve the absolute top value of a DOM element
  590. * @param {Element} elem A dom element, for example a div
  591. * @return {number} top The absolute top position of this element
  592. * in the browser page.
  593. */
  594. exports.getAbsoluteTop = function(elem) {
  595. return elem.getBoundingClientRect().top + window.pageYOffset;
  596. };
  597. /**
  598. * add a className to the given elements style
  599. * @param {Element} elem
  600. * @param {String} className
  601. */
  602. exports.addClassName = function(elem, className) {
  603. var classes = elem.className.split(' ');
  604. if (classes.indexOf(className) == -1) {
  605. classes.push(className); // add the class to the array
  606. elem.className = classes.join(' ');
  607. }
  608. };
  609. /**
  610. * add a className to the given elements style
  611. * @param {Element} elem
  612. * @param {String} className
  613. */
  614. exports.removeClassName = function(elem, className) {
  615. var classes = elem.className.split(' ');
  616. var index = classes.indexOf(className);
  617. if (index != -1) {
  618. classes.splice(index, 1); // remove the class from the array
  619. elem.className = classes.join(' ');
  620. }
  621. };
  622. /**
  623. * For each method for both arrays and objects.
  624. * In case of an array, the built-in Array.forEach() is applied.
  625. * In case of an Object, the method loops over all properties of the object.
  626. * @param {Object | Array} object An Object or Array
  627. * @param {function} callback Callback method, called for each item in
  628. * the object or array with three parameters:
  629. * callback(value, index, object)
  630. */
  631. exports.forEach = function(object, callback) {
  632. var i,
  633. len;
  634. if (Array.isArray(object)) {
  635. // array
  636. for (i = 0, len = object.length; i < len; i++) {
  637. callback(object[i], i, object);
  638. }
  639. }
  640. else {
  641. // object
  642. for (i in object) {
  643. if (object.hasOwnProperty(i)) {
  644. callback(object[i], i, object);
  645. }
  646. }
  647. }
  648. };
  649. /**
  650. * Convert an object into an array: all objects properties are put into the
  651. * array. The resulting array is unordered.
  652. * @param {Object} object
  653. * @param {Array} array
  654. */
  655. exports.toArray = function(object) {
  656. var array = [];
  657. for (var prop in object) {
  658. if (object.hasOwnProperty(prop)) array.push(object[prop]);
  659. }
  660. return array;
  661. }
  662. /**
  663. * Update a property in an object
  664. * @param {Object} object
  665. * @param {String} key
  666. * @param {*} value
  667. * @return {Boolean} changed
  668. */
  669. exports.updateProperty = function(object, key, value) {
  670. if (object[key] !== value) {
  671. object[key] = value;
  672. return true;
  673. }
  674. else {
  675. return false;
  676. }
  677. };
  678. /**
  679. * Add and event listener. Works for all browsers
  680. * @param {Element} element An html element
  681. * @param {string} action The action, for example "click",
  682. * without the prefix "on"
  683. * @param {function} listener The callback function to be executed
  684. * @param {boolean} [useCapture]
  685. */
  686. exports.addEventListener = function(element, action, listener, useCapture) {
  687. if (element.addEventListener) {
  688. if (useCapture === undefined)
  689. useCapture = false;
  690. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  691. action = "DOMMouseScroll"; // For Firefox
  692. }
  693. element.addEventListener(action, listener, useCapture);
  694. } else {
  695. element.attachEvent("on" + action, listener); // IE browsers
  696. }
  697. };
  698. /**
  699. * Remove an event listener from an element
  700. * @param {Element} element An html dom element
  701. * @param {string} action The name of the event, for example "mousedown"
  702. * @param {function} listener The listener function
  703. * @param {boolean} [useCapture]
  704. */
  705. exports.removeEventListener = function(element, action, listener, useCapture) {
  706. if (element.removeEventListener) {
  707. // non-IE browsers
  708. if (useCapture === undefined)
  709. useCapture = false;
  710. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  711. action = "DOMMouseScroll"; // For Firefox
  712. }
  713. element.removeEventListener(action, listener, useCapture);
  714. } else {
  715. // IE browsers
  716. element.detachEvent("on" + action, listener);
  717. }
  718. };
  719. /**
  720. * Cancels the event if it is cancelable, without stopping further propagation of the event.
  721. */
  722. exports.preventDefault = function (event) {
  723. if (!event)
  724. event = window.event;
  725. if (event.preventDefault) {
  726. event.preventDefault(); // non-IE browsers
  727. }
  728. else {
  729. event.returnValue = false; // IE browsers
  730. }
  731. };
  732. /**
  733. * Get HTML element which is the target of the event
  734. * @param {Event} event
  735. * @return {Element} target element
  736. */
  737. exports.getTarget = function(event) {
  738. // code from http://www.quirksmode.org/js/events_properties.html
  739. if (!event) {
  740. event = window.event;
  741. }
  742. var target;
  743. if (event.target) {
  744. target = event.target;
  745. }
  746. else if (event.srcElement) {
  747. target = event.srcElement;
  748. }
  749. if (target.nodeType != undefined && target.nodeType == 3) {
  750. // defeat Safari bug
  751. target = target.parentNode;
  752. }
  753. return target;
  754. };
  755. exports.option = {};
  756. /**
  757. * Convert a value into a boolean
  758. * @param {Boolean | function | undefined} value
  759. * @param {Boolean} [defaultValue]
  760. * @returns {Boolean} bool
  761. */
  762. exports.option.asBoolean = function (value, defaultValue) {
  763. if (typeof value == 'function') {
  764. value = value();
  765. }
  766. if (value != null) {
  767. return (value != false);
  768. }
  769. return defaultValue || null;
  770. };
  771. /**
  772. * Convert a value into a number
  773. * @param {Boolean | function | undefined} value
  774. * @param {Number} [defaultValue]
  775. * @returns {Number} number
  776. */
  777. exports.option.asNumber = function (value, defaultValue) {
  778. if (typeof value == 'function') {
  779. value = value();
  780. }
  781. if (value != null) {
  782. return Number(value) || defaultValue || null;
  783. }
  784. return defaultValue || null;
  785. };
  786. /**
  787. * Convert a value into a string
  788. * @param {String | function | undefined} value
  789. * @param {String} [defaultValue]
  790. * @returns {String} str
  791. */
  792. exports.option.asString = function (value, defaultValue) {
  793. if (typeof value == 'function') {
  794. value = value();
  795. }
  796. if (value != null) {
  797. return String(value);
  798. }
  799. return defaultValue || null;
  800. };
  801. /**
  802. * Convert a size or location into a string with pixels or a percentage
  803. * @param {String | Number | function | undefined} value
  804. * @param {String} [defaultValue]
  805. * @returns {String} size
  806. */
  807. exports.option.asSize = function (value, defaultValue) {
  808. if (typeof value == 'function') {
  809. value = value();
  810. }
  811. if (exports.isString(value)) {
  812. return value;
  813. }
  814. else if (exports.isNumber(value)) {
  815. return value + 'px';
  816. }
  817. else {
  818. return defaultValue || null;
  819. }
  820. };
  821. /**
  822. * Convert a value into a DOM element
  823. * @param {HTMLElement | function | undefined} value
  824. * @param {HTMLElement} [defaultValue]
  825. * @returns {HTMLElement | null} dom
  826. */
  827. exports.option.asElement = function (value, defaultValue) {
  828. if (typeof value == 'function') {
  829. value = value();
  830. }
  831. return value || defaultValue || null;
  832. };
  833. /**
  834. * http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
  835. *
  836. * @param {String} hex
  837. * @returns {{r: *, g: *, b: *}} | 255 range
  838. */
  839. exports.hexToRGB = function(hex) {
  840. // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
  841. var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
  842. hex = hex.replace(shorthandRegex, function(m, r, g, b) {
  843. return r + r + g + g + b + b;
  844. });
  845. var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  846. return result ? {
  847. r: parseInt(result[1], 16),
  848. g: parseInt(result[2], 16),
  849. b: parseInt(result[3], 16)
  850. } : null;
  851. };
  852. /**
  853. * This function takes color in hex format or rgb() or rgba() format and overrides the opacity. Returns rgba() string.
  854. * @param color
  855. * @param opacity
  856. * @returns {*}
  857. */
  858. exports.overrideOpacity = function(color,opacity) {
  859. if (color.indexOf("rgb") != -1) {
  860. var rgb = color.substr(color.indexOf("(")+1).replace(")","").split(",");
  861. return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + opacity + ")"
  862. }
  863. else {
  864. var rgb = exports.hexToRGB(color);
  865. if (rgb == null) {
  866. return color;
  867. }
  868. else {
  869. return "rgba(" + rgb.r + "," + rgb.g + "," + rgb.b + "," + opacity + ")"
  870. }
  871. }
  872. }
  873. /**
  874. *
  875. * @param red 0 -- 255
  876. * @param green 0 -- 255
  877. * @param blue 0 -- 255
  878. * @returns {string}
  879. * @constructor
  880. */
  881. exports.RGBToHex = function(red,green,blue) {
  882. return "#" + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1);
  883. };
  884. /**
  885. * Parse a color property into an object with border, background, and
  886. * highlight colors
  887. * @param {Object | String} color
  888. * @return {Object} colorObject
  889. */
  890. exports.parseColor = function(color) {
  891. var c;
  892. if (exports.isString(color)) {
  893. if (exports.isValidRGB(color)) {
  894. var rgb = color.substr(4).substr(0,color.length-5).split(',');
  895. color = exports.RGBToHex(rgb[0],rgb[1],rgb[2]);
  896. }
  897. if (exports.isValidHex(color)) {
  898. var hsv = exports.hexToHSV(color);
  899. var lighterColorHSV = {h:hsv.h,s:hsv.s * 0.45,v:Math.min(1,hsv.v * 1.05)};
  900. var darkerColorHSV = {h:hsv.h,s:Math.min(1,hsv.v * 1.25),v:hsv.v*0.6};
  901. var darkerColorHex = exports.HSVToHex(darkerColorHSV.h ,darkerColorHSV.h ,darkerColorHSV.v);
  902. var lighterColorHex = exports.HSVToHex(lighterColorHSV.h,lighterColorHSV.s,lighterColorHSV.v);
  903. c = {
  904. background: color,
  905. border:darkerColorHex,
  906. highlight: {
  907. background:lighterColorHex,
  908. border:darkerColorHex
  909. },
  910. hover: {
  911. background:lighterColorHex,
  912. border:darkerColorHex
  913. }
  914. };
  915. }
  916. else {
  917. c = {
  918. background:color,
  919. border:color,
  920. highlight: {
  921. background:color,
  922. border:color
  923. },
  924. hover: {
  925. background:color,
  926. border:color
  927. }
  928. };
  929. }
  930. }
  931. else {
  932. c = {};
  933. c.background = color.background || 'white';
  934. c.border = color.border || c.background;
  935. if (exports.isString(color.highlight)) {
  936. c.highlight = {
  937. border: color.highlight,
  938. background: color.highlight
  939. }
  940. }
  941. else {
  942. c.highlight = {};
  943. c.highlight.background = color.highlight && color.highlight.background || c.background;
  944. c.highlight.border = color.highlight && color.highlight.border || c.border;
  945. }
  946. if (exports.isString(color.hover)) {
  947. c.hover = {
  948. border: color.hover,
  949. background: color.hover
  950. }
  951. }
  952. else {
  953. c.hover = {};
  954. c.hover.background = color.hover && color.hover.background || c.background;
  955. c.hover.border = color.hover && color.hover.border || c.border;
  956. }
  957. }
  958. return c;
  959. };
  960. /**
  961. * http://www.javascripter.net/faq/rgb2hsv.htm
  962. *
  963. * @param red
  964. * @param green
  965. * @param blue
  966. * @returns {*}
  967. * @constructor
  968. */
  969. exports.RGBToHSV = function(red,green,blue) {
  970. red=red/255; green=green/255; blue=blue/255;
  971. var minRGB = Math.min(red,Math.min(green,blue));
  972. var maxRGB = Math.max(red,Math.max(green,blue));
  973. // Black-gray-white
  974. if (minRGB == maxRGB) {
  975. return {h:0,s:0,v:minRGB};
  976. }
  977. // Colors other than black-gray-white:
  978. var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
  979. var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
  980. var hue = 60*(h - d/(maxRGB - minRGB))/360;
  981. var saturation = (maxRGB - minRGB)/maxRGB;
  982. var value = maxRGB;
  983. return {h:hue,s:saturation,v:value};
  984. };
  985. var cssUtil = {
  986. // split a string with css styles into an object with key/values
  987. split: function (cssText) {
  988. var styles = {};
  989. cssText.split(';').forEach(function (style) {
  990. if (style.trim() != '') {
  991. var parts = style.split(':');
  992. var key = parts[0].trim();
  993. var value = parts[1].trim();
  994. styles[key] = value;
  995. }
  996. });
  997. return styles;
  998. },
  999. // build a css text string from an object with key/values
  1000. join: function (styles) {
  1001. return Object.keys(styles)
  1002. .map(function (key) {
  1003. return key + ': ' + styles[key];
  1004. })
  1005. .join('; ');
  1006. }
  1007. };
  1008. /**
  1009. * Append a string with css styles to an element
  1010. * @param {Element} element
  1011. * @param {String} cssText
  1012. */
  1013. exports.addCssText = function (element, cssText) {
  1014. var currentStyles = cssUtil.split(element.style.cssText);
  1015. var newStyles = cssUtil.split(cssText);
  1016. var styles = exports.extend(currentStyles, newStyles);
  1017. element.style.cssText = cssUtil.join(styles);
  1018. };
  1019. /**
  1020. * Remove a string with css styles from an element
  1021. * @param {Element} element
  1022. * @param {String} cssText
  1023. */
  1024. exports.removeCssText = function (element, cssText) {
  1025. var styles = cssUtil.split(element.style.cssText);
  1026. var removeStyles = cssUtil.split(cssText);
  1027. for (var key in removeStyles) {
  1028. if (removeStyles.hasOwnProperty(key)) {
  1029. delete styles[key];
  1030. }
  1031. }
  1032. element.style.cssText = cssUtil.join(styles);
  1033. };
  1034. /**
  1035. * https://gist.github.com/mjijackson/5311256
  1036. * @param h
  1037. * @param s
  1038. * @param v
  1039. * @returns {{r: number, g: number, b: number}}
  1040. * @constructor
  1041. */
  1042. exports.HSVToRGB = function(h, s, v) {
  1043. var r, g, b;
  1044. var i = Math.floor(h * 6);
  1045. var f = h * 6 - i;
  1046. var p = v * (1 - s);
  1047. var q = v * (1 - f * s);
  1048. var t = v * (1 - (1 - f) * s);
  1049. switch (i % 6) {
  1050. case 0: r = v, g = t, b = p; break;
  1051. case 1: r = q, g = v, b = p; break;
  1052. case 2: r = p, g = v, b = t; break;
  1053. case 3: r = p, g = q, b = v; break;
  1054. case 4: r = t, g = p, b = v; break;
  1055. case 5: r = v, g = p, b = q; break;
  1056. }
  1057. return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
  1058. };
  1059. exports.HSVToHex = function(h, s, v) {
  1060. var rgb = exports.HSVToRGB(h, s, v);
  1061. return exports.RGBToHex(rgb.r, rgb.g, rgb.b);
  1062. };
  1063. exports.hexToHSV = function(hex) {
  1064. var rgb = exports.hexToRGB(hex);
  1065. return exports.RGBToHSV(rgb.r, rgb.g, rgb.b);
  1066. };
  1067. exports.isValidHex = function(hex) {
  1068. var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
  1069. return isOk;
  1070. };
  1071. exports.isValidRGB = function(rgb) {
  1072. rgb = rgb.replace(" ","");
  1073. var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb);
  1074. return isOk;
  1075. }
  1076. /**
  1077. * This recursively redirects the prototype of JSON objects to the referenceObject
  1078. * This is used for default options.
  1079. *
  1080. * @param referenceObject
  1081. * @returns {*}
  1082. */
  1083. exports.selectiveBridgeObject = function(fields, referenceObject) {
  1084. if (typeof referenceObject == "object") {
  1085. var objectTo = Object.create(referenceObject);
  1086. for (var i = 0; i < fields.length; i++) {
  1087. if (referenceObject.hasOwnProperty(fields[i])) {
  1088. if (typeof referenceObject[fields[i]] == "object") {
  1089. objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]);
  1090. }
  1091. }
  1092. }
  1093. return objectTo;
  1094. }
  1095. else {
  1096. return null;
  1097. }
  1098. };
  1099. /**
  1100. * This recursively redirects the prototype of JSON objects to the referenceObject
  1101. * This is used for default options.
  1102. *
  1103. * @param referenceObject
  1104. * @returns {*}
  1105. */
  1106. exports.bridgeObject = function(referenceObject) {
  1107. if (typeof referenceObject == "object") {
  1108. var objectTo = Object.create(referenceObject);
  1109. for (var i in referenceObject) {
  1110. if (referenceObject.hasOwnProperty(i)) {
  1111. if (typeof referenceObject[i] == "object") {
  1112. objectTo[i] = exports.bridgeObject(referenceObject[i]);
  1113. }
  1114. }
  1115. }
  1116. return objectTo;
  1117. }
  1118. else {
  1119. return null;
  1120. }
  1121. };
  1122. /**
  1123. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  1124. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  1125. *
  1126. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  1127. * @param [object] options | options
  1128. * @param [String] option | this is the option key in the options argument
  1129. * @private
  1130. */
  1131. exports.mergeOptions = function (mergeTarget, options, option) {
  1132. if (options[option] !== undefined) {
  1133. if (typeof options[option] == 'boolean') {
  1134. mergeTarget[option].enabled = options[option];
  1135. }
  1136. else {
  1137. mergeTarget[option].enabled = true;
  1138. for (var prop in options[option]) {
  1139. if (options[option].hasOwnProperty(prop)) {
  1140. mergeTarget[option][prop] = options[option][prop];
  1141. }
  1142. }
  1143. }
  1144. }
  1145. }
  1146. /**
  1147. * This function does a binary search for a visible item in a sorted list. If we find a visible item, the code that uses
  1148. * this function will then iterate in both directions over this sorted list to find all visible items.
  1149. *
  1150. * @param {Item[]} orderedItems | Items ordered by start
  1151. * @param {function} searchFunction | -1 is lower, 0 is found, 1 is higher
  1152. * @param {String} field
  1153. * @param {String} field2
  1154. * @returns {number}
  1155. * @private
  1156. */
  1157. exports.binarySearchCustom = function(orderedItems, searchFunction, field, field2) {
  1158. var maxIterations = 10000;
  1159. var iteration = 0;
  1160. var low = 0;
  1161. var high = orderedItems.length - 1;
  1162. while (low <= high && iteration < maxIterations) {
  1163. var middle = Math.floor((low + high) / 2);
  1164. var item = orderedItems[middle];
  1165. var value = (field2 === undefined) ? item[field] : item[field][field2];
  1166. var searchResult = searchFunction(value);
  1167. if (searchResult == 0) { // jihaa, found a visible item!
  1168. return middle;
  1169. }
  1170. else if (searchResult == -1) { // it is too small --> increase low
  1171. low = middle + 1;
  1172. }
  1173. else { // it is too big --> decrease high
  1174. high = middle - 1;
  1175. }
  1176. iteration++;
  1177. }
  1178. return -1;
  1179. };
  1180. /**
  1181. * This function does a binary search for a specific value in a sorted array. If it does not exist but is in between of
  1182. * two values, we return either the one before or the one after, depending on user input
  1183. * If it is found, we return the index, else -1.
  1184. *
  1185. * @param {Array} orderedItems
  1186. * @param {{start: number, end: number}} target
  1187. * @param {String} field
  1188. * @param {String} sidePreference 'before' or 'after'
  1189. * @returns {number}
  1190. * @private
  1191. */
  1192. exports.binarySearchValue = function(orderedItems, target, field, sidePreference) {
  1193. var maxIterations = 10000;
  1194. var iteration = 0;
  1195. var low = 0;
  1196. var high = orderedItems.length - 1;
  1197. var prevValue, value, nextValue, middle;
  1198. while (low <= high && iteration < maxIterations) {
  1199. // get a new guess
  1200. middle = Math.floor(0.5*(high+low));
  1201. prevValue = orderedItems[Math.max(0,middle - 1)][field];
  1202. value = orderedItems[middle][field];
  1203. nextValue = orderedItems[Math.min(orderedItems.length-1,middle + 1)][field];
  1204. if (value == target) { // we found the target
  1205. return middle;
  1206. }
  1207. else if (prevValue < target && value > target) { // target is in between of the previous and the current
  1208. return sidePreference == 'before' ? Math.max(0,middle - 1) : middle;
  1209. }
  1210. else if (value < target && nextValue > target) { // target is in between of the current and the next
  1211. return sidePreference == 'before' ? middle : Math.min(orderedItems.length-1,middle + 1);
  1212. }
  1213. else { // didnt find the target, we need to change our boundaries.
  1214. if (value < target) { // it is too small --> increase low
  1215. low = middle + 1;
  1216. }
  1217. else { // it is too big --> decrease high
  1218. high = middle - 1;
  1219. }
  1220. }
  1221. iteration++;
  1222. }
  1223. // didnt find anything. Return -1.
  1224. return -1;
  1225. };
  1226. /**
  1227. * Quadratic ease-in-out
  1228. * http://gizma.com/easing/
  1229. * @param {number} t Current time
  1230. * @param {number} start Start value
  1231. * @param {number} end End value
  1232. * @param {number} duration Duration
  1233. * @returns {number} Value corresponding with current time
  1234. */
  1235. exports.easeInOutQuad = function (t, start, end, duration) {
  1236. var change = end - start;
  1237. t /= duration/2;
  1238. if (t < 1) return change/2*t*t + start;
  1239. t--;
  1240. return -change/2 * (t*(t-2) - 1) + start;
  1241. };
  1242. /*
  1243. * Easing Functions - inspired from http://gizma.com/easing/
  1244. * only considering the t value for the range [0, 1] => [0, 1]
  1245. * https://gist.github.com/gre/1650294
  1246. */
  1247. exports.easingFunctions = {
  1248. // no easing, no acceleration
  1249. linear: function (t) {
  1250. return t
  1251. },
  1252. // accelerating from zero velocity
  1253. easeInQuad: function (t) {
  1254. return t * t
  1255. },
  1256. // decelerating to zero velocity
  1257. easeOutQuad: function (t) {
  1258. return t * (2 - t)
  1259. },
  1260. // acceleration until halfway, then deceleration
  1261. easeInOutQuad: function (t) {
  1262. return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t
  1263. },
  1264. // accelerating from zero velocity
  1265. easeInCubic: function (t) {
  1266. return t * t * t
  1267. },
  1268. // decelerating to zero velocity
  1269. easeOutCubic: function (t) {
  1270. return (--t) * t * t + 1
  1271. },
  1272. // acceleration until halfway, then deceleration
  1273. easeInOutCubic: function (t) {
  1274. return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
  1275. },
  1276. // accelerating from zero velocity
  1277. easeInQuart: function (t) {
  1278. return t * t * t * t
  1279. },
  1280. // decelerating to zero velocity
  1281. easeOutQuart: function (t) {
  1282. return 1 - (--t) * t * t * t
  1283. },
  1284. // acceleration until halfway, then deceleration
  1285. easeInOutQuart: function (t) {
  1286. return t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t
  1287. },
  1288. // accelerating from zero velocity
  1289. easeInQuint: function (t) {
  1290. return t * t * t * t * t
  1291. },
  1292. // decelerating to zero velocity
  1293. easeOutQuint: function (t) {
  1294. return 1 + (--t) * t * t * t * t
  1295. },
  1296. // acceleration until halfway, then deceleration
  1297. easeInOutQuint: function (t) {
  1298. return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
  1299. }
  1300. };
  1301. /***/ },
  1302. /* 2 */
  1303. /***/ function(module, exports, __webpack_require__) {
  1304. // first check if moment.js is already loaded in the browser window, if so,
  1305. // use this instance. Else, load via commonjs.
  1306. module.exports = (typeof window !== 'undefined') && window['moment'] || __webpack_require__(3);
  1307. /***/ },
  1308. /* 3 */
  1309. /***/ function(module, exports, __webpack_require__) {
  1310. var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(global, module) {//! moment.js
  1311. //! version : 2.9.0
  1312. //! authors : Tim Wood, Iskren Chernev, Moment.js contributors
  1313. //! license : MIT
  1314. //! momentjs.com
  1315. (function (undefined) {
  1316. /************************************
  1317. Constants
  1318. ************************************/
  1319. var moment,
  1320. VERSION = '2.9.0',
  1321. // the global-scope this is NOT the global object in Node.js
  1322. globalScope = (typeof global !== 'undefined' && (typeof window === 'undefined' || window === global.window)) ? global : this,
  1323. oldGlobalMoment,
  1324. round = Math.round,
  1325. hasOwnProperty = Object.prototype.hasOwnProperty,
  1326. i,
  1327. YEAR = 0,
  1328. MONTH = 1,
  1329. DATE = 2,
  1330. HOUR = 3,
  1331. MINUTE = 4,
  1332. SECOND = 5,
  1333. MILLISECOND = 6,
  1334. // internal storage for locale config files
  1335. locales = {},
  1336. // extra moment internal properties (plugins register props here)
  1337. momentProperties = [],
  1338. // check for nodeJS
  1339. hasModule = (typeof module !== 'undefined' && module && module.exports),
  1340. // ASP.NET json date format regex
  1341. aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
  1342. aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,
  1343. // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
  1344. // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
  1345. isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,
  1346. // format tokens
  1347. 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,
  1348. localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,
  1349. // parsing token regexes
  1350. parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
  1351. parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
  1352. parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999
  1353. parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
  1354. parseTokenDigits = /\d+/, // nonzero number of digits
  1355. 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.
  1356. parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
  1357. parseTokenT = /T/i, // T (ISO separator)
  1358. parseTokenOffsetMs = /[\+\-]?\d+/, // 1234567890123
  1359. parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
  1360. //strict parsing regexes
  1361. parseTokenOneDigit = /\d/, // 0 - 9
  1362. parseTokenTwoDigits = /\d\d/, // 00 - 99
  1363. parseTokenThreeDigits = /\d{3}/, // 000 - 999
  1364. parseTokenFourDigits = /\d{4}/, // 0000 - 9999
  1365. parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999
  1366. parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf
  1367. // iso 8601 regex
  1368. // 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)
  1369. 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)?)?$/,
  1370. isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
  1371. isoDates = [
  1372. ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/],
  1373. ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/],
  1374. ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/],
  1375. ['GGGG-[W]WW', /\d{4}-W\d{2}/],
  1376. ['YYYY-DDD', /\d{4}-\d{3}/]
  1377. ],
  1378. // iso time formats and regexes
  1379. isoTimes = [
  1380. ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/],
  1381. ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
  1382. ['HH:mm', /(T| )\d\d:\d\d/],
  1383. ['HH', /(T| )\d\d/]
  1384. ],
  1385. // timezone chunker '+10:00' > ['10', '00'] or '-1530' > ['-', '15', '30']
  1386. parseTimezoneChunker = /([\+\-]|\d\d)/gi,
  1387. // getter and setter names
  1388. proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
  1389. unitMillisecondFactors = {
  1390. 'Milliseconds' : 1,
  1391. 'Seconds' : 1e3,
  1392. 'Minutes' : 6e4,
  1393. 'Hours' : 36e5,
  1394. 'Days' : 864e5,
  1395. 'Months' : 2592e6,
  1396. 'Years' : 31536e6
  1397. },
  1398. unitAliases = {
  1399. ms : 'millisecond',
  1400. s : 'second',
  1401. m : 'minute',
  1402. h : 'hour',
  1403. d : 'day',
  1404. D : 'date',
  1405. w : 'week',
  1406. W : 'isoWeek',
  1407. M : 'month',
  1408. Q : 'quarter',
  1409. y : 'year',
  1410. DDD : 'dayOfYear',
  1411. e : 'weekday',
  1412. E : 'isoWeekday',
  1413. gg: 'weekYear',
  1414. GG: 'isoWeekYear'
  1415. },
  1416. camelFunctions = {
  1417. dayofyear : 'dayOfYear',
  1418. isoweekday : 'isoWeekday',
  1419. isoweek : 'isoWeek',
  1420. weekyear : 'weekYear',
  1421. isoweekyear : 'isoWeekYear'
  1422. },
  1423. // format function strings
  1424. formatFunctions = {},
  1425. // default relative time thresholds
  1426. relativeTimeThresholds = {
  1427. s: 45, // seconds to minute
  1428. m: 45, // minutes to hour
  1429. h: 22, // hours to day
  1430. d: 26, // days to month
  1431. M: 11 // months to year
  1432. },
  1433. // tokens to ordinalize and pad
  1434. ordinalizeTokens = 'DDD w W M D d'.split(' '),
  1435. paddedTokens = 'M D H h m s w W'.split(' '),
  1436. formatTokenFunctions = {
  1437. M : function () {
  1438. return this.month() + 1;
  1439. },
  1440. MMM : function (format) {
  1441. return this.localeData().monthsShort(this, format);
  1442. },
  1443. MMMM : function (format) {
  1444. return this.localeData().months(this, format);
  1445. },
  1446. D : function () {
  1447. return this.date();
  1448. },
  1449. DDD : function () {
  1450. return this.dayOfYear();
  1451. },
  1452. d : function () {
  1453. return this.day();
  1454. },
  1455. dd : function (format) {
  1456. return this.localeData().weekdaysMin(this, format);
  1457. },
  1458. ddd : function (format) {
  1459. return this.localeData().weekdaysShort(this, format);
  1460. },
  1461. dddd : function (format) {
  1462. return this.localeData().weekdays(this, format);
  1463. },
  1464. w : function () {
  1465. return this.week();
  1466. },
  1467. W : function () {
  1468. return this.isoWeek();
  1469. },
  1470. YY : function () {
  1471. return leftZeroFill(this.year() % 100, 2);
  1472. },
  1473. YYYY : function () {
  1474. return leftZeroFill(this.year(), 4);
  1475. },
  1476. YYYYY : function () {
  1477. return leftZeroFill(this.year(), 5);
  1478. },
  1479. YYYYYY : function () {
  1480. var y = this.year(), sign = y >= 0 ? '+' : '-';
  1481. return sign + leftZeroFill(Math.abs(y), 6);
  1482. },
  1483. gg : function () {
  1484. return leftZeroFill(this.weekYear() % 100, 2);
  1485. },
  1486. gggg : function () {
  1487. return leftZeroFill(this.weekYear(), 4);
  1488. },
  1489. ggggg : function () {
  1490. return leftZeroFill(this.weekYear(), 5);
  1491. },
  1492. GG : function () {
  1493. return leftZeroFill(this.isoWeekYear() % 100, 2);
  1494. },
  1495. GGGG : function () {
  1496. return leftZeroFill(this.isoWeekYear(), 4);
  1497. },
  1498. GGGGG : function () {
  1499. return leftZeroFill(this.isoWeekYear(), 5);
  1500. },
  1501. e : function () {
  1502. return this.weekday();
  1503. },
  1504. E : function () {
  1505. return this.isoWeekday();
  1506. },
  1507. a : function () {
  1508. return this.localeData().meridiem(this.hours(), this.minutes(), true);
  1509. },
  1510. A : function () {
  1511. return this.localeData().meridiem(this.hours(), this.minutes(), false);
  1512. },
  1513. H : function () {
  1514. return this.hours();
  1515. },
  1516. h : function () {
  1517. return this.hours() % 12 || 12;
  1518. },
  1519. m : function () {
  1520. return this.minutes();
  1521. },
  1522. s : function () {
  1523. return this.seconds();
  1524. },
  1525. S : function () {
  1526. return toInt(this.milliseconds() / 100);
  1527. },
  1528. SS : function () {
  1529. return leftZeroFill(toInt(this.milliseconds() / 10), 2);
  1530. },
  1531. SSS : function () {
  1532. return leftZeroFill(this.milliseconds(), 3);
  1533. },
  1534. SSSS : function () {
  1535. return leftZeroFill(this.milliseconds(), 3);
  1536. },
  1537. Z : function () {
  1538. var a = this.utcOffset(),
  1539. b = '+';
  1540. if (a < 0) {
  1541. a = -a;
  1542. b = '-';
  1543. }
  1544. return b + leftZeroFill(toInt(a / 60), 2) + ':' + leftZeroFill(toInt(a) % 60, 2);
  1545. },
  1546. ZZ : function () {
  1547. var a = this.utcOffset(),
  1548. b = '+';
  1549. if (a < 0) {
  1550. a = -a;
  1551. b = '-';
  1552. }
  1553. return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2);
  1554. },
  1555. z : function () {
  1556. return this.zoneAbbr();
  1557. },
  1558. zz : function () {
  1559. return this.zoneName();
  1560. },
  1561. x : function () {
  1562. return this.valueOf();
  1563. },
  1564. X : function () {
  1565. return this.unix();
  1566. },
  1567. Q : function () {
  1568. return this.quarter();
  1569. }
  1570. },
  1571. deprecations = {},
  1572. lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'],
  1573. updateInProgress = false;
  1574. // Pick the first defined of two or three arguments. dfl comes from
  1575. // default.
  1576. function dfl(a, b, c) {
  1577. switch (arguments.length) {
  1578. case 2: return a != null ? a : b;
  1579. case 3: return a != null ? a : b != null ? b : c;
  1580. default: throw new Error('Implement me');
  1581. }
  1582. }
  1583. function hasOwnProp(a, b) {
  1584. return hasOwnProperty.call(a, b);
  1585. }
  1586. function defaultParsingFlags() {
  1587. // We need to deep clone this object, and es5 standard is not very
  1588. // helpful.
  1589. return {
  1590. empty : false,
  1591. unusedTokens : [],
  1592. unusedInput : [],
  1593. overflow : -2,
  1594. charsLeftOver : 0,
  1595. nullInput : false,
  1596. invalidMonth : null,
  1597. invalidFormat : false,
  1598. userInvalidated : false,
  1599. iso: false
  1600. };
  1601. }
  1602. function printMsg(msg) {
  1603. if (moment.suppressDeprecationWarnings === false &&
  1604. typeof console !== 'undefined' && console.warn) {
  1605. console.warn('Deprecation warning: ' + msg);
  1606. }
  1607. }
  1608. function deprecate(msg, fn) {
  1609. var firstTime = true;
  1610. return extend(function () {
  1611. if (firstTime) {
  1612. printMsg(msg);
  1613. firstTime = false;
  1614. }
  1615. return fn.apply(this, arguments);
  1616. }, fn);
  1617. }
  1618. function deprecateSimple(name, msg) {
  1619. if (!deprecations[name]) {
  1620. printMsg(msg);
  1621. deprecations[name] = true;
  1622. }
  1623. }
  1624. function padToken(func, count) {
  1625. return function (a) {
  1626. return leftZeroFill(func.call(this, a), count);
  1627. };
  1628. }
  1629. function ordinalizeToken(func, period) {
  1630. return function (a) {
  1631. return this.localeData().ordinal(func.call(this, a), period);
  1632. };
  1633. }
  1634. function monthDiff(a, b) {
  1635. // difference in months
  1636. var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
  1637. // b is in (anchor - 1 month, anchor + 1 month)
  1638. anchor = a.clone().add(wholeMonthDiff, 'months'),
  1639. anchor2, adjust;
  1640. if (b - anchor < 0) {
  1641. anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
  1642. // linear across the month
  1643. adjust = (b - anchor) / (anchor - anchor2);
  1644. } else {
  1645. anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
  1646. // linear across the month
  1647. adjust = (b - anchor) / (anchor2 - anchor);
  1648. }
  1649. return -(wholeMonthDiff + adjust);
  1650. }
  1651. while (ordinalizeTokens.length) {
  1652. i = ordinalizeTokens.pop();
  1653. formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i);
  1654. }
  1655. while (paddedTokens.length) {
  1656. i = paddedTokens.pop();
  1657. formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2);
  1658. }
  1659. formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3);
  1660. function meridiemFixWrap(locale, hour, meridiem) {
  1661. var isPm;
  1662. if (meridiem == null) {
  1663. // nothing to do
  1664. return hour;
  1665. }
  1666. if (locale.meridiemHour != null) {
  1667. return locale.meridiemHour(hour, meridiem);
  1668. } else if (locale.isPM != null) {
  1669. // Fallback
  1670. isPm = locale.isPM(meridiem);
  1671. if (isPm && hour < 12) {
  1672. hour += 12;
  1673. }
  1674. if (!isPm && hour === 12) {
  1675. hour = 0;
  1676. }
  1677. return hour;
  1678. } else {
  1679. // thie is not supposed to happen
  1680. return hour;
  1681. }
  1682. }
  1683. /************************************
  1684. Constructors
  1685. ************************************/
  1686. function Locale() {
  1687. }
  1688. // Moment prototype object
  1689. function Moment(config, skipOverflow) {
  1690. if (skipOverflow !== false) {
  1691. checkOverflow(config);
  1692. }
  1693. copyConfig(this, config);
  1694. this._d = new Date(+config._d);
  1695. // Prevent infinite loop in case updateOffset creates new moment
  1696. // objects.
  1697. if (updateInProgress === false) {
  1698. updateInProgress = true;
  1699. moment.updateOffset(this);
  1700. updateInProgress = false;
  1701. }
  1702. }
  1703. // Duration Constructor
  1704. function Duration(duration) {
  1705. var normalizedInput = normalizeObjectUnits(duration),
  1706. years = normalizedInput.year || 0,
  1707. quarters = normalizedInput.quarter || 0,
  1708. months = normalizedInput.month || 0,
  1709. weeks = normalizedInput.week || 0,
  1710. days = normalizedInput.day || 0,
  1711. hours = normalizedInput.hour || 0,
  1712. minutes = normalizedInput.minute || 0,
  1713. seconds = normalizedInput.second || 0,
  1714. milliseconds = normalizedInput.millisecond || 0;
  1715. // representation for dateAddRemove
  1716. this._milliseconds = +milliseconds +
  1717. seconds * 1e3 + // 1000
  1718. minutes * 6e4 + // 1000 * 60
  1719. hours * 36e5; // 1000 * 60 * 60
  1720. // Because of dateAddRemove treats 24 hours as different from a
  1721. // day when working around DST, we need to store them separately
  1722. this._days = +days +
  1723. weeks * 7;
  1724. // It is impossible translate months into days without knowing
  1725. // which months you are are talking about, so we have to store
  1726. // it separately.
  1727. this._months = +months +
  1728. quarters * 3 +
  1729. years * 12;
  1730. this._data = {};
  1731. this._locale = moment.localeData();
  1732. this._bubble();
  1733. }
  1734. /************************************
  1735. Helpers
  1736. ************************************/
  1737. function extend(a, b) {
  1738. for (var i in b) {
  1739. if (hasOwnProp(b, i)) {
  1740. a[i] = b[i];
  1741. }
  1742. }
  1743. if (hasOwnProp(b, 'toString')) {
  1744. a.toString = b.toString;
  1745. }
  1746. if (hasOwnProp(b, 'valueOf')) {
  1747. a.valueOf = b.valueOf;
  1748. }
  1749. return a;
  1750. }
  1751. function copyConfig(to, from) {
  1752. var i, prop, val;
  1753. if (typeof from._isAMomentObject !== 'undefined') {
  1754. to._isAMomentObject = from._isAMomentObject;
  1755. }
  1756. if (typeof from._i !== 'undefined') {
  1757. to._i = from._i;
  1758. }
  1759. if (typeof from._f !== 'undefined') {
  1760. to._f = from._f;
  1761. }
  1762. if (typeof from._l !== 'undefined') {
  1763. to._l = from._l;
  1764. }
  1765. if (typeof from._strict !== 'undefined') {
  1766. to._strict = from._strict;
  1767. }
  1768. if (typeof from._tzm !== 'undefined') {
  1769. to._tzm = from._tzm;
  1770. }
  1771. if (typeof from._isUTC !== 'undefined') {
  1772. to._isUTC = from._isUTC;
  1773. }
  1774. if (typeof from._offset !== 'undefined') {
  1775. to._offset = from._offset;
  1776. }
  1777. if (typeof from._pf !== 'undefined') {
  1778. to._pf = from._pf;
  1779. }
  1780. if (typeof from._locale !== 'undefined') {
  1781. to._locale = from._locale;
  1782. }
  1783. if (momentProperties.length > 0) {
  1784. for (i in momentProperties) {
  1785. prop = momentProperties[i];
  1786. val = from[prop];
  1787. if (typeof val !== 'undefined') {
  1788. to[prop] = val;
  1789. }
  1790. }
  1791. }
  1792. return to;
  1793. }
  1794. function absRound(number) {
  1795. if (number < 0) {
  1796. return Math.ceil(number);
  1797. } else {
  1798. return Math.floor(number);
  1799. }
  1800. }
  1801. // left zero fill a number
  1802. // see http://jsperf.com/left-zero-filling for performance comparison
  1803. function leftZeroFill(number, targetLength, forceSign) {
  1804. var output = '' + Math.abs(number),
  1805. sign = number >= 0;
  1806. while (output.length < targetLength) {
  1807. output = '0' + output;
  1808. }
  1809. return (sign ? (forceSign ? '+' : '') : '-') + output;
  1810. }
  1811. function positiveMomentsDifference(base, other) {
  1812. var res = {milliseconds: 0, months: 0};
  1813. res.months = other.month() - base.month() +
  1814. (other.year() - base.year()) * 12;
  1815. if (base.clone().add(res.months, 'M').isAfter(other)) {
  1816. --res.months;
  1817. }
  1818. res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
  1819. return res;
  1820. }
  1821. function momentsDifference(base, other) {
  1822. var res;
  1823. other = makeAs(other, base);
  1824. if (base.isBefore(other)) {
  1825. res = positiveMomentsDifference(base, other);
  1826. } else {
  1827. res = positiveMomentsDifference(other, base);
  1828. res.milliseconds = -res.milliseconds;
  1829. res.months = -res.months;
  1830. }
  1831. return res;
  1832. }
  1833. // TODO: remove 'name' arg after deprecation is removed
  1834. function createAdder(direction, name) {
  1835. return function (val, period) {
  1836. var dur, tmp;
  1837. //invert the arguments, but complain about it
  1838. if (period !== null && !isNaN(+period)) {
  1839. deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).');
  1840. tmp = val; val = period; period = tmp;
  1841. }
  1842. val = typeof val === 'string' ? +val : val;
  1843. dur = moment.duration(val, period);
  1844. addOrSubtractDurationFromMoment(this, dur, direction);
  1845. return this;
  1846. };
  1847. }
  1848. function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) {
  1849. var milliseconds = duration._milliseconds,
  1850. days = duration._days,
  1851. months = duration._months;
  1852. updateOffset = updateOffset == null ? true : updateOffset;
  1853. if (milliseconds) {
  1854. mom._d.setTime(+mom._d + milliseconds * isAdding);
  1855. }
  1856. if (days) {
  1857. rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding);
  1858. }
  1859. if (months) {
  1860. rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding);
  1861. }
  1862. if (updateOffset) {
  1863. moment.updateOffset(mom, days || months);
  1864. }
  1865. }
  1866. // check if is an array
  1867. function isArray(input) {
  1868. return Object.prototype.toString.call(input) === '[object Array]';
  1869. }
  1870. function isDate(input) {
  1871. return Object.prototype.toString.call(input) === '[object Date]' ||
  1872. input instanceof Date;
  1873. }
  1874. // compare two arrays, return the number of differences
  1875. function compareArrays(array1, array2, dontConvert) {
  1876. var len = Math.min(array1.length, array2.length),
  1877. lengthDiff = Math.abs(array1.length - array2.length),
  1878. diffs = 0,
  1879. i;
  1880. for (i = 0; i < len; i++) {
  1881. if ((dontConvert && array1[i] !== array2[i]) ||
  1882. (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
  1883. diffs++;
  1884. }
  1885. }
  1886. return diffs + lengthDiff;
  1887. }
  1888. function normalizeUnits(units) {
  1889. if (units) {
  1890. var lowered = units.toLowerCase().replace(/(.)s$/, '$1');
  1891. units = unitAliases[units] || camelFunctions[lowered] || lowered;
  1892. }
  1893. return units;
  1894. }
  1895. function normalizeObjectUnits(inputObject) {
  1896. var normalizedInput = {},
  1897. normalizedProp,
  1898. prop;
  1899. for (prop in inputObject) {
  1900. if (hasOwnProp(inputObject, prop)) {
  1901. normalizedProp = normalizeUnits(prop);
  1902. if (normalizedProp) {
  1903. normalizedInput[normalizedProp] = inputObject[prop];
  1904. }
  1905. }
  1906. }
  1907. return normalizedInput;
  1908. }
  1909. function makeList(field) {
  1910. var count, setter;
  1911. if (field.indexOf('week') === 0) {
  1912. count = 7;
  1913. setter = 'day';
  1914. }
  1915. else if (field.indexOf('month') === 0) {
  1916. count = 12;
  1917. setter = 'month';
  1918. }
  1919. else {
  1920. return;
  1921. }
  1922. moment[field] = function (format, index) {
  1923. var i, getter,
  1924. method = moment._locale[field],
  1925. results = [];
  1926. if (typeof format === 'number') {
  1927. index = format;
  1928. format = undefined;
  1929. }
  1930. getter = function (i) {
  1931. var m = moment().utc().set(setter, i);
  1932. return method.call(moment._locale, m, format || '');
  1933. };
  1934. if (index != null) {
  1935. return getter(index);
  1936. }
  1937. else {
  1938. for (i = 0; i < count; i++) {
  1939. results.push(getter(i));
  1940. }
  1941. return results;
  1942. }
  1943. };
  1944. }
  1945. function toInt(argumentForCoercion) {
  1946. var coercedNumber = +argumentForCoercion,
  1947. value = 0;
  1948. if (coercedNumber !== 0 && isFinite(coercedNumber)) {
  1949. if (coercedNumber >= 0) {
  1950. value = Math.floor(coercedNumber);
  1951. } else {
  1952. value = Math.ceil(coercedNumber);
  1953. }
  1954. }
  1955. return value;
  1956. }
  1957. function daysInMonth(year, month) {
  1958. return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
  1959. }
  1960. function weeksInYear(year, dow, doy) {
  1961. return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week;
  1962. }
  1963. function daysInYear(year) {
  1964. return isLeapYear(year) ? 366 : 365;
  1965. }
  1966. function isLeapYear(year) {
  1967. return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
  1968. }
  1969. function checkOverflow(m) {
  1970. var overflow;
  1971. if (m._a && m._pf.overflow === -2) {
  1972. overflow =
  1973. m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH :
  1974. m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE :
  1975. m._a[HOUR] < 0 || m._a[HOUR] > 24 ||
  1976. (m._a[HOUR] === 24 && (m._a[MINUTE] !== 0 ||
  1977. m._a[SECOND] !== 0 ||
  1978. m._a[MILLISECOND] !== 0)) ? HOUR :
  1979. m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE :
  1980. m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND :
  1981. m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND :
  1982. -1;
  1983. if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
  1984. overflow = DATE;
  1985. }
  1986. m._pf.overflow = overflow;
  1987. }
  1988. }
  1989. function isValid(m) {
  1990. if (m._isValid == null) {
  1991. m._isValid = !isNaN(m._d.getTime()) &&
  1992. m._pf.overflow < 0 &&
  1993. !m._pf.empty &&
  1994. !m._pf.invalidMonth &&
  1995. !m._pf.nullInput &&
  1996. !m._pf.invalidFormat &&
  1997. !m._pf.userInvalidated;
  1998. if (m._strict) {
  1999. m._isValid = m._isValid &&
  2000. m._pf.charsLeftOver === 0 &&
  2001. m._pf.unusedTokens.length === 0 &&
  2002. m._pf.bigHour === undefined;
  2003. }
  2004. }
  2005. return m._isValid;
  2006. }
  2007. function normalizeLocale(key) {
  2008. return key ? key.toLowerCase().replace('_', '-') : key;
  2009. }
  2010. // pick the locale from the array
  2011. // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
  2012. // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
  2013. function chooseLocale(names) {
  2014. var i = 0, j, next, locale, split;
  2015. while (i < names.length) {
  2016. split = normalizeLocale(names[i]).split('-');
  2017. j = split.length;
  2018. next = normalizeLocale(names[i + 1]);
  2019. next = next ? next.split('-') : null;
  2020. while (j > 0) {
  2021. locale = loadLocale(split.slice(0, j).join('-'));
  2022. if (locale) {
  2023. return locale;
  2024. }
  2025. if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
  2026. //the next array item is better than a shallower substring of this one
  2027. break;
  2028. }
  2029. j--;
  2030. }
  2031. i++;
  2032. }
  2033. return null;
  2034. }
  2035. function loadLocale(name) {
  2036. var oldLocale = null;
  2037. if (!locales[name] && hasModule) {
  2038. try {
  2039. oldLocale = moment.locale();
  2040. !(function webpackMissingModule() { var e = new Error("Cannot find module \"./locale\""); e.code = 'MODULE_NOT_FOUND'; throw e; }());
  2041. // because defineLocale currently also sets the global locale, we want to undo that for lazy loaded locales
  2042. moment.locale(oldLocale);
  2043. } catch (e) { }
  2044. }
  2045. return locales[name];
  2046. }
  2047. // Return a moment from input, that is local/utc/utcOffset equivalent to
  2048. // model.
  2049. function makeAs(input, model) {
  2050. var res, diff;
  2051. if (model._isUTC) {
  2052. res = model.clone();
  2053. diff = (moment.isMoment(input) || isDate(input) ?
  2054. +input : +moment(input)) - (+res);
  2055. // Use low-level api, because this fn is low-level api.
  2056. res._d.setTime(+res._d + diff);
  2057. moment.updateOffset(res, false);
  2058. return res;
  2059. } else {
  2060. return moment(input).local();
  2061. }
  2062. }
  2063. /************************************
  2064. Locale
  2065. ************************************/
  2066. extend(Locale.prototype, {
  2067. set : function (config) {
  2068. var prop, i;
  2069. for (i in config) {
  2070. prop = config[i];
  2071. if (typeof prop === 'function') {
  2072. this[i] = prop;
  2073. } else {
  2074. this['_' + i] = prop;
  2075. }
  2076. }
  2077. // Lenient ordinal parsing accepts just a number in addition to
  2078. // number + (possibly) stuff coming from _ordinalParseLenient.
  2079. this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + /\d{1,2}/.source);
  2080. },
  2081. _months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
  2082. months : function (m) {
  2083. return this._months[m.month()];
  2084. },
  2085. _monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
  2086. monthsShort : function (m) {
  2087. return this._monthsShort[m.month()];
  2088. },
  2089. monthsParse : function (monthName, format, strict) {
  2090. var i, mom, regex;
  2091. if (!this._monthsParse) {
  2092. this._monthsParse = [];
  2093. this._longMonthsParse = [];
  2094. this._shortMonthsParse = [];
  2095. }
  2096. for (i = 0; i < 12; i++) {
  2097. // make the regex if we don't have it already
  2098. mom = moment.utc([2000, i]);
  2099. if (strict && !this._longMonthsParse[i]) {
  2100. this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
  2101. this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
  2102. }
  2103. if (!strict && !this._monthsParse[i]) {
  2104. regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
  2105. this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
  2106. }
  2107. // test the regex
  2108. if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
  2109. return i;
  2110. } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
  2111. return i;
  2112. } else if (!strict && this._monthsParse[i].test(monthName)) {
  2113. return i;
  2114. }
  2115. }
  2116. },
  2117. _weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
  2118. weekdays : function (m) {
  2119. return this._weekdays[m.day()];
  2120. },
  2121. _weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
  2122. weekdaysShort : function (m) {
  2123. return this._weekdaysShort[m.day()];
  2124. },
  2125. _weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
  2126. weekdaysMin : function (m) {
  2127. return this._weekdaysMin[m.day()];
  2128. },
  2129. weekdaysParse : function (weekdayName) {
  2130. var i, mom, regex;
  2131. if (!this._weekdaysParse) {
  2132. this._weekdaysParse = [];
  2133. }
  2134. for (i = 0; i < 7; i++) {
  2135. // make the regex if we don't have it already
  2136. if (!this._weekdaysParse[i]) {
  2137. mom = moment([2000, 1]).day(i);
  2138. regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
  2139. this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
  2140. }
  2141. // test the regex
  2142. if (this._weekdaysParse[i].test(weekdayName)) {
  2143. return i;
  2144. }
  2145. }
  2146. },
  2147. _longDateFormat : {
  2148. LTS : 'h:mm:ss A',
  2149. LT : 'h:mm A',
  2150. L : 'MM/DD/YYYY',
  2151. LL : 'MMMM D, YYYY',
  2152. LLL : 'MMMM D, YYYY LT',
  2153. LLLL : 'dddd, MMMM D, YYYY LT'
  2154. },
  2155. longDateFormat : function (key) {
  2156. var output = this._longDateFormat[key];
  2157. if (!output && this._longDateFormat[key.toUpperCase()]) {
  2158. output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
  2159. return val.slice(1);
  2160. });
  2161. this._longDateFormat[key] = output;
  2162. }
  2163. return output;
  2164. },
  2165. isPM : function (input) {
  2166. // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
  2167. // Using charAt should be more compatible.
  2168. return ((input + '').toLowerCase().charAt(0) === 'p');
  2169. },
  2170. _meridiemParse : /[ap]\.?m?\.?/i,
  2171. meridiem : function (hours, minutes, isLower) {
  2172. if (hours > 11) {
  2173. return isLower ? 'pm' : 'PM';
  2174. } else {
  2175. return isLower ? 'am' : 'AM';
  2176. }
  2177. },
  2178. _calendar : {
  2179. sameDay : '[Today at] LT',
  2180. nextDay : '[Tomorrow at] LT',
  2181. nextWeek : 'dddd [at] LT',
  2182. lastDay : '[Yesterday at] LT',
  2183. lastWeek : '[Last] dddd [at] LT',
  2184. sameElse : 'L'
  2185. },
  2186. calendar : function (key, mom, now) {
  2187. var output = this._calendar[key];
  2188. return typeof output === 'function' ? output.apply(mom, [now]) : output;
  2189. },
  2190. _relativeTime : {
  2191. future : 'in %s',
  2192. past : '%s ago',
  2193. s : 'a few seconds',
  2194. m : 'a minute',
  2195. mm : '%d minutes',
  2196. h : 'an hour',
  2197. hh : '%d hours',
  2198. d : 'a day',
  2199. dd : '%d days',
  2200. M : 'a month',
  2201. MM : '%d months',
  2202. y : 'a year',
  2203. yy : '%d years'
  2204. },
  2205. relativeTime : function (number, withoutSuffix, string, isFuture) {
  2206. var output = this._relativeTime[string];
  2207. return (typeof output === 'function') ?
  2208. output(number, withoutSuffix, string, isFuture) :
  2209. output.replace(/%d/i, number);
  2210. },
  2211. pastFuture : function (diff, output) {
  2212. var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
  2213. return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
  2214. },
  2215. ordinal : function (number) {
  2216. return this._ordinal.replace('%d', number);
  2217. },
  2218. _ordinal : '%d',
  2219. _ordinalParse : /\d{1,2}/,
  2220. preparse : function (string) {
  2221. return string;
  2222. },
  2223. postformat : function (string) {
  2224. return string;
  2225. },
  2226. week : function (mom) {
  2227. return weekOfYear(mom, this._week.dow, this._week.doy).week;
  2228. },
  2229. _week : {
  2230. dow : 0, // Sunday is the first day of the week.
  2231. doy : 6 // The week that contains Jan 1st is the first week of the year.
  2232. },
  2233. firstDayOfWeek : function () {
  2234. return this._week.dow;
  2235. },
  2236. firstDayOfYear : function () {
  2237. return this._week.doy;
  2238. },
  2239. _invalidDate: 'Invalid date',
  2240. invalidDate: function () {
  2241. return this._invalidDate;
  2242. }
  2243. });
  2244. /************************************
  2245. Formatting
  2246. ************************************/
  2247. function removeFormattingTokens(input) {
  2248. if (input.match(/\[[\s\S]/)) {
  2249. return input.replace(/^\[|\]$/g, '');
  2250. }
  2251. return input.replace(/\\/g, '');
  2252. }
  2253. function makeFormatFunction(format) {
  2254. var array = format.match(formattingTokens), i, length;
  2255. for (i = 0, length = array.length; i < length; i++) {
  2256. if (formatTokenFunctions[array[i]]) {
  2257. array[i] = formatTokenFunctions[array[i]];
  2258. } else {
  2259. array[i] = removeFormattingTokens(array[i]);
  2260. }
  2261. }
  2262. return function (mom) {
  2263. var output = '';
  2264. for (i = 0; i < length; i++) {
  2265. output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
  2266. }
  2267. return output;
  2268. };
  2269. }
  2270. // format date using native date object
  2271. function formatMoment(m, format) {
  2272. if (!m.isValid()) {
  2273. return m.localeData().invalidDate();
  2274. }
  2275. format = expandFormat(format, m.localeData());
  2276. if (!formatFunctions[format]) {
  2277. formatFunctions[format] = makeFormatFunction(format);
  2278. }
  2279. return formatFunctions[format](m);
  2280. }
  2281. function expandFormat(format, locale) {
  2282. var i = 5;
  2283. function replaceLongDateFormatTokens(input) {
  2284. return locale.longDateFormat(input) || input;
  2285. }
  2286. localFormattingTokens.lastIndex = 0;
  2287. while (i >= 0 && localFormattingTokens.test(format)) {
  2288. format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
  2289. localFormattingTokens.lastIndex = 0;
  2290. i -= 1;
  2291. }
  2292. return format;
  2293. }
  2294. /************************************
  2295. Parsing
  2296. ************************************/
  2297. // get the regex to find the next token
  2298. function getParseRegexForToken(token, config) {
  2299. var a, strict = config._strict;
  2300. switch (token) {
  2301. case 'Q':
  2302. return parseTokenOneDigit;
  2303. case 'DDDD':
  2304. return parseTokenThreeDigits;
  2305. case 'YYYY':
  2306. case 'GGGG':
  2307. case 'gggg':
  2308. return strict ? parseTokenFourDigits : parseTokenOneToFourDigits;
  2309. case 'Y':
  2310. case 'G':
  2311. case 'g':
  2312. return parseTokenSignedNumber;
  2313. case 'YYYYYY':
  2314. case 'YYYYY':
  2315. case 'GGGGG':
  2316. case 'ggggg':
  2317. return strict ? parseTokenSixDigits : parseTokenOneToSixDigits;
  2318. case 'S':
  2319. if (strict) {
  2320. return parseTokenOneDigit;
  2321. }
  2322. /* falls through */
  2323. case 'SS':
  2324. if (strict) {
  2325. return parseTokenTwoDigits;
  2326. }
  2327. /* falls through */
  2328. case 'SSS':
  2329. if (strict) {
  2330. return parseTokenThreeDigits;
  2331. }
  2332. /* falls through */
  2333. case 'DDD':
  2334. return parseTokenOneToThreeDigits;
  2335. case 'MMM':
  2336. case 'MMMM':
  2337. case 'dd':
  2338. case 'ddd':
  2339. case 'dddd':
  2340. return parseTokenWord;
  2341. case 'a':
  2342. case 'A':
  2343. return config._locale._meridiemParse;
  2344. case 'x':
  2345. return parseTokenOffsetMs;
  2346. case 'X':
  2347. return parseTokenTimestampMs;
  2348. case 'Z':
  2349. case 'ZZ':
  2350. return parseTokenTimezone;
  2351. case 'T':
  2352. return parseTokenT;
  2353. case 'SSSS':
  2354. return parseTokenDigits;
  2355. case 'MM':
  2356. case 'DD':
  2357. case 'YY':
  2358. case 'GG':
  2359. case 'gg':
  2360. case 'HH':
  2361. case 'hh':
  2362. case 'mm':
  2363. case 'ss':
  2364. case 'ww':
  2365. case 'WW':
  2366. return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits;
  2367. case 'M':
  2368. case 'D':
  2369. case 'd':
  2370. case 'H':
  2371. case 'h':
  2372. case 'm':
  2373. case 's':
  2374. case 'w':
  2375. case 'W':
  2376. case 'e':
  2377. case 'E':
  2378. return parseTokenOneOrTwoDigits;
  2379. case 'Do':
  2380. return strict ? config._locale._ordinalParse : config._locale._ordinalParseLenient;
  2381. default :
  2382. a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), 'i'));
  2383. return a;
  2384. }
  2385. }
  2386. function utcOffsetFromString(string) {
  2387. string = string || '';
  2388. var possibleTzMatches = (string.match(parseTokenTimezone) || []),
  2389. tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [],
  2390. parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
  2391. minutes = +(parts[1] * 60) + toInt(parts[2]);
  2392. return parts[0] === '+' ? minutes : -minutes;
  2393. }
  2394. // function to convert string input to date
  2395. function addTimeToArrayFromToken(token, input, config) {
  2396. var a, datePartArray = config._a;
  2397. switch (token) {
  2398. // QUARTER
  2399. case 'Q':
  2400. if (input != null) {
  2401. datePartArray[MONTH] = (toInt(input) - 1) * 3;
  2402. }
  2403. break;
  2404. // MONTH
  2405. case 'M' : // fall through to MM
  2406. case 'MM' :
  2407. if (input != null) {
  2408. datePartArray[MONTH] = toInt(input) - 1;
  2409. }
  2410. break;
  2411. case 'MMM' : // fall through to MMMM
  2412. case 'MMMM' :
  2413. a = config._locale.monthsParse(input, token, config._strict);
  2414. // if we didn't find a month name, mark the date as invalid.
  2415. if (a != null) {
  2416. datePartArray[MONTH] = a;
  2417. } else {
  2418. config._pf.invalidMonth = input;
  2419. }
  2420. break;
  2421. // DAY OF MONTH
  2422. case 'D' : // fall through to DD
  2423. case 'DD' :
  2424. if (input != null) {
  2425. datePartArray[DATE] = toInt(input);
  2426. }
  2427. break;
  2428. case 'Do' :
  2429. if (input != null) {
  2430. datePartArray[DATE] = toInt(parseInt(
  2431. input.match(/\d{1,2}/)[0], 10));
  2432. }
  2433. break;
  2434. // DAY OF YEAR
  2435. case 'DDD' : // fall through to DDDD
  2436. case 'DDDD' :
  2437. if (input != null) {
  2438. config._dayOfYear = toInt(input);
  2439. }
  2440. break;
  2441. // YEAR
  2442. case 'YY' :
  2443. datePartArray[YEAR] = moment.parseTwoDigitYear(input);
  2444. break;
  2445. case 'YYYY' :
  2446. case 'YYYYY' :
  2447. case 'YYYYYY' :
  2448. datePartArray[YEAR] = toInt(input);
  2449. break;
  2450. // AM / PM
  2451. case 'a' : // fall through to A
  2452. case 'A' :
  2453. config._meridiem = input;
  2454. // config._isPm = config._locale.isPM(input);
  2455. break;
  2456. // HOUR
  2457. case 'h' : // fall through to hh
  2458. case 'hh' :
  2459. config._pf.bigHour = true;
  2460. /* falls through */
  2461. case 'H' : // fall through to HH
  2462. case 'HH' :
  2463. datePartArray[HOUR] = toInt(input);
  2464. break;
  2465. // MINUTE
  2466. case 'm' : // fall through to mm
  2467. case 'mm' :
  2468. datePartArray[MINUTE] = toInt(input);
  2469. break;
  2470. // SECOND
  2471. case 's' : // fall through to ss
  2472. case 'ss' :
  2473. datePartArray[SECOND] = toInt(input);
  2474. break;
  2475. // MILLISECOND
  2476. case 'S' :
  2477. case 'SS' :
  2478. case 'SSS' :
  2479. case 'SSSS' :
  2480. datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000);
  2481. break;
  2482. // UNIX OFFSET (MILLISECONDS)
  2483. case 'x':
  2484. config._d = new Date(toInt(input));
  2485. break;
  2486. // UNIX TIMESTAMP WITH MS
  2487. case 'X':
  2488. config._d = new Date(parseFloat(input) * 1000);
  2489. break;
  2490. // TIMEZONE
  2491. case 'Z' : // fall through to ZZ
  2492. case 'ZZ' :
  2493. config._useUTC = true;
  2494. config._tzm = utcOffsetFromString(input);
  2495. break;
  2496. // WEEKDAY - human
  2497. case 'dd':
  2498. case 'ddd':
  2499. case 'dddd':
  2500. a = config._locale.weekdaysParse(input);
  2501. // if we didn't get a weekday name, mark the date as invalid
  2502. if (a != null) {
  2503. config._w = config._w || {};
  2504. config._w['d'] = a;
  2505. } else {
  2506. config._pf.invalidWeekday = input;
  2507. }
  2508. break;
  2509. // WEEK, WEEK DAY - numeric
  2510. case 'w':
  2511. case 'ww':
  2512. case 'W':
  2513. case 'WW':
  2514. case 'd':
  2515. case 'e':
  2516. case 'E':
  2517. token = token.substr(0, 1);
  2518. /* falls through */
  2519. case 'gggg':
  2520. case 'GGGG':
  2521. case 'GGGGG':
  2522. token = token.substr(0, 2);
  2523. if (input) {
  2524. config._w = config._w || {};
  2525. config._w[token] = toInt(input);
  2526. }
  2527. break;
  2528. case 'gg':
  2529. case 'GG':
  2530. config._w = config._w || {};
  2531. config._w[token] = moment.parseTwoDigitYear(input);
  2532. }
  2533. }
  2534. function dayOfYearFromWeekInfo(config) {
  2535. var w, weekYear, week, weekday, dow, doy, temp;
  2536. w = config._w;
  2537. if (w.GG != null || w.W != null || w.E != null) {
  2538. dow = 1;
  2539. doy = 4;
  2540. // TODO: We need to take the current isoWeekYear, but that depends on
  2541. // how we interpret now (local, utc, fixed offset). So create
  2542. // a now version of current config (take local/utc/offset flags, and
  2543. // create now).
  2544. weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year);
  2545. week = dfl(w.W, 1);
  2546. weekday = dfl(w.E, 1);
  2547. } else {
  2548. dow = config._locale._week.dow;
  2549. doy = config._locale._week.doy;
  2550. weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year);
  2551. week = dfl(w.w, 1);
  2552. if (w.d != null) {
  2553. // weekday -- low day numbers are considered next week
  2554. weekday = w.d;
  2555. if (weekday < dow) {
  2556. ++week;
  2557. }
  2558. } else if (w.e != null) {
  2559. // local weekday -- counting starts from begining of week
  2560. weekday = w.e + dow;
  2561. } else {
  2562. // default to begining of week
  2563. weekday = dow;
  2564. }
  2565. }
  2566. temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow);
  2567. config._a[YEAR] = temp.year;
  2568. config._dayOfYear = temp.dayOfYear;
  2569. }
  2570. // convert an array to a date.
  2571. // the array should mirror the parameters below
  2572. // note: all values past the year are optional and will default to the lowest possible value.
  2573. // [year, month, day , hour, minute, second, millisecond]
  2574. function dateFromConfig(config) {
  2575. var i, date, input = [], currentDate, yearToUse;
  2576. if (config._d) {
  2577. return;
  2578. }
  2579. currentDate = currentDateArray(config);
  2580. //compute day of the year from weeks and weekdays
  2581. if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
  2582. dayOfYearFromWeekInfo(config);
  2583. }
  2584. //if the day of the year is set, figure out what it is
  2585. if (config._dayOfYear) {
  2586. yearToUse = dfl(config._a[YEAR], currentDate[YEAR]);
  2587. if (config._dayOfYear > daysInYear(yearToUse)) {
  2588. config._pf._overflowDayOfYear = true;
  2589. }
  2590. date = makeUTCDate(yearToUse, 0, config._dayOfYear);
  2591. config._a[MONTH] = date.getUTCMonth();
  2592. config._a[DATE] = date.getUTCDate();
  2593. }
  2594. // Default to current date.
  2595. // * if no year, month, day of month are given, default to today
  2596. // * if day of month is given, default month and year
  2597. // * if month is given, default only year
  2598. // * if year is given, don't default anything
  2599. for (i = 0; i < 3 && config._a[i] == null; ++i) {
  2600. config._a[i] = input[i] = currentDate[i];
  2601. }
  2602. // Zero out whatever was not defaulted, including time
  2603. for (; i < 7; i++) {
  2604. config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
  2605. }
  2606. // Check for 24:00:00.000
  2607. if (config._a[HOUR] === 24 &&
  2608. config._a[MINUTE] === 0 &&
  2609. config._a[SECOND] === 0 &&
  2610. config._a[MILLISECOND] === 0) {
  2611. config._nextDay = true;
  2612. config._a[HOUR] = 0;
  2613. }
  2614. config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input);
  2615. // Apply timezone offset from input. The actual utcOffset can be changed
  2616. // with parseZone.
  2617. if (config._tzm != null) {
  2618. config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
  2619. }
  2620. if (config._nextDay) {
  2621. config._a[HOUR] = 24;
  2622. }
  2623. }
  2624. function dateFromObject(config) {
  2625. var normalizedInput;
  2626. if (config._d) {
  2627. return;
  2628. }
  2629. normalizedInput = normalizeObjectUnits(config._i);
  2630. config._a = [
  2631. normalizedInput.year,
  2632. normalizedInput.month,
  2633. normalizedInput.day || normalizedInput.date,
  2634. normalizedInput.hour,
  2635. normalizedInput.minute,
  2636. normalizedInput.second,
  2637. normalizedInput.millisecond
  2638. ];
  2639. dateFromConfig(config);
  2640. }
  2641. function currentDateArray(config) {
  2642. var now = new Date();
  2643. if (config._useUTC) {
  2644. return [
  2645. now.getUTCFullYear(),
  2646. now.getUTCMonth(),
  2647. now.getUTCDate()
  2648. ];
  2649. } else {
  2650. return [now.getFullYear(), now.getMonth(), now.getDate()];
  2651. }
  2652. }
  2653. // date from string and format string
  2654. function makeDateFromStringAndFormat(config) {
  2655. if (config._f === moment.ISO_8601) {
  2656. parseISO(config);
  2657. return;
  2658. }
  2659. config._a = [];
  2660. config._pf.empty = true;
  2661. // This array is used to make a Date, either with `new Date` or `Date.UTC`
  2662. var string = '' + config._i,
  2663. i, parsedInput, tokens, token, skipped,
  2664. stringLength = string.length,
  2665. totalParsedInputLength = 0;
  2666. tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
  2667. for (i = 0; i < tokens.length; i++) {
  2668. token = tokens[i];
  2669. parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
  2670. if (parsedInput) {
  2671. skipped = string.substr(0, string.indexOf(parsedInput));
  2672. if (skipped.length > 0) {
  2673. config._pf.unusedInput.push(skipped);
  2674. }
  2675. string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
  2676. totalParsedInputLength += parsedInput.length;
  2677. }
  2678. // don't parse if it's not a known token
  2679. if (formatTokenFunctions[token]) {
  2680. if (parsedInput) {
  2681. config._pf.empty = false;
  2682. }
  2683. else {
  2684. config._pf.unusedTokens.push(token);
  2685. }
  2686. addTimeToArrayFromToken(token, parsedInput, config);
  2687. }
  2688. else if (config._strict && !parsedInput) {
  2689. config._pf.unusedTokens.push(token);
  2690. }
  2691. }
  2692. // add remaining unparsed input length to the string
  2693. config._pf.charsLeftOver = stringLength - totalParsedInputLength;
  2694. if (string.length > 0) {
  2695. config._pf.unusedInput.push(string);
  2696. }
  2697. // clear _12h flag if hour is <= 12
  2698. if (config._pf.bigHour === true && config._a[HOUR] <= 12) {
  2699. config._pf.bigHour = undefined;
  2700. }
  2701. // handle meridiem
  2702. config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR],
  2703. config._meridiem);
  2704. dateFromConfig(config);
  2705. checkOverflow(config);
  2706. }
  2707. function unescapeFormat(s) {
  2708. return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
  2709. return p1 || p2 || p3 || p4;
  2710. });
  2711. }
  2712. // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
  2713. function regexpEscape(s) {
  2714. return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
  2715. }
  2716. // date from string and array of format strings
  2717. function makeDateFromStringAndArray(config) {
  2718. var tempConfig,
  2719. bestMoment,
  2720. scoreToBeat,
  2721. i,
  2722. currentScore;
  2723. if (config._f.length === 0) {
  2724. config._pf.invalidFormat = true;
  2725. config._d = new Date(NaN);
  2726. return;
  2727. }
  2728. for (i = 0; i < config._f.length; i++) {
  2729. currentScore = 0;
  2730. tempConfig = copyConfig({}, config);
  2731. if (config._useUTC != null) {
  2732. tempConfig._useUTC = config._useUTC;
  2733. }
  2734. tempConfig._pf = defaultParsingFlags();
  2735. tempConfig._f = config._f[i];
  2736. makeDateFromStringAndFormat(tempConfig);
  2737. if (!isValid(tempConfig)) {
  2738. continue;
  2739. }
  2740. // if there is any input that was not parsed add a penalty for that format
  2741. currentScore += tempConfig._pf.charsLeftOver;
  2742. //or tokens
  2743. currentScore += tempConfig._pf.unusedTokens.length * 10;
  2744. tempConfig._pf.score = currentScore;
  2745. if (scoreToBeat == null || currentScore < scoreToBeat) {
  2746. scoreToBeat = currentScore;
  2747. bestMoment = tempConfig;
  2748. }
  2749. }
  2750. extend(config, bestMoment || tempConfig);
  2751. }
  2752. // date from iso format
  2753. function parseISO(config) {
  2754. var i, l,
  2755. string = config._i,
  2756. match = isoRegex.exec(string);
  2757. if (match) {
  2758. config._pf.iso = true;
  2759. for (i = 0, l = isoDates.length; i < l; i++) {
  2760. if (isoDates[i][1].exec(string)) {
  2761. // match[5] should be 'T' or undefined
  2762. config._f = isoDates[i][0] + (match[6] || ' ');
  2763. break;
  2764. }
  2765. }
  2766. for (i = 0, l = isoTimes.length; i < l; i++) {
  2767. if (isoTimes[i][1].exec(string)) {
  2768. config._f += isoTimes[i][0];
  2769. break;
  2770. }
  2771. }
  2772. if (string.match(parseTokenTimezone)) {
  2773. config._f += 'Z';
  2774. }
  2775. makeDateFromStringAndFormat(config);
  2776. } else {
  2777. config._isValid = false;
  2778. }
  2779. }
  2780. // date from iso format or fallback
  2781. function makeDateFromString(config) {
  2782. parseISO(config);
  2783. if (config._isValid === false) {
  2784. delete config._isValid;
  2785. moment.createFromInputFallback(config);
  2786. }
  2787. }
  2788. function map(arr, fn) {
  2789. var res = [], i;
  2790. for (i = 0; i < arr.length; ++i) {
  2791. res.push(fn(arr[i], i));
  2792. }
  2793. return res;
  2794. }
  2795. function makeDateFromInput(config) {
  2796. var input = config._i, matched;
  2797. if (input === undefined) {
  2798. config._d = new Date();
  2799. } else if (isDate(input)) {
  2800. config._d = new Date(+input);
  2801. } else if ((matched = aspNetJsonRegex.exec(input)) !== null) {
  2802. config._d = new Date(+matched[1]);
  2803. } else if (typeof input === 'string') {
  2804. makeDateFromString(config);
  2805. } else if (isArray(input)) {
  2806. config._a = map(input.slice(0), function (obj) {
  2807. return parseInt(obj, 10);
  2808. });
  2809. dateFromConfig(config);
  2810. } else if (typeof(input) === 'object') {
  2811. dateFromObject(config);
  2812. } else if (typeof(input) === 'number') {
  2813. // from milliseconds
  2814. config._d = new Date(input);
  2815. } else {
  2816. moment.createFromInputFallback(config);
  2817. }
  2818. }
  2819. function makeDate(y, m, d, h, M, s, ms) {
  2820. //can't just apply() to create a date:
  2821. //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
  2822. var date = new Date(y, m, d, h, M, s, ms);
  2823. //the date constructor doesn't accept years < 1970
  2824. if (y < 1970) {
  2825. date.setFullYear(y);
  2826. }
  2827. return date;
  2828. }
  2829. function makeUTCDate(y) {
  2830. var date = new Date(Date.UTC.apply(null, arguments));
  2831. if (y < 1970) {
  2832. date.setUTCFullYear(y);
  2833. }
  2834. return date;
  2835. }
  2836. function parseWeekday(input, locale) {
  2837. if (typeof input === 'string') {
  2838. if (!isNaN(input)) {
  2839. input = parseInt(input, 10);
  2840. }
  2841. else {
  2842. input = locale.weekdaysParse(input);
  2843. if (typeof input !== 'number') {
  2844. return null;
  2845. }
  2846. }
  2847. }
  2848. return input;
  2849. }
  2850. /************************************
  2851. Relative Time
  2852. ************************************/
  2853. // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
  2854. function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
  2855. return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
  2856. }
  2857. function relativeTime(posNegDuration, withoutSuffix, locale) {
  2858. var duration = moment.duration(posNegDuration).abs(),
  2859. seconds = round(duration.as('s')),
  2860. minutes = round(duration.as('m')),
  2861. hours = round(duration.as('h')),
  2862. days = round(duration.as('d')),
  2863. months = round(duration.as('M')),
  2864. years = round(duration.as('y')),
  2865. args = seconds < relativeTimeThresholds.s && ['s', seconds] ||
  2866. minutes === 1 && ['m'] ||
  2867. minutes < relativeTimeThresholds.m && ['mm', minutes] ||
  2868. hours === 1 && ['h'] ||
  2869. hours < relativeTimeThresholds.h && ['hh', hours] ||
  2870. days === 1 && ['d'] ||
  2871. days < relativeTimeThresholds.d && ['dd', days] ||
  2872. months === 1 && ['M'] ||
  2873. months < relativeTimeThresholds.M && ['MM', months] ||
  2874. years === 1 && ['y'] || ['yy', years];
  2875. args[2] = withoutSuffix;
  2876. args[3] = +posNegDuration > 0;
  2877. args[4] = locale;
  2878. return substituteTimeAgo.apply({}, args);
  2879. }
  2880. /************************************
  2881. Week of Year
  2882. ************************************/
  2883. // firstDayOfWeek 0 = sun, 6 = sat
  2884. // the day of the week that starts the week
  2885. // (usually sunday or monday)
  2886. // firstDayOfWeekOfYear 0 = sun, 6 = sat
  2887. // the first week is the week that contains the first
  2888. // of this day of the week
  2889. // (eg. ISO weeks use thursday (4))
  2890. function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
  2891. var end = firstDayOfWeekOfYear - firstDayOfWeek,
  2892. daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(),
  2893. adjustedMoment;
  2894. if (daysToDayOfWeek > end) {
  2895. daysToDayOfWeek -= 7;
  2896. }
  2897. if (daysToDayOfWeek < end - 7) {
  2898. daysToDayOfWeek += 7;
  2899. }
  2900. adjustedMoment = moment(mom).add(daysToDayOfWeek, 'd');
  2901. return {
  2902. week: Math.ceil(adjustedMoment.dayOfYear() / 7),
  2903. year: adjustedMoment.year()
  2904. };
  2905. }
  2906. //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
  2907. function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) {
  2908. var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear;
  2909. d = d === 0 ? 7 : d;
  2910. weekday = weekday != null ? weekday : firstDayOfWeek;
  2911. daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0);
  2912. dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1;
  2913. return {
  2914. year: dayOfYear > 0 ? year : year - 1,
  2915. dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear
  2916. };
  2917. }
  2918. /************************************
  2919. Top Level Functions
  2920. ************************************/
  2921. function makeMoment(config) {
  2922. var input = config._i,
  2923. format = config._f,
  2924. res;
  2925. config._locale = config._locale || moment.localeData(config._l);
  2926. if (input === null || (format === undefined && input === '')) {
  2927. return moment.invalid({nullInput: true});
  2928. }
  2929. if (typeof input === 'string') {
  2930. config._i = input = config._locale.preparse(input);
  2931. }
  2932. if (moment.isMoment(input)) {
  2933. return new Moment(input, true);
  2934. } else if (format) {
  2935. if (isArray(format)) {
  2936. makeDateFromStringAndArray(config);
  2937. } else {
  2938. makeDateFromStringAndFormat(config);
  2939. }
  2940. } else {
  2941. makeDateFromInput(config);
  2942. }
  2943. res = new Moment(config);
  2944. if (res._nextDay) {
  2945. // Adding is smart enough around DST
  2946. res.add(1, 'd');
  2947. res._nextDay = undefined;
  2948. }
  2949. return res;
  2950. }
  2951. moment = function (input, format, locale, strict) {
  2952. var c;
  2953. if (typeof(locale) === 'boolean') {
  2954. strict = locale;
  2955. locale = undefined;
  2956. }
  2957. // object construction must be done this way.
  2958. // https://github.com/moment/moment/issues/1423
  2959. c = {};
  2960. c._isAMomentObject = true;
  2961. c._i = input;
  2962. c._f = format;
  2963. c._l = locale;
  2964. c._strict = strict;
  2965. c._isUTC = false;
  2966. c._pf = defaultParsingFlags();
  2967. return makeMoment(c);
  2968. };
  2969. moment.suppressDeprecationWarnings = false;
  2970. moment.createFromInputFallback = deprecate(
  2971. 'moment construction falls back to js Date. This is ' +
  2972. 'discouraged and will be removed in upcoming major ' +
  2973. 'release. Please refer to ' +
  2974. 'https://github.com/moment/moment/issues/1407 for more info.',
  2975. function (config) {
  2976. config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
  2977. }
  2978. );
  2979. // Pick a moment m from moments so that m[fn](other) is true for all
  2980. // other. This relies on the function fn to be transitive.
  2981. //
  2982. // moments should either be an array of moment objects or an array, whose
  2983. // first element is an array of moment objects.
  2984. function pickBy(fn, moments) {
  2985. var res, i;
  2986. if (moments.length === 1 && isArray(moments[0])) {
  2987. moments = moments[0];
  2988. }
  2989. if (!moments.length) {
  2990. return moment();
  2991. }
  2992. res = moments[0];
  2993. for (i = 1; i < moments.length; ++i) {
  2994. if (moments[i][fn](res)) {
  2995. res = moments[i];
  2996. }
  2997. }
  2998. return res;
  2999. }
  3000. moment.min = function () {
  3001. var args = [].slice.call(arguments, 0);
  3002. return pickBy('isBefore', args);
  3003. };
  3004. moment.max = function () {
  3005. var args = [].slice.call(arguments, 0);
  3006. return pickBy('isAfter', args);
  3007. };
  3008. // creating with utc
  3009. moment.utc = function (input, format, locale, strict) {
  3010. var c;
  3011. if (typeof(locale) === 'boolean') {
  3012. strict = locale;
  3013. locale = undefined;
  3014. }
  3015. // object construction must be done this way.
  3016. // https://github.com/moment/moment/issues/1423
  3017. c = {};
  3018. c._isAMomentObject = true;
  3019. c._useUTC = true;
  3020. c._isUTC = true;
  3021. c._l = locale;
  3022. c._i = input;
  3023. c._f = format;
  3024. c._strict = strict;
  3025. c._pf = defaultParsingFlags();
  3026. return makeMoment(c).utc();
  3027. };
  3028. // creating with unix timestamp (in seconds)
  3029. moment.unix = function (input) {
  3030. return moment(input * 1000);
  3031. };
  3032. // duration
  3033. moment.duration = function (input, key) {
  3034. var duration = input,
  3035. // matching against regexp is expensive, do it on demand
  3036. match = null,
  3037. sign,
  3038. ret,
  3039. parseIso,
  3040. diffRes;
  3041. if (moment.isDuration(input)) {
  3042. duration = {
  3043. ms: input._milliseconds,
  3044. d: input._days,
  3045. M: input._months
  3046. };
  3047. } else if (typeof input === 'number') {
  3048. duration = {};
  3049. if (key) {
  3050. duration[key] = input;
  3051. } else {
  3052. duration.milliseconds = input;
  3053. }
  3054. } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) {
  3055. sign = (match[1] === '-') ? -1 : 1;
  3056. duration = {
  3057. y: 0,
  3058. d: toInt(match[DATE]) * sign,
  3059. h: toInt(match[HOUR]) * sign,
  3060. m: toInt(match[MINUTE]) * sign,
  3061. s: toInt(match[SECOND]) * sign,
  3062. ms: toInt(match[MILLISECOND]) * sign
  3063. };
  3064. } else if (!!(match = isoDurationRegex.exec(input))) {
  3065. sign = (match[1] === '-') ? -1 : 1;
  3066. parseIso = function (inp) {
  3067. // We'd normally use ~~inp for this, but unfortunately it also
  3068. // converts floats to ints.
  3069. // inp may be undefined, so careful calling replace on it.
  3070. var res = inp && parseFloat(inp.replace(',', '.'));
  3071. // apply sign while we're at it
  3072. return (isNaN(res) ? 0 : res) * sign;
  3073. };
  3074. duration = {
  3075. y: parseIso(match[2]),
  3076. M: parseIso(match[3]),
  3077. d: parseIso(match[4]),
  3078. h: parseIso(match[5]),
  3079. m: parseIso(match[6]),
  3080. s: parseIso(match[7]),
  3081. w: parseIso(match[8])
  3082. };
  3083. } else if (duration == null) {// checks for null or undefined
  3084. duration = {};
  3085. } else if (typeof duration === 'object' &&
  3086. ('from' in duration || 'to' in duration)) {
  3087. diffRes = momentsDifference(moment(duration.from), moment(duration.to));
  3088. duration = {};
  3089. duration.ms = diffRes.milliseconds;
  3090. duration.M = diffRes.months;
  3091. }
  3092. ret = new Duration(duration);
  3093. if (moment.isDuration(input) && hasOwnProp(input, '_locale')) {
  3094. ret._locale = input._locale;
  3095. }
  3096. return ret;
  3097. };
  3098. // version number
  3099. moment.version = VERSION;
  3100. // default format
  3101. moment.defaultFormat = isoFormat;
  3102. // constant that refers to the ISO standard
  3103. moment.ISO_8601 = function () {};
  3104. // Plugins that add properties should also add the key here (null value),
  3105. // so we can properly clone ourselves.
  3106. moment.momentProperties = momentProperties;
  3107. // This function will be called whenever a moment is mutated.
  3108. // It is intended to keep the offset in sync with the timezone.
  3109. moment.updateOffset = function () {};
  3110. // This function allows you to set a threshold for relative time strings
  3111. moment.relativeTimeThreshold = function (threshold, limit) {
  3112. if (relativeTimeThresholds[threshold] === undefined) {
  3113. return false;
  3114. }
  3115. if (limit === undefined) {
  3116. return relativeTimeThresholds[threshold];
  3117. }
  3118. relativeTimeThresholds[threshold] = limit;
  3119. return true;
  3120. };
  3121. moment.lang = deprecate(
  3122. 'moment.lang is deprecated. Use moment.locale instead.',
  3123. function (key, value) {
  3124. return moment.locale(key, value);
  3125. }
  3126. );
  3127. // This function will load locale and then set the global locale. If
  3128. // no arguments are passed in, it will simply return the current global
  3129. // locale key.
  3130. moment.locale = function (key, values) {
  3131. var data;
  3132. if (key) {
  3133. if (typeof(values) !== 'undefined') {
  3134. data = moment.defineLocale(key, values);
  3135. }
  3136. else {
  3137. data = moment.localeData(key);
  3138. }
  3139. if (data) {
  3140. moment.duration._locale = moment._locale = data;
  3141. }
  3142. }
  3143. return moment._locale._abbr;
  3144. };
  3145. moment.defineLocale = function (name, values) {
  3146. if (values !== null) {
  3147. values.abbr = name;
  3148. if (!locales[name]) {
  3149. locales[name] = new Locale();
  3150. }
  3151. locales[name].set(values);
  3152. // backwards compat for now: also set the locale
  3153. moment.locale(name);
  3154. return locales[name];
  3155. } else {
  3156. // useful for testing
  3157. delete locales[name];
  3158. return null;
  3159. }
  3160. };
  3161. moment.langData = deprecate(
  3162. 'moment.langData is deprecated. Use moment.localeData instead.',
  3163. function (key) {
  3164. return moment.localeData(key);
  3165. }
  3166. );
  3167. // returns locale data
  3168. moment.localeData = function (key) {
  3169. var locale;
  3170. if (key && key._locale && key._locale._abbr) {
  3171. key = key._locale._abbr;
  3172. }
  3173. if (!key) {
  3174. return moment._locale;
  3175. }
  3176. if (!isArray(key)) {
  3177. //short-circuit everything else
  3178. locale = loadLocale(key);
  3179. if (locale) {
  3180. return locale;
  3181. }
  3182. key = [key];
  3183. }
  3184. return chooseLocale(key);
  3185. };
  3186. // compare moment object
  3187. moment.isMoment = function (obj) {
  3188. return obj instanceof Moment ||
  3189. (obj != null && hasOwnProp(obj, '_isAMomentObject'));
  3190. };
  3191. // for typechecking Duration objects
  3192. moment.isDuration = function (obj) {
  3193. return obj instanceof Duration;
  3194. };
  3195. for (i = lists.length - 1; i >= 0; --i) {
  3196. makeList(lists[i]);
  3197. }
  3198. moment.normalizeUnits = function (units) {
  3199. return normalizeUnits(units);
  3200. };
  3201. moment.invalid = function (flags) {
  3202. var m = moment.utc(NaN);
  3203. if (flags != null) {
  3204. extend(m._pf, flags);
  3205. }
  3206. else {
  3207. m._pf.userInvalidated = true;
  3208. }
  3209. return m;
  3210. };
  3211. moment.parseZone = function () {
  3212. return moment.apply(null, arguments).parseZone();
  3213. };
  3214. moment.parseTwoDigitYear = function (input) {
  3215. return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
  3216. };
  3217. moment.isDate = isDate;
  3218. /************************************
  3219. Moment Prototype
  3220. ************************************/
  3221. extend(moment.fn = Moment.prototype, {
  3222. clone : function () {
  3223. return moment(this);
  3224. },
  3225. valueOf : function () {
  3226. return +this._d - ((this._offset || 0) * 60000);
  3227. },
  3228. unix : function () {
  3229. return Math.floor(+this / 1000);
  3230. },
  3231. toString : function () {
  3232. return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
  3233. },
  3234. toDate : function () {
  3235. return this._offset ? new Date(+this) : this._d;
  3236. },
  3237. toISOString : function () {
  3238. var m = moment(this).utc();
  3239. if (0 < m.year() && m.year() <= 9999) {
  3240. if ('function' === typeof Date.prototype.toISOString) {
  3241. // native implementation is ~50x faster, use it when we can
  3242. return this.toDate().toISOString();
  3243. } else {
  3244. return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  3245. }
  3246. } else {
  3247. return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  3248. }
  3249. },
  3250. toArray : function () {
  3251. var m = this;
  3252. return [
  3253. m.year(),
  3254. m.month(),
  3255. m.date(),
  3256. m.hours(),
  3257. m.minutes(),
  3258. m.seconds(),
  3259. m.milliseconds()
  3260. ];
  3261. },
  3262. isValid : function () {
  3263. return isValid(this);
  3264. },
  3265. isDSTShifted : function () {
  3266. if (this._a) {
  3267. return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0;
  3268. }
  3269. return false;
  3270. },
  3271. parsingFlags : function () {
  3272. return extend({}, this._pf);
  3273. },
  3274. invalidAt: function () {
  3275. return this._pf.overflow;
  3276. },
  3277. utc : function (keepLocalTime) {
  3278. return this.utcOffset(0, keepLocalTime);
  3279. },
  3280. local : function (keepLocalTime) {
  3281. if (this._isUTC) {
  3282. this.utcOffset(0, keepLocalTime);
  3283. this._isUTC = false;
  3284. if (keepLocalTime) {
  3285. this.subtract(this._dateUtcOffset(), 'm');
  3286. }
  3287. }
  3288. return this;
  3289. },
  3290. format : function (inputString) {
  3291. var output = formatMoment(this, inputString || moment.defaultFormat);
  3292. return this.localeData().postformat(output);
  3293. },
  3294. add : createAdder(1, 'add'),
  3295. subtract : createAdder(-1, 'subtract'),
  3296. diff : function (input, units, asFloat) {
  3297. var that = makeAs(input, this),
  3298. zoneDiff = (that.utcOffset() - this.utcOffset()) * 6e4,
  3299. anchor, diff, output, daysAdjust;
  3300. units = normalizeUnits(units);
  3301. if (units === 'year' || units === 'month' || units === 'quarter') {
  3302. output = monthDiff(this, that);
  3303. if (units === 'quarter') {
  3304. output = output / 3;
  3305. } else if (units === 'year') {
  3306. output = output / 12;
  3307. }
  3308. } else {
  3309. diff = this - that;
  3310. output = units === 'second' ? diff / 1e3 : // 1000
  3311. units === 'minute' ? diff / 6e4 : // 1000 * 60
  3312. units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
  3313. units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
  3314. units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
  3315. diff;
  3316. }
  3317. return asFloat ? output : absRound(output);
  3318. },
  3319. from : function (time, withoutSuffix) {
  3320. return moment.duration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
  3321. },
  3322. fromNow : function (withoutSuffix) {
  3323. return this.from(moment(), withoutSuffix);
  3324. },
  3325. calendar : function (time) {
  3326. // We want to compare the start of today, vs this.
  3327. // Getting start-of-today depends on whether we're locat/utc/offset
  3328. // or not.
  3329. var now = time || moment(),
  3330. sod = makeAs(now, this).startOf('day'),
  3331. diff = this.diff(sod, 'days', true),
  3332. format = diff < -6 ? 'sameElse' :
  3333. diff < -1 ? 'lastWeek' :
  3334. diff < 0 ? 'lastDay' :
  3335. diff < 1 ? 'sameDay' :
  3336. diff < 2 ? 'nextDay' :
  3337. diff < 7 ? 'nextWeek' : 'sameElse';
  3338. return this.format(this.localeData().calendar(format, this, moment(now)));
  3339. },
  3340. isLeapYear : function () {
  3341. return isLeapYear(this.year());
  3342. },
  3343. isDST : function () {
  3344. return (this.utcOffset() > this.clone().month(0).utcOffset() ||
  3345. this.utcOffset() > this.clone().month(5).utcOffset());
  3346. },
  3347. day : function (input) {
  3348. var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
  3349. if (input != null) {
  3350. input = parseWeekday(input, this.localeData());
  3351. return this.add(input - day, 'd');
  3352. } else {
  3353. return day;
  3354. }
  3355. },
  3356. month : makeAccessor('Month', true),
  3357. startOf : function (units) {
  3358. units = normalizeUnits(units);
  3359. // the following switch intentionally omits break keywords
  3360. // to utilize falling through the cases.
  3361. switch (units) {
  3362. case 'year':
  3363. this.month(0);
  3364. /* falls through */
  3365. case 'quarter':
  3366. case 'month':
  3367. this.date(1);
  3368. /* falls through */
  3369. case 'week':
  3370. case 'isoWeek':
  3371. case 'day':
  3372. this.hours(0);
  3373. /* falls through */
  3374. case 'hour':
  3375. this.minutes(0);
  3376. /* falls through */
  3377. case 'minute':
  3378. this.seconds(0);
  3379. /* falls through */
  3380. case 'second':
  3381. this.milliseconds(0);
  3382. /* falls through */
  3383. }
  3384. // weeks are a special case
  3385. if (units === 'week') {
  3386. this.weekday(0);
  3387. } else if (units === 'isoWeek') {
  3388. this.isoWeekday(1);
  3389. }
  3390. // quarters are also special
  3391. if (units === 'quarter') {
  3392. this.month(Math.floor(this.month() / 3) * 3);
  3393. }
  3394. return this;
  3395. },
  3396. endOf: function (units) {
  3397. units = normalizeUnits(units);
  3398. if (units === undefined || units === 'millisecond') {
  3399. return this;
  3400. }
  3401. return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
  3402. },
  3403. isAfter: function (input, units) {
  3404. var inputMs;
  3405. units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond');
  3406. if (units === 'millisecond') {
  3407. input = moment.isMoment(input) ? input : moment(input);
  3408. return +this > +input;
  3409. } else {
  3410. inputMs = moment.isMoment(input) ? +input : +moment(input);
  3411. return inputMs < +this.clone().startOf(units);
  3412. }
  3413. },
  3414. isBefore: function (input, units) {
  3415. var inputMs;
  3416. units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond');
  3417. if (units === 'millisecond') {
  3418. input = moment.isMoment(input) ? input : moment(input);
  3419. return +this < +input;
  3420. } else {
  3421. inputMs = moment.isMoment(input) ? +input : +moment(input);
  3422. return +this.clone().endOf(units) < inputMs;
  3423. }
  3424. },
  3425. isBetween: function (from, to, units) {
  3426. return this.isAfter(from, units) && this.isBefore(to, units);
  3427. },
  3428. isSame: function (input, units) {
  3429. var inputMs;
  3430. units = normalizeUnits(units || 'millisecond');
  3431. if (units === 'millisecond') {
  3432. input = moment.isMoment(input) ? input : moment(input);
  3433. return +this === +input;
  3434. } else {
  3435. inputMs = +moment(input);
  3436. return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units));
  3437. }
  3438. },
  3439. min: deprecate(
  3440. 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548',
  3441. function (other) {
  3442. other = moment.apply(null, arguments);
  3443. return other < this ? this : other;
  3444. }
  3445. ),
  3446. max: deprecate(
  3447. 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548',
  3448. function (other) {
  3449. other = moment.apply(null, arguments);
  3450. return other > this ? this : other;
  3451. }
  3452. ),
  3453. zone : deprecate(
  3454. 'moment().zone is deprecated, use moment().utcOffset instead. ' +
  3455. 'https://github.com/moment/moment/issues/1779',
  3456. function (input, keepLocalTime) {
  3457. if (input != null) {
  3458. if (typeof input !== 'string') {
  3459. input = -input;
  3460. }
  3461. this.utcOffset(input, keepLocalTime);
  3462. return this;
  3463. } else {
  3464. return -this.utcOffset();
  3465. }
  3466. }
  3467. ),
  3468. // keepLocalTime = true means only change the timezone, without
  3469. // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
  3470. // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
  3471. // +0200, so we adjust the time as needed, to be valid.
  3472. //
  3473. // Keeping the time actually adds/subtracts (one hour)
  3474. // from the actual represented time. That is why we call updateOffset
  3475. // a second time. In case it wants us to change the offset again
  3476. // _changeInProgress == true case, then we have to adjust, because
  3477. // there is no such time in the given timezone.
  3478. utcOffset : function (input, keepLocalTime) {
  3479. var offset = this._offset || 0,
  3480. localAdjust;
  3481. if (input != null) {
  3482. if (typeof input === 'string') {
  3483. input = utcOffsetFromString(input);
  3484. }
  3485. if (Math.abs(input) < 16) {
  3486. input = input * 60;
  3487. }
  3488. if (!this._isUTC && keepLocalTime) {
  3489. localAdjust = this._dateUtcOffset();
  3490. }
  3491. this._offset = input;
  3492. this._isUTC = true;
  3493. if (localAdjust != null) {
  3494. this.add(localAdjust, 'm');
  3495. }
  3496. if (offset !== input) {
  3497. if (!keepLocalTime || this._changeInProgress) {
  3498. addOrSubtractDurationFromMoment(this,
  3499. moment.duration(input - offset, 'm'), 1, false);
  3500. } else if (!this._changeInProgress) {
  3501. this._changeInProgress = true;
  3502. moment.updateOffset(this, true);
  3503. this._changeInProgress = null;
  3504. }
  3505. }
  3506. return this;
  3507. } else {
  3508. return this._isUTC ? offset : this._dateUtcOffset();
  3509. }
  3510. },
  3511. isLocal : function () {
  3512. return !this._isUTC;
  3513. },
  3514. isUtcOffset : function () {
  3515. return this._isUTC;
  3516. },
  3517. isUtc : function () {
  3518. return this._isUTC && this._offset === 0;
  3519. },
  3520. zoneAbbr : function () {
  3521. return this._isUTC ? 'UTC' : '';
  3522. },
  3523. zoneName : function () {
  3524. return this._isUTC ? 'Coordinated Universal Time' : '';
  3525. },
  3526. parseZone : function () {
  3527. if (this._tzm) {
  3528. this.utcOffset(this._tzm);
  3529. } else if (typeof this._i === 'string') {
  3530. this.utcOffset(utcOffsetFromString(this._i));
  3531. }
  3532. return this;
  3533. },
  3534. hasAlignedHourOffset : function (input) {
  3535. if (!input) {
  3536. input = 0;
  3537. }
  3538. else {
  3539. input = moment(input).utcOffset();
  3540. }
  3541. return (this.utcOffset() - input) % 60 === 0;
  3542. },
  3543. daysInMonth : function () {
  3544. return daysInMonth(this.year(), this.month());
  3545. },
  3546. dayOfYear : function (input) {
  3547. var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1;
  3548. return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
  3549. },
  3550. quarter : function (input) {
  3551. return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
  3552. },
  3553. weekYear : function (input) {
  3554. var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year;
  3555. return input == null ? year : this.add((input - year), 'y');
  3556. },
  3557. isoWeekYear : function (input) {
  3558. var year = weekOfYear(this, 1, 4).year;
  3559. return input == null ? year : this.add((input - year), 'y');
  3560. },
  3561. week : function (input) {
  3562. var week = this.localeData().week(this);
  3563. return input == null ? week : this.add((input - week) * 7, 'd');
  3564. },
  3565. isoWeek : function (input) {
  3566. var week = weekOfYear(this, 1, 4).week;
  3567. return input == null ? week : this.add((input - week) * 7, 'd');
  3568. },
  3569. weekday : function (input) {
  3570. var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
  3571. return input == null ? weekday : this.add(input - weekday, 'd');
  3572. },
  3573. isoWeekday : function (input) {
  3574. // behaves the same as moment#day except
  3575. // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
  3576. // as a setter, sunday should belong to the previous week.
  3577. return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
  3578. },
  3579. isoWeeksInYear : function () {
  3580. return weeksInYear(this.year(), 1, 4);
  3581. },
  3582. weeksInYear : function () {
  3583. var weekInfo = this.localeData()._week;
  3584. return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
  3585. },
  3586. get : function (units) {
  3587. units = normalizeUnits(units);
  3588. return this[units]();
  3589. },
  3590. set : function (units, value) {
  3591. var unit;
  3592. if (typeof units === 'object') {
  3593. for (unit in units) {
  3594. this.set(unit, units[unit]);
  3595. }
  3596. }
  3597. else {
  3598. units = normalizeUnits(units);
  3599. if (typeof this[units] === 'function') {
  3600. this[units](value);
  3601. }
  3602. }
  3603. return this;
  3604. },
  3605. // If passed a locale key, it will set the locale for this
  3606. // instance. Otherwise, it will return the locale configuration
  3607. // variables for this instance.
  3608. locale : function (key) {
  3609. var newLocaleData;
  3610. if (key === undefined) {
  3611. return this._locale._abbr;
  3612. } else {
  3613. newLocaleData = moment.localeData(key);
  3614. if (newLocaleData != null) {
  3615. this._locale = newLocaleData;
  3616. }
  3617. return this;
  3618. }
  3619. },
  3620. lang : deprecate(
  3621. 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
  3622. function (key) {
  3623. if (key === undefined) {
  3624. return this.localeData();
  3625. } else {
  3626. return this.locale(key);
  3627. }
  3628. }
  3629. ),
  3630. localeData : function () {
  3631. return this._locale;
  3632. },
  3633. _dateUtcOffset : function () {
  3634. // On Firefox.24 Date#getTimezoneOffset returns a floating point.
  3635. // https://github.com/moment/moment/pull/1871
  3636. return -Math.round(this._d.getTimezoneOffset() / 15) * 15;
  3637. }
  3638. });
  3639. function rawMonthSetter(mom, value) {
  3640. var dayOfMonth;
  3641. // TODO: Move this out of here!
  3642. if (typeof value === 'string') {
  3643. value = mom.localeData().monthsParse(value);
  3644. // TODO: Another silent failure?
  3645. if (typeof value !== 'number') {
  3646. return mom;
  3647. }
  3648. }
  3649. dayOfMonth = Math.min(mom.date(),
  3650. daysInMonth(mom.year(), value));
  3651. mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
  3652. return mom;
  3653. }
  3654. function rawGetter(mom, unit) {
  3655. return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]();
  3656. }
  3657. function rawSetter(mom, unit, value) {
  3658. if (unit === 'Month') {
  3659. return rawMonthSetter(mom, value);
  3660. } else {
  3661. return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
  3662. }
  3663. }
  3664. function makeAccessor(unit, keepTime) {
  3665. return function (value) {
  3666. if (value != null) {
  3667. rawSetter(this, unit, value);
  3668. moment.updateOffset(this, keepTime);
  3669. return this;
  3670. } else {
  3671. return rawGetter(this, unit);
  3672. }
  3673. };
  3674. }
  3675. moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false);
  3676. moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false);
  3677. moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false);
  3678. // Setting the hour should keep the time, because the user explicitly
  3679. // specified which hour he wants. So trying to maintain the same hour (in
  3680. // a new timezone) makes sense. Adding/subtracting hours does not follow
  3681. // this rule.
  3682. moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true);
  3683. // moment.fn.month is defined separately
  3684. moment.fn.date = makeAccessor('Date', true);
  3685. moment.fn.dates = deprecate('dates accessor is deprecated. Use date instead.', makeAccessor('Date', true));
  3686. moment.fn.year = makeAccessor('FullYear', true);
  3687. moment.fn.years = deprecate('years accessor is deprecated. Use year instead.', makeAccessor('FullYear', true));
  3688. // add plural methods
  3689. moment.fn.days = moment.fn.day;
  3690. moment.fn.months = moment.fn.month;
  3691. moment.fn.weeks = moment.fn.week;
  3692. moment.fn.isoWeeks = moment.fn.isoWeek;
  3693. moment.fn.quarters = moment.fn.quarter;
  3694. // add aliased format methods
  3695. moment.fn.toJSON = moment.fn.toISOString;
  3696. // alias isUtc for dev-friendliness
  3697. moment.fn.isUTC = moment.fn.isUtc;
  3698. /************************************
  3699. Duration Prototype
  3700. ************************************/
  3701. function daysToYears (days) {
  3702. // 400 years have 146097 days (taking into account leap year rules)
  3703. return days * 400 / 146097;
  3704. }
  3705. function yearsToDays (years) {
  3706. // years * 365 + absRound(years / 4) -
  3707. // absRound(years / 100) + absRound(years / 400);
  3708. return years * 146097 / 400;
  3709. }
  3710. extend(moment.duration.fn = Duration.prototype, {
  3711. _bubble : function () {
  3712. var milliseconds = this._milliseconds,
  3713. days = this._days,
  3714. months = this._months,
  3715. data = this._data,
  3716. seconds, minutes, hours, years = 0;
  3717. // The following code bubbles up values, see the tests for
  3718. // examples of what that means.
  3719. data.milliseconds = milliseconds % 1000;
  3720. seconds = absRound(milliseconds / 1000);
  3721. data.seconds = seconds % 60;
  3722. minutes = absRound(seconds / 60);
  3723. data.minutes = minutes % 60;
  3724. hours = absRound(minutes / 60);
  3725. data.hours = hours % 24;
  3726. days += absRound(hours / 24);
  3727. // Accurately convert days to years, assume start from year 0.
  3728. years = absRound(daysToYears(days));
  3729. days -= absRound(yearsToDays(years));
  3730. // 30 days to a month
  3731. // TODO (iskren): Use anchor date (like 1st Jan) to compute this.
  3732. months += absRound(days / 30);
  3733. days %= 30;
  3734. // 12 months -> 1 year
  3735. years += absRound(months / 12);
  3736. months %= 12;
  3737. data.days = days;
  3738. data.months = months;
  3739. data.years = years;
  3740. },
  3741. abs : function () {
  3742. this._milliseconds = Math.abs(this._milliseconds);
  3743. this._days = Math.abs(this._days);
  3744. this._months = Math.abs(this._months);
  3745. this._data.milliseconds = Math.abs(this._data.milliseconds);
  3746. this._data.seconds = Math.abs(this._data.seconds);
  3747. this._data.minutes = Math.abs(this._data.minutes);
  3748. this._data.hours = Math.abs(this._data.hours);
  3749. this._data.months = Math.abs(this._data.months);
  3750. this._data.years = Math.abs(this._data.years);
  3751. return this;
  3752. },
  3753. weeks : function () {
  3754. return absRound(this.days() / 7);
  3755. },
  3756. valueOf : function () {
  3757. return this._milliseconds +
  3758. this._days * 864e5 +
  3759. (this._months % 12) * 2592e6 +
  3760. toInt(this._months / 12) * 31536e6;
  3761. },
  3762. humanize : function (withSuffix) {
  3763. var output = relativeTime(this, !withSuffix, this.localeData());
  3764. if (withSuffix) {
  3765. output = this.localeData().pastFuture(+this, output);
  3766. }
  3767. return this.localeData().postformat(output);
  3768. },
  3769. add : function (input, val) {
  3770. // supports only 2.0-style add(1, 's') or add(moment)
  3771. var dur = moment.duration(input, val);
  3772. this._milliseconds += dur._milliseconds;
  3773. this._days += dur._days;
  3774. this._months += dur._months;
  3775. this._bubble();
  3776. return this;
  3777. },
  3778. subtract : function (input, val) {
  3779. var dur = moment.duration(input, val);
  3780. this._milliseconds -= dur._milliseconds;
  3781. this._days -= dur._days;
  3782. this._months -= dur._months;
  3783. this._bubble();
  3784. return this;
  3785. },
  3786. get : function (units) {
  3787. units = normalizeUnits(units);
  3788. return this[units.toLowerCase() + 's']();
  3789. },
  3790. as : function (units) {
  3791. var days, months;
  3792. units = normalizeUnits(units);
  3793. if (units === 'month' || units === 'year') {
  3794. days = this._days + this._milliseconds / 864e5;
  3795. months = this._months + daysToYears(days) * 12;
  3796. return units === 'month' ? months : months / 12;
  3797. } else {
  3798. // handle milliseconds separately because of floating point math errors (issue #1867)
  3799. days = this._days + Math.round(yearsToDays(this._months / 12));
  3800. switch (units) {
  3801. case 'week': return days / 7 + this._milliseconds / 6048e5;
  3802. case 'day': return days + this._milliseconds / 864e5;
  3803. case 'hour': return days * 24 + this._milliseconds / 36e5;
  3804. case 'minute': return days * 24 * 60 + this._milliseconds / 6e4;
  3805. case 'second': return days * 24 * 60 * 60 + this._milliseconds / 1000;
  3806. // Math.floor prevents floating point math errors here
  3807. case 'millisecond': return Math.floor(days * 24 * 60 * 60 * 1000) + this._milliseconds;
  3808. default: throw new Error('Unknown unit ' + units);
  3809. }
  3810. }
  3811. },
  3812. lang : moment.fn.lang,
  3813. locale : moment.fn.locale,
  3814. toIsoString : deprecate(
  3815. 'toIsoString() is deprecated. Please use toISOString() instead ' +
  3816. '(notice the capitals)',
  3817. function () {
  3818. return this.toISOString();
  3819. }
  3820. ),
  3821. toISOString : function () {
  3822. // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
  3823. var years = Math.abs(this.years()),
  3824. months = Math.abs(this.months()),
  3825. days = Math.abs(this.days()),
  3826. hours = Math.abs(this.hours()),
  3827. minutes = Math.abs(this.minutes()),
  3828. seconds = Math.abs(this.seconds() + this.milliseconds() / 1000);
  3829. if (!this.asSeconds()) {
  3830. // this is the same as C#'s (Noda) and python (isodate)...
  3831. // but not other JS (goog.date)
  3832. return 'P0D';
  3833. }
  3834. return (this.asSeconds() < 0 ? '-' : '') +
  3835. 'P' +
  3836. (years ? years + 'Y' : '') +
  3837. (months ? months + 'M' : '') +
  3838. (days ? days + 'D' : '') +
  3839. ((hours || minutes || seconds) ? 'T' : '') +
  3840. (hours ? hours + 'H' : '') +
  3841. (minutes ? minutes + 'M' : '') +
  3842. (seconds ? seconds + 'S' : '');
  3843. },
  3844. localeData : function () {
  3845. return this._locale;
  3846. },
  3847. toJSON : function () {
  3848. return this.toISOString();
  3849. }
  3850. });
  3851. moment.duration.fn.toString = moment.duration.fn.toISOString;
  3852. function makeDurationGetter(name) {
  3853. moment.duration.fn[name] = function () {
  3854. return this._data[name];
  3855. };
  3856. }
  3857. for (i in unitMillisecondFactors) {
  3858. if (hasOwnProp(unitMillisecondFactors, i)) {
  3859. makeDurationGetter(i.toLowerCase());
  3860. }
  3861. }
  3862. moment.duration.fn.asMilliseconds = function () {
  3863. return this.as('ms');
  3864. };
  3865. moment.duration.fn.asSeconds = function () {
  3866. return this.as('s');
  3867. };
  3868. moment.duration.fn.asMinutes = function () {
  3869. return this.as('m');
  3870. };
  3871. moment.duration.fn.asHours = function () {
  3872. return this.as('h');
  3873. };
  3874. moment.duration.fn.asDays = function () {
  3875. return this.as('d');
  3876. };
  3877. moment.duration.fn.asWeeks = function () {
  3878. return this.as('weeks');
  3879. };
  3880. moment.duration.fn.asMonths = function () {
  3881. return this.as('M');
  3882. };
  3883. moment.duration.fn.asYears = function () {
  3884. return this.as('y');
  3885. };
  3886. /************************************
  3887. Default Locale
  3888. ************************************/
  3889. // Set default locale, other locale will inherit from English.
  3890. moment.locale('en', {
  3891. ordinalParse: /\d{1,2}(th|st|nd|rd)/,
  3892. ordinal : function (number) {
  3893. var b = number % 10,
  3894. output = (toInt(number % 100 / 10) === 1) ? 'th' :
  3895. (b === 1) ? 'st' :
  3896. (b === 2) ? 'nd' :
  3897. (b === 3) ? 'rd' : 'th';
  3898. return number + output;
  3899. }
  3900. });
  3901. /* EMBED_LOCALES */
  3902. /************************************
  3903. Exposing Moment
  3904. ************************************/
  3905. function makeGlobal(shouldDeprecate) {
  3906. /*global ender:false */
  3907. if (typeof ender !== 'undefined') {
  3908. return;
  3909. }
  3910. oldGlobalMoment = globalScope.moment;
  3911. if (shouldDeprecate) {
  3912. globalScope.moment = deprecate(
  3913. 'Accessing Moment through the global scope is ' +
  3914. 'deprecated, and will be removed in an upcoming ' +
  3915. 'release.',
  3916. moment);
  3917. } else {
  3918. globalScope.moment = moment;
  3919. }
  3920. }
  3921. // CommonJS module is defined
  3922. if (hasModule) {
  3923. module.exports = moment;
  3924. } else if (true) {
  3925. !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
  3926. if (module.config && module.config() && module.config().noGlobal === true) {
  3927. // release the global variable
  3928. globalScope.moment = oldGlobalMoment;
  3929. }
  3930. return moment;
  3931. }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  3932. makeGlobal(true);
  3933. } else {
  3934. makeGlobal();
  3935. }
  3936. }).call(this);
  3937. /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()), __webpack_require__(5)(module)))
  3938. /***/ },
  3939. /* 4 */
  3940. /***/ function(module, exports, __webpack_require__) {
  3941. function webpackContext(req) {
  3942. throw new Error("Cannot find module '" + req + "'.");
  3943. }
  3944. webpackContext.keys = function() { return []; };
  3945. webpackContext.resolve = webpackContext;
  3946. module.exports = webpackContext;
  3947. webpackContext.id = 4;
  3948. /***/ },
  3949. /* 5 */
  3950. /***/ function(module, exports, __webpack_require__) {
  3951. module.exports = function(module) {
  3952. if(!module.webpackPolyfill) {
  3953. module.deprecate = function() {};
  3954. module.paths = [];
  3955. // module.parent = undefined by default
  3956. module.children = [];
  3957. module.webpackPolyfill = 1;
  3958. }
  3959. return module;
  3960. }
  3961. /***/ },
  3962. /* 6 */
  3963. /***/ function(module, exports, __webpack_require__) {
  3964. // DOM utility methods
  3965. /**
  3966. * this prepares the JSON container for allocating SVG elements
  3967. * @param JSONcontainer
  3968. * @private
  3969. */
  3970. exports.prepareElements = function(JSONcontainer) {
  3971. // cleanup the redundant svgElements;
  3972. for (var elementType in JSONcontainer) {
  3973. if (JSONcontainer.hasOwnProperty(elementType)) {
  3974. JSONcontainer[elementType].redundant = JSONcontainer[elementType].used;
  3975. JSONcontainer[elementType].used = [];
  3976. }
  3977. }
  3978. };
  3979. /**
  3980. * this cleans up all the unused SVG elements. By asking for the parentNode, we only need to supply the JSON container from
  3981. * which to remove the redundant elements.
  3982. *
  3983. * @param JSONcontainer
  3984. * @private
  3985. */
  3986. exports.cleanupElements = function(JSONcontainer) {
  3987. // cleanup the redundant svgElements;
  3988. for (var elementType in JSONcontainer) {
  3989. if (JSONcontainer.hasOwnProperty(elementType)) {
  3990. if (JSONcontainer[elementType].redundant) {
  3991. for (var i = 0; i < JSONcontainer[elementType].redundant.length; i++) {
  3992. JSONcontainer[elementType].redundant[i].parentNode.removeChild(JSONcontainer[elementType].redundant[i]);
  3993. }
  3994. JSONcontainer[elementType].redundant = [];
  3995. }
  3996. }
  3997. }
  3998. };
  3999. /**
  4000. * Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer
  4001. * the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this.
  4002. *
  4003. * @param elementType
  4004. * @param JSONcontainer
  4005. * @param svgContainer
  4006. * @returns {*}
  4007. * @private
  4008. */
  4009. exports.getSVGElement = function (elementType, JSONcontainer, svgContainer) {
  4010. var element;
  4011. // allocate SVG element, if it doesnt yet exist, create one.
  4012. if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
  4013. // check if there is an redundant element
  4014. if (JSONcontainer[elementType].redundant.length > 0) {
  4015. element = JSONcontainer[elementType].redundant[0];
  4016. JSONcontainer[elementType].redundant.shift();
  4017. }
  4018. else {
  4019. // create a new element and add it to the SVG
  4020. element = document.createElementNS('http://www.w3.org/2000/svg', elementType);
  4021. svgContainer.appendChild(element);
  4022. }
  4023. }
  4024. else {
  4025. // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it.
  4026. element = document.createElementNS('http://www.w3.org/2000/svg', elementType);
  4027. JSONcontainer[elementType] = {used: [], redundant: []};
  4028. svgContainer.appendChild(element);
  4029. }
  4030. JSONcontainer[elementType].used.push(element);
  4031. return element;
  4032. };
  4033. /**
  4034. * Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer
  4035. * the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this.
  4036. *
  4037. * @param elementType
  4038. * @param JSONcontainer
  4039. * @param DOMContainer
  4040. * @returns {*}
  4041. * @private
  4042. */
  4043. exports.getDOMElement = function (elementType, JSONcontainer, DOMContainer, insertBefore) {
  4044. var element;
  4045. // allocate DOM element, if it doesnt yet exist, create one.
  4046. if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
  4047. // check if there is an redundant element
  4048. if (JSONcontainer[elementType].redundant.length > 0) {
  4049. element = JSONcontainer[elementType].redundant[0];
  4050. JSONcontainer[elementType].redundant.shift();
  4051. }
  4052. else {
  4053. // create a new element and add it to the SVG
  4054. element = document.createElement(elementType);
  4055. if (insertBefore !== undefined) {
  4056. DOMContainer.insertBefore(element, insertBefore);
  4057. }
  4058. else {
  4059. DOMContainer.appendChild(element);
  4060. }
  4061. }
  4062. }
  4063. else {
  4064. // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it.
  4065. element = document.createElement(elementType);
  4066. JSONcontainer[elementType] = {used: [], redundant: []};
  4067. if (insertBefore !== undefined) {
  4068. DOMContainer.insertBefore(element, insertBefore);
  4069. }
  4070. else {
  4071. DOMContainer.appendChild(element);
  4072. }
  4073. }
  4074. JSONcontainer[elementType].used.push(element);
  4075. return element;
  4076. };
  4077. /**
  4078. * draw a point object. this is a seperate function because it can also be called by the legend.
  4079. * The reason the JSONcontainer and the target SVG svgContainer have to be supplied is so the legend can use these functions
  4080. * as well.
  4081. *
  4082. * @param x
  4083. * @param y
  4084. * @param group
  4085. * @param JSONcontainer
  4086. * @param svgContainer
  4087. * @returns {*}
  4088. */
  4089. exports.drawPoint = function(x, y, group, JSONcontainer, svgContainer) {
  4090. var point;
  4091. if (group.options.drawPoints.style == 'circle') {
  4092. point = exports.getSVGElement('circle',JSONcontainer,svgContainer);
  4093. point.setAttributeNS(null, "cx", x);
  4094. point.setAttributeNS(null, "cy", y);
  4095. point.setAttributeNS(null, "r", 0.5 * group.options.drawPoints.size);
  4096. }
  4097. else {
  4098. point = exports.getSVGElement('rect',JSONcontainer,svgContainer);
  4099. point.setAttributeNS(null, "x", x - 0.5*group.options.drawPoints.size);
  4100. point.setAttributeNS(null, "y", y - 0.5*group.options.drawPoints.size);
  4101. point.setAttributeNS(null, "width", group.options.drawPoints.size);
  4102. point.setAttributeNS(null, "height", group.options.drawPoints.size);
  4103. }
  4104. if(group.options.drawPoints.styles !== undefined) {
  4105. point.setAttributeNS(null, "style", group.group.options.drawPoints.styles);
  4106. }
  4107. point.setAttributeNS(null, "class", group.className + " point");
  4108. return point;
  4109. };
  4110. /**
  4111. * draw a bar SVG element centered on the X coordinate
  4112. *
  4113. * @param x
  4114. * @param y
  4115. * @param className
  4116. */
  4117. exports.drawBar = function (x, y, width, height, className, JSONcontainer, svgContainer) {
  4118. if (height != 0) {
  4119. if (height < 0) {
  4120. height *= -1;
  4121. y -= height;
  4122. }
  4123. var rect = exports.getSVGElement('rect',JSONcontainer, svgContainer);
  4124. rect.setAttributeNS(null, "x", x - 0.5 * width);
  4125. rect.setAttributeNS(null, "y", y);
  4126. rect.setAttributeNS(null, "width", width);
  4127. rect.setAttributeNS(null, "height", height);
  4128. rect.setAttributeNS(null, "class", className);
  4129. }
  4130. };
  4131. /***/ },
  4132. /* 7 */
  4133. /***/ function(module, exports, __webpack_require__) {
  4134. var util = __webpack_require__(1);
  4135. var Queue = __webpack_require__(8);
  4136. /**
  4137. * DataSet
  4138. *
  4139. * Usage:
  4140. * var dataSet = new DataSet({
  4141. * fieldId: '_id',
  4142. * type: {
  4143. * // ...
  4144. * }
  4145. * });
  4146. *
  4147. * dataSet.add(item);
  4148. * dataSet.add(data);
  4149. * dataSet.update(item);
  4150. * dataSet.update(data);
  4151. * dataSet.remove(id);
  4152. * dataSet.remove(ids);
  4153. * var data = dataSet.get();
  4154. * var data = dataSet.get(id);
  4155. * var data = dataSet.get(ids);
  4156. * var data = dataSet.get(ids, options, data);
  4157. * dataSet.clear();
  4158. *
  4159. * A data set can:
  4160. * - add/remove/update data
  4161. * - gives triggers upon changes in the data
  4162. * - can import/export data in various data formats
  4163. *
  4164. * @param {Array | DataTable} [data] Optional array with initial data
  4165. * @param {Object} [options] Available options:
  4166. * {String} fieldId Field name of the id in the
  4167. * items, 'id' by default.
  4168. * {Object.<String, String} type
  4169. * A map with field names as key,
  4170. * and the field type as value.
  4171. * {Object} queue Queue changes to the DataSet,
  4172. * flush them all at once.
  4173. * Queue options:
  4174. * - {number} delay Delay in ms, null by default
  4175. * - {number} max Maximum number of entries in the queue, Infinity by default
  4176. * @constructor DataSet
  4177. */
  4178. // TODO: add a DataSet constructor DataSet(data, options)
  4179. function DataSet (data, options) {
  4180. // correctly read optional arguments
  4181. if (data && !Array.isArray(data) && !util.isDataTable(data)) {
  4182. options = data;
  4183. data = null;
  4184. }
  4185. this._options = options || {};
  4186. this._data = {}; // map with data indexed by id
  4187. this.length = 0; // number of items in the DataSet
  4188. this._fieldId = this._options.fieldId || 'id'; // name of the field containing id
  4189. this._type = {}; // internal field types (NOTE: this can differ from this._options.type)
  4190. // all variants of a Date are internally stored as Date, so we can convert
  4191. // from everything to everything (also from ISODate to Number for example)
  4192. if (this._options.type) {
  4193. for (var field in this._options.type) {
  4194. if (this._options.type.hasOwnProperty(field)) {
  4195. var value = this._options.type[field];
  4196. if (value == 'Date' || value == 'ISODate' || value == 'ASPDate') {
  4197. this._type[field] = 'Date';
  4198. }
  4199. else {
  4200. this._type[field] = value;
  4201. }
  4202. }
  4203. }
  4204. }
  4205. // TODO: deprecated since version 1.1.1 (or 2.0.0?)
  4206. if (this._options.convert) {
  4207. throw new Error('Option "convert" is deprecated. Use "type" instead.');
  4208. }
  4209. this._subscribers = {}; // event subscribers
  4210. // add initial data when provided
  4211. if (data) {
  4212. this.add(data);
  4213. }
  4214. this.setOptions(options);
  4215. }
  4216. /**
  4217. * @param {Object} [options] Available options:
  4218. * {Object} queue Queue changes to the DataSet,
  4219. * flush them all at once.
  4220. * Queue options:
  4221. * - {number} delay Delay in ms, null by default
  4222. * - {number} max Maximum number of entries in the queue, Infinity by default
  4223. * @param options
  4224. */
  4225. DataSet.prototype.setOptions = function(options) {
  4226. if (options && options.queue !== undefined) {
  4227. if (options.queue === false) {
  4228. // delete queue if loaded
  4229. if (this._queue) {
  4230. this._queue.destroy();
  4231. delete this._queue;
  4232. }
  4233. }
  4234. else {
  4235. // create queue and update its options
  4236. if (!this._queue) {
  4237. this._queue = Queue.extend(this, {
  4238. replace: ['add', 'update', 'remove']
  4239. });
  4240. }
  4241. if (typeof options.queue === 'object') {
  4242. this._queue.setOptions(options.queue);
  4243. }
  4244. }
  4245. }
  4246. };
  4247. /**
  4248. * Subscribe to an event, add an event listener
  4249. * @param {String} event Event name. Available events: 'put', 'update',
  4250. * 'remove'
  4251. * @param {function} callback Callback method. Called with three parameters:
  4252. * {String} event
  4253. * {Object | null} params
  4254. * {String | Number} senderId
  4255. */
  4256. DataSet.prototype.on = function(event, callback) {
  4257. var subscribers = this._subscribers[event];
  4258. if (!subscribers) {
  4259. subscribers = [];
  4260. this._subscribers[event] = subscribers;
  4261. }
  4262. subscribers.push({
  4263. callback: callback
  4264. });
  4265. };
  4266. // TODO: make this function deprecated (replaced with `on` since version 0.5)
  4267. DataSet.prototype.subscribe = DataSet.prototype.on;
  4268. /**
  4269. * Unsubscribe from an event, remove an event listener
  4270. * @param {String} event
  4271. * @param {function} callback
  4272. */
  4273. DataSet.prototype.off = function(event, callback) {
  4274. var subscribers = this._subscribers[event];
  4275. if (subscribers) {
  4276. this._subscribers[event] = subscribers.filter(function (listener) {
  4277. return (listener.callback != callback);
  4278. });
  4279. }
  4280. };
  4281. // TODO: make this function deprecated (replaced with `on` since version 0.5)
  4282. DataSet.prototype.unsubscribe = DataSet.prototype.off;
  4283. /**
  4284. * Trigger an event
  4285. * @param {String} event
  4286. * @param {Object | null} params
  4287. * @param {String} [senderId] Optional id of the sender.
  4288. * @private
  4289. */
  4290. DataSet.prototype._trigger = function (event, params, senderId) {
  4291. if (event == '*') {
  4292. throw new Error('Cannot trigger event *');
  4293. }
  4294. var subscribers = [];
  4295. if (event in this._subscribers) {
  4296. subscribers = subscribers.concat(this._subscribers[event]);
  4297. }
  4298. if ('*' in this._subscribers) {
  4299. subscribers = subscribers.concat(this._subscribers['*']);
  4300. }
  4301. for (var i = 0; i < subscribers.length; i++) {
  4302. var subscriber = subscribers[i];
  4303. if (subscriber.callback) {
  4304. subscriber.callback(event, params, senderId || null);
  4305. }
  4306. }
  4307. };
  4308. /**
  4309. * Add data.
  4310. * Adding an item will fail when there already is an item with the same id.
  4311. * @param {Object | Array | DataTable} data
  4312. * @param {String} [senderId] Optional sender id
  4313. * @return {Array} addedIds Array with the ids of the added items
  4314. */
  4315. DataSet.prototype.add = function (data, senderId) {
  4316. var addedIds = [],
  4317. id,
  4318. me = this;
  4319. if (Array.isArray(data)) {
  4320. // Array
  4321. for (var i = 0, len = data.length; i < len; i++) {
  4322. id = me._addItem(data[i]);
  4323. addedIds.push(id);
  4324. }
  4325. }
  4326. else if (util.isDataTable(data)) {
  4327. // Google DataTable
  4328. var columns = this._getColumnNames(data);
  4329. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  4330. var item = {};
  4331. for (var col = 0, cols = columns.length; col < cols; col++) {
  4332. var field = columns[col];
  4333. item[field] = data.getValue(row, col);
  4334. }
  4335. id = me._addItem(item);
  4336. addedIds.push(id);
  4337. }
  4338. }
  4339. else if (data instanceof Object) {
  4340. // Single item
  4341. id = me._addItem(data);
  4342. addedIds.push(id);
  4343. }
  4344. else {
  4345. throw new Error('Unknown dataType');
  4346. }
  4347. if (addedIds.length) {
  4348. this._trigger('add', {items: addedIds}, senderId);
  4349. }
  4350. return addedIds;
  4351. };
  4352. /**
  4353. * Update existing items. When an item does not exist, it will be created
  4354. * @param {Object | Array | DataTable} data
  4355. * @param {String} [senderId] Optional sender id
  4356. * @return {Array} updatedIds The ids of the added or updated items
  4357. */
  4358. DataSet.prototype.update = function (data, senderId) {
  4359. var addedIds = [];
  4360. var updatedIds = [];
  4361. var updatedData = [];
  4362. var me = this;
  4363. var fieldId = me._fieldId;
  4364. var addOrUpdate = function (item) {
  4365. var id = item[fieldId];
  4366. if (me._data[id]) {
  4367. // update item
  4368. id = me._updateItem(item);
  4369. updatedIds.push(id);
  4370. updatedData.push(item);
  4371. }
  4372. else {
  4373. // add new item
  4374. id = me._addItem(item);
  4375. addedIds.push(id);
  4376. }
  4377. };
  4378. if (Array.isArray(data)) {
  4379. // Array
  4380. for (var i = 0, len = data.length; i < len; i++) {
  4381. addOrUpdate(data[i]);
  4382. }
  4383. }
  4384. else if (util.isDataTable(data)) {
  4385. // Google DataTable
  4386. var columns = this._getColumnNames(data);
  4387. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  4388. var item = {};
  4389. for (var col = 0, cols = columns.length; col < cols; col++) {
  4390. var field = columns[col];
  4391. item[field] = data.getValue(row, col);
  4392. }
  4393. addOrUpdate(item);
  4394. }
  4395. }
  4396. else if (data instanceof Object) {
  4397. // Single item
  4398. addOrUpdate(data);
  4399. }
  4400. else {
  4401. throw new Error('Unknown dataType');
  4402. }
  4403. if (addedIds.length) {
  4404. this._trigger('add', {items: addedIds}, senderId);
  4405. }
  4406. if (updatedIds.length) {
  4407. this._trigger('update', {items: updatedIds, data: updatedData}, senderId);
  4408. }
  4409. return addedIds.concat(updatedIds);
  4410. };
  4411. /**
  4412. * Get a data item or multiple items.
  4413. *
  4414. * Usage:
  4415. *
  4416. * get()
  4417. * get(options: Object)
  4418. * get(options: Object, data: Array | DataTable)
  4419. *
  4420. * get(id: Number | String)
  4421. * get(id: Number | String, options: Object)
  4422. * get(id: Number | String, options: Object, data: Array | DataTable)
  4423. *
  4424. * get(ids: Number[] | String[])
  4425. * get(ids: Number[] | String[], options: Object)
  4426. * get(ids: Number[] | String[], options: Object, data: Array | DataTable)
  4427. *
  4428. * Where:
  4429. *
  4430. * {Number | String} id The id of an item
  4431. * {Number[] | String{}} ids An array with ids of items
  4432. * {Object} options An Object with options. Available options:
  4433. * {String} [returnType] Type of data to be
  4434. * returned. Can be 'DataTable' or 'Array' (default)
  4435. * {Object.<String, String>} [type]
  4436. * {String[]} [fields] field names to be returned
  4437. * {function} [filter] filter items
  4438. * {String | function} [order] Order the items by
  4439. * a field name or custom sort function.
  4440. * {Array | DataTable} [data] If provided, items will be appended to this
  4441. * array or table. Required in case of Google
  4442. * DataTable.
  4443. *
  4444. * @throws Error
  4445. */
  4446. DataSet.prototype.get = function (args) {
  4447. var me = this;
  4448. // parse the arguments
  4449. var id, ids, options, data;
  4450. var firstType = util.getType(arguments[0]);
  4451. if (firstType == 'String' || firstType == 'Number') {
  4452. // get(id [, options] [, data])
  4453. id = arguments[0];
  4454. options = arguments[1];
  4455. data = arguments[2];
  4456. }
  4457. else if (firstType == 'Array') {
  4458. // get(ids [, options] [, data])
  4459. ids = arguments[0];
  4460. options = arguments[1];
  4461. data = arguments[2];
  4462. }
  4463. else {
  4464. // get([, options] [, data])
  4465. options = arguments[0];
  4466. data = arguments[1];
  4467. }
  4468. // determine the return type
  4469. var returnType;
  4470. if (options && options.returnType) {
  4471. var allowedValues = ["DataTable", "Array", "Object"];
  4472. returnType = allowedValues.indexOf(options.returnType) == -1 ? "Array" : options.returnType;
  4473. if (data && (returnType != util.getType(data))) {
  4474. throw new Error('Type of parameter "data" (' + util.getType(data) + ') ' +
  4475. 'does not correspond with specified options.type (' + options.type + ')');
  4476. }
  4477. if (returnType == 'DataTable' && !util.isDataTable(data)) {
  4478. throw new Error('Parameter "data" must be a DataTable ' +
  4479. 'when options.type is "DataTable"');
  4480. }
  4481. }
  4482. else if (data) {
  4483. returnType = (util.getType(data) == 'DataTable') ? 'DataTable' : 'Array';
  4484. }
  4485. else {
  4486. returnType = 'Array';
  4487. }
  4488. // build options
  4489. var type = options && options.type || this._options.type;
  4490. var filter = options && options.filter;
  4491. var items = [], item, itemId, i, len;
  4492. // convert items
  4493. if (id != undefined) {
  4494. // return a single item
  4495. item = me._getItem(id, type);
  4496. if (filter && !filter(item)) {
  4497. item = null;
  4498. }
  4499. }
  4500. else if (ids != undefined) {
  4501. // return a subset of items
  4502. for (i = 0, len = ids.length; i < len; i++) {
  4503. item = me._getItem(ids[i], type);
  4504. if (!filter || filter(item)) {
  4505. items.push(item);
  4506. }
  4507. }
  4508. }
  4509. else {
  4510. // return all items
  4511. for (itemId in this._data) {
  4512. if (this._data.hasOwnProperty(itemId)) {
  4513. item = me._getItem(itemId, type);
  4514. if (!filter || filter(item)) {
  4515. items.push(item);
  4516. }
  4517. }
  4518. }
  4519. }
  4520. // order the results
  4521. if (options && options.order && id == undefined) {
  4522. this._sort(items, options.order);
  4523. }
  4524. // filter fields of the items
  4525. if (options && options.fields) {
  4526. var fields = options.fields;
  4527. if (id != undefined) {
  4528. item = this._filterFields(item, fields);
  4529. }
  4530. else {
  4531. for (i = 0, len = items.length; i < len; i++) {
  4532. items[i] = this._filterFields(items[i], fields);
  4533. }
  4534. }
  4535. }
  4536. // return the results
  4537. if (returnType == 'DataTable') {
  4538. var columns = this._getColumnNames(data);
  4539. if (id != undefined) {
  4540. // append a single item to the data table
  4541. me._appendRow(data, columns, item);
  4542. }
  4543. else {
  4544. // copy the items to the provided data table
  4545. for (i = 0; i < items.length; i++) {
  4546. me._appendRow(data, columns, items[i]);
  4547. }
  4548. }
  4549. return data;
  4550. }
  4551. else if (returnType == "Object") {
  4552. var result = {};
  4553. for (i = 0; i < items.length; i++) {
  4554. result[items[i].id] = items[i];
  4555. }
  4556. return result;
  4557. }
  4558. else {
  4559. // return an array
  4560. if (id != undefined) {
  4561. // a single item
  4562. return item;
  4563. }
  4564. else {
  4565. // multiple items
  4566. if (data) {
  4567. // copy the items to the provided array
  4568. for (i = 0, len = items.length; i < len; i++) {
  4569. data.push(items[i]);
  4570. }
  4571. return data;
  4572. }
  4573. else {
  4574. // just return our array
  4575. return items;
  4576. }
  4577. }
  4578. }
  4579. };
  4580. /**
  4581. * Get ids of all items or from a filtered set of items.
  4582. * @param {Object} [options] An Object with options. Available options:
  4583. * {function} [filter] filter items
  4584. * {String | function} [order] Order the items by
  4585. * a field name or custom sort function.
  4586. * @return {Array} ids
  4587. */
  4588. DataSet.prototype.getIds = function (options) {
  4589. var data = this._data,
  4590. filter = options && options.filter,
  4591. order = options && options.order,
  4592. type = options && options.type || this._options.type,
  4593. i,
  4594. len,
  4595. id,
  4596. item,
  4597. items,
  4598. ids = [];
  4599. if (filter) {
  4600. // get filtered items
  4601. if (order) {
  4602. // create ordered list
  4603. items = [];
  4604. for (id in data) {
  4605. if (data.hasOwnProperty(id)) {
  4606. item = this._getItem(id, type);
  4607. if (filter(item)) {
  4608. items.push(item);
  4609. }
  4610. }
  4611. }
  4612. this._sort(items, order);
  4613. for (i = 0, len = items.length; i < len; i++) {
  4614. ids[i] = items[i][this._fieldId];
  4615. }
  4616. }
  4617. else {
  4618. // create unordered list
  4619. for (id in data) {
  4620. if (data.hasOwnProperty(id)) {
  4621. item = this._getItem(id, type);
  4622. if (filter(item)) {
  4623. ids.push(item[this._fieldId]);
  4624. }
  4625. }
  4626. }
  4627. }
  4628. }
  4629. else {
  4630. // get all items
  4631. if (order) {
  4632. // create an ordered list
  4633. items = [];
  4634. for (id in data) {
  4635. if (data.hasOwnProperty(id)) {
  4636. items.push(data[id]);
  4637. }
  4638. }
  4639. this._sort(items, order);
  4640. for (i = 0, len = items.length; i < len; i++) {
  4641. ids[i] = items[i][this._fieldId];
  4642. }
  4643. }
  4644. else {
  4645. // create unordered list
  4646. for (id in data) {
  4647. if (data.hasOwnProperty(id)) {
  4648. item = data[id];
  4649. ids.push(item[this._fieldId]);
  4650. }
  4651. }
  4652. }
  4653. }
  4654. return ids;
  4655. };
  4656. /**
  4657. * Returns the DataSet itself. Is overwritten for example by the DataView,
  4658. * which returns the DataSet it is connected to instead.
  4659. */
  4660. DataSet.prototype.getDataSet = function () {
  4661. return this;
  4662. };
  4663. /**
  4664. * Execute a callback function for every item in the dataset.
  4665. * @param {function} callback
  4666. * @param {Object} [options] Available options:
  4667. * {Object.<String, String>} [type]
  4668. * {String[]} [fields] filter fields
  4669. * {function} [filter] filter items
  4670. * {String | function} [order] Order the items by
  4671. * a field name or custom sort function.
  4672. */
  4673. DataSet.prototype.forEach = function (callback, options) {
  4674. var filter = options && options.filter,
  4675. type = options && options.type || this._options.type,
  4676. data = this._data,
  4677. item,
  4678. id;
  4679. if (options && options.order) {
  4680. // execute forEach on ordered list
  4681. var items = this.get(options);
  4682. for (var i = 0, len = items.length; i < len; i++) {
  4683. item = items[i];
  4684. id = item[this._fieldId];
  4685. callback(item, id);
  4686. }
  4687. }
  4688. else {
  4689. // unordered
  4690. for (id in data) {
  4691. if (data.hasOwnProperty(id)) {
  4692. item = this._getItem(id, type);
  4693. if (!filter || filter(item)) {
  4694. callback(item, id);
  4695. }
  4696. }
  4697. }
  4698. }
  4699. };
  4700. /**
  4701. * Map every item in the dataset.
  4702. * @param {function} callback
  4703. * @param {Object} [options] Available options:
  4704. * {Object.<String, String>} [type]
  4705. * {String[]} [fields] filter fields
  4706. * {function} [filter] filter items
  4707. * {String | function} [order] Order the items by
  4708. * a field name or custom sort function.
  4709. * @return {Object[]} mappedItems
  4710. */
  4711. DataSet.prototype.map = function (callback, options) {
  4712. var filter = options && options.filter,
  4713. type = options && options.type || this._options.type,
  4714. mappedItems = [],
  4715. data = this._data,
  4716. item;
  4717. // convert and filter items
  4718. for (var id in data) {
  4719. if (data.hasOwnProperty(id)) {
  4720. item = this._getItem(id, type);
  4721. if (!filter || filter(item)) {
  4722. mappedItems.push(callback(item, id));
  4723. }
  4724. }
  4725. }
  4726. // order items
  4727. if (options && options.order) {
  4728. this._sort(mappedItems, options.order);
  4729. }
  4730. return mappedItems;
  4731. };
  4732. /**
  4733. * Filter the fields of an item
  4734. * @param {Object | null} item
  4735. * @param {String[]} fields Field names
  4736. * @return {Object | null} filteredItem or null if no item is provided
  4737. * @private
  4738. */
  4739. DataSet.prototype._filterFields = function (item, fields) {
  4740. if (!item) { // item is null
  4741. return item;
  4742. }
  4743. var filteredItem = {};
  4744. for (var field in item) {
  4745. if (item.hasOwnProperty(field) && (fields.indexOf(field) != -1)) {
  4746. filteredItem[field] = item[field];
  4747. }
  4748. }
  4749. return filteredItem;
  4750. };
  4751. /**
  4752. * Sort the provided array with items
  4753. * @param {Object[]} items
  4754. * @param {String | function} order A field name or custom sort function.
  4755. * @private
  4756. */
  4757. DataSet.prototype._sort = function (items, order) {
  4758. if (util.isString(order)) {
  4759. // order by provided field name
  4760. var name = order; // field name
  4761. items.sort(function (a, b) {
  4762. var av = a[name];
  4763. var bv = b[name];
  4764. return (av > bv) ? 1 : ((av < bv) ? -1 : 0);
  4765. });
  4766. }
  4767. else if (typeof order === 'function') {
  4768. // order by sort function
  4769. items.sort(order);
  4770. }
  4771. // TODO: extend order by an Object {field:String, direction:String}
  4772. // where direction can be 'asc' or 'desc'
  4773. else {
  4774. throw new TypeError('Order must be a function or a string');
  4775. }
  4776. };
  4777. /**
  4778. * Remove an object by pointer or by id
  4779. * @param {String | Number | Object | Array} id Object or id, or an array with
  4780. * objects or ids to be removed
  4781. * @param {String} [senderId] Optional sender id
  4782. * @return {Array} removedIds
  4783. */
  4784. DataSet.prototype.remove = function (id, senderId) {
  4785. var removedIds = [],
  4786. i, len, removedId;
  4787. if (Array.isArray(id)) {
  4788. for (i = 0, len = id.length; i < len; i++) {
  4789. removedId = this._remove(id[i]);
  4790. if (removedId != null) {
  4791. removedIds.push(removedId);
  4792. }
  4793. }
  4794. }
  4795. else {
  4796. removedId = this._remove(id);
  4797. if (removedId != null) {
  4798. removedIds.push(removedId);
  4799. }
  4800. }
  4801. if (removedIds.length) {
  4802. this._trigger('remove', {items: removedIds}, senderId);
  4803. }
  4804. return removedIds;
  4805. };
  4806. /**
  4807. * Remove an item by its id
  4808. * @param {Number | String | Object} id id or item
  4809. * @returns {Number | String | null} id
  4810. * @private
  4811. */
  4812. DataSet.prototype._remove = function (id) {
  4813. if (util.isNumber(id) || util.isString(id)) {
  4814. if (this._data[id]) {
  4815. delete this._data[id];
  4816. this.length--;
  4817. return id;
  4818. }
  4819. }
  4820. else if (id instanceof Object) {
  4821. var itemId = id[this._fieldId];
  4822. if (itemId && this._data[itemId]) {
  4823. delete this._data[itemId];
  4824. this.length--;
  4825. return itemId;
  4826. }
  4827. }
  4828. return null;
  4829. };
  4830. /**
  4831. * Clear the data
  4832. * @param {String} [senderId] Optional sender id
  4833. * @return {Array} removedIds The ids of all removed items
  4834. */
  4835. DataSet.prototype.clear = function (senderId) {
  4836. var ids = Object.keys(this._data);
  4837. this._data = {};
  4838. this.length = 0;
  4839. this._trigger('remove', {items: ids}, senderId);
  4840. return ids;
  4841. };
  4842. /**
  4843. * Find the item with maximum value of a specified field
  4844. * @param {String} field
  4845. * @return {Object | null} item Item containing max value, or null if no items
  4846. */
  4847. DataSet.prototype.max = function (field) {
  4848. var data = this._data,
  4849. max = null,
  4850. maxField = null;
  4851. for (var id in data) {
  4852. if (data.hasOwnProperty(id)) {
  4853. var item = data[id];
  4854. var itemField = item[field];
  4855. if (itemField != null && (!max || itemField > maxField)) {
  4856. max = item;
  4857. maxField = itemField;
  4858. }
  4859. }
  4860. }
  4861. return max;
  4862. };
  4863. /**
  4864. * Find the item with minimum value of a specified field
  4865. * @param {String} field
  4866. * @return {Object | null} item Item containing max value, or null if no items
  4867. */
  4868. DataSet.prototype.min = function (field) {
  4869. var data = this._data,
  4870. min = null,
  4871. minField = null;
  4872. for (var id in data) {
  4873. if (data.hasOwnProperty(id)) {
  4874. var item = data[id];
  4875. var itemField = item[field];
  4876. if (itemField != null && (!min || itemField < minField)) {
  4877. min = item;
  4878. minField = itemField;
  4879. }
  4880. }
  4881. }
  4882. return min;
  4883. };
  4884. /**
  4885. * Find all distinct values of a specified field
  4886. * @param {String} field
  4887. * @return {Array} values Array containing all distinct values. If data items
  4888. * do not contain the specified field are ignored.
  4889. * The returned array is unordered.
  4890. */
  4891. DataSet.prototype.distinct = function (field) {
  4892. var data = this._data;
  4893. var values = [];
  4894. var fieldType = this._options.type && this._options.type[field] || null;
  4895. var count = 0;
  4896. var i;
  4897. for (var prop in data) {
  4898. if (data.hasOwnProperty(prop)) {
  4899. var item = data[prop];
  4900. var value = item[field];
  4901. var exists = false;
  4902. for (i = 0; i < count; i++) {
  4903. if (values[i] == value) {
  4904. exists = true;
  4905. break;
  4906. }
  4907. }
  4908. if (!exists && (value !== undefined)) {
  4909. values[count] = value;
  4910. count++;
  4911. }
  4912. }
  4913. }
  4914. if (fieldType) {
  4915. for (i = 0; i < values.length; i++) {
  4916. values[i] = util.convert(values[i], fieldType);
  4917. }
  4918. }
  4919. return values;
  4920. };
  4921. /**
  4922. * Add a single item. Will fail when an item with the same id already exists.
  4923. * @param {Object} item
  4924. * @return {String} id
  4925. * @private
  4926. */
  4927. DataSet.prototype._addItem = function (item) {
  4928. var id = item[this._fieldId];
  4929. if (id != undefined) {
  4930. // check whether this id is already taken
  4931. if (this._data[id]) {
  4932. // item already exists
  4933. throw new Error('Cannot add item: item with id ' + id + ' already exists');
  4934. }
  4935. }
  4936. else {
  4937. // generate an id
  4938. id = util.randomUUID();
  4939. item[this._fieldId] = id;
  4940. }
  4941. var d = {};
  4942. for (var field in item) {
  4943. if (item.hasOwnProperty(field)) {
  4944. var fieldType = this._type[field]; // type may be undefined
  4945. d[field] = util.convert(item[field], fieldType);
  4946. }
  4947. }
  4948. this._data[id] = d;
  4949. this.length++;
  4950. return id;
  4951. };
  4952. /**
  4953. * Get an item. Fields can be converted to a specific type
  4954. * @param {String} id
  4955. * @param {Object.<String, String>} [types] field types to convert
  4956. * @return {Object | null} item
  4957. * @private
  4958. */
  4959. DataSet.prototype._getItem = function (id, types) {
  4960. var field, value;
  4961. // get the item from the dataset
  4962. var raw = this._data[id];
  4963. if (!raw) {
  4964. return null;
  4965. }
  4966. // convert the items field types
  4967. var converted = {};
  4968. if (types) {
  4969. for (field in raw) {
  4970. if (raw.hasOwnProperty(field)) {
  4971. value = raw[field];
  4972. converted[field] = util.convert(value, types[field]);
  4973. }
  4974. }
  4975. }
  4976. else {
  4977. // no field types specified, no converting needed
  4978. for (field in raw) {
  4979. if (raw.hasOwnProperty(field)) {
  4980. value = raw[field];
  4981. converted[field] = value;
  4982. }
  4983. }
  4984. }
  4985. return converted;
  4986. };
  4987. /**
  4988. * Update a single item: merge with existing item.
  4989. * Will fail when the item has no id, or when there does not exist an item
  4990. * with the same id.
  4991. * @param {Object} item
  4992. * @return {String} id
  4993. * @private
  4994. */
  4995. DataSet.prototype._updateItem = function (item) {
  4996. var id = item[this._fieldId];
  4997. if (id == undefined) {
  4998. throw new Error('Cannot update item: item has no id (item: ' + JSON.stringify(item) + ')');
  4999. }
  5000. var d = this._data[id];
  5001. if (!d) {
  5002. // item doesn't exist
  5003. throw new Error('Cannot update item: no item with id ' + id + ' found');
  5004. }
  5005. // merge with current item
  5006. for (var field in item) {
  5007. if (item.hasOwnProperty(field)) {
  5008. var fieldType = this._type[field]; // type may be undefined
  5009. d[field] = util.convert(item[field], fieldType);
  5010. }
  5011. }
  5012. return id;
  5013. };
  5014. /**
  5015. * Get an array with the column names of a Google DataTable
  5016. * @param {DataTable} dataTable
  5017. * @return {String[]} columnNames
  5018. * @private
  5019. */
  5020. DataSet.prototype._getColumnNames = function (dataTable) {
  5021. var columns = [];
  5022. for (var col = 0, cols = dataTable.getNumberOfColumns(); col < cols; col++) {
  5023. columns[col] = dataTable.getColumnId(col) || dataTable.getColumnLabel(col);
  5024. }
  5025. return columns;
  5026. };
  5027. /**
  5028. * Append an item as a row to the dataTable
  5029. * @param dataTable
  5030. * @param columns
  5031. * @param item
  5032. * @private
  5033. */
  5034. DataSet.prototype._appendRow = function (dataTable, columns, item) {
  5035. var row = dataTable.addRow();
  5036. for (var col = 0, cols = columns.length; col < cols; col++) {
  5037. var field = columns[col];
  5038. dataTable.setValue(row, col, item[field]);
  5039. }
  5040. };
  5041. module.exports = DataSet;
  5042. /***/ },
  5043. /* 8 */
  5044. /***/ function(module, exports, __webpack_require__) {
  5045. /**
  5046. * A queue
  5047. * @param {Object} options
  5048. * Available options:
  5049. * - delay: number When provided, the queue will be flushed
  5050. * automatically after an inactivity of this delay
  5051. * in milliseconds.
  5052. * Default value is null.
  5053. * - max: number When the queue exceeds the given maximum number
  5054. * of entries, the queue is flushed automatically.
  5055. * Default value of max is Infinity.
  5056. * @constructor
  5057. */
  5058. function Queue(options) {
  5059. // options
  5060. this.delay = null;
  5061. this.max = Infinity;
  5062. // properties
  5063. this._queue = [];
  5064. this._timeout = null;
  5065. this._extended = null;
  5066. this.setOptions(options);
  5067. }
  5068. /**
  5069. * Update the configuration of the queue
  5070. * @param {Object} options
  5071. * Available options:
  5072. * - delay: number When provided, the queue will be flushed
  5073. * automatically after an inactivity of this delay
  5074. * in milliseconds.
  5075. * Default value is null.
  5076. * - max: number When the queue exceeds the given maximum number
  5077. * of entries, the queue is flushed automatically.
  5078. * Default value of max is Infinity.
  5079. * @param options
  5080. */
  5081. Queue.prototype.setOptions = function (options) {
  5082. if (options && typeof options.delay !== 'undefined') {
  5083. this.delay = options.delay;
  5084. }
  5085. if (options && typeof options.max !== 'undefined') {
  5086. this.max = options.max;
  5087. }
  5088. this._flushIfNeeded();
  5089. };
  5090. /**
  5091. * Extend an object with queuing functionality.
  5092. * The object will be extended with a function flush, and the methods provided
  5093. * in options.replace will be replaced with queued ones.
  5094. * @param {Object} object
  5095. * @param {Object} options
  5096. * Available options:
  5097. * - replace: Array.<string>
  5098. * A list with method names of the methods
  5099. * on the object to be replaced with queued ones.
  5100. * - delay: number When provided, the queue will be flushed
  5101. * automatically after an inactivity of this delay
  5102. * in milliseconds.
  5103. * Default value is null.
  5104. * - max: number When the queue exceeds the given maximum number
  5105. * of entries, the queue is flushed automatically.
  5106. * Default value of max is Infinity.
  5107. * @return {Queue} Returns the created queue
  5108. */
  5109. Queue.extend = function (object, options) {
  5110. var queue = new Queue(options);
  5111. if (object.flush !== undefined) {
  5112. throw new Error('Target object already has a property flush');
  5113. }
  5114. object.flush = function () {
  5115. queue.flush();
  5116. };
  5117. var methods = [{
  5118. name: 'flush',
  5119. original: undefined
  5120. }];
  5121. if (options && options.replace) {
  5122. for (var i = 0; i < options.replace.length; i++) {
  5123. var name = options.replace[i];
  5124. methods.push({
  5125. name: name,
  5126. original: object[name]
  5127. });
  5128. queue.replace(object, name);
  5129. }
  5130. }
  5131. queue._extended = {
  5132. object: object,
  5133. methods: methods
  5134. };
  5135. return queue;
  5136. };
  5137. /**
  5138. * Destroy the queue. The queue will first flush all queued actions, and in
  5139. * case it has extended an object, will restore the original object.
  5140. */
  5141. Queue.prototype.destroy = function () {
  5142. this.flush();
  5143. if (this._extended) {
  5144. var object = this._extended.object;
  5145. var methods = this._extended.methods;
  5146. for (var i = 0; i < methods.length; i++) {
  5147. var method = methods[i];
  5148. if (method.original) {
  5149. object[method.name] = method.original;
  5150. }
  5151. else {
  5152. delete object[method.name];
  5153. }
  5154. }
  5155. this._extended = null;
  5156. }
  5157. };
  5158. /**
  5159. * Replace a method on an object with a queued version
  5160. * @param {Object} object Object having the method
  5161. * @param {string} method The method name
  5162. */
  5163. Queue.prototype.replace = function(object, method) {
  5164. var me = this;
  5165. var original = object[method];
  5166. if (!original) {
  5167. throw new Error('Method ' + method + ' undefined');
  5168. }
  5169. object[method] = function () {
  5170. // create an Array with the arguments
  5171. var args = [];
  5172. for (var i = 0; i < arguments.length; i++) {
  5173. args[i] = arguments[i];
  5174. }
  5175. // add this call to the queue
  5176. me.queue({
  5177. args: args,
  5178. fn: original,
  5179. context: this
  5180. });
  5181. };
  5182. };
  5183. /**
  5184. * Queue a call
  5185. * @param {function | {fn: function, args: Array} | {fn: function, args: Array, context: Object}} entry
  5186. */
  5187. Queue.prototype.queue = function(entry) {
  5188. if (typeof entry === 'function') {
  5189. this._queue.push({fn: entry});
  5190. }
  5191. else {
  5192. this._queue.push(entry);
  5193. }
  5194. this._flushIfNeeded();
  5195. };
  5196. /**
  5197. * Check whether the queue needs to be flushed
  5198. * @private
  5199. */
  5200. Queue.prototype._flushIfNeeded = function () {
  5201. // flush when the maximum is exceeded.
  5202. if (this._queue.length > this.max) {
  5203. this.flush();
  5204. }
  5205. // flush after a period of inactivity when a delay is configured
  5206. clearTimeout(this._timeout);
  5207. if (this.queue.length > 0 && typeof this.delay === 'number') {
  5208. var me = this;
  5209. this._timeout = setTimeout(function () {
  5210. me.flush();
  5211. }, this.delay);
  5212. }
  5213. };
  5214. /**
  5215. * Flush all queued calls
  5216. */
  5217. Queue.prototype.flush = function () {
  5218. while (this._queue.length > 0) {
  5219. var entry = this._queue.shift();
  5220. entry.fn.apply(entry.context || entry.fn, entry.args || []);
  5221. }
  5222. };
  5223. module.exports = Queue;
  5224. /***/ },
  5225. /* 9 */
  5226. /***/ function(module, exports, __webpack_require__) {
  5227. var util = __webpack_require__(1);
  5228. var DataSet = __webpack_require__(7);
  5229. /**
  5230. * DataView
  5231. *
  5232. * a dataview offers a filtered view on a dataset or an other dataview.
  5233. *
  5234. * @param {DataSet | DataView} data
  5235. * @param {Object} [options] Available options: see method get
  5236. *
  5237. * @constructor DataView
  5238. */
  5239. function DataView (data, options) {
  5240. this._data = null;
  5241. this._ids = {}; // ids of the items currently in memory (just contains a boolean true)
  5242. this.length = 0; // number of items in the DataView
  5243. this._options = options || {};
  5244. this._fieldId = 'id'; // name of the field containing id
  5245. this._subscribers = {}; // event subscribers
  5246. var me = this;
  5247. this.listener = function () {
  5248. me._onEvent.apply(me, arguments);
  5249. };
  5250. this.setData(data);
  5251. }
  5252. // TODO: implement a function .config() to dynamically update things like configured filter
  5253. // and trigger changes accordingly
  5254. /**
  5255. * Set a data source for the view
  5256. * @param {DataSet | DataView} data
  5257. */
  5258. DataView.prototype.setData = function (data) {
  5259. var ids, i, len;
  5260. if (this._data) {
  5261. // unsubscribe from current dataset
  5262. if (this._data.unsubscribe) {
  5263. this._data.unsubscribe('*', this.listener);
  5264. }
  5265. // trigger a remove of all items in memory
  5266. ids = [];
  5267. for (var id in this._ids) {
  5268. if (this._ids.hasOwnProperty(id)) {
  5269. ids.push(id);
  5270. }
  5271. }
  5272. this._ids = {};
  5273. this.length = 0;
  5274. this._trigger('remove', {items: ids});
  5275. }
  5276. this._data = data;
  5277. if (this._data) {
  5278. // update fieldId
  5279. this._fieldId = this._options.fieldId ||
  5280. (this._data && this._data.options && this._data.options.fieldId) ||
  5281. 'id';
  5282. // trigger an add of all added items
  5283. ids = this._data.getIds({filter: this._options && this._options.filter});
  5284. for (i = 0, len = ids.length; i < len; i++) {
  5285. id = ids[i];
  5286. this._ids[id] = true;
  5287. }
  5288. this.length = ids.length;
  5289. this._trigger('add', {items: ids});
  5290. // subscribe to new dataset
  5291. if (this._data.on) {
  5292. this._data.on('*', this.listener);
  5293. }
  5294. }
  5295. };
  5296. /**
  5297. * Refresh the DataView. Useful when the DataView has a filter function
  5298. * containing a variable parameter.
  5299. */
  5300. DataView.prototype.refresh = function () {
  5301. var id;
  5302. var ids = this._data.getIds({filter: this._options && this._options.filter});
  5303. var newIds = {};
  5304. var added = [];
  5305. var removed = [];
  5306. // check for additions
  5307. for (var i = 0; i < ids.length; i++) {
  5308. id = ids[i];
  5309. newIds[id] = true;
  5310. if (!this._ids[id]) {
  5311. added.push(id);
  5312. this._ids[id] = true;
  5313. this.length++;
  5314. }
  5315. }
  5316. // check for removals
  5317. for (id in this._ids) {
  5318. if (this._ids.hasOwnProperty(id)) {
  5319. if (!newIds[id]) {
  5320. removed.push(id);
  5321. delete this._ids[id];
  5322. this.length--;
  5323. }
  5324. }
  5325. }
  5326. // trigger events
  5327. if (added.length) {
  5328. this._trigger('add', {items: added});
  5329. }
  5330. if (removed.length) {
  5331. this._trigger('remove', {items: removed});
  5332. }
  5333. };
  5334. /**
  5335. * Get data from the data view
  5336. *
  5337. * Usage:
  5338. *
  5339. * get()
  5340. * get(options: Object)
  5341. * get(options: Object, data: Array | DataTable)
  5342. *
  5343. * get(id: Number)
  5344. * get(id: Number, options: Object)
  5345. * get(id: Number, options: Object, data: Array | DataTable)
  5346. *
  5347. * get(ids: Number[])
  5348. * get(ids: Number[], options: Object)
  5349. * get(ids: Number[], options: Object, data: Array | DataTable)
  5350. *
  5351. * Where:
  5352. *
  5353. * {Number | String} id The id of an item
  5354. * {Number[] | String{}} ids An array with ids of items
  5355. * {Object} options An Object with options. Available options:
  5356. * {String} [type] Type of data to be returned. Can
  5357. * be 'DataTable' or 'Array' (default)
  5358. * {Object.<String, String>} [convert]
  5359. * {String[]} [fields] field names to be returned
  5360. * {function} [filter] filter items
  5361. * {String | function} [order] Order the items by
  5362. * a field name or custom sort function.
  5363. * {Array | DataTable} [data] If provided, items will be appended to this
  5364. * array or table. Required in case of Google
  5365. * DataTable.
  5366. * @param args
  5367. */
  5368. DataView.prototype.get = function (args) {
  5369. var me = this;
  5370. // parse the arguments
  5371. var ids, options, data;
  5372. var firstType = util.getType(arguments[0]);
  5373. if (firstType == 'String' || firstType == 'Number' || firstType == 'Array') {
  5374. // get(id(s) [, options] [, data])
  5375. ids = arguments[0]; // can be a single id or an array with ids
  5376. options = arguments[1];
  5377. data = arguments[2];
  5378. }
  5379. else {
  5380. // get([, options] [, data])
  5381. options = arguments[0];
  5382. data = arguments[1];
  5383. }
  5384. // extend the options with the default options and provided options
  5385. var viewOptions = util.extend({}, this._options, options);
  5386. // create a combined filter method when needed
  5387. if (this._options.filter && options && options.filter) {
  5388. viewOptions.filter = function (item) {
  5389. return me._options.filter(item) && options.filter(item);
  5390. }
  5391. }
  5392. // build up the call to the linked data set
  5393. var getArguments = [];
  5394. if (ids != undefined) {
  5395. getArguments.push(ids);
  5396. }
  5397. getArguments.push(viewOptions);
  5398. getArguments.push(data);
  5399. return this._data && this._data.get.apply(this._data, getArguments);
  5400. };
  5401. /**
  5402. * Get ids of all items or from a filtered set of items.
  5403. * @param {Object} [options] An Object with options. Available options:
  5404. * {function} [filter] filter items
  5405. * {String | function} [order] Order the items by
  5406. * a field name or custom sort function.
  5407. * @return {Array} ids
  5408. */
  5409. DataView.prototype.getIds = function (options) {
  5410. var ids;
  5411. if (this._data) {
  5412. var defaultFilter = this._options.filter;
  5413. var filter;
  5414. if (options && options.filter) {
  5415. if (defaultFilter) {
  5416. filter = function (item) {
  5417. return defaultFilter(item) && options.filter(item);
  5418. }
  5419. }
  5420. else {
  5421. filter = options.filter;
  5422. }
  5423. }
  5424. else {
  5425. filter = defaultFilter;
  5426. }
  5427. ids = this._data.getIds({
  5428. filter: filter,
  5429. order: options && options.order
  5430. });
  5431. }
  5432. else {
  5433. ids = [];
  5434. }
  5435. return ids;
  5436. };
  5437. /**
  5438. * Get the DataSet to which this DataView is connected. In case there is a chain
  5439. * of multiple DataViews, the root DataSet of this chain is returned.
  5440. * @return {DataSet} dataSet
  5441. */
  5442. DataView.prototype.getDataSet = function () {
  5443. var dataSet = this;
  5444. while (dataSet instanceof DataView) {
  5445. dataSet = dataSet._data;
  5446. }
  5447. return dataSet || null;
  5448. };
  5449. /**
  5450. * Event listener. Will propagate all events from the connected data set to
  5451. * the subscribers of the DataView, but will filter the items and only trigger
  5452. * when there are changes in the filtered data set.
  5453. * @param {String} event
  5454. * @param {Object | null} params
  5455. * @param {String} senderId
  5456. * @private
  5457. */
  5458. DataView.prototype._onEvent = function (event, params, senderId) {
  5459. var i, len, id, item,
  5460. ids = params && params.items,
  5461. data = this._data,
  5462. added = [],
  5463. updated = [],
  5464. removed = [];
  5465. if (ids && data) {
  5466. switch (event) {
  5467. case 'add':
  5468. // filter the ids of the added items
  5469. for (i = 0, len = ids.length; i < len; i++) {
  5470. id = ids[i];
  5471. item = this.get(id);
  5472. if (item) {
  5473. this._ids[id] = true;
  5474. added.push(id);
  5475. }
  5476. }
  5477. break;
  5478. case 'update':
  5479. // determine the event from the views viewpoint: an updated
  5480. // item can be added, updated, or removed from this view.
  5481. for (i = 0, len = ids.length; i < len; i++) {
  5482. id = ids[i];
  5483. item = this.get(id);
  5484. if (item) {
  5485. if (this._ids[id]) {
  5486. updated.push(id);
  5487. }
  5488. else {
  5489. this._ids[id] = true;
  5490. added.push(id);
  5491. }
  5492. }
  5493. else {
  5494. if (this._ids[id]) {
  5495. delete this._ids[id];
  5496. removed.push(id);
  5497. }
  5498. else {
  5499. // nothing interesting for me :-(
  5500. }
  5501. }
  5502. }
  5503. break;
  5504. case 'remove':
  5505. // filter the ids of the removed items
  5506. for (i = 0, len = ids.length; i < len; i++) {
  5507. id = ids[i];
  5508. if (this._ids[id]) {
  5509. delete this._ids[id];
  5510. removed.push(id);
  5511. }
  5512. }
  5513. break;
  5514. }
  5515. this.length += added.length - removed.length;
  5516. if (added.length) {
  5517. this._trigger('add', {items: added}, senderId);
  5518. }
  5519. if (updated.length) {
  5520. this._trigger('update', {items: updated}, senderId);
  5521. }
  5522. if (removed.length) {
  5523. this._trigger('remove', {items: removed}, senderId);
  5524. }
  5525. }
  5526. };
  5527. // copy subscription functionality from DataSet
  5528. DataView.prototype.on = DataSet.prototype.on;
  5529. DataView.prototype.off = DataSet.prototype.off;
  5530. DataView.prototype._trigger = DataSet.prototype._trigger;
  5531. // TODO: make these functions deprecated (replaced with `on` and `off` since version 0.5)
  5532. DataView.prototype.subscribe = DataView.prototype.on;
  5533. DataView.prototype.unsubscribe = DataView.prototype.off;
  5534. module.exports = DataView;
  5535. /***/ },
  5536. /* 10 */
  5537. /***/ function(module, exports, __webpack_require__) {
  5538. var Emitter = __webpack_require__(11);
  5539. var DataSet = __webpack_require__(7);
  5540. var DataView = __webpack_require__(9);
  5541. var util = __webpack_require__(1);
  5542. var Point3d = __webpack_require__(12);
  5543. var Point2d = __webpack_require__(13);
  5544. var Camera = __webpack_require__(14);
  5545. var Filter = __webpack_require__(15);
  5546. var Slider = __webpack_require__(16);
  5547. var StepNumber = __webpack_require__(17);
  5548. /**
  5549. * @constructor Graph3d
  5550. * Graph3d displays data in 3d.
  5551. *
  5552. * Graph3d is developed in javascript as a Google Visualization Chart.
  5553. *
  5554. * @param {Element} container The DOM element in which the Graph3d will
  5555. * be created. Normally a div element.
  5556. * @param {DataSet | DataView | Array} [data]
  5557. * @param {Object} [options]
  5558. */
  5559. function Graph3d(container, data, options) {
  5560. if (!(this instanceof Graph3d)) {
  5561. throw new SyntaxError('Constructor must be called with the new operator');
  5562. }
  5563. // create variables and set default values
  5564. this.containerElement = container;
  5565. this.width = '400px';
  5566. this.height = '400px';
  5567. this.margin = 10; // px
  5568. this.defaultXCenter = '55%';
  5569. this.defaultYCenter = '50%';
  5570. this.xLabel = 'x';
  5571. this.yLabel = 'y';
  5572. this.zLabel = 'z';
  5573. var passValueFn = function(v) { return v; };
  5574. this.xValueLabel = passValueFn;
  5575. this.yValueLabel = passValueFn;
  5576. this.zValueLabel = passValueFn;
  5577. this.filterLabel = 'time';
  5578. this.legendLabel = 'value';
  5579. this.style = Graph3d.STYLE.DOT;
  5580. this.showPerspective = true;
  5581. this.showGrid = true;
  5582. this.keepAspectRatio = true;
  5583. this.showShadow = false;
  5584. this.showGrayBottom = false; // TODO: this does not work correctly
  5585. this.showTooltip = false;
  5586. this.verticalRatio = 0.5; // 0.1 to 1.0, where 1.0 results in a 'cube'
  5587. this.animationInterval = 1000; // milliseconds
  5588. this.animationPreload = false;
  5589. this.camera = new Camera();
  5590. this.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?
  5591. this.dataTable = null; // The original data table
  5592. this.dataPoints = null; // The table with point objects
  5593. // the column indexes
  5594. this.colX = undefined;
  5595. this.colY = undefined;
  5596. this.colZ = undefined;
  5597. this.colValue = undefined;
  5598. this.colFilter = undefined;
  5599. this.xMin = 0;
  5600. this.xStep = undefined; // auto by default
  5601. this.xMax = 1;
  5602. this.yMin = 0;
  5603. this.yStep = undefined; // auto by default
  5604. this.yMax = 1;
  5605. this.zMin = 0;
  5606. this.zStep = undefined; // auto by default
  5607. this.zMax = 1;
  5608. this.valueMin = 0;
  5609. this.valueMax = 1;
  5610. this.xBarWidth = 1;
  5611. this.yBarWidth = 1;
  5612. // TODO: customize axis range
  5613. // constants
  5614. this.colorAxis = '#4D4D4D';
  5615. this.colorGrid = '#D3D3D3';
  5616. this.colorDot = '#7DC1FF';
  5617. this.colorDotBorder = '#3267D2';
  5618. // create a frame and canvas
  5619. this.create();
  5620. // apply options (also when undefined)
  5621. this.setOptions(options);
  5622. // apply data
  5623. if (data) {
  5624. this.setData(data);
  5625. }
  5626. }
  5627. // Extend Graph3d with an Emitter mixin
  5628. Emitter(Graph3d.prototype);
  5629. /**
  5630. * Calculate the scaling values, dependent on the range in x, y, and z direction
  5631. */
  5632. Graph3d.prototype._setScale = function() {
  5633. this.scale = new Point3d(1 / (this.xMax - this.xMin),
  5634. 1 / (this.yMax - this.yMin),
  5635. 1 / (this.zMax - this.zMin));
  5636. // keep aspect ration between x and y scale if desired
  5637. if (this.keepAspectRatio) {
  5638. if (this.scale.x < this.scale.y) {
  5639. //noinspection JSSuspiciousNameCombination
  5640. this.scale.y = this.scale.x;
  5641. }
  5642. else {
  5643. //noinspection JSSuspiciousNameCombination
  5644. this.scale.x = this.scale.y;
  5645. }
  5646. }
  5647. // scale the vertical axis
  5648. this.scale.z *= this.verticalRatio;
  5649. // TODO: can this be automated? verticalRatio?
  5650. // determine scale for (optional) value
  5651. this.scale.value = 1 / (this.valueMax - this.valueMin);
  5652. // position the camera arm
  5653. var xCenter = (this.xMax + this.xMin) / 2 * this.scale.x;
  5654. var yCenter = (this.yMax + this.yMin) / 2 * this.scale.y;
  5655. var zCenter = (this.zMax + this.zMin) / 2 * this.scale.z;
  5656. this.camera.setArmLocation(xCenter, yCenter, zCenter);
  5657. };
  5658. /**
  5659. * Convert a 3D location to a 2D location on screen
  5660. * http://en.wikipedia.org/wiki/3D_projection
  5661. * @param {Point3d} point3d A 3D point with parameters x, y, z
  5662. * @return {Point2d} point2d A 2D point with parameters x, y
  5663. */
  5664. Graph3d.prototype._convert3Dto2D = function(point3d) {
  5665. var translation = this._convertPointToTranslation(point3d);
  5666. return this._convertTranslationToScreen(translation);
  5667. };
  5668. /**
  5669. * Convert a 3D location its translation seen from the camera
  5670. * http://en.wikipedia.org/wiki/3D_projection
  5671. * @param {Point3d} point3d A 3D point with parameters x, y, z
  5672. * @return {Point3d} translation A 3D point with parameters x, y, z This is
  5673. * the translation of the point, seen from the
  5674. * camera
  5675. */
  5676. Graph3d.prototype._convertPointToTranslation = function(point3d) {
  5677. var ax = point3d.x * this.scale.x,
  5678. ay = point3d.y * this.scale.y,
  5679. az = point3d.z * this.scale.z,
  5680. cx = this.camera.getCameraLocation().x,
  5681. cy = this.camera.getCameraLocation().y,
  5682. cz = this.camera.getCameraLocation().z,
  5683. // calculate angles
  5684. sinTx = Math.sin(this.camera.getCameraRotation().x),
  5685. cosTx = Math.cos(this.camera.getCameraRotation().x),
  5686. sinTy = Math.sin(this.camera.getCameraRotation().y),
  5687. cosTy = Math.cos(this.camera.getCameraRotation().y),
  5688. sinTz = Math.sin(this.camera.getCameraRotation().z),
  5689. cosTz = Math.cos(this.camera.getCameraRotation().z),
  5690. // calculate translation
  5691. dx = cosTy * (sinTz * (ay - cy) + cosTz * (ax - cx)) - sinTy * (az - cz),
  5692. dy = sinTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) + cosTx * (cosTz * (ay - cy) - sinTz * (ax-cx)),
  5693. dz = cosTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) - sinTx * (cosTz * (ay - cy) - sinTz * (ax-cx));
  5694. return new Point3d(dx, dy, dz);
  5695. };
  5696. /**
  5697. * Convert a translation point to a point on the screen
  5698. * @param {Point3d} translation A 3D point with parameters x, y, z This is
  5699. * the translation of the point, seen from the
  5700. * camera
  5701. * @return {Point2d} point2d A 2D point with parameters x, y
  5702. */
  5703. Graph3d.prototype._convertTranslationToScreen = function(translation) {
  5704. var ex = this.eye.x,
  5705. ey = this.eye.y,
  5706. ez = this.eye.z,
  5707. dx = translation.x,
  5708. dy = translation.y,
  5709. dz = translation.z;
  5710. // calculate position on screen from translation
  5711. var bx;
  5712. var by;
  5713. if (this.showPerspective) {
  5714. bx = (dx - ex) * (ez / dz);
  5715. by = (dy - ey) * (ez / dz);
  5716. }
  5717. else {
  5718. bx = dx * -(ez / this.camera.getArmLength());
  5719. by = dy * -(ez / this.camera.getArmLength());
  5720. }
  5721. // shift and scale the point to the center of the screen
  5722. // use the width of the graph to scale both horizontally and vertically.
  5723. return new Point2d(
  5724. this.xcenter + bx * this.frame.canvas.clientWidth,
  5725. this.ycenter - by * this.frame.canvas.clientWidth);
  5726. };
  5727. /**
  5728. * Set the background styling for the graph
  5729. * @param {string | {fill: string, stroke: string, strokeWidth: string}} backgroundColor
  5730. */
  5731. Graph3d.prototype._setBackgroundColor = function(backgroundColor) {
  5732. var fill = 'white';
  5733. var stroke = 'gray';
  5734. var strokeWidth = 1;
  5735. if (typeof(backgroundColor) === 'string') {
  5736. fill = backgroundColor;
  5737. stroke = 'none';
  5738. strokeWidth = 0;
  5739. }
  5740. else if (typeof(backgroundColor) === 'object') {
  5741. if (backgroundColor.fill !== undefined) fill = backgroundColor.fill;
  5742. if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke;
  5743. if (backgroundColor.strokeWidth !== undefined) strokeWidth = backgroundColor.strokeWidth;
  5744. }
  5745. else if (backgroundColor === undefined) {
  5746. // use use defaults
  5747. }
  5748. else {
  5749. throw 'Unsupported type of backgroundColor';
  5750. }
  5751. this.frame.style.backgroundColor = fill;
  5752. this.frame.style.borderColor = stroke;
  5753. this.frame.style.borderWidth = strokeWidth + 'px';
  5754. this.frame.style.borderStyle = 'solid';
  5755. };
  5756. /// enumerate the available styles
  5757. Graph3d.STYLE = {
  5758. BAR: 0,
  5759. BARCOLOR: 1,
  5760. BARSIZE: 2,
  5761. DOT : 3,
  5762. DOTLINE : 4,
  5763. DOTCOLOR: 5,
  5764. DOTSIZE: 6,
  5765. GRID : 7,
  5766. LINE: 8,
  5767. SURFACE : 9
  5768. };
  5769. /**
  5770. * Retrieve the style index from given styleName
  5771. * @param {string} styleName Style name such as 'dot', 'grid', 'dot-line'
  5772. * @return {Number} styleNumber Enumeration value representing the style, or -1
  5773. * when not found
  5774. */
  5775. Graph3d.prototype._getStyleNumber = function(styleName) {
  5776. switch (styleName) {
  5777. case 'dot': return Graph3d.STYLE.DOT;
  5778. case 'dot-line': return Graph3d.STYLE.DOTLINE;
  5779. case 'dot-color': return Graph3d.STYLE.DOTCOLOR;
  5780. case 'dot-size': return Graph3d.STYLE.DOTSIZE;
  5781. case 'line': return Graph3d.STYLE.LINE;
  5782. case 'grid': return Graph3d.STYLE.GRID;
  5783. case 'surface': return Graph3d.STYLE.SURFACE;
  5784. case 'bar': return Graph3d.STYLE.BAR;
  5785. case 'bar-color': return Graph3d.STYLE.BARCOLOR;
  5786. case 'bar-size': return Graph3d.STYLE.BARSIZE;
  5787. }
  5788. return -1;
  5789. };
  5790. /**
  5791. * Determine the indexes of the data columns, based on the given style and data
  5792. * @param {DataSet} data
  5793. * @param {Number} style
  5794. */
  5795. Graph3d.prototype._determineColumnIndexes = function(data, style) {
  5796. if (this.style === Graph3d.STYLE.DOT ||
  5797. this.style === Graph3d.STYLE.DOTLINE ||
  5798. this.style === Graph3d.STYLE.LINE ||
  5799. this.style === Graph3d.STYLE.GRID ||
  5800. this.style === Graph3d.STYLE.SURFACE ||
  5801. this.style === Graph3d.STYLE.BAR) {
  5802. // 3 columns expected, and optionally a 4th with filter values
  5803. this.colX = 0;
  5804. this.colY = 1;
  5805. this.colZ = 2;
  5806. this.colValue = undefined;
  5807. if (data.getNumberOfColumns() > 3) {
  5808. this.colFilter = 3;
  5809. }
  5810. }
  5811. else if (this.style === Graph3d.STYLE.DOTCOLOR ||
  5812. this.style === Graph3d.STYLE.DOTSIZE ||
  5813. this.style === Graph3d.STYLE.BARCOLOR ||
  5814. this.style === Graph3d.STYLE.BARSIZE) {
  5815. // 4 columns expected, and optionally a 5th with filter values
  5816. this.colX = 0;
  5817. this.colY = 1;
  5818. this.colZ = 2;
  5819. this.colValue = 3;
  5820. if (data.getNumberOfColumns() > 4) {
  5821. this.colFilter = 4;
  5822. }
  5823. }
  5824. else {
  5825. throw 'Unknown style "' + this.style + '"';
  5826. }
  5827. };
  5828. Graph3d.prototype.getNumberOfRows = function(data) {
  5829. return data.length;
  5830. }
  5831. Graph3d.prototype.getNumberOfColumns = function(data) {
  5832. var counter = 0;
  5833. for (var column in data[0]) {
  5834. if (data[0].hasOwnProperty(column)) {
  5835. counter++;
  5836. }
  5837. }
  5838. return counter;
  5839. }
  5840. Graph3d.prototype.getDistinctValues = function(data, column) {
  5841. var distinctValues = [];
  5842. for (var i = 0; i < data.length; i++) {
  5843. if (distinctValues.indexOf(data[i][column]) == -1) {
  5844. distinctValues.push(data[i][column]);
  5845. }
  5846. }
  5847. return distinctValues;
  5848. }
  5849. Graph3d.prototype.getColumnRange = function(data,column) {
  5850. var minMax = {min:data[0][column],max:data[0][column]};
  5851. for (var i = 0; i < data.length; i++) {
  5852. if (minMax.min > data[i][column]) { minMax.min = data[i][column]; }
  5853. if (minMax.max < data[i][column]) { minMax.max = data[i][column]; }
  5854. }
  5855. return minMax;
  5856. };
  5857. /**
  5858. * Initialize the data from the data table. Calculate minimum and maximum values
  5859. * and column index values
  5860. * @param {Array | DataSet | DataView} rawData The data containing the items for the Graph.
  5861. * @param {Number} style Style Number
  5862. */
  5863. Graph3d.prototype._dataInitialize = function (rawData, style) {
  5864. var me = this;
  5865. // unsubscribe from the dataTable
  5866. if (this.dataSet) {
  5867. this.dataSet.off('*', this._onChange);
  5868. }
  5869. if (rawData === undefined)
  5870. return;
  5871. if (Array.isArray(rawData)) {
  5872. rawData = new DataSet(rawData);
  5873. }
  5874. var data;
  5875. if (rawData instanceof DataSet || rawData instanceof DataView) {
  5876. data = rawData.get();
  5877. }
  5878. else {
  5879. throw new Error('Array, DataSet, or DataView expected');
  5880. }
  5881. if (data.length == 0)
  5882. return;
  5883. this.dataSet = rawData;
  5884. this.dataTable = data;
  5885. // subscribe to changes in the dataset
  5886. this._onChange = function () {
  5887. me.setData(me.dataSet);
  5888. };
  5889. this.dataSet.on('*', this._onChange);
  5890. // _determineColumnIndexes
  5891. // getNumberOfRows (points)
  5892. // getNumberOfColumns (x,y,z,v,t,t1,t2...)
  5893. // getDistinctValues (unique values?)
  5894. // getColumnRange
  5895. // determine the location of x,y,z,value,filter columns
  5896. this.colX = 'x';
  5897. this.colY = 'y';
  5898. this.colZ = 'z';
  5899. this.colValue = 'style';
  5900. this.colFilter = 'filter';
  5901. // check if a filter column is provided
  5902. if (data[0].hasOwnProperty('filter')) {
  5903. if (this.dataFilter === undefined) {
  5904. this.dataFilter = new Filter(rawData, this.colFilter, this);
  5905. this.dataFilter.setOnLoadCallback(function() {me.redraw();});
  5906. }
  5907. }
  5908. var withBars = this.style == Graph3d.STYLE.BAR ||
  5909. this.style == Graph3d.STYLE.BARCOLOR ||
  5910. this.style == Graph3d.STYLE.BARSIZE;
  5911. // determine barWidth from data
  5912. if (withBars) {
  5913. if (this.defaultXBarWidth !== undefined) {
  5914. this.xBarWidth = this.defaultXBarWidth;
  5915. }
  5916. else {
  5917. var dataX = this.getDistinctValues(data,this.colX);
  5918. this.xBarWidth = (dataX[1] - dataX[0]) || 1;
  5919. }
  5920. if (this.defaultYBarWidth !== undefined) {
  5921. this.yBarWidth = this.defaultYBarWidth;
  5922. }
  5923. else {
  5924. var dataY = this.getDistinctValues(data,this.colY);
  5925. this.yBarWidth = (dataY[1] - dataY[0]) || 1;
  5926. }
  5927. }
  5928. // calculate minimums and maximums
  5929. var xRange = this.getColumnRange(data,this.colX);
  5930. if (withBars) {
  5931. xRange.min -= this.xBarWidth / 2;
  5932. xRange.max += this.xBarWidth / 2;
  5933. }
  5934. this.xMin = (this.defaultXMin !== undefined) ? this.defaultXMin : xRange.min;
  5935. this.xMax = (this.defaultXMax !== undefined) ? this.defaultXMax : xRange.max;
  5936. if (this.xMax <= this.xMin) this.xMax = this.xMin + 1;
  5937. this.xStep = (this.defaultXStep !== undefined) ? this.defaultXStep : (this.xMax-this.xMin)/5;
  5938. var yRange = this.getColumnRange(data,this.colY);
  5939. if (withBars) {
  5940. yRange.min -= this.yBarWidth / 2;
  5941. yRange.max += this.yBarWidth / 2;
  5942. }
  5943. this.yMin = (this.defaultYMin !== undefined) ? this.defaultYMin : yRange.min;
  5944. this.yMax = (this.defaultYMax !== undefined) ? this.defaultYMax : yRange.max;
  5945. if (this.yMax <= this.yMin) this.yMax = this.yMin + 1;
  5946. this.yStep = (this.defaultYStep !== undefined) ? this.defaultYStep : (this.yMax-this.yMin)/5;
  5947. var zRange = this.getColumnRange(data,this.colZ);
  5948. this.zMin = (this.defaultZMin !== undefined) ? this.defaultZMin : zRange.min;
  5949. this.zMax = (this.defaultZMax !== undefined) ? this.defaultZMax : zRange.max;
  5950. if (this.zMax <= this.zMin) this.zMax = this.zMin + 1;
  5951. this.zStep = (this.defaultZStep !== undefined) ? this.defaultZStep : (this.zMax-this.zMin)/5;
  5952. if (this.colValue !== undefined) {
  5953. var valueRange = this.getColumnRange(data,this.colValue);
  5954. this.valueMin = (this.defaultValueMin !== undefined) ? this.defaultValueMin : valueRange.min;
  5955. this.valueMax = (this.defaultValueMax !== undefined) ? this.defaultValueMax : valueRange.max;
  5956. if (this.valueMax <= this.valueMin) this.valueMax = this.valueMin + 1;
  5957. }
  5958. // set the scale dependent on the ranges.
  5959. this._setScale();
  5960. };
  5961. /**
  5962. * Filter the data based on the current filter
  5963. * @param {Array} data
  5964. * @return {Array} dataPoints Array with point objects which can be drawn on screen
  5965. */
  5966. Graph3d.prototype._getDataPoints = function (data) {
  5967. // TODO: store the created matrix dataPoints in the filters instead of reloading each time
  5968. var x, y, i, z, obj, point;
  5969. var dataPoints = [];
  5970. if (this.style === Graph3d.STYLE.GRID ||
  5971. this.style === Graph3d.STYLE.SURFACE) {
  5972. // copy all values from the google data table to a matrix
  5973. // the provided values are supposed to form a grid of (x,y) positions
  5974. // create two lists with all present x and y values
  5975. var dataX = [];
  5976. var dataY = [];
  5977. for (i = 0; i < this.getNumberOfRows(data); i++) {
  5978. x = data[i][this.colX] || 0;
  5979. y = data[i][this.colY] || 0;
  5980. if (dataX.indexOf(x) === -1) {
  5981. dataX.push(x);
  5982. }
  5983. if (dataY.indexOf(y) === -1) {
  5984. dataY.push(y);
  5985. }
  5986. }
  5987. var sortNumber = function (a, b) {
  5988. return a - b;
  5989. };
  5990. dataX.sort(sortNumber);
  5991. dataY.sort(sortNumber);
  5992. // create a grid, a 2d matrix, with all values.
  5993. var dataMatrix = []; // temporary data matrix
  5994. for (i = 0; i < data.length; i++) {
  5995. x = data[i][this.colX] || 0;
  5996. y = data[i][this.colY] || 0;
  5997. z = data[i][this.colZ] || 0;
  5998. var xIndex = dataX.indexOf(x); // TODO: implement Array().indexOf() for Internet Explorer
  5999. var yIndex = dataY.indexOf(y);
  6000. if (dataMatrix[xIndex] === undefined) {
  6001. dataMatrix[xIndex] = [];
  6002. }
  6003. var point3d = new Point3d();
  6004. point3d.x = x;
  6005. point3d.y = y;
  6006. point3d.z = z;
  6007. obj = {};
  6008. obj.point = point3d;
  6009. obj.trans = undefined;
  6010. obj.screen = undefined;
  6011. obj.bottom = new Point3d(x, y, this.zMin);
  6012. dataMatrix[xIndex][yIndex] = obj;
  6013. dataPoints.push(obj);
  6014. }
  6015. // fill in the pointers to the neighbors.
  6016. for (x = 0; x < dataMatrix.length; x++) {
  6017. for (y = 0; y < dataMatrix[x].length; y++) {
  6018. if (dataMatrix[x][y]) {
  6019. dataMatrix[x][y].pointRight = (x < dataMatrix.length-1) ? dataMatrix[x+1][y] : undefined;
  6020. dataMatrix[x][y].pointTop = (y < dataMatrix[x].length-1) ? dataMatrix[x][y+1] : undefined;
  6021. dataMatrix[x][y].pointCross =
  6022. (x < dataMatrix.length-1 && y < dataMatrix[x].length-1) ?
  6023. dataMatrix[x+1][y+1] :
  6024. undefined;
  6025. }
  6026. }
  6027. }
  6028. }
  6029. else { // 'dot', 'dot-line', etc.
  6030. // copy all values from the google data table to a list with Point3d objects
  6031. for (i = 0; i < data.length; i++) {
  6032. point = new Point3d();
  6033. point.x = data[i][this.colX] || 0;
  6034. point.y = data[i][this.colY] || 0;
  6035. point.z = data[i][this.colZ] || 0;
  6036. if (this.colValue !== undefined) {
  6037. point.value = data[i][this.colValue] || 0;
  6038. }
  6039. obj = {};
  6040. obj.point = point;
  6041. obj.bottom = new Point3d(point.x, point.y, this.zMin);
  6042. obj.trans = undefined;
  6043. obj.screen = undefined;
  6044. dataPoints.push(obj);
  6045. }
  6046. }
  6047. return dataPoints;
  6048. };
  6049. /**
  6050. * Create the main frame for the Graph3d.
  6051. * This function is executed once when a Graph3d object is created. The frame
  6052. * contains a canvas, and this canvas contains all objects like the axis and
  6053. * nodes.
  6054. */
  6055. Graph3d.prototype.create = function () {
  6056. // remove all elements from the container element.
  6057. while (this.containerElement.hasChildNodes()) {
  6058. this.containerElement.removeChild(this.containerElement.firstChild);
  6059. }
  6060. this.frame = document.createElement('div');
  6061. this.frame.style.position = 'relative';
  6062. this.frame.style.overflow = 'hidden';
  6063. // create the graph canvas (HTML canvas element)
  6064. this.frame.canvas = document.createElement( 'canvas' );
  6065. this.frame.canvas.style.position = 'relative';
  6066. this.frame.appendChild(this.frame.canvas);
  6067. //if (!this.frame.canvas.getContext) {
  6068. {
  6069. var noCanvas = document.createElement( 'DIV' );
  6070. noCanvas.style.color = 'red';
  6071. noCanvas.style.fontWeight = 'bold' ;
  6072. noCanvas.style.padding = '10px';
  6073. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  6074. this.frame.canvas.appendChild(noCanvas);
  6075. }
  6076. this.frame.filter = document.createElement( 'div' );
  6077. this.frame.filter.style.position = 'absolute';
  6078. this.frame.filter.style.bottom = '0px';
  6079. this.frame.filter.style.left = '0px';
  6080. this.frame.filter.style.width = '100%';
  6081. this.frame.appendChild(this.frame.filter);
  6082. // add event listeners to handle moving and zooming the contents
  6083. var me = this;
  6084. var onmousedown = function (event) {me._onMouseDown(event);};
  6085. var ontouchstart = function (event) {me._onTouchStart(event);};
  6086. var onmousewheel = function (event) {me._onWheel(event);};
  6087. var ontooltip = function (event) {me._onTooltip(event);};
  6088. // TODO: these events are never cleaned up... can give a 'memory leakage'
  6089. util.addEventListener(this.frame.canvas, 'keydown', onkeydown);
  6090. util.addEventListener(this.frame.canvas, 'mousedown', onmousedown);
  6091. util.addEventListener(this.frame.canvas, 'touchstart', ontouchstart);
  6092. util.addEventListener(this.frame.canvas, 'mousewheel', onmousewheel);
  6093. util.addEventListener(this.frame.canvas, 'mousemove', ontooltip);
  6094. // add the new graph to the container element
  6095. this.containerElement.appendChild(this.frame);
  6096. };
  6097. /**
  6098. * Set a new size for the graph
  6099. * @param {string} width Width in pixels or percentage (for example '800px'
  6100. * or '50%')
  6101. * @param {string} height Height in pixels or percentage (for example '400px'
  6102. * or '30%')
  6103. */
  6104. Graph3d.prototype.setSize = function(width, height) {
  6105. this.frame.style.width = width;
  6106. this.frame.style.height = height;
  6107. this._resizeCanvas();
  6108. };
  6109. /**
  6110. * Resize the canvas to the current size of the frame
  6111. */
  6112. Graph3d.prototype._resizeCanvas = function() {
  6113. this.frame.canvas.style.width = '100%';
  6114. this.frame.canvas.style.height = '100%';
  6115. this.frame.canvas.width = this.frame.canvas.clientWidth;
  6116. this.frame.canvas.height = this.frame.canvas.clientHeight;
  6117. // adjust with for margin
  6118. this.frame.filter.style.width = (this.frame.canvas.clientWidth - 2 * 10) + 'px';
  6119. };
  6120. /**
  6121. * Start animation
  6122. */
  6123. Graph3d.prototype.animationStart = function() {
  6124. if (!this.frame.filter || !this.frame.filter.slider)
  6125. throw 'No animation available';
  6126. this.frame.filter.slider.play();
  6127. };
  6128. /**
  6129. * Stop animation
  6130. */
  6131. Graph3d.prototype.animationStop = function() {
  6132. if (!this.frame.filter || !this.frame.filter.slider) return;
  6133. this.frame.filter.slider.stop();
  6134. };
  6135. /**
  6136. * Resize the center position based on the current values in this.defaultXCenter
  6137. * and this.defaultYCenter (which are strings with a percentage or a value
  6138. * in pixels). The center positions are the variables this.xCenter
  6139. * and this.yCenter
  6140. */
  6141. Graph3d.prototype._resizeCenter = function() {
  6142. // calculate the horizontal center position
  6143. if (this.defaultXCenter.charAt(this.defaultXCenter.length-1) === '%') {
  6144. this.xcenter =
  6145. parseFloat(this.defaultXCenter) / 100 *
  6146. this.frame.canvas.clientWidth;
  6147. }
  6148. else {
  6149. this.xcenter = parseFloat(this.defaultXCenter); // supposed to be in px
  6150. }
  6151. // calculate the vertical center position
  6152. if (this.defaultYCenter.charAt(this.defaultYCenter.length-1) === '%') {
  6153. this.ycenter =
  6154. parseFloat(this.defaultYCenter) / 100 *
  6155. (this.frame.canvas.clientHeight - this.frame.filter.clientHeight);
  6156. }
  6157. else {
  6158. this.ycenter = parseFloat(this.defaultYCenter); // supposed to be in px
  6159. }
  6160. };
  6161. /**
  6162. * Set the rotation and distance of the camera
  6163. * @param {Object} pos An object with the camera position. The object
  6164. * contains three parameters:
  6165. * - horizontal {Number}
  6166. * The horizontal rotation, between 0 and 2*PI.
  6167. * Optional, can be left undefined.
  6168. * - vertical {Number}
  6169. * The vertical rotation, between 0 and 0.5*PI
  6170. * if vertical=0.5*PI, the graph is shown from the
  6171. * top. Optional, can be left undefined.
  6172. * - distance {Number}
  6173. * The (normalized) distance of the camera to the
  6174. * center of the graph, a value between 0.71 and 5.0.
  6175. * Optional, can be left undefined.
  6176. */
  6177. Graph3d.prototype.setCameraPosition = function(pos) {
  6178. if (pos === undefined) {
  6179. return;
  6180. }
  6181. if (pos.horizontal !== undefined && pos.vertical !== undefined) {
  6182. this.camera.setArmRotation(pos.horizontal, pos.vertical);
  6183. }
  6184. if (pos.distance !== undefined) {
  6185. this.camera.setArmLength(pos.distance);
  6186. }
  6187. this.redraw();
  6188. };
  6189. /**
  6190. * Retrieve the current camera rotation
  6191. * @return {object} An object with parameters horizontal, vertical, and
  6192. * distance
  6193. */
  6194. Graph3d.prototype.getCameraPosition = function() {
  6195. var pos = this.camera.getArmRotation();
  6196. pos.distance = this.camera.getArmLength();
  6197. return pos;
  6198. };
  6199. /**
  6200. * Load data into the 3D Graph
  6201. */
  6202. Graph3d.prototype._readData = function(data) {
  6203. // read the data
  6204. this._dataInitialize(data, this.style);
  6205. if (this.dataFilter) {
  6206. // apply filtering
  6207. this.dataPoints = this.dataFilter._getDataPoints();
  6208. }
  6209. else {
  6210. // no filtering. load all data
  6211. this.dataPoints = this._getDataPoints(this.dataTable);
  6212. }
  6213. // draw the filter
  6214. this._redrawFilter();
  6215. };
  6216. /**
  6217. * Replace the dataset of the Graph3d
  6218. * @param {Array | DataSet | DataView} data
  6219. */
  6220. Graph3d.prototype.setData = function (data) {
  6221. this._readData(data);
  6222. this.redraw();
  6223. // start animation when option is true
  6224. if (this.animationAutoStart && this.dataFilter) {
  6225. this.animationStart();
  6226. }
  6227. };
  6228. /**
  6229. * Update the options. Options will be merged with current options
  6230. * @param {Object} options
  6231. */
  6232. Graph3d.prototype.setOptions = function (options) {
  6233. var cameraPosition = undefined;
  6234. this.animationStop();
  6235. if (options !== undefined) {
  6236. // retrieve parameter values
  6237. if (options.width !== undefined) this.width = options.width;
  6238. if (options.height !== undefined) this.height = options.height;
  6239. if (options.xCenter !== undefined) this.defaultXCenter = options.xCenter;
  6240. if (options.yCenter !== undefined) this.defaultYCenter = options.yCenter;
  6241. if (options.filterLabel !== undefined) this.filterLabel = options.filterLabel;
  6242. if (options.legendLabel !== undefined) this.legendLabel = options.legendLabel;
  6243. if (options.xLabel !== undefined) this.xLabel = options.xLabel;
  6244. if (options.yLabel !== undefined) this.yLabel = options.yLabel;
  6245. if (options.zLabel !== undefined) this.zLabel = options.zLabel;
  6246. if (options.xValueLabel !== undefined) this.xValueLabel = options.xValueLabel;
  6247. if (options.yValueLabel !== undefined) this.yValueLabel = options.yValueLabel;
  6248. if (options.zValueLabel !== undefined) this.zValueLabel = options.zValueLabel;
  6249. if (options.style !== undefined) {
  6250. var styleNumber = this._getStyleNumber(options.style);
  6251. if (styleNumber !== -1) {
  6252. this.style = styleNumber;
  6253. }
  6254. }
  6255. if (options.showGrid !== undefined) this.showGrid = options.showGrid;
  6256. if (options.showPerspective !== undefined) this.showPerspective = options.showPerspective;
  6257. if (options.showShadow !== undefined) this.showShadow = options.showShadow;
  6258. if (options.tooltip !== undefined) this.showTooltip = options.tooltip;
  6259. if (options.showAnimationControls !== undefined) this.showAnimationControls = options.showAnimationControls;
  6260. if (options.keepAspectRatio !== undefined) this.keepAspectRatio = options.keepAspectRatio;
  6261. if (options.verticalRatio !== undefined) this.verticalRatio = options.verticalRatio;
  6262. if (options.animationInterval !== undefined) this.animationInterval = options.animationInterval;
  6263. if (options.animationPreload !== undefined) this.animationPreload = options.animationPreload;
  6264. if (options.animationAutoStart !== undefined)this.animationAutoStart = options.animationAutoStart;
  6265. if (options.xBarWidth !== undefined) this.defaultXBarWidth = options.xBarWidth;
  6266. if (options.yBarWidth !== undefined) this.defaultYBarWidth = options.yBarWidth;
  6267. if (options.xMin !== undefined) this.defaultXMin = options.xMin;
  6268. if (options.xStep !== undefined) this.defaultXStep = options.xStep;
  6269. if (options.xMax !== undefined) this.defaultXMax = options.xMax;
  6270. if (options.yMin !== undefined) this.defaultYMin = options.yMin;
  6271. if (options.yStep !== undefined) this.defaultYStep = options.yStep;
  6272. if (options.yMax !== undefined) this.defaultYMax = options.yMax;
  6273. if (options.zMin !== undefined) this.defaultZMin = options.zMin;
  6274. if (options.zStep !== undefined) this.defaultZStep = options.zStep;
  6275. if (options.zMax !== undefined) this.defaultZMax = options.zMax;
  6276. if (options.valueMin !== undefined) this.defaultValueMin = options.valueMin;
  6277. if (options.valueMax !== undefined) this.defaultValueMax = options.valueMax;
  6278. if (options.cameraPosition !== undefined) cameraPosition = options.cameraPosition;
  6279. if (cameraPosition !== undefined) {
  6280. this.camera.setArmRotation(cameraPosition.horizontal, cameraPosition.vertical);
  6281. this.camera.setArmLength(cameraPosition.distance);
  6282. }
  6283. else {
  6284. this.camera.setArmRotation(1.0, 0.5);
  6285. this.camera.setArmLength(1.7);
  6286. }
  6287. }
  6288. this._setBackgroundColor(options && options.backgroundColor);
  6289. this.setSize(this.width, this.height);
  6290. // re-load the data
  6291. if (this.dataTable) {
  6292. this.setData(this.dataTable);
  6293. }
  6294. // start animation when option is true
  6295. if (this.animationAutoStart && this.dataFilter) {
  6296. this.animationStart();
  6297. }
  6298. };
  6299. /**
  6300. * Redraw the Graph.
  6301. */
  6302. Graph3d.prototype.redraw = function() {
  6303. if (this.dataPoints === undefined) {
  6304. throw 'Error: graph data not initialized';
  6305. }
  6306. this._resizeCanvas();
  6307. this._resizeCenter();
  6308. this._redrawSlider();
  6309. this._redrawClear();
  6310. this._redrawAxis();
  6311. if (this.style === Graph3d.STYLE.GRID ||
  6312. this.style === Graph3d.STYLE.SURFACE) {
  6313. this._redrawDataGrid();
  6314. }
  6315. else if (this.style === Graph3d.STYLE.LINE) {
  6316. this._redrawDataLine();
  6317. }
  6318. else if (this.style === Graph3d.STYLE.BAR ||
  6319. this.style === Graph3d.STYLE.BARCOLOR ||
  6320. this.style === Graph3d.STYLE.BARSIZE) {
  6321. this._redrawDataBar();
  6322. }
  6323. else {
  6324. // style is DOT, DOTLINE, DOTCOLOR, DOTSIZE
  6325. this._redrawDataDot();
  6326. }
  6327. this._redrawInfo();
  6328. this._redrawLegend();
  6329. };
  6330. /**
  6331. * Clear the canvas before redrawing
  6332. */
  6333. Graph3d.prototype._redrawClear = function() {
  6334. var canvas = this.frame.canvas;
  6335. var ctx = canvas.getContext('2d');
  6336. ctx.clearRect(0, 0, canvas.width, canvas.height);
  6337. };
  6338. /**
  6339. * Redraw the legend showing the colors
  6340. */
  6341. Graph3d.prototype._redrawLegend = function() {
  6342. var y;
  6343. if (this.style === Graph3d.STYLE.DOTCOLOR ||
  6344. this.style === Graph3d.STYLE.DOTSIZE) {
  6345. var dotSize = this.frame.clientWidth * 0.02;
  6346. var widthMin, widthMax;
  6347. if (this.style === Graph3d.STYLE.DOTSIZE) {
  6348. widthMin = dotSize / 2; // px
  6349. widthMax = dotSize / 2 + dotSize * 2; // Todo: put this in one function
  6350. }
  6351. else {
  6352. widthMin = 20; // px
  6353. widthMax = 20; // px
  6354. }
  6355. var height = Math.max(this.frame.clientHeight * 0.25, 100);
  6356. var top = this.margin;
  6357. var right = this.frame.clientWidth - this.margin;
  6358. var left = right - widthMax;
  6359. var bottom = top + height;
  6360. }
  6361. var canvas = this.frame.canvas;
  6362. var ctx = canvas.getContext('2d');
  6363. ctx.lineWidth = 1;
  6364. ctx.font = '14px arial'; // TODO: put in options
  6365. if (this.style === Graph3d.STYLE.DOTCOLOR) {
  6366. // draw the color bar
  6367. var ymin = 0;
  6368. var ymax = height; // Todo: make height customizable
  6369. for (y = ymin; y < ymax; y++) {
  6370. var f = (y - ymin) / (ymax - ymin);
  6371. //var width = (dotSize / 2 + (1-f) * dotSize * 2); // Todo: put this in one function
  6372. var hue = f * 240;
  6373. var color = this._hsv2rgb(hue, 1, 1);
  6374. ctx.strokeStyle = color;
  6375. ctx.beginPath();
  6376. ctx.moveTo(left, top + y);
  6377. ctx.lineTo(right, top + y);
  6378. ctx.stroke();
  6379. }
  6380. ctx.strokeStyle = this.colorAxis;
  6381. ctx.strokeRect(left, top, widthMax, height);
  6382. }
  6383. if (this.style === Graph3d.STYLE.DOTSIZE) {
  6384. // draw border around color bar
  6385. ctx.strokeStyle = this.colorAxis;
  6386. ctx.fillStyle = this.colorDot;
  6387. ctx.beginPath();
  6388. ctx.moveTo(left, top);
  6389. ctx.lineTo(right, top);
  6390. ctx.lineTo(right - widthMax + widthMin, bottom);
  6391. ctx.lineTo(left, bottom);
  6392. ctx.closePath();
  6393. ctx.fill();
  6394. ctx.stroke();
  6395. }
  6396. if (this.style === Graph3d.STYLE.DOTCOLOR ||
  6397. this.style === Graph3d.STYLE.DOTSIZE) {
  6398. // print values along the color bar
  6399. var gridLineLen = 5; // px
  6400. var step = new StepNumber(this.valueMin, this.valueMax, (this.valueMax-this.valueMin)/5, true);
  6401. step.start();
  6402. if (step.getCurrent() < this.valueMin) {
  6403. step.next();
  6404. }
  6405. while (!step.end()) {
  6406. y = bottom - (step.getCurrent() - this.valueMin) / (this.valueMax - this.valueMin) * height;
  6407. ctx.beginPath();
  6408. ctx.moveTo(left - gridLineLen, y);
  6409. ctx.lineTo(left, y);
  6410. ctx.stroke();
  6411. ctx.textAlign = 'right';
  6412. ctx.textBaseline = 'middle';
  6413. ctx.fillStyle = this.colorAxis;
  6414. ctx.fillText(step.getCurrent(), left - 2 * gridLineLen, y);
  6415. step.next();
  6416. }
  6417. ctx.textAlign = 'right';
  6418. ctx.textBaseline = 'top';
  6419. var label = this.legendLabel;
  6420. ctx.fillText(label, right, bottom + this.margin);
  6421. }
  6422. };
  6423. /**
  6424. * Redraw the filter
  6425. */
  6426. Graph3d.prototype._redrawFilter = function() {
  6427. this.frame.filter.innerHTML = '';
  6428. if (this.dataFilter) {
  6429. var options = {
  6430. 'visible': this.showAnimationControls
  6431. };
  6432. var slider = new Slider(this.frame.filter, options);
  6433. this.frame.filter.slider = slider;
  6434. // TODO: css here is not nice here...
  6435. this.frame.filter.style.padding = '10px';
  6436. //this.frame.filter.style.backgroundColor = '#EFEFEF';
  6437. slider.setValues(this.dataFilter.values);
  6438. slider.setPlayInterval(this.animationInterval);
  6439. // create an event handler
  6440. var me = this;
  6441. var onchange = function () {
  6442. var index = slider.getIndex();
  6443. me.dataFilter.selectValue(index);
  6444. me.dataPoints = me.dataFilter._getDataPoints();
  6445. me.redraw();
  6446. };
  6447. slider.setOnChangeCallback(onchange);
  6448. }
  6449. else {
  6450. this.frame.filter.slider = undefined;
  6451. }
  6452. };
  6453. /**
  6454. * Redraw the slider
  6455. */
  6456. Graph3d.prototype._redrawSlider = function() {
  6457. if ( this.frame.filter.slider !== undefined) {
  6458. this.frame.filter.slider.redraw();
  6459. }
  6460. };
  6461. /**
  6462. * Redraw common information
  6463. */
  6464. Graph3d.prototype._redrawInfo = function() {
  6465. if (this.dataFilter) {
  6466. var canvas = this.frame.canvas;
  6467. var ctx = canvas.getContext('2d');
  6468. ctx.font = '14px arial'; // TODO: put in options
  6469. ctx.lineStyle = 'gray';
  6470. ctx.fillStyle = 'gray';
  6471. ctx.textAlign = 'left';
  6472. ctx.textBaseline = 'top';
  6473. var x = this.margin;
  6474. var y = this.margin;
  6475. ctx.fillText(this.dataFilter.getLabel() + ': ' + this.dataFilter.getSelectedValue(), x, y);
  6476. }
  6477. };
  6478. /**
  6479. * Redraw the axis
  6480. */
  6481. Graph3d.prototype._redrawAxis = function() {
  6482. var canvas = this.frame.canvas,
  6483. ctx = canvas.getContext('2d'),
  6484. from, to, step, prettyStep,
  6485. text, xText, yText, zText,
  6486. offset, xOffset, yOffset,
  6487. xMin2d, xMax2d;
  6488. // TODO: get the actual rendered style of the containerElement
  6489. //ctx.font = this.containerElement.style.font;
  6490. ctx.font = 24 / this.camera.getArmLength() + 'px arial';
  6491. // calculate the length for the short grid lines
  6492. var gridLenX = 0.025 / this.scale.x;
  6493. var gridLenY = 0.025 / this.scale.y;
  6494. var textMargin = 5 / this.camera.getArmLength(); // px
  6495. var armAngle = this.camera.getArmRotation().horizontal;
  6496. // draw x-grid lines
  6497. ctx.lineWidth = 1;
  6498. prettyStep = (this.defaultXStep === undefined);
  6499. step = new StepNumber(this.xMin, this.xMax, this.xStep, prettyStep);
  6500. step.start();
  6501. if (step.getCurrent() < this.xMin) {
  6502. step.next();
  6503. }
  6504. while (!step.end()) {
  6505. var x = step.getCurrent();
  6506. if (this.showGrid) {
  6507. from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin));
  6508. to = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin));
  6509. ctx.strokeStyle = this.colorGrid;
  6510. ctx.beginPath();
  6511. ctx.moveTo(from.x, from.y);
  6512. ctx.lineTo(to.x, to.y);
  6513. ctx.stroke();
  6514. }
  6515. else {
  6516. from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin));
  6517. to = this._convert3Dto2D(new Point3d(x, this.yMin+gridLenX, this.zMin));
  6518. ctx.strokeStyle = this.colorAxis;
  6519. ctx.beginPath();
  6520. ctx.moveTo(from.x, from.y);
  6521. ctx.lineTo(to.x, to.y);
  6522. ctx.stroke();
  6523. from = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin));
  6524. to = this._convert3Dto2D(new Point3d(x, this.yMax-gridLenX, this.zMin));
  6525. ctx.strokeStyle = this.colorAxis;
  6526. ctx.beginPath();
  6527. ctx.moveTo(from.x, from.y);
  6528. ctx.lineTo(to.x, to.y);
  6529. ctx.stroke();
  6530. }
  6531. yText = (Math.cos(armAngle) > 0) ? this.yMin : this.yMax;
  6532. text = this._convert3Dto2D(new Point3d(x, yText, this.zMin));
  6533. if (Math.cos(armAngle * 2) > 0) {
  6534. ctx.textAlign = 'center';
  6535. ctx.textBaseline = 'top';
  6536. text.y += textMargin;
  6537. }
  6538. else if (Math.sin(armAngle * 2) < 0){
  6539. ctx.textAlign = 'right';
  6540. ctx.textBaseline = 'middle';
  6541. }
  6542. else {
  6543. ctx.textAlign = 'left';
  6544. ctx.textBaseline = 'middle';
  6545. }
  6546. ctx.fillStyle = this.colorAxis;
  6547. ctx.fillText(' ' + this.xValueLabel(step.getCurrent()) + ' ', text.x, text.y);
  6548. step.next();
  6549. }
  6550. // draw y-grid lines
  6551. ctx.lineWidth = 1;
  6552. prettyStep = (this.defaultYStep === undefined);
  6553. step = new StepNumber(this.yMin, this.yMax, this.yStep, prettyStep);
  6554. step.start();
  6555. if (step.getCurrent() < this.yMin) {
  6556. step.next();
  6557. }
  6558. while (!step.end()) {
  6559. if (this.showGrid) {
  6560. from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin));
  6561. to = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin));
  6562. ctx.strokeStyle = this.colorGrid;
  6563. ctx.beginPath();
  6564. ctx.moveTo(from.x, from.y);
  6565. ctx.lineTo(to.x, to.y);
  6566. ctx.stroke();
  6567. }
  6568. else {
  6569. from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin));
  6570. to = this._convert3Dto2D(new Point3d(this.xMin+gridLenY, step.getCurrent(), this.zMin));
  6571. ctx.strokeStyle = this.colorAxis;
  6572. ctx.beginPath();
  6573. ctx.moveTo(from.x, from.y);
  6574. ctx.lineTo(to.x, to.y);
  6575. ctx.stroke();
  6576. from = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin));
  6577. to = this._convert3Dto2D(new Point3d(this.xMax-gridLenY, step.getCurrent(), this.zMin));
  6578. ctx.strokeStyle = this.colorAxis;
  6579. ctx.beginPath();
  6580. ctx.moveTo(from.x, from.y);
  6581. ctx.lineTo(to.x, to.y);
  6582. ctx.stroke();
  6583. }
  6584. xText = (Math.sin(armAngle ) > 0) ? this.xMin : this.xMax;
  6585. text = this._convert3Dto2D(new Point3d(xText, step.getCurrent(), this.zMin));
  6586. if (Math.cos(armAngle * 2) < 0) {
  6587. ctx.textAlign = 'center';
  6588. ctx.textBaseline = 'top';
  6589. text.y += textMargin;
  6590. }
  6591. else if (Math.sin(armAngle * 2) > 0){
  6592. ctx.textAlign = 'right';
  6593. ctx.textBaseline = 'middle';
  6594. }
  6595. else {
  6596. ctx.textAlign = 'left';
  6597. ctx.textBaseline = 'middle';
  6598. }
  6599. ctx.fillStyle = this.colorAxis;
  6600. ctx.fillText(' ' + this.yValueLabel(step.getCurrent()) + ' ', text.x, text.y);
  6601. step.next();
  6602. }
  6603. // draw z-grid lines and axis
  6604. ctx.lineWidth = 1;
  6605. prettyStep = (this.defaultZStep === undefined);
  6606. step = new StepNumber(this.zMin, this.zMax, this.zStep, prettyStep);
  6607. step.start();
  6608. if (step.getCurrent() < this.zMin) {
  6609. step.next();
  6610. }
  6611. xText = (Math.cos(armAngle ) > 0) ? this.xMin : this.xMax;
  6612. yText = (Math.sin(armAngle ) < 0) ? this.yMin : this.yMax;
  6613. while (!step.end()) {
  6614. // TODO: make z-grid lines really 3d?
  6615. from = this._convert3Dto2D(new Point3d(xText, yText, step.getCurrent()));
  6616. ctx.strokeStyle = this.colorAxis;
  6617. ctx.beginPath();
  6618. ctx.moveTo(from.x, from.y);
  6619. ctx.lineTo(from.x - textMargin, from.y);
  6620. ctx.stroke();
  6621. ctx.textAlign = 'right';
  6622. ctx.textBaseline = 'middle';
  6623. ctx.fillStyle = this.colorAxis;
  6624. ctx.fillText(this.zValueLabel(step.getCurrent()) + ' ', from.x - 5, from.y);
  6625. step.next();
  6626. }
  6627. ctx.lineWidth = 1;
  6628. from = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  6629. to = this._convert3Dto2D(new Point3d(xText, yText, this.zMax));
  6630. ctx.strokeStyle = this.colorAxis;
  6631. ctx.beginPath();
  6632. ctx.moveTo(from.x, from.y);
  6633. ctx.lineTo(to.x, to.y);
  6634. ctx.stroke();
  6635. // draw x-axis
  6636. ctx.lineWidth = 1;
  6637. // line at yMin
  6638. xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin));
  6639. xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin));
  6640. ctx.strokeStyle = this.colorAxis;
  6641. ctx.beginPath();
  6642. ctx.moveTo(xMin2d.x, xMin2d.y);
  6643. ctx.lineTo(xMax2d.x, xMax2d.y);
  6644. ctx.stroke();
  6645. // line at ymax
  6646. xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin));
  6647. xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin));
  6648. ctx.strokeStyle = this.colorAxis;
  6649. ctx.beginPath();
  6650. ctx.moveTo(xMin2d.x, xMin2d.y);
  6651. ctx.lineTo(xMax2d.x, xMax2d.y);
  6652. ctx.stroke();
  6653. // draw y-axis
  6654. ctx.lineWidth = 1;
  6655. // line at xMin
  6656. from = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin));
  6657. to = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin));
  6658. ctx.strokeStyle = this.colorAxis;
  6659. ctx.beginPath();
  6660. ctx.moveTo(from.x, from.y);
  6661. ctx.lineTo(to.x, to.y);
  6662. ctx.stroke();
  6663. // line at xMax
  6664. from = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin));
  6665. to = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin));
  6666. ctx.strokeStyle = this.colorAxis;
  6667. ctx.beginPath();
  6668. ctx.moveTo(from.x, from.y);
  6669. ctx.lineTo(to.x, to.y);
  6670. ctx.stroke();
  6671. // draw x-label
  6672. var xLabel = this.xLabel;
  6673. if (xLabel.length > 0) {
  6674. yOffset = 0.1 / this.scale.y;
  6675. xText = (this.xMin + this.xMax) / 2;
  6676. yText = (Math.cos(armAngle) > 0) ? this.yMin - yOffset: this.yMax + yOffset;
  6677. text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  6678. if (Math.cos(armAngle * 2) > 0) {
  6679. ctx.textAlign = 'center';
  6680. ctx.textBaseline = 'top';
  6681. }
  6682. else if (Math.sin(armAngle * 2) < 0){
  6683. ctx.textAlign = 'right';
  6684. ctx.textBaseline = 'middle';
  6685. }
  6686. else {
  6687. ctx.textAlign = 'left';
  6688. ctx.textBaseline = 'middle';
  6689. }
  6690. ctx.fillStyle = this.colorAxis;
  6691. ctx.fillText(xLabel, text.x, text.y);
  6692. }
  6693. // draw y-label
  6694. var yLabel = this.yLabel;
  6695. if (yLabel.length > 0) {
  6696. xOffset = 0.1 / this.scale.x;
  6697. xText = (Math.sin(armAngle ) > 0) ? this.xMin - xOffset : this.xMax + xOffset;
  6698. yText = (this.yMin + this.yMax) / 2;
  6699. text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  6700. if (Math.cos(armAngle * 2) < 0) {
  6701. ctx.textAlign = 'center';
  6702. ctx.textBaseline = 'top';
  6703. }
  6704. else if (Math.sin(armAngle * 2) > 0){
  6705. ctx.textAlign = 'right';
  6706. ctx.textBaseline = 'middle';
  6707. }
  6708. else {
  6709. ctx.textAlign = 'left';
  6710. ctx.textBaseline = 'middle';
  6711. }
  6712. ctx.fillStyle = this.colorAxis;
  6713. ctx.fillText(yLabel, text.x, text.y);
  6714. }
  6715. // draw z-label
  6716. var zLabel = this.zLabel;
  6717. if (zLabel.length > 0) {
  6718. offset = 30; // pixels. // TODO: relate to the max width of the values on the z axis?
  6719. xText = (Math.cos(armAngle ) > 0) ? this.xMin : this.xMax;
  6720. yText = (Math.sin(armAngle ) < 0) ? this.yMin : this.yMax;
  6721. zText = (this.zMin + this.zMax) / 2;
  6722. text = this._convert3Dto2D(new Point3d(xText, yText, zText));
  6723. ctx.textAlign = 'right';
  6724. ctx.textBaseline = 'middle';
  6725. ctx.fillStyle = this.colorAxis;
  6726. ctx.fillText(zLabel, text.x - offset, text.y);
  6727. }
  6728. };
  6729. /**
  6730. * Calculate the color based on the given value.
  6731. * @param {Number} H Hue, a value be between 0 and 360
  6732. * @param {Number} S Saturation, a value between 0 and 1
  6733. * @param {Number} V Value, a value between 0 and 1
  6734. */
  6735. Graph3d.prototype._hsv2rgb = function(H, S, V) {
  6736. var R, G, B, C, Hi, X;
  6737. C = V * S;
  6738. Hi = Math.floor(H/60); // hi = 0,1,2,3,4,5
  6739. X = C * (1 - Math.abs(((H/60) % 2) - 1));
  6740. switch (Hi) {
  6741. case 0: R = C; G = X; B = 0; break;
  6742. case 1: R = X; G = C; B = 0; break;
  6743. case 2: R = 0; G = C; B = X; break;
  6744. case 3: R = 0; G = X; B = C; break;
  6745. case 4: R = X; G = 0; B = C; break;
  6746. case 5: R = C; G = 0; B = X; break;
  6747. default: R = 0; G = 0; B = 0; break;
  6748. }
  6749. return 'RGB(' + parseInt(R*255) + ',' + parseInt(G*255) + ',' + parseInt(B*255) + ')';
  6750. };
  6751. /**
  6752. * Draw all datapoints as a grid
  6753. * This function can be used when the style is 'grid'
  6754. */
  6755. Graph3d.prototype._redrawDataGrid = function() {
  6756. var canvas = this.frame.canvas,
  6757. ctx = canvas.getContext('2d'),
  6758. point, right, top, cross,
  6759. i,
  6760. topSideVisible, fillStyle, strokeStyle, lineWidth,
  6761. h, s, v, zAvg;
  6762. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  6763. return; // TODO: throw exception?
  6764. // calculate the translations and screen position of all points
  6765. for (i = 0; i < this.dataPoints.length; i++) {
  6766. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  6767. var screen = this._convertTranslationToScreen(trans);
  6768. this.dataPoints[i].trans = trans;
  6769. this.dataPoints[i].screen = screen;
  6770. // calculate the translation of the point at the bottom (needed for sorting)
  6771. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  6772. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  6773. }
  6774. // sort the points on depth of their (x,y) position (not on z)
  6775. var sortDepth = function (a, b) {
  6776. return b.dist - a.dist;
  6777. };
  6778. this.dataPoints.sort(sortDepth);
  6779. if (this.style === Graph3d.STYLE.SURFACE) {
  6780. for (i = 0; i < this.dataPoints.length; i++) {
  6781. point = this.dataPoints[i];
  6782. right = this.dataPoints[i].pointRight;
  6783. top = this.dataPoints[i].pointTop;
  6784. cross = this.dataPoints[i].pointCross;
  6785. if (point !== undefined && right !== undefined && top !== undefined && cross !== undefined) {
  6786. if (this.showGrayBottom || this.showShadow) {
  6787. // calculate the cross product of the two vectors from center
  6788. // to left and right, in order to know whether we are looking at the
  6789. // bottom or at the top side. We can also use the cross product
  6790. // for calculating light intensity
  6791. var aDiff = Point3d.subtract(cross.trans, point.trans);
  6792. var bDiff = Point3d.subtract(top.trans, right.trans);
  6793. var crossproduct = Point3d.crossProduct(aDiff, bDiff);
  6794. var len = crossproduct.length();
  6795. // FIXME: there is a bug with determining the surface side (shadow or colored)
  6796. topSideVisible = (crossproduct.z > 0);
  6797. }
  6798. else {
  6799. topSideVisible = true;
  6800. }
  6801. if (topSideVisible) {
  6802. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  6803. zAvg = (point.point.z + right.point.z + top.point.z + cross.point.z) / 4;
  6804. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  6805. s = 1; // saturation
  6806. if (this.showShadow) {
  6807. v = Math.min(1 + (crossproduct.x / len) / 2, 1); // value. TODO: scale
  6808. fillStyle = this._hsv2rgb(h, s, v);
  6809. strokeStyle = fillStyle;
  6810. }
  6811. else {
  6812. v = 1;
  6813. fillStyle = this._hsv2rgb(h, s, v);
  6814. strokeStyle = this.colorAxis;
  6815. }
  6816. }
  6817. else {
  6818. fillStyle = 'gray';
  6819. strokeStyle = this.colorAxis;
  6820. }
  6821. lineWidth = 0.5;
  6822. ctx.lineWidth = lineWidth;
  6823. ctx.fillStyle = fillStyle;
  6824. ctx.strokeStyle = strokeStyle;
  6825. ctx.beginPath();
  6826. ctx.moveTo(point.screen.x, point.screen.y);
  6827. ctx.lineTo(right.screen.x, right.screen.y);
  6828. ctx.lineTo(cross.screen.x, cross.screen.y);
  6829. ctx.lineTo(top.screen.x, top.screen.y);
  6830. ctx.closePath();
  6831. ctx.fill();
  6832. ctx.stroke();
  6833. }
  6834. }
  6835. }
  6836. else { // grid style
  6837. for (i = 0; i < this.dataPoints.length; i++) {
  6838. point = this.dataPoints[i];
  6839. right = this.dataPoints[i].pointRight;
  6840. top = this.dataPoints[i].pointTop;
  6841. if (point !== undefined) {
  6842. if (this.showPerspective) {
  6843. lineWidth = 2 / -point.trans.z;
  6844. }
  6845. else {
  6846. lineWidth = 2 * -(this.eye.z / this.camera.getArmLength());
  6847. }
  6848. }
  6849. if (point !== undefined && right !== undefined) {
  6850. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  6851. zAvg = (point.point.z + right.point.z) / 2;
  6852. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  6853. ctx.lineWidth = lineWidth;
  6854. ctx.strokeStyle = this._hsv2rgb(h, 1, 1);
  6855. ctx.beginPath();
  6856. ctx.moveTo(point.screen.x, point.screen.y);
  6857. ctx.lineTo(right.screen.x, right.screen.y);
  6858. ctx.stroke();
  6859. }
  6860. if (point !== undefined && top !== undefined) {
  6861. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  6862. zAvg = (point.point.z + top.point.z) / 2;
  6863. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  6864. ctx.lineWidth = lineWidth;
  6865. ctx.strokeStyle = this._hsv2rgb(h, 1, 1);
  6866. ctx.beginPath();
  6867. ctx.moveTo(point.screen.x, point.screen.y);
  6868. ctx.lineTo(top.screen.x, top.screen.y);
  6869. ctx.stroke();
  6870. }
  6871. }
  6872. }
  6873. };
  6874. /**
  6875. * Draw all datapoints as dots.
  6876. * This function can be used when the style is 'dot' or 'dot-line'
  6877. */
  6878. Graph3d.prototype._redrawDataDot = function() {
  6879. var canvas = this.frame.canvas;
  6880. var ctx = canvas.getContext('2d');
  6881. var i;
  6882. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  6883. return; // TODO: throw exception?
  6884. // calculate the translations of all points
  6885. for (i = 0; i < this.dataPoints.length; i++) {
  6886. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  6887. var screen = this._convertTranslationToScreen(trans);
  6888. this.dataPoints[i].trans = trans;
  6889. this.dataPoints[i].screen = screen;
  6890. // calculate the distance from the point at the bottom to the camera
  6891. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  6892. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  6893. }
  6894. // order the translated points by depth
  6895. var sortDepth = function (a, b) {
  6896. return b.dist - a.dist;
  6897. };
  6898. this.dataPoints.sort(sortDepth);
  6899. // draw the datapoints as colored circles
  6900. var dotSize = this.frame.clientWidth * 0.02; // px
  6901. for (i = 0; i < this.dataPoints.length; i++) {
  6902. var point = this.dataPoints[i];
  6903. if (this.style === Graph3d.STYLE.DOTLINE) {
  6904. // draw a vertical line from the bottom to the graph value
  6905. //var from = this._convert3Dto2D(new Point3d(point.point.x, point.point.y, this.zMin));
  6906. var from = this._convert3Dto2D(point.bottom);
  6907. ctx.lineWidth = 1;
  6908. ctx.strokeStyle = this.colorGrid;
  6909. ctx.beginPath();
  6910. ctx.moveTo(from.x, from.y);
  6911. ctx.lineTo(point.screen.x, point.screen.y);
  6912. ctx.stroke();
  6913. }
  6914. // calculate radius for the circle
  6915. var size;
  6916. if (this.style === Graph3d.STYLE.DOTSIZE) {
  6917. size = dotSize/2 + 2*dotSize * (point.point.value - this.valueMin) / (this.valueMax - this.valueMin);
  6918. }
  6919. else {
  6920. size = dotSize;
  6921. }
  6922. var radius;
  6923. if (this.showPerspective) {
  6924. radius = size / -point.trans.z;
  6925. }
  6926. else {
  6927. radius = size * -(this.eye.z / this.camera.getArmLength());
  6928. }
  6929. if (radius < 0) {
  6930. radius = 0;
  6931. }
  6932. var hue, color, borderColor;
  6933. if (this.style === Graph3d.STYLE.DOTCOLOR ) {
  6934. // calculate the color based on the value
  6935. hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240;
  6936. color = this._hsv2rgb(hue, 1, 1);
  6937. borderColor = this._hsv2rgb(hue, 1, 0.8);
  6938. }
  6939. else if (this.style === Graph3d.STYLE.DOTSIZE) {
  6940. color = this.colorDot;
  6941. borderColor = this.colorDotBorder;
  6942. }
  6943. else {
  6944. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  6945. hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  6946. color = this._hsv2rgb(hue, 1, 1);
  6947. borderColor = this._hsv2rgb(hue, 1, 0.8);
  6948. }
  6949. // draw the circle
  6950. ctx.lineWidth = 1.0;
  6951. ctx.strokeStyle = borderColor;
  6952. ctx.fillStyle = color;
  6953. ctx.beginPath();
  6954. ctx.arc(point.screen.x, point.screen.y, radius, 0, Math.PI*2, true);
  6955. ctx.fill();
  6956. ctx.stroke();
  6957. }
  6958. };
  6959. /**
  6960. * Draw all datapoints as bars.
  6961. * This function can be used when the style is 'bar', 'bar-color', or 'bar-size'
  6962. */
  6963. Graph3d.prototype._redrawDataBar = function() {
  6964. var canvas = this.frame.canvas;
  6965. var ctx = canvas.getContext('2d');
  6966. var i, j, surface, corners;
  6967. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  6968. return; // TODO: throw exception?
  6969. // calculate the translations of all points
  6970. for (i = 0; i < this.dataPoints.length; i++) {
  6971. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  6972. var screen = this._convertTranslationToScreen(trans);
  6973. this.dataPoints[i].trans = trans;
  6974. this.dataPoints[i].screen = screen;
  6975. // calculate the distance from the point at the bottom to the camera
  6976. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  6977. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  6978. }
  6979. // order the translated points by depth
  6980. var sortDepth = function (a, b) {
  6981. return b.dist - a.dist;
  6982. };
  6983. this.dataPoints.sort(sortDepth);
  6984. // draw the datapoints as bars
  6985. var xWidth = this.xBarWidth / 2;
  6986. var yWidth = this.yBarWidth / 2;
  6987. for (i = 0; i < this.dataPoints.length; i++) {
  6988. var point = this.dataPoints[i];
  6989. // determine color
  6990. var hue, color, borderColor;
  6991. if (this.style === Graph3d.STYLE.BARCOLOR ) {
  6992. // calculate the color based on the value
  6993. hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240;
  6994. color = this._hsv2rgb(hue, 1, 1);
  6995. borderColor = this._hsv2rgb(hue, 1, 0.8);
  6996. }
  6997. else if (this.style === Graph3d.STYLE.BARSIZE) {
  6998. color = this.colorDot;
  6999. borderColor = this.colorDotBorder;
  7000. }
  7001. else {
  7002. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  7003. hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  7004. color = this._hsv2rgb(hue, 1, 1);
  7005. borderColor = this._hsv2rgb(hue, 1, 0.8);
  7006. }
  7007. // calculate size for the bar
  7008. if (this.style === Graph3d.STYLE.BARSIZE) {
  7009. xWidth = (this.xBarWidth / 2) * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2);
  7010. yWidth = (this.yBarWidth / 2) * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2);
  7011. }
  7012. // calculate all corner points
  7013. var me = this;
  7014. var point3d = point.point;
  7015. var top = [
  7016. {point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, point3d.z)},
  7017. {point: new Point3d(point3d.x + xWidth, point3d.y - yWidth, point3d.z)},
  7018. {point: new Point3d(point3d.x + xWidth, point3d.y + yWidth, point3d.z)},
  7019. {point: new Point3d(point3d.x - xWidth, point3d.y + yWidth, point3d.z)}
  7020. ];
  7021. var bottom = [
  7022. {point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, this.zMin)},
  7023. {point: new Point3d(point3d.x + xWidth, point3d.y - yWidth, this.zMin)},
  7024. {point: new Point3d(point3d.x + xWidth, point3d.y + yWidth, this.zMin)},
  7025. {point: new Point3d(point3d.x - xWidth, point3d.y + yWidth, this.zMin)}
  7026. ];
  7027. // calculate screen location of the points
  7028. top.forEach(function (obj) {
  7029. obj.screen = me._convert3Dto2D(obj.point);
  7030. });
  7031. bottom.forEach(function (obj) {
  7032. obj.screen = me._convert3Dto2D(obj.point);
  7033. });
  7034. // create five sides, calculate both corner points and center points
  7035. var surfaces = [
  7036. {corners: top, center: Point3d.avg(bottom[0].point, bottom[2].point)},
  7037. {corners: [top[0], top[1], bottom[1], bottom[0]], center: Point3d.avg(bottom[1].point, bottom[0].point)},
  7038. {corners: [top[1], top[2], bottom[2], bottom[1]], center: Point3d.avg(bottom[2].point, bottom[1].point)},
  7039. {corners: [top[2], top[3], bottom[3], bottom[2]], center: Point3d.avg(bottom[3].point, bottom[2].point)},
  7040. {corners: [top[3], top[0], bottom[0], bottom[3]], center: Point3d.avg(bottom[0].point, bottom[3].point)}
  7041. ];
  7042. point.surfaces = surfaces;
  7043. // calculate the distance of each of the surface centers to the camera
  7044. for (j = 0; j < surfaces.length; j++) {
  7045. surface = surfaces[j];
  7046. var transCenter = this._convertPointToTranslation(surface.center);
  7047. surface.dist = this.showPerspective ? transCenter.length() : -transCenter.z;
  7048. // TODO: this dept calculation doesn't work 100% of the cases due to perspective,
  7049. // but the current solution is fast/simple and works in 99.9% of all cases
  7050. // the issue is visible in example 14, with graph.setCameraPosition({horizontal: 2.97, vertical: 0.5, distance: 0.9})
  7051. }
  7052. // order the surfaces by their (translated) depth
  7053. surfaces.sort(function (a, b) {
  7054. var diff = b.dist - a.dist;
  7055. if (diff) return diff;
  7056. // if equal depth, sort the top surface last
  7057. if (a.corners === top) return 1;
  7058. if (b.corners === top) return -1;
  7059. // both are equal
  7060. return 0;
  7061. });
  7062. // draw the ordered surfaces
  7063. ctx.lineWidth = 1;
  7064. ctx.strokeStyle = borderColor;
  7065. ctx.fillStyle = color;
  7066. // NOTE: we start at j=2 instead of j=0 as we don't need to draw the two surfaces at the backside
  7067. for (j = 2; j < surfaces.length; j++) {
  7068. surface = surfaces[j];
  7069. corners = surface.corners;
  7070. ctx.beginPath();
  7071. ctx.moveTo(corners[3].screen.x, corners[3].screen.y);
  7072. ctx.lineTo(corners[0].screen.x, corners[0].screen.y);
  7073. ctx.lineTo(corners[1].screen.x, corners[1].screen.y);
  7074. ctx.lineTo(corners[2].screen.x, corners[2].screen.y);
  7075. ctx.lineTo(corners[3].screen.x, corners[3].screen.y);
  7076. ctx.fill();
  7077. ctx.stroke();
  7078. }
  7079. }
  7080. };
  7081. /**
  7082. * Draw a line through all datapoints.
  7083. * This function can be used when the style is 'line'
  7084. */
  7085. Graph3d.prototype._redrawDataLine = function() {
  7086. var canvas = this.frame.canvas,
  7087. ctx = canvas.getContext('2d'),
  7088. point, i;
  7089. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  7090. return; // TODO: throw exception?
  7091. // calculate the translations of all points
  7092. for (i = 0; i < this.dataPoints.length; i++) {
  7093. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  7094. var screen = this._convertTranslationToScreen(trans);
  7095. this.dataPoints[i].trans = trans;
  7096. this.dataPoints[i].screen = screen;
  7097. }
  7098. // start the line
  7099. if (this.dataPoints.length > 0) {
  7100. point = this.dataPoints[0];
  7101. ctx.lineWidth = 1; // TODO: make customizable
  7102. ctx.strokeStyle = 'blue'; // TODO: make customizable
  7103. ctx.beginPath();
  7104. ctx.moveTo(point.screen.x, point.screen.y);
  7105. }
  7106. // draw the datapoints as colored circles
  7107. for (i = 1; i < this.dataPoints.length; i++) {
  7108. point = this.dataPoints[i];
  7109. ctx.lineTo(point.screen.x, point.screen.y);
  7110. }
  7111. // finish the line
  7112. if (this.dataPoints.length > 0) {
  7113. ctx.stroke();
  7114. }
  7115. };
  7116. /**
  7117. * Start a moving operation inside the provided parent element
  7118. * @param {Event} event The event that occurred (required for
  7119. * retrieving the mouse position)
  7120. */
  7121. Graph3d.prototype._onMouseDown = function(event) {
  7122. event = event || window.event;
  7123. // check if mouse is still down (may be up when focus is lost for example
  7124. // in an iframe)
  7125. if (this.leftButtonDown) {
  7126. this._onMouseUp(event);
  7127. }
  7128. // only react on left mouse button down
  7129. this.leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
  7130. if (!this.leftButtonDown && !this.touchDown) return;
  7131. // get mouse position (different code for IE and all other browsers)
  7132. this.startMouseX = getMouseX(event);
  7133. this.startMouseY = getMouseY(event);
  7134. this.startStart = new Date(this.start);
  7135. this.startEnd = new Date(this.end);
  7136. this.startArmRotation = this.camera.getArmRotation();
  7137. this.frame.style.cursor = 'move';
  7138. // add event listeners to handle moving the contents
  7139. // we store the function onmousemove and onmouseup in the graph, so we can
  7140. // remove the eventlisteners lateron in the function mouseUp()
  7141. var me = this;
  7142. this.onmousemove = function (event) {me._onMouseMove(event);};
  7143. this.onmouseup = function (event) {me._onMouseUp(event);};
  7144. util.addEventListener(document, 'mousemove', me.onmousemove);
  7145. util.addEventListener(document, 'mouseup', me.onmouseup);
  7146. util.preventDefault(event);
  7147. };
  7148. /**
  7149. * Perform moving operating.
  7150. * This function activated from within the funcion Graph.mouseDown().
  7151. * @param {Event} event Well, eehh, the event
  7152. */
  7153. Graph3d.prototype._onMouseMove = function (event) {
  7154. event = event || window.event;
  7155. // calculate change in mouse position
  7156. var diffX = parseFloat(getMouseX(event)) - this.startMouseX;
  7157. var diffY = parseFloat(getMouseY(event)) - this.startMouseY;
  7158. var horizontalNew = this.startArmRotation.horizontal + diffX / 200;
  7159. var verticalNew = this.startArmRotation.vertical + diffY / 200;
  7160. var snapAngle = 4; // degrees
  7161. var snapValue = Math.sin(snapAngle / 360 * 2 * Math.PI);
  7162. // snap horizontally to nice angles at 0pi, 0.5pi, 1pi, 1.5pi, etc...
  7163. // the -0.001 is to take care that the vertical axis is always drawn at the left front corner
  7164. if (Math.abs(Math.sin(horizontalNew)) < snapValue) {
  7165. horizontalNew = Math.round((horizontalNew / Math.PI)) * Math.PI - 0.001;
  7166. }
  7167. if (Math.abs(Math.cos(horizontalNew)) < snapValue) {
  7168. horizontalNew = (Math.round((horizontalNew/ Math.PI - 0.5)) + 0.5) * Math.PI - 0.001;
  7169. }
  7170. // snap vertically to nice angles
  7171. if (Math.abs(Math.sin(verticalNew)) < snapValue) {
  7172. verticalNew = Math.round((verticalNew / Math.PI)) * Math.PI;
  7173. }
  7174. if (Math.abs(Math.cos(verticalNew)) < snapValue) {
  7175. verticalNew = (Math.round((verticalNew/ Math.PI - 0.5)) + 0.5) * Math.PI;
  7176. }
  7177. this.camera.setArmRotation(horizontalNew, verticalNew);
  7178. this.redraw();
  7179. // fire a cameraPositionChange event
  7180. var parameters = this.getCameraPosition();
  7181. this.emit('cameraPositionChange', parameters);
  7182. util.preventDefault(event);
  7183. };
  7184. /**
  7185. * Stop moving operating.
  7186. * This function activated from within the funcion Graph.mouseDown().
  7187. * @param {event} event The event
  7188. */
  7189. Graph3d.prototype._onMouseUp = function (event) {
  7190. this.frame.style.cursor = 'auto';
  7191. this.leftButtonDown = false;
  7192. // remove event listeners here
  7193. util.removeEventListener(document, 'mousemove', this.onmousemove);
  7194. util.removeEventListener(document, 'mouseup', this.onmouseup);
  7195. util.preventDefault(event);
  7196. };
  7197. /**
  7198. * After having moved the mouse, a tooltip should pop up when the mouse is resting on a data point
  7199. * @param {Event} event A mouse move event
  7200. */
  7201. Graph3d.prototype._onTooltip = function (event) {
  7202. var delay = 300; // ms
  7203. var boundingRect = this.frame.getBoundingClientRect();
  7204. var mouseX = getMouseX(event) - boundingRect.left;
  7205. var mouseY = getMouseY(event) - boundingRect.top;
  7206. if (!this.showTooltip) {
  7207. return;
  7208. }
  7209. if (this.tooltipTimeout) {
  7210. clearTimeout(this.tooltipTimeout);
  7211. }
  7212. // (delayed) display of a tooltip only if no mouse button is down
  7213. if (this.leftButtonDown) {
  7214. this._hideTooltip();
  7215. return;
  7216. }
  7217. if (this.tooltip && this.tooltip.dataPoint) {
  7218. // tooltip is currently visible
  7219. var dataPoint = this._dataPointFromXY(mouseX, mouseY);
  7220. if (dataPoint !== this.tooltip.dataPoint) {
  7221. // datapoint changed
  7222. if (dataPoint) {
  7223. this._showTooltip(dataPoint);
  7224. }
  7225. else {
  7226. this._hideTooltip();
  7227. }
  7228. }
  7229. }
  7230. else {
  7231. // tooltip is currently not visible
  7232. var me = this;
  7233. this.tooltipTimeout = setTimeout(function () {
  7234. me.tooltipTimeout = null;
  7235. // show a tooltip if we have a data point
  7236. var dataPoint = me._dataPointFromXY(mouseX, mouseY);
  7237. if (dataPoint) {
  7238. me._showTooltip(dataPoint);
  7239. }
  7240. }, delay);
  7241. }
  7242. };
  7243. /**
  7244. * Event handler for touchstart event on mobile devices
  7245. */
  7246. Graph3d.prototype._onTouchStart = function(event) {
  7247. this.touchDown = true;
  7248. var me = this;
  7249. this.ontouchmove = function (event) {me._onTouchMove(event);};
  7250. this.ontouchend = function (event) {me._onTouchEnd(event);};
  7251. util.addEventListener(document, 'touchmove', me.ontouchmove);
  7252. util.addEventListener(document, 'touchend', me.ontouchend);
  7253. this._onMouseDown(event);
  7254. };
  7255. /**
  7256. * Event handler for touchmove event on mobile devices
  7257. */
  7258. Graph3d.prototype._onTouchMove = function(event) {
  7259. this._onMouseMove(event);
  7260. };
  7261. /**
  7262. * Event handler for touchend event on mobile devices
  7263. */
  7264. Graph3d.prototype._onTouchEnd = function(event) {
  7265. this.touchDown = false;
  7266. util.removeEventListener(document, 'touchmove', this.ontouchmove);
  7267. util.removeEventListener(document, 'touchend', this.ontouchend);
  7268. this._onMouseUp(event);
  7269. };
  7270. /**
  7271. * Event handler for mouse wheel event, used to zoom the graph
  7272. * Code from http://adomas.org/javascript-mouse-wheel/
  7273. * @param {event} event The event
  7274. */
  7275. Graph3d.prototype._onWheel = function(event) {
  7276. if (!event) /* For IE. */
  7277. event = window.event;
  7278. // retrieve delta
  7279. var delta = 0;
  7280. if (event.wheelDelta) { /* IE/Opera. */
  7281. delta = event.wheelDelta/120;
  7282. } else if (event.detail) { /* Mozilla case. */
  7283. // In Mozilla, sign of delta is different than in IE.
  7284. // Also, delta is multiple of 3.
  7285. delta = -event.detail/3;
  7286. }
  7287. // If delta is nonzero, handle it.
  7288. // Basically, delta is now positive if wheel was scrolled up,
  7289. // and negative, if wheel was scrolled down.
  7290. if (delta) {
  7291. var oldLength = this.camera.getArmLength();
  7292. var newLength = oldLength * (1 - delta / 10);
  7293. this.camera.setArmLength(newLength);
  7294. this.redraw();
  7295. this._hideTooltip();
  7296. }
  7297. // fire a cameraPositionChange event
  7298. var parameters = this.getCameraPosition();
  7299. this.emit('cameraPositionChange', parameters);
  7300. // Prevent default actions caused by mouse wheel.
  7301. // That might be ugly, but we handle scrolls somehow
  7302. // anyway, so don't bother here..
  7303. util.preventDefault(event);
  7304. };
  7305. /**
  7306. * Test whether a point lies inside given 2D triangle
  7307. * @param {Point2d} point
  7308. * @param {Point2d[]} triangle
  7309. * @return {boolean} Returns true if given point lies inside or on the edge of the triangle
  7310. * @private
  7311. */
  7312. Graph3d.prototype._insideTriangle = function (point, triangle) {
  7313. var a = triangle[0],
  7314. b = triangle[1],
  7315. c = triangle[2];
  7316. function sign (x) {
  7317. return x > 0 ? 1 : x < 0 ? -1 : 0;
  7318. }
  7319. var as = sign((b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x));
  7320. var bs = sign((c.x - b.x) * (point.y - b.y) - (c.y - b.y) * (point.x - b.x));
  7321. var cs = sign((a.x - c.x) * (point.y - c.y) - (a.y - c.y) * (point.x - c.x));
  7322. // each of the three signs must be either equal to each other or zero
  7323. return (as == 0 || bs == 0 || as == bs) &&
  7324. (bs == 0 || cs == 0 || bs == cs) &&
  7325. (as == 0 || cs == 0 || as == cs);
  7326. };
  7327. /**
  7328. * Find a data point close to given screen position (x, y)
  7329. * @param {Number} x
  7330. * @param {Number} y
  7331. * @return {Object | null} The closest data point or null if not close to any data point
  7332. * @private
  7333. */
  7334. Graph3d.prototype._dataPointFromXY = function (x, y) {
  7335. var i,
  7336. distMax = 100, // px
  7337. dataPoint = null,
  7338. closestDataPoint = null,
  7339. closestDist = null,
  7340. center = new Point2d(x, y);
  7341. if (this.style === Graph3d.STYLE.BAR ||
  7342. this.style === Graph3d.STYLE.BARCOLOR ||
  7343. this.style === Graph3d.STYLE.BARSIZE) {
  7344. // the data points are ordered from far away to closest
  7345. for (i = this.dataPoints.length - 1; i >= 0; i--) {
  7346. dataPoint = this.dataPoints[i];
  7347. var surfaces = dataPoint.surfaces;
  7348. if (surfaces) {
  7349. for (var s = surfaces.length - 1; s >= 0; s--) {
  7350. // split each surface in two triangles, and see if the center point is inside one of these
  7351. var surface = surfaces[s];
  7352. var corners = surface.corners;
  7353. var triangle1 = [corners[0].screen, corners[1].screen, corners[2].screen];
  7354. var triangle2 = [corners[2].screen, corners[3].screen, corners[0].screen];
  7355. if (this._insideTriangle(center, triangle1) ||
  7356. this._insideTriangle(center, triangle2)) {
  7357. // return immediately at the first hit
  7358. return dataPoint;
  7359. }
  7360. }
  7361. }
  7362. }
  7363. }
  7364. else {
  7365. // find the closest data point, using distance to the center of the point on 2d screen
  7366. for (i = 0; i < this.dataPoints.length; i++) {
  7367. dataPoint = this.dataPoints[i];
  7368. var point = dataPoint.screen;
  7369. if (point) {
  7370. var distX = Math.abs(x - point.x);
  7371. var distY = Math.abs(y - point.y);
  7372. var dist = Math.sqrt(distX * distX + distY * distY);
  7373. if ((closestDist === null || dist < closestDist) && dist < distMax) {
  7374. closestDist = dist;
  7375. closestDataPoint = dataPoint;
  7376. }
  7377. }
  7378. }
  7379. }
  7380. return closestDataPoint;
  7381. };
  7382. /**
  7383. * Display a tooltip for given data point
  7384. * @param {Object} dataPoint
  7385. * @private
  7386. */
  7387. Graph3d.prototype._showTooltip = function (dataPoint) {
  7388. var content, line, dot;
  7389. if (!this.tooltip) {
  7390. content = document.createElement('div');
  7391. content.style.position = 'absolute';
  7392. content.style.padding = '10px';
  7393. content.style.border = '1px solid #4d4d4d';
  7394. content.style.color = '#1a1a1a';
  7395. content.style.background = 'rgba(255,255,255,0.7)';
  7396. content.style.borderRadius = '2px';
  7397. content.style.boxShadow = '5px 5px 10px rgba(128,128,128,0.5)';
  7398. line = document.createElement('div');
  7399. line.style.position = 'absolute';
  7400. line.style.height = '40px';
  7401. line.style.width = '0';
  7402. line.style.borderLeft = '1px solid #4d4d4d';
  7403. dot = document.createElement('div');
  7404. dot.style.position = 'absolute';
  7405. dot.style.height = '0';
  7406. dot.style.width = '0';
  7407. dot.style.border = '5px solid #4d4d4d';
  7408. dot.style.borderRadius = '5px';
  7409. this.tooltip = {
  7410. dataPoint: null,
  7411. dom: {
  7412. content: content,
  7413. line: line,
  7414. dot: dot
  7415. }
  7416. };
  7417. }
  7418. else {
  7419. content = this.tooltip.dom.content;
  7420. line = this.tooltip.dom.line;
  7421. dot = this.tooltip.dom.dot;
  7422. }
  7423. this._hideTooltip();
  7424. this.tooltip.dataPoint = dataPoint;
  7425. if (typeof this.showTooltip === 'function') {
  7426. content.innerHTML = this.showTooltip(dataPoint.point);
  7427. }
  7428. else {
  7429. content.innerHTML = '<table>' +
  7430. '<tr><td>x:</td><td>' + dataPoint.point.x + '</td></tr>' +
  7431. '<tr><td>y:</td><td>' + dataPoint.point.y + '</td></tr>' +
  7432. '<tr><td>z:</td><td>' + dataPoint.point.z + '</td></tr>' +
  7433. '</table>';
  7434. }
  7435. content.style.left = '0';
  7436. content.style.top = '0';
  7437. this.frame.appendChild(content);
  7438. this.frame.appendChild(line);
  7439. this.frame.appendChild(dot);
  7440. // calculate sizes
  7441. var contentWidth = content.offsetWidth;
  7442. var contentHeight = content.offsetHeight;
  7443. var lineHeight = line.offsetHeight;
  7444. var dotWidth = dot.offsetWidth;
  7445. var dotHeight = dot.offsetHeight;
  7446. var left = dataPoint.screen.x - contentWidth / 2;
  7447. left = Math.min(Math.max(left, 10), this.frame.clientWidth - 10 - contentWidth);
  7448. line.style.left = dataPoint.screen.x + 'px';
  7449. line.style.top = (dataPoint.screen.y - lineHeight) + 'px';
  7450. content.style.left = left + 'px';
  7451. content.style.top = (dataPoint.screen.y - lineHeight - contentHeight) + 'px';
  7452. dot.style.left = (dataPoint.screen.x - dotWidth / 2) + 'px';
  7453. dot.style.top = (dataPoint.screen.y - dotHeight / 2) + 'px';
  7454. };
  7455. /**
  7456. * Hide the tooltip when displayed
  7457. * @private
  7458. */
  7459. Graph3d.prototype._hideTooltip = function () {
  7460. if (this.tooltip) {
  7461. this.tooltip.dataPoint = null;
  7462. for (var prop in this.tooltip.dom) {
  7463. if (this.tooltip.dom.hasOwnProperty(prop)) {
  7464. var elem = this.tooltip.dom[prop];
  7465. if (elem && elem.parentNode) {
  7466. elem.parentNode.removeChild(elem);
  7467. }
  7468. }
  7469. }
  7470. }
  7471. };
  7472. /**--------------------------------------------------------------------------**/
  7473. /**
  7474. * Get the horizontal mouse position from a mouse event
  7475. * @param {Event} event
  7476. * @return {Number} mouse x
  7477. */
  7478. function getMouseX (event) {
  7479. if ('clientX' in event) return event.clientX;
  7480. return event.targetTouches[0] && event.targetTouches[0].clientX || 0;
  7481. }
  7482. /**
  7483. * Get the vertical mouse position from a mouse event
  7484. * @param {Event} event
  7485. * @return {Number} mouse y
  7486. */
  7487. function getMouseY (event) {
  7488. if ('clientY' in event) return event.clientY;
  7489. return event.targetTouches[0] && event.targetTouches[0].clientY || 0;
  7490. }
  7491. module.exports = Graph3d;
  7492. /***/ },
  7493. /* 11 */
  7494. /***/ function(module, exports, __webpack_require__) {
  7495. /**
  7496. * Expose `Emitter`.
  7497. */
  7498. module.exports = Emitter;
  7499. /**
  7500. * Initialize a new `Emitter`.
  7501. *
  7502. * @api public
  7503. */
  7504. function Emitter(obj) {
  7505. if (obj) return mixin(obj);
  7506. };
  7507. /**
  7508. * Mixin the emitter properties.
  7509. *
  7510. * @param {Object} obj
  7511. * @return {Object}
  7512. * @api private
  7513. */
  7514. function mixin(obj) {
  7515. for (var key in Emitter.prototype) {
  7516. obj[key] = Emitter.prototype[key];
  7517. }
  7518. return obj;
  7519. }
  7520. /**
  7521. * Listen on the given `event` with `fn`.
  7522. *
  7523. * @param {String} event
  7524. * @param {Function} fn
  7525. * @return {Emitter}
  7526. * @api public
  7527. */
  7528. Emitter.prototype.on =
  7529. Emitter.prototype.addEventListener = function(event, fn){
  7530. this._callbacks = this._callbacks || {};
  7531. (this._callbacks[event] = this._callbacks[event] || [])
  7532. .push(fn);
  7533. return this;
  7534. };
  7535. /**
  7536. * Adds an `event` listener that will be invoked a single
  7537. * time then automatically removed.
  7538. *
  7539. * @param {String} event
  7540. * @param {Function} fn
  7541. * @return {Emitter}
  7542. * @api public
  7543. */
  7544. Emitter.prototype.once = function(event, fn){
  7545. var self = this;
  7546. this._callbacks = this._callbacks || {};
  7547. function on() {
  7548. self.off(event, on);
  7549. fn.apply(this, arguments);
  7550. }
  7551. on.fn = fn;
  7552. this.on(event, on);
  7553. return this;
  7554. };
  7555. /**
  7556. * Remove the given callback for `event` or all
  7557. * registered callbacks.
  7558. *
  7559. * @param {String} event
  7560. * @param {Function} fn
  7561. * @return {Emitter}
  7562. * @api public
  7563. */
  7564. Emitter.prototype.off =
  7565. Emitter.prototype.removeListener =
  7566. Emitter.prototype.removeAllListeners =
  7567. Emitter.prototype.removeEventListener = function(event, fn){
  7568. this._callbacks = this._callbacks || {};
  7569. // all
  7570. if (0 == arguments.length) {
  7571. this._callbacks = {};
  7572. return this;
  7573. }
  7574. // specific event
  7575. var callbacks = this._callbacks[event];
  7576. if (!callbacks) return this;
  7577. // remove all handlers
  7578. if (1 == arguments.length) {
  7579. delete this._callbacks[event];
  7580. return this;
  7581. }
  7582. // remove specific handler
  7583. var cb;
  7584. for (var i = 0; i < callbacks.length; i++) {
  7585. cb = callbacks[i];
  7586. if (cb === fn || cb.fn === fn) {
  7587. callbacks.splice(i, 1);
  7588. break;
  7589. }
  7590. }
  7591. return this;
  7592. };
  7593. /**
  7594. * Emit `event` with the given args.
  7595. *
  7596. * @param {String} event
  7597. * @param {Mixed} ...
  7598. * @return {Emitter}
  7599. */
  7600. Emitter.prototype.emit = function(event){
  7601. this._callbacks = this._callbacks || {};
  7602. var args = [].slice.call(arguments, 1)
  7603. , callbacks = this._callbacks[event];
  7604. if (callbacks) {
  7605. callbacks = callbacks.slice(0);
  7606. for (var i = 0, len = callbacks.length; i < len; ++i) {
  7607. callbacks[i].apply(this, args);
  7608. }
  7609. }
  7610. return this;
  7611. };
  7612. /**
  7613. * Return array of callbacks for `event`.
  7614. *
  7615. * @param {String} event
  7616. * @return {Array}
  7617. * @api public
  7618. */
  7619. Emitter.prototype.listeners = function(event){
  7620. this._callbacks = this._callbacks || {};
  7621. return this._callbacks[event] || [];
  7622. };
  7623. /**
  7624. * Check if this emitter has `event` handlers.
  7625. *
  7626. * @param {String} event
  7627. * @return {Boolean}
  7628. * @api public
  7629. */
  7630. Emitter.prototype.hasListeners = function(event){
  7631. return !! this.listeners(event).length;
  7632. };
  7633. /***/ },
  7634. /* 12 */
  7635. /***/ function(module, exports, __webpack_require__) {
  7636. /**
  7637. * @prototype Point3d
  7638. * @param {Number} [x]
  7639. * @param {Number} [y]
  7640. * @param {Number} [z]
  7641. */
  7642. function Point3d(x, y, z) {
  7643. this.x = x !== undefined ? x : 0;
  7644. this.y = y !== undefined ? y : 0;
  7645. this.z = z !== undefined ? z : 0;
  7646. };
  7647. /**
  7648. * Subtract the two provided points, returns a-b
  7649. * @param {Point3d} a
  7650. * @param {Point3d} b
  7651. * @return {Point3d} a-b
  7652. */
  7653. Point3d.subtract = function(a, b) {
  7654. var sub = new Point3d();
  7655. sub.x = a.x - b.x;
  7656. sub.y = a.y - b.y;
  7657. sub.z = a.z - b.z;
  7658. return sub;
  7659. };
  7660. /**
  7661. * Add the two provided points, returns a+b
  7662. * @param {Point3d} a
  7663. * @param {Point3d} b
  7664. * @return {Point3d} a+b
  7665. */
  7666. Point3d.add = function(a, b) {
  7667. var sum = new Point3d();
  7668. sum.x = a.x + b.x;
  7669. sum.y = a.y + b.y;
  7670. sum.z = a.z + b.z;
  7671. return sum;
  7672. };
  7673. /**
  7674. * Calculate the average of two 3d points
  7675. * @param {Point3d} a
  7676. * @param {Point3d} b
  7677. * @return {Point3d} The average, (a+b)/2
  7678. */
  7679. Point3d.avg = function(a, b) {
  7680. return new Point3d(
  7681. (a.x + b.x) / 2,
  7682. (a.y + b.y) / 2,
  7683. (a.z + b.z) / 2
  7684. );
  7685. };
  7686. /**
  7687. * Calculate the cross product of the two provided points, returns axb
  7688. * Documentation: http://en.wikipedia.org/wiki/Cross_product
  7689. * @param {Point3d} a
  7690. * @param {Point3d} b
  7691. * @return {Point3d} cross product axb
  7692. */
  7693. Point3d.crossProduct = function(a, b) {
  7694. var crossproduct = new Point3d();
  7695. crossproduct.x = a.y * b.z - a.z * b.y;
  7696. crossproduct.y = a.z * b.x - a.x * b.z;
  7697. crossproduct.z = a.x * b.y - a.y * b.x;
  7698. return crossproduct;
  7699. };
  7700. /**
  7701. * Rtrieve the length of the vector (or the distance from this point to the origin
  7702. * @return {Number} length
  7703. */
  7704. Point3d.prototype.length = function() {
  7705. return Math.sqrt(
  7706. this.x * this.x +
  7707. this.y * this.y +
  7708. this.z * this.z
  7709. );
  7710. };
  7711. module.exports = Point3d;
  7712. /***/ },
  7713. /* 13 */
  7714. /***/ function(module, exports, __webpack_require__) {
  7715. /**
  7716. * @prototype Point2d
  7717. * @param {Number} [x]
  7718. * @param {Number} [y]
  7719. */
  7720. function Point2d (x, y) {
  7721. this.x = x !== undefined ? x : 0;
  7722. this.y = y !== undefined ? y : 0;
  7723. }
  7724. module.exports = Point2d;
  7725. /***/ },
  7726. /* 14 */
  7727. /***/ function(module, exports, __webpack_require__) {
  7728. var Point3d = __webpack_require__(12);
  7729. /**
  7730. * @class Camera
  7731. * The camera is mounted on a (virtual) camera arm. The camera arm can rotate
  7732. * The camera is always looking in the direction of the origin of the arm.
  7733. * This way, the camera always rotates around one fixed point, the location
  7734. * of the camera arm.
  7735. *
  7736. * Documentation:
  7737. * http://en.wikipedia.org/wiki/3D_projection
  7738. */
  7739. function Camera() {
  7740. this.armLocation = new Point3d();
  7741. this.armRotation = {};
  7742. this.armRotation.horizontal = 0;
  7743. this.armRotation.vertical = 0;
  7744. this.armLength = 1.7;
  7745. this.cameraLocation = new Point3d();
  7746. this.cameraRotation = new Point3d(0.5*Math.PI, 0, 0);
  7747. this.calculateCameraOrientation();
  7748. }
  7749. /**
  7750. * Set the location (origin) of the arm
  7751. * @param {Number} x Normalized value of x
  7752. * @param {Number} y Normalized value of y
  7753. * @param {Number} z Normalized value of z
  7754. */
  7755. Camera.prototype.setArmLocation = function(x, y, z) {
  7756. this.armLocation.x = x;
  7757. this.armLocation.y = y;
  7758. this.armLocation.z = z;
  7759. this.calculateCameraOrientation();
  7760. };
  7761. /**
  7762. * Set the rotation of the camera arm
  7763. * @param {Number} horizontal The horizontal rotation, between 0 and 2*PI.
  7764. * Optional, can be left undefined.
  7765. * @param {Number} vertical The vertical rotation, between 0 and 0.5*PI
  7766. * if vertical=0.5*PI, the graph is shown from the
  7767. * top. Optional, can be left undefined.
  7768. */
  7769. Camera.prototype.setArmRotation = function(horizontal, vertical) {
  7770. if (horizontal !== undefined) {
  7771. this.armRotation.horizontal = horizontal;
  7772. }
  7773. if (vertical !== undefined) {
  7774. this.armRotation.vertical = vertical;
  7775. if (this.armRotation.vertical < 0) this.armRotation.vertical = 0;
  7776. if (this.armRotation.vertical > 0.5*Math.PI) this.armRotation.vertical = 0.5*Math.PI;
  7777. }
  7778. if (horizontal !== undefined || vertical !== undefined) {
  7779. this.calculateCameraOrientation();
  7780. }
  7781. };
  7782. /**
  7783. * Retrieve the current arm rotation
  7784. * @return {object} An object with parameters horizontal and vertical
  7785. */
  7786. Camera.prototype.getArmRotation = function() {
  7787. var rot = {};
  7788. rot.horizontal = this.armRotation.horizontal;
  7789. rot.vertical = this.armRotation.vertical;
  7790. return rot;
  7791. };
  7792. /**
  7793. * Set the (normalized) length of the camera arm.
  7794. * @param {Number} length A length between 0.71 and 5.0
  7795. */
  7796. Camera.prototype.setArmLength = function(length) {
  7797. if (length === undefined)
  7798. return;
  7799. this.armLength = length;
  7800. // Radius must be larger than the corner of the graph,
  7801. // which has a distance of sqrt(0.5^2+0.5^2) = 0.71 from the center of the
  7802. // graph
  7803. if (this.armLength < 0.71) this.armLength = 0.71;
  7804. if (this.armLength > 5.0) this.armLength = 5.0;
  7805. this.calculateCameraOrientation();
  7806. };
  7807. /**
  7808. * Retrieve the arm length
  7809. * @return {Number} length
  7810. */
  7811. Camera.prototype.getArmLength = function() {
  7812. return this.armLength;
  7813. };
  7814. /**
  7815. * Retrieve the camera location
  7816. * @return {Point3d} cameraLocation
  7817. */
  7818. Camera.prototype.getCameraLocation = function() {
  7819. return this.cameraLocation;
  7820. };
  7821. /**
  7822. * Retrieve the camera rotation
  7823. * @return {Point3d} cameraRotation
  7824. */
  7825. Camera.prototype.getCameraRotation = function() {
  7826. return this.cameraRotation;
  7827. };
  7828. /**
  7829. * Calculate the location and rotation of the camera based on the
  7830. * position and orientation of the camera arm
  7831. */
  7832. Camera.prototype.calculateCameraOrientation = function() {
  7833. // calculate location of the camera
  7834. this.cameraLocation.x = this.armLocation.x - this.armLength * Math.sin(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical);
  7835. this.cameraLocation.y = this.armLocation.y - this.armLength * Math.cos(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical);
  7836. this.cameraLocation.z = this.armLocation.z + this.armLength * Math.sin(this.armRotation.vertical);
  7837. // calculate rotation of the camera
  7838. this.cameraRotation.x = Math.PI/2 - this.armRotation.vertical;
  7839. this.cameraRotation.y = 0;
  7840. this.cameraRotation.z = -this.armRotation.horizontal;
  7841. };
  7842. module.exports = Camera;
  7843. /***/ },
  7844. /* 15 */
  7845. /***/ function(module, exports, __webpack_require__) {
  7846. var DataView = __webpack_require__(9);
  7847. /**
  7848. * @class Filter
  7849. *
  7850. * @param {DataSet} data The google data table
  7851. * @param {Number} column The index of the column to be filtered
  7852. * @param {Graph} graph The graph
  7853. */
  7854. function Filter (data, column, graph) {
  7855. this.data = data;
  7856. this.column = column;
  7857. this.graph = graph; // the parent graph
  7858. this.index = undefined;
  7859. this.value = undefined;
  7860. // read all distinct values and select the first one
  7861. this.values = graph.getDistinctValues(data.get(), this.column);
  7862. // sort both numeric and string values correctly
  7863. this.values.sort(function (a, b) {
  7864. return a > b ? 1 : a < b ? -1 : 0;
  7865. });
  7866. if (this.values.length > 0) {
  7867. this.selectValue(0);
  7868. }
  7869. // create an array with the filtered datapoints. this will be loaded afterwards
  7870. this.dataPoints = [];
  7871. this.loaded = false;
  7872. this.onLoadCallback = undefined;
  7873. if (graph.animationPreload) {
  7874. this.loaded = false;
  7875. this.loadInBackground();
  7876. }
  7877. else {
  7878. this.loaded = true;
  7879. }
  7880. };
  7881. /**
  7882. * Return the label
  7883. * @return {string} label
  7884. */
  7885. Filter.prototype.isLoaded = function() {
  7886. return this.loaded;
  7887. };
  7888. /**
  7889. * Return the loaded progress
  7890. * @return {Number} percentage between 0 and 100
  7891. */
  7892. Filter.prototype.getLoadedProgress = function() {
  7893. var len = this.values.length;
  7894. var i = 0;
  7895. while (this.dataPoints[i]) {
  7896. i++;
  7897. }
  7898. return Math.round(i / len * 100);
  7899. };
  7900. /**
  7901. * Return the label
  7902. * @return {string} label
  7903. */
  7904. Filter.prototype.getLabel = function() {
  7905. return this.graph.filterLabel;
  7906. };
  7907. /**
  7908. * Return the columnIndex of the filter
  7909. * @return {Number} columnIndex
  7910. */
  7911. Filter.prototype.getColumn = function() {
  7912. return this.column;
  7913. };
  7914. /**
  7915. * Return the currently selected value. Returns undefined if there is no selection
  7916. * @return {*} value
  7917. */
  7918. Filter.prototype.getSelectedValue = function() {
  7919. if (this.index === undefined)
  7920. return undefined;
  7921. return this.values[this.index];
  7922. };
  7923. /**
  7924. * Retrieve all values of the filter
  7925. * @return {Array} values
  7926. */
  7927. Filter.prototype.getValues = function() {
  7928. return this.values;
  7929. };
  7930. /**
  7931. * Retrieve one value of the filter
  7932. * @param {Number} index
  7933. * @return {*} value
  7934. */
  7935. Filter.prototype.getValue = function(index) {
  7936. if (index >= this.values.length)
  7937. throw 'Error: index out of range';
  7938. return this.values[index];
  7939. };
  7940. /**
  7941. * Retrieve the (filtered) dataPoints for the currently selected filter index
  7942. * @param {Number} [index] (optional)
  7943. * @return {Array} dataPoints
  7944. */
  7945. Filter.prototype._getDataPoints = function(index) {
  7946. if (index === undefined)
  7947. index = this.index;
  7948. if (index === undefined)
  7949. return [];
  7950. var dataPoints;
  7951. if (this.dataPoints[index]) {
  7952. dataPoints = this.dataPoints[index];
  7953. }
  7954. else {
  7955. var f = {};
  7956. f.column = this.column;
  7957. f.value = this.values[index];
  7958. var dataView = new DataView(this.data,{filter: function (item) {return (item[f.column] == f.value);}}).get();
  7959. dataPoints = this.graph._getDataPoints(dataView);
  7960. this.dataPoints[index] = dataPoints;
  7961. }
  7962. return dataPoints;
  7963. };
  7964. /**
  7965. * Set a callback function when the filter is fully loaded.
  7966. */
  7967. Filter.prototype.setOnLoadCallback = function(callback) {
  7968. this.onLoadCallback = callback;
  7969. };
  7970. /**
  7971. * Add a value to the list with available values for this filter
  7972. * No double entries will be created.
  7973. * @param {Number} index
  7974. */
  7975. Filter.prototype.selectValue = function(index) {
  7976. if (index >= this.values.length)
  7977. throw 'Error: index out of range';
  7978. this.index = index;
  7979. this.value = this.values[index];
  7980. };
  7981. /**
  7982. * Load all filtered rows in the background one by one
  7983. * Start this method without providing an index!
  7984. */
  7985. Filter.prototype.loadInBackground = function(index) {
  7986. if (index === undefined)
  7987. index = 0;
  7988. var frame = this.graph.frame;
  7989. if (index < this.values.length) {
  7990. var dataPointsTemp = this._getDataPoints(index);
  7991. //this.graph.redrawInfo(); // TODO: not neat
  7992. // create a progress box
  7993. if (frame.progress === undefined) {
  7994. frame.progress = document.createElement('DIV');
  7995. frame.progress.style.position = 'absolute';
  7996. frame.progress.style.color = 'gray';
  7997. frame.appendChild(frame.progress);
  7998. }
  7999. var progress = this.getLoadedProgress();
  8000. frame.progress.innerHTML = 'Loading animation... ' + progress + '%';
  8001. // TODO: this is no nice solution...
  8002. frame.progress.style.bottom = 60 + 'px'; // TODO: use height of slider
  8003. frame.progress.style.left = 10 + 'px';
  8004. var me = this;
  8005. setTimeout(function() {me.loadInBackground(index+1);}, 10);
  8006. this.loaded = false;
  8007. }
  8008. else {
  8009. this.loaded = true;
  8010. // remove the progress box
  8011. if (frame.progress !== undefined) {
  8012. frame.removeChild(frame.progress);
  8013. frame.progress = undefined;
  8014. }
  8015. if (this.onLoadCallback)
  8016. this.onLoadCallback();
  8017. }
  8018. };
  8019. module.exports = Filter;
  8020. /***/ },
  8021. /* 16 */
  8022. /***/ function(module, exports, __webpack_require__) {
  8023. var util = __webpack_require__(1);
  8024. /**
  8025. * @constructor Slider
  8026. *
  8027. * An html slider control with start/stop/prev/next buttons
  8028. * @param {Element} container The element where the slider will be created
  8029. * @param {Object} options Available options:
  8030. * {boolean} visible If true (default) the
  8031. * slider is visible.
  8032. */
  8033. function Slider(container, options) {
  8034. if (container === undefined) {
  8035. throw 'Error: No container element defined';
  8036. }
  8037. this.container = container;
  8038. this.visible = (options && options.visible != undefined) ? options.visible : true;
  8039. if (this.visible) {
  8040. this.frame = document.createElement('DIV');
  8041. //this.frame.style.backgroundColor = '#E5E5E5';
  8042. this.frame.style.width = '100%';
  8043. this.frame.style.position = 'relative';
  8044. this.container.appendChild(this.frame);
  8045. this.frame.prev = document.createElement('INPUT');
  8046. this.frame.prev.type = 'BUTTON';
  8047. this.frame.prev.value = 'Prev';
  8048. this.frame.appendChild(this.frame.prev);
  8049. this.frame.play = document.createElement('INPUT');
  8050. this.frame.play.type = 'BUTTON';
  8051. this.frame.play.value = 'Play';
  8052. this.frame.appendChild(this.frame.play);
  8053. this.frame.next = document.createElement('INPUT');
  8054. this.frame.next.type = 'BUTTON';
  8055. this.frame.next.value = 'Next';
  8056. this.frame.appendChild(this.frame.next);
  8057. this.frame.bar = document.createElement('INPUT');
  8058. this.frame.bar.type = 'BUTTON';
  8059. this.frame.bar.style.position = 'absolute';
  8060. this.frame.bar.style.border = '1px solid red';
  8061. this.frame.bar.style.width = '100px';
  8062. this.frame.bar.style.height = '6px';
  8063. this.frame.bar.style.borderRadius = '2px';
  8064. this.frame.bar.style.MozBorderRadius = '2px';
  8065. this.frame.bar.style.border = '1px solid #7F7F7F';
  8066. this.frame.bar.style.backgroundColor = '#E5E5E5';
  8067. this.frame.appendChild(this.frame.bar);
  8068. this.frame.slide = document.createElement('INPUT');
  8069. this.frame.slide.type = 'BUTTON';
  8070. this.frame.slide.style.margin = '0px';
  8071. this.frame.slide.value = ' ';
  8072. this.frame.slide.style.position = 'relative';
  8073. this.frame.slide.style.left = '-100px';
  8074. this.frame.appendChild(this.frame.slide);
  8075. // create events
  8076. var me = this;
  8077. this.frame.slide.onmousedown = function (event) {me._onMouseDown(event);};
  8078. this.frame.prev.onclick = function (event) {me.prev(event);};
  8079. this.frame.play.onclick = function (event) {me.togglePlay(event);};
  8080. this.frame.next.onclick = function (event) {me.next(event);};
  8081. }
  8082. this.onChangeCallback = undefined;
  8083. this.values = [];
  8084. this.index = undefined;
  8085. this.playTimeout = undefined;
  8086. this.playInterval = 1000; // milliseconds
  8087. this.playLoop = true;
  8088. }
  8089. /**
  8090. * Select the previous index
  8091. */
  8092. Slider.prototype.prev = function() {
  8093. var index = this.getIndex();
  8094. if (index > 0) {
  8095. index--;
  8096. this.setIndex(index);
  8097. }
  8098. };
  8099. /**
  8100. * Select the next index
  8101. */
  8102. Slider.prototype.next = function() {
  8103. var index = this.getIndex();
  8104. if (index < this.values.length - 1) {
  8105. index++;
  8106. this.setIndex(index);
  8107. }
  8108. };
  8109. /**
  8110. * Select the next index
  8111. */
  8112. Slider.prototype.playNext = function() {
  8113. var start = new Date();
  8114. var index = this.getIndex();
  8115. if (index < this.values.length - 1) {
  8116. index++;
  8117. this.setIndex(index);
  8118. }
  8119. else if (this.playLoop) {
  8120. // jump to the start
  8121. index = 0;
  8122. this.setIndex(index);
  8123. }
  8124. var end = new Date();
  8125. var diff = (end - start);
  8126. // calculate how much time it to to set the index and to execute the callback
  8127. // function.
  8128. var interval = Math.max(this.playInterval - diff, 0);
  8129. // document.title = diff // TODO: cleanup
  8130. var me = this;
  8131. this.playTimeout = setTimeout(function() {me.playNext();}, interval);
  8132. };
  8133. /**
  8134. * Toggle start or stop playing
  8135. */
  8136. Slider.prototype.togglePlay = function() {
  8137. if (this.playTimeout === undefined) {
  8138. this.play();
  8139. } else {
  8140. this.stop();
  8141. }
  8142. };
  8143. /**
  8144. * Start playing
  8145. */
  8146. Slider.prototype.play = function() {
  8147. // Test whether already playing
  8148. if (this.playTimeout) return;
  8149. this.playNext();
  8150. if (this.frame) {
  8151. this.frame.play.value = 'Stop';
  8152. }
  8153. };
  8154. /**
  8155. * Stop playing
  8156. */
  8157. Slider.prototype.stop = function() {
  8158. clearInterval(this.playTimeout);
  8159. this.playTimeout = undefined;
  8160. if (this.frame) {
  8161. this.frame.play.value = 'Play';
  8162. }
  8163. };
  8164. /**
  8165. * Set a callback function which will be triggered when the value of the
  8166. * slider bar has changed.
  8167. */
  8168. Slider.prototype.setOnChangeCallback = function(callback) {
  8169. this.onChangeCallback = callback;
  8170. };
  8171. /**
  8172. * Set the interval for playing the list
  8173. * @param {Number} interval The interval in milliseconds
  8174. */
  8175. Slider.prototype.setPlayInterval = function(interval) {
  8176. this.playInterval = interval;
  8177. };
  8178. /**
  8179. * Retrieve the current play interval
  8180. * @return {Number} interval The interval in milliseconds
  8181. */
  8182. Slider.prototype.getPlayInterval = function(interval) {
  8183. return this.playInterval;
  8184. };
  8185. /**
  8186. * Set looping on or off
  8187. * @pararm {boolean} doLoop If true, the slider will jump to the start when
  8188. * the end is passed, and will jump to the end
  8189. * when the start is passed.
  8190. */
  8191. Slider.prototype.setPlayLoop = function(doLoop) {
  8192. this.playLoop = doLoop;
  8193. };
  8194. /**
  8195. * Execute the onchange callback function
  8196. */
  8197. Slider.prototype.onChange = function() {
  8198. if (this.onChangeCallback !== undefined) {
  8199. this.onChangeCallback();
  8200. }
  8201. };
  8202. /**
  8203. * redraw the slider on the correct place
  8204. */
  8205. Slider.prototype.redraw = function() {
  8206. if (this.frame) {
  8207. // resize the bar
  8208. this.frame.bar.style.top = (this.frame.clientHeight/2 -
  8209. this.frame.bar.offsetHeight/2) + 'px';
  8210. this.frame.bar.style.width = (this.frame.clientWidth -
  8211. this.frame.prev.clientWidth -
  8212. this.frame.play.clientWidth -
  8213. this.frame.next.clientWidth - 30) + 'px';
  8214. // position the slider button
  8215. var left = this.indexToLeft(this.index);
  8216. this.frame.slide.style.left = (left) + 'px';
  8217. }
  8218. };
  8219. /**
  8220. * Set the list with values for the slider
  8221. * @param {Array} values A javascript array with values (any type)
  8222. */
  8223. Slider.prototype.setValues = function(values) {
  8224. this.values = values;
  8225. if (this.values.length > 0)
  8226. this.setIndex(0);
  8227. else
  8228. this.index = undefined;
  8229. };
  8230. /**
  8231. * Select a value by its index
  8232. * @param {Number} index
  8233. */
  8234. Slider.prototype.setIndex = function(index) {
  8235. if (index < this.values.length) {
  8236. this.index = index;
  8237. this.redraw();
  8238. this.onChange();
  8239. }
  8240. else {
  8241. throw 'Error: index out of range';
  8242. }
  8243. };
  8244. /**
  8245. * retrieve the index of the currently selected vaue
  8246. * @return {Number} index
  8247. */
  8248. Slider.prototype.getIndex = function() {
  8249. return this.index;
  8250. };
  8251. /**
  8252. * retrieve the currently selected value
  8253. * @return {*} value
  8254. */
  8255. Slider.prototype.get = function() {
  8256. return this.values[this.index];
  8257. };
  8258. Slider.prototype._onMouseDown = function(event) {
  8259. // only react on left mouse button down
  8260. var leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
  8261. if (!leftButtonDown) return;
  8262. this.startClientX = event.clientX;
  8263. this.startSlideX = parseFloat(this.frame.slide.style.left);
  8264. this.frame.style.cursor = 'move';
  8265. // add event listeners to handle moving the contents
  8266. // we store the function onmousemove and onmouseup in the graph, so we can
  8267. // remove the eventlisteners lateron in the function mouseUp()
  8268. var me = this;
  8269. this.onmousemove = function (event) {me._onMouseMove(event);};
  8270. this.onmouseup = function (event) {me._onMouseUp(event);};
  8271. util.addEventListener(document, 'mousemove', this.onmousemove);
  8272. util.addEventListener(document, 'mouseup', this.onmouseup);
  8273. util.preventDefault(event);
  8274. };
  8275. Slider.prototype.leftToIndex = function (left) {
  8276. var width = parseFloat(this.frame.bar.style.width) -
  8277. this.frame.slide.clientWidth - 10;
  8278. var x = left - 3;
  8279. var index = Math.round(x / width * (this.values.length-1));
  8280. if (index < 0) index = 0;
  8281. if (index > this.values.length-1) index = this.values.length-1;
  8282. return index;
  8283. };
  8284. Slider.prototype.indexToLeft = function (index) {
  8285. var width = parseFloat(this.frame.bar.style.width) -
  8286. this.frame.slide.clientWidth - 10;
  8287. var x = index / (this.values.length-1) * width;
  8288. var left = x + 3;
  8289. return left;
  8290. };
  8291. Slider.prototype._onMouseMove = function (event) {
  8292. var diff = event.clientX - this.startClientX;
  8293. var x = this.startSlideX + diff;
  8294. var index = this.leftToIndex(x);
  8295. this.setIndex(index);
  8296. util.preventDefault();
  8297. };
  8298. Slider.prototype._onMouseUp = function (event) {
  8299. this.frame.style.cursor = 'auto';
  8300. // remove event listeners
  8301. util.removeEventListener(document, 'mousemove', this.onmousemove);
  8302. util.removeEventListener(document, 'mouseup', this.onmouseup);
  8303. util.preventDefault();
  8304. };
  8305. module.exports = Slider;
  8306. /***/ },
  8307. /* 17 */
  8308. /***/ function(module, exports, __webpack_require__) {
  8309. /**
  8310. * @prototype StepNumber
  8311. * The class StepNumber is an iterator for Numbers. You provide a start and end
  8312. * value, and a best step size. StepNumber itself rounds to fixed values and
  8313. * a finds the step that best fits the provided step.
  8314. *
  8315. * If prettyStep is true, the step size is chosen as close as possible to the
  8316. * provided step, but being a round value like 1, 2, 5, 10, 20, 50, ....
  8317. *
  8318. * Example usage:
  8319. * var step = new StepNumber(0, 10, 2.5, true);
  8320. * step.start();
  8321. * while (!step.end()) {
  8322. * alert(step.getCurrent());
  8323. * step.next();
  8324. * }
  8325. *
  8326. * Version: 1.0
  8327. *
  8328. * @param {Number} start The start value
  8329. * @param {Number} end The end value
  8330. * @param {Number} step Optional. Step size. Must be a positive value.
  8331. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  8332. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  8333. */
  8334. function StepNumber(start, end, step, prettyStep) {
  8335. // set default values
  8336. this._start = 0;
  8337. this._end = 0;
  8338. this._step = 1;
  8339. this.prettyStep = true;
  8340. this.precision = 5;
  8341. this._current = 0;
  8342. this.setRange(start, end, step, prettyStep);
  8343. };
  8344. /**
  8345. * Set a new range: start, end and step.
  8346. *
  8347. * @param {Number} start The start value
  8348. * @param {Number} end The end value
  8349. * @param {Number} step Optional. Step size. Must be a positive value.
  8350. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  8351. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  8352. */
  8353. StepNumber.prototype.setRange = function(start, end, step, prettyStep) {
  8354. this._start = start ? start : 0;
  8355. this._end = end ? end : 0;
  8356. this.setStep(step, prettyStep);
  8357. };
  8358. /**
  8359. * Set a new step size
  8360. * @param {Number} step New step size. Must be a positive value
  8361. * @param {boolean} prettyStep Optional. If true, the provided step is rounded
  8362. * to a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  8363. */
  8364. StepNumber.prototype.setStep = function(step, prettyStep) {
  8365. if (step === undefined || step <= 0)
  8366. return;
  8367. if (prettyStep !== undefined)
  8368. this.prettyStep = prettyStep;
  8369. if (this.prettyStep === true)
  8370. this._step = StepNumber.calculatePrettyStep(step);
  8371. else
  8372. this._step = step;
  8373. };
  8374. /**
  8375. * Calculate a nice step size, closest to the desired step size.
  8376. * Returns a value in one of the ranges 1*10^n, 2*10^n, or 5*10^n, where n is an
  8377. * integer Number. For example 1, 2, 5, 10, 20, 50, etc...
  8378. * @param {Number} step Desired step size
  8379. * @return {Number} Nice step size
  8380. */
  8381. StepNumber.calculatePrettyStep = function (step) {
  8382. var log10 = function (x) {return Math.log(x) / Math.LN10;};
  8383. // try three steps (multiple of 1, 2, or 5
  8384. var step1 = Math.pow(10, Math.round(log10(step))),
  8385. step2 = 2 * Math.pow(10, Math.round(log10(step / 2))),
  8386. step5 = 5 * Math.pow(10, Math.round(log10(step / 5)));
  8387. // choose the best step (closest to minimum step)
  8388. var prettyStep = step1;
  8389. if (Math.abs(step2 - step) <= Math.abs(prettyStep - step)) prettyStep = step2;
  8390. if (Math.abs(step5 - step) <= Math.abs(prettyStep - step)) prettyStep = step5;
  8391. // for safety
  8392. if (prettyStep <= 0) {
  8393. prettyStep = 1;
  8394. }
  8395. return prettyStep;
  8396. };
  8397. /**
  8398. * returns the current value of the step
  8399. * @return {Number} current value
  8400. */
  8401. StepNumber.prototype.getCurrent = function () {
  8402. return parseFloat(this._current.toPrecision(this.precision));
  8403. };
  8404. /**
  8405. * returns the current step size
  8406. * @return {Number} current step size
  8407. */
  8408. StepNumber.prototype.getStep = function () {
  8409. return this._step;
  8410. };
  8411. /**
  8412. * Set the current value to the largest value smaller than start, which
  8413. * is a multiple of the step size
  8414. */
  8415. StepNumber.prototype.start = function() {
  8416. this._current = this._start - this._start % this._step;
  8417. };
  8418. /**
  8419. * Do a step, add the step size to the current value
  8420. */
  8421. StepNumber.prototype.next = function () {
  8422. this._current += this._step;
  8423. };
  8424. /**
  8425. * Returns true whether the end is reached
  8426. * @return {boolean} True if the current value has passed the end value.
  8427. */
  8428. StepNumber.prototype.end = function () {
  8429. return (this._current > this._end);
  8430. };
  8431. module.exports = StepNumber;
  8432. /***/ },
  8433. /* 18 */
  8434. /***/ function(module, exports, __webpack_require__) {
  8435. var Emitter = __webpack_require__(11);
  8436. var Hammer = __webpack_require__(19);
  8437. var util = __webpack_require__(1);
  8438. var DataSet = __webpack_require__(7);
  8439. var DataView = __webpack_require__(9);
  8440. var Range = __webpack_require__(21);
  8441. var Core = __webpack_require__(25);
  8442. var TimeAxis = __webpack_require__(37);
  8443. var CurrentTime = __webpack_require__(39);
  8444. var CustomTime = __webpack_require__(41);
  8445. var ItemSet = __webpack_require__(26);
  8446. /**
  8447. * Create a timeline visualization
  8448. * @param {HTMLElement} container
  8449. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  8450. * @param {vis.DataSet | Array | google.visualization.DataTable} [groups]
  8451. * @param {Object} [options] See Timeline.setOptions for the available options.
  8452. * @constructor
  8453. * @extends Core
  8454. */
  8455. function Timeline (container, items, groups, options) {
  8456. if (!(this instanceof Timeline)) {
  8457. throw new SyntaxError('Constructor must be called with the new operator');
  8458. }
  8459. // if the third element is options, the forth is groups (optionally);
  8460. if (!(Array.isArray(groups) || groups instanceof DataSet) && groups instanceof Object) {
  8461. var forthArgument = options;
  8462. options = groups;
  8463. groups = forthArgument;
  8464. }
  8465. var me = this;
  8466. this.defaultOptions = {
  8467. start: null,
  8468. end: null,
  8469. autoResize: true,
  8470. orientation: 'bottom',
  8471. width: null,
  8472. height: null,
  8473. maxHeight: null,
  8474. minHeight: null
  8475. };
  8476. this.options = util.deepExtend({}, this.defaultOptions);
  8477. // Create the DOM, props, and emitter
  8478. this._create(container);
  8479. // all components listed here will be repainted automatically
  8480. this.components = [];
  8481. this.body = {
  8482. dom: this.dom,
  8483. domProps: this.props,
  8484. emitter: {
  8485. on: this.on.bind(this),
  8486. off: this.off.bind(this),
  8487. emit: this.emit.bind(this)
  8488. },
  8489. hiddenDates: [],
  8490. util: {
  8491. snap: null, // will be specified after TimeAxis is created
  8492. toScreen: me._toScreen.bind(me),
  8493. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  8494. toTime: me._toTime.bind(me),
  8495. toGlobalTime : me._toGlobalTime.bind(me)
  8496. }
  8497. };
  8498. // range
  8499. this.range = new Range(this.body);
  8500. this.components.push(this.range);
  8501. this.body.range = this.range;
  8502. // time axis
  8503. this.timeAxis = new TimeAxis(this.body);
  8504. this.components.push(this.timeAxis);
  8505. this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
  8506. // current time bar
  8507. this.currentTime = new CurrentTime(this.body);
  8508. this.components.push(this.currentTime);
  8509. // custom time bar
  8510. // Note: time bar will be attached in this.setOptions when selected
  8511. this.customTime = new CustomTime(this.body);
  8512. this.components.push(this.customTime);
  8513. // item set
  8514. this.itemSet = new ItemSet(this.body);
  8515. this.components.push(this.itemSet);
  8516. this.itemsData = null; // DataSet
  8517. this.groupsData = null; // DataSet
  8518. // apply options
  8519. if (options) {
  8520. this.setOptions(options);
  8521. }
  8522. // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
  8523. if (groups) {
  8524. this.setGroups(groups);
  8525. }
  8526. // create itemset
  8527. if (items) {
  8528. this.setItems(items);
  8529. }
  8530. else {
  8531. this.redraw();
  8532. }
  8533. }
  8534. // Extend the functionality from Core
  8535. Timeline.prototype = new Core();
  8536. /**
  8537. * Set items
  8538. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  8539. */
  8540. Timeline.prototype.setItems = function(items) {
  8541. var initialLoad = (this.itemsData == null);
  8542. // convert to type DataSet when needed
  8543. var newDataSet;
  8544. if (!items) {
  8545. newDataSet = null;
  8546. }
  8547. else if (items instanceof DataSet || items instanceof DataView) {
  8548. newDataSet = items;
  8549. }
  8550. else {
  8551. // turn an array into a dataset
  8552. newDataSet = new DataSet(items, {
  8553. type: {
  8554. start: 'Date',
  8555. end: 'Date'
  8556. }
  8557. });
  8558. }
  8559. // set items
  8560. this.itemsData = newDataSet;
  8561. this.itemSet && this.itemSet.setItems(newDataSet);
  8562. if (initialLoad) {
  8563. if (this.options.start != undefined || this.options.end != undefined) {
  8564. if (this.options.start == undefined || this.options.end == undefined) {
  8565. var dataRange = this._getDataRange();
  8566. }
  8567. var start = this.options.start != undefined ? this.options.start : dataRange.start;
  8568. var end = this.options.end != undefined ? this.options.end : dataRange.end;
  8569. this.setWindow(start, end, {animate: false});
  8570. }
  8571. else {
  8572. this.fit({animate: false});
  8573. }
  8574. }
  8575. };
  8576. /**
  8577. * Set groups
  8578. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  8579. */
  8580. Timeline.prototype.setGroups = function(groups) {
  8581. // convert to type DataSet when needed
  8582. var newDataSet;
  8583. if (!groups) {
  8584. newDataSet = null;
  8585. }
  8586. else if (groups instanceof DataSet || groups instanceof DataView) {
  8587. newDataSet = groups;
  8588. }
  8589. else {
  8590. // turn an array into a dataset
  8591. newDataSet = new DataSet(groups);
  8592. }
  8593. this.groupsData = newDataSet;
  8594. this.itemSet.setGroups(newDataSet);
  8595. };
  8596. /**
  8597. * Set selected items by their id. Replaces the current selection
  8598. * Unknown id's are silently ignored.
  8599. * @param {string[] | string} [ids] An array with zero or more id's of the items to be
  8600. * selected. If ids is an empty array, all items will be
  8601. * unselected.
  8602. * @param {Object} [options] Available options:
  8603. * `focus: boolean`
  8604. * If true, focus will be set to the selected item(s)
  8605. * `animate: boolean | number`
  8606. * If true (default), the range is animated
  8607. * smoothly to the new window.
  8608. * If a number, the number is taken as duration
  8609. * for the animation. Default duration is 500 ms.
  8610. * Only applicable when option focus is true.
  8611. */
  8612. Timeline.prototype.setSelection = function(ids, options) {
  8613. this.itemSet && this.itemSet.setSelection(ids);
  8614. if (options && options.focus) {
  8615. this.focus(ids, options);
  8616. }
  8617. };
  8618. /**
  8619. * Get the selected items by their id
  8620. * @return {Array} ids The ids of the selected items
  8621. */
  8622. Timeline.prototype.getSelection = function() {
  8623. return this.itemSet && this.itemSet.getSelection() || [];
  8624. };
  8625. /**
  8626. * Adjust the visible window such that the selected item (or multiple items)
  8627. * are centered on screen.
  8628. * @param {String | String[]} id An item id or array with item ids
  8629. * @param {Object} [options] Available options:
  8630. * `animate: boolean | number`
  8631. * If true (default), the range is animated
  8632. * smoothly to the new window.
  8633. * If a number, the number is taken as duration
  8634. * for the animation. Default duration is 500 ms.
  8635. * Only applicable when option focus is true
  8636. */
  8637. Timeline.prototype.focus = function(id, options) {
  8638. if (!this.itemsData || id == undefined) return;
  8639. var ids = Array.isArray(id) ? id : [id];
  8640. // get the specified item(s)
  8641. var itemsData = this.itemsData.getDataSet().get(ids, {
  8642. type: {
  8643. start: 'Date',
  8644. end: 'Date'
  8645. }
  8646. });
  8647. // calculate minimum start and maximum end of specified items
  8648. var start = null;
  8649. var end = null;
  8650. itemsData.forEach(function (itemData) {
  8651. var s = itemData.start.valueOf();
  8652. var e = 'end' in itemData ? itemData.end.valueOf() : itemData.start.valueOf();
  8653. if (start === null || s < start) {
  8654. start = s;
  8655. }
  8656. if (end === null || e > end) {
  8657. end = e;
  8658. }
  8659. });
  8660. if (start !== null && end !== null) {
  8661. // calculate the new middle and interval for the window
  8662. var middle = (start + end) / 2;
  8663. var interval = Math.max((this.range.end - this.range.start), (end - start) * 1.1);
  8664. var animate = (options && options.animate !== undefined) ? options.animate : true;
  8665. this.range.setRange(middle - interval / 2, middle + interval / 2, animate);
  8666. }
  8667. };
  8668. /**
  8669. * Get the data range of the item set.
  8670. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  8671. * When no minimum is found, min==null
  8672. * When no maximum is found, max==null
  8673. */
  8674. Timeline.prototype.getItemRange = function() {
  8675. // calculate min from start filed
  8676. var dataset = this.itemsData.getDataSet(),
  8677. min = null,
  8678. max = null;
  8679. if (dataset) {
  8680. // calculate the minimum value of the field 'start'
  8681. var minItem = dataset.min('start');
  8682. min = minItem ? util.convert(minItem.start, 'Date').valueOf() : null;
  8683. // Note: we convert first to Date and then to number because else
  8684. // a conversion from ISODate to Number will fail
  8685. // calculate maximum value of fields 'start' and 'end'
  8686. var maxStartItem = dataset.max('start');
  8687. if (maxStartItem) {
  8688. max = util.convert(maxStartItem.start, 'Date').valueOf();
  8689. }
  8690. var maxEndItem = dataset.max('end');
  8691. if (maxEndItem) {
  8692. if (max == null) {
  8693. max = util.convert(maxEndItem.end, 'Date').valueOf();
  8694. }
  8695. else {
  8696. max = Math.max(max, util.convert(maxEndItem.end, 'Date').valueOf());
  8697. }
  8698. }
  8699. }
  8700. return {
  8701. min: (min != null) ? new Date(min) : null,
  8702. max: (max != null) ? new Date(max) : null
  8703. };
  8704. };
  8705. module.exports = Timeline;
  8706. /***/ },
  8707. /* 19 */
  8708. /***/ function(module, exports, __webpack_require__) {
  8709. // Only load hammer.js when in a browser environment
  8710. // (loading hammer.js in a node.js environment gives errors)
  8711. if (typeof window !== 'undefined') {
  8712. module.exports = window['Hammer'] || __webpack_require__(20);
  8713. }
  8714. else {
  8715. module.exports = function () {
  8716. throw Error('hammer.js is only available in a browser, not in node.js.');
  8717. }
  8718. }
  8719. /***/ },
  8720. /* 20 */
  8721. /***/ function(module, exports, __webpack_require__) {
  8722. var __WEBPACK_AMD_DEFINE_RESULT__;/*! Hammer.JS - v1.1.3 - 2014-05-20
  8723. * http://eightmedia.github.io/hammer.js
  8724. *
  8725. * Copyright (c) 2014 Jorik Tangelder <j.tangelder@gmail.com>;
  8726. * Licensed under the MIT license */
  8727. (function(window, undefined) {
  8728. 'use strict';
  8729. /**
  8730. * @main
  8731. * @module hammer
  8732. *
  8733. * @class Hammer
  8734. * @static
  8735. */
  8736. /**
  8737. * Hammer, use this to create instances
  8738. * ````
  8739. * var hammertime = new Hammer(myElement);
  8740. * ````
  8741. *
  8742. * @method Hammer
  8743. * @param {HTMLElement} element
  8744. * @param {Object} [options={}]
  8745. * @return {Hammer.Instance}
  8746. */
  8747. var Hammer = function Hammer(element, options) {
  8748. return new Hammer.Instance(element, options || {});
  8749. };
  8750. /**
  8751. * version, as defined in package.json
  8752. * the value will be set at each build
  8753. * @property VERSION
  8754. * @final
  8755. * @type {String}
  8756. */
  8757. Hammer.VERSION = '1.1.3';
  8758. /**
  8759. * default settings.
  8760. * more settings are defined per gesture at `/gestures`. Each gesture can be disabled/enabled
  8761. * by setting it's name (like `swipe`) to false.
  8762. * You can set the defaults for all instances by changing this object before creating an instance.
  8763. * @example
  8764. * ````
  8765. * Hammer.defaults.drag = false;
  8766. * Hammer.defaults.behavior.touchAction = 'pan-y';
  8767. * delete Hammer.defaults.behavior.userSelect;
  8768. * ````
  8769. * @property defaults
  8770. * @type {Object}
  8771. */
  8772. Hammer.defaults = {
  8773. /**
  8774. * this setting object adds styles and attributes to the element to prevent the browser from doing
  8775. * its native behavior. The css properties are auto prefixed for the browsers when needed.
  8776. * @property defaults.behavior
  8777. * @type {Object}
  8778. */
  8779. behavior: {
  8780. /**
  8781. * Disables text selection to improve the dragging gesture. When the value is `none` it also sets
  8782. * `onselectstart=false` for IE on the element. Mainly for desktop browsers.
  8783. * @property defaults.behavior.userSelect
  8784. * @type {String}
  8785. * @default 'none'
  8786. */
  8787. userSelect: 'none',
  8788. /**
  8789. * Specifies whether and how a given region can be manipulated by the user (for instance, by panning or zooming).
  8790. * Used by Chrome 35> and IE10>. By default this makes the element blocking any touch event.
  8791. * @property defaults.behavior.touchAction
  8792. * @type {String}
  8793. * @default: 'pan-y'
  8794. */
  8795. touchAction: 'pan-y',
  8796. /**
  8797. * Disables the default callout shown when you touch and hold a touch target.
  8798. * On iOS, when you touch and hold a touch target such as a link, Safari displays
  8799. * a callout containing information about the link. This property allows you to disable that callout.
  8800. * @property defaults.behavior.touchCallout
  8801. * @type {String}
  8802. * @default 'none'
  8803. */
  8804. touchCallout: 'none',
  8805. /**
  8806. * Specifies whether zooming is enabled. Used by IE10>
  8807. * @property defaults.behavior.contentZooming
  8808. * @type {String}
  8809. * @default 'none'
  8810. */
  8811. contentZooming: 'none',
  8812. /**
  8813. * Specifies that an entire element should be draggable instead of its contents.
  8814. * Mainly for desktop browsers.
  8815. * @property defaults.behavior.userDrag
  8816. * @type {String}
  8817. * @default 'none'
  8818. */
  8819. userDrag: 'none',
  8820. /**
  8821. * Overrides the highlight color shown when the user taps a link or a JavaScript
  8822. * clickable element in Safari on iPhone. This property obeys the alpha value, if specified.
  8823. *
  8824. * If you don't specify an alpha value, Safari on iPhone applies a default alpha value
  8825. * to the color. To disable tap highlighting, set the alpha value to 0 (invisible).
  8826. * If you set the alpha value to 1.0 (opaque), the element is not visible when tapped.
  8827. * @property defaults.behavior.tapHighlightColor
  8828. * @type {String}
  8829. * @default 'rgba(0,0,0,0)'
  8830. */
  8831. tapHighlightColor: 'rgba(0,0,0,0)'
  8832. }
  8833. };
  8834. /**
  8835. * hammer document where the base events are added at
  8836. * @property DOCUMENT
  8837. * @type {HTMLElement}
  8838. * @default window.document
  8839. */
  8840. Hammer.DOCUMENT = document;
  8841. /**
  8842. * detect support for pointer events
  8843. * @property HAS_POINTEREVENTS
  8844. * @type {Boolean}
  8845. */
  8846. Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;
  8847. /**
  8848. * detect support for touch events
  8849. * @property HAS_TOUCHEVENTS
  8850. * @type {Boolean}
  8851. */
  8852. Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);
  8853. /**
  8854. * detect mobile browsers
  8855. * @property IS_MOBILE
  8856. * @type {Boolean}
  8857. */
  8858. Hammer.IS_MOBILE = /mobile|tablet|ip(ad|hone|od)|android|silk/i.test(navigator.userAgent);
  8859. /**
  8860. * detect if we want to support mouseevents at all
  8861. * @property NO_MOUSEEVENTS
  8862. * @type {Boolean}
  8863. */
  8864. Hammer.NO_MOUSEEVENTS = (Hammer.HAS_TOUCHEVENTS && Hammer.IS_MOBILE) || Hammer.HAS_POINTEREVENTS;
  8865. /**
  8866. * interval in which Hammer recalculates current velocity/direction/angle in ms
  8867. * @property CALCULATE_INTERVAL
  8868. * @type {Number}
  8869. * @default 25
  8870. */
  8871. Hammer.CALCULATE_INTERVAL = 25;
  8872. /**
  8873. * eventtypes per touchevent (start, move, end) are filled by `Event.determineEventTypes` on `setup`
  8874. * the object contains the DOM event names per type (`EVENT_START`, `EVENT_MOVE`, `EVENT_END`)
  8875. * @property EVENT_TYPES
  8876. * @private
  8877. * @writeOnce
  8878. * @type {Object}
  8879. */
  8880. var EVENT_TYPES = {};
  8881. /**
  8882. * direction strings, for safe comparisons
  8883. * @property DIRECTION_DOWN|LEFT|UP|RIGHT
  8884. * @final
  8885. * @type {String}
  8886. * @default 'down' 'left' 'up' 'right'
  8887. */
  8888. var DIRECTION_DOWN = Hammer.DIRECTION_DOWN = 'down';
  8889. var DIRECTION_LEFT = Hammer.DIRECTION_LEFT = 'left';
  8890. var DIRECTION_UP = Hammer.DIRECTION_UP = 'up';
  8891. var DIRECTION_RIGHT = Hammer.DIRECTION_RIGHT = 'right';
  8892. /**
  8893. * pointertype strings, for safe comparisons
  8894. * @property POINTER_MOUSE|TOUCH|PEN
  8895. * @final
  8896. * @type {String}
  8897. * @default 'mouse' 'touch' 'pen'
  8898. */
  8899. var POINTER_MOUSE = Hammer.POINTER_MOUSE = 'mouse';
  8900. var POINTER_TOUCH = Hammer.POINTER_TOUCH = 'touch';
  8901. var POINTER_PEN = Hammer.POINTER_PEN = 'pen';
  8902. /**
  8903. * eventtypes
  8904. * @property EVENT_START|MOVE|END|RELEASE|TOUCH
  8905. * @final
  8906. * @type {String}
  8907. * @default 'start' 'change' 'move' 'end' 'release' 'touch'
  8908. */
  8909. var EVENT_START = Hammer.EVENT_START = 'start';
  8910. var EVENT_MOVE = Hammer.EVENT_MOVE = 'move';
  8911. var EVENT_END = Hammer.EVENT_END = 'end';
  8912. var EVENT_RELEASE = Hammer.EVENT_RELEASE = 'release';
  8913. var EVENT_TOUCH = Hammer.EVENT_TOUCH = 'touch';
  8914. /**
  8915. * if the window events are set...
  8916. * @property READY
  8917. * @writeOnce
  8918. * @type {Boolean}
  8919. * @default false
  8920. */
  8921. Hammer.READY = false;
  8922. /**
  8923. * plugins namespace
  8924. * @property plugins
  8925. * @type {Object}
  8926. */
  8927. Hammer.plugins = Hammer.plugins || {};
  8928. /**
  8929. * gestures namespace
  8930. * see `/gestures` for the definitions
  8931. * @property gestures
  8932. * @type {Object}
  8933. */
  8934. Hammer.gestures = Hammer.gestures || {};
  8935. /**
  8936. * setup events to detect gestures on the document
  8937. * this function is called when creating an new instance
  8938. * @private
  8939. */
  8940. function setup() {
  8941. if(Hammer.READY) {
  8942. return;
  8943. }
  8944. // find what eventtypes we add listeners to
  8945. Event.determineEventTypes();
  8946. // Register all gestures inside Hammer.gestures
  8947. Utils.each(Hammer.gestures, function(gesture) {
  8948. Detection.register(gesture);
  8949. });
  8950. // Add touch events on the document
  8951. Event.onTouch(Hammer.DOCUMENT, EVENT_MOVE, Detection.detect);
  8952. Event.onTouch(Hammer.DOCUMENT, EVENT_END, Detection.detect);
  8953. // Hammer is ready...!
  8954. Hammer.READY = true;
  8955. }
  8956. /**
  8957. * @module hammer
  8958. *
  8959. * @class Utils
  8960. * @static
  8961. */
  8962. var Utils = Hammer.utils = {
  8963. /**
  8964. * extend method, could also be used for cloning when `dest` is an empty object.
  8965. * changes the dest object
  8966. * @method extend
  8967. * @param {Object} dest
  8968. * @param {Object} src
  8969. * @param {Boolean} [merge=false] do a merge
  8970. * @return {Object} dest
  8971. */
  8972. extend: function extend(dest, src, merge) {
  8973. for(var key in src) {
  8974. if(!src.hasOwnProperty(key) || (dest[key] !== undefined && merge)) {
  8975. continue;
  8976. }
  8977. dest[key] = src[key];
  8978. }
  8979. return dest;
  8980. },
  8981. /**
  8982. * simple addEventListener wrapper
  8983. * @method on
  8984. * @param {HTMLElement} element
  8985. * @param {String} type
  8986. * @param {Function} handler
  8987. */
  8988. on: function on(element, type, handler) {
  8989. element.addEventListener(type, handler, false);
  8990. },
  8991. /**
  8992. * simple removeEventListener wrapper
  8993. * @method off
  8994. * @param {HTMLElement} element
  8995. * @param {String} type
  8996. * @param {Function} handler
  8997. */
  8998. off: function off(element, type, handler) {
  8999. element.removeEventListener(type, handler, false);
  9000. },
  9001. /**
  9002. * forEach over arrays and objects
  9003. * @method each
  9004. * @param {Object|Array} obj
  9005. * @param {Function} iterator
  9006. * @param {any} iterator.item
  9007. * @param {Number} iterator.index
  9008. * @param {Object|Array} iterator.obj the source object
  9009. * @param {Object} context value to use as `this` in the iterator
  9010. */
  9011. each: function each(obj, iterator, context) {
  9012. var i, len;
  9013. // native forEach on arrays
  9014. if('forEach' in obj) {
  9015. obj.forEach(iterator, context);
  9016. // arrays
  9017. } else if(obj.length !== undefined) {
  9018. for(i = 0, len = obj.length; i < len; i++) {
  9019. if(iterator.call(context, obj[i], i, obj) === false) {
  9020. return;
  9021. }
  9022. }
  9023. // objects
  9024. } else {
  9025. for(i in obj) {
  9026. if(obj.hasOwnProperty(i) &&
  9027. iterator.call(context, obj[i], i, obj) === false) {
  9028. return;
  9029. }
  9030. }
  9031. }
  9032. },
  9033. /**
  9034. * find if a string contains the string using indexOf
  9035. * @method inStr
  9036. * @param {String} src
  9037. * @param {String} find
  9038. * @return {Boolean} found
  9039. */
  9040. inStr: function inStr(src, find) {
  9041. return src.indexOf(find) > -1;
  9042. },
  9043. /**
  9044. * find if a array contains the object using indexOf or a simple polyfill
  9045. * @method inArray
  9046. * @param {String} src
  9047. * @param {String} find
  9048. * @return {Boolean|Number} false when not found, or the index
  9049. */
  9050. inArray: function inArray(src, find) {
  9051. if(src.indexOf) {
  9052. var index = src.indexOf(find);
  9053. return (index === -1) ? false : index;
  9054. } else {
  9055. for(var i = 0, len = src.length; i < len; i++) {
  9056. if(src[i] === find) {
  9057. return i;
  9058. }
  9059. }
  9060. return false;
  9061. }
  9062. },
  9063. /**
  9064. * convert an array-like object (`arguments`, `touchlist`) to an array
  9065. * @method toArray
  9066. * @param {Object} obj
  9067. * @return {Array}
  9068. */
  9069. toArray: function toArray(obj) {
  9070. return Array.prototype.slice.call(obj, 0);
  9071. },
  9072. /**
  9073. * find if a node is in the given parent
  9074. * @method hasParent
  9075. * @param {HTMLElement} node
  9076. * @param {HTMLElement} parent
  9077. * @return {Boolean} found
  9078. */
  9079. hasParent: function hasParent(node, parent) {
  9080. while(node) {
  9081. if(node == parent) {
  9082. return true;
  9083. }
  9084. node = node.parentNode;
  9085. }
  9086. return false;
  9087. },
  9088. /**
  9089. * get the center of all the touches
  9090. * @method getCenter
  9091. * @param {Array} touches
  9092. * @return {Object} center contains `pageX`, `pageY`, `clientX` and `clientY` properties
  9093. */
  9094. getCenter: function getCenter(touches) {
  9095. var pageX = [],
  9096. pageY = [],
  9097. clientX = [],
  9098. clientY = [],
  9099. min = Math.min,
  9100. max = Math.max;
  9101. // no need to loop when only one touch
  9102. if(touches.length === 1) {
  9103. return {
  9104. pageX: touches[0].pageX,
  9105. pageY: touches[0].pageY,
  9106. clientX: touches[0].clientX,
  9107. clientY: touches[0].clientY
  9108. };
  9109. }
  9110. Utils.each(touches, function(touch) {
  9111. pageX.push(touch.pageX);
  9112. pageY.push(touch.pageY);
  9113. clientX.push(touch.clientX);
  9114. clientY.push(touch.clientY);
  9115. });
  9116. return {
  9117. pageX: (min.apply(Math, pageX) + max.apply(Math, pageX)) / 2,
  9118. pageY: (min.apply(Math, pageY) + max.apply(Math, pageY)) / 2,
  9119. clientX: (min.apply(Math, clientX) + max.apply(Math, clientX)) / 2,
  9120. clientY: (min.apply(Math, clientY) + max.apply(Math, clientY)) / 2
  9121. };
  9122. },
  9123. /**
  9124. * calculate the velocity between two points. unit is in px per ms.
  9125. * @method getVelocity
  9126. * @param {Number} deltaTime
  9127. * @param {Number} deltaX
  9128. * @param {Number} deltaY
  9129. * @return {Object} velocity `x` and `y`
  9130. */
  9131. getVelocity: function getVelocity(deltaTime, deltaX, deltaY) {
  9132. return {
  9133. x: Math.abs(deltaX / deltaTime) || 0,
  9134. y: Math.abs(deltaY / deltaTime) || 0
  9135. };
  9136. },
  9137. /**
  9138. * calculate the angle between two coordinates
  9139. * @method getAngle
  9140. * @param {Touch} touch1
  9141. * @param {Touch} touch2
  9142. * @return {Number} angle
  9143. */
  9144. getAngle: function getAngle(touch1, touch2) {
  9145. var x = touch2.clientX - touch1.clientX,
  9146. y = touch2.clientY - touch1.clientY;
  9147. return Math.atan2(y, x) * 180 / Math.PI;
  9148. },
  9149. /**
  9150. * do a small comparision to get the direction between two touches.
  9151. * @method getDirection
  9152. * @param {Touch} touch1
  9153. * @param {Touch} touch2
  9154. * @return {String} direction matches `DIRECTION_LEFT|RIGHT|UP|DOWN`
  9155. */
  9156. getDirection: function getDirection(touch1, touch2) {
  9157. var x = Math.abs(touch1.clientX - touch2.clientX),
  9158. y = Math.abs(touch1.clientY - touch2.clientY);
  9159. if(x >= y) {
  9160. return touch1.clientX - touch2.clientX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
  9161. }
  9162. return touch1.clientY - touch2.clientY > 0 ? DIRECTION_UP : DIRECTION_DOWN;
  9163. },
  9164. /**
  9165. * calculate the distance between two touches
  9166. * @method getDistance
  9167. * @param {Touch}touch1
  9168. * @param {Touch} touch2
  9169. * @return {Number} distance
  9170. */
  9171. getDistance: function getDistance(touch1, touch2) {
  9172. var x = touch2.clientX - touch1.clientX,
  9173. y = touch2.clientY - touch1.clientY;
  9174. return Math.sqrt((x * x) + (y * y));
  9175. },
  9176. /**
  9177. * calculate the scale factor between two touchLists
  9178. * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
  9179. * @method getScale
  9180. * @param {Array} start array of touches
  9181. * @param {Array} end array of touches
  9182. * @return {Number} scale
  9183. */
  9184. getScale: function getScale(start, end) {
  9185. // need two fingers...
  9186. if(start.length >= 2 && end.length >= 2) {
  9187. return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]);
  9188. }
  9189. return 1;
  9190. },
  9191. /**
  9192. * calculate the rotation degrees between two touchLists
  9193. * @method getRotation
  9194. * @param {Array} start array of touches
  9195. * @param {Array} end array of touches
  9196. * @return {Number} rotation
  9197. */
  9198. getRotation: function getRotation(start, end) {
  9199. // need two fingers
  9200. if(start.length >= 2 && end.length >= 2) {
  9201. return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]);
  9202. }
  9203. return 0;
  9204. },
  9205. /**
  9206. * find out if the direction is vertical *
  9207. * @method isVertical
  9208. * @param {String} direction matches `DIRECTION_UP|DOWN`
  9209. * @return {Boolean} is_vertical
  9210. */
  9211. isVertical: function isVertical(direction) {
  9212. return direction == DIRECTION_UP || direction == DIRECTION_DOWN;
  9213. },
  9214. /**
  9215. * set css properties with their prefixes
  9216. * @param {HTMLElement} element
  9217. * @param {String} prop
  9218. * @param {String} value
  9219. * @param {Boolean} [toggle=true]
  9220. * @return {Boolean}
  9221. */
  9222. setPrefixedCss: function setPrefixedCss(element, prop, value, toggle) {
  9223. var prefixes = ['', 'Webkit', 'Moz', 'O', 'ms'];
  9224. prop = Utils.toCamelCase(prop);
  9225. for(var i = 0; i < prefixes.length; i++) {
  9226. var p = prop;
  9227. // prefixes
  9228. if(prefixes[i]) {
  9229. p = prefixes[i] + p.slice(0, 1).toUpperCase() + p.slice(1);
  9230. }
  9231. // test the style
  9232. if(p in element.style) {
  9233. element.style[p] = (toggle == null || toggle) && value || '';
  9234. break;
  9235. }
  9236. }
  9237. },
  9238. /**
  9239. * toggle browser default behavior by setting css properties.
  9240. * `userSelect='none'` also sets `element.onselectstart` to false
  9241. * `userDrag='none'` also sets `element.ondragstart` to false
  9242. *
  9243. * @method toggleBehavior
  9244. * @param {HtmlElement} element
  9245. * @param {Object} props
  9246. * @param {Boolean} [toggle=true]
  9247. */
  9248. toggleBehavior: function toggleBehavior(element, props, toggle) {
  9249. if(!props || !element || !element.style) {
  9250. return;
  9251. }
  9252. // set the css properties
  9253. Utils.each(props, function(value, prop) {
  9254. Utils.setPrefixedCss(element, prop, value, toggle);
  9255. });
  9256. var falseFn = toggle && function() {
  9257. return false;
  9258. };
  9259. // also the disable onselectstart
  9260. if(props.userSelect == 'none') {
  9261. element.onselectstart = falseFn;
  9262. }
  9263. // and disable ondragstart
  9264. if(props.userDrag == 'none') {
  9265. element.ondragstart = falseFn;
  9266. }
  9267. },
  9268. /**
  9269. * convert a string with underscores to camelCase
  9270. * so prevent_default becomes preventDefault
  9271. * @param {String} str
  9272. * @return {String} camelCaseStr
  9273. */
  9274. toCamelCase: function toCamelCase(str) {
  9275. return str.replace(/[_-]([a-z])/g, function(s) {
  9276. return s[1].toUpperCase();
  9277. });
  9278. }
  9279. };
  9280. /**
  9281. * @module hammer
  9282. */
  9283. /**
  9284. * @class Event
  9285. * @static
  9286. */
  9287. var Event = Hammer.event = {
  9288. /**
  9289. * when touch events have been fired, this is true
  9290. * this is used to stop mouse events
  9291. * @property prevent_mouseevents
  9292. * @private
  9293. * @type {Boolean}
  9294. */
  9295. preventMouseEvents: false,
  9296. /**
  9297. * if EVENT_START has been fired
  9298. * @property started
  9299. * @private
  9300. * @type {Boolean}
  9301. */
  9302. started: false,
  9303. /**
  9304. * when the mouse is hold down, this is true
  9305. * @property should_detect
  9306. * @private
  9307. * @type {Boolean}
  9308. */
  9309. shouldDetect: false,
  9310. /**
  9311. * simple event binder with a hook and support for multiple types
  9312. * @method on
  9313. * @param {HTMLElement} element
  9314. * @param {String} type
  9315. * @param {Function} handler
  9316. * @param {Function} [hook]
  9317. * @param {Object} hook.type
  9318. */
  9319. on: function on(element, type, handler, hook) {
  9320. var types = type.split(' ');
  9321. Utils.each(types, function(type) {
  9322. Utils.on(element, type, handler);
  9323. hook && hook(type);
  9324. });
  9325. },
  9326. /**
  9327. * simple event unbinder with a hook and support for multiple types
  9328. * @method off
  9329. * @param {HTMLElement} element
  9330. * @param {String} type
  9331. * @param {Function} handler
  9332. * @param {Function} [hook]
  9333. * @param {Object} hook.type
  9334. */
  9335. off: function off(element, type, handler, hook) {
  9336. var types = type.split(' ');
  9337. Utils.each(types, function(type) {
  9338. Utils.off(element, type, handler);
  9339. hook && hook(type);
  9340. });
  9341. },
  9342. /**
  9343. * the core touch event handler.
  9344. * this finds out if we should to detect gestures
  9345. * @method onTouch
  9346. * @param {HTMLElement} element
  9347. * @param {String} eventType matches `EVENT_START|MOVE|END`
  9348. * @param {Function} handler
  9349. * @return onTouchHandler {Function} the core event handler
  9350. */
  9351. onTouch: function onTouch(element, eventType, handler) {
  9352. var self = this;
  9353. var onTouchHandler = function onTouchHandler(ev) {
  9354. var srcType = ev.type.toLowerCase(),
  9355. isPointer = Hammer.HAS_POINTEREVENTS,
  9356. isMouse = Utils.inStr(srcType, 'mouse'),
  9357. triggerType;
  9358. // if we are in a mouseevent, but there has been a touchevent triggered in this session
  9359. // we want to do nothing. simply break out of the event.
  9360. if(isMouse && self.preventMouseEvents) {
  9361. return;
  9362. // mousebutton must be down
  9363. } else if(isMouse && eventType == EVENT_START && ev.button === 0) {
  9364. self.preventMouseEvents = false;
  9365. self.shouldDetect = true;
  9366. } else if(isPointer && eventType == EVENT_START) {
  9367. self.shouldDetect = (ev.buttons === 1 || PointerEvent.matchType(POINTER_TOUCH, ev));
  9368. // just a valid start event, but no mouse
  9369. } else if(!isMouse && eventType == EVENT_START) {
  9370. self.preventMouseEvents = true;
  9371. self.shouldDetect = true;
  9372. }
  9373. // update the pointer event before entering the detection
  9374. if(isPointer && eventType != EVENT_END) {
  9375. PointerEvent.updatePointer(eventType, ev);
  9376. }
  9377. // we are in a touch/down state, so allowed detection of gestures
  9378. if(self.shouldDetect) {
  9379. triggerType = self.doDetect.call(self, ev, eventType, element, handler);
  9380. }
  9381. // ...and we are done with the detection
  9382. // so reset everything to start each detection totally fresh
  9383. if(triggerType == EVENT_END) {
  9384. self.preventMouseEvents = false;
  9385. self.shouldDetect = false;
  9386. PointerEvent.reset();
  9387. // update the pointerevent object after the detection
  9388. }
  9389. if(isPointer && eventType == EVENT_END) {
  9390. PointerEvent.updatePointer(eventType, ev);
  9391. }
  9392. };
  9393. this.on(element, EVENT_TYPES[eventType], onTouchHandler);
  9394. return onTouchHandler;
  9395. },
  9396. /**
  9397. * the core detection method
  9398. * this finds out what hammer-touch-events to trigger
  9399. * @method doDetect
  9400. * @param {Object} ev
  9401. * @param {String} eventType matches `EVENT_START|MOVE|END`
  9402. * @param {HTMLElement} element
  9403. * @param {Function} handler
  9404. * @return {String} triggerType matches `EVENT_START|MOVE|END`
  9405. */
  9406. doDetect: function doDetect(ev, eventType, element, handler) {
  9407. var touchList = this.getTouchList(ev, eventType);
  9408. var touchListLength = touchList.length;
  9409. var triggerType = eventType;
  9410. var triggerChange = touchList.trigger; // used by fakeMultitouch plugin
  9411. var changedLength = touchListLength;
  9412. // at each touchstart-like event we want also want to trigger a TOUCH event...
  9413. if(eventType == EVENT_START) {
  9414. triggerChange = EVENT_TOUCH;
  9415. // ...the same for a touchend-like event
  9416. } else if(eventType == EVENT_END) {
  9417. triggerChange = EVENT_RELEASE;
  9418. // keep track of how many touches have been removed
  9419. changedLength = touchList.length - ((ev.changedTouches) ? ev.changedTouches.length : 1);
  9420. }
  9421. // after there are still touches on the screen,
  9422. // we just want to trigger a MOVE event. so change the START or END to a MOVE
  9423. // but only after detection has been started, the first time we actualy want a START
  9424. if(changedLength > 0 && this.started) {
  9425. triggerType = EVENT_MOVE;
  9426. }
  9427. // detection has been started, we keep track of this, see above
  9428. this.started = true;
  9429. // generate some event data, some basic information
  9430. var evData = this.collectEventData(element, triggerType, touchList, ev);
  9431. // trigger the triggerType event before the change (TOUCH, RELEASE) events
  9432. // but the END event should be at last
  9433. if(eventType != EVENT_END) {
  9434. handler.call(Detection, evData);
  9435. }
  9436. // trigger a change (TOUCH, RELEASE) event, this means the length of the touches changed
  9437. if(triggerChange) {
  9438. evData.changedLength = changedLength;
  9439. evData.eventType = triggerChange;
  9440. handler.call(Detection, evData);
  9441. evData.eventType = triggerType;
  9442. delete evData.changedLength;
  9443. }
  9444. // trigger the END event
  9445. if(triggerType == EVENT_END) {
  9446. handler.call(Detection, evData);
  9447. // ...and we are done with the detection
  9448. // so reset everything to start each detection totally fresh
  9449. this.started = false;
  9450. }
  9451. return triggerType;
  9452. },
  9453. /**
  9454. * we have different events for each device/browser
  9455. * determine what we need and set them in the EVENT_TYPES constant
  9456. * the `onTouch` method is bind to these properties.
  9457. * @method determineEventTypes
  9458. * @return {Object} events
  9459. */
  9460. determineEventTypes: function determineEventTypes() {
  9461. var types;
  9462. if(Hammer.HAS_POINTEREVENTS) {
  9463. if(window.PointerEvent) {
  9464. types = [
  9465. 'pointerdown',
  9466. 'pointermove',
  9467. 'pointerup pointercancel lostpointercapture'
  9468. ];
  9469. } else {
  9470. types = [
  9471. 'MSPointerDown',
  9472. 'MSPointerMove',
  9473. 'MSPointerUp MSPointerCancel MSLostPointerCapture'
  9474. ];
  9475. }
  9476. } else if(Hammer.NO_MOUSEEVENTS) {
  9477. types = [
  9478. 'touchstart',
  9479. 'touchmove',
  9480. 'touchend touchcancel'
  9481. ];
  9482. } else {
  9483. types = [
  9484. 'touchstart mousedown',
  9485. 'touchmove mousemove',
  9486. 'touchend touchcancel mouseup'
  9487. ];
  9488. }
  9489. EVENT_TYPES[EVENT_START] = types[0];
  9490. EVENT_TYPES[EVENT_MOVE] = types[1];
  9491. EVENT_TYPES[EVENT_END] = types[2];
  9492. return EVENT_TYPES;
  9493. },
  9494. /**
  9495. * create touchList depending on the event
  9496. * @method getTouchList
  9497. * @param {Object} ev
  9498. * @param {String} eventType
  9499. * @return {Array} touches
  9500. */
  9501. getTouchList: function getTouchList(ev, eventType) {
  9502. // get the fake pointerEvent touchlist
  9503. if(Hammer.HAS_POINTEREVENTS) {
  9504. return PointerEvent.getTouchList();
  9505. }
  9506. // get the touchlist
  9507. if(ev.touches) {
  9508. if(eventType == EVENT_MOVE) {
  9509. return ev.touches;
  9510. }
  9511. var identifiers = [];
  9512. var concat = [].concat(Utils.toArray(ev.touches), Utils.toArray(ev.changedTouches));
  9513. var touchList = [];
  9514. Utils.each(concat, function(touch) {
  9515. if(Utils.inArray(identifiers, touch.identifier) === false) {
  9516. touchList.push(touch);
  9517. }
  9518. identifiers.push(touch.identifier);
  9519. });
  9520. return touchList;
  9521. }
  9522. // make fake touchList from mouse position
  9523. ev.identifier = 1;
  9524. return [ev];
  9525. },
  9526. /**
  9527. * collect basic event data
  9528. * @method collectEventData
  9529. * @param {HTMLElement} element
  9530. * @param {String} eventType matches `EVENT_START|MOVE|END`
  9531. * @param {Array} touches
  9532. * @param {Object} ev
  9533. * @return {Object} ev
  9534. */
  9535. collectEventData: function collectEventData(element, eventType, touches, ev) {
  9536. // find out pointerType
  9537. var pointerType = POINTER_TOUCH;
  9538. if(Utils.inStr(ev.type, 'mouse') || PointerEvent.matchType(POINTER_MOUSE, ev)) {
  9539. pointerType = POINTER_MOUSE;
  9540. } else if(PointerEvent.matchType(POINTER_PEN, ev)) {
  9541. pointerType = POINTER_PEN;
  9542. }
  9543. return {
  9544. center: Utils.getCenter(touches),
  9545. timeStamp: Date.now(),
  9546. target: ev.target,
  9547. touches: touches,
  9548. eventType: eventType,
  9549. pointerType: pointerType,
  9550. srcEvent: ev,
  9551. /**
  9552. * prevent the browser default actions
  9553. * mostly used to disable scrolling of the browser
  9554. */
  9555. preventDefault: function() {
  9556. var srcEvent = this.srcEvent;
  9557. srcEvent.preventManipulation && srcEvent.preventManipulation();
  9558. srcEvent.preventDefault && srcEvent.preventDefault();
  9559. },
  9560. /**
  9561. * stop bubbling the event up to its parents
  9562. */
  9563. stopPropagation: function() {
  9564. this.srcEvent.stopPropagation();
  9565. },
  9566. /**
  9567. * immediately stop gesture detection
  9568. * might be useful after a swipe was detected
  9569. * @return {*}
  9570. */
  9571. stopDetect: function() {
  9572. return Detection.stopDetect();
  9573. }
  9574. };
  9575. }
  9576. };
  9577. /**
  9578. * @module hammer
  9579. *
  9580. * @class PointerEvent
  9581. * @static
  9582. */
  9583. var PointerEvent = Hammer.PointerEvent = {
  9584. /**
  9585. * holds all pointers, by `identifier`
  9586. * @property pointers
  9587. * @type {Object}
  9588. */
  9589. pointers: {},
  9590. /**
  9591. * get the pointers as an array
  9592. * @method getTouchList
  9593. * @return {Array} touchlist
  9594. */
  9595. getTouchList: function getTouchList() {
  9596. var touchlist = [];
  9597. // we can use forEach since pointerEvents only is in IE10
  9598. Utils.each(this.pointers, function(pointer) {
  9599. touchlist.push(pointer);
  9600. });
  9601. return touchlist;
  9602. },
  9603. /**
  9604. * update the position of a pointer
  9605. * @method updatePointer
  9606. * @param {String} eventType matches `EVENT_START|MOVE|END`
  9607. * @param {Object} pointerEvent
  9608. */
  9609. updatePointer: function updatePointer(eventType, pointerEvent) {
  9610. if(eventType == EVENT_END || (eventType != EVENT_END && pointerEvent.buttons !== 1)) {
  9611. delete this.pointers[pointerEvent.pointerId];
  9612. } else {
  9613. pointerEvent.identifier = pointerEvent.pointerId;
  9614. this.pointers[pointerEvent.pointerId] = pointerEvent;
  9615. }
  9616. },
  9617. /**
  9618. * check if ev matches pointertype
  9619. * @method matchType
  9620. * @param {String} pointerType matches `POINTER_MOUSE|TOUCH|PEN`
  9621. * @param {PointerEvent} ev
  9622. */
  9623. matchType: function matchType(pointerType, ev) {
  9624. if(!ev.pointerType) {
  9625. return false;
  9626. }
  9627. var pt = ev.pointerType,
  9628. types = {};
  9629. types[POINTER_MOUSE] = (pt === (ev.MSPOINTER_TYPE_MOUSE || POINTER_MOUSE));
  9630. types[POINTER_TOUCH] = (pt === (ev.MSPOINTER_TYPE_TOUCH || POINTER_TOUCH));
  9631. types[POINTER_PEN] = (pt === (ev.MSPOINTER_TYPE_PEN || POINTER_PEN));
  9632. return types[pointerType];
  9633. },
  9634. /**
  9635. * reset the stored pointers
  9636. * @method reset
  9637. */
  9638. reset: function resetList() {
  9639. this.pointers = {};
  9640. }
  9641. };
  9642. /**
  9643. * @module hammer
  9644. *
  9645. * @class Detection
  9646. * @static
  9647. */
  9648. var Detection = Hammer.detection = {
  9649. // contains all registred Hammer.gestures in the correct order
  9650. gestures: [],
  9651. // data of the current Hammer.gesture detection session
  9652. current: null,
  9653. // the previous Hammer.gesture session data
  9654. // is a full clone of the previous gesture.current object
  9655. previous: null,
  9656. // when this becomes true, no gestures are fired
  9657. stopped: false,
  9658. /**
  9659. * start Hammer.gesture detection
  9660. * @method startDetect
  9661. * @param {Hammer.Instance} inst
  9662. * @param {Object} eventData
  9663. */
  9664. startDetect: function startDetect(inst, eventData) {
  9665. // already busy with a Hammer.gesture detection on an element
  9666. if(this.current) {
  9667. return;
  9668. }
  9669. this.stopped = false;
  9670. // holds current session
  9671. this.current = {
  9672. inst: inst, // reference to HammerInstance we're working for
  9673. startEvent: Utils.extend({}, eventData), // start eventData for distances, timing etc
  9674. lastEvent: false, // last eventData
  9675. lastCalcEvent: false, // last eventData for calculations.
  9676. futureCalcEvent: false, // last eventData for calculations.
  9677. lastCalcData: {}, // last lastCalcData
  9678. name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
  9679. };
  9680. this.detect(eventData);
  9681. },
  9682. /**
  9683. * Hammer.gesture detection
  9684. * @method detect
  9685. * @param {Object} eventData
  9686. * @return {any}
  9687. */
  9688. detect: function detect(eventData) {
  9689. if(!this.current || this.stopped) {
  9690. return;
  9691. }
  9692. // extend event data with calculations about scale, distance etc
  9693. eventData = this.extendEventData(eventData);
  9694. // hammer instance and instance options
  9695. var inst = this.current.inst,
  9696. instOptions = inst.options;
  9697. // call Hammer.gesture handlers
  9698. Utils.each(this.gestures, function triggerGesture(gesture) {
  9699. // only when the instance options have enabled this gesture
  9700. if(!this.stopped && inst.enabled && instOptions[gesture.name]) {
  9701. gesture.handler.call(gesture, eventData, inst);
  9702. }
  9703. }, this);
  9704. // store as previous event event
  9705. if(this.current) {
  9706. this.current.lastEvent = eventData;
  9707. }
  9708. if(eventData.eventType == EVENT_END) {
  9709. this.stopDetect();
  9710. }
  9711. return eventData;
  9712. },
  9713. /**
  9714. * clear the Hammer.gesture vars
  9715. * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
  9716. * to stop other Hammer.gestures from being fired
  9717. * @method stopDetect
  9718. */
  9719. stopDetect: function stopDetect() {
  9720. // clone current data to the store as the previous gesture
  9721. // used for the double tap gesture, since this is an other gesture detect session
  9722. this.previous = Utils.extend({}, this.current);
  9723. // reset the current
  9724. this.current = null;
  9725. this.stopped = true;
  9726. },
  9727. /**
  9728. * calculate velocity, angle and direction
  9729. * @method getVelocityData
  9730. * @param {Object} ev
  9731. * @param {Object} center
  9732. * @param {Number} deltaTime
  9733. * @param {Number} deltaX
  9734. * @param {Number} deltaY
  9735. */
  9736. getCalculatedData: function getCalculatedData(ev, center, deltaTime, deltaX, deltaY) {
  9737. var cur = this.current,
  9738. recalc = false,
  9739. calcEv = cur.lastCalcEvent,
  9740. calcData = cur.lastCalcData;
  9741. if(calcEv && ev.timeStamp - calcEv.timeStamp > Hammer.CALCULATE_INTERVAL) {
  9742. center = calcEv.center;
  9743. deltaTime = ev.timeStamp - calcEv.timeStamp;
  9744. deltaX = ev.center.clientX - calcEv.center.clientX;
  9745. deltaY = ev.center.clientY - calcEv.center.clientY;
  9746. recalc = true;
  9747. }
  9748. if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
  9749. cur.futureCalcEvent = ev;
  9750. }
  9751. if(!cur.lastCalcEvent || recalc) {
  9752. calcData.velocity = Utils.getVelocity(deltaTime, deltaX, deltaY);
  9753. calcData.angle = Utils.getAngle(center, ev.center);
  9754. calcData.direction = Utils.getDirection(center, ev.center);
  9755. cur.lastCalcEvent = cur.futureCalcEvent || ev;
  9756. cur.futureCalcEvent = ev;
  9757. }
  9758. ev.velocityX = calcData.velocity.x;
  9759. ev.velocityY = calcData.velocity.y;
  9760. ev.interimAngle = calcData.angle;
  9761. ev.interimDirection = calcData.direction;
  9762. },
  9763. /**
  9764. * extend eventData for Hammer.gestures
  9765. * @method extendEventData
  9766. * @param {Object} ev
  9767. * @return {Object} ev
  9768. */
  9769. extendEventData: function extendEventData(ev) {
  9770. var cur = this.current,
  9771. startEv = cur.startEvent,
  9772. lastEv = cur.lastEvent || startEv;
  9773. // update the start touchlist to calculate the scale/rotation
  9774. if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
  9775. startEv.touches = [];
  9776. Utils.each(ev.touches, function(touch) {
  9777. startEv.touches.push({
  9778. clientX: touch.clientX,
  9779. clientY: touch.clientY
  9780. });
  9781. });
  9782. }
  9783. var deltaTime = ev.timeStamp - startEv.timeStamp,
  9784. deltaX = ev.center.clientX - startEv.center.clientX,
  9785. deltaY = ev.center.clientY - startEv.center.clientY;
  9786. this.getCalculatedData(ev, lastEv.center, deltaTime, deltaX, deltaY);
  9787. Utils.extend(ev, {
  9788. startEvent: startEv,
  9789. deltaTime: deltaTime,
  9790. deltaX: deltaX,
  9791. deltaY: deltaY,
  9792. distance: Utils.getDistance(startEv.center, ev.center),
  9793. angle: Utils.getAngle(startEv.center, ev.center),
  9794. direction: Utils.getDirection(startEv.center, ev.center),
  9795. scale: Utils.getScale(startEv.touches, ev.touches),
  9796. rotation: Utils.getRotation(startEv.touches, ev.touches)
  9797. });
  9798. return ev;
  9799. },
  9800. /**
  9801. * register new gesture
  9802. * @method register
  9803. * @param {Object} gesture object, see `gestures/` for documentation
  9804. * @return {Array} gestures
  9805. */
  9806. register: function register(gesture) {
  9807. // add an enable gesture options if there is no given
  9808. var options = gesture.defaults || {};
  9809. if(options[gesture.name] === undefined) {
  9810. options[gesture.name] = true;
  9811. }
  9812. // extend Hammer default options with the Hammer.gesture options
  9813. Utils.extend(Hammer.defaults, options, true);
  9814. // set its index
  9815. gesture.index = gesture.index || 1000;
  9816. // add Hammer.gesture to the list
  9817. this.gestures.push(gesture);
  9818. // sort the list by index
  9819. this.gestures.sort(function(a, b) {
  9820. if(a.index < b.index) {
  9821. return -1;
  9822. }
  9823. if(a.index > b.index) {
  9824. return 1;
  9825. }
  9826. return 0;
  9827. });
  9828. return this.gestures;
  9829. }
  9830. };
  9831. /**
  9832. * @module hammer
  9833. */
  9834. /**
  9835. * create new hammer instance
  9836. * all methods should return the instance itself, so it is chainable.
  9837. *
  9838. * @class Instance
  9839. * @constructor
  9840. * @param {HTMLElement} element
  9841. * @param {Object} [options={}] options are merged with `Hammer.defaults`
  9842. * @return {Hammer.Instance}
  9843. */
  9844. Hammer.Instance = function(element, options) {
  9845. var self = this;
  9846. // setup HammerJS window events and register all gestures
  9847. // this also sets up the default options
  9848. setup();
  9849. /**
  9850. * @property element
  9851. * @type {HTMLElement}
  9852. */
  9853. this.element = element;
  9854. /**
  9855. * @property enabled
  9856. * @type {Boolean}
  9857. * @protected
  9858. */
  9859. this.enabled = true;
  9860. /**
  9861. * options, merged with the defaults
  9862. * options with an _ are converted to camelCase
  9863. * @property options
  9864. * @type {Object}
  9865. */
  9866. Utils.each(options, function(value, name) {
  9867. delete options[name];
  9868. options[Utils.toCamelCase(name)] = value;
  9869. });
  9870. this.options = Utils.extend(Utils.extend({}, Hammer.defaults), options || {});
  9871. // add some css to the element to prevent the browser from doing its native behavoir
  9872. if(this.options.behavior) {
  9873. Utils.toggleBehavior(this.element, this.options.behavior, true);
  9874. }
  9875. /**
  9876. * event start handler on the element to start the detection
  9877. * @property eventStartHandler
  9878. * @type {Object}
  9879. */
  9880. this.eventStartHandler = Event.onTouch(element, EVENT_START, function(ev) {
  9881. if(self.enabled && ev.eventType == EVENT_START) {
  9882. Detection.startDetect(self, ev);
  9883. } else if(ev.eventType == EVENT_TOUCH) {
  9884. Detection.detect(ev);
  9885. }
  9886. });
  9887. /**
  9888. * keep a list of user event handlers which needs to be removed when calling 'dispose'
  9889. * @property eventHandlers
  9890. * @type {Array}
  9891. */
  9892. this.eventHandlers = [];
  9893. };
  9894. Hammer.Instance.prototype = {
  9895. /**
  9896. * bind events to the instance
  9897. * @method on
  9898. * @chainable
  9899. * @param {String} gestures multiple gestures by splitting with a space
  9900. * @param {Function} handler
  9901. * @param {Object} handler.ev event object
  9902. */
  9903. on: function onEvent(gestures, handler) {
  9904. var self = this;
  9905. Event.on(self.element, gestures, handler, function(type) {
  9906. self.eventHandlers.push({ gesture: type, handler: handler });
  9907. });
  9908. return self;
  9909. },
  9910. /**
  9911. * unbind events to the instance
  9912. * @method off
  9913. * @chainable
  9914. * @param {String} gestures
  9915. * @param {Function} handler
  9916. */
  9917. off: function offEvent(gestures, handler) {
  9918. var self = this;
  9919. Event.off(self.element, gestures, handler, function(type) {
  9920. var index = Utils.inArray({ gesture: type, handler: handler });
  9921. if(index !== false) {
  9922. self.eventHandlers.splice(index, 1);
  9923. }
  9924. });
  9925. return self;
  9926. },
  9927. /**
  9928. * trigger gesture event
  9929. * @method trigger
  9930. * @chainable
  9931. * @param {String} gesture
  9932. * @param {Object} [eventData]
  9933. */
  9934. trigger: function triggerEvent(gesture, eventData) {
  9935. // optional
  9936. if(!eventData) {
  9937. eventData = {};
  9938. }
  9939. // create DOM event
  9940. var event = Hammer.DOCUMENT.createEvent('Event');
  9941. event.initEvent(gesture, true, true);
  9942. event.gesture = eventData;
  9943. // trigger on the target if it is in the instance element,
  9944. // this is for event delegation tricks
  9945. var element = this.element;
  9946. if(Utils.hasParent(eventData.target, element)) {
  9947. element = eventData.target;
  9948. }
  9949. element.dispatchEvent(event);
  9950. return this;
  9951. },
  9952. /**
  9953. * enable of disable hammer.js detection
  9954. * @method enable
  9955. * @chainable
  9956. * @param {Boolean} state
  9957. */
  9958. enable: function enable(state) {
  9959. this.enabled = state;
  9960. return this;
  9961. },
  9962. /**
  9963. * dispose this hammer instance
  9964. * @method dispose
  9965. * @return {Null}
  9966. */
  9967. dispose: function dispose() {
  9968. var i, eh;
  9969. // undo all changes made by stop_browser_behavior
  9970. Utils.toggleBehavior(this.element, this.options.behavior, false);
  9971. // unbind all custom event handlers
  9972. for(i = -1; (eh = this.eventHandlers[++i]);) {
  9973. Utils.off(this.element, eh.gesture, eh.handler);
  9974. }
  9975. this.eventHandlers = [];
  9976. // unbind the start event listener
  9977. Event.off(this.element, EVENT_TYPES[EVENT_START], this.eventStartHandler);
  9978. return null;
  9979. }
  9980. };
  9981. /**
  9982. * @module gestures
  9983. */
  9984. /**
  9985. * Move with x fingers (default 1) around on the page.
  9986. * Preventing the default browser behavior is a good way to improve feel and working.
  9987. * ````
  9988. * hammertime.on("drag", function(ev) {
  9989. * console.log(ev);
  9990. * ev.gesture.preventDefault();
  9991. * });
  9992. * ````
  9993. *
  9994. * @class Drag
  9995. * @static
  9996. */
  9997. /**
  9998. * @event drag
  9999. * @param {Object} ev
  10000. */
  10001. /**
  10002. * @event dragstart
  10003. * @param {Object} ev
  10004. */
  10005. /**
  10006. * @event dragend
  10007. * @param {Object} ev
  10008. */
  10009. /**
  10010. * @event drapleft
  10011. * @param {Object} ev
  10012. */
  10013. /**
  10014. * @event dragright
  10015. * @param {Object} ev
  10016. */
  10017. /**
  10018. * @event dragup
  10019. * @param {Object} ev
  10020. */
  10021. /**
  10022. * @event dragdown
  10023. * @param {Object} ev
  10024. */
  10025. /**
  10026. * @param {String} name
  10027. */
  10028. (function(name) {
  10029. var triggered = false;
  10030. function dragGesture(ev, inst) {
  10031. var cur = Detection.current;
  10032. // max touches
  10033. if(inst.options.dragMaxTouches > 0 &&
  10034. ev.touches.length > inst.options.dragMaxTouches) {
  10035. return;
  10036. }
  10037. switch(ev.eventType) {
  10038. case EVENT_START:
  10039. triggered = false;
  10040. break;
  10041. case EVENT_MOVE:
  10042. // when the distance we moved is too small we skip this gesture
  10043. // or we can be already in dragging
  10044. if(ev.distance < inst.options.dragMinDistance &&
  10045. cur.name != name) {
  10046. return;
  10047. }
  10048. var startCenter = cur.startEvent.center;
  10049. // we are dragging!
  10050. if(cur.name != name) {
  10051. cur.name = name;
  10052. if(inst.options.dragDistanceCorrection && ev.distance > 0) {
  10053. // When a drag is triggered, set the event center to dragMinDistance pixels from the original event center.
  10054. // Without this correction, the dragged distance would jumpstart at dragMinDistance pixels instead of at 0.
  10055. // It might be useful to save the original start point somewhere
  10056. var factor = Math.abs(inst.options.dragMinDistance / ev.distance);
  10057. startCenter.pageX += ev.deltaX * factor;
  10058. startCenter.pageY += ev.deltaY * factor;
  10059. startCenter.clientX += ev.deltaX * factor;
  10060. startCenter.clientY += ev.deltaY * factor;
  10061. // recalculate event data using new start point
  10062. ev = Detection.extendEventData(ev);
  10063. }
  10064. }
  10065. // lock drag to axis?
  10066. if(cur.lastEvent.dragLockToAxis ||
  10067. ( inst.options.dragLockToAxis &&
  10068. inst.options.dragLockMinDistance <= ev.distance
  10069. )) {
  10070. ev.dragLockToAxis = true;
  10071. }
  10072. // keep direction on the axis that the drag gesture started on
  10073. var lastDirection = cur.lastEvent.direction;
  10074. if(ev.dragLockToAxis && lastDirection !== ev.direction) {
  10075. if(Utils.isVertical(lastDirection)) {
  10076. ev.direction = (ev.deltaY < 0) ? DIRECTION_UP : DIRECTION_DOWN;
  10077. } else {
  10078. ev.direction = (ev.deltaX < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
  10079. }
  10080. }
  10081. // first time, trigger dragstart event
  10082. if(!triggered) {
  10083. inst.trigger(name + 'start', ev);
  10084. triggered = true;
  10085. }
  10086. // trigger events
  10087. inst.trigger(name, ev);
  10088. inst.trigger(name + ev.direction, ev);
  10089. var isVertical = Utils.isVertical(ev.direction);
  10090. // block the browser events
  10091. if((inst.options.dragBlockVertical && isVertical) ||
  10092. (inst.options.dragBlockHorizontal && !isVertical)) {
  10093. ev.preventDefault();
  10094. }
  10095. break;
  10096. case EVENT_RELEASE:
  10097. if(triggered && ev.changedLength <= inst.options.dragMaxTouches) {
  10098. inst.trigger(name + 'end', ev);
  10099. triggered = false;
  10100. }
  10101. break;
  10102. case EVENT_END:
  10103. triggered = false;
  10104. break;
  10105. }
  10106. }
  10107. Hammer.gestures.Drag = {
  10108. name: name,
  10109. index: 50,
  10110. handler: dragGesture,
  10111. defaults: {
  10112. /**
  10113. * minimal movement that have to be made before the drag event gets triggered
  10114. * @property dragMinDistance
  10115. * @type {Number}
  10116. * @default 10
  10117. */
  10118. dragMinDistance: 10,
  10119. /**
  10120. * Set dragDistanceCorrection to true to make the starting point of the drag
  10121. * be calculated from where the drag was triggered, not from where the touch started.
  10122. * Useful to avoid a jerk-starting drag, which can make fine-adjustments
  10123. * through dragging difficult, and be visually unappealing.
  10124. * @property dragDistanceCorrection
  10125. * @type {Boolean}
  10126. * @default true
  10127. */
  10128. dragDistanceCorrection: true,
  10129. /**
  10130. * set 0 for unlimited, but this can conflict with transform
  10131. * @property dragMaxTouches
  10132. * @type {Number}
  10133. * @default 1
  10134. */
  10135. dragMaxTouches: 1,
  10136. /**
  10137. * prevent default browser behavior when dragging occurs
  10138. * be careful with it, it makes the element a blocking element
  10139. * when you are using the drag gesture, it is a good practice to set this true
  10140. * @property dragBlockHorizontal
  10141. * @type {Boolean}
  10142. * @default false
  10143. */
  10144. dragBlockHorizontal: false,
  10145. /**
  10146. * same as `dragBlockHorizontal`, but for vertical movement
  10147. * @property dragBlockVertical
  10148. * @type {Boolean}
  10149. * @default false
  10150. */
  10151. dragBlockVertical: false,
  10152. /**
  10153. * dragLockToAxis keeps the drag gesture on the axis that it started on,
  10154. * It disallows vertical directions if the initial direction was horizontal, and vice versa.
  10155. * @property dragLockToAxis
  10156. * @type {Boolean}
  10157. * @default false
  10158. */
  10159. dragLockToAxis: false,
  10160. /**
  10161. * drag lock only kicks in when distance > dragLockMinDistance
  10162. * This way, locking occurs only when the distance has become large enough to reliably determine the direction
  10163. * @property dragLockMinDistance
  10164. * @type {Number}
  10165. * @default 25
  10166. */
  10167. dragLockMinDistance: 25
  10168. }
  10169. };
  10170. })('drag');
  10171. /**
  10172. * @module gestures
  10173. */
  10174. /**
  10175. * trigger a simple gesture event, so you can do anything in your handler.
  10176. * only usable if you know what your doing...
  10177. *
  10178. * @class Gesture
  10179. * @static
  10180. */
  10181. /**
  10182. * @event gesture
  10183. * @param {Object} ev
  10184. */
  10185. Hammer.gestures.Gesture = {
  10186. name: 'gesture',
  10187. index: 1337,
  10188. handler: function releaseGesture(ev, inst) {
  10189. inst.trigger(this.name, ev);
  10190. }
  10191. };
  10192. /**
  10193. * @module gestures
  10194. */
  10195. /**
  10196. * Touch stays at the same place for x time
  10197. *
  10198. * @class Hold
  10199. * @static
  10200. */
  10201. /**
  10202. * @event hold
  10203. * @param {Object} ev
  10204. */
  10205. /**
  10206. * @param {String} name
  10207. */
  10208. (function(name) {
  10209. var timer;
  10210. function holdGesture(ev, inst) {
  10211. var options = inst.options,
  10212. current = Detection.current;
  10213. switch(ev.eventType) {
  10214. case EVENT_START:
  10215. clearTimeout(timer);
  10216. // set the gesture so we can check in the timeout if it still is
  10217. current.name = name;
  10218. // set timer and if after the timeout it still is hold,
  10219. // we trigger the hold event
  10220. timer = setTimeout(function() {
  10221. if(current && current.name == name) {
  10222. inst.trigger(name, ev);
  10223. }
  10224. }, options.holdTimeout);
  10225. break;
  10226. case EVENT_MOVE:
  10227. if(ev.distance > options.holdThreshold) {
  10228. clearTimeout(timer);
  10229. }
  10230. break;
  10231. case EVENT_RELEASE:
  10232. clearTimeout(timer);
  10233. break;
  10234. }
  10235. }
  10236. Hammer.gestures.Hold = {
  10237. name: name,
  10238. index: 10,
  10239. defaults: {
  10240. /**
  10241. * @property holdTimeout
  10242. * @type {Number}
  10243. * @default 500
  10244. */
  10245. holdTimeout: 500,
  10246. /**
  10247. * movement allowed while holding
  10248. * @property holdThreshold
  10249. * @type {Number}
  10250. * @default 2
  10251. */
  10252. holdThreshold: 2
  10253. },
  10254. handler: holdGesture
  10255. };
  10256. })('hold');
  10257. /**
  10258. * @module gestures
  10259. */
  10260. /**
  10261. * when a touch is being released from the page
  10262. *
  10263. * @class Release
  10264. * @static
  10265. */
  10266. /**
  10267. * @event release
  10268. * @param {Object} ev
  10269. */
  10270. Hammer.gestures.Release = {
  10271. name: 'release',
  10272. index: Infinity,
  10273. handler: function releaseGesture(ev, inst) {
  10274. if(ev.eventType == EVENT_RELEASE) {
  10275. inst.trigger(this.name, ev);
  10276. }
  10277. }
  10278. };
  10279. /**
  10280. * @module gestures
  10281. */
  10282. /**
  10283. * triggers swipe events when the end velocity is above the threshold
  10284. * for best usage, set `preventDefault` (on the drag gesture) to `true`
  10285. * ````
  10286. * hammertime.on("dragleft swipeleft", function(ev) {
  10287. * console.log(ev);
  10288. * ev.gesture.preventDefault();
  10289. * });
  10290. * ````
  10291. *
  10292. * @class Swipe
  10293. * @static
  10294. */
  10295. /**
  10296. * @event swipe
  10297. * @param {Object} ev
  10298. */
  10299. /**
  10300. * @event swipeleft
  10301. * @param {Object} ev
  10302. */
  10303. /**
  10304. * @event swiperight
  10305. * @param {Object} ev
  10306. */
  10307. /**
  10308. * @event swipeup
  10309. * @param {Object} ev
  10310. */
  10311. /**
  10312. * @event swipedown
  10313. * @param {Object} ev
  10314. */
  10315. Hammer.gestures.Swipe = {
  10316. name: 'swipe',
  10317. index: 40,
  10318. defaults: {
  10319. /**
  10320. * @property swipeMinTouches
  10321. * @type {Number}
  10322. * @default 1
  10323. */
  10324. swipeMinTouches: 1,
  10325. /**
  10326. * @property swipeMaxTouches
  10327. * @type {Number}
  10328. * @default 1
  10329. */
  10330. swipeMaxTouches: 1,
  10331. /**
  10332. * horizontal swipe velocity
  10333. * @property swipeVelocityX
  10334. * @type {Number}
  10335. * @default 0.6
  10336. */
  10337. swipeVelocityX: 0.6,
  10338. /**
  10339. * vertical swipe velocity
  10340. * @property swipeVelocityY
  10341. * @type {Number}
  10342. * @default 0.6
  10343. */
  10344. swipeVelocityY: 0.6
  10345. },
  10346. handler: function swipeGesture(ev, inst) {
  10347. if(ev.eventType == EVENT_RELEASE) {
  10348. var touches = ev.touches.length,
  10349. options = inst.options;
  10350. // max touches
  10351. if(touches < options.swipeMinTouches ||
  10352. touches > options.swipeMaxTouches) {
  10353. return;
  10354. }
  10355. // when the distance we moved is too small we skip this gesture
  10356. // or we can be already in dragging
  10357. if(ev.velocityX > options.swipeVelocityX ||
  10358. ev.velocityY > options.swipeVelocityY) {
  10359. // trigger swipe events
  10360. inst.trigger(this.name, ev);
  10361. inst.trigger(this.name + ev.direction, ev);
  10362. }
  10363. }
  10364. }
  10365. };
  10366. /**
  10367. * @module gestures
  10368. */
  10369. /**
  10370. * Single tap and a double tap on a place
  10371. *
  10372. * @class Tap
  10373. * @static
  10374. */
  10375. /**
  10376. * @event tap
  10377. * @param {Object} ev
  10378. */
  10379. /**
  10380. * @event doubletap
  10381. * @param {Object} ev
  10382. */
  10383. /**
  10384. * @param {String} name
  10385. */
  10386. (function(name) {
  10387. var hasMoved = false;
  10388. function tapGesture(ev, inst) {
  10389. var options = inst.options,
  10390. current = Detection.current,
  10391. prev = Detection.previous,
  10392. sincePrev,
  10393. didDoubleTap;
  10394. switch(ev.eventType) {
  10395. case EVENT_START:
  10396. hasMoved = false;
  10397. break;
  10398. case EVENT_MOVE:
  10399. hasMoved = hasMoved || (ev.distance > options.tapMaxDistance);
  10400. break;
  10401. case EVENT_END:
  10402. if(!Utils.inStr(ev.srcEvent.type, 'cancel') && ev.deltaTime < options.tapMaxTime && !hasMoved) {
  10403. // previous gesture, for the double tap since these are two different gesture detections
  10404. sincePrev = prev && prev.lastEvent && ev.timeStamp - prev.lastEvent.timeStamp;
  10405. didDoubleTap = false;
  10406. // check if double tap
  10407. if(prev && prev.name == name &&
  10408. (sincePrev && sincePrev < options.doubleTapInterval) &&
  10409. ev.distance < options.doubleTapDistance) {
  10410. inst.trigger('doubletap', ev);
  10411. didDoubleTap = true;
  10412. }
  10413. // do a single tap
  10414. if(!didDoubleTap || options.tapAlways) {
  10415. current.name = name;
  10416. inst.trigger(current.name, ev);
  10417. }
  10418. }
  10419. break;
  10420. }
  10421. }
  10422. Hammer.gestures.Tap = {
  10423. name: name,
  10424. index: 100,
  10425. handler: tapGesture,
  10426. defaults: {
  10427. /**
  10428. * max time of a tap, this is for the slow tappers
  10429. * @property tapMaxTime
  10430. * @type {Number}
  10431. * @default 250
  10432. */
  10433. tapMaxTime: 250,
  10434. /**
  10435. * max distance of movement of a tap, this is for the slow tappers
  10436. * @property tapMaxDistance
  10437. * @type {Number}
  10438. * @default 10
  10439. */
  10440. tapMaxDistance: 10,
  10441. /**
  10442. * always trigger the `tap` event, even while double-tapping
  10443. * @property tapAlways
  10444. * @type {Boolean}
  10445. * @default true
  10446. */
  10447. tapAlways: true,
  10448. /**
  10449. * max distance between two taps
  10450. * @property doubleTapDistance
  10451. * @type {Number}
  10452. * @default 20
  10453. */
  10454. doubleTapDistance: 20,
  10455. /**
  10456. * max time between two taps
  10457. * @property doubleTapInterval
  10458. * @type {Number}
  10459. * @default 300
  10460. */
  10461. doubleTapInterval: 300
  10462. }
  10463. };
  10464. })('tap');
  10465. /**
  10466. * @module gestures
  10467. */
  10468. /**
  10469. * when a touch is being touched at the page
  10470. *
  10471. * @class Touch
  10472. * @static
  10473. */
  10474. /**
  10475. * @event touch
  10476. * @param {Object} ev
  10477. */
  10478. Hammer.gestures.Touch = {
  10479. name: 'touch',
  10480. index: -Infinity,
  10481. defaults: {
  10482. /**
  10483. * call preventDefault at touchstart, and makes the element blocking by disabling the scrolling of the page,
  10484. * but it improves gestures like transforming and dragging.
  10485. * be careful with using this, it can be very annoying for users to be stuck on the page
  10486. * @property preventDefault
  10487. * @type {Boolean}
  10488. * @default false
  10489. */
  10490. preventDefault: false,
  10491. /**
  10492. * disable mouse events, so only touch (or pen!) input triggers events
  10493. * @property preventMouse
  10494. * @type {Boolean}
  10495. * @default false
  10496. */
  10497. preventMouse: false
  10498. },
  10499. handler: function touchGesture(ev, inst) {
  10500. if(inst.options.preventMouse && ev.pointerType == POINTER_MOUSE) {
  10501. ev.stopDetect();
  10502. return;
  10503. }
  10504. if(inst.options.preventDefault) {
  10505. ev.preventDefault();
  10506. }
  10507. if(ev.eventType == EVENT_TOUCH) {
  10508. inst.trigger('touch', ev);
  10509. }
  10510. }
  10511. };
  10512. /**
  10513. * @module gestures
  10514. */
  10515. /**
  10516. * User want to scale or rotate with 2 fingers
  10517. * Preventing the default browser behavior is a good way to improve feel and working. This can be done with the
  10518. * `preventDefault` option.
  10519. *
  10520. * @class Transform
  10521. * @static
  10522. */
  10523. /**
  10524. * @event transform
  10525. * @param {Object} ev
  10526. */
  10527. /**
  10528. * @event transformstart
  10529. * @param {Object} ev
  10530. */
  10531. /**
  10532. * @event transformend
  10533. * @param {Object} ev
  10534. */
  10535. /**
  10536. * @event pinchin
  10537. * @param {Object} ev
  10538. */
  10539. /**
  10540. * @event pinchout
  10541. * @param {Object} ev
  10542. */
  10543. /**
  10544. * @event rotate
  10545. * @param {Object} ev
  10546. */
  10547. /**
  10548. * @param {String} name
  10549. */
  10550. (function(name) {
  10551. var triggered = false;
  10552. function transformGesture(ev, inst) {
  10553. switch(ev.eventType) {
  10554. case EVENT_START:
  10555. triggered = false;
  10556. break;
  10557. case EVENT_MOVE:
  10558. // at least multitouch
  10559. if(ev.touches.length < 2) {
  10560. return;
  10561. }
  10562. var scaleThreshold = Math.abs(1 - ev.scale);
  10563. var rotationThreshold = Math.abs(ev.rotation);
  10564. // when the distance we moved is too small we skip this gesture
  10565. // or we can be already in dragging
  10566. if(scaleThreshold < inst.options.transformMinScale &&
  10567. rotationThreshold < inst.options.transformMinRotation) {
  10568. return;
  10569. }
  10570. // we are transforming!
  10571. Detection.current.name = name;
  10572. // first time, trigger dragstart event
  10573. if(!triggered) {
  10574. inst.trigger(name + 'start', ev);
  10575. triggered = true;
  10576. }
  10577. inst.trigger(name, ev); // basic transform event
  10578. // trigger rotate event
  10579. if(rotationThreshold > inst.options.transformMinRotation) {
  10580. inst.trigger('rotate', ev);
  10581. }
  10582. // trigger pinch event
  10583. if(scaleThreshold > inst.options.transformMinScale) {
  10584. inst.trigger('pinch', ev);
  10585. inst.trigger('pinch' + (ev.scale < 1 ? 'in' : 'out'), ev);
  10586. }
  10587. break;
  10588. case EVENT_RELEASE:
  10589. if(triggered && ev.changedLength < 2) {
  10590. inst.trigger(name + 'end', ev);
  10591. triggered = false;
  10592. }
  10593. break;
  10594. }
  10595. }
  10596. Hammer.gestures.Transform = {
  10597. name: name,
  10598. index: 45,
  10599. defaults: {
  10600. /**
  10601. * minimal scale factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
  10602. * @property transformMinScale
  10603. * @type {Number}
  10604. * @default 0.01
  10605. */
  10606. transformMinScale: 0.01,
  10607. /**
  10608. * rotation in degrees
  10609. * @property transformMinRotation
  10610. * @type {Number}
  10611. * @default 1
  10612. */
  10613. transformMinRotation: 1
  10614. },
  10615. handler: transformGesture
  10616. };
  10617. })('transform');
  10618. /**
  10619. * @module hammer
  10620. */
  10621. // AMD export
  10622. if(true) {
  10623. !(__WEBPACK_AMD_DEFINE_RESULT__ = function() {
  10624. return Hammer;
  10625. }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  10626. // commonjs export
  10627. } else if(typeof module !== 'undefined' && module.exports) {
  10628. module.exports = Hammer;
  10629. // browser export
  10630. } else {
  10631. window.Hammer = Hammer;
  10632. }
  10633. })(window);
  10634. /***/ },
  10635. /* 21 */
  10636. /***/ function(module, exports, __webpack_require__) {
  10637. var util = __webpack_require__(1);
  10638. var hammerUtil = __webpack_require__(22);
  10639. var moment = __webpack_require__(2);
  10640. var Component = __webpack_require__(23);
  10641. var DateUtil = __webpack_require__(24);
  10642. /**
  10643. * @constructor Range
  10644. * A Range controls a numeric range with a start and end value.
  10645. * The Range adjusts the range based on mouse events or programmatic changes,
  10646. * and triggers events when the range is changing or has been changed.
  10647. * @param {{dom: Object, domProps: Object, emitter: Emitter}} body
  10648. * @param {Object} [options] See description at Range.setOptions
  10649. */
  10650. function Range(body, options) {
  10651. var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
  10652. this.start = now.clone().add(-3, 'days').valueOf(); // Number
  10653. this.end = now.clone().add(4, 'days').valueOf(); // Number
  10654. this.body = body;
  10655. this.deltaDifference = 0;
  10656. this.scaleOffset = 0;
  10657. this.startToFront = false;
  10658. this.endToFront = true;
  10659. // default options
  10660. this.defaultOptions = {
  10661. start: null,
  10662. end: null,
  10663. direction: 'horizontal', // 'horizontal' or 'vertical'
  10664. moveable: true,
  10665. zoomable: true,
  10666. min: null,
  10667. max: null,
  10668. zoomMin: 10, // milliseconds
  10669. zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000 // milliseconds
  10670. };
  10671. this.options = util.extend({}, this.defaultOptions);
  10672. this.props = {
  10673. touch: {}
  10674. };
  10675. this.animateTimer = null;
  10676. // drag listeners for dragging
  10677. this.body.emitter.on('dragstart', this._onDragStart.bind(this));
  10678. this.body.emitter.on('drag', this._onDrag.bind(this));
  10679. this.body.emitter.on('dragend', this._onDragEnd.bind(this));
  10680. // ignore dragging when holding
  10681. this.body.emitter.on('hold', this._onHold.bind(this));
  10682. // mouse wheel for zooming
  10683. this.body.emitter.on('mousewheel', this._onMouseWheel.bind(this));
  10684. this.body.emitter.on('DOMMouseScroll', this._onMouseWheel.bind(this)); // For FF
  10685. // pinch to zoom
  10686. this.body.emitter.on('touch', this._onTouch.bind(this));
  10687. this.body.emitter.on('pinch', this._onPinch.bind(this));
  10688. this.setOptions(options);
  10689. }
  10690. Range.prototype = new Component();
  10691. /**
  10692. * Set options for the range controller
  10693. * @param {Object} options Available options:
  10694. * {Number | Date | String} start Start date for the range
  10695. * {Number | Date | String} end End date for the range
  10696. * {Number} min Minimum value for start
  10697. * {Number} max Maximum value for end
  10698. * {Number} zoomMin Set a minimum value for
  10699. * (end - start).
  10700. * {Number} zoomMax Set a maximum value for
  10701. * (end - start).
  10702. * {Boolean} moveable Enable moving of the range
  10703. * by dragging. True by default
  10704. * {Boolean} zoomable Enable zooming of the range
  10705. * by pinching/scrolling. True by default
  10706. */
  10707. Range.prototype.setOptions = function (options) {
  10708. if (options) {
  10709. // copy the options that we know
  10710. var fields = ['direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable', 'activate', 'hiddenDates'];
  10711. util.selectiveExtend(fields, this.options, options);
  10712. if ('start' in options || 'end' in options) {
  10713. // apply a new range. both start and end are optional
  10714. this.setRange(options.start, options.end);
  10715. }
  10716. }
  10717. };
  10718. /**
  10719. * Test whether direction has a valid value
  10720. * @param {String} direction 'horizontal' or 'vertical'
  10721. */
  10722. function validateDirection (direction) {
  10723. if (direction != 'horizontal' && direction != 'vertical') {
  10724. throw new TypeError('Unknown direction "' + direction + '". ' +
  10725. 'Choose "horizontal" or "vertical".');
  10726. }
  10727. }
  10728. /**
  10729. * Set a new start and end range
  10730. * @param {Date | Number | String} [start]
  10731. * @param {Date | Number | String} [end]
  10732. * @param {boolean | number} [animate=false] If true, the range is animated
  10733. * smoothly to the new window.
  10734. * If animate is a number, the
  10735. * number is taken as duration
  10736. * Default duration is 500 ms.
  10737. * @param {Boolean} [byUser=false]
  10738. *
  10739. */
  10740. Range.prototype.setRange = function(start, end, animate, byUser) {
  10741. if (byUser !== true) {
  10742. byUser = false;
  10743. }
  10744. var _start = start != undefined ? util.convert(start, 'Date').valueOf() : null;
  10745. var _end = end != undefined ? util.convert(end, 'Date').valueOf() : null;
  10746. this._cancelAnimation();
  10747. if (animate) {
  10748. var me = this;
  10749. var initStart = this.start;
  10750. var initEnd = this.end;
  10751. var duration = typeof animate === 'number' ? animate : 500;
  10752. var initTime = new Date().valueOf();
  10753. var anyChanged = false;
  10754. var next = function () {
  10755. if (!me.props.touch.dragging) {
  10756. var now = new Date().valueOf();
  10757. var time = now - initTime;
  10758. var done = time > duration;
  10759. var s = (done || _start === null) ? _start : util.easeInOutQuad(time, initStart, _start, duration);
  10760. var e = (done || _end === null) ? _end : util.easeInOutQuad(time, initEnd, _end, duration);
  10761. changed = me._applyRange(s, e);
  10762. DateUtil.updateHiddenDates(me.body, me.options.hiddenDates);
  10763. anyChanged = anyChanged || changed;
  10764. if (changed) {
  10765. me.body.emitter.emit('rangechange', {start: new Date(me.start), end: new Date(me.end), byUser:byUser});
  10766. }
  10767. if (done) {
  10768. if (anyChanged) {
  10769. me.body.emitter.emit('rangechanged', {start: new Date(me.start), end: new Date(me.end), byUser:byUser});
  10770. }
  10771. }
  10772. else {
  10773. // animate with as high as possible frame rate, leave 20 ms in between
  10774. // each to prevent the browser from blocking
  10775. me.animateTimer = setTimeout(next, 20);
  10776. }
  10777. }
  10778. }
  10779. return next();
  10780. }
  10781. else {
  10782. var changed = this._applyRange(_start, _end);
  10783. DateUtil.updateHiddenDates(this.body, this.options.hiddenDates);
  10784. if (changed) {
  10785. var params = {start: new Date(this.start), end: new Date(this.end), byUser:byUser};
  10786. this.body.emitter.emit('rangechange', params);
  10787. this.body.emitter.emit('rangechanged', params);
  10788. }
  10789. }
  10790. };
  10791. /**
  10792. * Stop an animation
  10793. * @private
  10794. */
  10795. Range.prototype._cancelAnimation = function () {
  10796. if (this.animateTimer) {
  10797. clearTimeout(this.animateTimer);
  10798. this.animateTimer = null;
  10799. }
  10800. };
  10801. /**
  10802. * Set a new start and end range. This method is the same as setRange, but
  10803. * does not trigger a range change and range changed event, and it returns
  10804. * true when the range is changed
  10805. * @param {Number} [start]
  10806. * @param {Number} [end]
  10807. * @return {Boolean} changed
  10808. * @private
  10809. */
  10810. Range.prototype._applyRange = function(start, end) {
  10811. var newStart = (start != null) ? util.convert(start, 'Date').valueOf() : this.start,
  10812. newEnd = (end != null) ? util.convert(end, 'Date').valueOf() : this.end,
  10813. max = (this.options.max != null) ? util.convert(this.options.max, 'Date').valueOf() : null,
  10814. min = (this.options.min != null) ? util.convert(this.options.min, 'Date').valueOf() : null,
  10815. diff;
  10816. // check for valid number
  10817. if (isNaN(newStart) || newStart === null) {
  10818. throw new Error('Invalid start "' + start + '"');
  10819. }
  10820. if (isNaN(newEnd) || newEnd === null) {
  10821. throw new Error('Invalid end "' + end + '"');
  10822. }
  10823. // prevent start < end
  10824. if (newEnd < newStart) {
  10825. newEnd = newStart;
  10826. }
  10827. // prevent start < min
  10828. if (min !== null) {
  10829. if (newStart < min) {
  10830. diff = (min - newStart);
  10831. newStart += diff;
  10832. newEnd += diff;
  10833. // prevent end > max
  10834. if (max != null) {
  10835. if (newEnd > max) {
  10836. newEnd = max;
  10837. }
  10838. }
  10839. }
  10840. }
  10841. // prevent end > max
  10842. if (max !== null) {
  10843. if (newEnd > max) {
  10844. diff = (newEnd - max);
  10845. newStart -= diff;
  10846. newEnd -= diff;
  10847. // prevent start < min
  10848. if (min != null) {
  10849. if (newStart < min) {
  10850. newStart = min;
  10851. }
  10852. }
  10853. }
  10854. }
  10855. // prevent (end-start) < zoomMin
  10856. if (this.options.zoomMin !== null) {
  10857. var zoomMin = parseFloat(this.options.zoomMin);
  10858. if (zoomMin < 0) {
  10859. zoomMin = 0;
  10860. }
  10861. if ((newEnd - newStart) < zoomMin) {
  10862. if ((this.end - this.start) === zoomMin) {
  10863. // ignore this action, we are already zoomed to the minimum
  10864. newStart = this.start;
  10865. newEnd = this.end;
  10866. }
  10867. else {
  10868. // zoom to the minimum
  10869. diff = (zoomMin - (newEnd - newStart));
  10870. newStart -= diff / 2;
  10871. newEnd += diff / 2;
  10872. }
  10873. }
  10874. }
  10875. // prevent (end-start) > zoomMax
  10876. if (this.options.zoomMax !== null) {
  10877. var zoomMax = parseFloat(this.options.zoomMax);
  10878. if (zoomMax < 0) {
  10879. zoomMax = 0;
  10880. }
  10881. if ((newEnd - newStart) > zoomMax) {
  10882. if ((this.end - this.start) === zoomMax) {
  10883. // ignore this action, we are already zoomed to the maximum
  10884. newStart = this.start;
  10885. newEnd = this.end;
  10886. }
  10887. else {
  10888. // zoom to the maximum
  10889. diff = ((newEnd - newStart) - zoomMax);
  10890. newStart += diff / 2;
  10891. newEnd -= diff / 2;
  10892. }
  10893. }
  10894. }
  10895. var changed = (this.start != newStart || this.end != newEnd);
  10896. // 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 neccesarily of type Range)
  10897. if (!((newStart >= this.start && newStart <= this.end) || (newEnd >= this.start && newEnd <= this.end)) &&
  10898. !((this.start >= newStart && this.start <= newEnd) || (this.end >= newStart && this.end <= newEnd) )) {
  10899. this.body.emitter.emit('checkRangedItems');
  10900. }
  10901. this.start = newStart;
  10902. this.end = newEnd;
  10903. return changed;
  10904. };
  10905. /**
  10906. * Retrieve the current range.
  10907. * @return {Object} An object with start and end properties
  10908. */
  10909. Range.prototype.getRange = function() {
  10910. return {
  10911. start: this.start,
  10912. end: this.end
  10913. };
  10914. };
  10915. /**
  10916. * Calculate the conversion offset and scale for current range, based on
  10917. * the provided width
  10918. * @param {Number} width
  10919. * @returns {{offset: number, scale: number}} conversion
  10920. */
  10921. Range.prototype.conversion = function (width, totalHidden) {
  10922. return Range.conversion(this.start, this.end, width, totalHidden);
  10923. };
  10924. /**
  10925. * Static method to calculate the conversion offset and scale for a range,
  10926. * based on the provided start, end, and width
  10927. * @param {Number} start
  10928. * @param {Number} end
  10929. * @param {Number} width
  10930. * @returns {{offset: number, scale: number}} conversion
  10931. */
  10932. Range.conversion = function (start, end, width, totalHidden) {
  10933. if (totalHidden === undefined) {
  10934. totalHidden = 0;
  10935. }
  10936. if (width != 0 && (end - start != 0)) {
  10937. return {
  10938. offset: start,
  10939. scale: width / (end - start - totalHidden)
  10940. }
  10941. }
  10942. else {
  10943. return {
  10944. offset: 0,
  10945. scale: 1
  10946. };
  10947. }
  10948. };
  10949. /**
  10950. * Start dragging horizontally or vertically
  10951. * @param {Event} event
  10952. * @private
  10953. */
  10954. Range.prototype._onDragStart = function(event) {
  10955. this.deltaDifference = 0;
  10956. this.previousDelta = 0;
  10957. // only allow dragging when configured as movable
  10958. if (!this.options.moveable) return;
  10959. // refuse to drag when we where pinching to prevent the timeline make a jump
  10960. // when releasing the fingers in opposite order from the touch screen
  10961. if (!this.props.touch.allowDragging) return;
  10962. this.props.touch.start = this.start;
  10963. this.props.touch.end = this.end;
  10964. this.props.touch.dragging = true;
  10965. if (this.body.dom.root) {
  10966. this.body.dom.root.style.cursor = 'move';
  10967. }
  10968. };
  10969. /**
  10970. * Perform dragging operation
  10971. * @param {Event} event
  10972. * @private
  10973. */
  10974. Range.prototype._onDrag = function (event) {
  10975. // only allow dragging when configured as movable
  10976. if (!this.options.moveable) return;
  10977. // refuse to drag when we where pinching to prevent the timeline make a jump
  10978. // when releasing the fingers in opposite order from the touch screen
  10979. if (!this.props.touch.allowDragging) return;
  10980. var direction = this.options.direction;
  10981. validateDirection(direction);
  10982. var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY;
  10983. delta -= this.deltaDifference;
  10984. var interval = (this.props.touch.end - this.props.touch.start);
  10985. // normalize dragging speed if cutout is in between.
  10986. var duration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  10987. interval -= duration;
  10988. var width = (direction == 'horizontal') ? this.body.domProps.center.width : this.body.domProps.center.height;
  10989. var diffRange = -delta / width * interval;
  10990. var newStart = this.props.touch.start + diffRange;
  10991. var newEnd = this.props.touch.end + diffRange;
  10992. // snapping times away from hidden zones
  10993. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, this.previousDelta-delta, true);
  10994. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, this.previousDelta-delta, true);
  10995. if (safeStart != newStart || safeEnd != newEnd) {
  10996. this.deltaDifference += delta;
  10997. this.props.touch.start = safeStart;
  10998. this.props.touch.end = safeEnd;
  10999. this._onDrag(event);
  11000. return;
  11001. }
  11002. this.previousDelta = delta;
  11003. this._applyRange(newStart, newEnd);
  11004. // fire a rangechange event
  11005. this.body.emitter.emit('rangechange', {
  11006. start: new Date(this.start),
  11007. end: new Date(this.end),
  11008. byUser: true
  11009. });
  11010. };
  11011. /**
  11012. * Stop dragging operation
  11013. * @param {event} event
  11014. * @private
  11015. */
  11016. Range.prototype._onDragEnd = function (event) {
  11017. // only allow dragging when configured as movable
  11018. if (!this.options.moveable) return;
  11019. // refuse to drag when we where pinching to prevent the timeline make a jump
  11020. // when releasing the fingers in opposite order from the touch screen
  11021. if (!this.props.touch.allowDragging) return;
  11022. this.props.touch.dragging = false;
  11023. if (this.body.dom.root) {
  11024. this.body.dom.root.style.cursor = 'auto';
  11025. }
  11026. // fire a rangechanged event
  11027. this.body.emitter.emit('rangechanged', {
  11028. start: new Date(this.start),
  11029. end: new Date(this.end),
  11030. byUser: true
  11031. });
  11032. };
  11033. /**
  11034. * Event handler for mouse wheel event, used to zoom
  11035. * Code from http://adomas.org/javascript-mouse-wheel/
  11036. * @param {Event} event
  11037. * @private
  11038. */
  11039. Range.prototype._onMouseWheel = function(event) {
  11040. // only allow zooming when configured as zoomable and moveable
  11041. if (!(this.options.zoomable && this.options.moveable)) return;
  11042. // retrieve delta
  11043. var delta = 0;
  11044. if (event.wheelDelta) { /* IE/Opera. */
  11045. delta = event.wheelDelta / 120;
  11046. } else if (event.detail) { /* Mozilla case. */
  11047. // In Mozilla, sign of delta is different than in IE.
  11048. // Also, delta is multiple of 3.
  11049. delta = -event.detail / 3;
  11050. }
  11051. // If delta is nonzero, handle it.
  11052. // Basically, delta is now positive if wheel was scrolled up,
  11053. // and negative, if wheel was scrolled down.
  11054. if (delta) {
  11055. // perform the zoom action. Delta is normally 1 or -1
  11056. // adjust a negative delta such that zooming in with delta 0.1
  11057. // equals zooming out with a delta -0.1
  11058. var scale;
  11059. if (delta < 0) {
  11060. scale = 1 - (delta / 5);
  11061. }
  11062. else {
  11063. scale = 1 / (1 + (delta / 5)) ;
  11064. }
  11065. // calculate center, the date to zoom around
  11066. var gesture = hammerUtil.fakeGesture(this, event),
  11067. pointer = getPointer(gesture.center, this.body.dom.center),
  11068. pointerDate = this._pointerToDate(pointer);
  11069. this.zoom(scale, pointerDate, delta);
  11070. }
  11071. // Prevent default actions caused by mouse wheel
  11072. // (else the page and timeline both zoom and scroll)
  11073. event.preventDefault();
  11074. };
  11075. /**
  11076. * Start of a touch gesture
  11077. * @private
  11078. */
  11079. Range.prototype._onTouch = function (event) {
  11080. this.props.touch.start = this.start;
  11081. this.props.touch.end = this.end;
  11082. this.props.touch.allowDragging = true;
  11083. this.props.touch.center = null;
  11084. this.scaleOffset = 0;
  11085. this.deltaDifference = 0;
  11086. };
  11087. /**
  11088. * On start of a hold gesture
  11089. * @private
  11090. */
  11091. Range.prototype._onHold = function () {
  11092. this.props.touch.allowDragging = false;
  11093. };
  11094. /**
  11095. * Handle pinch event
  11096. * @param {Event} event
  11097. * @private
  11098. */
  11099. Range.prototype._onPinch = function (event) {
  11100. // only allow zooming when configured as zoomable and moveable
  11101. if (!(this.options.zoomable && this.options.moveable)) return;
  11102. this.props.touch.allowDragging = false;
  11103. if (event.gesture.touches.length > 1) {
  11104. if (!this.props.touch.center) {
  11105. this.props.touch.center = getPointer(event.gesture.center, this.body.dom.center);
  11106. }
  11107. var scale = 1 / (event.gesture.scale + this.scaleOffset);
  11108. var centerDate = this._pointerToDate(this.props.touch.center);
  11109. var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  11110. var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this, centerDate);
  11111. var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore;
  11112. // calculate new start and end
  11113. var newStart = (centerDate - hiddenDurationBefore) + (this.props.touch.start - (centerDate - hiddenDurationBefore)) * scale;
  11114. var newEnd = (centerDate + hiddenDurationAfter) + (this.props.touch.end - (centerDate + hiddenDurationAfter)) * scale;
  11115. // snapping times away from hidden zones
  11116. this.startToFront = 1 - scale > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  11117. this.endToFront = scale - 1 > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  11118. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, 1 - scale, true);
  11119. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, scale - 1, true);
  11120. if (safeStart != newStart || safeEnd != newEnd) {
  11121. this.props.touch.start = safeStart;
  11122. this.props.touch.end = safeEnd;
  11123. this.scaleOffset = 1 - event.gesture.scale;
  11124. newStart = safeStart;
  11125. newEnd = safeEnd;
  11126. }
  11127. this.setRange(newStart, newEnd, false, true);
  11128. this.startToFront = false; // revert to default
  11129. this.endToFront = true; // revert to default
  11130. }
  11131. };
  11132. /**
  11133. * Helper function to calculate the center date for zooming
  11134. * @param {{x: Number, y: Number}} pointer
  11135. * @return {number} date
  11136. * @private
  11137. */
  11138. Range.prototype._pointerToDate = function (pointer) {
  11139. var conversion;
  11140. var direction = this.options.direction;
  11141. validateDirection(direction);
  11142. if (direction == 'horizontal') {
  11143. return this.body.util.toTime(pointer.x).valueOf();
  11144. }
  11145. else {
  11146. var height = this.body.domProps.center.height;
  11147. conversion = this.conversion(height);
  11148. return pointer.y / conversion.scale + conversion.offset;
  11149. }
  11150. };
  11151. /**
  11152. * Get the pointer location relative to the location of the dom element
  11153. * @param {{pageX: Number, pageY: Number}} touch
  11154. * @param {Element} element HTML DOM element
  11155. * @return {{x: Number, y: Number}} pointer
  11156. * @private
  11157. */
  11158. function getPointer (touch, element) {
  11159. return {
  11160. x: touch.pageX - util.getAbsoluteLeft(element),
  11161. y: touch.pageY - util.getAbsoluteTop(element)
  11162. };
  11163. }
  11164. /**
  11165. * Zoom the range the given scale in or out. Start and end date will
  11166. * be adjusted, and the timeline will be redrawn. You can optionally give a
  11167. * date around which to zoom.
  11168. * For example, try scale = 0.9 or 1.1
  11169. * @param {Number} scale Scaling factor. Values above 1 will zoom out,
  11170. * values below 1 will zoom in.
  11171. * @param {Number} [center] Value representing a date around which will
  11172. * be zoomed.
  11173. */
  11174. Range.prototype.zoom = function(scale, center, delta) {
  11175. // if centerDate is not provided, take it half between start Date and end Date
  11176. if (center == null) {
  11177. center = (this.start + this.end) / 2;
  11178. }
  11179. var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  11180. var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this, center);
  11181. var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore;
  11182. // calculate new start and end
  11183. var newStart = (center-hiddenDurationBefore) + (this.start - (center-hiddenDurationBefore)) * scale;
  11184. var newEnd = (center+hiddenDurationAfter) + (this.end - (center+hiddenDurationAfter)) * scale;
  11185. // snapping times away from hidden zones
  11186. this.startToFront = delta > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  11187. this.endToFront = -delta > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  11188. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, delta, true);
  11189. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, -delta, true);
  11190. if (safeStart != newStart || safeEnd != newEnd) {
  11191. newStart = safeStart;
  11192. newEnd = safeEnd;
  11193. }
  11194. this.setRange(newStart, newEnd, false, true);
  11195. this.startToFront = false; // revert to default
  11196. this.endToFront = true; // revert to default
  11197. };
  11198. /**
  11199. * Move the range with a given delta to the left or right. Start and end
  11200. * value will be adjusted. For example, try delta = 0.1 or -0.1
  11201. * @param {Number} delta Moving amount. Positive value will move right,
  11202. * negative value will move left
  11203. */
  11204. Range.prototype.move = function(delta) {
  11205. // zoom start Date and end Date relative to the centerDate
  11206. var diff = (this.end - this.start);
  11207. // apply new values
  11208. var newStart = this.start + diff * delta;
  11209. var newEnd = this.end + diff * delta;
  11210. // TODO: reckon with min and max range
  11211. this.start = newStart;
  11212. this.end = newEnd;
  11213. };
  11214. /**
  11215. * Move the range to a new center point
  11216. * @param {Number} moveTo New center point of the range
  11217. */
  11218. Range.prototype.moveTo = function(moveTo) {
  11219. var center = (this.start + this.end) / 2;
  11220. var diff = center - moveTo;
  11221. // calculate new start and end
  11222. var newStart = this.start - diff;
  11223. var newEnd = this.end - diff;
  11224. this.setRange(newStart, newEnd);
  11225. };
  11226. module.exports = Range;
  11227. /***/ },
  11228. /* 22 */
  11229. /***/ function(module, exports, __webpack_require__) {
  11230. var Hammer = __webpack_require__(19);
  11231. /**
  11232. * Fake a hammer.js gesture. Event can be a ScrollEvent or MouseMoveEvent
  11233. * @param {Element} element
  11234. * @param {Event} event
  11235. */
  11236. exports.fakeGesture = function(element, event) {
  11237. var eventType = null;
  11238. // for hammer.js 1.0.5
  11239. // var gesture = Hammer.event.collectEventData(this, eventType, event);
  11240. // for hammer.js 1.0.6+
  11241. var touches = Hammer.event.getTouchList(event, eventType);
  11242. var gesture = Hammer.event.collectEventData(this, eventType, touches, event);
  11243. // on IE in standards mode, no touches are recognized by hammer.js,
  11244. // resulting in NaN values for center.pageX and center.pageY
  11245. if (isNaN(gesture.center.pageX)) {
  11246. gesture.center.pageX = event.pageX;
  11247. }
  11248. if (isNaN(gesture.center.pageY)) {
  11249. gesture.center.pageY = event.pageY;
  11250. }
  11251. return gesture;
  11252. };
  11253. /***/ },
  11254. /* 23 */
  11255. /***/ function(module, exports, __webpack_require__) {
  11256. /**
  11257. * Prototype for visual components
  11258. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} [body]
  11259. * @param {Object} [options]
  11260. */
  11261. function Component (body, options) {
  11262. this.options = null;
  11263. this.props = null;
  11264. }
  11265. /**
  11266. * Set options for the component. The new options will be merged into the
  11267. * current options.
  11268. * @param {Object} options
  11269. */
  11270. Component.prototype.setOptions = function(options) {
  11271. if (options) {
  11272. util.extend(this.options, options);
  11273. }
  11274. };
  11275. /**
  11276. * Repaint the component
  11277. * @return {boolean} Returns true if the component is resized
  11278. */
  11279. Component.prototype.redraw = function() {
  11280. // should be implemented by the component
  11281. return false;
  11282. };
  11283. /**
  11284. * Destroy the component. Cleanup DOM and event listeners
  11285. */
  11286. Component.prototype.destroy = function() {
  11287. // should be implemented by the component
  11288. };
  11289. /**
  11290. * Test whether the component is resized since the last time _isResized() was
  11291. * called.
  11292. * @return {Boolean} Returns true if the component is resized
  11293. * @protected
  11294. */
  11295. Component.prototype._isResized = function() {
  11296. var resized = (this.props._previousWidth !== this.props.width ||
  11297. this.props._previousHeight !== this.props.height);
  11298. this.props._previousWidth = this.props.width;
  11299. this.props._previousHeight = this.props.height;
  11300. return resized;
  11301. };
  11302. module.exports = Component;
  11303. /***/ },
  11304. /* 24 */
  11305. /***/ function(module, exports, __webpack_require__) {
  11306. /**
  11307. * Created by Alex on 10/3/2014.
  11308. */
  11309. var moment = __webpack_require__(2);
  11310. /**
  11311. * used in Core to convert the options into a volatile variable
  11312. *
  11313. * @param Core
  11314. */
  11315. exports.convertHiddenOptions = function(body, hiddenDates) {
  11316. body.hiddenDates = [];
  11317. if (hiddenDates) {
  11318. if (Array.isArray(hiddenDates) == true) {
  11319. for (var i = 0; i < hiddenDates.length; i++) {
  11320. if (hiddenDates[i].repeat === undefined) {
  11321. var dateItem = {};
  11322. dateItem.start = moment(hiddenDates[i].start).toDate().valueOf();
  11323. dateItem.end = moment(hiddenDates[i].end).toDate().valueOf();
  11324. body.hiddenDates.push(dateItem);
  11325. }
  11326. }
  11327. body.hiddenDates.sort(function (a, b) {
  11328. return a.start - b.start;
  11329. }); // sort by start time
  11330. }
  11331. }
  11332. };
  11333. /**
  11334. * create new entrees for the repeating hidden dates
  11335. * @param body
  11336. * @param hiddenDates
  11337. */
  11338. exports.updateHiddenDates = function (body, hiddenDates) {
  11339. if (hiddenDates && body.domProps.centerContainer.width !== undefined) {
  11340. exports.convertHiddenOptions(body, hiddenDates);
  11341. var start = moment(body.range.start);
  11342. var end = moment(body.range.end);
  11343. var totalRange = (body.range.end - body.range.start);
  11344. var pixelTime = totalRange / body.domProps.centerContainer.width;
  11345. for (var i = 0; i < hiddenDates.length; i++) {
  11346. if (hiddenDates[i].repeat !== undefined) {
  11347. var startDate = moment(hiddenDates[i].start);
  11348. var endDate = moment(hiddenDates[i].end);
  11349. if (startDate._d == "Invalid Date") {
  11350. throw new Error("Supplied start date is not valid: " + hiddenDates[i].start);
  11351. }
  11352. if (endDate._d == "Invalid Date") {
  11353. throw new Error("Supplied end date is not valid: " + hiddenDates[i].end);
  11354. }
  11355. var duration = endDate - startDate;
  11356. if (duration >= 4 * pixelTime) {
  11357. var offset = 0;
  11358. var runUntil = end.clone();
  11359. switch (hiddenDates[i].repeat) {
  11360. case "daily": // case of time
  11361. if (startDate.day() != endDate.day()) {
  11362. offset = 1;
  11363. }
  11364. startDate.dayOfYear(start.dayOfYear());
  11365. startDate.year(start.year());
  11366. startDate.subtract(7,'days');
  11367. endDate.dayOfYear(start.dayOfYear());
  11368. endDate.year(start.year());
  11369. endDate.subtract(7 - offset,'days');
  11370. runUntil.add(1, 'weeks');
  11371. break;
  11372. case "weekly":
  11373. var dayOffset = endDate.diff(startDate,'days')
  11374. var day = startDate.day();
  11375. // set the start date to the range.start
  11376. startDate.date(start.date());
  11377. startDate.month(start.month());
  11378. startDate.year(start.year());
  11379. endDate = startDate.clone();
  11380. // force
  11381. startDate.day(day);
  11382. endDate.day(day);
  11383. endDate.add(dayOffset,'days');
  11384. startDate.subtract(1,'weeks');
  11385. endDate.subtract(1,'weeks');
  11386. runUntil.add(1, 'weeks');
  11387. break
  11388. case "monthly":
  11389. if (startDate.month() != endDate.month()) {
  11390. offset = 1;
  11391. }
  11392. startDate.month(start.month());
  11393. startDate.year(start.year());
  11394. startDate.subtract(1,'months');
  11395. endDate.month(start.month());
  11396. endDate.year(start.year());
  11397. endDate.subtract(1,'months');
  11398. endDate.add(offset,'months');
  11399. runUntil.add(1, 'months');
  11400. break;
  11401. case "yearly":
  11402. if (startDate.year() != endDate.year()) {
  11403. offset = 1;
  11404. }
  11405. startDate.year(start.year());
  11406. startDate.subtract(1,'years');
  11407. endDate.year(start.year());
  11408. endDate.subtract(1,'years');
  11409. endDate.add(offset,'years');
  11410. runUntil.add(1, 'years');
  11411. break;
  11412. default:
  11413. console.log("Wrong repeat format, allowed are: daily, weekly, monthly, yearly. Given:", hiddenDates[i].repeat);
  11414. return;
  11415. }
  11416. while (startDate < runUntil) {
  11417. body.hiddenDates.push({start: startDate.valueOf(), end: endDate.valueOf()});
  11418. switch (hiddenDates[i].repeat) {
  11419. case "daily":
  11420. startDate.add(1, 'days');
  11421. endDate.add(1, 'days');
  11422. break;
  11423. case "weekly":
  11424. startDate.add(1, 'weeks');
  11425. endDate.add(1, 'weeks');
  11426. break
  11427. case "monthly":
  11428. startDate.add(1, 'months');
  11429. endDate.add(1, 'months');
  11430. break;
  11431. case "yearly":
  11432. startDate.add(1, 'y');
  11433. endDate.add(1, 'y');
  11434. break;
  11435. default:
  11436. console.log("Wrong repeat format, allowed are: daily, weekly, monthly, yearly. Given:", hiddenDates[i].repeat);
  11437. return;
  11438. }
  11439. }
  11440. body.hiddenDates.push({start: startDate.valueOf(), end: endDate.valueOf()});
  11441. }
  11442. }
  11443. }
  11444. // remove duplicates, merge where possible
  11445. exports.removeDuplicates(body);
  11446. // ensure the new positions are not on hidden dates
  11447. var startHidden = exports.isHidden(body.range.start, body.hiddenDates);
  11448. var endHidden = exports.isHidden(body.range.end,body.hiddenDates);
  11449. var rangeStart = body.range.start;
  11450. var rangeEnd = body.range.end;
  11451. if (startHidden.hidden == true) {rangeStart = body.range.startToFront == true ? startHidden.startDate - 1 : startHidden.endDate + 1;}
  11452. if (endHidden.hidden == true) {rangeEnd = body.range.endToFront == true ? endHidden.startDate - 1 : endHidden.endDate + 1;}
  11453. if (startHidden.hidden == true || endHidden.hidden == true) {
  11454. body.range._applyRange(rangeStart, rangeEnd);
  11455. }
  11456. }
  11457. }
  11458. /**
  11459. * remove duplicates from the hidden dates list. Duplicates are evil. They mess everything up.
  11460. * Scales with N^2
  11461. * @param body
  11462. */
  11463. exports.removeDuplicates = function(body) {
  11464. var hiddenDates = body.hiddenDates;
  11465. var safeDates = [];
  11466. for (var i = 0; i < hiddenDates.length; i++) {
  11467. for (var j = 0; j < hiddenDates.length; j++) {
  11468. if (i != j && hiddenDates[j].remove != true && hiddenDates[i].remove != true) {
  11469. // j inside i
  11470. if (hiddenDates[j].start >= hiddenDates[i].start && hiddenDates[j].end <= hiddenDates[i].end) {
  11471. hiddenDates[j].remove = true;
  11472. }
  11473. // j start inside i
  11474. else if (hiddenDates[j].start >= hiddenDates[i].start && hiddenDates[j].start <= hiddenDates[i].end) {
  11475. hiddenDates[i].end = hiddenDates[j].end;
  11476. hiddenDates[j].remove = true;
  11477. }
  11478. // j end inside i
  11479. else if (hiddenDates[j].end >= hiddenDates[i].start && hiddenDates[j].end <= hiddenDates[i].end) {
  11480. hiddenDates[i].start = hiddenDates[j].start;
  11481. hiddenDates[j].remove = true;
  11482. }
  11483. }
  11484. }
  11485. }
  11486. for (var i = 0; i < hiddenDates.length; i++) {
  11487. if (hiddenDates[i].remove !== true) {
  11488. safeDates.push(hiddenDates[i]);
  11489. }
  11490. }
  11491. body.hiddenDates = safeDates;
  11492. body.hiddenDates.sort(function (a, b) {
  11493. return a.start - b.start;
  11494. }); // sort by start time
  11495. }
  11496. exports.printDates = function(dates) {
  11497. for (var i =0; i < dates.length; i++) {
  11498. console.log(i, new Date(dates[i].start),new Date(dates[i].end), dates[i].start, dates[i].end, dates[i].remove);
  11499. }
  11500. }
  11501. /**
  11502. * Used in TimeStep to avoid the hidden times.
  11503. * @param timeStep
  11504. * @param previousTime
  11505. */
  11506. exports.stepOverHiddenDates = function(timeStep, previousTime) {
  11507. var stepInHidden = false;
  11508. var currentValue = timeStep.current.valueOf();
  11509. for (var i = 0; i < timeStep.hiddenDates.length; i++) {
  11510. var startDate = timeStep.hiddenDates[i].start;
  11511. var endDate = timeStep.hiddenDates[i].end;
  11512. if (currentValue >= startDate && currentValue < endDate) {
  11513. stepInHidden = true;
  11514. break;
  11515. }
  11516. }
  11517. if (stepInHidden == true && currentValue < timeStep._end.valueOf() && currentValue != previousTime) {
  11518. var prevValue = moment(previousTime);
  11519. var newValue = moment(endDate);
  11520. //check if the next step should be major
  11521. if (prevValue.year() != newValue.year()) {timeStep.switchedYear = true;}
  11522. else if (prevValue.month() != newValue.month()) {timeStep.switchedMonth = true;}
  11523. else if (prevValue.dayOfYear() != newValue.dayOfYear()) {timeStep.switchedDay = true;}
  11524. timeStep.current = newValue.toDate();
  11525. }
  11526. };
  11527. ///**
  11528. // * Used in TimeStep to avoid the hidden times.
  11529. // * @param timeStep
  11530. // * @param previousTime
  11531. // */
  11532. //exports.checkFirstStep = function(timeStep) {
  11533. // var stepInHidden = false;
  11534. // var currentValue = timeStep.current.valueOf();
  11535. // for (var i = 0; i < timeStep.hiddenDates.length; i++) {
  11536. // var startDate = timeStep.hiddenDates[i].start;
  11537. // var endDate = timeStep.hiddenDates[i].end;
  11538. // if (currentValue >= startDate && currentValue < endDate) {
  11539. // stepInHidden = true;
  11540. // break;
  11541. // }
  11542. // }
  11543. //
  11544. // if (stepInHidden == true && currentValue <= timeStep._end.valueOf()) {
  11545. // var newValue = moment(endDate);
  11546. // timeStep.current = newValue.toDate();
  11547. // }
  11548. //};
  11549. /**
  11550. * replaces the Core toScreen methods
  11551. * @param Core
  11552. * @param time
  11553. * @param width
  11554. * @returns {number}
  11555. */
  11556. exports.toScreen = function(Core, time, width) {
  11557. if (Core.body.hiddenDates.length == 0) {
  11558. var conversion = Core.range.conversion(width);
  11559. return (time.valueOf() - conversion.offset) * conversion.scale;
  11560. }
  11561. else {
  11562. var hidden = exports.isHidden(time, Core.body.hiddenDates)
  11563. if (hidden.hidden == true) {
  11564. time = hidden.startDate;
  11565. }
  11566. var duration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
  11567. time = exports.correctTimeForHidden(Core.body.hiddenDates, Core.range, time);
  11568. var conversion = Core.range.conversion(width, duration);
  11569. return (time.valueOf() - conversion.offset) * conversion.scale;
  11570. }
  11571. };
  11572. /**
  11573. * Replaces the core toTime methods
  11574. * @param body
  11575. * @param range
  11576. * @param x
  11577. * @param width
  11578. * @returns {Date}
  11579. */
  11580. exports.toTime = function(Core, x, width) {
  11581. if (Core.body.hiddenDates.length == 0) {
  11582. var conversion = Core.range.conversion(width);
  11583. return new Date(x / conversion.scale + conversion.offset);
  11584. }
  11585. else {
  11586. var hiddenDuration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
  11587. var totalDuration = Core.range.end - Core.range.start - hiddenDuration;
  11588. var partialDuration = totalDuration * x / width;
  11589. var accumulatedHiddenDuration = exports.getAccumulatedHiddenDuration(Core.body.hiddenDates, Core.range, partialDuration);
  11590. var newTime = new Date(accumulatedHiddenDuration + partialDuration + Core.range.start);
  11591. return newTime;
  11592. }
  11593. };
  11594. /**
  11595. * Support function
  11596. *
  11597. * @param hiddenDates
  11598. * @param range
  11599. * @returns {number}
  11600. */
  11601. exports.getHiddenDurationBetween = function(hiddenDates, start, end) {
  11602. var duration = 0;
  11603. for (var i = 0; i < hiddenDates.length; i++) {
  11604. var startDate = hiddenDates[i].start;
  11605. var endDate = hiddenDates[i].end;
  11606. // if time after the cutout, and the
  11607. if (startDate >= start && endDate < end) {
  11608. duration += endDate - startDate;
  11609. }
  11610. }
  11611. return duration;
  11612. };
  11613. /**
  11614. * Support function
  11615. * @param hiddenDates
  11616. * @param range
  11617. * @param time
  11618. * @returns {{duration: number, time: *, offset: number}}
  11619. */
  11620. exports.correctTimeForHidden = function(hiddenDates, range, time) {
  11621. time = moment(time).toDate().valueOf();
  11622. time -= exports.getHiddenDurationBefore(hiddenDates,range,time);
  11623. return time;
  11624. };
  11625. exports.getHiddenDurationBefore = function(hiddenDates, range, time) {
  11626. var timeOffset = 0;
  11627. time = moment(time).toDate().valueOf();
  11628. for (var i = 0; i < hiddenDates.length; i++) {
  11629. var startDate = hiddenDates[i].start;
  11630. var endDate = hiddenDates[i].end;
  11631. // if time after the cutout, and the
  11632. if (startDate >= range.start && endDate < range.end) {
  11633. if (time >= endDate) {
  11634. timeOffset += (endDate - startDate);
  11635. }
  11636. }
  11637. }
  11638. return timeOffset;
  11639. }
  11640. /**
  11641. * sum the duration from start to finish, including the hidden duration,
  11642. * until the required amount has been reached, return the accumulated hidden duration
  11643. * @param hiddenDates
  11644. * @param range
  11645. * @param time
  11646. * @returns {{duration: number, time: *, offset: number}}
  11647. */
  11648. exports.getAccumulatedHiddenDuration = function(hiddenDates, range, requiredDuration) {
  11649. var hiddenDuration = 0;
  11650. var duration = 0;
  11651. var previousPoint = range.start;
  11652. //exports.printDates(hiddenDates)
  11653. for (var i = 0; i < hiddenDates.length; i++) {
  11654. var startDate = hiddenDates[i].start;
  11655. var endDate = hiddenDates[i].end;
  11656. // if time after the cutout, and the
  11657. if (startDate >= range.start && endDate < range.end) {
  11658. duration += startDate - previousPoint;
  11659. previousPoint = endDate;
  11660. if (duration >= requiredDuration) {
  11661. break;
  11662. }
  11663. else {
  11664. hiddenDuration += endDate - startDate;
  11665. }
  11666. }
  11667. }
  11668. return hiddenDuration;
  11669. };
  11670. /**
  11671. * used to step over to either side of a hidden block. Correction is disabled on tablets, might be set to true
  11672. * @param hiddenDates
  11673. * @param time
  11674. * @param direction
  11675. * @param correctionEnabled
  11676. * @returns {*}
  11677. */
  11678. exports.snapAwayFromHidden = function(hiddenDates, time, direction, correctionEnabled) {
  11679. var isHidden = exports.isHidden(time, hiddenDates);
  11680. if (isHidden.hidden == true) {
  11681. if (direction < 0) {
  11682. if (correctionEnabled == true) {
  11683. return isHidden.startDate - (isHidden.endDate - time) - 1;
  11684. }
  11685. else {
  11686. return isHidden.startDate - 1;
  11687. }
  11688. }
  11689. else {
  11690. if (correctionEnabled == true) {
  11691. return isHidden.endDate + (time - isHidden.startDate) + 1;
  11692. }
  11693. else {
  11694. return isHidden.endDate + 1;
  11695. }
  11696. }
  11697. }
  11698. else {
  11699. return time;
  11700. }
  11701. }
  11702. /**
  11703. * Check if a time is hidden
  11704. *
  11705. * @param time
  11706. * @param hiddenDates
  11707. * @returns {{hidden: boolean, startDate: Window.start, endDate: *}}
  11708. */
  11709. exports.isHidden = function(time, hiddenDates) {
  11710. for (var i = 0; i < hiddenDates.length; i++) {
  11711. var startDate = hiddenDates[i].start;
  11712. var endDate = hiddenDates[i].end;
  11713. if (time >= startDate && time < endDate) { // if the start is entering a hidden zone
  11714. return {hidden: true, startDate: startDate, endDate: endDate};
  11715. break;
  11716. }
  11717. }
  11718. return {hidden: false, startDate: startDate, endDate: endDate};
  11719. }
  11720. /***/ },
  11721. /* 25 */
  11722. /***/ function(module, exports, __webpack_require__) {
  11723. var Emitter = __webpack_require__(11);
  11724. var Hammer = __webpack_require__(19);
  11725. var util = __webpack_require__(1);
  11726. var DataSet = __webpack_require__(7);
  11727. var DataView = __webpack_require__(9);
  11728. var Range = __webpack_require__(21);
  11729. var ItemSet = __webpack_require__(26);
  11730. var Activator = __webpack_require__(35);
  11731. var DateUtil = __webpack_require__(24);
  11732. /**
  11733. * Create a timeline visualization
  11734. * @param {HTMLElement} container
  11735. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  11736. * @param {Object} [options] See Core.setOptions for the available options.
  11737. * @constructor
  11738. */
  11739. function Core () {}
  11740. // turn Core into an event emitter
  11741. Emitter(Core.prototype);
  11742. /**
  11743. * Create the main DOM for the Core: a root panel containing left, right,
  11744. * top, bottom, content, and background panel.
  11745. * @param {Element} container The container element where the Core will
  11746. * be attached.
  11747. * @private
  11748. */
  11749. Core.prototype._create = function (container) {
  11750. this.dom = {};
  11751. this.dom.root = document.createElement('div');
  11752. this.dom.background = document.createElement('div');
  11753. this.dom.backgroundVertical = document.createElement('div');
  11754. this.dom.backgroundHorizontal = document.createElement('div');
  11755. this.dom.centerContainer = document.createElement('div');
  11756. this.dom.leftContainer = document.createElement('div');
  11757. this.dom.rightContainer = document.createElement('div');
  11758. this.dom.center = document.createElement('div');
  11759. this.dom.left = document.createElement('div');
  11760. this.dom.right = document.createElement('div');
  11761. this.dom.top = document.createElement('div');
  11762. this.dom.bottom = document.createElement('div');
  11763. this.dom.shadowTop = document.createElement('div');
  11764. this.dom.shadowBottom = document.createElement('div');
  11765. this.dom.shadowTopLeft = document.createElement('div');
  11766. this.dom.shadowBottomLeft = document.createElement('div');
  11767. this.dom.shadowTopRight = document.createElement('div');
  11768. this.dom.shadowBottomRight = document.createElement('div');
  11769. this.dom.root.className = 'vis timeline root';
  11770. this.dom.background.className = 'vispanel background';
  11771. this.dom.backgroundVertical.className = 'vispanel background vertical';
  11772. this.dom.backgroundHorizontal.className = 'vispanel background horizontal';
  11773. this.dom.centerContainer.className = 'vispanel center';
  11774. this.dom.leftContainer.className = 'vispanel left';
  11775. this.dom.rightContainer.className = 'vispanel right';
  11776. this.dom.top.className = 'vispanel top';
  11777. this.dom.bottom.className = 'vispanel bottom';
  11778. this.dom.left.className = 'content';
  11779. this.dom.center.className = 'content';
  11780. this.dom.right.className = 'content';
  11781. this.dom.shadowTop.className = 'shadow top';
  11782. this.dom.shadowBottom.className = 'shadow bottom';
  11783. this.dom.shadowTopLeft.className = 'shadow top';
  11784. this.dom.shadowBottomLeft.className = 'shadow bottom';
  11785. this.dom.shadowTopRight.className = 'shadow top';
  11786. this.dom.shadowBottomRight.className = 'shadow bottom';
  11787. this.dom.root.appendChild(this.dom.background);
  11788. this.dom.root.appendChild(this.dom.backgroundVertical);
  11789. this.dom.root.appendChild(this.dom.backgroundHorizontal);
  11790. this.dom.root.appendChild(this.dom.centerContainer);
  11791. this.dom.root.appendChild(this.dom.leftContainer);
  11792. this.dom.root.appendChild(this.dom.rightContainer);
  11793. this.dom.root.appendChild(this.dom.top);
  11794. this.dom.root.appendChild(this.dom.bottom);
  11795. this.dom.centerContainer.appendChild(this.dom.center);
  11796. this.dom.leftContainer.appendChild(this.dom.left);
  11797. this.dom.rightContainer.appendChild(this.dom.right);
  11798. this.dom.centerContainer.appendChild(this.dom.shadowTop);
  11799. this.dom.centerContainer.appendChild(this.dom.shadowBottom);
  11800. this.dom.leftContainer.appendChild(this.dom.shadowTopLeft);
  11801. this.dom.leftContainer.appendChild(this.dom.shadowBottomLeft);
  11802. this.dom.rightContainer.appendChild(this.dom.shadowTopRight);
  11803. this.dom.rightContainer.appendChild(this.dom.shadowBottomRight);
  11804. this.on('rangechange', this.redraw.bind(this));
  11805. this.on('touch', this._onTouch.bind(this));
  11806. this.on('pinch', this._onPinch.bind(this));
  11807. this.on('dragstart', this._onDragStart.bind(this));
  11808. this.on('drag', this._onDrag.bind(this));
  11809. var me = this;
  11810. this.on('change', function (properties) {
  11811. if (properties && properties.queue == true) {
  11812. // redraw once on next tick
  11813. if (!me._redrawTimer) {
  11814. me._redrawTimer = setTimeout(function () {
  11815. me._redrawTimer = null;
  11816. me.redraw();
  11817. }, 0)
  11818. }
  11819. }
  11820. else {
  11821. // redraw immediately
  11822. me.redraw();
  11823. }
  11824. });
  11825. // create event listeners for all interesting events, these events will be
  11826. // emitted via emitter
  11827. this.hammer = Hammer(this.dom.root, {
  11828. preventDefault: true
  11829. });
  11830. this.listeners = {};
  11831. var events = [
  11832. 'touch', 'pinch',
  11833. 'tap', 'doubletap', 'hold',
  11834. 'dragstart', 'drag', 'dragend',
  11835. 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
  11836. ];
  11837. events.forEach(function (event) {
  11838. var listener = function () {
  11839. var args = [event].concat(Array.prototype.slice.call(arguments, 0));
  11840. if (me.isActive()) {
  11841. me.emit.apply(me, args);
  11842. }
  11843. };
  11844. me.hammer.on(event, listener);
  11845. me.listeners[event] = listener;
  11846. });
  11847. // size properties of each of the panels
  11848. this.props = {
  11849. root: {},
  11850. background: {},
  11851. centerContainer: {},
  11852. leftContainer: {},
  11853. rightContainer: {},
  11854. center: {},
  11855. left: {},
  11856. right: {},
  11857. top: {},
  11858. bottom: {},
  11859. border: {},
  11860. scrollTop: 0,
  11861. scrollTopMin: 0
  11862. };
  11863. this.touch = {}; // store state information needed for touch events
  11864. this.redrawCount = 0;
  11865. // attach the root panel to the provided container
  11866. if (!container) throw new Error('No container provided');
  11867. container.appendChild(this.dom.root);
  11868. };
  11869. /**
  11870. * Set options. Options will be passed to all components loaded in the Timeline.
  11871. * @param {Object} [options]
  11872. * {String} orientation
  11873. * Vertical orientation for the Timeline,
  11874. * can be 'bottom' (default) or 'top'.
  11875. * {String | Number} width
  11876. * Width for the timeline, a number in pixels or
  11877. * a css string like '1000px' or '75%'. '100%' by default.
  11878. * {String | Number} height
  11879. * Fixed height for the Timeline, a number in pixels or
  11880. * a css string like '400px' or '75%'. If undefined,
  11881. * The Timeline will automatically size such that
  11882. * its contents fit.
  11883. * {String | Number} minHeight
  11884. * Minimum height for the Timeline, a number in pixels or
  11885. * a css string like '400px' or '75%'.
  11886. * {String | Number} maxHeight
  11887. * Maximum height for the Timeline, a number in pixels or
  11888. * a css string like '400px' or '75%'.
  11889. * {Number | Date | String} start
  11890. * Start date for the visible window
  11891. * {Number | Date | String} end
  11892. * End date for the visible window
  11893. */
  11894. Core.prototype.setOptions = function (options) {
  11895. if (options) {
  11896. // copy the known options
  11897. var fields = ['width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'start', 'end', 'orientation', 'clickToUse', 'dataAttributes', 'hiddenDates'];
  11898. util.selectiveExtend(fields, this.options, options);
  11899. if ('hiddenDates' in this.options) {
  11900. DateUtil.convertHiddenOptions(this.body, this.options.hiddenDates);
  11901. }
  11902. if ('clickToUse' in options) {
  11903. if (options.clickToUse) {
  11904. if (!this.activator) {
  11905. this.activator = new Activator(this.dom.root);
  11906. }
  11907. }
  11908. else {
  11909. if (this.activator) {
  11910. this.activator.destroy();
  11911. delete this.activator;
  11912. }
  11913. }
  11914. }
  11915. // enable/disable autoResize
  11916. this._initAutoResize();
  11917. }
  11918. // propagate options to all components
  11919. this.components.forEach(function (component) {
  11920. component.setOptions(options);
  11921. });
  11922. // TODO: remove deprecation error one day (deprecated since version 0.8.0)
  11923. if (options && options.order) {
  11924. throw new Error('Option order is deprecated. There is no replacement for this feature.');
  11925. }
  11926. // redraw everything
  11927. this.redraw();
  11928. };
  11929. /**
  11930. * Returns true when the Timeline is active.
  11931. * @returns {boolean}
  11932. */
  11933. Core.prototype.isActive = function () {
  11934. return !this.activator || this.activator.active;
  11935. };
  11936. /**
  11937. * Destroy the Core, clean up all DOM elements and event listeners.
  11938. */
  11939. Core.prototype.destroy = function () {
  11940. // unbind datasets
  11941. this.clear();
  11942. // remove all event listeners
  11943. this.off();
  11944. // stop checking for changed size
  11945. this._stopAutoResize();
  11946. // remove from DOM
  11947. if (this.dom.root.parentNode) {
  11948. this.dom.root.parentNode.removeChild(this.dom.root);
  11949. }
  11950. this.dom = null;
  11951. // remove Activator
  11952. if (this.activator) {
  11953. this.activator.destroy();
  11954. delete this.activator;
  11955. }
  11956. // cleanup hammer touch events
  11957. for (var event in this.listeners) {
  11958. if (this.listeners.hasOwnProperty(event)) {
  11959. delete this.listeners[event];
  11960. }
  11961. }
  11962. this.listeners = null;
  11963. this.hammer = null;
  11964. // give all components the opportunity to cleanup
  11965. this.components.forEach(function (component) {
  11966. component.destroy();
  11967. });
  11968. this.body = null;
  11969. };
  11970. /**
  11971. * Set a custom time bar
  11972. * @param {Date} time
  11973. */
  11974. Core.prototype.setCustomTime = function (time) {
  11975. if (!this.customTime) {
  11976. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  11977. }
  11978. this.customTime.setCustomTime(time);
  11979. };
  11980. /**
  11981. * Retrieve the current custom time.
  11982. * @return {Date} customTime
  11983. */
  11984. Core.prototype.getCustomTime = function() {
  11985. if (!this.customTime) {
  11986. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  11987. }
  11988. return this.customTime.getCustomTime();
  11989. };
  11990. /**
  11991. * Get the id's of the currently visible items.
  11992. * @returns {Array} The ids of the visible items
  11993. */
  11994. Core.prototype.getVisibleItems = function() {
  11995. return this.itemSet && this.itemSet.getVisibleItems() || [];
  11996. };
  11997. /**
  11998. * Clear the Core. By Default, items, groups and options are cleared.
  11999. * Example usage:
  12000. *
  12001. * timeline.clear(); // clear items, groups, and options
  12002. * timeline.clear({options: true}); // clear options only
  12003. *
  12004. * @param {Object} [what] Optionally specify what to clear. By default:
  12005. * {items: true, groups: true, options: true}
  12006. */
  12007. Core.prototype.clear = function(what) {
  12008. // clear items
  12009. if (!what || what.items) {
  12010. this.setItems(null);
  12011. }
  12012. // clear groups
  12013. if (!what || what.groups) {
  12014. this.setGroups(null);
  12015. }
  12016. // clear options of timeline and of each of the components
  12017. if (!what || what.options) {
  12018. this.components.forEach(function (component) {
  12019. component.setOptions(component.defaultOptions);
  12020. });
  12021. this.setOptions(this.defaultOptions); // this will also do a redraw
  12022. }
  12023. };
  12024. /**
  12025. * Set Core window such that it fits all items
  12026. * @param {Object} [options] Available options:
  12027. * `animate: boolean | number`
  12028. * If true (default), the range is animated
  12029. * smoothly to the new window.
  12030. * If a number, the number is taken as duration
  12031. * for the animation. Default duration is 500 ms.
  12032. */
  12033. Core.prototype.fit = function(options) {
  12034. var range = this._getDataRange();
  12035. // skip range set if there is no start and end date
  12036. if (range.start === null && range.end === null) {
  12037. return;
  12038. }
  12039. var animate = (options && options.animate !== undefined) ? options.animate : true;
  12040. this.range.setRange(range.start, range.end, animate);
  12041. };
  12042. /**
  12043. * Calculate the data range of the items and applies a 5% window around it.
  12044. * @returns {{start: Date | null, end: Date | null}}
  12045. * @protected
  12046. */
  12047. Core.prototype._getDataRange = function() {
  12048. // apply the data range as range
  12049. var dataRange = this.getItemRange();
  12050. // add 5% space on both sides
  12051. var start = dataRange.min;
  12052. var end = dataRange.max;
  12053. if (start != null && end != null) {
  12054. var interval = (end.valueOf() - start.valueOf());
  12055. if (interval <= 0) {
  12056. // prevent an empty interval
  12057. interval = 24 * 60 * 60 * 1000; // 1 day
  12058. }
  12059. start = new Date(start.valueOf() - interval * 0.05);
  12060. end = new Date(end.valueOf() + interval * 0.05);
  12061. }
  12062. return {
  12063. start: start,
  12064. end: end
  12065. }
  12066. };
  12067. /**
  12068. * Set the visible window. Both parameters are optional, you can change only
  12069. * start or only end. Syntax:
  12070. *
  12071. * TimeLine.setWindow(start, end)
  12072. * TimeLine.setWindow(range)
  12073. *
  12074. * Where start and end can be a Date, number, or string, and range is an
  12075. * object with properties start and end.
  12076. *
  12077. * @param {Date | Number | String | Object} [start] Start date of visible window
  12078. * @param {Date | Number | String} [end] End date of visible window
  12079. * @param {Object} [options] Available options:
  12080. * `animate: boolean | number`
  12081. * If true (default), the range is animated
  12082. * smoothly to the new window.
  12083. * If a number, the number is taken as duration
  12084. * for the animation. Default duration is 500 ms.
  12085. */
  12086. Core.prototype.setWindow = function(start, end, options) {
  12087. var animate = (options && options.animate !== undefined) ? options.animate : true;
  12088. if (arguments.length == 1) {
  12089. var range = arguments[0];
  12090. this.range.setRange(range.start, range.end, animate);
  12091. }
  12092. else {
  12093. this.range.setRange(start, end, animate);
  12094. }
  12095. };
  12096. /**
  12097. * Move the window such that given time is centered on screen.
  12098. * @param {Date | Number | String} time
  12099. * @param {Object} [options] Available options:
  12100. * `animate: boolean | number`
  12101. * If true (default), the range is animated
  12102. * smoothly to the new window.
  12103. * If a number, the number is taken as duration
  12104. * for the animation. Default duration is 500 ms.
  12105. */
  12106. Core.prototype.moveTo = function(time, options) {
  12107. var interval = this.range.end - this.range.start;
  12108. var t = util.convert(time, 'Date').valueOf();
  12109. var start = t - interval / 2;
  12110. var end = t + interval / 2;
  12111. var animate = (options && options.animate !== undefined) ? options.animate : true;
  12112. this.range.setRange(start, end, animate);
  12113. };
  12114. /**
  12115. * Get the visible window
  12116. * @return {{start: Date, end: Date}} Visible range
  12117. */
  12118. Core.prototype.getWindow = function() {
  12119. var range = this.range.getRange();
  12120. return {
  12121. start: new Date(range.start),
  12122. end: new Date(range.end)
  12123. };
  12124. };
  12125. /**
  12126. * Force a redraw of the Core. Can be useful to manually redraw when
  12127. * option autoResize=false
  12128. */
  12129. Core.prototype.redraw = function() {
  12130. var resized = false;
  12131. var options = this.options;
  12132. var props = this.props;
  12133. var dom = this.dom;
  12134. if (!dom) return; // when destroyed
  12135. DateUtil.updateHiddenDates(this.body, this.options.hiddenDates);
  12136. // update class names
  12137. if (options.orientation == 'top') {
  12138. util.addClassName(dom.root, 'top');
  12139. util.removeClassName(dom.root, 'bottom');
  12140. }
  12141. else {
  12142. util.removeClassName(dom.root, 'top');
  12143. util.addClassName(dom.root, 'bottom');
  12144. }
  12145. // update root width and height options
  12146. dom.root.style.maxHeight = util.option.asSize(options.maxHeight, '');
  12147. dom.root.style.minHeight = util.option.asSize(options.minHeight, '');
  12148. dom.root.style.width = util.option.asSize(options.width, '');
  12149. // calculate border widths
  12150. props.border.left = (dom.centerContainer.offsetWidth - dom.centerContainer.clientWidth) / 2;
  12151. props.border.right = props.border.left;
  12152. props.border.top = (dom.centerContainer.offsetHeight - dom.centerContainer.clientHeight) / 2;
  12153. props.border.bottom = props.border.top;
  12154. var borderRootHeight= dom.root.offsetHeight - dom.root.clientHeight;
  12155. var borderRootWidth = dom.root.offsetWidth - dom.root.clientWidth;
  12156. // workaround for a bug in IE: the clientWidth of an element with
  12157. // a height:0px and overflow:hidden is not calculated and always has value 0
  12158. if (dom.centerContainer.clientHeight === 0) {
  12159. props.border.left = props.border.top;
  12160. props.border.right = props.border.left;
  12161. }
  12162. if (dom.root.clientHeight === 0) {
  12163. borderRootWidth = borderRootHeight;
  12164. }
  12165. // calculate the heights. If any of the side panels is empty, we set the height to
  12166. // minus the border width, such that the border will be invisible
  12167. props.center.height = dom.center.offsetHeight;
  12168. props.left.height = dom.left.offsetHeight;
  12169. props.right.height = dom.right.offsetHeight;
  12170. props.top.height = dom.top.clientHeight || -props.border.top;
  12171. props.bottom.height = dom.bottom.clientHeight || -props.border.bottom;
  12172. // TODO: compensate borders when any of the panels is empty.
  12173. // apply auto height
  12174. // TODO: only calculate autoHeight when needed (else we cause an extra reflow/repaint of the DOM)
  12175. var contentHeight = Math.max(props.left.height, props.center.height, props.right.height);
  12176. var autoHeight = props.top.height + contentHeight + props.bottom.height +
  12177. borderRootHeight + props.border.top + props.border.bottom;
  12178. dom.root.style.height = util.option.asSize(options.height, autoHeight + 'px');
  12179. // calculate heights of the content panels
  12180. props.root.height = dom.root.offsetHeight;
  12181. props.background.height = props.root.height - borderRootHeight;
  12182. var containerHeight = props.root.height - props.top.height - props.bottom.height -
  12183. borderRootHeight;
  12184. props.centerContainer.height = containerHeight;
  12185. props.leftContainer.height = containerHeight;
  12186. props.rightContainer.height = props.leftContainer.height;
  12187. // calculate the widths of the panels
  12188. props.root.width = dom.root.offsetWidth;
  12189. props.background.width = props.root.width - borderRootWidth;
  12190. props.left.width = dom.leftContainer.clientWidth || -props.border.left;
  12191. props.leftContainer.width = props.left.width;
  12192. props.right.width = dom.rightContainer.clientWidth || -props.border.right;
  12193. props.rightContainer.width = props.right.width;
  12194. var centerWidth = props.root.width - props.left.width - props.right.width - borderRootWidth;
  12195. props.center.width = centerWidth;
  12196. props.centerContainer.width = centerWidth;
  12197. props.top.width = centerWidth;
  12198. props.bottom.width = centerWidth;
  12199. // resize the panels
  12200. dom.background.style.height = props.background.height + 'px';
  12201. dom.backgroundVertical.style.height = props.background.height + 'px';
  12202. dom.backgroundHorizontal.style.height = props.centerContainer.height + 'px';
  12203. dom.centerContainer.style.height = props.centerContainer.height + 'px';
  12204. dom.leftContainer.style.height = props.leftContainer.height + 'px';
  12205. dom.rightContainer.style.height = props.rightContainer.height + 'px';
  12206. dom.background.style.width = props.background.width + 'px';
  12207. dom.backgroundVertical.style.width = props.centerContainer.width + 'px';
  12208. dom.backgroundHorizontal.style.width = props.background.width + 'px';
  12209. dom.centerContainer.style.width = props.center.width + 'px';
  12210. dom.top.style.width = props.top.width + 'px';
  12211. dom.bottom.style.width = props.bottom.width + 'px';
  12212. // reposition the panels
  12213. dom.background.style.left = '0';
  12214. dom.background.style.top = '0';
  12215. dom.backgroundVertical.style.left = (props.left.width + props.border.left) + 'px';
  12216. dom.backgroundVertical.style.top = '0';
  12217. dom.backgroundHorizontal.style.left = '0';
  12218. dom.backgroundHorizontal.style.top = props.top.height + 'px';
  12219. dom.centerContainer.style.left = props.left.width + 'px';
  12220. dom.centerContainer.style.top = props.top.height + 'px';
  12221. dom.leftContainer.style.left = '0';
  12222. dom.leftContainer.style.top = props.top.height + 'px';
  12223. dom.rightContainer.style.left = (props.left.width + props.center.width) + 'px';
  12224. dom.rightContainer.style.top = props.top.height + 'px';
  12225. dom.top.style.left = props.left.width + 'px';
  12226. dom.top.style.top = '0';
  12227. dom.bottom.style.left = props.left.width + 'px';
  12228. dom.bottom.style.top = (props.top.height + props.centerContainer.height) + 'px';
  12229. // update the scrollTop, feasible range for the offset can be changed
  12230. // when the height of the Core or of the contents of the center changed
  12231. this._updateScrollTop();
  12232. // reposition the scrollable contents
  12233. var offset = this.props.scrollTop;
  12234. if (options.orientation == 'bottom') {
  12235. offset += Math.max(this.props.centerContainer.height - this.props.center.height -
  12236. this.props.border.top - this.props.border.bottom, 0);
  12237. }
  12238. dom.center.style.left = '0';
  12239. dom.center.style.top = offset + 'px';
  12240. dom.left.style.left = '0';
  12241. dom.left.style.top = offset + 'px';
  12242. dom.right.style.left = '0';
  12243. dom.right.style.top = offset + 'px';
  12244. // show shadows when vertical scrolling is available
  12245. var visibilityTop = this.props.scrollTop == 0 ? 'hidden' : '';
  12246. var visibilityBottom = this.props.scrollTop == this.props.scrollTopMin ? 'hidden' : '';
  12247. dom.shadowTop.style.visibility = visibilityTop;
  12248. dom.shadowBottom.style.visibility = visibilityBottom;
  12249. dom.shadowTopLeft.style.visibility = visibilityTop;
  12250. dom.shadowBottomLeft.style.visibility = visibilityBottom;
  12251. dom.shadowTopRight.style.visibility = visibilityTop;
  12252. dom.shadowBottomRight.style.visibility = visibilityBottom;
  12253. // redraw all components
  12254. this.components.forEach(function (component) {
  12255. resized = component.redraw() || resized;
  12256. });
  12257. if (resized) {
  12258. // keep repainting until all sizes are settled
  12259. var MAX_REDRAWS = 3; // maximum number of consecutive redraws
  12260. if (this.redrawCount < MAX_REDRAWS) {
  12261. this.redrawCount++;
  12262. this.redraw();
  12263. }
  12264. else {
  12265. console.log('WARNING: infinite loop in redraw?');
  12266. }
  12267. this.redrawCount = 0;
  12268. }
  12269. this.emit("finishedRedraw");
  12270. };
  12271. // TODO: deprecated since version 1.1.0, remove some day
  12272. Core.prototype.repaint = function () {
  12273. throw new Error('Function repaint is deprecated. Use redraw instead.');
  12274. };
  12275. /**
  12276. * Set a current time. This can be used for example to ensure that a client's
  12277. * time is synchronized with a shared server time.
  12278. * Only applicable when option `showCurrentTime` is true.
  12279. * @param {Date | String | Number} time A Date, unix timestamp, or
  12280. * ISO date string.
  12281. */
  12282. Core.prototype.setCurrentTime = function(time) {
  12283. if (!this.currentTime) {
  12284. throw new Error('Option showCurrentTime must be true');
  12285. }
  12286. this.currentTime.setCurrentTime(time);
  12287. };
  12288. /**
  12289. * Get the current time.
  12290. * Only applicable when option `showCurrentTime` is true.
  12291. * @return {Date} Returns the current time.
  12292. */
  12293. Core.prototype.getCurrentTime = function() {
  12294. if (!this.currentTime) {
  12295. throw new Error('Option showCurrentTime must be true');
  12296. }
  12297. return this.currentTime.getCurrentTime();
  12298. };
  12299. /**
  12300. * Convert a position on screen (pixels) to a datetime
  12301. * @param {int} x Position on the screen in pixels
  12302. * @return {Date} time The datetime the corresponds with given position x
  12303. * @private
  12304. */
  12305. // TODO: move this function to Range
  12306. Core.prototype._toTime = function(x) {
  12307. return DateUtil.toTime(this, x, this.props.center.width);
  12308. };
  12309. /**
  12310. * Convert a position on the global screen (pixels) to a datetime
  12311. * @param {int} x Position on the screen in pixels
  12312. * @return {Date} time The datetime the corresponds with given position x
  12313. * @private
  12314. */
  12315. // TODO: move this function to Range
  12316. Core.prototype._toGlobalTime = function(x) {
  12317. return DateUtil.toTime(this, x, this.props.root.width);
  12318. //var conversion = this.range.conversion(this.props.root.width);
  12319. //return new Date(x / conversion.scale + conversion.offset);
  12320. };
  12321. /**
  12322. * Convert a datetime (Date object) into a position on the screen
  12323. * @param {Date} time A date
  12324. * @return {int} x The position on the screen in pixels which corresponds
  12325. * with the given date.
  12326. * @private
  12327. */
  12328. // TODO: move this function to Range
  12329. Core.prototype._toScreen = function(time) {
  12330. return DateUtil.toScreen(this, time, this.props.center.width);
  12331. };
  12332. /**
  12333. * Convert a datetime (Date object) into a position on the root
  12334. * This is used to get the pixel density estimate for the screen, not the center panel
  12335. * @param {Date} time A date
  12336. * @return {int} x The position on root in pixels which corresponds
  12337. * with the given date.
  12338. * @private
  12339. */
  12340. // TODO: move this function to Range
  12341. Core.prototype._toGlobalScreen = function(time) {
  12342. return DateUtil.toScreen(this, time, this.props.root.width);
  12343. //var conversion = this.range.conversion(this.props.root.width);
  12344. //return (time.valueOf() - conversion.offset) * conversion.scale;
  12345. };
  12346. /**
  12347. * Initialize watching when option autoResize is true
  12348. * @private
  12349. */
  12350. Core.prototype._initAutoResize = function () {
  12351. if (this.options.autoResize == true) {
  12352. this._startAutoResize();
  12353. }
  12354. else {
  12355. this._stopAutoResize();
  12356. }
  12357. };
  12358. /**
  12359. * Watch for changes in the size of the container. On resize, the Panel will
  12360. * automatically redraw itself.
  12361. * @private
  12362. */
  12363. Core.prototype._startAutoResize = function () {
  12364. var me = this;
  12365. this._stopAutoResize();
  12366. this._onResize = function() {
  12367. if (me.options.autoResize != true) {
  12368. // stop watching when the option autoResize is changed to false
  12369. me._stopAutoResize();
  12370. return;
  12371. }
  12372. if (me.dom.root) {
  12373. // check whether the frame is resized
  12374. // Note: we compare offsetWidth here, not clientWidth. For some reason,
  12375. // IE does not restore the clientWidth from 0 to the actual width after
  12376. // changing the timeline's container display style from none to visible
  12377. if ((me.dom.root.offsetWidth != me.props.lastWidth) ||
  12378. (me.dom.root.offsetHeight != me.props.lastHeight)) {
  12379. me.props.lastWidth = me.dom.root.offsetWidth;
  12380. me.props.lastHeight = me.dom.root.offsetHeight;
  12381. me.emit('change');
  12382. }
  12383. }
  12384. };
  12385. // add event listener to window resize
  12386. util.addEventListener(window, 'resize', this._onResize);
  12387. this.watchTimer = setInterval(this._onResize, 1000);
  12388. };
  12389. /**
  12390. * Stop watching for a resize of the frame.
  12391. * @private
  12392. */
  12393. Core.prototype._stopAutoResize = function () {
  12394. if (this.watchTimer) {
  12395. clearInterval(this.watchTimer);
  12396. this.watchTimer = undefined;
  12397. }
  12398. // remove event listener on window.resize
  12399. util.removeEventListener(window, 'resize', this._onResize);
  12400. this._onResize = null;
  12401. };
  12402. /**
  12403. * Start moving the timeline vertically
  12404. * @param {Event} event
  12405. * @private
  12406. */
  12407. Core.prototype._onTouch = function (event) {
  12408. this.touch.allowDragging = true;
  12409. };
  12410. /**
  12411. * Start moving the timeline vertically
  12412. * @param {Event} event
  12413. * @private
  12414. */
  12415. Core.prototype._onPinch = function (event) {
  12416. this.touch.allowDragging = false;
  12417. };
  12418. /**
  12419. * Start moving the timeline vertically
  12420. * @param {Event} event
  12421. * @private
  12422. */
  12423. Core.prototype._onDragStart = function (event) {
  12424. this.touch.initialScrollTop = this.props.scrollTop;
  12425. };
  12426. /**
  12427. * Move the timeline vertically
  12428. * @param {Event} event
  12429. * @private
  12430. */
  12431. Core.prototype._onDrag = function (event) {
  12432. // refuse to drag when we where pinching to prevent the timeline make a jump
  12433. // when releasing the fingers in opposite order from the touch screen
  12434. if (!this.touch.allowDragging) return;
  12435. var delta = event.gesture.deltaY;
  12436. var oldScrollTop = this._getScrollTop();
  12437. var newScrollTop = this._setScrollTop(this.touch.initialScrollTop + delta);
  12438. if (newScrollTop != oldScrollTop) {
  12439. this.redraw(); // TODO: this causes two redraws when dragging, the other is triggered by rangechange already
  12440. this.emit("verticalDrag");
  12441. }
  12442. };
  12443. /**
  12444. * Apply a scrollTop
  12445. * @param {Number} scrollTop
  12446. * @returns {Number} scrollTop Returns the applied scrollTop
  12447. * @private
  12448. */
  12449. Core.prototype._setScrollTop = function (scrollTop) {
  12450. this.props.scrollTop = scrollTop;
  12451. this._updateScrollTop();
  12452. return this.props.scrollTop;
  12453. };
  12454. /**
  12455. * Update the current scrollTop when the height of the containers has been changed
  12456. * @returns {Number} scrollTop Returns the applied scrollTop
  12457. * @private
  12458. */
  12459. Core.prototype._updateScrollTop = function () {
  12460. // recalculate the scrollTopMin
  12461. var scrollTopMin = Math.min(this.props.centerContainer.height - this.props.center.height, 0); // is negative or zero
  12462. if (scrollTopMin != this.props.scrollTopMin) {
  12463. // in case of bottom orientation, change the scrollTop such that the contents
  12464. // do not move relative to the time axis at the bottom
  12465. if (this.options.orientation == 'bottom') {
  12466. this.props.scrollTop += (scrollTopMin - this.props.scrollTopMin);
  12467. }
  12468. this.props.scrollTopMin = scrollTopMin;
  12469. }
  12470. // limit the scrollTop to the feasible scroll range
  12471. if (this.props.scrollTop > 0) this.props.scrollTop = 0;
  12472. if (this.props.scrollTop < scrollTopMin) this.props.scrollTop = scrollTopMin;
  12473. return this.props.scrollTop;
  12474. };
  12475. /**
  12476. * Get the current scrollTop
  12477. * @returns {number} scrollTop
  12478. * @private
  12479. */
  12480. Core.prototype._getScrollTop = function () {
  12481. return this.props.scrollTop;
  12482. };
  12483. module.exports = Core;
  12484. /***/ },
  12485. /* 26 */
  12486. /***/ function(module, exports, __webpack_require__) {
  12487. var Hammer = __webpack_require__(19);
  12488. var util = __webpack_require__(1);
  12489. var DataSet = __webpack_require__(7);
  12490. var DataView = __webpack_require__(9);
  12491. var Component = __webpack_require__(23);
  12492. var Group = __webpack_require__(27);
  12493. var BackgroundGroup = __webpack_require__(31);
  12494. var BoxItem = __webpack_require__(32);
  12495. var PointItem = __webpack_require__(33);
  12496. var RangeItem = __webpack_require__(29);
  12497. var BackgroundItem = __webpack_require__(34);
  12498. var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
  12499. var BACKGROUND = '__background__'; // reserved group id for background items without group
  12500. /**
  12501. * An ItemSet holds a set of items and ranges which can be displayed in a
  12502. * range. The width is determined by the parent of the ItemSet, and the height
  12503. * is determined by the size of the items.
  12504. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
  12505. * @param {Object} [options] See ItemSet.setOptions for the available options.
  12506. * @constructor ItemSet
  12507. * @extends Component
  12508. */
  12509. function ItemSet(body, options) {
  12510. this.body = body;
  12511. this.defaultOptions = {
  12512. type: null, // 'box', 'point', 'range', 'background'
  12513. orientation: 'bottom', // 'top' or 'bottom'
  12514. align: 'auto', // alignment of box items
  12515. stack: true,
  12516. groupOrder: null,
  12517. selectable: true,
  12518. editable: {
  12519. updateTime: false,
  12520. updateGroup: false,
  12521. add: false,
  12522. remove: false
  12523. },
  12524. onAdd: function (item, callback) {
  12525. callback(item);
  12526. },
  12527. onUpdate: function (item, callback) {
  12528. callback(item);
  12529. },
  12530. onMove: function (item, callback) {
  12531. callback(item);
  12532. },
  12533. onRemove: function (item, callback) {
  12534. callback(item);
  12535. },
  12536. onMoving: function (item, callback) {
  12537. callback(item);
  12538. },
  12539. margin: {
  12540. item: {
  12541. horizontal: 10,
  12542. vertical: 10
  12543. },
  12544. axis: 20
  12545. },
  12546. padding: 5
  12547. };
  12548. // options is shared by this ItemSet and all its items
  12549. this.options = util.extend({}, this.defaultOptions);
  12550. // options for getting items from the DataSet with the correct type
  12551. this.itemOptions = {
  12552. type: {start: 'Date', end: 'Date'}
  12553. };
  12554. this.conversion = {
  12555. toScreen: body.util.toScreen,
  12556. toTime: body.util.toTime
  12557. };
  12558. this.dom = {};
  12559. this.props = {};
  12560. this.hammer = null;
  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.groups = {}; // Group object for every group
  12590. this.groupIds = [];
  12591. this.selection = []; // list with the ids of all selected nodes
  12592. this.stackDirty = true; // if true, all items will be restacked on next redraw
  12593. this.touchParams = {}; // stores properties while dragging
  12594. // create the HTML DOM
  12595. this._create();
  12596. this.setOptions(options);
  12597. }
  12598. ItemSet.prototype = new Component();
  12599. // available item types will be registered here
  12600. ItemSet.types = {
  12601. background: BackgroundItem,
  12602. box: BoxItem,
  12603. range: RangeItem,
  12604. point: PointItem
  12605. };
  12606. /**
  12607. * Create the HTML DOM for the ItemSet
  12608. */
  12609. ItemSet.prototype._create = function(){
  12610. var frame = document.createElement('div');
  12611. frame.className = 'itemset';
  12612. frame['timeline-itemset'] = this;
  12613. this.dom.frame = frame;
  12614. // create background panel
  12615. var background = document.createElement('div');
  12616. background.className = 'background';
  12617. frame.appendChild(background);
  12618. this.dom.background = background;
  12619. // create foreground panel
  12620. var foreground = document.createElement('div');
  12621. foreground.className = 'foreground';
  12622. frame.appendChild(foreground);
  12623. this.dom.foreground = foreground;
  12624. // create axis panel
  12625. var axis = document.createElement('div');
  12626. axis.className = 'axis';
  12627. this.dom.axis = axis;
  12628. // create labelset
  12629. var labelSet = document.createElement('div');
  12630. labelSet.className = 'labelset';
  12631. this.dom.labelSet = labelSet;
  12632. // create ungrouped Group
  12633. this._updateUngrouped();
  12634. // create background Group
  12635. var backgroundGroup = new BackgroundGroup(BACKGROUND, null, this);
  12636. backgroundGroup.show();
  12637. this.groups[BACKGROUND] = backgroundGroup;
  12638. // attach event listeners
  12639. // Note: we bind to the centerContainer for the case where the height
  12640. // of the center container is larger than of the ItemSet, so we
  12641. // can click in the empty area to create a new item or deselect an item.
  12642. this.hammer = Hammer(this.body.dom.centerContainer, {
  12643. preventDefault: true
  12644. });
  12645. // drag items when selected
  12646. this.hammer.on('touch', this._onTouch.bind(this));
  12647. this.hammer.on('dragstart', this._onDragStart.bind(this));
  12648. this.hammer.on('drag', this._onDrag.bind(this));
  12649. this.hammer.on('dragend', this._onDragEnd.bind(this));
  12650. // single select (or unselect) when tapping an item
  12651. this.hammer.on('tap', this._onSelectItem.bind(this));
  12652. // multi select when holding mouse/touch, or on ctrl+click
  12653. this.hammer.on('hold', this._onMultiSelectItem.bind(this));
  12654. // add item on doubletap
  12655. this.hammer.on('doubletap', this._onAddItem.bind(this));
  12656. // attach to the DOM
  12657. this.show();
  12658. };
  12659. /**
  12660. * Set options for the ItemSet. Existing options will be extended/overwritten.
  12661. * @param {Object} [options] The following options are available:
  12662. * {String} type
  12663. * Default type for the items. Choose from 'box'
  12664. * (default), 'point', 'range', or 'background'.
  12665. * The default style can be overwritten by
  12666. * individual items.
  12667. * {String} align
  12668. * Alignment for the items, only applicable for
  12669. * BoxItem. Choose 'center' (default), 'left', or
  12670. * 'right'.
  12671. * {String} orientation
  12672. * Orientation of the item set. Choose 'top' or
  12673. * 'bottom' (default).
  12674. * {Function} groupOrder
  12675. * A sorting function for ordering groups
  12676. * {Boolean} stack
  12677. * If true (deafult), items will be stacked on
  12678. * top of each other.
  12679. * {Number} margin.axis
  12680. * Margin between the axis and the items in pixels.
  12681. * Default is 20.
  12682. * {Number} margin.item.horizontal
  12683. * Horizontal margin between items in pixels.
  12684. * Default is 10.
  12685. * {Number} margin.item.vertical
  12686. * Vertical Margin between items in pixels.
  12687. * Default is 10.
  12688. * {Number} margin.item
  12689. * Margin between items in pixels in both horizontal
  12690. * and vertical direction. Default is 10.
  12691. * {Number} margin
  12692. * Set margin for both axis and items in pixels.
  12693. * {Number} padding
  12694. * Padding of the contents of an item in pixels.
  12695. * Must correspond with the items css. Default is 5.
  12696. * {Boolean} selectable
  12697. * If true (default), items can be selected.
  12698. * {Boolean} editable
  12699. * Set all editable options to true or false
  12700. * {Boolean} editable.updateTime
  12701. * Allow dragging an item to an other moment in time
  12702. * {Boolean} editable.updateGroup
  12703. * Allow dragging an item to an other group
  12704. * {Boolean} editable.add
  12705. * Allow creating new items on double tap
  12706. * {Boolean} editable.remove
  12707. * Allow removing items by clicking the delete button
  12708. * top right of a selected item.
  12709. * {Function(item: Item, callback: Function)} onAdd
  12710. * Callback function triggered when an item is about to be added:
  12711. * when the user double taps an empty space in the Timeline.
  12712. * {Function(item: Item, callback: Function)} onUpdate
  12713. * Callback function fired when an item is about to be updated.
  12714. * This function typically has to show a dialog where the user
  12715. * change the item. If not implemented, nothing happens.
  12716. * {Function(item: Item, callback: Function)} onMove
  12717. * Fired when an item has been moved. If not implemented,
  12718. * the move action will be accepted.
  12719. * {Function(item: Item, callback: Function)} onRemove
  12720. * Fired when an item is about to be deleted.
  12721. * If not implemented, the item will be always removed.
  12722. */
  12723. ItemSet.prototype.setOptions = function(options) {
  12724. if (options) {
  12725. // copy all options that we know
  12726. var fields = ['type', 'align', 'orientation', 'padding', 'stack', 'selectable', 'groupOrder', 'dataAttributes', 'template','hide'];
  12727. util.selectiveExtend(fields, this.options, options);
  12728. if ('margin' in options) {
  12729. if (typeof options.margin === 'number') {
  12730. this.options.margin.axis = options.margin;
  12731. this.options.margin.item.horizontal = options.margin;
  12732. this.options.margin.item.vertical = options.margin;
  12733. }
  12734. else if (typeof options.margin === 'object') {
  12735. util.selectiveExtend(['axis'], this.options.margin, options.margin);
  12736. if ('item' in options.margin) {
  12737. if (typeof options.margin.item === 'number') {
  12738. this.options.margin.item.horizontal = options.margin.item;
  12739. this.options.margin.item.vertical = options.margin.item;
  12740. }
  12741. else if (typeof options.margin.item === 'object') {
  12742. util.selectiveExtend(['horizontal', 'vertical'], this.options.margin.item, options.margin.item);
  12743. }
  12744. }
  12745. }
  12746. }
  12747. if ('editable' in options) {
  12748. if (typeof options.editable === 'boolean') {
  12749. this.options.editable.updateTime = options.editable;
  12750. this.options.editable.updateGroup = options.editable;
  12751. this.options.editable.add = options.editable;
  12752. this.options.editable.remove = options.editable;
  12753. }
  12754. else if (typeof options.editable === 'object') {
  12755. util.selectiveExtend(['updateTime', 'updateGroup', 'add', 'remove'], this.options.editable, options.editable);
  12756. }
  12757. }
  12758. // callback functions
  12759. var addCallback = (function (name) {
  12760. var fn = options[name];
  12761. if (fn) {
  12762. if (!(fn instanceof Function)) {
  12763. throw new Error('option ' + name + ' must be a function ' + name + '(item, callback)');
  12764. }
  12765. this.options[name] = fn;
  12766. }
  12767. }).bind(this);
  12768. ['onAdd', 'onUpdate', 'onRemove', 'onMove', 'onMoving'].forEach(addCallback);
  12769. // force the itemSet to refresh: options like orientation and margins may be changed
  12770. this.markDirty();
  12771. }
  12772. };
  12773. /**
  12774. * Mark the ItemSet dirty so it will refresh everything with next redraw
  12775. */
  12776. ItemSet.prototype.markDirty = function() {
  12777. this.groupIds = [];
  12778. this.stackDirty = true;
  12779. };
  12780. /**
  12781. * Destroy the ItemSet
  12782. */
  12783. ItemSet.prototype.destroy = function() {
  12784. this.hide();
  12785. this.setItems(null);
  12786. this.setGroups(null);
  12787. this.hammer = null;
  12788. this.body = null;
  12789. this.conversion = null;
  12790. };
  12791. /**
  12792. * Hide the component from the DOM
  12793. */
  12794. ItemSet.prototype.hide = function() {
  12795. // remove the frame containing the items
  12796. if (this.dom.frame.parentNode) {
  12797. this.dom.frame.parentNode.removeChild(this.dom.frame);
  12798. }
  12799. // remove the axis with dots
  12800. if (this.dom.axis.parentNode) {
  12801. this.dom.axis.parentNode.removeChild(this.dom.axis);
  12802. }
  12803. // remove the labelset containing all group labels
  12804. if (this.dom.labelSet.parentNode) {
  12805. this.dom.labelSet.parentNode.removeChild(this.dom.labelSet);
  12806. }
  12807. };
  12808. /**
  12809. * Show the component in the DOM (when not already visible).
  12810. * @return {Boolean} changed
  12811. */
  12812. ItemSet.prototype.show = function() {
  12813. // show frame containing the items
  12814. if (!this.dom.frame.parentNode) {
  12815. this.body.dom.center.appendChild(this.dom.frame);
  12816. }
  12817. // show axis with dots
  12818. if (!this.dom.axis.parentNode) {
  12819. this.body.dom.backgroundVertical.appendChild(this.dom.axis);
  12820. }
  12821. // show labelset containing labels
  12822. if (!this.dom.labelSet.parentNode) {
  12823. this.body.dom.left.appendChild(this.dom.labelSet);
  12824. }
  12825. };
  12826. /**
  12827. * Set selected items by their id. Replaces the current selection
  12828. * Unknown id's are silently ignored.
  12829. * @param {string[] | string} [ids] An array with zero or more id's of the items to be
  12830. * selected, or a single item id. If ids is undefined
  12831. * or an empty array, all items will be unselected.
  12832. */
  12833. ItemSet.prototype.setSelection = function(ids) {
  12834. var i, ii, id, item;
  12835. if (ids == undefined) ids = [];
  12836. if (!Array.isArray(ids)) ids = [ids];
  12837. // unselect currently selected items
  12838. for (i = 0, ii = this.selection.length; i < ii; i++) {
  12839. id = this.selection[i];
  12840. item = this.items[id];
  12841. if (item) item.unselect();
  12842. }
  12843. // select items
  12844. this.selection = [];
  12845. for (i = 0, ii = ids.length; i < ii; i++) {
  12846. id = ids[i];
  12847. item = this.items[id];
  12848. if (item) {
  12849. this.selection.push(id);
  12850. item.select();
  12851. }
  12852. }
  12853. };
  12854. /**
  12855. * Get the selected items by their id
  12856. * @return {Array} ids The ids of the selected items
  12857. */
  12858. ItemSet.prototype.getSelection = function() {
  12859. return this.selection.concat([]);
  12860. };
  12861. /**
  12862. * Get the id's of the currently visible items.
  12863. * @returns {Array} The ids of the visible items
  12864. */
  12865. ItemSet.prototype.getVisibleItems = function() {
  12866. var range = this.body.range.getRange();
  12867. var left = this.body.util.toScreen(range.start);
  12868. var right = this.body.util.toScreen(range.end);
  12869. var ids = [];
  12870. for (var groupId in this.groups) {
  12871. if (this.groups.hasOwnProperty(groupId)) {
  12872. var group = this.groups[groupId];
  12873. var rawVisibleItems = group.visibleItems;
  12874. // filter the "raw" set with visibleItems into a set which is really
  12875. // visible by pixels
  12876. for (var i = 0; i < rawVisibleItems.length; i++) {
  12877. var item = rawVisibleItems[i];
  12878. // TODO: also check whether visible vertically
  12879. if ((item.left < right) && (item.left + item.width > left)) {
  12880. ids.push(item.id);
  12881. }
  12882. }
  12883. }
  12884. }
  12885. return ids;
  12886. };
  12887. /**
  12888. * Deselect a selected item
  12889. * @param {String | Number} id
  12890. * @private
  12891. */
  12892. ItemSet.prototype._deselect = function(id) {
  12893. var selection = this.selection;
  12894. for (var i = 0, ii = selection.length; i < ii; i++) {
  12895. if (selection[i] == id) { // non-strict comparison!
  12896. selection.splice(i, 1);
  12897. break;
  12898. }
  12899. }
  12900. };
  12901. /**
  12902. * Repaint the component
  12903. * @return {boolean} Returns true if the component is resized
  12904. */
  12905. ItemSet.prototype.redraw = function() {
  12906. var margin = this.options.margin,
  12907. range = this.body.range,
  12908. asSize = util.option.asSize,
  12909. options = this.options,
  12910. orientation = options.orientation,
  12911. resized = false,
  12912. frame = this.dom.frame,
  12913. editable = options.editable.updateTime || options.editable.updateGroup;
  12914. // recalculate absolute position (before redrawing groups)
  12915. this.props.top = this.body.domProps.top.height + this.body.domProps.border.top;
  12916. this.props.left = this.body.domProps.left.width + this.body.domProps.border.left;
  12917. // update class name
  12918. frame.className = 'itemset' + (editable ? ' editable' : '');
  12919. // reorder the groups (if needed)
  12920. resized = this._orderGroups() || resized;
  12921. // check whether zoomed (in that case we need to re-stack everything)
  12922. // TODO: would be nicer to get this as a trigger from Range
  12923. var visibleInterval = range.end - range.start;
  12924. var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.props.width != this.props.lastWidth);
  12925. if (zoomed) this.stackDirty = true;
  12926. this.lastVisibleInterval = visibleInterval;
  12927. this.props.lastWidth = this.props.width;
  12928. var restack = this.stackDirty;
  12929. var firstGroup = this._firstGroup();
  12930. var firstMargin = {
  12931. item: margin.item,
  12932. axis: margin.axis
  12933. };
  12934. var nonFirstMargin = {
  12935. item: margin.item,
  12936. axis: margin.item.vertical / 2
  12937. };
  12938. var height = 0;
  12939. var minHeight = margin.axis + margin.item.vertical;
  12940. // redraw the background group
  12941. this.groups[BACKGROUND].redraw(range, nonFirstMargin, restack);
  12942. // redraw all regular groups
  12943. util.forEach(this.groups, function (group) {
  12944. var groupMargin = (group == firstGroup) ? firstMargin : nonFirstMargin;
  12945. var groupResized = group.redraw(range, groupMargin, restack);
  12946. resized = groupResized || resized;
  12947. height += group.height;
  12948. });
  12949. height = Math.max(height, minHeight);
  12950. this.stackDirty = false;
  12951. // update frame height
  12952. frame.style.height = asSize(height);
  12953. // calculate actual size
  12954. this.props.width = frame.offsetWidth;
  12955. this.props.height = height;
  12956. // reposition axis
  12957. this.dom.axis.style.top = asSize((orientation == 'top') ?
  12958. (this.body.domProps.top.height + this.body.domProps.border.top) :
  12959. (this.body.domProps.top.height + this.body.domProps.centerContainer.height));
  12960. this.dom.axis.style.left = '0';
  12961. // check if this component is resized
  12962. resized = this._isResized() || resized;
  12963. return resized;
  12964. };
  12965. /**
  12966. * Get the first group, aligned with the axis
  12967. * @return {Group | null} firstGroup
  12968. * @private
  12969. */
  12970. ItemSet.prototype._firstGroup = function() {
  12971. var firstGroupIndex = (this.options.orientation == 'top') ? 0 : (this.groupIds.length - 1);
  12972. var firstGroupId = this.groupIds[firstGroupIndex];
  12973. var firstGroup = this.groups[firstGroupId] || this.groups[UNGROUPED];
  12974. return firstGroup || null;
  12975. };
  12976. /**
  12977. * Create or delete the group holding all ungrouped items. This group is used when
  12978. * there are no groups specified.
  12979. * @protected
  12980. */
  12981. ItemSet.prototype._updateUngrouped = function() {
  12982. var ungrouped = this.groups[UNGROUPED];
  12983. var background = this.groups[BACKGROUND];
  12984. var item, itemId;
  12985. if (this.groupsData) {
  12986. // remove the group holding all ungrouped items
  12987. if (ungrouped) {
  12988. ungrouped.hide();
  12989. delete this.groups[UNGROUPED];
  12990. for (itemId in this.items) {
  12991. if (this.items.hasOwnProperty(itemId)) {
  12992. item = this.items[itemId];
  12993. item.parent && item.parent.remove(item);
  12994. var groupId = this._getGroupId(item.data);
  12995. var group = this.groups[groupId];
  12996. group && group.add(item) || item.hide();
  12997. }
  12998. }
  12999. }
  13000. }
  13001. else {
  13002. // create a group holding all (unfiltered) items
  13003. if (!ungrouped) {
  13004. var id = null;
  13005. var data = null;
  13006. ungrouped = new Group(id, data, this);
  13007. this.groups[UNGROUPED] = ungrouped;
  13008. for (itemId in this.items) {
  13009. if (this.items.hasOwnProperty(itemId)) {
  13010. item = this.items[itemId];
  13011. ungrouped.add(item);
  13012. }
  13013. }
  13014. ungrouped.show();
  13015. }
  13016. }
  13017. };
  13018. /**
  13019. * Get the element for the labelset
  13020. * @return {HTMLElement} labelSet
  13021. */
  13022. ItemSet.prototype.getLabelSet = function() {
  13023. return this.dom.labelSet;
  13024. };
  13025. /**
  13026. * Set items
  13027. * @param {vis.DataSet | null} items
  13028. */
  13029. ItemSet.prototype.setItems = function(items) {
  13030. var me = this,
  13031. ids,
  13032. oldItemsData = this.itemsData;
  13033. // replace the dataset
  13034. if (!items) {
  13035. this.itemsData = null;
  13036. }
  13037. else if (items instanceof DataSet || items instanceof DataView) {
  13038. this.itemsData = items;
  13039. }
  13040. else {
  13041. throw new TypeError('Data must be an instance of DataSet or DataView');
  13042. }
  13043. if (oldItemsData) {
  13044. // unsubscribe from old dataset
  13045. util.forEach(this.itemListeners, function (callback, event) {
  13046. oldItemsData.off(event, callback);
  13047. });
  13048. // remove all drawn items
  13049. ids = oldItemsData.getIds();
  13050. this._onRemove(ids);
  13051. }
  13052. if (this.itemsData) {
  13053. // subscribe to new dataset
  13054. var id = this.id;
  13055. util.forEach(this.itemListeners, function (callback, event) {
  13056. me.itemsData.on(event, callback, id);
  13057. });
  13058. // add all new items
  13059. ids = this.itemsData.getIds();
  13060. this._onAdd(ids);
  13061. // update the group holding all ungrouped items
  13062. this._updateUngrouped();
  13063. }
  13064. };
  13065. /**
  13066. * Get the current items
  13067. * @returns {vis.DataSet | null}
  13068. */
  13069. ItemSet.prototype.getItems = function() {
  13070. return this.itemsData;
  13071. };
  13072. /**
  13073. * Set groups
  13074. * @param {vis.DataSet} groups
  13075. */
  13076. ItemSet.prototype.setGroups = function(groups) {
  13077. var me = this,
  13078. ids;
  13079. // unsubscribe from current dataset
  13080. if (this.groupsData) {
  13081. util.forEach(this.groupListeners, function (callback, event) {
  13082. me.groupsData.unsubscribe(event, callback);
  13083. });
  13084. // remove all drawn groups
  13085. ids = this.groupsData.getIds();
  13086. this.groupsData = null;
  13087. this._onRemoveGroups(ids); // note: this will cause a redraw
  13088. }
  13089. // replace the dataset
  13090. if (!groups) {
  13091. this.groupsData = null;
  13092. }
  13093. else if (groups instanceof DataSet || groups instanceof DataView) {
  13094. this.groupsData = groups;
  13095. }
  13096. else {
  13097. throw new TypeError('Data must be an instance of DataSet or DataView');
  13098. }
  13099. if (this.groupsData) {
  13100. // subscribe to new dataset
  13101. var id = this.id;
  13102. util.forEach(this.groupListeners, function (callback, event) {
  13103. me.groupsData.on(event, callback, id);
  13104. });
  13105. // draw all ms
  13106. ids = this.groupsData.getIds();
  13107. this._onAddGroups(ids);
  13108. }
  13109. // update the group holding all ungrouped items
  13110. this._updateUngrouped();
  13111. // update the order of all items in each group
  13112. this._order();
  13113. this.body.emitter.emit('change', {queue: true});
  13114. };
  13115. /**
  13116. * Get the current groups
  13117. * @returns {vis.DataSet | null} groups
  13118. */
  13119. ItemSet.prototype.getGroups = function() {
  13120. return this.groupsData;
  13121. };
  13122. /**
  13123. * Remove an item by its id
  13124. * @param {String | Number} id
  13125. */
  13126. ItemSet.prototype.removeItem = function(id) {
  13127. var item = this.itemsData.get(id),
  13128. dataset = this.itemsData.getDataSet();
  13129. if (item) {
  13130. // confirm deletion
  13131. this.options.onRemove(item, function (item) {
  13132. if (item) {
  13133. // remove by id here, it is possible that an item has no id defined
  13134. // itself, so better not delete by the item itself
  13135. dataset.remove(id);
  13136. }
  13137. });
  13138. }
  13139. };
  13140. /**
  13141. * Get the time of an item based on it's data and options.type
  13142. * @param {Object} itemData
  13143. * @returns {string} Returns the type
  13144. * @private
  13145. */
  13146. ItemSet.prototype._getType = function (itemData) {
  13147. return itemData.type || this.options.type || (itemData.end ? 'range' : 'box');
  13148. };
  13149. /**
  13150. * Get the group id for an item
  13151. * @param {Object} itemData
  13152. * @returns {string} Returns the groupId
  13153. * @private
  13154. */
  13155. ItemSet.prototype._getGroupId = function (itemData) {
  13156. var type = this._getType(itemData);
  13157. if (type == 'background' && itemData.group == undefined) {
  13158. return BACKGROUND;
  13159. }
  13160. else {
  13161. return this.groupsData ? itemData.group : UNGROUPED;
  13162. }
  13163. };
  13164. /**
  13165. * Handle updated items
  13166. * @param {Number[]} ids
  13167. * @protected
  13168. */
  13169. ItemSet.prototype._onUpdate = function(ids) {
  13170. var me = this;
  13171. ids.forEach(function (id) {
  13172. var itemData = me.itemsData.get(id, me.itemOptions);
  13173. var item = me.items[id];
  13174. var type = me._getType(itemData);
  13175. var constructor = ItemSet.types[type];
  13176. if (item) {
  13177. // update item
  13178. if (!constructor || !(item instanceof constructor)) {
  13179. // item type has changed, delete the item and recreate it
  13180. me._removeItem(item);
  13181. item = null;
  13182. }
  13183. else {
  13184. me._updateItem(item, itemData);
  13185. }
  13186. }
  13187. if (!item) {
  13188. // create item
  13189. if (constructor) {
  13190. item = new constructor(itemData, me.conversion, me.options);
  13191. item.id = id; // TODO: not so nice setting id afterwards
  13192. me._addItem(item);
  13193. }
  13194. else if (type == 'rangeoverflow') {
  13195. // TODO: deprecated since version 2.1.0 (or 3.0.0?). cleanup some day
  13196. throw new TypeError('Item type "rangeoverflow" is deprecated. Use css styling instead: ' +
  13197. '.vis.timeline .item.range .content {overflow: visible;}');
  13198. }
  13199. else {
  13200. throw new TypeError('Unknown item type "' + type + '"');
  13201. }
  13202. }
  13203. });
  13204. this._order();
  13205. this.stackDirty = true; // force re-stacking of all items next redraw
  13206. this.body.emitter.emit('change', {queue: true});
  13207. };
  13208. /**
  13209. * Handle added items
  13210. * @param {Number[]} ids
  13211. * @protected
  13212. */
  13213. ItemSet.prototype._onAdd = ItemSet.prototype._onUpdate;
  13214. /**
  13215. * Handle removed items
  13216. * @param {Number[]} ids
  13217. * @protected
  13218. */
  13219. ItemSet.prototype._onRemove = function(ids) {
  13220. var count = 0;
  13221. var me = this;
  13222. ids.forEach(function (id) {
  13223. var item = me.items[id];
  13224. if (item) {
  13225. count++;
  13226. me._removeItem(item);
  13227. }
  13228. });
  13229. if (count) {
  13230. // update order
  13231. this._order();
  13232. this.stackDirty = true; // force re-stacking of all items next redraw
  13233. this.body.emitter.emit('change', {queue: true});
  13234. }
  13235. };
  13236. /**
  13237. * Update the order of item in all groups
  13238. * @private
  13239. */
  13240. ItemSet.prototype._order = function() {
  13241. // reorder the items in all groups
  13242. // TODO: optimization: only reorder groups affected by the changed items
  13243. util.forEach(this.groups, function (group) {
  13244. group.order();
  13245. });
  13246. };
  13247. /**
  13248. * Handle updated groups
  13249. * @param {Number[]} ids
  13250. * @private
  13251. */
  13252. ItemSet.prototype._onUpdateGroups = function(ids) {
  13253. this._onAddGroups(ids);
  13254. };
  13255. /**
  13256. * Handle changed groups (added or updated)
  13257. * @param {Number[]} ids
  13258. * @private
  13259. */
  13260. ItemSet.prototype._onAddGroups = function(ids) {
  13261. var me = this;
  13262. ids.forEach(function (id) {
  13263. var groupData = me.groupsData.get(id);
  13264. var group = me.groups[id];
  13265. if (!group) {
  13266. // check for reserved ids
  13267. if (id == UNGROUPED || id == BACKGROUND) {
  13268. throw new Error('Illegal group id. ' + id + ' is a reserved id.');
  13269. }
  13270. var groupOptions = Object.create(me.options);
  13271. util.extend(groupOptions, {
  13272. height: null
  13273. });
  13274. group = new Group(id, groupData, me);
  13275. me.groups[id] = group;
  13276. // add items with this groupId to the new group
  13277. for (var itemId in me.items) {
  13278. if (me.items.hasOwnProperty(itemId)) {
  13279. var item = me.items[itemId];
  13280. if (item.data.group == id) {
  13281. group.add(item);
  13282. }
  13283. }
  13284. }
  13285. group.order();
  13286. group.show();
  13287. }
  13288. else {
  13289. // update group
  13290. group.setData(groupData);
  13291. }
  13292. });
  13293. this.body.emitter.emit('change', {queue: true});
  13294. };
  13295. /**
  13296. * Handle removed groups
  13297. * @param {Number[]} ids
  13298. * @private
  13299. */
  13300. ItemSet.prototype._onRemoveGroups = function(ids) {
  13301. var groups = this.groups;
  13302. ids.forEach(function (id) {
  13303. var group = groups[id];
  13304. if (group) {
  13305. group.hide();
  13306. delete groups[id];
  13307. }
  13308. });
  13309. this.markDirty();
  13310. this.body.emitter.emit('change', {queue: true});
  13311. };
  13312. /**
  13313. * Reorder the groups if needed
  13314. * @return {boolean} changed
  13315. * @private
  13316. */
  13317. ItemSet.prototype._orderGroups = function () {
  13318. if (this.groupsData) {
  13319. // reorder the groups
  13320. var groupIds = this.groupsData.getIds({
  13321. order: this.options.groupOrder
  13322. });
  13323. var changed = !util.equalArray(groupIds, this.groupIds);
  13324. if (changed) {
  13325. // hide all groups, removes them from the DOM
  13326. var groups = this.groups;
  13327. groupIds.forEach(function (groupId) {
  13328. groups[groupId].hide();
  13329. });
  13330. // show the groups again, attach them to the DOM in correct order
  13331. groupIds.forEach(function (groupId) {
  13332. groups[groupId].show();
  13333. });
  13334. this.groupIds = groupIds;
  13335. }
  13336. return changed;
  13337. }
  13338. else {
  13339. return false;
  13340. }
  13341. };
  13342. /**
  13343. * Add a new item
  13344. * @param {Item} item
  13345. * @private
  13346. */
  13347. ItemSet.prototype._addItem = function(item) {
  13348. this.items[item.id] = item;
  13349. // add to group
  13350. var groupId = this._getGroupId(item.data);
  13351. var group = this.groups[groupId];
  13352. if (group) group.add(item);
  13353. };
  13354. /**
  13355. * Update an existing item
  13356. * @param {Item} item
  13357. * @param {Object} itemData
  13358. * @private
  13359. */
  13360. ItemSet.prototype._updateItem = function(item, itemData) {
  13361. var oldGroupId = item.data.group;
  13362. // update the items data (will redraw the item when displayed)
  13363. item.setData(itemData);
  13364. // update group
  13365. if (oldGroupId != item.data.group) {
  13366. var oldGroup = this.groups[oldGroupId];
  13367. if (oldGroup) oldGroup.remove(item);
  13368. var groupId = this._getGroupId(item.data);
  13369. var group = this.groups[groupId];
  13370. if (group) group.add(item);
  13371. }
  13372. };
  13373. /**
  13374. * Delete an item from the ItemSet: remove it from the DOM, from the map
  13375. * with items, and from the map with visible items, and from the selection
  13376. * @param {Item} item
  13377. * @private
  13378. */
  13379. ItemSet.prototype._removeItem = function(item) {
  13380. // remove from DOM
  13381. item.hide();
  13382. // remove from items
  13383. delete this.items[item.id];
  13384. // remove from selection
  13385. var index = this.selection.indexOf(item.id);
  13386. if (index != -1) this.selection.splice(index, 1);
  13387. // remove from group
  13388. item.parent && item.parent.remove(item);
  13389. };
  13390. /**
  13391. * Create an array containing all items being a range (having an end date)
  13392. * @param array
  13393. * @returns {Array}
  13394. * @private
  13395. */
  13396. ItemSet.prototype._constructByEndArray = function(array) {
  13397. var endArray = [];
  13398. for (var i = 0; i < array.length; i++) {
  13399. if (array[i] instanceof RangeItem) {
  13400. endArray.push(array[i]);
  13401. }
  13402. }
  13403. return endArray;
  13404. };
  13405. /**
  13406. * Register the clicked item on touch, before dragStart is initiated.
  13407. *
  13408. * dragStart is initiated from a mousemove event, which can have left the item
  13409. * already resulting in an item == null
  13410. *
  13411. * @param {Event} event
  13412. * @private
  13413. */
  13414. ItemSet.prototype._onTouch = function (event) {
  13415. // store the touched item, used in _onDragStart
  13416. this.touchParams.item = ItemSet.itemFromTarget(event);
  13417. };
  13418. /**
  13419. * Start dragging the selected events
  13420. * @param {Event} event
  13421. * @private
  13422. */
  13423. ItemSet.prototype._onDragStart = function (event) {
  13424. if (!this.options.editable.updateTime && !this.options.editable.updateGroup) {
  13425. return;
  13426. }
  13427. var item = this.touchParams.item || null;
  13428. var me = this;
  13429. var props;
  13430. if (item && item.selected) {
  13431. var dragLeftItem = event.target.dragLeftItem;
  13432. var dragRightItem = event.target.dragRightItem;
  13433. if (dragLeftItem) {
  13434. props = {
  13435. item: dragLeftItem,
  13436. initialX: event.gesture.center.clientX
  13437. };
  13438. if (me.options.editable.updateTime) {
  13439. props.start = item.data.start.valueOf();
  13440. }
  13441. if (me.options.editable.updateGroup) {
  13442. if ('group' in item.data) props.group = item.data.group;
  13443. }
  13444. this.touchParams.itemProps = [props];
  13445. }
  13446. else if (dragRightItem) {
  13447. props = {
  13448. item: dragRightItem,
  13449. initialX: event.gesture.center.clientX
  13450. };
  13451. if (me.options.editable.updateTime) {
  13452. props.end = item.data.end.valueOf();
  13453. }
  13454. if (me.options.editable.updateGroup) {
  13455. if ('group' in item.data) props.group = item.data.group;
  13456. }
  13457. this.touchParams.itemProps = [props];
  13458. }
  13459. else {
  13460. this.touchParams.itemProps = this.getSelection().map(function (id) {
  13461. var item = me.items[id];
  13462. var props = {
  13463. item: item,
  13464. initialX: event.gesture.center.clientX
  13465. };
  13466. if (me.options.editable.updateTime) {
  13467. if ('start' in item.data) props.start = item.data.start.valueOf();
  13468. if ('end' in item.data) props.end = item.data.end.valueOf();
  13469. }
  13470. if (me.options.editable.updateGroup) {
  13471. if ('group' in item.data) props.group = item.data.group;
  13472. }
  13473. return props;
  13474. });
  13475. }
  13476. event.stopPropagation();
  13477. }
  13478. };
  13479. /**
  13480. * Drag selected items
  13481. * @param {Event} event
  13482. * @private
  13483. */
  13484. ItemSet.prototype._onDrag = function (event) {
  13485. event.preventDefault()
  13486. if (this.touchParams.itemProps) {
  13487. var me = this;
  13488. var snap = this.body.util.snap || null;
  13489. var xOffset = this.body.dom.root.offsetLeft + this.body.domProps.left.width;
  13490. // move
  13491. this.touchParams.itemProps.forEach(function (props) {
  13492. var newProps = {};
  13493. var current = me.body.util.toTime(event.gesture.center.clientX - xOffset);
  13494. var initial = me.body.util.toTime(props.initialX - xOffset);
  13495. var offset = current - initial;
  13496. if ('start' in props) {
  13497. var start = new Date(props.start + offset);
  13498. newProps.start = snap ? snap(start) : start;
  13499. }
  13500. if ('end' in props) {
  13501. var end = new Date(props.end + offset);
  13502. newProps.end = snap ? snap(end) : end;
  13503. }
  13504. if ('group' in props) {
  13505. // drag from one group to another
  13506. var group = ItemSet.groupFromTarget(event);
  13507. newProps.group = group && group.groupId;
  13508. }
  13509. // confirm moving the item
  13510. var itemData = util.extend({}, props.item.data, newProps);
  13511. me.options.onMoving(itemData, function (itemData) {
  13512. if (itemData) {
  13513. me._updateItemProps(props.item, itemData);
  13514. }
  13515. });
  13516. });
  13517. this.stackDirty = true; // force re-stacking of all items next redraw
  13518. this.body.emitter.emit('change');
  13519. event.stopPropagation();
  13520. }
  13521. };
  13522. /**
  13523. * Update an items properties
  13524. * @param {Item} item
  13525. * @param {Object} props Can contain properties start, end, and group.
  13526. * @private
  13527. */
  13528. ItemSet.prototype._updateItemProps = function(item, props) {
  13529. // TODO: copy all properties from props to item? (also new ones)
  13530. if ('start' in props) item.data.start = props.start;
  13531. if ('end' in props) item.data.end = props.end;
  13532. if ('group' in props && item.data.group != props.group) {
  13533. this._moveToGroup(item, props.group)
  13534. }
  13535. };
  13536. /**
  13537. * Move an item to another group
  13538. * @param {Item} item
  13539. * @param {String | Number} groupId
  13540. * @private
  13541. */
  13542. ItemSet.prototype._moveToGroup = function(item, groupId) {
  13543. var group = this.groups[groupId];
  13544. if (group && group.groupId != item.data.group) {
  13545. var oldGroup = item.parent;
  13546. oldGroup.remove(item);
  13547. oldGroup.order();
  13548. group.add(item);
  13549. group.order();
  13550. item.data.group = group.groupId;
  13551. }
  13552. };
  13553. /**
  13554. * End of dragging selected items
  13555. * @param {Event} event
  13556. * @private
  13557. */
  13558. ItemSet.prototype._onDragEnd = function (event) {
  13559. event.preventDefault()
  13560. if (this.touchParams.itemProps) {
  13561. // prepare a change set for the changed items
  13562. var changes = [],
  13563. me = this,
  13564. dataset = this.itemsData.getDataSet();
  13565. var itemProps = this.touchParams.itemProps ;
  13566. this.touchParams.itemProps = null;
  13567. itemProps.forEach(function (props) {
  13568. var id = props.item.id,
  13569. itemData = me.itemsData.get(id, me.itemOptions);
  13570. var changed = false;
  13571. if ('start' in props.item.data) {
  13572. changed = (props.start != props.item.data.start.valueOf());
  13573. itemData.start = util.convert(props.item.data.start,
  13574. dataset._options.type && dataset._options.type.start || 'Date');
  13575. }
  13576. if ('end' in props.item.data) {
  13577. changed = changed || (props.end != props.item.data.end.valueOf());
  13578. itemData.end = util.convert(props.item.data.end,
  13579. dataset._options.type && dataset._options.type.end || 'Date');
  13580. }
  13581. if ('group' in props.item.data) {
  13582. changed = changed || (props.group != props.item.data.group);
  13583. itemData.group = props.item.data.group;
  13584. }
  13585. // only apply changes when start or end is actually changed
  13586. if (changed) {
  13587. me.options.onMove(itemData, function (itemData) {
  13588. if (itemData) {
  13589. // apply changes
  13590. itemData[dataset._fieldId] = id; // ensure the item contains its id (can be undefined)
  13591. changes.push(itemData);
  13592. }
  13593. else {
  13594. // restore original values
  13595. me._updateItemProps(props.item, props);
  13596. me.stackDirty = true; // force re-stacking of all items next redraw
  13597. me.body.emitter.emit('change');
  13598. }
  13599. });
  13600. }
  13601. });
  13602. // apply the changes to the data (if there are changes)
  13603. if (changes.length) {
  13604. dataset.update(changes);
  13605. }
  13606. event.stopPropagation();
  13607. }
  13608. };
  13609. /**
  13610. * Handle selecting/deselecting an item when tapping it
  13611. * @param {Event} event
  13612. * @private
  13613. */
  13614. ItemSet.prototype._onSelectItem = function (event) {
  13615. if (!this.options.selectable) return;
  13616. var ctrlKey = event.gesture.srcEvent && event.gesture.srcEvent.ctrlKey;
  13617. var shiftKey = event.gesture.srcEvent && event.gesture.srcEvent.shiftKey;
  13618. if (ctrlKey || shiftKey) {
  13619. this._onMultiSelectItem(event);
  13620. return;
  13621. }
  13622. var oldSelection = this.getSelection();
  13623. var item = ItemSet.itemFromTarget(event);
  13624. var selection = item ? [item.id] : [];
  13625. this.setSelection(selection);
  13626. var newSelection = this.getSelection();
  13627. // emit a select event,
  13628. // except when old selection is empty and new selection is still empty
  13629. if (newSelection.length > 0 || oldSelection.length > 0) {
  13630. this.body.emitter.emit('select', {
  13631. items: newSelection
  13632. });
  13633. }
  13634. };
  13635. /**
  13636. * Handle creation and updates of an item on double tap
  13637. * @param event
  13638. * @private
  13639. */
  13640. ItemSet.prototype._onAddItem = function (event) {
  13641. if (!this.options.selectable) return;
  13642. if (!this.options.editable.add) return;
  13643. var me = this,
  13644. snap = this.body.util.snap || null,
  13645. item = ItemSet.itemFromTarget(event);
  13646. if (item) {
  13647. // update item
  13648. // execute async handler to update the item (or cancel it)
  13649. var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset
  13650. this.options.onUpdate(itemData, function (itemData) {
  13651. if (itemData) {
  13652. me.itemsData.getDataSet().update(itemData);
  13653. }
  13654. });
  13655. }
  13656. else {
  13657. // add item
  13658. var xAbs = util.getAbsoluteLeft(this.dom.frame);
  13659. var x = event.gesture.center.pageX - xAbs;
  13660. var start = this.body.util.toTime(x);
  13661. var newItem = {
  13662. start: snap ? snap(start) : start,
  13663. content: 'new item'
  13664. };
  13665. // when default type is a range, add a default end date to the new item
  13666. if (this.options.type === 'range') {
  13667. var end = this.body.util.toTime(x + this.props.width / 5);
  13668. newItem.end = snap ? snap(end) : end;
  13669. }
  13670. newItem[this.itemsData._fieldId] = util.randomUUID();
  13671. var group = ItemSet.groupFromTarget(event);
  13672. if (group) {
  13673. newItem.group = group.groupId;
  13674. }
  13675. // execute async handler to customize (or cancel) adding an item
  13676. this.options.onAdd(newItem, function (item) {
  13677. if (item) {
  13678. me.itemsData.getDataSet().add(item);
  13679. // TODO: need to trigger a redraw?
  13680. }
  13681. });
  13682. }
  13683. };
  13684. /**
  13685. * Handle selecting/deselecting multiple items when holding an item
  13686. * @param {Event} event
  13687. * @private
  13688. */
  13689. ItemSet.prototype._onMultiSelectItem = function (event) {
  13690. if (!this.options.selectable) return;
  13691. var selection,
  13692. item = ItemSet.itemFromTarget(event);
  13693. if (item) {
  13694. // multi select items
  13695. selection = this.getSelection(); // current selection
  13696. var shiftKey = event.gesture.touches[0] && event.gesture.touches[0].shiftKey || false;
  13697. if (shiftKey) {
  13698. // select all items between the old selection and the tapped item
  13699. // determine the selection range
  13700. selection.push(item.id);
  13701. var range = ItemSet._getItemRange(this.itemsData.get(selection, this.itemOptions));
  13702. // select all items within the selection range
  13703. selection = [];
  13704. for (var id in this.items) {
  13705. if (this.items.hasOwnProperty(id)) {
  13706. var _item = this.items[id];
  13707. var start = _item.data.start;
  13708. var end = (_item.data.end !== undefined) ? _item.data.end : start;
  13709. if (start >= range.min && end <= range.max) {
  13710. selection.push(_item.id); // do not use id but item.id, id itself is stringified
  13711. }
  13712. }
  13713. }
  13714. }
  13715. else {
  13716. // add/remove this item from the current selection
  13717. var index = selection.indexOf(item.id);
  13718. if (index == -1) {
  13719. // item is not yet selected -> select it
  13720. selection.push(item.id);
  13721. }
  13722. else {
  13723. // item is already selected -> deselect it
  13724. selection.splice(index, 1);
  13725. }
  13726. }
  13727. this.setSelection(selection);
  13728. this.body.emitter.emit('select', {
  13729. items: this.getSelection()
  13730. });
  13731. }
  13732. };
  13733. /**
  13734. * Calculate the time range of a list of items
  13735. * @param {Array.<Object>} itemsData
  13736. * @return {{min: Date, max: Date}} Returns the range of the provided items
  13737. * @private
  13738. */
  13739. ItemSet._getItemRange = function(itemsData) {
  13740. var max = null;
  13741. var min = null;
  13742. itemsData.forEach(function (data) {
  13743. if (min == null || data.start < min) {
  13744. min = data.start;
  13745. }
  13746. if (data.end != undefined) {
  13747. if (max == null || data.end > max) {
  13748. max = data.end;
  13749. }
  13750. }
  13751. else {
  13752. if (max == null || data.start > max) {
  13753. max = data.start;
  13754. }
  13755. }
  13756. });
  13757. return {
  13758. min: min,
  13759. max: max
  13760. }
  13761. };
  13762. /**
  13763. * Find an item from an event target:
  13764. * searches for the attribute 'timeline-item' in the event target's element tree
  13765. * @param {Event} event
  13766. * @return {Item | null} item
  13767. */
  13768. ItemSet.itemFromTarget = function(event) {
  13769. var target = event.target;
  13770. while (target) {
  13771. if (target.hasOwnProperty('timeline-item')) {
  13772. return target['timeline-item'];
  13773. }
  13774. target = target.parentNode;
  13775. }
  13776. return null;
  13777. };
  13778. /**
  13779. * Find the Group from an event target:
  13780. * searches for the attribute 'timeline-group' in the event target's element tree
  13781. * @param {Event} event
  13782. * @return {Group | null} group
  13783. */
  13784. ItemSet.groupFromTarget = function(event) {
  13785. var target = event.target;
  13786. while (target) {
  13787. if (target.hasOwnProperty('timeline-group')) {
  13788. return target['timeline-group'];
  13789. }
  13790. target = target.parentNode;
  13791. }
  13792. return null;
  13793. };
  13794. /**
  13795. * Find the ItemSet from an event target:
  13796. * searches for the attribute 'timeline-itemset' in the event target's element tree
  13797. * @param {Event} event
  13798. * @return {ItemSet | null} item
  13799. */
  13800. ItemSet.itemSetFromTarget = function(event) {
  13801. var target = event.target;
  13802. while (target) {
  13803. if (target.hasOwnProperty('timeline-itemset')) {
  13804. return target['timeline-itemset'];
  13805. }
  13806. target = target.parentNode;
  13807. }
  13808. return null;
  13809. };
  13810. module.exports = ItemSet;
  13811. /***/ },
  13812. /* 27 */
  13813. /***/ function(module, exports, __webpack_require__) {
  13814. var util = __webpack_require__(1);
  13815. var stack = __webpack_require__(28);
  13816. var RangeItem = __webpack_require__(29);
  13817. /**
  13818. * @constructor Group
  13819. * @param {Number | String} groupId
  13820. * @param {Object} data
  13821. * @param {ItemSet} itemSet
  13822. */
  13823. function Group (groupId, data, itemSet) {
  13824. this.groupId = groupId;
  13825. this.subgroups = {};
  13826. this.subgroupIndex = 0;
  13827. this.subgroupOrderer = data && data.subgroupOrder;
  13828. this.itemSet = itemSet;
  13829. this.dom = {};
  13830. this.props = {
  13831. label: {
  13832. width: 0,
  13833. height: 0
  13834. }
  13835. };
  13836. this.className = null;
  13837. this.items = {}; // items filtered by groupId of this group
  13838. this.visibleItems = []; // items currently visible in window
  13839. this.orderedItems = {
  13840. byStart: [],
  13841. byEnd: []
  13842. };
  13843. this.checkRangedItems = false; // needed to refresh the ranged items if the window is programatically changed with NO overlap.
  13844. var me = this;
  13845. this.itemSet.body.emitter.on("checkRangedItems", function () {
  13846. me.checkRangedItems = true;
  13847. })
  13848. this._create();
  13849. this.setData(data);
  13850. }
  13851. /**
  13852. * Create DOM elements for the group
  13853. * @private
  13854. */
  13855. Group.prototype._create = function() {
  13856. var label = document.createElement('div');
  13857. label.className = 'vlabel';
  13858. this.dom.label = label;
  13859. var inner = document.createElement('div');
  13860. inner.className = 'inner';
  13861. label.appendChild(inner);
  13862. this.dom.inner = inner;
  13863. var foreground = document.createElement('div');
  13864. foreground.className = 'group';
  13865. foreground['timeline-group'] = this;
  13866. this.dom.foreground = foreground;
  13867. this.dom.background = document.createElement('div');
  13868. this.dom.background.className = 'group';
  13869. this.dom.axis = document.createElement('div');
  13870. this.dom.axis.className = 'group';
  13871. // create a hidden marker to detect when the Timelines container is attached
  13872. // to the DOM, or the style of a parent of the Timeline is changed from
  13873. // display:none is changed to visible.
  13874. this.dom.marker = document.createElement('div');
  13875. this.dom.marker.style.visibility = 'hidden'; // TODO: ask jos why this is not none?
  13876. this.dom.marker.innerHTML = '?';
  13877. this.dom.background.appendChild(this.dom.marker);
  13878. };
  13879. /**
  13880. * Set the group data for this group
  13881. * @param {Object} data Group data, can contain properties content and className
  13882. */
  13883. Group.prototype.setData = function(data) {
  13884. // update contents
  13885. var content = data && data.content;
  13886. if (content instanceof Element) {
  13887. this.dom.inner.appendChild(content);
  13888. }
  13889. else if (content !== undefined && content !== null) {
  13890. this.dom.inner.innerHTML = content;
  13891. }
  13892. else {
  13893. this.dom.inner.innerHTML = this.groupId || ''; // groupId can be null
  13894. }
  13895. // update title
  13896. this.dom.label.title = data && data.title || '';
  13897. if (!this.dom.inner.firstChild) {
  13898. util.addClassName(this.dom.inner, 'hidden');
  13899. }
  13900. else {
  13901. util.removeClassName(this.dom.inner, 'hidden');
  13902. }
  13903. // update className
  13904. var className = data && data.className || null;
  13905. if (className != this.className) {
  13906. if (this.className) {
  13907. util.removeClassName(this.dom.label, this.className);
  13908. util.removeClassName(this.dom.foreground, this.className);
  13909. util.removeClassName(this.dom.background, this.className);
  13910. util.removeClassName(this.dom.axis, this.className);
  13911. }
  13912. util.addClassName(this.dom.label, className);
  13913. util.addClassName(this.dom.foreground, className);
  13914. util.addClassName(this.dom.background, className);
  13915. util.addClassName(this.dom.axis, className);
  13916. this.className = className;
  13917. }
  13918. // update style
  13919. if (this.style) {
  13920. util.removeCssText(this.dom.label, this.style);
  13921. this.style = null;
  13922. }
  13923. if (data && data.style) {
  13924. util.addCssText(this.dom.label, data.style);
  13925. this.style = data.style;
  13926. }
  13927. };
  13928. /**
  13929. * Get the width of the group label
  13930. * @return {number} width
  13931. */
  13932. Group.prototype.getLabelWidth = function() {
  13933. return this.props.label.width;
  13934. };
  13935. /**
  13936. * Repaint this group
  13937. * @param {{start: number, end: number}} range
  13938. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  13939. * @param {boolean} [restack=false] Force restacking of all items
  13940. * @return {boolean} Returns true if the group is resized
  13941. */
  13942. Group.prototype.redraw = function(range, margin, restack) {
  13943. var resized = false;
  13944. this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
  13945. // force recalculation of the height of the items when the marker height changed
  13946. // (due to the Timeline being attached to the DOM or changed from display:none to visible)
  13947. var markerHeight = this.dom.marker.clientHeight;
  13948. if (markerHeight != this.lastMarkerHeight) {
  13949. this.lastMarkerHeight = markerHeight;
  13950. util.forEach(this.items, function (item) {
  13951. item.dirty = true;
  13952. if (item.displayed) item.redraw();
  13953. });
  13954. restack = true;
  13955. }
  13956. // reposition visible items vertically
  13957. if (this.itemSet.options.stack) { // TODO: ugly way to access options...
  13958. stack.stack(this.visibleItems, margin, restack);
  13959. }
  13960. else { // no stacking
  13961. stack.nostack(this.visibleItems, margin, this.subgroups);
  13962. }
  13963. // recalculate the height of the group
  13964. var height = this._calculateHeight(margin);
  13965. // calculate actual size and position
  13966. var foreground = this.dom.foreground;
  13967. this.top = foreground.offsetTop;
  13968. this.left = foreground.offsetLeft;
  13969. this.width = foreground.offsetWidth;
  13970. resized = util.updateProperty(this, 'height', height) || resized;
  13971. // recalculate size of label
  13972. resized = util.updateProperty(this.props.label, 'width', this.dom.inner.clientWidth) || resized;
  13973. resized = util.updateProperty(this.props.label, 'height', this.dom.inner.clientHeight) || resized;
  13974. // apply new height
  13975. this.dom.background.style.height = height + 'px';
  13976. this.dom.foreground.style.height = height + 'px';
  13977. this.dom.label.style.height = height + 'px';
  13978. // update vertical position of items after they are re-stacked and the height of the group is calculated
  13979. for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
  13980. var item = this.visibleItems[i];
  13981. item.repositionY(margin);
  13982. }
  13983. return resized;
  13984. };
  13985. /**
  13986. * recalculate the height of the group
  13987. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  13988. * @returns {number} Returns the height
  13989. * @private
  13990. */
  13991. Group.prototype._calculateHeight = function (margin) {
  13992. // recalculate the height of the group
  13993. var height;
  13994. var visibleItems = this.visibleItems;
  13995. //var visibleSubgroups = [];
  13996. //this.visibleSubgroups = 0;
  13997. this.resetSubgroups();
  13998. var me = this;
  13999. if (visibleItems.length) {
  14000. var min = visibleItems[0].top;
  14001. var max = visibleItems[0].top + visibleItems[0].height;
  14002. util.forEach(visibleItems, function (item) {
  14003. min = Math.min(min, item.top);
  14004. max = Math.max(max, (item.top + item.height));
  14005. if (item.data.subgroup !== undefined) {
  14006. me.subgroups[item.data.subgroup].height = Math.max(me.subgroups[item.data.subgroup].height,item.height);
  14007. me.subgroups[item.data.subgroup].visible = true;
  14008. //if (visibleSubgroups.indexOf(item.data.subgroup) == -1){
  14009. // visibleSubgroups.push(item.data.subgroup);
  14010. // me.visibleSubgroups += 1;
  14011. //}
  14012. }
  14013. });
  14014. if (min > margin.axis) {
  14015. // there is an empty gap between the lowest item and the axis
  14016. var offset = min - margin.axis;
  14017. max -= offset;
  14018. util.forEach(visibleItems, function (item) {
  14019. item.top -= offset;
  14020. });
  14021. }
  14022. height = max + margin.item.vertical / 2;
  14023. }
  14024. else {
  14025. height = margin.axis + margin.item.vertical;
  14026. }
  14027. height = Math.max(height, this.props.label.height);
  14028. return height;
  14029. };
  14030. /**
  14031. * Show this group: attach to the DOM
  14032. */
  14033. Group.prototype.show = function() {
  14034. if (!this.dom.label.parentNode) {
  14035. this.itemSet.dom.labelSet.appendChild(this.dom.label);
  14036. }
  14037. if (!this.dom.foreground.parentNode) {
  14038. this.itemSet.dom.foreground.appendChild(this.dom.foreground);
  14039. }
  14040. if (!this.dom.background.parentNode) {
  14041. this.itemSet.dom.background.appendChild(this.dom.background);
  14042. }
  14043. if (!this.dom.axis.parentNode) {
  14044. this.itemSet.dom.axis.appendChild(this.dom.axis);
  14045. }
  14046. };
  14047. /**
  14048. * Hide this group: remove from the DOM
  14049. */
  14050. Group.prototype.hide = function() {
  14051. var label = this.dom.label;
  14052. if (label.parentNode) {
  14053. label.parentNode.removeChild(label);
  14054. }
  14055. var foreground = this.dom.foreground;
  14056. if (foreground.parentNode) {
  14057. foreground.parentNode.removeChild(foreground);
  14058. }
  14059. var background = this.dom.background;
  14060. if (background.parentNode) {
  14061. background.parentNode.removeChild(background);
  14062. }
  14063. var axis = this.dom.axis;
  14064. if (axis.parentNode) {
  14065. axis.parentNode.removeChild(axis);
  14066. }
  14067. };
  14068. /**
  14069. * Add an item to the group
  14070. * @param {Item} item
  14071. */
  14072. Group.prototype.add = function(item) {
  14073. this.items[item.id] = item;
  14074. item.setParent(this);
  14075. // add to
  14076. if (item.data.subgroup !== undefined) {
  14077. if (this.subgroups[item.data.subgroup] === undefined) {
  14078. this.subgroups[item.data.subgroup] = {height:0, visible: false, index:this.subgroupIndex, items: []};
  14079. this.subgroupIndex++;
  14080. }
  14081. this.subgroups[item.data.subgroup].items.push(item);
  14082. }
  14083. this.orderSubgroups();
  14084. if (this.visibleItems.indexOf(item) == -1) {
  14085. var range = this.itemSet.body.range; // TODO: not nice accessing the range like this
  14086. this._checkIfVisible(item, this.visibleItems, range);
  14087. }
  14088. };
  14089. Group.prototype.orderSubgroups = function() {
  14090. if (this.subgroupOrderer !== undefined) {
  14091. var sortArray = [];
  14092. if (typeof this.subgroupOrderer == 'string') {
  14093. for (var subgroup in this.subgroups) {
  14094. sortArray.push({subgroup: subgroup, sortField: this.subgroups[subgroup].items[0].data[this.subgroupOrderer]})
  14095. }
  14096. sortArray.sort(function (a, b) {
  14097. return a.sortField - b.sortField;
  14098. })
  14099. }
  14100. else if (typeof this.subgroupOrderer == 'function') {
  14101. for (var subgroup in this.subgroups) {
  14102. sortArray.push(this.subgroups[subgroup].items[0].data);
  14103. }
  14104. sortArray.sort(this.subgroupOrderer);
  14105. }
  14106. if (sortArray.length > 0) {
  14107. for (var i = 0; i < sortArray.length; i++) {
  14108. this.subgroups[sortArray[i].subgroup].index = i;
  14109. }
  14110. }
  14111. }
  14112. };
  14113. Group.prototype.resetSubgroups = function() {
  14114. for (var subgroup in this.subgroups) {
  14115. if (this.subgroups.hasOwnProperty(subgroup)) {
  14116. this.subgroups[subgroup].visible = false;
  14117. }
  14118. }
  14119. };
  14120. /**
  14121. * Remove an item from the group
  14122. * @param {Item} item
  14123. */
  14124. Group.prototype.remove = function(item) {
  14125. delete this.items[item.id];
  14126. item.setParent(null);
  14127. // remove from visible items
  14128. var index = this.visibleItems.indexOf(item);
  14129. if (index != -1) this.visibleItems.splice(index, 1);
  14130. // TODO: also remove from ordered items?
  14131. };
  14132. /**
  14133. * Remove an item from the corresponding DataSet
  14134. * @param {Item} item
  14135. */
  14136. Group.prototype.removeFromDataSet = function(item) {
  14137. this.itemSet.removeItem(item.id);
  14138. };
  14139. /**
  14140. * Reorder the items
  14141. */
  14142. Group.prototype.order = function() {
  14143. var array = util.toArray(this.items);
  14144. var startArray = [];
  14145. var endArray = [];
  14146. for (var i = 0; i < array.length; i++) {
  14147. if (array[i].data.end !== undefined) {
  14148. endArray.push(array[i]);
  14149. }
  14150. startArray.push(array[i]);
  14151. }
  14152. this.orderedItems = {
  14153. byStart: startArray,
  14154. byEnd: endArray
  14155. };
  14156. stack.orderByStart(this.orderedItems.byStart);
  14157. stack.orderByEnd(this.orderedItems.byEnd);
  14158. };
  14159. /**
  14160. * Update the visible items
  14161. * @param {{byStart: Item[], byEnd: Item[]}} orderedItems All items ordered by start date and by end date
  14162. * @param {Item[]} visibleItems The previously visible items.
  14163. * @param {{start: number, end: number}} range Visible range
  14164. * @return {Item[]} visibleItems The new visible items.
  14165. * @private
  14166. */
  14167. Group.prototype._updateVisibleItems = function(orderedItems, oldVisibleItems, range) {
  14168. var visibleItems = [];
  14169. var visibleItemsLookup = {}; // we keep this to quickly look up if an item already exists in the list without using indexOf on visibleItems
  14170. var interval = (range.end - range.start) / 4;
  14171. var lowerBound = range.start - interval;
  14172. var upperBound = range.end + interval;
  14173. var item, i;
  14174. // this function is used to do the binary search.
  14175. var searchFunction = function (value) {
  14176. if (value < lowerBound) {return -1;}
  14177. else if (value <= upperBound) {return 0;}
  14178. else {return 1;}
  14179. }
  14180. // first check if the items that were in view previously are still in view.
  14181. // IMPORTANT: this handles the case for the items with startdate before the window and enddate after the window!
  14182. // also cleans up invisible items.
  14183. if (oldVisibleItems.length > 0) {
  14184. for (i = 0; i < oldVisibleItems.length; i++) {
  14185. this._checkIfVisibleWithReference(oldVisibleItems[i], visibleItems, visibleItemsLookup, range);
  14186. }
  14187. }
  14188. // we do a binary search for the items that have only start values.
  14189. var initialPosByStart = util.binarySearchCustom(orderedItems.byStart, searchFunction, 'data','start');
  14190. // trace the visible items from the inital start pos both ways until an invisible item is found, we only look at the start values.
  14191. this._traceVisible(initialPosByStart, orderedItems.byStart, visibleItems, visibleItemsLookup, function (item) {
  14192. return (item.data.start < lowerBound || item.data.start > upperBound);
  14193. });
  14194. // if the window has changed programmatically without overlapping the old window, the ranged items with start < lowerBound and end > upperbound are not shown.
  14195. // We therefore have to brute force check all items in the byEnd list
  14196. if (this.checkRangedItems == true) {
  14197. this.checkRangedItems = false;
  14198. for (i = 0; i < orderedItems.byEnd.length; i++) {
  14199. this._checkIfVisibleWithReference(orderedItems.byEnd[i], visibleItems, visibleItemsLookup, range);
  14200. }
  14201. }
  14202. else {
  14203. // we do a binary search for the items that have defined end times.
  14204. var initialPosByEnd = util.binarySearchCustom(orderedItems.byEnd, searchFunction, 'data','end');
  14205. // trace the visible items from the inital start pos both ways until an invisible item is found, we only look at the end values.
  14206. this._traceVisible(initialPosByEnd, orderedItems.byEnd, visibleItems, visibleItemsLookup, function (item) {
  14207. return (item.data.end < lowerBound || item.data.end > upperBound);
  14208. });
  14209. }
  14210. // finally, we reposition all the visible items.
  14211. for (i = 0; i < visibleItems.length; i++) {
  14212. item = visibleItems[i];
  14213. if (!item.displayed) item.show();
  14214. // reposition item horizontally
  14215. item.repositionX();
  14216. }
  14217. // debug
  14218. //console.log("new line")
  14219. //if (this.groupId == null) {
  14220. // for (i = 0; i < orderedItems.byStart.length; i++) {
  14221. // item = orderedItems.byStart[i].data;
  14222. // console.log('start',i,initialPosByStart, item.start.valueOf(), item.content, item.start >= lowerBound && item.start <= upperBound,i == initialPosByStart ? "<------------------- HEREEEE" : "")
  14223. // }
  14224. // for (i = 0; i < orderedItems.byEnd.length; i++) {
  14225. // item = orderedItems.byEnd[i].data;
  14226. // console.log('rangeEnd',i,initialPosByEnd, item.end.valueOf(), item.content, item.end >= range.start && item.end <= range.end,i == initialPosByEnd ? "<------------------- HEREEEE" : "")
  14227. // }
  14228. //}
  14229. return visibleItems;
  14230. };
  14231. Group.prototype._traceVisible = function (initialPos, items, visibleItems, visibleItemsLookup, breakCondition) {
  14232. var item;
  14233. var i;
  14234. if (initialPos != -1) {
  14235. for (i = initialPos; i >= 0; i--) {
  14236. item = items[i];
  14237. if (breakCondition(item)) {
  14238. break;
  14239. }
  14240. else {
  14241. if (visibleItemsLookup[item.id] === undefined) {
  14242. visibleItemsLookup[item.id] = true;
  14243. visibleItems.push(item);
  14244. }
  14245. }
  14246. }
  14247. for (i = initialPos + 1; i < items.length; i++) {
  14248. item = items[i];
  14249. if (breakCondition(item)) {
  14250. break;
  14251. }
  14252. else {
  14253. if (visibleItemsLookup[item.id] === undefined) {
  14254. visibleItemsLookup[item.id] = true;
  14255. visibleItems.push(item);
  14256. }
  14257. }
  14258. }
  14259. }
  14260. }
  14261. /**
  14262. * this function is very similar to the _checkIfInvisible() but it does not
  14263. * return booleans, hides the item if it should not be seen and always adds to
  14264. * the visibleItems.
  14265. * this one is for brute forcing and hiding.
  14266. *
  14267. * @param {Item} item
  14268. * @param {Array} visibleItems
  14269. * @param {{start:number, end:number}} range
  14270. * @private
  14271. */
  14272. Group.prototype._checkIfVisible = function(item, visibleItems, range) {
  14273. if (item.isVisible(range)) {
  14274. if (!item.displayed) item.show();
  14275. // reposition item horizontally
  14276. item.repositionX();
  14277. visibleItems.push(item);
  14278. }
  14279. else {
  14280. if (item.displayed) item.hide();
  14281. }
  14282. };
  14283. /**
  14284. * this function is very similar to the _checkIfInvisible() but it does not
  14285. * return booleans, hides the item if it should not be seen and always adds to
  14286. * the visibleItems.
  14287. * this one is for brute forcing and hiding.
  14288. *
  14289. * @param {Item} item
  14290. * @param {Array} visibleItems
  14291. * @param {{start:number, end:number}} range
  14292. * @private
  14293. */
  14294. Group.prototype._checkIfVisibleWithReference = function(item, visibleItems, visibleItemsLookup, range) {
  14295. if (item.isVisible(range)) {
  14296. if (visibleItemsLookup[item.id] === undefined) {
  14297. visibleItemsLookup[item.id] = true;
  14298. visibleItems.push(item);
  14299. }
  14300. }
  14301. else {
  14302. if (item.displayed) item.hide();
  14303. }
  14304. };
  14305. module.exports = Group;
  14306. /***/ },
  14307. /* 28 */
  14308. /***/ function(module, exports, __webpack_require__) {
  14309. // Utility functions for ordering and stacking of items
  14310. var EPSILON = 0.001; // used when checking collisions, to prevent round-off errors
  14311. /**
  14312. * Order items by their start data
  14313. * @param {Item[]} items
  14314. */
  14315. exports.orderByStart = function(items) {
  14316. items.sort(function (a, b) {
  14317. return a.data.start - b.data.start;
  14318. });
  14319. };
  14320. /**
  14321. * Order items by their end date. If they have no end date, their start date
  14322. * is used.
  14323. * @param {Item[]} items
  14324. */
  14325. exports.orderByEnd = function(items) {
  14326. items.sort(function (a, b) {
  14327. var aTime = ('end' in a.data) ? a.data.end : a.data.start,
  14328. bTime = ('end' in b.data) ? b.data.end : b.data.start;
  14329. return aTime - bTime;
  14330. });
  14331. };
  14332. /**
  14333. * Adjust vertical positions of the items such that they don't overlap each
  14334. * other.
  14335. * @param {Item[]} items
  14336. * All visible items
  14337. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  14338. * Margins between items and between items and the axis.
  14339. * @param {boolean} [force=false]
  14340. * If true, all items will be repositioned. If false (default), only
  14341. * items having a top===null will be re-stacked
  14342. */
  14343. exports.stack = function(items, margin, force) {
  14344. var i, iMax;
  14345. if (force) {
  14346. // reset top position of all items
  14347. for (i = 0, iMax = items.length; i < iMax; i++) {
  14348. items[i].top = null;
  14349. }
  14350. }
  14351. // calculate new, non-overlapping positions
  14352. for (i = 0, iMax = items.length; i < iMax; i++) {
  14353. var item = items[i];
  14354. if (item.stack && item.top === null) {
  14355. // initialize top position
  14356. item.top = margin.axis;
  14357. do {
  14358. // TODO: optimize checking for overlap. when there is a gap without items,
  14359. // you only need to check for items from the next item on, not from zero
  14360. var collidingItem = null;
  14361. for (var j = 0, jj = items.length; j < jj; j++) {
  14362. var other = items[j];
  14363. if (other.top !== null && other !== item && other.stack && exports.collision(item, other, margin.item)) {
  14364. collidingItem = other;
  14365. break;
  14366. }
  14367. }
  14368. if (collidingItem != null) {
  14369. // There is a collision. Reposition the items above the colliding element
  14370. item.top = collidingItem.top + collidingItem.height + margin.item.vertical;
  14371. }
  14372. } while (collidingItem);
  14373. }
  14374. }
  14375. };
  14376. /**
  14377. * Adjust vertical positions of the items without stacking them
  14378. * @param {Item[]} items
  14379. * All visible items
  14380. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  14381. * Margins between items and between items and the axis.
  14382. */
  14383. exports.nostack = function(items, margin, subgroups) {
  14384. var i, iMax, newTop;
  14385. // reset top position of all items
  14386. for (i = 0, iMax = items.length; i < iMax; i++) {
  14387. if (items[i].data.subgroup !== undefined) {
  14388. newTop = margin.axis;
  14389. for (var subgroup in subgroups) {
  14390. if (subgroups.hasOwnProperty(subgroup)) {
  14391. if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroups[items[i].data.subgroup].index) {
  14392. newTop += subgroups[subgroup].height + margin.item.vertical;
  14393. }
  14394. }
  14395. }
  14396. items[i].top = newTop;
  14397. }
  14398. else {
  14399. items[i].top = margin.axis;
  14400. }
  14401. }
  14402. };
  14403. /**
  14404. * Test if the two provided items collide
  14405. * The items must have parameters left, width, top, and height.
  14406. * @param {Item} a The first item
  14407. * @param {Item} b The second item
  14408. * @param {{horizontal: number, vertical: number}} margin
  14409. * An object containing a horizontal and vertical
  14410. * minimum required margin.
  14411. * @return {boolean} true if a and b collide, else false
  14412. */
  14413. exports.collision = function(a, b, margin) {
  14414. return ((a.left - margin.horizontal + EPSILON) < (b.left + b.width) &&
  14415. (a.left + a.width + margin.horizontal - EPSILON) > b.left &&
  14416. (a.top - margin.vertical + EPSILON) < (b.top + b.height) &&
  14417. (a.top + a.height + margin.vertical - EPSILON) > b.top);
  14418. };
  14419. /***/ },
  14420. /* 29 */
  14421. /***/ function(module, exports, __webpack_require__) {
  14422. var Hammer = __webpack_require__(19);
  14423. var Item = __webpack_require__(30);
  14424. /**
  14425. * @constructor RangeItem
  14426. * @extends Item
  14427. * @param {Object} data Object containing parameters start, end
  14428. * content, className.
  14429. * @param {{toScreen: function, toTime: function}} conversion
  14430. * Conversion functions from time to screen and vice versa
  14431. * @param {Object} [options] Configuration options
  14432. * // TODO: describe options
  14433. */
  14434. function RangeItem (data, conversion, options) {
  14435. this.props = {
  14436. content: {
  14437. width: 0
  14438. }
  14439. };
  14440. this.overflow = false; // if contents can overflow (css styling), this flag is set to true
  14441. // validate data
  14442. if (data) {
  14443. if (data.start == undefined) {
  14444. throw new Error('Property "start" missing in item ' + data.id);
  14445. }
  14446. if (data.end == undefined) {
  14447. throw new Error('Property "end" missing in item ' + data.id);
  14448. }
  14449. }
  14450. Item.call(this, data, conversion, options);
  14451. }
  14452. RangeItem.prototype = new Item (null, null, null);
  14453. RangeItem.prototype.baseClassName = 'item range';
  14454. /**
  14455. * Check whether this item is visible inside given range
  14456. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  14457. * @returns {boolean} True if visible
  14458. */
  14459. RangeItem.prototype.isVisible = function(range) {
  14460. // determine visibility
  14461. return (this.data.start < range.end) && (this.data.end > range.start);
  14462. };
  14463. /**
  14464. * Repaint the item
  14465. */
  14466. RangeItem.prototype.redraw = function() {
  14467. var dom = this.dom;
  14468. if (!dom) {
  14469. // create DOM
  14470. this.dom = {};
  14471. dom = this.dom;
  14472. // background box
  14473. dom.box = document.createElement('div');
  14474. // className is updated in redraw()
  14475. // contents box
  14476. dom.content = document.createElement('div');
  14477. dom.content.className = 'content';
  14478. dom.box.appendChild(dom.content);
  14479. // attach this item as attribute
  14480. dom.box['timeline-item'] = this;
  14481. this.dirty = true;
  14482. }
  14483. // append DOM to parent DOM
  14484. if (!this.parent) {
  14485. throw new Error('Cannot redraw item: no parent attached');
  14486. }
  14487. if (!dom.box.parentNode) {
  14488. var foreground = this.parent.dom.foreground;
  14489. if (!foreground) {
  14490. throw new Error('Cannot redraw item: parent has no foreground container element');
  14491. }
  14492. foreground.appendChild(dom.box);
  14493. }
  14494. this.displayed = true;
  14495. // Update DOM when item is marked dirty. An item is marked dirty when:
  14496. // - the item is not yet rendered
  14497. // - the item's data is changed
  14498. // - the item is selected/deselected
  14499. if (this.dirty) {
  14500. this._updateContents(this.dom.content);
  14501. this._updateTitle(this.dom.box);
  14502. this._updateDataAttributes(this.dom.box);
  14503. this._updateStyle(this.dom.box);
  14504. // update class
  14505. var className = (this.data.className ? (' ' + this.data.className) : '') +
  14506. (this.selected ? ' selected' : '');
  14507. dom.box.className = this.baseClassName + className;
  14508. // determine from css whether this box has overflow
  14509. this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden';
  14510. // recalculate size
  14511. // turn off max-width to be able to calculate the real width
  14512. // this causes an extra browser repaint/reflow, but so be it
  14513. this.dom.content.style.maxWidth = 'none';
  14514. this.props.content.width = this.dom.content.offsetWidth;
  14515. this.height = this.dom.box.offsetHeight;
  14516. this.dom.content.style.maxWidth = '';
  14517. this.dirty = false;
  14518. }
  14519. this._repaintDeleteButton(dom.box);
  14520. this._repaintDragLeft();
  14521. this._repaintDragRight();
  14522. };
  14523. /**
  14524. * Show the item in the DOM (when not already visible). The items DOM will
  14525. * be created when needed.
  14526. */
  14527. RangeItem.prototype.show = function() {
  14528. if (!this.displayed) {
  14529. this.redraw();
  14530. }
  14531. };
  14532. /**
  14533. * Hide the item from the DOM (when visible)
  14534. * @return {Boolean} changed
  14535. */
  14536. RangeItem.prototype.hide = function() {
  14537. if (this.displayed) {
  14538. var box = this.dom.box;
  14539. if (box.parentNode) {
  14540. box.parentNode.removeChild(box);
  14541. }
  14542. this.top = null;
  14543. this.left = null;
  14544. this.displayed = false;
  14545. }
  14546. };
  14547. /**
  14548. * Reposition the item horizontally
  14549. * @Override
  14550. */
  14551. RangeItem.prototype.repositionX = function() {
  14552. var parentWidth = this.parent.width;
  14553. var start = this.conversion.toScreen(this.data.start);
  14554. var end = this.conversion.toScreen(this.data.end);
  14555. var contentLeft;
  14556. var contentWidth;
  14557. // limit the width of the this, as browsers cannot draw very wide divs
  14558. if (start < -parentWidth) {
  14559. start = -parentWidth;
  14560. }
  14561. if (end > 2 * parentWidth) {
  14562. end = 2 * parentWidth;
  14563. }
  14564. var boxWidth = Math.max(end - start, 1);
  14565. if (this.overflow) {
  14566. this.left = start;
  14567. this.width = boxWidth + this.props.content.width;
  14568. contentWidth = this.props.content.width;
  14569. // Note: The calculation of width is an optimistic calculation, giving
  14570. // a width which will not change when moving the Timeline
  14571. // So no re-stacking needed, which is nicer for the eye;
  14572. }
  14573. else {
  14574. this.left = start;
  14575. this.width = boxWidth;
  14576. contentWidth = Math.min(end - start - 2 * this.options.padding, this.props.content.width);
  14577. }
  14578. this.dom.box.style.left = this.left + 'px';
  14579. this.dom.box.style.width = boxWidth + 'px';
  14580. switch (this.options.align) {
  14581. case 'left':
  14582. this.dom.content.style.left = '0';
  14583. break;
  14584. case 'right':
  14585. this.dom.content.style.left = Math.max((boxWidth - contentWidth - 2 * this.options.padding), 0) + 'px';
  14586. break;
  14587. case 'center':
  14588. this.dom.content.style.left = Math.max((boxWidth - contentWidth - 2 * this.options.padding) / 2, 0) + 'px';
  14589. break;
  14590. default: // 'auto'
  14591. // when range exceeds left of the window, position the contents at the left of the visible area
  14592. if (this.overflow) {
  14593. if (end > 0) {
  14594. contentLeft = Math.max(-start, 0);
  14595. }
  14596. else {
  14597. contentLeft = -contentWidth; // ensure it's not visible anymore
  14598. }
  14599. }
  14600. else {
  14601. if (start < 0) {
  14602. contentLeft = Math.min(-start,
  14603. (end - start - contentWidth - 2 * this.options.padding));
  14604. // TODO: remove the need for options.padding. it's terrible.
  14605. }
  14606. else {
  14607. contentLeft = 0;
  14608. }
  14609. }
  14610. this.dom.content.style.left = contentLeft + 'px';
  14611. }
  14612. };
  14613. /**
  14614. * Reposition the item vertically
  14615. * @Override
  14616. */
  14617. RangeItem.prototype.repositionY = function() {
  14618. var orientation = this.options.orientation,
  14619. box = this.dom.box;
  14620. if (orientation == 'top') {
  14621. box.style.top = this.top + 'px';
  14622. }
  14623. else {
  14624. box.style.top = (this.parent.height - this.top - this.height) + 'px';
  14625. }
  14626. };
  14627. /**
  14628. * Repaint a drag area on the left side of the range when the range is selected
  14629. * @protected
  14630. */
  14631. RangeItem.prototype._repaintDragLeft = function () {
  14632. if (this.selected && this.options.editable.updateTime && !this.dom.dragLeft) {
  14633. // create and show drag area
  14634. var dragLeft = document.createElement('div');
  14635. dragLeft.className = 'drag-left';
  14636. dragLeft.dragLeftItem = this;
  14637. // TODO: this should be redundant?
  14638. Hammer(dragLeft, {
  14639. preventDefault: true
  14640. }).on('drag', function () {
  14641. //console.log('drag left')
  14642. });
  14643. this.dom.box.appendChild(dragLeft);
  14644. this.dom.dragLeft = dragLeft;
  14645. }
  14646. else if (!this.selected && this.dom.dragLeft) {
  14647. // delete drag area
  14648. if (this.dom.dragLeft.parentNode) {
  14649. this.dom.dragLeft.parentNode.removeChild(this.dom.dragLeft);
  14650. }
  14651. this.dom.dragLeft = null;
  14652. }
  14653. };
  14654. /**
  14655. * Repaint a drag area on the right side of the range when the range is selected
  14656. * @protected
  14657. */
  14658. RangeItem.prototype._repaintDragRight = function () {
  14659. if (this.selected && this.options.editable.updateTime && !this.dom.dragRight) {
  14660. // create and show drag area
  14661. var dragRight = document.createElement('div');
  14662. dragRight.className = 'drag-right';
  14663. dragRight.dragRightItem = this;
  14664. // TODO: this should be redundant?
  14665. Hammer(dragRight, {
  14666. preventDefault: true
  14667. }).on('drag', function () {
  14668. //console.log('drag right')
  14669. });
  14670. this.dom.box.appendChild(dragRight);
  14671. this.dom.dragRight = dragRight;
  14672. }
  14673. else if (!this.selected && this.dom.dragRight) {
  14674. // delete drag area
  14675. if (this.dom.dragRight.parentNode) {
  14676. this.dom.dragRight.parentNode.removeChild(this.dom.dragRight);
  14677. }
  14678. this.dom.dragRight = null;
  14679. }
  14680. };
  14681. module.exports = RangeItem;
  14682. /***/ },
  14683. /* 30 */
  14684. /***/ function(module, exports, __webpack_require__) {
  14685. var Hammer = __webpack_require__(19);
  14686. var util = __webpack_require__(1);
  14687. /**
  14688. * @constructor Item
  14689. * @param {Object} data Object containing (optional) parameters type,
  14690. * start, end, content, group, className.
  14691. * @param {{toScreen: function, toTime: function}} conversion
  14692. * Conversion functions from time to screen and vice versa
  14693. * @param {Object} options Configuration options
  14694. * // TODO: describe available options
  14695. */
  14696. function Item (data, conversion, options) {
  14697. this.id = null;
  14698. this.parent = null;
  14699. this.data = data;
  14700. this.dom = null;
  14701. this.conversion = conversion || {};
  14702. this.options = options || {};
  14703. this.selected = false;
  14704. this.displayed = false;
  14705. this.dirty = true;
  14706. this.top = null;
  14707. this.left = null;
  14708. this.width = null;
  14709. this.height = null;
  14710. }
  14711. Item.prototype.stack = true;
  14712. /**
  14713. * Select current item
  14714. */
  14715. Item.prototype.select = function() {
  14716. this.selected = true;
  14717. this.dirty = true;
  14718. if (this.displayed) this.redraw();
  14719. };
  14720. /**
  14721. * Unselect current item
  14722. */
  14723. Item.prototype.unselect = function() {
  14724. this.selected = false;
  14725. this.dirty = true;
  14726. if (this.displayed) this.redraw();
  14727. };
  14728. /**
  14729. * Set data for the item. Existing data will be updated. The id should not
  14730. * be changed. When the item is displayed, it will be redrawn immediately.
  14731. * @param {Object} data
  14732. */
  14733. Item.prototype.setData = function(data) {
  14734. this.data = data;
  14735. this.dirty = true;
  14736. if (this.displayed) this.redraw();
  14737. };
  14738. /**
  14739. * Set a parent for the item
  14740. * @param {ItemSet | Group} parent
  14741. */
  14742. Item.prototype.setParent = function(parent) {
  14743. if (this.displayed) {
  14744. this.hide();
  14745. this.parent = parent;
  14746. if (this.parent) {
  14747. this.show();
  14748. }
  14749. }
  14750. else {
  14751. this.parent = parent;
  14752. }
  14753. };
  14754. /**
  14755. * Check whether this item is visible inside given range
  14756. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  14757. * @returns {boolean} True if visible
  14758. */
  14759. Item.prototype.isVisible = function(range) {
  14760. // Should be implemented by Item implementations
  14761. return false;
  14762. };
  14763. /**
  14764. * Show the Item in the DOM (when not already visible)
  14765. * @return {Boolean} changed
  14766. */
  14767. Item.prototype.show = function() {
  14768. return false;
  14769. };
  14770. /**
  14771. * Hide the Item from the DOM (when visible)
  14772. * @return {Boolean} changed
  14773. */
  14774. Item.prototype.hide = function() {
  14775. return false;
  14776. };
  14777. /**
  14778. * Repaint the item
  14779. */
  14780. Item.prototype.redraw = function() {
  14781. // should be implemented by the item
  14782. };
  14783. /**
  14784. * Reposition the Item horizontally
  14785. */
  14786. Item.prototype.repositionX = function() {
  14787. // should be implemented by the item
  14788. };
  14789. /**
  14790. * Reposition the Item vertically
  14791. */
  14792. Item.prototype.repositionY = function() {
  14793. // should be implemented by the item
  14794. };
  14795. /**
  14796. * Repaint a delete button on the top right of the item when the item is selected
  14797. * @param {HTMLElement} anchor
  14798. * @protected
  14799. */
  14800. Item.prototype._repaintDeleteButton = function (anchor) {
  14801. if (this.selected && this.options.editable.remove && !this.dom.deleteButton) {
  14802. // create and show button
  14803. var me = this;
  14804. var deleteButton = document.createElement('div');
  14805. deleteButton.className = 'delete';
  14806. deleteButton.title = 'Delete this item';
  14807. Hammer(deleteButton, {
  14808. preventDefault: true
  14809. }).on('tap', function (event) {
  14810. me.parent.removeFromDataSet(me);
  14811. event.stopPropagation();
  14812. });
  14813. anchor.appendChild(deleteButton);
  14814. this.dom.deleteButton = deleteButton;
  14815. }
  14816. else if (!this.selected && this.dom.deleteButton) {
  14817. // remove button
  14818. if (this.dom.deleteButton.parentNode) {
  14819. this.dom.deleteButton.parentNode.removeChild(this.dom.deleteButton);
  14820. }
  14821. this.dom.deleteButton = null;
  14822. }
  14823. };
  14824. /**
  14825. * Set HTML contents for the item
  14826. * @param {Element} element HTML element to fill with the contents
  14827. * @private
  14828. */
  14829. Item.prototype._updateContents = function (element) {
  14830. var content;
  14831. if (this.options.template) {
  14832. var itemData = this.parent.itemSet.itemsData.get(this.id); // get a clone of the data from the dataset
  14833. content = this.options.template(itemData);
  14834. }
  14835. else {
  14836. content = this.data.content;
  14837. }
  14838. if(content !== this.content) {
  14839. // only replace the content when changed
  14840. if (content instanceof Element) {
  14841. element.innerHTML = '';
  14842. element.appendChild(content);
  14843. }
  14844. else if (content != undefined) {
  14845. element.innerHTML = content;
  14846. }
  14847. else {
  14848. if (!(this.data.type == 'background' && this.data.content === undefined)) {
  14849. throw new Error('Property "content" missing in item ' + this.id);
  14850. }
  14851. }
  14852. this.content = content;
  14853. }
  14854. };
  14855. /**
  14856. * Set HTML contents for the item
  14857. * @param {Element} element HTML element to fill with the contents
  14858. * @private
  14859. */
  14860. Item.prototype._updateTitle = function (element) {
  14861. if (this.data.title != null) {
  14862. element.title = this.data.title || '';
  14863. }
  14864. else {
  14865. element.removeAttribute('title');
  14866. }
  14867. };
  14868. /**
  14869. * Process dataAttributes timeline option and set as data- attributes on dom.content
  14870. * @param {Element} element HTML element to which the attributes will be attached
  14871. * @private
  14872. */
  14873. Item.prototype._updateDataAttributes = function(element) {
  14874. if (this.options.dataAttributes && this.options.dataAttributes.length > 0) {
  14875. var attributes = [];
  14876. if (Array.isArray(this.options.dataAttributes)) {
  14877. attributes = this.options.dataAttributes;
  14878. }
  14879. else if (this.options.dataAttributes == 'all') {
  14880. attributes = Object.keys(this.data);
  14881. }
  14882. else {
  14883. return;
  14884. }
  14885. for (var i = 0; i < attributes.length; i++) {
  14886. var name = attributes[i];
  14887. var value = this.data[name];
  14888. if (value != null) {
  14889. element.setAttribute('data-' + name, value);
  14890. }
  14891. else {
  14892. element.removeAttribute('data-' + name);
  14893. }
  14894. }
  14895. }
  14896. };
  14897. /**
  14898. * Update custom styles of the element
  14899. * @param element
  14900. * @private
  14901. */
  14902. Item.prototype._updateStyle = function(element) {
  14903. // remove old styles
  14904. if (this.style) {
  14905. util.removeCssText(element, this.style);
  14906. this.style = null;
  14907. }
  14908. // append new styles
  14909. if (this.data.style) {
  14910. util.addCssText(element, this.data.style);
  14911. this.style = this.data.style;
  14912. }
  14913. };
  14914. module.exports = Item;
  14915. /***/ },
  14916. /* 31 */
  14917. /***/ function(module, exports, __webpack_require__) {
  14918. var util = __webpack_require__(1);
  14919. var Group = __webpack_require__(27);
  14920. /**
  14921. * @constructor BackgroundGroup
  14922. * @param {Number | String} groupId
  14923. * @param {Object} data
  14924. * @param {ItemSet} itemSet
  14925. */
  14926. function BackgroundGroup (groupId, data, itemSet) {
  14927. Group.call(this, groupId, data, itemSet);
  14928. this.width = 0;
  14929. this.height = 0;
  14930. this.top = 0;
  14931. this.left = 0;
  14932. }
  14933. BackgroundGroup.prototype = Object.create(Group.prototype);
  14934. /**
  14935. * Repaint this group
  14936. * @param {{start: number, end: number}} range
  14937. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  14938. * @param {boolean} [restack=false] Force restacking of all items
  14939. * @return {boolean} Returns true if the group is resized
  14940. */
  14941. BackgroundGroup.prototype.redraw = function(range, margin, restack) {
  14942. var resized = false;
  14943. this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
  14944. // calculate actual size
  14945. this.width = this.dom.background.offsetWidth;
  14946. // apply new height (just always zero for BackgroundGroup
  14947. this.dom.background.style.height = '0';
  14948. // update vertical position of items after they are re-stacked and the height of the group is calculated
  14949. for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
  14950. var item = this.visibleItems[i];
  14951. item.repositionY(margin);
  14952. }
  14953. return resized;
  14954. };
  14955. /**
  14956. * Show this group: attach to the DOM
  14957. */
  14958. BackgroundGroup.prototype.show = function() {
  14959. if (!this.dom.background.parentNode) {
  14960. this.itemSet.dom.background.appendChild(this.dom.background);
  14961. }
  14962. };
  14963. module.exports = BackgroundGroup;
  14964. /***/ },
  14965. /* 32 */
  14966. /***/ function(module, exports, __webpack_require__) {
  14967. var Item = __webpack_require__(30);
  14968. var util = __webpack_require__(1);
  14969. /**
  14970. * @constructor BoxItem
  14971. * @extends Item
  14972. * @param {Object} data Object containing parameters start
  14973. * content, className.
  14974. * @param {{toScreen: function, toTime: function}} conversion
  14975. * Conversion functions from time to screen and vice versa
  14976. * @param {Object} [options] Configuration options
  14977. * // TODO: describe available options
  14978. */
  14979. function BoxItem (data, conversion, options) {
  14980. this.props = {
  14981. dot: {
  14982. width: 0,
  14983. height: 0
  14984. },
  14985. line: {
  14986. width: 0,
  14987. height: 0
  14988. }
  14989. };
  14990. // validate data
  14991. if (data) {
  14992. if (data.start == undefined) {
  14993. throw new Error('Property "start" missing in item ' + data);
  14994. }
  14995. }
  14996. Item.call(this, data, conversion, options);
  14997. }
  14998. BoxItem.prototype = new Item (null, null, null);
  14999. /**
  15000. * Check whether this item is visible inside given range
  15001. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  15002. * @returns {boolean} True if visible
  15003. */
  15004. BoxItem.prototype.isVisible = function(range) {
  15005. // determine visibility
  15006. // TODO: account for the real width of the item. Right now we just add 1/4 to the window
  15007. var interval = (range.end - range.start) / 4;
  15008. return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
  15009. };
  15010. /**
  15011. * Repaint the item
  15012. */
  15013. BoxItem.prototype.redraw = function() {
  15014. var dom = this.dom;
  15015. if (!dom) {
  15016. // create DOM
  15017. this.dom = {};
  15018. dom = this.dom;
  15019. // create main box
  15020. dom.box = document.createElement('DIV');
  15021. // contents box (inside the background box). used for making margins
  15022. dom.content = document.createElement('DIV');
  15023. dom.content.className = 'content';
  15024. dom.box.appendChild(dom.content);
  15025. // line to axis
  15026. dom.line = document.createElement('DIV');
  15027. dom.line.className = 'line';
  15028. // dot on axis
  15029. dom.dot = document.createElement('DIV');
  15030. dom.dot.className = 'dot';
  15031. // attach this item as attribute
  15032. dom.box['timeline-item'] = this;
  15033. this.dirty = true;
  15034. }
  15035. // append DOM to parent DOM
  15036. if (!this.parent) {
  15037. throw new Error('Cannot redraw item: no parent attached');
  15038. }
  15039. if (!dom.box.parentNode) {
  15040. var foreground = this.parent.dom.foreground;
  15041. if (!foreground) throw new Error('Cannot redraw item: parent has no foreground container element');
  15042. foreground.appendChild(dom.box);
  15043. }
  15044. if (!dom.line.parentNode) {
  15045. var background = this.parent.dom.background;
  15046. if (!background) throw new Error('Cannot redraw item: parent has no background container element');
  15047. background.appendChild(dom.line);
  15048. }
  15049. if (!dom.dot.parentNode) {
  15050. var axis = this.parent.dom.axis;
  15051. if (!background) throw new Error('Cannot redraw item: parent has no axis container element');
  15052. axis.appendChild(dom.dot);
  15053. }
  15054. this.displayed = true;
  15055. // Update DOM when item is marked dirty. An item is marked dirty when:
  15056. // - the item is not yet rendered
  15057. // - the item's data is changed
  15058. // - the item is selected/deselected
  15059. if (this.dirty) {
  15060. this._updateContents(this.dom.content);
  15061. this._updateTitle(this.dom.box);
  15062. this._updateDataAttributes(this.dom.box);
  15063. this._updateStyle(this.dom.box);
  15064. // update class
  15065. var className = (this.data.className? ' ' + this.data.className : '') +
  15066. (this.selected ? ' selected' : '');
  15067. dom.box.className = 'item box' + className;
  15068. dom.line.className = 'item line' + className;
  15069. dom.dot.className = 'item dot' + className;
  15070. // recalculate size
  15071. this.props.dot.height = dom.dot.offsetHeight;
  15072. this.props.dot.width = dom.dot.offsetWidth;
  15073. this.props.line.width = dom.line.offsetWidth;
  15074. this.width = dom.box.offsetWidth;
  15075. this.height = dom.box.offsetHeight;
  15076. this.dirty = false;
  15077. }
  15078. this._repaintDeleteButton(dom.box);
  15079. };
  15080. /**
  15081. * Show the item in the DOM (when not already displayed). The items DOM will
  15082. * be created when needed.
  15083. */
  15084. BoxItem.prototype.show = function() {
  15085. if (!this.displayed) {
  15086. this.redraw();
  15087. }
  15088. };
  15089. /**
  15090. * Hide the item from the DOM (when visible)
  15091. */
  15092. BoxItem.prototype.hide = function() {
  15093. if (this.displayed) {
  15094. var dom = this.dom;
  15095. if (dom.box.parentNode) dom.box.parentNode.removeChild(dom.box);
  15096. if (dom.line.parentNode) dom.line.parentNode.removeChild(dom.line);
  15097. if (dom.dot.parentNode) dom.dot.parentNode.removeChild(dom.dot);
  15098. this.top = null;
  15099. this.left = null;
  15100. this.displayed = false;
  15101. }
  15102. };
  15103. /**
  15104. * Reposition the item horizontally
  15105. * @Override
  15106. */
  15107. BoxItem.prototype.repositionX = function() {
  15108. var start = this.conversion.toScreen(this.data.start);
  15109. var align = this.options.align;
  15110. var left;
  15111. var box = this.dom.box;
  15112. var line = this.dom.line;
  15113. var dot = this.dom.dot;
  15114. // calculate left position of the box
  15115. if (align == 'right') {
  15116. this.left = start - this.width;
  15117. }
  15118. else if (align == 'left') {
  15119. this.left = start;
  15120. }
  15121. else {
  15122. // default or 'center'
  15123. this.left = start - this.width / 2;
  15124. }
  15125. // reposition box
  15126. box.style.left = this.left + 'px';
  15127. // reposition line
  15128. line.style.left = (start - this.props.line.width / 2) + 'px';
  15129. // reposition dot
  15130. dot.style.left = (start - this.props.dot.width / 2) + 'px';
  15131. };
  15132. /**
  15133. * Reposition the item vertically
  15134. * @Override
  15135. */
  15136. BoxItem.prototype.repositionY = function() {
  15137. var orientation = this.options.orientation;
  15138. var box = this.dom.box;
  15139. var line = this.dom.line;
  15140. var dot = this.dom.dot;
  15141. if (orientation == 'top') {
  15142. box.style.top = (this.top || 0) + 'px';
  15143. line.style.top = '0';
  15144. line.style.height = (this.parent.top + this.top + 1) + 'px';
  15145. line.style.bottom = '';
  15146. }
  15147. else { // orientation 'bottom'
  15148. var itemSetHeight = this.parent.itemSet.props.height; // TODO: this is nasty
  15149. var lineHeight = itemSetHeight - this.parent.top - this.parent.height + this.top;
  15150. box.style.top = (this.parent.height - this.top - this.height || 0) + 'px';
  15151. line.style.top = (itemSetHeight - lineHeight) + 'px';
  15152. line.style.bottom = '0';
  15153. }
  15154. dot.style.top = (-this.props.dot.height / 2) + 'px';
  15155. };
  15156. module.exports = BoxItem;
  15157. /***/ },
  15158. /* 33 */
  15159. /***/ function(module, exports, __webpack_require__) {
  15160. var Item = __webpack_require__(30);
  15161. /**
  15162. * @constructor PointItem
  15163. * @extends Item
  15164. * @param {Object} data Object containing parameters start
  15165. * content, className.
  15166. * @param {{toScreen: function, toTime: function}} conversion
  15167. * Conversion functions from time to screen and vice versa
  15168. * @param {Object} [options] Configuration options
  15169. * // TODO: describe available options
  15170. */
  15171. function PointItem (data, conversion, options) {
  15172. this.props = {
  15173. dot: {
  15174. top: 0,
  15175. width: 0,
  15176. height: 0
  15177. },
  15178. content: {
  15179. height: 0,
  15180. marginLeft: 0
  15181. }
  15182. };
  15183. // validate data
  15184. if (data) {
  15185. if (data.start == undefined) {
  15186. throw new Error('Property "start" missing in item ' + data);
  15187. }
  15188. }
  15189. Item.call(this, data, conversion, options);
  15190. }
  15191. PointItem.prototype = new Item (null, null, null);
  15192. /**
  15193. * Check whether this item is visible inside given range
  15194. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  15195. * @returns {boolean} True if visible
  15196. */
  15197. PointItem.prototype.isVisible = function(range) {
  15198. // determine visibility
  15199. // TODO: account for the real width of the item. Right now we just add 1/4 to the window
  15200. var interval = (range.end - range.start) / 4;
  15201. return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
  15202. };
  15203. /**
  15204. * Repaint the item
  15205. */
  15206. PointItem.prototype.redraw = function() {
  15207. var dom = this.dom;
  15208. if (!dom) {
  15209. // create DOM
  15210. this.dom = {};
  15211. dom = this.dom;
  15212. // background box
  15213. dom.point = document.createElement('div');
  15214. // className is updated in redraw()
  15215. // contents box, right from the dot
  15216. dom.content = document.createElement('div');
  15217. dom.content.className = 'content';
  15218. dom.point.appendChild(dom.content);
  15219. // dot at start
  15220. dom.dot = document.createElement('div');
  15221. dom.point.appendChild(dom.dot);
  15222. // attach this item as attribute
  15223. dom.point['timeline-item'] = this;
  15224. this.dirty = true;
  15225. }
  15226. // append DOM to parent DOM
  15227. if (!this.parent) {
  15228. throw new Error('Cannot redraw item: no parent attached');
  15229. }
  15230. if (!dom.point.parentNode) {
  15231. var foreground = this.parent.dom.foreground;
  15232. if (!foreground) {
  15233. throw new Error('Cannot redraw item: parent has no foreground container element');
  15234. }
  15235. foreground.appendChild(dom.point);
  15236. }
  15237. this.displayed = true;
  15238. // Update DOM when item is marked dirty. An item is marked dirty when:
  15239. // - the item is not yet rendered
  15240. // - the item's data is changed
  15241. // - the item is selected/deselected
  15242. if (this.dirty) {
  15243. this._updateContents(this.dom.content);
  15244. this._updateTitle(this.dom.point);
  15245. this._updateDataAttributes(this.dom.point);
  15246. this._updateStyle(this.dom.point);
  15247. // update class
  15248. var className = (this.data.className? ' ' + this.data.className : '') +
  15249. (this.selected ? ' selected' : '');
  15250. dom.point.className = 'item point' + className;
  15251. dom.dot.className = 'item dot' + className;
  15252. // recalculate size
  15253. this.width = dom.point.offsetWidth;
  15254. this.height = dom.point.offsetHeight;
  15255. this.props.dot.width = dom.dot.offsetWidth;
  15256. this.props.dot.height = dom.dot.offsetHeight;
  15257. this.props.content.height = dom.content.offsetHeight;
  15258. // resize contents
  15259. dom.content.style.marginLeft = 2 * this.props.dot.width + 'px';
  15260. //dom.content.style.marginRight = ... + 'px'; // TODO: margin right
  15261. dom.dot.style.top = ((this.height - this.props.dot.height) / 2) + 'px';
  15262. dom.dot.style.left = (this.props.dot.width / 2) + 'px';
  15263. this.dirty = false;
  15264. }
  15265. this._repaintDeleteButton(dom.point);
  15266. };
  15267. /**
  15268. * Show the item in the DOM (when not already visible). The items DOM will
  15269. * be created when needed.
  15270. */
  15271. PointItem.prototype.show = function() {
  15272. if (!this.displayed) {
  15273. this.redraw();
  15274. }
  15275. };
  15276. /**
  15277. * Hide the item from the DOM (when visible)
  15278. */
  15279. PointItem.prototype.hide = function() {
  15280. if (this.displayed) {
  15281. if (this.dom.point.parentNode) {
  15282. this.dom.point.parentNode.removeChild(this.dom.point);
  15283. }
  15284. this.top = null;
  15285. this.left = null;
  15286. this.displayed = false;
  15287. }
  15288. };
  15289. /**
  15290. * Reposition the item horizontally
  15291. * @Override
  15292. */
  15293. PointItem.prototype.repositionX = function() {
  15294. var start = this.conversion.toScreen(this.data.start);
  15295. this.left = start - this.props.dot.width;
  15296. // reposition point
  15297. this.dom.point.style.left = this.left + 'px';
  15298. };
  15299. /**
  15300. * Reposition the item vertically
  15301. * @Override
  15302. */
  15303. PointItem.prototype.repositionY = function() {
  15304. var orientation = this.options.orientation,
  15305. point = this.dom.point;
  15306. if (orientation == 'top') {
  15307. point.style.top = this.top + 'px';
  15308. }
  15309. else {
  15310. point.style.top = (this.parent.height - this.top - this.height) + 'px';
  15311. }
  15312. };
  15313. module.exports = PointItem;
  15314. /***/ },
  15315. /* 34 */
  15316. /***/ function(module, exports, __webpack_require__) {
  15317. var Hammer = __webpack_require__(19);
  15318. var Item = __webpack_require__(30);
  15319. var BackgroundGroup = __webpack_require__(31);
  15320. var RangeItem = __webpack_require__(29);
  15321. /**
  15322. * @constructor BackgroundItem
  15323. * @extends Item
  15324. * @param {Object} data Object containing parameters start, end
  15325. * content, className.
  15326. * @param {{toScreen: function, toTime: function}} conversion
  15327. * Conversion functions from time to screen and vice versa
  15328. * @param {Object} [options] Configuration options
  15329. * // TODO: describe options
  15330. */
  15331. // TODO: implement support for the BackgroundItem just having a start, then being displayed as a sort of an annotation
  15332. function BackgroundItem (data, conversion, options) {
  15333. this.props = {
  15334. content: {
  15335. width: 0
  15336. }
  15337. };
  15338. this.overflow = false; // if contents can overflow (css styling), this flag is set to true
  15339. // validate data
  15340. if (data) {
  15341. if (data.start == undefined) {
  15342. throw new Error('Property "start" missing in item ' + data.id);
  15343. }
  15344. if (data.end == undefined) {
  15345. throw new Error('Property "end" missing in item ' + data.id);
  15346. }
  15347. }
  15348. Item.call(this, data, conversion, options);
  15349. this.emptyContent = false;
  15350. }
  15351. BackgroundItem.prototype = new Item (null, null, null);
  15352. BackgroundItem.prototype.baseClassName = 'item background';
  15353. BackgroundItem.prototype.stack = false;
  15354. /**
  15355. * Check whether this item is visible inside given range
  15356. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  15357. * @returns {boolean} True if visible
  15358. */
  15359. BackgroundItem.prototype.isVisible = function(range) {
  15360. // determine visibility
  15361. return (this.data.start < range.end) && (this.data.end > range.start);
  15362. };
  15363. /**
  15364. * Repaint the item
  15365. */
  15366. BackgroundItem.prototype.redraw = function() {
  15367. var dom = this.dom;
  15368. if (!dom) {
  15369. // create DOM
  15370. this.dom = {};
  15371. dom = this.dom;
  15372. // background box
  15373. dom.box = document.createElement('div');
  15374. // className is updated in redraw()
  15375. // contents box
  15376. dom.content = document.createElement('div');
  15377. dom.content.className = 'content';
  15378. dom.box.appendChild(dom.content);
  15379. // Note: we do NOT attach this item as attribute to the DOM,
  15380. // such that background items cannot be selected
  15381. //dom.box['timeline-item'] = this;
  15382. this.dirty = true;
  15383. }
  15384. // append DOM to parent DOM
  15385. if (!this.parent) {
  15386. throw new Error('Cannot redraw item: no parent attached');
  15387. }
  15388. if (!dom.box.parentNode) {
  15389. var background = this.parent.dom.background;
  15390. if (!background) {
  15391. throw new Error('Cannot redraw item: parent has no background container element');
  15392. }
  15393. background.appendChild(dom.box);
  15394. }
  15395. this.displayed = true;
  15396. // Update DOM when item is marked dirty. An item is marked dirty when:
  15397. // - the item is not yet rendered
  15398. // - the item's data is changed
  15399. // - the item is selected/deselected
  15400. if (this.dirty) {
  15401. this._updateContents(this.dom.content);
  15402. this._updateTitle(this.dom.content);
  15403. this._updateDataAttributes(this.dom.content);
  15404. this._updateStyle(this.dom.box);
  15405. // update class
  15406. var className = (this.data.className ? (' ' + this.data.className) : '') +
  15407. (this.selected ? ' selected' : '');
  15408. dom.box.className = this.baseClassName + className;
  15409. // determine from css whether this box has overflow
  15410. this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden';
  15411. // recalculate size
  15412. this.props.content.width = this.dom.content.offsetWidth;
  15413. this.height = 0; // set height zero, so this item will be ignored when stacking items
  15414. this.dirty = false;
  15415. }
  15416. };
  15417. /**
  15418. * Show the item in the DOM (when not already visible). The items DOM will
  15419. * be created when needed.
  15420. */
  15421. BackgroundItem.prototype.show = RangeItem.prototype.show;
  15422. /**
  15423. * Hide the item from the DOM (when visible)
  15424. * @return {Boolean} changed
  15425. */
  15426. BackgroundItem.prototype.hide = RangeItem.prototype.hide;
  15427. /**
  15428. * Reposition the item horizontally
  15429. * @Override
  15430. */
  15431. BackgroundItem.prototype.repositionX = RangeItem.prototype.repositionX;
  15432. /**
  15433. * Reposition the item vertically
  15434. * @Override
  15435. */
  15436. BackgroundItem.prototype.repositionY = function(margin) {
  15437. var onTop = this.options.orientation === 'top';
  15438. this.dom.content.style.top = onTop ? '' : '0';
  15439. this.dom.content.style.bottom = onTop ? '0' : '';
  15440. var height;
  15441. // special positioning for subgroups
  15442. if (this.data.subgroup !== undefined) {
  15443. var itemSubgroup = this.data.subgroup;
  15444. var subgroups = this.parent.subgroups;
  15445. var subgroupIndex = subgroups[itemSubgroup].index;
  15446. // if the orientation is top, we need to take the difference in height into account.
  15447. if (onTop == true) {
  15448. // the first subgroup will have to account for the distance from the top to the first item.
  15449. height = this.parent.subgroups[itemSubgroup].height + margin.item.vertical;
  15450. height += subgroupIndex == 0 ? margin.axis - 0.5*margin.item.vertical : 0;
  15451. var newTop = this.parent.top;
  15452. for (var subgroup in subgroups) {
  15453. if (subgroups.hasOwnProperty(subgroup)) {
  15454. if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroupIndex) {
  15455. newTop += subgroups[subgroup].height + margin.item.vertical;
  15456. }
  15457. }
  15458. }
  15459. // the others will have to be offset downwards with this same distance.
  15460. newTop += subgroupIndex != 0 ? margin.axis - 0.5 * margin.item.vertical : 0;
  15461. this.dom.box.style.top = newTop + 'px';
  15462. this.dom.box.style.bottom = '';
  15463. }
  15464. // and when the orientation is bottom:
  15465. else {
  15466. var newTop = this.parent.top;
  15467. for (var subgroup in subgroups) {
  15468. if (subgroups.hasOwnProperty(subgroup)) {
  15469. if (subgroups[subgroup].visible == true && subgroups[subgroup].index > subgroupIndex) {
  15470. newTop += subgroups[subgroup].height + margin.item.vertical;
  15471. }
  15472. }
  15473. }
  15474. height = this.parent.subgroups[itemSubgroup].height + margin.item.vertical;
  15475. this.dom.box.style.top = newTop + 'px';
  15476. this.dom.box.style.bottom = '';
  15477. }
  15478. }
  15479. // and in the case of no subgroups:
  15480. else {
  15481. // we want backgrounds with groups to only show in groups.
  15482. if (this.parent instanceof BackgroundGroup) {
  15483. // if the item is not in a group:
  15484. height = Math.max(this.parent.height,
  15485. this.parent.itemSet.body.domProps.center.height,
  15486. this.parent.itemSet.body.domProps.centerContainer.height);
  15487. this.dom.box.style.top = onTop ? '0' : '';
  15488. this.dom.box.style.bottom = onTop ? '' : '0';
  15489. }
  15490. else {
  15491. height = this.parent.height;
  15492. // same alignment for items when orientation is top or bottom
  15493. this.dom.box.style.top = this.parent.top + 'px';
  15494. this.dom.box.style.bottom = '';
  15495. }
  15496. }
  15497. this.dom.box.style.height = height + 'px';
  15498. };
  15499. module.exports = BackgroundItem;
  15500. /***/ },
  15501. /* 35 */
  15502. /***/ function(module, exports, __webpack_require__) {
  15503. var keycharm = __webpack_require__(36);
  15504. var Emitter = __webpack_require__(11);
  15505. var Hammer = __webpack_require__(19);
  15506. var util = __webpack_require__(1);
  15507. /**
  15508. * Turn an element into an clickToUse element.
  15509. * When not active, the element has a transparent overlay. When the overlay is
  15510. * clicked, the mode is changed to active.
  15511. * When active, the element is displayed with a blue border around it, and
  15512. * the interactive contents of the element can be used. When clicked outside
  15513. * the element, the elements mode is changed to inactive.
  15514. * @param {Element} container
  15515. * @constructor
  15516. */
  15517. function Activator(container) {
  15518. this.active = false;
  15519. this.dom = {
  15520. container: container
  15521. };
  15522. this.dom.overlay = document.createElement('div');
  15523. this.dom.overlay.className = 'overlay';
  15524. this.dom.container.appendChild(this.dom.overlay);
  15525. this.hammer = Hammer(this.dom.overlay, {prevent_default: false});
  15526. this.hammer.on('tap', this._onTapOverlay.bind(this));
  15527. // block all touch events (except tap)
  15528. var me = this;
  15529. var events = [
  15530. 'touch', 'pinch',
  15531. 'doubletap', 'hold',
  15532. 'dragstart', 'drag', 'dragend',
  15533. 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
  15534. ];
  15535. events.forEach(function (event) {
  15536. me.hammer.on(event, function (event) {
  15537. event.stopPropagation();
  15538. });
  15539. });
  15540. // attach a tap event to the window, in order to deactivate when clicking outside the timeline
  15541. this.windowHammer = Hammer(window, {prevent_default: false});
  15542. this.windowHammer.on('tap', function (event) {
  15543. // deactivate when clicked outside the container
  15544. if (!_hasParent(event.target, container)) {
  15545. me.deactivate();
  15546. }
  15547. });
  15548. if (this.keycharm !== undefined) {
  15549. this.keycharm.destroy();
  15550. }
  15551. this.keycharm = keycharm();
  15552. // keycharm listener only bounded when active)
  15553. this.escListener = this.deactivate.bind(this);
  15554. }
  15555. // turn into an event emitter
  15556. Emitter(Activator.prototype);
  15557. // The currently active activator
  15558. Activator.current = null;
  15559. /**
  15560. * Destroy the activator. Cleans up all created DOM and event listeners
  15561. */
  15562. Activator.prototype.destroy = function () {
  15563. this.deactivate();
  15564. // remove dom
  15565. this.dom.overlay.parentNode.removeChild(this.dom.overlay);
  15566. // cleanup hammer instances
  15567. this.hammer = null;
  15568. this.windowHammer = null;
  15569. // FIXME: cleaning up hammer instances doesn't work (Timeline not removed from memory)
  15570. };
  15571. /**
  15572. * Activate the element
  15573. * Overlay is hidden, element is decorated with a blue shadow border
  15574. */
  15575. Activator.prototype.activate = function () {
  15576. // we allow only one active activator at a time
  15577. if (Activator.current) {
  15578. Activator.current.deactivate();
  15579. }
  15580. Activator.current = this;
  15581. this.active = true;
  15582. this.dom.overlay.style.display = 'none';
  15583. util.addClassName(this.dom.container, 'vis-active');
  15584. this.emit('change');
  15585. this.emit('activate');
  15586. // ugly hack: bind ESC after emitting the events, as the Network rebinds all
  15587. // keyboard events on a 'change' event
  15588. this.keycharm.bind('esc', this.escListener);
  15589. };
  15590. /**
  15591. * Deactivate the element
  15592. * Overlay is displayed on top of the element
  15593. */
  15594. Activator.prototype.deactivate = function () {
  15595. this.active = false;
  15596. this.dom.overlay.style.display = '';
  15597. util.removeClassName(this.dom.container, 'vis-active');
  15598. this.keycharm.unbind('esc', this.escListener);
  15599. this.emit('change');
  15600. this.emit('deactivate');
  15601. };
  15602. /**
  15603. * Handle a tap event: activate the container
  15604. * @param event
  15605. * @private
  15606. */
  15607. Activator.prototype._onTapOverlay = function (event) {
  15608. // activate the container
  15609. this.activate();
  15610. event.stopPropagation();
  15611. };
  15612. /**
  15613. * Test whether the element has the requested parent element somewhere in
  15614. * its chain of parent nodes.
  15615. * @param {HTMLElement} element
  15616. * @param {HTMLElement} parent
  15617. * @returns {boolean} Returns true when the parent is found somewhere in the
  15618. * chain of parent nodes.
  15619. * @private
  15620. */
  15621. function _hasParent(element, parent) {
  15622. while (element) {
  15623. if (element === parent) {
  15624. return true
  15625. }
  15626. element = element.parentNode;
  15627. }
  15628. return false;
  15629. }
  15630. module.exports = Activator;
  15631. /***/ },
  15632. /* 36 */
  15633. /***/ function(module, exports, __webpack_require__) {
  15634. var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;"use strict";
  15635. /**
  15636. * Created by Alex on 11/6/2014.
  15637. */
  15638. // https://github.com/umdjs/umd/blob/master/returnExports.js#L40-L60
  15639. // if the module has no dependencies, the above pattern can be simplified to
  15640. (function (root, factory) {
  15641. if (true) {
  15642. // AMD. Register as an anonymous module.
  15643. !(__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__));
  15644. } else if (typeof exports === 'object') {
  15645. // Node. Does not work with strict CommonJS, but
  15646. // only CommonJS-like environments that support module.exports,
  15647. // like Node.
  15648. module.exports = factory();
  15649. } else {
  15650. // Browser globals (root is window)
  15651. root.keycharm = factory();
  15652. }
  15653. }(this, function () {
  15654. function keycharm(options) {
  15655. var preventDefault = options && options.preventDefault || false;
  15656. var container = options && options.container || window;
  15657. var _exportFunctions = {};
  15658. var _bound = {keydown:{}, keyup:{}};
  15659. var _keys = {};
  15660. var i;
  15661. // a - z
  15662. for (i = 97; i <= 122; i++) {_keys[String.fromCharCode(i)] = {code:65 + (i - 97), shift: false};}
  15663. // A - Z
  15664. for (i = 65; i <= 90; i++) {_keys[String.fromCharCode(i)] = {code:i, shift: true};}
  15665. // 0 - 9
  15666. for (i = 0; i <= 9; i++) {_keys['' + i] = {code:48 + i, shift: false};}
  15667. // F1 - F12
  15668. for (i = 1; i <= 12; i++) {_keys['F' + i] = {code:111 + i, shift: false};}
  15669. // num0 - num9
  15670. for (i = 0; i <= 9; i++) {_keys['num' + i] = {code:96 + i, shift: false};}
  15671. // numpad misc
  15672. _keys['num*'] = {code:106, shift: false};
  15673. _keys['num+'] = {code:107, shift: false};
  15674. _keys['num-'] = {code:109, shift: false};
  15675. _keys['num/'] = {code:111, shift: false};
  15676. _keys['num.'] = {code:110, shift: false};
  15677. // arrows
  15678. _keys['left'] = {code:37, shift: false};
  15679. _keys['up'] = {code:38, shift: false};
  15680. _keys['right'] = {code:39, shift: false};
  15681. _keys['down'] = {code:40, shift: false};
  15682. // extra keys
  15683. _keys['space'] = {code:32, shift: false};
  15684. _keys['enter'] = {code:13, shift: false};
  15685. _keys['shift'] = {code:16, shift: undefined};
  15686. _keys['esc'] = {code:27, shift: false};
  15687. _keys['backspace'] = {code:8, shift: false};
  15688. _keys['tab'] = {code:9, shift: false};
  15689. _keys['ctrl'] = {code:17, shift: false};
  15690. _keys['alt'] = {code:18, shift: false};
  15691. _keys['delete'] = {code:46, shift: false};
  15692. _keys['pageup'] = {code:33, shift: false};
  15693. _keys['pagedown'] = {code:34, shift: false};
  15694. // symbols
  15695. _keys['='] = {code:187, shift: false};
  15696. _keys['-'] = {code:189, shift: false};
  15697. _keys[']'] = {code:221, shift: false};
  15698. _keys['['] = {code:219, shift: false};
  15699. var down = function(event) {handleEvent(event,'keydown');};
  15700. var up = function(event) {handleEvent(event,'keyup');};
  15701. // handle the actualy bound key with the event
  15702. var handleEvent = function(event,type) {
  15703. if (_bound[type][event.keyCode] !== undefined) {
  15704. var bound = _bound[type][event.keyCode];
  15705. for (var i = 0; i < bound.length; i++) {
  15706. if (bound[i].shift === undefined) {
  15707. bound[i].fn(event);
  15708. }
  15709. else if (bound[i].shift == true && event.shiftKey == true) {
  15710. bound[i].fn(event);
  15711. }
  15712. else if (bound[i].shift == false && event.shiftKey == false) {
  15713. bound[i].fn(event);
  15714. }
  15715. }
  15716. if (preventDefault == true) {
  15717. event.preventDefault();
  15718. }
  15719. }
  15720. };
  15721. // bind a key to a callback
  15722. _exportFunctions.bind = function(key, callback, type) {
  15723. if (type === undefined) {
  15724. type = 'keydown';
  15725. }
  15726. if (_keys[key] === undefined) {
  15727. throw new Error("unsupported key: " + key);
  15728. }
  15729. if (_bound[type][_keys[key].code] === undefined) {
  15730. _bound[type][_keys[key].code] = [];
  15731. }
  15732. _bound[type][_keys[key].code].push({fn:callback, shift:_keys[key].shift});
  15733. };
  15734. // bind all keys to a call back (demo purposes)
  15735. _exportFunctions.bindAll = function(callback, type) {
  15736. if (type === undefined) {
  15737. type = 'keydown';
  15738. }
  15739. for (var key in _keys) {
  15740. if (_keys.hasOwnProperty(key)) {
  15741. _exportFunctions.bind(key,callback,type);
  15742. }
  15743. }
  15744. };
  15745. // get the key label from an event
  15746. _exportFunctions.getKey = function(event) {
  15747. for (var key in _keys) {
  15748. if (_keys.hasOwnProperty(key)) {
  15749. if (event.shiftKey == true && _keys[key].shift == true && event.keyCode == _keys[key].code) {
  15750. return key;
  15751. }
  15752. else if (event.shiftKey == false && _keys[key].shift == false && event.keyCode == _keys[key].code) {
  15753. return key;
  15754. }
  15755. else if (event.keyCode == _keys[key].code && key == 'shift') {
  15756. return key;
  15757. }
  15758. }
  15759. }
  15760. return "unknown key, currently not supported";
  15761. };
  15762. // unbind either a specific callback from a key or all of them (by leaving callback undefined)
  15763. _exportFunctions.unbind = function(key, callback, type) {
  15764. if (type === undefined) {
  15765. type = 'keydown';
  15766. }
  15767. if (_keys[key] === undefined) {
  15768. throw new Error("unsupported key: " + key);
  15769. }
  15770. if (callback !== undefined) {
  15771. var newBindings = [];
  15772. var bound = _bound[type][_keys[key].code];
  15773. if (bound !== undefined) {
  15774. for (var i = 0; i < bound.length; i++) {
  15775. if (!(bound[i].fn == callback && bound[i].shift == _keys[key].shift)) {
  15776. newBindings.push(_bound[type][_keys[key].code][i]);
  15777. }
  15778. }
  15779. }
  15780. _bound[type][_keys[key].code] = newBindings;
  15781. }
  15782. else {
  15783. _bound[type][_keys[key].code] = [];
  15784. }
  15785. };
  15786. // reset all bound variables.
  15787. _exportFunctions.reset = function() {
  15788. _bound = {keydown:{}, keyup:{}};
  15789. };
  15790. // unbind all listeners and reset all variables.
  15791. _exportFunctions.destroy = function() {
  15792. _bound = {keydown:{}, keyup:{}};
  15793. container.removeEventListener('keydown', down, true);
  15794. container.removeEventListener('keyup', up, true);
  15795. };
  15796. // create listeners.
  15797. container.addEventListener('keydown',down,true);
  15798. container.addEventListener('keyup',up,true);
  15799. // return the public functions.
  15800. return _exportFunctions;
  15801. }
  15802. return keycharm;
  15803. }));
  15804. /***/ },
  15805. /* 37 */
  15806. /***/ function(module, exports, __webpack_require__) {
  15807. var util = __webpack_require__(1);
  15808. var Component = __webpack_require__(23);
  15809. var TimeStep = __webpack_require__(38);
  15810. var DateUtil = __webpack_require__(24);
  15811. var moment = __webpack_require__(2);
  15812. /**
  15813. * A horizontal time axis
  15814. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
  15815. * @param {Object} [options] See TimeAxis.setOptions for the available
  15816. * options.
  15817. * @constructor TimeAxis
  15818. * @extends Component
  15819. */
  15820. function TimeAxis (body, options) {
  15821. this.dom = {
  15822. foreground: null,
  15823. lines: [],
  15824. majorTexts: [],
  15825. minorTexts: [],
  15826. redundant: {
  15827. lines: [],
  15828. majorTexts: [],
  15829. minorTexts: []
  15830. }
  15831. };
  15832. this.props = {
  15833. range: {
  15834. start: 0,
  15835. end: 0,
  15836. minimumStep: 0
  15837. },
  15838. lineTop: 0
  15839. };
  15840. this.defaultOptions = {
  15841. orientation: 'bottom', // supported: 'top', 'bottom'
  15842. // TODO: implement timeaxis orientations 'left' and 'right'
  15843. showMinorLabels: true,
  15844. showMajorLabels: true,
  15845. format: null,
  15846. timeAxis: null
  15847. };
  15848. this.options = util.extend({}, this.defaultOptions);
  15849. this.body = body;
  15850. // create the HTML DOM
  15851. this._create();
  15852. this.setOptions(options);
  15853. }
  15854. TimeAxis.prototype = new Component();
  15855. /**
  15856. * Set options for the TimeAxis.
  15857. * Parameters will be merged in current options.
  15858. * @param {Object} options Available options:
  15859. * {string} [orientation]
  15860. * {boolean} [showMinorLabels]
  15861. * {boolean} [showMajorLabels]
  15862. */
  15863. TimeAxis.prototype.setOptions = function(options) {
  15864. if (options) {
  15865. // copy all options that we know
  15866. util.selectiveExtend([
  15867. 'orientation',
  15868. 'showMinorLabels',
  15869. 'showMajorLabels',
  15870. 'hiddenDates',
  15871. 'format',
  15872. 'timeAxis'
  15873. ], this.options, options);
  15874. // apply locale to moment.js
  15875. // TODO: not so nice, this is applied globally to moment.js
  15876. if ('locale' in options) {
  15877. if (typeof moment.locale === 'function') {
  15878. // moment.js 2.8.1+
  15879. moment.locale(options.locale);
  15880. }
  15881. else {
  15882. moment.lang(options.locale);
  15883. }
  15884. }
  15885. }
  15886. };
  15887. /**
  15888. * Create the HTML DOM for the TimeAxis
  15889. */
  15890. TimeAxis.prototype._create = function() {
  15891. this.dom.foreground = document.createElement('div');
  15892. this.dom.background = document.createElement('div');
  15893. this.dom.foreground.className = 'timeaxis foreground';
  15894. this.dom.background.className = 'timeaxis background';
  15895. };
  15896. /**
  15897. * Destroy the TimeAxis
  15898. */
  15899. TimeAxis.prototype.destroy = function() {
  15900. // remove from DOM
  15901. if (this.dom.foreground.parentNode) {
  15902. this.dom.foreground.parentNode.removeChild(this.dom.foreground);
  15903. }
  15904. if (this.dom.background.parentNode) {
  15905. this.dom.background.parentNode.removeChild(this.dom.background);
  15906. }
  15907. this.body = null;
  15908. };
  15909. /**
  15910. * Repaint the component
  15911. * @return {boolean} Returns true if the component is resized
  15912. */
  15913. TimeAxis.prototype.redraw = function () {
  15914. var options = this.options;
  15915. var props = this.props;
  15916. var foreground = this.dom.foreground;
  15917. var background = this.dom.background;
  15918. // determine the correct parent DOM element (depending on option orientation)
  15919. var parent = (options.orientation == 'top') ? this.body.dom.top : this.body.dom.bottom;
  15920. var parentChanged = (foreground.parentNode !== parent);
  15921. // calculate character width and height
  15922. this._calculateCharSize();
  15923. // TODO: recalculate sizes only needed when parent is resized or options is changed
  15924. var orientation = this.options.orientation,
  15925. showMinorLabels = this.options.showMinorLabels,
  15926. showMajorLabels = this.options.showMajorLabels;
  15927. // determine the width and height of the elemens for the axis
  15928. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  15929. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  15930. props.height = props.minorLabelHeight + props.majorLabelHeight;
  15931. props.width = foreground.offsetWidth;
  15932. props.minorLineHeight = this.body.domProps.root.height - props.majorLabelHeight -
  15933. (options.orientation == 'top' ? this.body.domProps.bottom.height : this.body.domProps.top.height);
  15934. props.minorLineWidth = 1; // TODO: really calculate width
  15935. props.majorLineHeight = props.minorLineHeight + props.majorLabelHeight;
  15936. props.majorLineWidth = 1; // TODO: really calculate width
  15937. // take foreground and background offline while updating (is almost twice as fast)
  15938. var foregroundNextSibling = foreground.nextSibling;
  15939. var backgroundNextSibling = background.nextSibling;
  15940. foreground.parentNode && foreground.parentNode.removeChild(foreground);
  15941. background.parentNode && background.parentNode.removeChild(background);
  15942. foreground.style.height = this.props.height + 'px';
  15943. this._repaintLabels();
  15944. // put DOM online again (at the same place)
  15945. if (foregroundNextSibling) {
  15946. parent.insertBefore(foreground, foregroundNextSibling);
  15947. }
  15948. else {
  15949. parent.appendChild(foreground)
  15950. }
  15951. if (backgroundNextSibling) {
  15952. this.body.dom.backgroundVertical.insertBefore(background, backgroundNextSibling);
  15953. }
  15954. else {
  15955. this.body.dom.backgroundVertical.appendChild(background)
  15956. }
  15957. return this._isResized() || parentChanged;
  15958. };
  15959. /**
  15960. * Repaint major and minor text labels and vertical grid lines
  15961. * @private
  15962. */
  15963. TimeAxis.prototype._repaintLabels = function () {
  15964. var orientation = this.options.orientation;
  15965. // calculate range and step (step such that we have space for 7 characters per label)
  15966. var start = util.convert(this.body.range.start, 'Number');
  15967. var end = util.convert(this.body.range.end, 'Number');
  15968. var timeLabelsize = this.body.util.toTime((this.props.minorCharWidth || 10) * 7).valueOf();
  15969. var minimumStep = timeLabelsize - DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this.body.range, timeLabelsize);
  15970. minimumStep -= this.body.util.toTime(0).valueOf();
  15971. var step = new TimeStep(new Date(start), new Date(end), minimumStep, this.body.hiddenDates);
  15972. if (this.options.format) {
  15973. step.setFormat(this.options.format);
  15974. }
  15975. if (this.options.timeAxis) {
  15976. step.setScale(this.options.timeAxis);
  15977. }
  15978. this.step = step;
  15979. // Move all DOM elements to a "redundant" list, where they
  15980. // can be picked for re-use, and clear the lists with lines and texts.
  15981. // At the end of the function _repaintLabels, left over elements will be cleaned up
  15982. var dom = this.dom;
  15983. dom.redundant.lines = dom.lines;
  15984. dom.redundant.majorTexts = dom.majorTexts;
  15985. dom.redundant.minorTexts = dom.minorTexts;
  15986. dom.lines = [];
  15987. dom.majorTexts = [];
  15988. dom.minorTexts = [];
  15989. var cur;
  15990. var x = 0;
  15991. var isMajor;
  15992. var xPrev = 0;
  15993. var width = 0;
  15994. var prevLine;
  15995. var xFirstMajorLabel = undefined;
  15996. var max = 0;
  15997. var className;
  15998. step.first();
  15999. while (step.hasNext() && max < 1000) {
  16000. max++;
  16001. cur = step.getCurrent();
  16002. isMajor = step.isMajor();
  16003. className = step.getClassName();
  16004. xPrev = x;
  16005. x = this.body.util.toScreen(cur);
  16006. width = x - xPrev;
  16007. if (prevLine) {
  16008. prevLine.style.width = width + 'px';
  16009. }
  16010. if (this.options.showMinorLabels) {
  16011. this._repaintMinorText(x, step.getLabelMinor(), orientation, className);
  16012. }
  16013. if (isMajor && this.options.showMajorLabels) {
  16014. if (x > 0) {
  16015. if (xFirstMajorLabel == undefined) {
  16016. xFirstMajorLabel = x;
  16017. }
  16018. this._repaintMajorText(x, step.getLabelMajor(), orientation, className);
  16019. }
  16020. prevLine = this._repaintMajorLine(x, orientation, className);
  16021. }
  16022. else {
  16023. prevLine = this._repaintMinorLine(x, orientation, className);
  16024. }
  16025. step.next();
  16026. }
  16027. // create a major label on the left when needed
  16028. if (this.options.showMajorLabels) {
  16029. var leftTime = this.body.util.toTime(0),
  16030. leftText = step.getLabelMajor(leftTime),
  16031. widthText = leftText.length * (this.props.majorCharWidth || 10) + 10; // upper bound estimation
  16032. if (xFirstMajorLabel == undefined || widthText < xFirstMajorLabel) {
  16033. this._repaintMajorText(0, leftText, orientation, className);
  16034. }
  16035. }
  16036. // Cleanup leftover DOM elements from the redundant list
  16037. util.forEach(this.dom.redundant, function (arr) {
  16038. while (arr.length) {
  16039. var elem = arr.pop();
  16040. if (elem && elem.parentNode) {
  16041. elem.parentNode.removeChild(elem);
  16042. }
  16043. }
  16044. });
  16045. };
  16046. /**
  16047. * Create a minor label for the axis at position x
  16048. * @param {Number} x
  16049. * @param {String} text
  16050. * @param {String} orientation "top" or "bottom" (default)
  16051. * @param {String} className
  16052. * @private
  16053. */
  16054. TimeAxis.prototype._repaintMinorText = function (x, text, orientation, className) {
  16055. // reuse redundant label
  16056. var label = this.dom.redundant.minorTexts.shift();
  16057. if (!label) {
  16058. // create new label
  16059. var content = document.createTextNode('');
  16060. label = document.createElement('div');
  16061. label.appendChild(content);
  16062. this.dom.foreground.appendChild(label);
  16063. }
  16064. this.dom.minorTexts.push(label);
  16065. label.childNodes[0].nodeValue = text;
  16066. label.style.top = (orientation == 'top') ? (this.props.majorLabelHeight + 'px') : '0';
  16067. label.style.left = x + 'px';
  16068. label.className = 'text minor ' + className;
  16069. //label.title = title; // TODO: this is a heavy operation
  16070. };
  16071. /**
  16072. * Create a Major label for the axis at position x
  16073. * @param {Number} x
  16074. * @param {String} text
  16075. * @param {String} orientation "top" or "bottom" (default)
  16076. * @param {String} className
  16077. * @private
  16078. */
  16079. TimeAxis.prototype._repaintMajorText = function (x, text, orientation, className) {
  16080. // reuse redundant label
  16081. var label = this.dom.redundant.majorTexts.shift();
  16082. if (!label) {
  16083. // create label
  16084. var content = document.createTextNode(text);
  16085. label = document.createElement('div');
  16086. label.appendChild(content);
  16087. this.dom.foreground.appendChild(label);
  16088. }
  16089. this.dom.majorTexts.push(label);
  16090. label.childNodes[0].nodeValue = text;
  16091. label.className = 'text major ' + className;
  16092. //label.title = title; // TODO: this is a heavy operation
  16093. label.style.top = (orientation == 'top') ? '0' : (this.props.minorLabelHeight + 'px');
  16094. label.style.left = x + 'px';
  16095. };
  16096. /**
  16097. * Create a minor line for the axis at position x
  16098. * @param {Number} x
  16099. * @param {String} orientation "top" or "bottom" (default)
  16100. * @param {String} className
  16101. * @return {Element} Returns the created line
  16102. * @private
  16103. */
  16104. TimeAxis.prototype._repaintMinorLine = function (x, orientation, className) {
  16105. // reuse redundant line
  16106. var line = this.dom.redundant.lines.shift();
  16107. if (!line) {
  16108. // create vertical line
  16109. line = document.createElement('div');
  16110. this.dom.background.appendChild(line);
  16111. }
  16112. this.dom.lines.push(line);
  16113. var props = this.props;
  16114. if (orientation == 'top') {
  16115. line.style.top = props.majorLabelHeight + 'px';
  16116. }
  16117. else {
  16118. line.style.top = this.body.domProps.top.height + 'px';
  16119. }
  16120. line.style.height = props.minorLineHeight + 'px';
  16121. line.style.left = (x - props.minorLineWidth / 2) + 'px';
  16122. line.className = 'grid vertical minor ' + className;
  16123. return line;
  16124. };
  16125. /**
  16126. * Create a Major line for the axis at position x
  16127. * @param {Number} x
  16128. * @param {String} orientation "top" or "bottom" (default)
  16129. * @param {String} className
  16130. * @return {Element} Returns the created line
  16131. * @private
  16132. */
  16133. TimeAxis.prototype._repaintMajorLine = function (x, orientation, className) {
  16134. // reuse redundant line
  16135. var line = this.dom.redundant.lines.shift();
  16136. if (!line) {
  16137. // create vertical line
  16138. line = document.createElement('div');
  16139. this.dom.background.appendChild(line);
  16140. }
  16141. this.dom.lines.push(line);
  16142. var props = this.props;
  16143. if (orientation == 'top') {
  16144. line.style.top = '0';
  16145. }
  16146. else {
  16147. line.style.top = this.body.domProps.top.height + 'px';
  16148. }
  16149. line.style.left = (x - props.majorLineWidth / 2) + 'px';
  16150. line.style.height = props.majorLineHeight + 'px';
  16151. line.className = 'grid vertical major ' + className;
  16152. return line;
  16153. };
  16154. /**
  16155. * Determine the size of text on the axis (both major and minor axis).
  16156. * The size is calculated only once and then cached in this.props.
  16157. * @private
  16158. */
  16159. TimeAxis.prototype._calculateCharSize = function () {
  16160. // Note: We calculate char size with every redraw. Size may change, for
  16161. // example when any of the timelines parents had display:none for example.
  16162. // determine the char width and height on the minor axis
  16163. if (!this.dom.measureCharMinor) {
  16164. this.dom.measureCharMinor = document.createElement('DIV');
  16165. this.dom.measureCharMinor.className = 'text minor measure';
  16166. this.dom.measureCharMinor.style.position = 'absolute';
  16167. this.dom.measureCharMinor.appendChild(document.createTextNode('0'));
  16168. this.dom.foreground.appendChild(this.dom.measureCharMinor);
  16169. }
  16170. this.props.minorCharHeight = this.dom.measureCharMinor.clientHeight;
  16171. this.props.minorCharWidth = this.dom.measureCharMinor.clientWidth;
  16172. // determine the char width and height on the major axis
  16173. if (!this.dom.measureCharMajor) {
  16174. this.dom.measureCharMajor = document.createElement('DIV');
  16175. this.dom.measureCharMajor.className = 'text major measure';
  16176. this.dom.measureCharMajor.style.position = 'absolute';
  16177. this.dom.measureCharMajor.appendChild(document.createTextNode('0'));
  16178. this.dom.foreground.appendChild(this.dom.measureCharMajor);
  16179. }
  16180. this.props.majorCharHeight = this.dom.measureCharMajor.clientHeight;
  16181. this.props.majorCharWidth = this.dom.measureCharMajor.clientWidth;
  16182. };
  16183. /**
  16184. * Snap a date to a rounded value.
  16185. * The snap intervals are dependent on the current scale and step.
  16186. * @param {Date} date the date to be snapped.
  16187. * @return {Date} snappedDate
  16188. */
  16189. TimeAxis.prototype.snap = function(date) {
  16190. return this.step.snap(date);
  16191. };
  16192. module.exports = TimeAxis;
  16193. /***/ },
  16194. /* 38 */
  16195. /***/ function(module, exports, __webpack_require__) {
  16196. var moment = __webpack_require__(2);
  16197. var DateUtil = __webpack_require__(24);
  16198. var util = __webpack_require__(1);
  16199. /**
  16200. * @constructor TimeStep
  16201. * The class TimeStep is an iterator for dates. You provide a start date and an
  16202. * end date. The class itself determines the best scale (step size) based on the
  16203. * provided start Date, end Date, and minimumStep.
  16204. *
  16205. * If minimumStep is provided, the step size is chosen as close as possible
  16206. * to the minimumStep but larger than minimumStep. If minimumStep is not
  16207. * provided, the scale is set to 1 DAY.
  16208. * The minimumStep should correspond with the onscreen size of about 6 characters
  16209. *
  16210. * Alternatively, you can set a scale by hand.
  16211. * After creation, you can initialize the class by executing first(). Then you
  16212. * can iterate from the start date to the end date via next(). You can check if
  16213. * the end date is reached with the function hasNext(). After each step, you can
  16214. * retrieve the current date via getCurrent().
  16215. * The TimeStep has scales ranging from milliseconds, seconds, minutes, hours,
  16216. * days, to years.
  16217. *
  16218. * Version: 1.2
  16219. *
  16220. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  16221. * or new Date(2010, 9, 21, 23, 45, 00)
  16222. * @param {Date} [end] The end date
  16223. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  16224. */
  16225. function TimeStep(start, end, minimumStep, hiddenDates) {
  16226. // variables
  16227. this.current = new Date();
  16228. this._start = new Date();
  16229. this._end = new Date();
  16230. this.autoScale = true;
  16231. this.scale = 'day';
  16232. this.step = 1;
  16233. // initialize the range
  16234. this.setRange(start, end, minimumStep);
  16235. // hidden Dates options
  16236. this.switchedDay = false;
  16237. this.switchedMonth = false;
  16238. this.switchedYear = false;
  16239. this.hiddenDates = hiddenDates;
  16240. if (hiddenDates === undefined) {
  16241. this.hiddenDates = [];
  16242. }
  16243. this.format = TimeStep.FORMAT; // default formatting
  16244. }
  16245. // Time formatting
  16246. TimeStep.FORMAT = {
  16247. minorLabels: {
  16248. millisecond:'SSS',
  16249. second: 's',
  16250. minute: 'HH:mm',
  16251. hour: 'HH:mm',
  16252. weekday: 'ddd D',
  16253. day: 'D',
  16254. month: 'MMM',
  16255. year: 'YYYY'
  16256. },
  16257. majorLabels: {
  16258. millisecond:'HH:mm:ss',
  16259. second: 'D MMMM HH:mm',
  16260. minute: 'ddd D MMMM',
  16261. hour: 'ddd D MMMM',
  16262. weekday: 'MMMM YYYY',
  16263. day: 'MMMM YYYY',
  16264. month: 'YYYY',
  16265. year: ''
  16266. }
  16267. };
  16268. /**
  16269. * Set custom formatting for the minor an major labels of the TimeStep.
  16270. * Both `minorLabels` and `majorLabels` are an Object with properties:
  16271. * 'millisecond, 'second, 'minute', 'hour', 'weekday, 'day, 'month, 'year'.
  16272. * @param {{minorLabels: Object, majorLabels: Object}} format
  16273. */
  16274. TimeStep.prototype.setFormat = function (format) {
  16275. var defaultFormat = util.deepExtend({}, TimeStep.FORMAT);
  16276. this.format = util.deepExtend(defaultFormat, format);
  16277. };
  16278. /**
  16279. * Set a new range
  16280. * If minimumStep is provided, the step size is chosen as close as possible
  16281. * to the minimumStep but larger than minimumStep. If minimumStep is not
  16282. * provided, the scale is set to 1 DAY.
  16283. * The minimumStep should correspond with the onscreen size of about 6 characters
  16284. * @param {Date} [start] The start date and time.
  16285. * @param {Date} [end] The end date and time.
  16286. * @param {int} [minimumStep] Optional. Minimum step size in milliseconds
  16287. */
  16288. TimeStep.prototype.setRange = function(start, end, minimumStep) {
  16289. if (!(start instanceof Date) || !(end instanceof Date)) {
  16290. throw "No legal start or end date in method setRange";
  16291. }
  16292. this._start = (start != undefined) ? new Date(start.valueOf()) : new Date();
  16293. this._end = (end != undefined) ? new Date(end.valueOf()) : new Date();
  16294. if (this.autoScale) {
  16295. this.setMinimumStep(minimumStep);
  16296. }
  16297. };
  16298. /**
  16299. * Set the range iterator to the start date.
  16300. */
  16301. TimeStep.prototype.first = function() {
  16302. this.current = new Date(this._start.valueOf());
  16303. this.roundToMinor();
  16304. };
  16305. /**
  16306. * Round the current date to the first minor date value
  16307. * This must be executed once when the current date is set to start Date
  16308. */
  16309. TimeStep.prototype.roundToMinor = function() {
  16310. // round to floor
  16311. // IMPORTANT: we have no breaks in this switch! (this is no bug)
  16312. // noinspection FallThroughInSwitchStatementJS
  16313. switch (this.scale) {
  16314. case 'year':
  16315. this.current.setFullYear(this.step * Math.floor(this.current.getFullYear() / this.step));
  16316. this.current.setMonth(0);
  16317. case 'month': this.current.setDate(1);
  16318. case 'day': // intentional fall through
  16319. case 'weekday': this.current.setHours(0);
  16320. case 'hour': this.current.setMinutes(0);
  16321. case 'minute': this.current.setSeconds(0);
  16322. case 'second': this.current.setMilliseconds(0);
  16323. //case 'millisecond': // nothing to do for milliseconds
  16324. }
  16325. if (this.step != 1) {
  16326. // round down to the first minor value that is a multiple of the current step size
  16327. switch (this.scale) {
  16328. case 'millisecond': this.current.setMilliseconds(this.current.getMilliseconds() - this.current.getMilliseconds() % this.step); break;
  16329. case 'second': this.current.setSeconds(this.current.getSeconds() - this.current.getSeconds() % this.step); break;
  16330. case 'minute': this.current.setMinutes(this.current.getMinutes() - this.current.getMinutes() % this.step); break;
  16331. case 'hour': this.current.setHours(this.current.getHours() - this.current.getHours() % this.step); break;
  16332. case 'weekday': // intentional fall through
  16333. case 'day': this.current.setDate((this.current.getDate()-1) - (this.current.getDate()-1) % this.step + 1); break;
  16334. case 'month': this.current.setMonth(this.current.getMonth() - this.current.getMonth() % this.step); break;
  16335. case 'year': this.current.setFullYear(this.current.getFullYear() - this.current.getFullYear() % this.step); break;
  16336. default: break;
  16337. }
  16338. }
  16339. };
  16340. /**
  16341. * Check if the there is a next step
  16342. * @return {boolean} true if the current date has not passed the end date
  16343. */
  16344. TimeStep.prototype.hasNext = function () {
  16345. return (this.current.valueOf() <= this._end.valueOf());
  16346. };
  16347. /**
  16348. * Do the next step
  16349. */
  16350. TimeStep.prototype.next = function() {
  16351. var prev = this.current.valueOf();
  16352. // Two cases, needed to prevent issues with switching daylight savings
  16353. // (end of March and end of October)
  16354. if (this.current.getMonth() < 6) {
  16355. switch (this.scale) {
  16356. case 'millisecond':
  16357. this.current = new Date(this.current.valueOf() + this.step); break;
  16358. case 'second': this.current = new Date(this.current.valueOf() + this.step * 1000); break;
  16359. case 'minute': this.current = new Date(this.current.valueOf() + this.step * 1000 * 60); break;
  16360. case 'hour':
  16361. this.current = new Date(this.current.valueOf() + this.step * 1000 * 60 * 60);
  16362. // in case of skipping an hour for daylight savings, adjust the hour again (else you get: 0h 5h 9h ... instead of 0h 4h 8h ...)
  16363. var h = this.current.getHours();
  16364. this.current.setHours(h - (h % this.step));
  16365. break;
  16366. case 'weekday': // intentional fall through
  16367. case 'day': this.current.setDate(this.current.getDate() + this.step); break;
  16368. case 'month': this.current.setMonth(this.current.getMonth() + this.step); break;
  16369. case 'year': this.current.setFullYear(this.current.getFullYear() + this.step); break;
  16370. default: break;
  16371. }
  16372. }
  16373. else {
  16374. switch (this.scale) {
  16375. case 'millisecond': this.current = new Date(this.current.valueOf() + this.step); break;
  16376. case 'second': this.current.setSeconds(this.current.getSeconds() + this.step); break;
  16377. case 'minute': this.current.setMinutes(this.current.getMinutes() + this.step); break;
  16378. case 'hour': this.current.setHours(this.current.getHours() + this.step); break;
  16379. case 'weekday': // intentional fall through
  16380. case 'day': this.current.setDate(this.current.getDate() + this.step); break;
  16381. case 'month': this.current.setMonth(this.current.getMonth() + this.step); break;
  16382. case 'year': this.current.setFullYear(this.current.getFullYear() + this.step); break;
  16383. default: break;
  16384. }
  16385. }
  16386. if (this.step != 1) {
  16387. // round down to the correct major value
  16388. switch (this.scale) {
  16389. case 'millisecond': if(this.current.getMilliseconds() < this.step) this.current.setMilliseconds(0); break;
  16390. case 'second': if(this.current.getSeconds() < this.step) this.current.setSeconds(0); break;
  16391. case 'minute': if(this.current.getMinutes() < this.step) this.current.setMinutes(0); break;
  16392. case 'hour': if(this.current.getHours() < this.step) this.current.setHours(0); break;
  16393. case 'weekday': // intentional fall through
  16394. case 'day': if(this.current.getDate() < this.step+1) this.current.setDate(1); break;
  16395. case 'month': if(this.current.getMonth() < this.step) this.current.setMonth(0); break;
  16396. case 'year': break; // nothing to do for year
  16397. default: break;
  16398. }
  16399. }
  16400. // safety mechanism: if current time is still unchanged, move to the end
  16401. if (this.current.valueOf() == prev) {
  16402. this.current = new Date(this._end.valueOf());
  16403. }
  16404. DateUtil.stepOverHiddenDates(this, prev);
  16405. };
  16406. /**
  16407. * Get the current datetime
  16408. * @return {Date} current The current date
  16409. */
  16410. TimeStep.prototype.getCurrent = function() {
  16411. return this.current;
  16412. };
  16413. /**
  16414. * Set a custom scale. Autoscaling will be disabled.
  16415. * For example setScale('minute', 5) will result
  16416. * in minor steps of 5 minutes, and major steps of an hour.
  16417. *
  16418. * @param {{scale: string, step: number}} params
  16419. * An object containing two properties:
  16420. * - A string 'scale'. Choose from 'millisecond', 'second',
  16421. * 'minute', 'hour', 'weekday, 'day, 'month, 'year'.
  16422. * - A number 'step'. A step size, by default 1.
  16423. * Choose for example 1, 2, 5, or 10.
  16424. */
  16425. TimeStep.prototype.setScale = function(params) {
  16426. if (params && typeof params.scale == 'string') {
  16427. this.scale = params.scale;
  16428. this.step = params.step > 0 ? params.step : 1;
  16429. this.autoScale = false;
  16430. }
  16431. };
  16432. /**
  16433. * Enable or disable autoscaling
  16434. * @param {boolean} enable If true, autoascaling is set true
  16435. */
  16436. TimeStep.prototype.setAutoScale = function (enable) {
  16437. this.autoScale = enable;
  16438. };
  16439. /**
  16440. * Automatically determine the scale that bests fits the provided minimum step
  16441. * @param {Number} [minimumStep] The minimum step size in milliseconds
  16442. */
  16443. TimeStep.prototype.setMinimumStep = function(minimumStep) {
  16444. if (minimumStep == undefined) {
  16445. return;
  16446. }
  16447. //var b = asc + ds;
  16448. var stepYear = (1000 * 60 * 60 * 24 * 30 * 12);
  16449. var stepMonth = (1000 * 60 * 60 * 24 * 30);
  16450. var stepDay = (1000 * 60 * 60 * 24);
  16451. var stepHour = (1000 * 60 * 60);
  16452. var stepMinute = (1000 * 60);
  16453. var stepSecond = (1000);
  16454. var stepMillisecond= (1);
  16455. // find the smallest step that is larger than the provided minimumStep
  16456. if (stepYear*1000 > minimumStep) {this.scale = 'year'; this.step = 1000;}
  16457. if (stepYear*500 > minimumStep) {this.scale = 'year'; this.step = 500;}
  16458. if (stepYear*100 > minimumStep) {this.scale = 'year'; this.step = 100;}
  16459. if (stepYear*50 > minimumStep) {this.scale = 'year'; this.step = 50;}
  16460. if (stepYear*10 > minimumStep) {this.scale = 'year'; this.step = 10;}
  16461. if (stepYear*5 > minimumStep) {this.scale = 'year'; this.step = 5;}
  16462. if (stepYear > minimumStep) {this.scale = 'year'; this.step = 1;}
  16463. if (stepMonth*3 > minimumStep) {this.scale = 'month'; this.step = 3;}
  16464. if (stepMonth > minimumStep) {this.scale = 'month'; this.step = 1;}
  16465. if (stepDay*5 > minimumStep) {this.scale = 'day'; this.step = 5;}
  16466. if (stepDay*2 > minimumStep) {this.scale = 'day'; this.step = 2;}
  16467. if (stepDay > minimumStep) {this.scale = 'day'; this.step = 1;}
  16468. if (stepDay/2 > minimumStep) {this.scale = 'weekday'; this.step = 1;}
  16469. if (stepHour*4 > minimumStep) {this.scale = 'hour'; this.step = 4;}
  16470. if (stepHour > minimumStep) {this.scale = 'hour'; this.step = 1;}
  16471. if (stepMinute*15 > minimumStep) {this.scale = 'minute'; this.step = 15;}
  16472. if (stepMinute*10 > minimumStep) {this.scale = 'minute'; this.step = 10;}
  16473. if (stepMinute*5 > minimumStep) {this.scale = 'minute'; this.step = 5;}
  16474. if (stepMinute > minimumStep) {this.scale = 'minute'; this.step = 1;}
  16475. if (stepSecond*15 > minimumStep) {this.scale = 'second'; this.step = 15;}
  16476. if (stepSecond*10 > minimumStep) {this.scale = 'second'; this.step = 10;}
  16477. if (stepSecond*5 > minimumStep) {this.scale = 'second'; this.step = 5;}
  16478. if (stepSecond > minimumStep) {this.scale = 'second'; this.step = 1;}
  16479. if (stepMillisecond*200 > minimumStep) {this.scale = 'millisecond'; this.step = 200;}
  16480. if (stepMillisecond*100 > minimumStep) {this.scale = 'millisecond'; this.step = 100;}
  16481. if (stepMillisecond*50 > minimumStep) {this.scale = 'millisecond'; this.step = 50;}
  16482. if (stepMillisecond*10 > minimumStep) {this.scale = 'millisecond'; this.step = 10;}
  16483. if (stepMillisecond*5 > minimumStep) {this.scale = 'millisecond'; this.step = 5;}
  16484. if (stepMillisecond > minimumStep) {this.scale = 'millisecond'; this.step = 1;}
  16485. };
  16486. /**
  16487. * Snap a date to a rounded value.
  16488. * The snap intervals are dependent on the current scale and step.
  16489. * @param {Date} date the date to be snapped.
  16490. * @return {Date} snappedDate
  16491. */
  16492. TimeStep.prototype.snap = function(date) {
  16493. var clone = new Date(date.valueOf());
  16494. if (this.scale == 'year') {
  16495. var year = clone.getFullYear() + Math.round(clone.getMonth() / 12);
  16496. clone.setFullYear(Math.round(year / this.step) * this.step);
  16497. clone.setMonth(0);
  16498. clone.setDate(0);
  16499. clone.setHours(0);
  16500. clone.setMinutes(0);
  16501. clone.setSeconds(0);
  16502. clone.setMilliseconds(0);
  16503. }
  16504. else if (this.scale == 'month') {
  16505. if (clone.getDate() > 15) {
  16506. clone.setDate(1);
  16507. clone.setMonth(clone.getMonth() + 1);
  16508. // important: first set Date to 1, after that change the month.
  16509. }
  16510. else {
  16511. clone.setDate(1);
  16512. }
  16513. clone.setHours(0);
  16514. clone.setMinutes(0);
  16515. clone.setSeconds(0);
  16516. clone.setMilliseconds(0);
  16517. }
  16518. else if (this.scale == 'day') {
  16519. //noinspection FallthroughInSwitchStatementJS
  16520. switch (this.step) {
  16521. case 5:
  16522. case 2:
  16523. clone.setHours(Math.round(clone.getHours() / 24) * 24); break;
  16524. default:
  16525. clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
  16526. }
  16527. clone.setMinutes(0);
  16528. clone.setSeconds(0);
  16529. clone.setMilliseconds(0);
  16530. }
  16531. else if (this.scale == 'weekday') {
  16532. //noinspection FallthroughInSwitchStatementJS
  16533. switch (this.step) {
  16534. case 5:
  16535. case 2:
  16536. clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
  16537. default:
  16538. clone.setHours(Math.round(clone.getHours() / 6) * 6); break;
  16539. }
  16540. clone.setMinutes(0);
  16541. clone.setSeconds(0);
  16542. clone.setMilliseconds(0);
  16543. }
  16544. else if (this.scale == 'hour') {
  16545. switch (this.step) {
  16546. case 4:
  16547. clone.setMinutes(Math.round(clone.getMinutes() / 60) * 60); break;
  16548. default:
  16549. clone.setMinutes(Math.round(clone.getMinutes() / 30) * 30); break;
  16550. }
  16551. clone.setSeconds(0);
  16552. clone.setMilliseconds(0);
  16553. } else if (this.scale == 'minute') {
  16554. //noinspection FallthroughInSwitchStatementJS
  16555. switch (this.step) {
  16556. case 15:
  16557. case 10:
  16558. clone.setMinutes(Math.round(clone.getMinutes() / 5) * 5);
  16559. clone.setSeconds(0);
  16560. break;
  16561. case 5:
  16562. clone.setSeconds(Math.round(clone.getSeconds() / 60) * 60); break;
  16563. default:
  16564. clone.setSeconds(Math.round(clone.getSeconds() / 30) * 30); break;
  16565. }
  16566. clone.setMilliseconds(0);
  16567. }
  16568. else if (this.scale == 'second') {
  16569. //noinspection FallthroughInSwitchStatementJS
  16570. switch (this.step) {
  16571. case 15:
  16572. case 10:
  16573. clone.setSeconds(Math.round(clone.getSeconds() / 5) * 5);
  16574. clone.setMilliseconds(0);
  16575. break;
  16576. case 5:
  16577. clone.setMilliseconds(Math.round(clone.getMilliseconds() / 1000) * 1000); break;
  16578. default:
  16579. clone.setMilliseconds(Math.round(clone.getMilliseconds() / 500) * 500); break;
  16580. }
  16581. }
  16582. else if (this.scale == 'millisecond') {
  16583. var step = this.step > 5 ? this.step / 2 : 1;
  16584. clone.setMilliseconds(Math.round(clone.getMilliseconds() / step) * step);
  16585. }
  16586. return clone;
  16587. };
  16588. /**
  16589. * Check if the current value is a major value (for example when the step
  16590. * is DAY, a major value is each first day of the MONTH)
  16591. * @return {boolean} true if current date is major, else false.
  16592. */
  16593. TimeStep.prototype.isMajor = function() {
  16594. if (this.switchedYear == true) {
  16595. this.switchedYear = false;
  16596. switch (this.scale) {
  16597. case 'year':
  16598. case 'month':
  16599. case 'weekday':
  16600. case 'day':
  16601. case 'hour':
  16602. case 'minute':
  16603. case 'second':
  16604. case 'millisecond':
  16605. return true;
  16606. default:
  16607. return false;
  16608. }
  16609. }
  16610. else if (this.switchedMonth == true) {
  16611. this.switchedMonth = false;
  16612. switch (this.scale) {
  16613. case 'weekday':
  16614. case 'day':
  16615. case 'hour':
  16616. case 'minute':
  16617. case 'second':
  16618. case 'millisecond':
  16619. return true;
  16620. default:
  16621. return false;
  16622. }
  16623. }
  16624. else if (this.switchedDay == true) {
  16625. this.switchedDay = false;
  16626. switch (this.scale) {
  16627. case 'millisecond':
  16628. case 'second':
  16629. case 'minute':
  16630. case 'hour':
  16631. return true;
  16632. default:
  16633. return false;
  16634. }
  16635. }
  16636. switch (this.scale) {
  16637. case 'millisecond':
  16638. return (this.current.getMilliseconds() == 0);
  16639. case 'second':
  16640. return (this.current.getSeconds() == 0);
  16641. case 'minute':
  16642. return (this.current.getHours() == 0) && (this.current.getMinutes() == 0);
  16643. case 'hour':
  16644. return (this.current.getHours() == 0);
  16645. case 'weekday': // intentional fall through
  16646. case 'day':
  16647. return (this.current.getDate() == 1);
  16648. case 'month':
  16649. return (this.current.getMonth() == 0);
  16650. case 'year':
  16651. return false;
  16652. default:
  16653. return false;
  16654. }
  16655. };
  16656. /**
  16657. * Returns formatted text for the minor axislabel, depending on the current
  16658. * date and the scale. For example when scale is MINUTE, the current time is
  16659. * formatted as "hh:mm".
  16660. * @param {Date} [date] custom date. if not provided, current date is taken
  16661. */
  16662. TimeStep.prototype.getLabelMinor = function(date) {
  16663. if (date == undefined) {
  16664. date = this.current;
  16665. }
  16666. var format = this.format.minorLabels[this.scale];
  16667. return (format && format.length > 0) ? moment(date).format(format) : '';
  16668. };
  16669. /**
  16670. * Returns formatted text for the major axis label, depending on the current
  16671. * date and the scale. For example when scale is MINUTE, the major scale is
  16672. * hours, and the hour will be formatted as "hh".
  16673. * @param {Date} [date] custom date. if not provided, current date is taken
  16674. */
  16675. TimeStep.prototype.getLabelMajor = function(date) {
  16676. if (date == undefined) {
  16677. date = this.current;
  16678. }
  16679. var format = this.format.majorLabels[this.scale];
  16680. return (format && format.length > 0) ? moment(date).format(format) : '';
  16681. };
  16682. TimeStep.prototype.getClassName = function() {
  16683. var m = moment(this.current);
  16684. var date = m.locale ? m.locale('en') : m.lang('en'); // old versions of moment have .lang() function
  16685. var step = this.step;
  16686. function even(value) {
  16687. return (value / step % 2 == 0) ? ' even' : ' odd';
  16688. }
  16689. function today(date) {
  16690. if (date.isSame(new Date(), 'day')) {
  16691. return ' today';
  16692. }
  16693. if (date.isSame(moment().add(1, 'day'), 'day')) {
  16694. return ' tomorrow';
  16695. }
  16696. if (date.isSame(moment().add(-1, 'day'), 'day')) {
  16697. return ' yesterday';
  16698. }
  16699. return '';
  16700. }
  16701. function currentWeek(date) {
  16702. return date.isSame(new Date(), 'week') ? ' current-week' : '';
  16703. }
  16704. function currentMonth(date) {
  16705. return date.isSame(new Date(), 'month') ? ' current-month' : '';
  16706. }
  16707. function currentYear(date) {
  16708. return date.isSame(new Date(), 'year') ? ' current-year' : '';
  16709. }
  16710. switch (this.scale) {
  16711. case 'millisecond':
  16712. return even(date.milliseconds()).trim();
  16713. case 'second':
  16714. return even(date.seconds()).trim();
  16715. case 'minute':
  16716. return even(date.minutes()).trim();
  16717. case 'hour':
  16718. var hours = date.hours();
  16719. if (this.step == 4) {
  16720. hours = hours + '-' + (hours + 4);
  16721. }
  16722. return hours + 'h' + today(date) + even(date.hours());
  16723. case 'weekday':
  16724. return date.format('dddd').toLowerCase() +
  16725. today(date) + currentWeek(date) + even(date.date());
  16726. case 'day':
  16727. var day = date.date();
  16728. var month = date.format('MMMM').toLowerCase();
  16729. return 'day' + day + ' ' + month + currentMonth(date) + even(day - 1);
  16730. case 'month':
  16731. return date.format('MMMM').toLowerCase() +
  16732. currentMonth(date) + even(date.month());
  16733. case 'year':
  16734. var year = date.year();
  16735. return 'year' + year + currentYear(date)+ even(year);
  16736. default:
  16737. return '';
  16738. }
  16739. };
  16740. module.exports = TimeStep;
  16741. /***/ },
  16742. /* 39 */
  16743. /***/ function(module, exports, __webpack_require__) {
  16744. var util = __webpack_require__(1);
  16745. var Component = __webpack_require__(23);
  16746. var moment = __webpack_require__(2);
  16747. var locales = __webpack_require__(40);
  16748. /**
  16749. * A current time bar
  16750. * @param {{range: Range, dom: Object, domProps: Object}} body
  16751. * @param {Object} [options] Available parameters:
  16752. * {Boolean} [showCurrentTime]
  16753. * @constructor CurrentTime
  16754. * @extends Component
  16755. */
  16756. function CurrentTime (body, options) {
  16757. this.body = body;
  16758. // default options
  16759. this.defaultOptions = {
  16760. showCurrentTime: true,
  16761. locales: locales,
  16762. locale: 'en'
  16763. };
  16764. this.options = util.extend({}, this.defaultOptions);
  16765. this.offset = 0;
  16766. this._create();
  16767. this.setOptions(options);
  16768. }
  16769. CurrentTime.prototype = new Component();
  16770. /**
  16771. * Create the HTML DOM for the current time bar
  16772. * @private
  16773. */
  16774. CurrentTime.prototype._create = function() {
  16775. var bar = document.createElement('div');
  16776. bar.className = 'currenttime';
  16777. bar.style.position = 'absolute';
  16778. bar.style.top = '0px';
  16779. bar.style.height = '100%';
  16780. this.bar = bar;
  16781. };
  16782. /**
  16783. * Destroy the CurrentTime bar
  16784. */
  16785. CurrentTime.prototype.destroy = function () {
  16786. this.options.showCurrentTime = false;
  16787. this.redraw(); // will remove the bar from the DOM and stop refreshing
  16788. this.body = null;
  16789. };
  16790. /**
  16791. * Set options for the component. Options will be merged in current options.
  16792. * @param {Object} options Available parameters:
  16793. * {boolean} [showCurrentTime]
  16794. */
  16795. CurrentTime.prototype.setOptions = function(options) {
  16796. if (options) {
  16797. // copy all options that we know
  16798. util.selectiveExtend(['showCurrentTime', 'locale', 'locales'], this.options, options);
  16799. }
  16800. };
  16801. /**
  16802. * Repaint the component
  16803. * @return {boolean} Returns true if the component is resized
  16804. */
  16805. CurrentTime.prototype.redraw = function() {
  16806. if (this.options.showCurrentTime) {
  16807. var parent = this.body.dom.backgroundVertical;
  16808. if (this.bar.parentNode != parent) {
  16809. // attach to the dom
  16810. if (this.bar.parentNode) {
  16811. this.bar.parentNode.removeChild(this.bar);
  16812. }
  16813. parent.appendChild(this.bar);
  16814. this.start();
  16815. }
  16816. var now = new Date(new Date().valueOf() + this.offset);
  16817. var x = this.body.util.toScreen(now);
  16818. var locale = this.options.locales[this.options.locale];
  16819. var title = locale.current + ' ' + locale.time + ': ' + moment(now).format('dddd, MMMM Do YYYY, H:mm:ss');
  16820. title = title.charAt(0).toUpperCase() + title.substring(1);
  16821. this.bar.style.left = x + 'px';
  16822. this.bar.title = title;
  16823. }
  16824. else {
  16825. // remove the line from the DOM
  16826. if (this.bar.parentNode) {
  16827. this.bar.parentNode.removeChild(this.bar);
  16828. }
  16829. this.stop();
  16830. }
  16831. return false;
  16832. };
  16833. /**
  16834. * Start auto refreshing the current time bar
  16835. */
  16836. CurrentTime.prototype.start = function() {
  16837. var me = this;
  16838. function update () {
  16839. me.stop();
  16840. // determine interval to refresh
  16841. var scale = me.body.range.conversion(me.body.domProps.center.width).scale;
  16842. var interval = 1 / scale / 10;
  16843. if (interval < 30) interval = 30;
  16844. if (interval > 1000) interval = 1000;
  16845. me.redraw();
  16846. // start a timer to adjust for the new time
  16847. me.currentTimeTimer = setTimeout(update, interval);
  16848. }
  16849. update();
  16850. };
  16851. /**
  16852. * Stop auto refreshing the current time bar
  16853. */
  16854. CurrentTime.prototype.stop = function() {
  16855. if (this.currentTimeTimer !== undefined) {
  16856. clearTimeout(this.currentTimeTimer);
  16857. delete this.currentTimeTimer;
  16858. }
  16859. };
  16860. /**
  16861. * Set a current time. This can be used for example to ensure that a client's
  16862. * time is synchronized with a shared server time.
  16863. * @param {Date | String | Number} time A Date, unix timestamp, or
  16864. * ISO date string.
  16865. */
  16866. CurrentTime.prototype.setCurrentTime = function(time) {
  16867. var t = util.convert(time, 'Date').valueOf();
  16868. var now = new Date().valueOf();
  16869. this.offset = t - now;
  16870. this.redraw();
  16871. };
  16872. /**
  16873. * Get the current time.
  16874. * @return {Date} Returns the current time.
  16875. */
  16876. CurrentTime.prototype.getCurrentTime = function() {
  16877. return new Date(new Date().valueOf() + this.offset);
  16878. };
  16879. module.exports = CurrentTime;
  16880. /***/ },
  16881. /* 40 */
  16882. /***/ function(module, exports, __webpack_require__) {
  16883. // English
  16884. exports['en'] = {
  16885. current: 'current',
  16886. time: 'time'
  16887. };
  16888. exports['en_EN'] = exports['en'];
  16889. exports['en_US'] = exports['en'];
  16890. // Dutch
  16891. exports['nl'] = {
  16892. custom: 'aangepaste',
  16893. time: 'tijd'
  16894. };
  16895. exports['nl_NL'] = exports['nl'];
  16896. exports['nl_BE'] = exports['nl'];
  16897. /***/ },
  16898. /* 41 */
  16899. /***/ function(module, exports, __webpack_require__) {
  16900. var Hammer = __webpack_require__(19);
  16901. var util = __webpack_require__(1);
  16902. var Component = __webpack_require__(23);
  16903. var moment = __webpack_require__(2);
  16904. var locales = __webpack_require__(40);
  16905. /**
  16906. * A custom time bar
  16907. * @param {{range: Range, dom: Object}} body
  16908. * @param {Object} [options] Available parameters:
  16909. * {Boolean} [showCustomTime]
  16910. * @constructor CustomTime
  16911. * @extends Component
  16912. */
  16913. function CustomTime (body, options) {
  16914. this.body = body;
  16915. // default options
  16916. this.defaultOptions = {
  16917. showCustomTime: false,
  16918. locales: locales,
  16919. locale: 'en'
  16920. };
  16921. this.options = util.extend({}, this.defaultOptions);
  16922. this.customTime = new Date();
  16923. this.eventParams = {}; // stores state parameters while dragging the bar
  16924. // create the DOM
  16925. this._create();
  16926. this.setOptions(options);
  16927. }
  16928. CustomTime.prototype = new Component();
  16929. /**
  16930. * Set options for the component. Options will be merged in current options.
  16931. * @param {Object} options Available parameters:
  16932. * {boolean} [showCustomTime]
  16933. */
  16934. CustomTime.prototype.setOptions = function(options) {
  16935. if (options) {
  16936. // copy all options that we know
  16937. util.selectiveExtend(['showCustomTime', 'locale', 'locales'], this.options, options);
  16938. }
  16939. };
  16940. /**
  16941. * Create the DOM for the custom time
  16942. * @private
  16943. */
  16944. CustomTime.prototype._create = function() {
  16945. var bar = document.createElement('div');
  16946. bar.className = 'customtime';
  16947. bar.style.position = 'absolute';
  16948. bar.style.top = '0px';
  16949. bar.style.height = '100%';
  16950. this.bar = bar;
  16951. var drag = document.createElement('div');
  16952. drag.style.position = 'relative';
  16953. drag.style.top = '0px';
  16954. drag.style.left = '-10px';
  16955. drag.style.height = '100%';
  16956. drag.style.width = '20px';
  16957. bar.appendChild(drag);
  16958. // attach event listeners
  16959. this.hammer = Hammer(bar, {
  16960. prevent_default: true
  16961. });
  16962. this.hammer.on('dragstart', this._onDragStart.bind(this));
  16963. this.hammer.on('drag', this._onDrag.bind(this));
  16964. this.hammer.on('dragend', this._onDragEnd.bind(this));
  16965. };
  16966. /**
  16967. * Destroy the CustomTime bar
  16968. */
  16969. CustomTime.prototype.destroy = function () {
  16970. this.options.showCustomTime = false;
  16971. this.redraw(); // will remove the bar from the DOM
  16972. this.hammer.enable(false);
  16973. this.hammer = null;
  16974. this.body = null;
  16975. };
  16976. /**
  16977. * Repaint the component
  16978. * @return {boolean} Returns true if the component is resized
  16979. */
  16980. CustomTime.prototype.redraw = function () {
  16981. if (this.options.showCustomTime) {
  16982. var parent = this.body.dom.backgroundVertical;
  16983. if (this.bar.parentNode != parent) {
  16984. // attach to the dom
  16985. if (this.bar.parentNode) {
  16986. this.bar.parentNode.removeChild(this.bar);
  16987. }
  16988. parent.appendChild(this.bar);
  16989. }
  16990. var x = this.body.util.toScreen(this.customTime);
  16991. var locale = this.options.locales[this.options.locale];
  16992. var title = locale.time + ': ' + moment(this.customTime).format('dddd, MMMM Do YYYY, H:mm:ss');
  16993. title = title.charAt(0).toUpperCase() + title.substring(1);
  16994. this.bar.style.left = x + 'px';
  16995. this.bar.title = title;
  16996. }
  16997. else {
  16998. // remove the line from the DOM
  16999. if (this.bar.parentNode) {
  17000. this.bar.parentNode.removeChild(this.bar);
  17001. }
  17002. }
  17003. return false;
  17004. };
  17005. /**
  17006. * Set custom time.
  17007. * @param {Date | number | string} time
  17008. */
  17009. CustomTime.prototype.setCustomTime = function(time) {
  17010. this.customTime = util.convert(time, 'Date');
  17011. this.redraw();
  17012. };
  17013. /**
  17014. * Retrieve the current custom time.
  17015. * @return {Date} customTime
  17016. */
  17017. CustomTime.prototype.getCustomTime = function() {
  17018. return new Date(this.customTime.valueOf());
  17019. };
  17020. /**
  17021. * Start moving horizontally
  17022. * @param {Event} event
  17023. * @private
  17024. */
  17025. CustomTime.prototype._onDragStart = function(event) {
  17026. this.eventParams.dragging = true;
  17027. this.eventParams.customTime = this.customTime;
  17028. event.stopPropagation();
  17029. event.preventDefault();
  17030. };
  17031. /**
  17032. * Perform moving operating.
  17033. * @param {Event} event
  17034. * @private
  17035. */
  17036. CustomTime.prototype._onDrag = function (event) {
  17037. if (!this.eventParams.dragging) return;
  17038. var deltaX = event.gesture.deltaX,
  17039. x = this.body.util.toScreen(this.eventParams.customTime) + deltaX,
  17040. time = this.body.util.toTime(x);
  17041. this.setCustomTime(time);
  17042. // fire a timechange event
  17043. this.body.emitter.emit('timechange', {
  17044. time: new Date(this.customTime.valueOf())
  17045. });
  17046. event.stopPropagation();
  17047. event.preventDefault();
  17048. };
  17049. /**
  17050. * Stop moving operating.
  17051. * @param {event} event
  17052. * @private
  17053. */
  17054. CustomTime.prototype._onDragEnd = function (event) {
  17055. if (!this.eventParams.dragging) return;
  17056. // fire a timechanged event
  17057. this.body.emitter.emit('timechanged', {
  17058. time: new Date(this.customTime.valueOf())
  17059. });
  17060. event.stopPropagation();
  17061. event.preventDefault();
  17062. };
  17063. module.exports = CustomTime;
  17064. /***/ },
  17065. /* 42 */
  17066. /***/ function(module, exports, __webpack_require__) {
  17067. var Emitter = __webpack_require__(11);
  17068. var Hammer = __webpack_require__(19);
  17069. var util = __webpack_require__(1);
  17070. var DataSet = __webpack_require__(7);
  17071. var DataView = __webpack_require__(9);
  17072. var Range = __webpack_require__(21);
  17073. var Core = __webpack_require__(25);
  17074. var TimeAxis = __webpack_require__(37);
  17075. var CurrentTime = __webpack_require__(39);
  17076. var CustomTime = __webpack_require__(41);
  17077. var LineGraph = __webpack_require__(43);
  17078. /**
  17079. * Create a timeline visualization
  17080. * @param {HTMLElement} container
  17081. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  17082. * @param {Object} [options] See Graph2d.setOptions for the available options.
  17083. * @constructor
  17084. * @extends Core
  17085. */
  17086. function Graph2d (container, items, groups, options) {
  17087. // if the third element is options, the forth is groups (optionally);
  17088. if (!(Array.isArray(groups) || groups instanceof DataSet) && groups instanceof Object) {
  17089. var forthArgument = options;
  17090. options = groups;
  17091. groups = forthArgument;
  17092. }
  17093. var me = this;
  17094. this.defaultOptions = {
  17095. start: null,
  17096. end: null,
  17097. autoResize: true,
  17098. orientation: 'bottom',
  17099. width: null,
  17100. height: null,
  17101. maxHeight: null,
  17102. minHeight: null
  17103. };
  17104. this.options = util.deepExtend({}, this.defaultOptions);
  17105. // Create the DOM, props, and emitter
  17106. this._create(container);
  17107. // all components listed here will be repainted automatically
  17108. this.components = [];
  17109. this.body = {
  17110. dom: this.dom,
  17111. domProps: this.props,
  17112. emitter: {
  17113. on: this.on.bind(this),
  17114. off: this.off.bind(this),
  17115. emit: this.emit.bind(this)
  17116. },
  17117. hiddenDates: [],
  17118. util: {
  17119. snap: null, // will be specified after TimeAxis is created
  17120. toScreen: me._toScreen.bind(me),
  17121. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  17122. toTime: me._toTime.bind(me),
  17123. toGlobalTime : me._toGlobalTime.bind(me)
  17124. }
  17125. };
  17126. // range
  17127. this.range = new Range(this.body);
  17128. this.components.push(this.range);
  17129. this.body.range = this.range;
  17130. // time axis
  17131. this.timeAxis = new TimeAxis(this.body);
  17132. this.components.push(this.timeAxis);
  17133. this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
  17134. // current time bar
  17135. this.currentTime = new CurrentTime(this.body);
  17136. this.components.push(this.currentTime);
  17137. // custom time bar
  17138. // Note: time bar will be attached in this.setOptions when selected
  17139. this.customTime = new CustomTime(this.body);
  17140. this.components.push(this.customTime);
  17141. // item set
  17142. this.linegraph = new LineGraph(this.body);
  17143. this.components.push(this.linegraph);
  17144. this.itemsData = null; // DataSet
  17145. this.groupsData = null; // DataSet
  17146. // apply options
  17147. if (options) {
  17148. this.setOptions(options);
  17149. }
  17150. // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
  17151. if (groups) {
  17152. this.setGroups(groups);
  17153. }
  17154. // create itemset
  17155. if (items) {
  17156. this.setItems(items);
  17157. }
  17158. else {
  17159. this.redraw();
  17160. }
  17161. }
  17162. // Extend the functionality from Core
  17163. Graph2d.prototype = new Core();
  17164. /**
  17165. * Set items
  17166. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  17167. */
  17168. Graph2d.prototype.setItems = function(items) {
  17169. var initialLoad = (this.itemsData == null);
  17170. // convert to type DataSet when needed
  17171. var newDataSet;
  17172. if (!items) {
  17173. newDataSet = null;
  17174. }
  17175. else if (items instanceof DataSet || items instanceof DataView) {
  17176. newDataSet = items;
  17177. }
  17178. else {
  17179. // turn an array into a dataset
  17180. newDataSet = new DataSet(items, {
  17181. type: {
  17182. start: 'Date',
  17183. end: 'Date'
  17184. }
  17185. });
  17186. }
  17187. // set items
  17188. this.itemsData = newDataSet;
  17189. this.linegraph && this.linegraph.setItems(newDataSet);
  17190. if (initialLoad) {
  17191. if (this.options.start != undefined || this.options.end != undefined) {
  17192. var start = this.options.start != undefined ? this.options.start : null;
  17193. var end = this.options.end != undefined ? this.options.end : null;
  17194. this.setWindow(start, end, {animate: false});
  17195. }
  17196. else {
  17197. this.fit({animate: false});
  17198. }
  17199. }
  17200. };
  17201. /**
  17202. * Set groups
  17203. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  17204. */
  17205. Graph2d.prototype.setGroups = function(groups) {
  17206. // convert to type DataSet when needed
  17207. var newDataSet;
  17208. if (!groups) {
  17209. newDataSet = null;
  17210. }
  17211. else if (groups instanceof DataSet || groups instanceof DataView) {
  17212. newDataSet = groups;
  17213. }
  17214. else {
  17215. // turn an array into a dataset
  17216. newDataSet = new DataSet(groups);
  17217. }
  17218. this.groupsData = newDataSet;
  17219. this.linegraph.setGroups(newDataSet);
  17220. };
  17221. /**
  17222. * 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).
  17223. * @param groupId
  17224. * @param width
  17225. * @param height
  17226. */
  17227. Graph2d.prototype.getLegend = function(groupId, width, height) {
  17228. if (width === undefined) {width = 15;}
  17229. if (height === undefined) {height = 15;}
  17230. if (this.linegraph.groups[groupId] !== undefined) {
  17231. return this.linegraph.groups[groupId].getLegend(width,height);
  17232. }
  17233. else {
  17234. return "cannot find group:" + groupId;
  17235. }
  17236. }
  17237. /**
  17238. * This checks if the visible option of the supplied group (by ID) is true or false.
  17239. * @param groupId
  17240. * @returns {*}
  17241. */
  17242. Graph2d.prototype.isGroupVisible = function(groupId) {
  17243. if (this.linegraph.groups[groupId] !== undefined) {
  17244. return (this.linegraph.groups[groupId].visible && (this.linegraph.options.groups.visibility[groupId] === undefined || this.linegraph.options.groups.visibility[groupId] == true));
  17245. }
  17246. else {
  17247. return false;
  17248. }
  17249. }
  17250. /**
  17251. * Get the data range of the item set.
  17252. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  17253. * When no minimum is found, min==null
  17254. * When no maximum is found, max==null
  17255. */
  17256. Graph2d.prototype.getItemRange = function() {
  17257. var min = null;
  17258. var max = null;
  17259. // calculate min from start filed
  17260. for (var groupId in this.linegraph.groups) {
  17261. if (this.linegraph.groups.hasOwnProperty(groupId)) {
  17262. if (this.linegraph.groups[groupId].visible == true) {
  17263. for (var i = 0; i < this.linegraph.groups[groupId].itemsData.length; i++) {
  17264. var item = this.linegraph.groups[groupId].itemsData[i];
  17265. var value = util.convert(item.x, 'Date').valueOf();
  17266. min = min == null ? value : min > value ? value : min;
  17267. max = max == null ? value : max < value ? value : max;
  17268. }
  17269. }
  17270. }
  17271. }
  17272. return {
  17273. min: (min != null) ? new Date(min) : null,
  17274. max: (max != null) ? new Date(max) : null
  17275. };
  17276. };
  17277. module.exports = Graph2d;
  17278. /***/ },
  17279. /* 43 */
  17280. /***/ function(module, exports, __webpack_require__) {
  17281. var util = __webpack_require__(1);
  17282. var DOMutil = __webpack_require__(6);
  17283. var DataSet = __webpack_require__(7);
  17284. var DataView = __webpack_require__(9);
  17285. var Component = __webpack_require__(23);
  17286. var DataAxis = __webpack_require__(44);
  17287. var GraphGroup = __webpack_require__(46);
  17288. var Legend = __webpack_require__(50);
  17289. var BarGraphFunctions = __webpack_require__(49);
  17290. var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
  17291. /**
  17292. * This is the constructor of the LineGraph. It requires a Timeline body and options.
  17293. *
  17294. * @param body
  17295. * @param options
  17296. * @constructor
  17297. */
  17298. function LineGraph(body, options) {
  17299. this.id = util.randomUUID();
  17300. this.body = body;
  17301. this.defaultOptions = {
  17302. yAxisOrientation: 'left',
  17303. defaultGroup: 'default',
  17304. sort: true,
  17305. sampling: true,
  17306. graphHeight: '400px',
  17307. shaded: {
  17308. enabled: false,
  17309. orientation: 'bottom' // top, bottom
  17310. },
  17311. style: 'line', // line, bar
  17312. barChart: {
  17313. width: 50,
  17314. handleOverlap: 'overlap',
  17315. align: 'center' // left, center, right
  17316. },
  17317. catmullRom: {
  17318. enabled: true,
  17319. parametrization: 'centripetal', // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5)
  17320. alpha: 0.5
  17321. },
  17322. drawPoints: {
  17323. enabled: true,
  17324. size: 6,
  17325. style: 'square' // square, circle
  17326. },
  17327. dataAxis: {
  17328. showMinorLabels: true,
  17329. showMajorLabels: true,
  17330. icons: false,
  17331. width: '40px',
  17332. visible: true,
  17333. alignZeros: true,
  17334. customRange: {
  17335. left: {min:undefined, max:undefined},
  17336. right: {min:undefined, max:undefined}
  17337. }
  17338. //, these options are not set by default, but this shows the format they will be in
  17339. //format: {
  17340. // left: {decimals: 2},
  17341. // right: {decimals: 2}
  17342. //},
  17343. //title: {
  17344. // left: {
  17345. // text: 'left',
  17346. // style: 'color:black;'
  17347. // },
  17348. // right: {
  17349. // text: 'right',
  17350. // style: 'color:black;'
  17351. // }
  17352. //}
  17353. },
  17354. legend: {
  17355. enabled: false,
  17356. icons: true,
  17357. left: {
  17358. visible: true,
  17359. position: 'top-left' // top/bottom - left,right
  17360. },
  17361. right: {
  17362. visible: true,
  17363. position: 'top-right' // top/bottom - left,right
  17364. }
  17365. },
  17366. groups: {
  17367. visibility: {}
  17368. }
  17369. };
  17370. // options is shared by this ItemSet and all its items
  17371. this.options = util.extend({}, this.defaultOptions);
  17372. this.dom = {};
  17373. this.props = {};
  17374. this.hammer = null;
  17375. this.groups = {};
  17376. this.abortedGraphUpdate = false;
  17377. this.updateSVGheight = false;
  17378. this.updateSVGheightOnResize = false;
  17379. var me = this;
  17380. this.itemsData = null; // DataSet
  17381. this.groupsData = null; // DataSet
  17382. // listeners for the DataSet of the items
  17383. this.itemListeners = {
  17384. 'add': function (event, params, senderId) {
  17385. me._onAdd(params.items);
  17386. },
  17387. 'update': function (event, params, senderId) {
  17388. me._onUpdate(params.items);
  17389. },
  17390. 'remove': function (event, params, senderId) {
  17391. me._onRemove(params.items);
  17392. }
  17393. };
  17394. // listeners for the DataSet of the groups
  17395. this.groupListeners = {
  17396. 'add': function (event, params, senderId) {
  17397. me._onAddGroups(params.items);
  17398. },
  17399. 'update': function (event, params, senderId) {
  17400. me._onUpdateGroups(params.items);
  17401. },
  17402. 'remove': function (event, params, senderId) {
  17403. me._onRemoveGroups(params.items);
  17404. }
  17405. };
  17406. this.items = {}; // object with an Item for every data item
  17407. this.selection = []; // list with the ids of all selected nodes
  17408. this.lastStart = this.body.range.start;
  17409. this.touchParams = {}; // stores properties while dragging
  17410. this.svgElements = {};
  17411. this.setOptions(options);
  17412. this.groupsUsingDefaultStyles = [0];
  17413. this.COUNTER = 0;
  17414. this.body.emitter.on('rangechanged', function() {
  17415. me.lastStart = me.body.range.start;
  17416. me.svg.style.left = util.option.asSize(-me.props.width);
  17417. me.redraw.call(me,true);
  17418. });
  17419. // create the HTML DOM
  17420. this._create();
  17421. this.framework = {svg: this.svg, svgElements: this.svgElements, options: this.options, groups: this.groups};
  17422. this.body.emitter.emit('change');
  17423. }
  17424. LineGraph.prototype = new Component();
  17425. /**
  17426. * Create the HTML DOM for the ItemSet
  17427. */
  17428. LineGraph.prototype._create = function(){
  17429. var frame = document.createElement('div');
  17430. frame.className = 'LineGraph';
  17431. this.dom.frame = frame;
  17432. // create svg element for graph drawing.
  17433. this.svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
  17434. this.svg.style.position = 'relative';
  17435. this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px';
  17436. this.svg.style.display = 'block';
  17437. frame.appendChild(this.svg);
  17438. // data axis
  17439. this.options.dataAxis.orientation = 'left';
  17440. this.yAxisLeft = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
  17441. this.options.dataAxis.orientation = 'right';
  17442. this.yAxisRight = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
  17443. delete this.options.dataAxis.orientation;
  17444. // legends
  17445. this.legendLeft = new Legend(this.body, this.options.legend, 'left', this.options.groups);
  17446. this.legendRight = new Legend(this.body, this.options.legend, 'right', this.options.groups);
  17447. this.show();
  17448. };
  17449. /**
  17450. * set the options of the LineGraph. the mergeOptions is used for subObjects that have an enabled element.
  17451. * @param {object} options
  17452. */
  17453. LineGraph.prototype.setOptions = function(options) {
  17454. if (options) {
  17455. var fields = ['sampling','defaultGroup','height','graphHeight','yAxisOrientation','style','barChart','dataAxis','sort','groups'];
  17456. if (options.graphHeight === undefined && options.height !== undefined && this.body.domProps.centerContainer.height !== undefined) {
  17457. this.updateSVGheight = true;
  17458. this.updateSVGheightOnResize = true;
  17459. }
  17460. else if (this.body.domProps.centerContainer.height !== undefined && options.graphHeight !== undefined) {
  17461. if (parseInt((options.graphHeight + '').replace("px",'')) < this.body.domProps.centerContainer.height) {
  17462. this.updateSVGheight = true;
  17463. }
  17464. }
  17465. util.selectiveDeepExtend(fields, this.options, options);
  17466. util.mergeOptions(this.options, options,'catmullRom');
  17467. util.mergeOptions(this.options, options,'drawPoints');
  17468. util.mergeOptions(this.options, options,'shaded');
  17469. util.mergeOptions(this.options, options,'legend');
  17470. if (options.catmullRom) {
  17471. if (typeof options.catmullRom == 'object') {
  17472. if (options.catmullRom.parametrization) {
  17473. if (options.catmullRom.parametrization == 'uniform') {
  17474. this.options.catmullRom.alpha = 0;
  17475. }
  17476. else if (options.catmullRom.parametrization == 'chordal') {
  17477. this.options.catmullRom.alpha = 1.0;
  17478. }
  17479. else {
  17480. this.options.catmullRom.parametrization = 'centripetal';
  17481. this.options.catmullRom.alpha = 0.5;
  17482. }
  17483. }
  17484. }
  17485. }
  17486. if (this.yAxisLeft) {
  17487. if (options.dataAxis !== undefined) {
  17488. this.yAxisLeft.setOptions(this.options.dataAxis);
  17489. this.yAxisRight.setOptions(this.options.dataAxis);
  17490. }
  17491. }
  17492. if (this.legendLeft) {
  17493. if (options.legend !== undefined) {
  17494. this.legendLeft.setOptions(this.options.legend);
  17495. this.legendRight.setOptions(this.options.legend);
  17496. }
  17497. }
  17498. if (this.groups.hasOwnProperty(UNGROUPED)) {
  17499. this.groups[UNGROUPED].setOptions(options);
  17500. }
  17501. }
  17502. // this is used to redraw the graph if the visibility of the groups is changed.
  17503. if (this.dom.frame) {
  17504. this.redraw(true);
  17505. }
  17506. };
  17507. /**
  17508. * Hide the component from the DOM
  17509. */
  17510. LineGraph.prototype.hide = function() {
  17511. // remove the frame containing the items
  17512. if (this.dom.frame.parentNode) {
  17513. this.dom.frame.parentNode.removeChild(this.dom.frame);
  17514. }
  17515. };
  17516. /**
  17517. * Show the component in the DOM (when not already visible).
  17518. * @return {Boolean} changed
  17519. */
  17520. LineGraph.prototype.show = function() {
  17521. // show frame containing the items
  17522. if (!this.dom.frame.parentNode) {
  17523. this.body.dom.center.appendChild(this.dom.frame);
  17524. }
  17525. };
  17526. /**
  17527. * Set items
  17528. * @param {vis.DataSet | null} items
  17529. */
  17530. LineGraph.prototype.setItems = function(items) {
  17531. var me = this,
  17532. ids,
  17533. oldItemsData = this.itemsData;
  17534. // replace the dataset
  17535. if (!items) {
  17536. this.itemsData = null;
  17537. }
  17538. else if (items instanceof DataSet || items instanceof DataView) {
  17539. this.itemsData = items;
  17540. }
  17541. else {
  17542. throw new TypeError('Data must be an instance of DataSet or DataView');
  17543. }
  17544. if (oldItemsData) {
  17545. // unsubscribe from old dataset
  17546. util.forEach(this.itemListeners, function (callback, event) {
  17547. oldItemsData.off(event, callback);
  17548. });
  17549. // remove all drawn items
  17550. ids = oldItemsData.getIds();
  17551. this._onRemove(ids);
  17552. }
  17553. if (this.itemsData) {
  17554. // subscribe to new dataset
  17555. var id = this.id;
  17556. util.forEach(this.itemListeners, function (callback, event) {
  17557. me.itemsData.on(event, callback, id);
  17558. });
  17559. // add all new items
  17560. ids = this.itemsData.getIds();
  17561. this._onAdd(ids);
  17562. }
  17563. this._updateUngrouped();
  17564. //this._updateGraph();
  17565. this.redraw(true);
  17566. };
  17567. /**
  17568. * Set groups
  17569. * @param {vis.DataSet} groups
  17570. */
  17571. LineGraph.prototype.setGroups = function(groups) {
  17572. var me = this;
  17573. var ids;
  17574. // unsubscribe from current dataset
  17575. if (this.groupsData) {
  17576. util.forEach(this.groupListeners, function (callback, event) {
  17577. me.groupsData.unsubscribe(event, callback);
  17578. });
  17579. // remove all drawn groups
  17580. ids = this.groupsData.getIds();
  17581. this.groupsData = null;
  17582. this._onRemoveGroups(ids); // note: this will cause a redraw
  17583. }
  17584. // replace the dataset
  17585. if (!groups) {
  17586. this.groupsData = null;
  17587. }
  17588. else if (groups instanceof DataSet || groups instanceof DataView) {
  17589. this.groupsData = groups;
  17590. }
  17591. else {
  17592. throw new TypeError('Data must be an instance of DataSet or DataView');
  17593. }
  17594. if (this.groupsData) {
  17595. // subscribe to new dataset
  17596. var id = this.id;
  17597. util.forEach(this.groupListeners, function (callback, event) {
  17598. me.groupsData.on(event, callback, id);
  17599. });
  17600. // draw all ms
  17601. ids = this.groupsData.getIds();
  17602. this._onAddGroups(ids);
  17603. }
  17604. this._onUpdate();
  17605. };
  17606. /**
  17607. * Update the data
  17608. * @param [ids]
  17609. * @private
  17610. */
  17611. LineGraph.prototype._onUpdate = function(ids) {
  17612. this._updateUngrouped();
  17613. this._updateAllGroupData();
  17614. //this._updateGraph();
  17615. this.redraw(true);
  17616. };
  17617. LineGraph.prototype._onAdd = function (ids) {this._onUpdate(ids);};
  17618. LineGraph.prototype._onRemove = function (ids) {this._onUpdate(ids);};
  17619. LineGraph.prototype._onUpdateGroups = function (groupIds) {
  17620. for (var i = 0; i < groupIds.length; i++) {
  17621. var group = this.groupsData.get(groupIds[i]);
  17622. this._updateGroup(group, groupIds[i]);
  17623. }
  17624. //this._updateGraph();
  17625. this.redraw(true);
  17626. };
  17627. LineGraph.prototype._onAddGroups = function (groupIds) {this._onUpdateGroups(groupIds);};
  17628. /**
  17629. * this cleans the group out off the legends and the dataaxis, updates the ungrouped and updates the graph
  17630. * @param {Array} groupIds
  17631. * @private
  17632. */
  17633. LineGraph.prototype._onRemoveGroups = function (groupIds) {
  17634. for (var i = 0; i < groupIds.length; i++) {
  17635. if (this.groups.hasOwnProperty(groupIds[i])) {
  17636. if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') {
  17637. this.yAxisRight.removeGroup(groupIds[i]);
  17638. this.legendRight.removeGroup(groupIds[i]);
  17639. this.legendRight.redraw();
  17640. }
  17641. else {
  17642. this.yAxisLeft.removeGroup(groupIds[i]);
  17643. this.legendLeft.removeGroup(groupIds[i]);
  17644. this.legendLeft.redraw();
  17645. }
  17646. delete this.groups[groupIds[i]];
  17647. }
  17648. }
  17649. this._updateUngrouped();
  17650. //this._updateGraph();
  17651. this.redraw(true);
  17652. };
  17653. /**
  17654. * update a group object with the group dataset entree
  17655. *
  17656. * @param group
  17657. * @param groupId
  17658. * @private
  17659. */
  17660. LineGraph.prototype._updateGroup = function (group, groupId) {
  17661. if (!this.groups.hasOwnProperty(groupId)) {
  17662. this.groups[groupId] = new GraphGroup(group, groupId, this.options, this.groupsUsingDefaultStyles);
  17663. if (this.groups[groupId].options.yAxisOrientation == 'right') {
  17664. this.yAxisRight.addGroup(groupId, this.groups[groupId]);
  17665. this.legendRight.addGroup(groupId, this.groups[groupId]);
  17666. }
  17667. else {
  17668. this.yAxisLeft.addGroup(groupId, this.groups[groupId]);
  17669. this.legendLeft.addGroup(groupId, this.groups[groupId]);
  17670. }
  17671. }
  17672. else {
  17673. this.groups[groupId].update(group);
  17674. if (this.groups[groupId].options.yAxisOrientation == 'right') {
  17675. this.yAxisRight.updateGroup(groupId, this.groups[groupId]);
  17676. this.legendRight.updateGroup(groupId, this.groups[groupId]);
  17677. }
  17678. else {
  17679. this.yAxisLeft.updateGroup(groupId, this.groups[groupId]);
  17680. this.legendLeft.updateGroup(groupId, this.groups[groupId]);
  17681. }
  17682. }
  17683. this.legendLeft.redraw();
  17684. this.legendRight.redraw();
  17685. };
  17686. /**
  17687. * this updates all groups, it is used when there is an update the the itemset.
  17688. *
  17689. * @private
  17690. */
  17691. LineGraph.prototype._updateAllGroupData = function () {
  17692. if (this.itemsData != null) {
  17693. var groupsContent = {};
  17694. var groupId;
  17695. for (groupId in this.groups) {
  17696. if (this.groups.hasOwnProperty(groupId)) {
  17697. groupsContent[groupId] = [];
  17698. }
  17699. }
  17700. for (var itemId in this.itemsData._data) {
  17701. if (this.itemsData._data.hasOwnProperty(itemId)) {
  17702. var item = this.itemsData._data[itemId];
  17703. if (groupsContent[item.group] === undefined) {
  17704. 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.')
  17705. }
  17706. item.x = util.convert(item.x,'Date');
  17707. groupsContent[item.group].push(item);
  17708. }
  17709. }
  17710. for (groupId in this.groups) {
  17711. if (this.groups.hasOwnProperty(groupId)) {
  17712. this.groups[groupId].setItems(groupsContent[groupId]);
  17713. }
  17714. }
  17715. }
  17716. };
  17717. /**
  17718. * Create or delete the group holding all ungrouped items. This group is used when
  17719. * there are no groups specified. This anonymous group is called 'graph'.
  17720. * @protected
  17721. */
  17722. LineGraph.prototype._updateUngrouped = function() {
  17723. if (this.itemsData && this.itemsData != null) {
  17724. var ungroupedCounter = 0;
  17725. for (var itemId in this.itemsData._data) {
  17726. if (this.itemsData._data.hasOwnProperty(itemId)) {
  17727. var item = this.itemsData._data[itemId];
  17728. if (item != undefined) {
  17729. if (item.hasOwnProperty('group')) {
  17730. if (item.group === undefined) {
  17731. item.group = UNGROUPED;
  17732. }
  17733. }
  17734. else {
  17735. item.group = UNGROUPED;
  17736. }
  17737. ungroupedCounter = item.group == UNGROUPED ? ungroupedCounter + 1 : ungroupedCounter;
  17738. }
  17739. }
  17740. }
  17741. if (ungroupedCounter == 0) {
  17742. delete this.groups[UNGROUPED];
  17743. this.legendLeft.removeGroup(UNGROUPED);
  17744. this.legendRight.removeGroup(UNGROUPED);
  17745. this.yAxisLeft.removeGroup(UNGROUPED);
  17746. this.yAxisRight.removeGroup(UNGROUPED);
  17747. }
  17748. else {
  17749. var group = {id: UNGROUPED, content: this.options.defaultGroup};
  17750. this._updateGroup(group, UNGROUPED);
  17751. }
  17752. }
  17753. else {
  17754. delete this.groups[UNGROUPED];
  17755. this.legendLeft.removeGroup(UNGROUPED);
  17756. this.legendRight.removeGroup(UNGROUPED);
  17757. this.yAxisLeft.removeGroup(UNGROUPED);
  17758. this.yAxisRight.removeGroup(UNGROUPED);
  17759. }
  17760. this.legendLeft.redraw();
  17761. this.legendRight.redraw();
  17762. };
  17763. /**
  17764. * Redraw the component, mandatory function
  17765. * @return {boolean} Returns true if the component is resized
  17766. */
  17767. LineGraph.prototype.redraw = function(forceGraphUpdate) {
  17768. var resized = false;
  17769. // calculate actual size and position
  17770. this.props.width = this.dom.frame.offsetWidth;
  17771. this.props.height = this.body.domProps.centerContainer.height;
  17772. // update the graph if there is no lastWidth or with, used for the initial draw
  17773. if (this.lastWidth === undefined && this.props.width) {
  17774. forceGraphUpdate = true;
  17775. }
  17776. // check if this component is resized
  17777. resized = this._isResized() || resized;
  17778. // check whether zoomed (in that case we need to re-stack everything)
  17779. var visibleInterval = this.body.range.end - this.body.range.start;
  17780. var zoomed = (visibleInterval != this.lastVisibleInterval);
  17781. this.lastVisibleInterval = visibleInterval;
  17782. // the svg element is three times as big as the width, this allows for fully dragging left and right
  17783. // without reloading the graph. the controls for this are bound to events in the constructor
  17784. if (resized == true) {
  17785. this.svg.style.width = util.option.asSize(3*this.props.width);
  17786. this.svg.style.left = util.option.asSize(-this.props.width);
  17787. // if the height of the graph is set as proportional, change the height of the svg
  17788. if ((this.options.height + '').indexOf("%") != -1 || this.updateSVGheightOnResize == true) {
  17789. this.updateSVGheight = true;
  17790. }
  17791. }
  17792. // update the height of the graph on each redraw of the graph.
  17793. if (this.updateSVGheight == true) {
  17794. if (this.options.graphHeight != this.body.domProps.centerContainer.height + 'px') {
  17795. this.options.graphHeight = this.body.domProps.centerContainer.height + 'px';
  17796. this.svg.style.height = this.body.domProps.centerContainer.height + 'px';
  17797. }
  17798. this.updateSVGheight = false;
  17799. }
  17800. else {
  17801. this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px';
  17802. }
  17803. // zoomed is here to ensure that animations are shown correctly.
  17804. if (resized == true || zoomed == true || this.abortedGraphUpdate == true || forceGraphUpdate == true) {
  17805. resized = this._updateGraph() || resized;
  17806. }
  17807. else {
  17808. // move the whole svg while dragging
  17809. if (this.lastStart != 0) {
  17810. var offset = this.body.range.start - this.lastStart;
  17811. var range = this.body.range.end - this.body.range.start;
  17812. if (this.props.width != 0) {
  17813. var rangePerPixelInv = this.props.width/range;
  17814. var xOffset = offset * rangePerPixelInv;
  17815. this.svg.style.left = (-this.props.width - xOffset) + 'px';
  17816. }
  17817. }
  17818. }
  17819. this.legendLeft.redraw();
  17820. this.legendRight.redraw();
  17821. return resized;
  17822. };
  17823. /**
  17824. * Update and redraw the graph.
  17825. *
  17826. */
  17827. LineGraph.prototype._updateGraph = function () {
  17828. // reset the svg elements
  17829. DOMutil.prepareElements(this.svgElements);
  17830. if (this.props.width != 0 && this.itemsData != null) {
  17831. var group, i;
  17832. var preprocessedGroupData = {};
  17833. var processedGroupData = {};
  17834. var groupRanges = {};
  17835. var changeCalled = false;
  17836. // getting group Ids
  17837. var groupIds = [];
  17838. for (var groupId in this.groups) {
  17839. if (this.groups.hasOwnProperty(groupId)) {
  17840. group = this.groups[groupId];
  17841. if (group.visible == true && (this.options.groups.visibility[groupId] === undefined || this.options.groups.visibility[groupId] == true)) {
  17842. groupIds.push(groupId);
  17843. }
  17844. }
  17845. }
  17846. if (groupIds.length > 0) {
  17847. // this is the range of the SVG canvas
  17848. var minDate = this.body.util.toGlobalTime(-this.body.domProps.root.width);
  17849. var maxDate = this.body.util.toGlobalTime(2 * this.body.domProps.root.width);
  17850. var groupsData = {};
  17851. // fill groups data, this only loads the data we require based on the timewindow
  17852. this._getRelevantData(groupIds, groupsData, minDate, maxDate);
  17853. // apply sampling, if disabled, it will pass through this function.
  17854. this._applySampling(groupIds, groupsData);
  17855. // we transform the X coordinates to detect collisions
  17856. for (i = 0; i < groupIds.length; i++) {
  17857. preprocessedGroupData[groupIds[i]] = this._convertXcoordinates(groupsData[groupIds[i]]);
  17858. }
  17859. // now all needed data has been collected we start the processing.
  17860. this._getYRanges(groupIds, preprocessedGroupData, groupRanges);
  17861. // update the Y axis first, we use this data to draw at the correct Y points
  17862. // changeCalled is required to clean the SVG on a change emit.
  17863. changeCalled = this._updateYAxis(groupIds, groupRanges);
  17864. var MAX_CYCLES = 5;
  17865. if (changeCalled == true && this.COUNTER < MAX_CYCLES) {
  17866. DOMutil.cleanupElements(this.svgElements);
  17867. this.abortedGraphUpdate = true;
  17868. this.COUNTER++;
  17869. this.body.emitter.emit('change');
  17870. return true;
  17871. }
  17872. else {
  17873. if (this.COUNTER > MAX_CYCLES) {
  17874. console.log("WARNING: there may be an infinite loop in the _updateGraph emitter cycle.")
  17875. }
  17876. this.COUNTER = 0;
  17877. this.abortedGraphUpdate = false;
  17878. // With the yAxis scaled correctly, use this to get the Y values of the points.
  17879. for (i = 0; i < groupIds.length; i++) {
  17880. group = this.groups[groupIds[i]];
  17881. processedGroupData[groupIds[i]] = this._convertYcoordinates(groupsData[groupIds[i]], group);
  17882. }
  17883. // draw the groups
  17884. for (i = 0; i < groupIds.length; i++) {
  17885. group = this.groups[groupIds[i]];
  17886. if (group.options.style != 'bar') { // bar needs to be drawn enmasse
  17887. group.draw(processedGroupData[groupIds[i]], group, this.framework);
  17888. }
  17889. }
  17890. BarGraphFunctions.draw(groupIds, processedGroupData, this.framework);
  17891. }
  17892. }
  17893. }
  17894. // cleanup unused svg elements
  17895. DOMutil.cleanupElements(this.svgElements);
  17896. return false;
  17897. };
  17898. /**
  17899. * first select and preprocess the data from the datasets.
  17900. * the groups have their preselection of data, we now loop over this data to see
  17901. * what data we need to draw. Sorted data is much faster.
  17902. * more optimization is possible by doing the sampling before and using the binary search
  17903. * to find the end date to determine the increment.
  17904. *
  17905. * @param {array} groupIds
  17906. * @param {object} groupsData
  17907. * @param {date} minDate
  17908. * @param {date} maxDate
  17909. * @private
  17910. */
  17911. LineGraph.prototype._getRelevantData = function (groupIds, groupsData, minDate, maxDate) {
  17912. var group, i, j, item;
  17913. if (groupIds.length > 0) {
  17914. for (i = 0; i < groupIds.length; i++) {
  17915. group = this.groups[groupIds[i]];
  17916. groupsData[groupIds[i]] = [];
  17917. var dataContainer = groupsData[groupIds[i]];
  17918. // optimization for sorted data
  17919. if (group.options.sort == true) {
  17920. var guess = Math.max(0, util.binarySearchValue(group.itemsData, minDate, 'x', 'before'));
  17921. for (j = guess; j < group.itemsData.length; j++) {
  17922. item = group.itemsData[j];
  17923. if (item !== undefined) {
  17924. if (item.x > maxDate) {
  17925. dataContainer.push(item);
  17926. break;
  17927. }
  17928. else {
  17929. dataContainer.push(item);
  17930. }
  17931. }
  17932. }
  17933. }
  17934. else {
  17935. for (j = 0; j < group.itemsData.length; j++) {
  17936. item = group.itemsData[j];
  17937. if (item !== undefined) {
  17938. if (item.x > minDate && item.x < maxDate) {
  17939. dataContainer.push(item);
  17940. }
  17941. }
  17942. }
  17943. }
  17944. }
  17945. }
  17946. };
  17947. /**
  17948. *
  17949. * @param groupIds
  17950. * @param groupsData
  17951. * @private
  17952. */
  17953. LineGraph.prototype._applySampling = function (groupIds, groupsData) {
  17954. var group;
  17955. if (groupIds.length > 0) {
  17956. for (var i = 0; i < groupIds.length; i++) {
  17957. group = this.groups[groupIds[i]];
  17958. if (group.options.sampling == true) {
  17959. var dataContainer = groupsData[groupIds[i]];
  17960. if (dataContainer.length > 0) {
  17961. var increment = 1;
  17962. var amountOfPoints = dataContainer.length;
  17963. // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop
  17964. // of width changing of the yAxis.
  17965. var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x);
  17966. var pointsPerPixel = amountOfPoints / xDistance;
  17967. increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel)));
  17968. var sampledData = [];
  17969. for (var j = 0; j < amountOfPoints; j += increment) {
  17970. sampledData.push(dataContainer[j]);
  17971. }
  17972. groupsData[groupIds[i]] = sampledData;
  17973. }
  17974. }
  17975. }
  17976. }
  17977. };
  17978. /**
  17979. *
  17980. *
  17981. * @param {array} groupIds
  17982. * @param {object} groupsData
  17983. * @param {object} groupRanges | this is being filled here
  17984. * @private
  17985. */
  17986. LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) {
  17987. var groupData, group, i;
  17988. var barCombinedDataLeft = [];
  17989. var barCombinedDataRight = [];
  17990. var options;
  17991. if (groupIds.length > 0) {
  17992. for (i = 0; i < groupIds.length; i++) {
  17993. groupData = groupsData[groupIds[i]];
  17994. options = this.groups[groupIds[i]].options;
  17995. if (groupData.length > 0) {
  17996. group = this.groups[groupIds[i]];
  17997. // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups.
  17998. if (options.barChart.handleOverlap == 'stack' && options.style == 'bar') {
  17999. if (options.yAxisOrientation == 'left') {barCombinedDataLeft = barCombinedDataLeft.concat(group.getYRange(groupData)) ;}
  18000. else {barCombinedDataRight = barCombinedDataRight.concat(group.getYRange(groupData));}
  18001. }
  18002. else {
  18003. groupRanges[groupIds[i]] = group.getYRange(groupData,groupIds[i]);
  18004. }
  18005. }
  18006. }
  18007. // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups.
  18008. BarGraphFunctions.getStackedBarYRange(barCombinedDataLeft , groupRanges, groupIds, '__barchartLeft' , 'left' );
  18009. BarGraphFunctions.getStackedBarYRange(barCombinedDataRight, groupRanges, groupIds, '__barchartRight', 'right');
  18010. }
  18011. };
  18012. /**
  18013. * this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden.
  18014. * @param {Array} groupIds
  18015. * @param {Object} groupRanges
  18016. * @private
  18017. */
  18018. LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
  18019. var resized = false;
  18020. var yAxisLeftUsed = false;
  18021. var yAxisRightUsed = false;
  18022. var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal;
  18023. // if groups are present
  18024. if (groupIds.length > 0) {
  18025. // 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.
  18026. for (var i = 0; i < groupIds.length; i++) {
  18027. var group = this.groups[groupIds[i]];
  18028. if (group && group.options.yAxisOrientation != 'right') {
  18029. yAxisLeftUsed = true;
  18030. minLeft = 0;
  18031. maxLeft = 0;
  18032. }
  18033. else if (group && group.options.yAxisOrientation) {
  18034. yAxisRightUsed = true;
  18035. minRight = 0;
  18036. maxRight = 0;
  18037. }
  18038. }
  18039. // if there are items:
  18040. for (var i = 0; i < groupIds.length; i++) {
  18041. if (groupRanges.hasOwnProperty(groupIds[i])) {
  18042. if (groupRanges[groupIds[i]].ignore !== true) {
  18043. minVal = groupRanges[groupIds[i]].min;
  18044. maxVal = groupRanges[groupIds[i]].max;
  18045. if (groupRanges[groupIds[i]].yAxisOrientation != 'right') {
  18046. yAxisLeftUsed = true;
  18047. minLeft = minLeft > minVal ? minVal : minLeft;
  18048. maxLeft = maxLeft < maxVal ? maxVal : maxLeft;
  18049. }
  18050. else {
  18051. yAxisRightUsed = true;
  18052. minRight = minRight > minVal ? minVal : minRight;
  18053. maxRight = maxRight < maxVal ? maxVal : maxRight;
  18054. }
  18055. }
  18056. }
  18057. }
  18058. if (yAxisLeftUsed == true) {
  18059. this.yAxisLeft.setRange(minLeft, maxLeft);
  18060. }
  18061. if (yAxisRightUsed == true) {
  18062. this.yAxisRight.setRange(minRight, maxRight);
  18063. }
  18064. }
  18065. resized = this._toggleAxisVisiblity(yAxisLeftUsed , this.yAxisLeft) || resized;
  18066. resized = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || resized;
  18067. if (yAxisRightUsed == true && yAxisLeftUsed == true) {
  18068. this.yAxisLeft.drawIcons = true;
  18069. this.yAxisRight.drawIcons = true;
  18070. }
  18071. else {
  18072. this.yAxisLeft.drawIcons = false;
  18073. this.yAxisRight.drawIcons = false;
  18074. }
  18075. this.yAxisRight.master = !yAxisLeftUsed;
  18076. if (this.yAxisRight.master == false) {
  18077. if (yAxisRightUsed == true) {this.yAxisLeft.lineOffset = this.yAxisRight.width;}
  18078. else {this.yAxisLeft.lineOffset = 0;}
  18079. resized = this.yAxisLeft.redraw() || resized;
  18080. this.yAxisRight.stepPixelsForced = this.yAxisLeft.stepPixels;
  18081. this.yAxisRight.zeroCrossing = this.yAxisLeft.zeroCrossing;
  18082. resized = this.yAxisRight.redraw() || resized;
  18083. }
  18084. else {
  18085. resized = this.yAxisRight.redraw() || resized;
  18086. }
  18087. // clean the accumulated lists
  18088. if (groupIds.indexOf('__barchartLeft') != -1) {
  18089. groupIds.splice(groupIds.indexOf('__barchartLeft'),1);
  18090. }
  18091. if (groupIds.indexOf('__barchartRight') != -1) {
  18092. groupIds.splice(groupIds.indexOf('__barchartRight'),1);
  18093. }
  18094. return resized;
  18095. };
  18096. /**
  18097. * This shows or hides the Y axis if needed. If there is a change, the changed event is emitted by the updateYAxis function
  18098. *
  18099. * @param {boolean} axisUsed
  18100. * @returns {boolean}
  18101. * @private
  18102. * @param axis
  18103. */
  18104. LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) {
  18105. var changed = false;
  18106. if (axisUsed == false) {
  18107. if (axis.dom.frame.parentNode && axis.hidden == false) {
  18108. axis.hide()
  18109. changed = true;
  18110. }
  18111. }
  18112. else {
  18113. if (!axis.dom.frame.parentNode && axis.hidden == true) {
  18114. axis.show();
  18115. changed = true;
  18116. }
  18117. }
  18118. return changed;
  18119. };
  18120. /**
  18121. * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
  18122. * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
  18123. * the yAxis.
  18124. *
  18125. * @param datapoints
  18126. * @returns {Array}
  18127. * @private
  18128. */
  18129. LineGraph.prototype._convertXcoordinates = function (datapoints) {
  18130. var extractedData = [];
  18131. var xValue, yValue;
  18132. var toScreen = this.body.util.toScreen;
  18133. for (var i = 0; i < datapoints.length; i++) {
  18134. xValue = toScreen(datapoints[i].x) + this.props.width;
  18135. yValue = datapoints[i].y;
  18136. extractedData.push({x: xValue, y: yValue});
  18137. }
  18138. return extractedData;
  18139. };
  18140. /**
  18141. * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
  18142. * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
  18143. * the yAxis.
  18144. *
  18145. * @param datapoints
  18146. * @param group
  18147. * @returns {Array}
  18148. * @private
  18149. */
  18150. LineGraph.prototype._convertYcoordinates = function (datapoints, group) {
  18151. var extractedData = [];
  18152. var xValue, yValue;
  18153. var toScreen = this.body.util.toScreen;
  18154. var axis = this.yAxisLeft;
  18155. var svgHeight = Number(this.svg.style.height.replace('px',''));
  18156. if (group.options.yAxisOrientation == 'right') {
  18157. axis = this.yAxisRight;
  18158. }
  18159. for (var i = 0; i < datapoints.length; i++) {
  18160. xValue = toScreen(datapoints[i].x) + this.props.width;
  18161. yValue = Math.round(axis.convertValue(datapoints[i].y));
  18162. extractedData.push({x: xValue, y: yValue});
  18163. }
  18164. group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0)));
  18165. return extractedData;
  18166. };
  18167. module.exports = LineGraph;
  18168. /***/ },
  18169. /* 44 */
  18170. /***/ function(module, exports, __webpack_require__) {
  18171. var util = __webpack_require__(1);
  18172. var DOMutil = __webpack_require__(6);
  18173. var Component = __webpack_require__(23);
  18174. var DataStep = __webpack_require__(45);
  18175. /**
  18176. * A horizontal time axis
  18177. * @param {Object} [options] See DataAxis.setOptions for the available
  18178. * options.
  18179. * @constructor DataAxis
  18180. * @extends Component
  18181. * @param body
  18182. */
  18183. function DataAxis (body, options, svg, linegraphOptions) {
  18184. this.id = util.randomUUID();
  18185. this.body = body;
  18186. this.defaultOptions = {
  18187. orientation: 'left', // supported: 'left', 'right'
  18188. showMinorLabels: true,
  18189. showMajorLabels: true,
  18190. icons: true,
  18191. majorLinesOffset: 7,
  18192. minorLinesOffset: 4,
  18193. labelOffsetX: 10,
  18194. labelOffsetY: 2,
  18195. iconWidth: 20,
  18196. width: '40px',
  18197. visible: true,
  18198. alignZeros: true,
  18199. customRange: {
  18200. left: {min:undefined, max:undefined},
  18201. right: {min:undefined, max:undefined}
  18202. },
  18203. title: {
  18204. left: {text:undefined},
  18205. right: {text:undefined}
  18206. },
  18207. format: {
  18208. left: {decimals: undefined},
  18209. right: {decimals: undefined}
  18210. }
  18211. };
  18212. this.linegraphOptions = linegraphOptions;
  18213. this.linegraphSVG = svg;
  18214. this.props = {};
  18215. this.DOMelements = { // dynamic elements
  18216. lines: {},
  18217. labels: {},
  18218. title: {}
  18219. };
  18220. this.dom = {};
  18221. this.range = {start:0, end:0};
  18222. this.options = util.extend({}, this.defaultOptions);
  18223. this.conversionFactor = 1;
  18224. this.setOptions(options);
  18225. this.width = Number(('' + this.options.width).replace("px",""));
  18226. this.minWidth = this.width;
  18227. this.height = this.linegraphSVG.offsetHeight;
  18228. this.hidden = false;
  18229. this.stepPixels = 25;
  18230. this.stepPixelsForced = 25;
  18231. this.zeroCrossing = -1;
  18232. this.lineOffset = 0;
  18233. this.master = true;
  18234. this.svgElements = {};
  18235. this.iconsRemoved = false;
  18236. this.groups = {};
  18237. this.amountOfGroups = 0;
  18238. // create the HTML DOM
  18239. this._create();
  18240. var me = this;
  18241. this.body.emitter.on("verticalDrag", function() {
  18242. me.dom.lineContainer.style.top = me.body.domProps.scrollTop + 'px';
  18243. });
  18244. }
  18245. DataAxis.prototype = new Component();
  18246. DataAxis.prototype.addGroup = function(label, graphOptions) {
  18247. if (!this.groups.hasOwnProperty(label)) {
  18248. this.groups[label] = graphOptions;
  18249. }
  18250. this.amountOfGroups += 1;
  18251. };
  18252. DataAxis.prototype.updateGroup = function(label, graphOptions) {
  18253. this.groups[label] = graphOptions;
  18254. };
  18255. DataAxis.prototype.removeGroup = function(label) {
  18256. if (this.groups.hasOwnProperty(label)) {
  18257. delete this.groups[label];
  18258. this.amountOfGroups -= 1;
  18259. }
  18260. };
  18261. DataAxis.prototype.setOptions = function (options) {
  18262. if (options) {
  18263. var redraw = false;
  18264. if (this.options.orientation != options.orientation && options.orientation !== undefined) {
  18265. redraw = true;
  18266. }
  18267. var fields = [
  18268. 'orientation',
  18269. 'showMinorLabels',
  18270. 'showMajorLabels',
  18271. 'icons',
  18272. 'majorLinesOffset',
  18273. 'minorLinesOffset',
  18274. 'labelOffsetX',
  18275. 'labelOffsetY',
  18276. 'iconWidth',
  18277. 'width',
  18278. 'visible',
  18279. 'customRange',
  18280. 'title',
  18281. 'format',
  18282. 'alignZeros'
  18283. ];
  18284. util.selectiveExtend(fields, this.options, options);
  18285. this.minWidth = Number(('' + this.options.width).replace("px",""));
  18286. if (redraw == true && this.dom.frame) {
  18287. this.hide();
  18288. this.show();
  18289. }
  18290. }
  18291. };
  18292. /**
  18293. * Create the HTML DOM for the DataAxis
  18294. */
  18295. DataAxis.prototype._create = function() {
  18296. this.dom.frame = document.createElement('div');
  18297. this.dom.frame.style.width = this.options.width;
  18298. this.dom.frame.style.height = this.height;
  18299. this.dom.lineContainer = document.createElement('div');
  18300. this.dom.lineContainer.style.width = '100%';
  18301. this.dom.lineContainer.style.height = this.height;
  18302. this.dom.lineContainer.style.position = 'relative';
  18303. // create svg element for graph drawing.
  18304. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  18305. this.svg.style.position = "absolute";
  18306. this.svg.style.top = '0px';
  18307. this.svg.style.height = '100%';
  18308. this.svg.style.width = '100%';
  18309. this.svg.style.display = "block";
  18310. this.dom.frame.appendChild(this.svg);
  18311. };
  18312. DataAxis.prototype._redrawGroupIcons = function () {
  18313. DOMutil.prepareElements(this.svgElements);
  18314. var x;
  18315. var iconWidth = this.options.iconWidth;
  18316. var iconHeight = 15;
  18317. var iconOffset = 4;
  18318. var y = iconOffset + 0.5 * iconHeight;
  18319. if (this.options.orientation == 'left') {
  18320. x = iconOffset;
  18321. }
  18322. else {
  18323. x = this.width - iconWidth - iconOffset;
  18324. }
  18325. for (var groupId in this.groups) {
  18326. if (this.groups.hasOwnProperty(groupId)) {
  18327. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  18328. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  18329. y += iconHeight + iconOffset;
  18330. }
  18331. }
  18332. }
  18333. DOMutil.cleanupElements(this.svgElements);
  18334. this.iconsRemoved = false;
  18335. };
  18336. DataAxis.prototype._cleanupIcons = function() {
  18337. if (this.iconsRemoved == false) {
  18338. DOMutil.prepareElements(this.svgElements);
  18339. DOMutil.cleanupElements(this.svgElements);
  18340. this.iconsRemoved = true;
  18341. }
  18342. }
  18343. /**
  18344. * Create the HTML DOM for the DataAxis
  18345. */
  18346. DataAxis.prototype.show = function() {
  18347. this.hidden = false;
  18348. if (!this.dom.frame.parentNode) {
  18349. if (this.options.orientation == 'left') {
  18350. this.body.dom.left.appendChild(this.dom.frame);
  18351. }
  18352. else {
  18353. this.body.dom.right.appendChild(this.dom.frame);
  18354. }
  18355. }
  18356. if (!this.dom.lineContainer.parentNode) {
  18357. this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer);
  18358. }
  18359. };
  18360. /**
  18361. * Create the HTML DOM for the DataAxis
  18362. */
  18363. DataAxis.prototype.hide = function() {
  18364. this.hidden = true;
  18365. if (this.dom.frame.parentNode) {
  18366. this.dom.frame.parentNode.removeChild(this.dom.frame);
  18367. }
  18368. if (this.dom.lineContainer.parentNode) {
  18369. this.dom.lineContainer.parentNode.removeChild(this.dom.lineContainer);
  18370. }
  18371. };
  18372. /**
  18373. * Set a range (start and end)
  18374. * @param end
  18375. * @param start
  18376. * @param end
  18377. */
  18378. DataAxis.prototype.setRange = function (start, end) {
  18379. if (this.master == false && this.options.alignZeros == true && this.zeroCrossing != -1) {
  18380. if (start > 0) {
  18381. start = 0;
  18382. }
  18383. }
  18384. this.range.start = start;
  18385. this.range.end = end;
  18386. };
  18387. /**
  18388. * Repaint the component
  18389. * @return {boolean} Returns true if the component is resized
  18390. */
  18391. DataAxis.prototype.redraw = function () {
  18392. var resized = false;
  18393. var activeGroups = 0;
  18394. // Make sure the line container adheres to the vertical scrolling.
  18395. this.dom.lineContainer.style.top = this.body.domProps.scrollTop + 'px';
  18396. for (var groupId in this.groups) {
  18397. if (this.groups.hasOwnProperty(groupId)) {
  18398. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  18399. activeGroups++;
  18400. }
  18401. }
  18402. }
  18403. if (this.amountOfGroups == 0 || activeGroups == 0) {
  18404. this.hide();
  18405. }
  18406. else {
  18407. this.show();
  18408. this.height = Number(this.linegraphSVG.style.height.replace("px",""));
  18409. // svg offsetheight did not work in firefox and explorer...
  18410. this.dom.lineContainer.style.height = this.height + 'px';
  18411. this.width = this.options.visible == true ? Number(('' + this.options.width).replace("px","")) : 0;
  18412. var props = this.props;
  18413. var frame = this.dom.frame;
  18414. // update classname
  18415. frame.className = 'dataaxis';
  18416. // calculate character width and height
  18417. this._calculateCharSize();
  18418. var orientation = this.options.orientation;
  18419. var showMinorLabels = this.options.showMinorLabels;
  18420. var showMajorLabels = this.options.showMajorLabels;
  18421. // determine the width and height of the elements for the axis
  18422. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  18423. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  18424. props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset;
  18425. props.minorLineHeight = 1;
  18426. props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset;
  18427. props.majorLineHeight = 1;
  18428. // take frame offline while updating (is almost twice as fast)
  18429. if (orientation == 'left') {
  18430. frame.style.top = '0';
  18431. frame.style.left = '0';
  18432. frame.style.bottom = '';
  18433. frame.style.width = this.width + 'px';
  18434. frame.style.height = this.height + "px";
  18435. this.props.width = this.body.domProps.left.width;
  18436. this.props.height = this.body.domProps.left.height;
  18437. }
  18438. else { // right
  18439. frame.style.top = '';
  18440. frame.style.bottom = '0';
  18441. frame.style.left = '0';
  18442. frame.style.width = this.width + 'px';
  18443. frame.style.height = this.height + "px";
  18444. this.props.width = this.body.domProps.right.width;
  18445. this.props.height = this.body.domProps.right.height;
  18446. }
  18447. resized = this._redrawLabels();
  18448. resized = this._isResized() || resized;
  18449. if (this.options.icons == true) {
  18450. this._redrawGroupIcons();
  18451. }
  18452. else {
  18453. this._cleanupIcons();
  18454. }
  18455. this._redrawTitle(orientation);
  18456. }
  18457. return resized;
  18458. };
  18459. /**
  18460. * Repaint major and minor text labels and vertical grid lines
  18461. * @private
  18462. */
  18463. DataAxis.prototype._redrawLabels = function () {
  18464. var resized = false;
  18465. DOMutil.prepareElements(this.DOMelements.lines);
  18466. DOMutil.prepareElements(this.DOMelements.labels);
  18467. var orientation = this.options['orientation'];
  18468. // calculate range and step (step such that we have space for 7 characters per label)
  18469. var minimumStep = this.master ? this.props.majorCharHeight || 10 : this.stepPixelsForced;
  18470. var step = new DataStep(
  18471. this.range.start,
  18472. this.range.end,
  18473. minimumStep,
  18474. this.dom.frame.offsetHeight,
  18475. this.options.customRange[this.options.orientation],
  18476. this.master == false && this.options.alignZeros // doess the step have to align zeros? only if not master and the options is on
  18477. );
  18478. this.step = step;
  18479. // get the distance in pixels for a step
  18480. // dead space is space that is "left over" after a step
  18481. var stepPixels = (this.dom.frame.offsetHeight - (step.deadSpace * (this.dom.frame.offsetHeight / step.marginRange))) / (((step.marginRange - step.deadSpace) / step.step));
  18482. this.stepPixels = stepPixels;
  18483. var amountOfSteps = this.height / stepPixels;
  18484. var stepDifference = 0;
  18485. // the slave axis needs to use the same horizontal lines as the master axis.
  18486. if (this.master == false) {
  18487. stepPixels = this.stepPixelsForced;
  18488. stepDifference = Math.round((this.dom.frame.offsetHeight / stepPixels) - amountOfSteps);
  18489. for (var i = 0; i < 0.5 * stepDifference; i++) {
  18490. step.previous();
  18491. }
  18492. amountOfSteps = this.height / stepPixels;
  18493. if (this.zeroCrossing != -1 && this.options.alignZeros == true) {
  18494. var zeroStepDifference = (step.marginEnd / step.step) - this.zeroCrossing;
  18495. if (zeroStepDifference > 0) {
  18496. for (var i = 0; i < zeroStepDifference; i++) {step.next();}
  18497. }
  18498. else if (zeroStepDifference < 0) {
  18499. for (var i = 0; i < -zeroStepDifference; i++) {step.previous();}
  18500. }
  18501. }
  18502. }
  18503. else {
  18504. amountOfSteps += 0.25;
  18505. }
  18506. this.valueAtZero = step.marginEnd;
  18507. var marginStartPos = 0;
  18508. // do not draw the first label
  18509. var max = 1;
  18510. // Get the number of decimal places
  18511. var decimals;
  18512. if(this.options.format[orientation] !== undefined) {
  18513. decimals = this.options.format[orientation].decimals;
  18514. }
  18515. this.maxLabelSize = 0;
  18516. var y = 0;
  18517. while (max < Math.round(amountOfSteps)) {
  18518. step.next();
  18519. y = Math.round(max * stepPixels);
  18520. marginStartPos = max * stepPixels;
  18521. var isMajor = step.isMajor();
  18522. if (this.options['showMinorLabels'] && isMajor == false || this.master == false && this.options['showMinorLabels'] == true) {
  18523. this._redrawLabel(y - 2, step.getCurrent(decimals), orientation, 'yAxis minor', this.props.minorCharHeight);
  18524. }
  18525. if (isMajor && this.options['showMajorLabels'] && this.master == true ||
  18526. this.options['showMinorLabels'] == false && this.master == false && isMajor == true) {
  18527. if (y >= 0) {
  18528. this._redrawLabel(y - 2, step.getCurrent(decimals), orientation, 'yAxis major', this.props.majorCharHeight);
  18529. }
  18530. this._redrawLine(y, orientation, 'grid horizontal major', this.options.majorLinesOffset, this.props.majorLineWidth);
  18531. }
  18532. else {
  18533. this._redrawLine(y, orientation, 'grid horizontal minor', this.options.minorLinesOffset, this.props.minorLineWidth);
  18534. }
  18535. if (this.master == true && step.current == 0) {
  18536. this.zeroCrossing = max;
  18537. }
  18538. max++;
  18539. }
  18540. if (this.master == false) {
  18541. this.conversionFactor = y / (this.valueAtZero - step.current);
  18542. }
  18543. else {
  18544. this.conversionFactor = this.dom.frame.offsetHeight / step.marginRange;
  18545. }
  18546. // Note that title is rotated, so we're using the height, not width!
  18547. var titleWidth = 0;
  18548. if (this.options.title[orientation] !== undefined && this.options.title[orientation].text !== undefined) {
  18549. titleWidth = this.props.titleCharHeight;
  18550. }
  18551. var offset = this.options.icons == true ? Math.max(this.options.iconWidth, titleWidth) + this.options.labelOffsetX + 15 : titleWidth + this.options.labelOffsetX + 15;
  18552. // this will resize the yAxis to accommodate the labels.
  18553. if (this.maxLabelSize > (this.width - offset) && this.options.visible == true) {
  18554. this.width = this.maxLabelSize + offset;
  18555. this.options.width = this.width + "px";
  18556. DOMutil.cleanupElements(this.DOMelements.lines);
  18557. DOMutil.cleanupElements(this.DOMelements.labels);
  18558. this.redraw();
  18559. resized = true;
  18560. }
  18561. // this will resize the yAxis if it is too big for the labels.
  18562. else if (this.maxLabelSize < (this.width - offset) && this.options.visible == true && this.width > this.minWidth) {
  18563. this.width = Math.max(this.minWidth,this.maxLabelSize + offset);
  18564. this.options.width = this.width + "px";
  18565. DOMutil.cleanupElements(this.DOMelements.lines);
  18566. DOMutil.cleanupElements(this.DOMelements.labels);
  18567. this.redraw();
  18568. resized = true;
  18569. }
  18570. else {
  18571. DOMutil.cleanupElements(this.DOMelements.lines);
  18572. DOMutil.cleanupElements(this.DOMelements.labels);
  18573. resized = false;
  18574. }
  18575. return resized;
  18576. };
  18577. DataAxis.prototype.convertValue = function (value) {
  18578. var invertedValue = this.valueAtZero - value;
  18579. var convertedValue = invertedValue * this.conversionFactor;
  18580. return convertedValue;
  18581. };
  18582. /**
  18583. * Create a label for the axis at position x
  18584. * @private
  18585. * @param y
  18586. * @param text
  18587. * @param orientation
  18588. * @param className
  18589. * @param characterHeight
  18590. */
  18591. DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) {
  18592. // reuse redundant label
  18593. var label = DOMutil.getDOMElement('div',this.DOMelements.labels, this.dom.frame); //this.dom.redundant.labels.shift();
  18594. label.className = className;
  18595. label.innerHTML = text;
  18596. if (orientation == 'left') {
  18597. label.style.left = '-' + this.options.labelOffsetX + 'px';
  18598. label.style.textAlign = "right";
  18599. }
  18600. else {
  18601. label.style.right = '-' + this.options.labelOffsetX + 'px';
  18602. label.style.textAlign = "left";
  18603. }
  18604. label.style.top = y - 0.5 * characterHeight + this.options.labelOffsetY + 'px';
  18605. text += '';
  18606. var largestWidth = Math.max(this.props.majorCharWidth,this.props.minorCharWidth);
  18607. if (this.maxLabelSize < text.length * largestWidth) {
  18608. this.maxLabelSize = text.length * largestWidth;
  18609. }
  18610. };
  18611. /**
  18612. * Create a minor line for the axis at position y
  18613. * @param y
  18614. * @param orientation
  18615. * @param className
  18616. * @param offset
  18617. * @param width
  18618. */
  18619. DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) {
  18620. if (this.master == true) {
  18621. var line = DOMutil.getDOMElement('div',this.DOMelements.lines, this.dom.lineContainer);//this.dom.redundant.lines.shift();
  18622. line.className = className;
  18623. line.innerHTML = '';
  18624. if (orientation == 'left') {
  18625. line.style.left = (this.width - offset) + 'px';
  18626. }
  18627. else {
  18628. line.style.right = (this.width - offset) + 'px';
  18629. }
  18630. line.style.width = width + 'px';
  18631. line.style.top = y + 'px';
  18632. }
  18633. };
  18634. /**
  18635. * Create a title for the axis
  18636. * @private
  18637. * @param orientation
  18638. */
  18639. DataAxis.prototype._redrawTitle = function (orientation) {
  18640. DOMutil.prepareElements(this.DOMelements.title);
  18641. // Check if the title is defined for this axes
  18642. if (this.options.title[orientation] !== undefined && this.options.title[orientation].text !== undefined) {
  18643. var title = DOMutil.getDOMElement('div', this.DOMelements.title, this.dom.frame);
  18644. title.className = 'yAxis title ' + orientation;
  18645. title.innerHTML = this.options.title[orientation].text;
  18646. // Add style - if provided
  18647. if (this.options.title[orientation].style !== undefined) {
  18648. util.addCssText(title, this.options.title[orientation].style);
  18649. }
  18650. if (orientation == 'left') {
  18651. title.style.left = this.props.titleCharHeight + 'px';
  18652. }
  18653. else {
  18654. title.style.right = this.props.titleCharHeight + 'px';
  18655. }
  18656. title.style.width = this.height + 'px';
  18657. }
  18658. // we need to clean up in case we did not use all elements.
  18659. DOMutil.cleanupElements(this.DOMelements.title);
  18660. };
  18661. /**
  18662. * Determine the size of text on the axis (both major and minor axis).
  18663. * The size is calculated only once and then cached in this.props.
  18664. * @private
  18665. */
  18666. DataAxis.prototype._calculateCharSize = function () {
  18667. // determine the char width and height on the minor axis
  18668. if (!('minorCharHeight' in this.props)) {
  18669. var textMinor = document.createTextNode('0');
  18670. var measureCharMinor = document.createElement('div');
  18671. measureCharMinor.className = 'yAxis minor measure';
  18672. measureCharMinor.appendChild(textMinor);
  18673. this.dom.frame.appendChild(measureCharMinor);
  18674. this.props.minorCharHeight = measureCharMinor.clientHeight;
  18675. this.props.minorCharWidth = measureCharMinor.clientWidth;
  18676. this.dom.frame.removeChild(measureCharMinor);
  18677. }
  18678. if (!('majorCharHeight' in this.props)) {
  18679. var textMajor = document.createTextNode('0');
  18680. var measureCharMajor = document.createElement('div');
  18681. measureCharMajor.className = 'yAxis major measure';
  18682. measureCharMajor.appendChild(textMajor);
  18683. this.dom.frame.appendChild(measureCharMajor);
  18684. this.props.majorCharHeight = measureCharMajor.clientHeight;
  18685. this.props.majorCharWidth = measureCharMajor.clientWidth;
  18686. this.dom.frame.removeChild(measureCharMajor);
  18687. }
  18688. if (!('titleCharHeight' in this.props)) {
  18689. var textTitle = document.createTextNode('0');
  18690. var measureCharTitle = document.createElement('div');
  18691. measureCharTitle.className = 'yAxis title measure';
  18692. measureCharTitle.appendChild(textTitle);
  18693. this.dom.frame.appendChild(measureCharTitle);
  18694. this.props.titleCharHeight = measureCharTitle.clientHeight;
  18695. this.props.titleCharWidth = measureCharTitle.clientWidth;
  18696. this.dom.frame.removeChild(measureCharTitle);
  18697. }
  18698. };
  18699. /**
  18700. * Snap a date to a rounded value.
  18701. * The snap intervals are dependent on the current scale and step.
  18702. * @param {Date} date the date to be snapped.
  18703. * @return {Date} snappedDate
  18704. */
  18705. DataAxis.prototype.snap = function(date) {
  18706. return this.step.snap(date);
  18707. };
  18708. module.exports = DataAxis;
  18709. /***/ },
  18710. /* 45 */
  18711. /***/ function(module, exports, __webpack_require__) {
  18712. /**
  18713. * @constructor DataStep
  18714. * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an
  18715. * end data point. The class itself determines the best scale (step size) based on the
  18716. * provided start Date, end Date, and minimumStep.
  18717. *
  18718. * If minimumStep is provided, the step size is chosen as close as possible
  18719. * to the minimumStep but larger than minimumStep. If minimumStep is not
  18720. * provided, the scale is set to 1 DAY.
  18721. * The minimumStep should correspond with the onscreen size of about 6 characters
  18722. *
  18723. * Alternatively, you can set a scale by hand.
  18724. * After creation, you can initialize the class by executing first(). Then you
  18725. * can iterate from the start date to the end date via next(). You can check if
  18726. * the end date is reached with the function hasNext(). After each step, you can
  18727. * retrieve the current date via getCurrent().
  18728. * The DataStep has scales ranging from milliseconds, seconds, minutes, hours,
  18729. * days, to years.
  18730. *
  18731. * Version: 1.2
  18732. *
  18733. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  18734. * or new Date(2010, 9, 21, 23, 45, 00)
  18735. * @param {Date} [end] The end date
  18736. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  18737. */
  18738. function DataStep(start, end, minimumStep, containerHeight, customRange, alignZeros) {
  18739. // variables
  18740. this.current = 0;
  18741. this.autoScale = true;
  18742. this.stepIndex = 0;
  18743. this.step = 1;
  18744. this.scale = 1;
  18745. this.marginStart;
  18746. this.marginEnd;
  18747. this.deadSpace = 0;
  18748. this.majorSteps = [1, 2, 5, 10];
  18749. this.minorSteps = [0.25, 0.5, 1, 2];
  18750. this.alignZeros = alignZeros;
  18751. this.setRange(start, end, minimumStep, containerHeight, customRange);
  18752. }
  18753. /**
  18754. * Set a new range
  18755. * If minimumStep is provided, the step size is chosen as close as possible
  18756. * to the minimumStep but larger than minimumStep. If minimumStep is not
  18757. * provided, the scale is set to 1 DAY.
  18758. * The minimumStep should correspond with the onscreen size of about 6 characters
  18759. * @param {Number} [start] The start date and time.
  18760. * @param {Number} [end] The end date and time.
  18761. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  18762. */
  18763. DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, customRange) {
  18764. this._start = customRange.min === undefined ? start : customRange.min;
  18765. this._end = customRange.max === undefined ? end : customRange.max;
  18766. if (this._start == this._end) {
  18767. this._start -= 0.75;
  18768. this._end += 1;
  18769. }
  18770. if (this.autoScale == true) {
  18771. this.setMinimumStep(minimumStep, containerHeight);
  18772. }
  18773. this.setFirst(customRange);
  18774. };
  18775. /**
  18776. * Automatically determine the scale that bests fits the provided minimum step
  18777. * @param {Number} [minimumStep] The minimum step size in milliseconds
  18778. */
  18779. DataStep.prototype.setMinimumStep = function(minimumStep, containerHeight) {
  18780. // round to floor
  18781. var size = this._end - this._start;
  18782. var safeSize = size * 1.2;
  18783. var minimumStepValue = minimumStep * (safeSize / containerHeight);
  18784. var orderOfMagnitude = Math.round(Math.log(safeSize)/Math.LN10);
  18785. var minorStepIdx = -1;
  18786. var magnitudefactor = Math.pow(10,orderOfMagnitude);
  18787. var start = 0;
  18788. if (orderOfMagnitude < 0) {
  18789. start = orderOfMagnitude;
  18790. }
  18791. var solutionFound = false;
  18792. for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) {
  18793. magnitudefactor = Math.pow(10,i);
  18794. for (var j = 0; j < this.minorSteps.length; j++) {
  18795. var stepSize = magnitudefactor * this.minorSteps[j];
  18796. if (stepSize >= minimumStepValue) {
  18797. solutionFound = true;
  18798. minorStepIdx = j;
  18799. break;
  18800. }
  18801. }
  18802. if (solutionFound == true) {
  18803. break;
  18804. }
  18805. }
  18806. this.stepIndex = minorStepIdx;
  18807. this.scale = magnitudefactor;
  18808. this.step = magnitudefactor * this.minorSteps[minorStepIdx];
  18809. };
  18810. /**
  18811. * Round the current date to the first minor date value
  18812. * This must be executed once when the current date is set to start Date
  18813. */
  18814. DataStep.prototype.setFirst = function(customRange) {
  18815. if (customRange === undefined) {
  18816. customRange = {};
  18817. }
  18818. var niceStart = customRange.min === undefined ? this._start - (this.scale * 2 * this.minorSteps[this.stepIndex]) : customRange.min;
  18819. var niceEnd = customRange.max === undefined ? this._end + (this.scale * this.minorSteps[this.stepIndex]) : customRange.max;
  18820. this.marginEnd = customRange.max === undefined ? this.roundToMinor(niceEnd) : customRange.max;
  18821. this.marginStart = customRange.min === undefined ? this.roundToMinor(niceStart) : customRange.min;
  18822. // if we need to align the zero's we need to make sure that there is a zero to use.
  18823. if (this.alignZeros == true && (this.marginEnd - this.marginStart) % this.step != 0) {
  18824. this.marginEnd += this.marginEnd % this.step;
  18825. }
  18826. this.deadSpace = this.roundToMinor(niceEnd) - niceEnd + this.roundToMinor(niceStart) - niceStart;
  18827. this.marginRange = this.marginEnd - this.marginStart;
  18828. this.current = this.marginEnd;
  18829. };
  18830. DataStep.prototype.roundToMinor = function(value) {
  18831. var rounded = value - (value % (this.scale * this.minorSteps[this.stepIndex]));
  18832. if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) {
  18833. return rounded + (this.scale * this.minorSteps[this.stepIndex]);
  18834. }
  18835. else {
  18836. return rounded;
  18837. }
  18838. }
  18839. /**
  18840. * Check if the there is a next step
  18841. * @return {boolean} true if the current date has not passed the end date
  18842. */
  18843. DataStep.prototype.hasNext = function () {
  18844. return (this.current >= this.marginStart);
  18845. };
  18846. /**
  18847. * Do the next step
  18848. */
  18849. DataStep.prototype.next = function() {
  18850. var prev = this.current;
  18851. this.current -= this.step;
  18852. // safety mechanism: if current time is still unchanged, move to the end
  18853. if (this.current == prev) {
  18854. this.current = this._end;
  18855. }
  18856. };
  18857. /**
  18858. * Do the next step
  18859. */
  18860. DataStep.prototype.previous = function() {
  18861. this.current += this.step;
  18862. this.marginEnd += this.step;
  18863. this.marginRange = this.marginEnd - this.marginStart;
  18864. };
  18865. /**
  18866. * Get the current datetime
  18867. * @return {String} current The current date
  18868. */
  18869. DataStep.prototype.getCurrent = function(decimals) {
  18870. // prevent round-off errors when close to zero
  18871. var current = (Math.abs(this.current) < this.step / 2) ? 0 : this.current;
  18872. var toPrecision = '' + Number(current).toPrecision(5);
  18873. // If decimals is specified, then limit or extend the string as required
  18874. if(decimals !== undefined && !isNaN(Number(decimals))) {
  18875. // If string includes exponent, then we need to add it to the end
  18876. var exp = "";
  18877. var index = toPrecision.indexOf("e");
  18878. if(index != -1) {
  18879. // Get the exponent
  18880. exp = toPrecision.slice(index);
  18881. // Remove the exponent in case we need to zero-extend
  18882. toPrecision = toPrecision.slice(0, index);
  18883. }
  18884. index = Math.max(toPrecision.indexOf(","), toPrecision.indexOf("."));
  18885. if(index === -1) {
  18886. // No decimal found - if we want decimals, then we need to add it
  18887. if(decimals !== 0) {
  18888. toPrecision += '.';
  18889. }
  18890. // Calculate how long the string should be
  18891. index = toPrecision.length + decimals;
  18892. }
  18893. else if(decimals !== 0) {
  18894. // Calculate how long the string should be - accounting for the decimal place
  18895. index += decimals + 1;
  18896. }
  18897. if(index > toPrecision.length) {
  18898. // We need to add zeros!
  18899. for(var cnt = index - toPrecision.length; cnt > 0; cnt--) {
  18900. toPrecision += '0';
  18901. }
  18902. }
  18903. else {
  18904. // we need to remove characters
  18905. toPrecision = toPrecision.slice(0, index);
  18906. }
  18907. // Add the exponent if there is one
  18908. toPrecision += exp;
  18909. }
  18910. else {
  18911. if (toPrecision.indexOf(",") != -1 || toPrecision.indexOf(".") != -1) {
  18912. // If no decimal is specified, and there are decimal places, remove trailing zeros
  18913. for (var i = toPrecision.length - 1; i > 0; i--) {
  18914. if (toPrecision[i] == "0") {
  18915. toPrecision = toPrecision.slice(0, i);
  18916. }
  18917. else if (toPrecision[i] == "." || toPrecision[i] == ",") {
  18918. toPrecision = toPrecision.slice(0, i);
  18919. break;
  18920. }
  18921. else {
  18922. break;
  18923. }
  18924. }
  18925. }
  18926. }
  18927. return toPrecision;
  18928. };
  18929. /**
  18930. * Snap a date to a rounded value.
  18931. * The snap intervals are dependent on the current scale and step.
  18932. * @param {Date} date the date to be snapped.
  18933. * @return {Date} snappedDate
  18934. */
  18935. DataStep.prototype.snap = function(date) {
  18936. };
  18937. /**
  18938. * Check if the current value is a major value (for example when the step
  18939. * is DAY, a major value is each first day of the MONTH)
  18940. * @return {boolean} true if current date is major, else false.
  18941. */
  18942. DataStep.prototype.isMajor = function() {
  18943. return (this.current % (this.scale * this.majorSteps[this.stepIndex]) == 0);
  18944. };
  18945. module.exports = DataStep;
  18946. /***/ },
  18947. /* 46 */
  18948. /***/ function(module, exports, __webpack_require__) {
  18949. var util = __webpack_require__(1);
  18950. var DOMutil = __webpack_require__(6);
  18951. var Line = __webpack_require__(47);
  18952. var Bar = __webpack_require__(49);
  18953. var Points = __webpack_require__(48);
  18954. /**
  18955. * /**
  18956. * @param {object} group | the object of the group from the dataset
  18957. * @param {string} groupId | ID of the group
  18958. * @param {object} options | the default options
  18959. * @param {array} groupsUsingDefaultStyles | this array has one entree.
  18960. * It is passed as an array so it is passed by reference.
  18961. * It enumerates through the default styles
  18962. * @constructor
  18963. */
  18964. function GraphGroup (group, groupId, options, groupsUsingDefaultStyles) {
  18965. this.id = groupId;
  18966. var fields = ['sampling','style','sort','yAxisOrientation','barChart','drawPoints','shaded','catmullRom']
  18967. this.options = util.selectiveBridgeObject(fields,options);
  18968. this.usingDefaultStyle = group.className === undefined;
  18969. this.groupsUsingDefaultStyles = groupsUsingDefaultStyles;
  18970. this.zeroPosition = 0;
  18971. this.update(group);
  18972. if (this.usingDefaultStyle == true) {
  18973. this.groupsUsingDefaultStyles[0] += 1;
  18974. }
  18975. this.itemsData = [];
  18976. this.visible = group.visible === undefined ? true : group.visible;
  18977. }
  18978. /**
  18979. * this loads a reference to all items in this group into this group.
  18980. * @param {array} items
  18981. */
  18982. GraphGroup.prototype.setItems = function(items) {
  18983. if (items != null) {
  18984. this.itemsData = items;
  18985. if (this.options.sort == true) {
  18986. this.itemsData.sort(function (a,b) {return a.x - b.x;})
  18987. }
  18988. }
  18989. else {
  18990. this.itemsData = [];
  18991. }
  18992. };
  18993. /**
  18994. * this is used for plotting barcharts, this way, we only have to calculate it once.
  18995. * @param pos
  18996. */
  18997. GraphGroup.prototype.setZeroPosition = function(pos) {
  18998. this.zeroPosition = pos;
  18999. };
  19000. /**
  19001. * set the options of the graph group over the default options.
  19002. * @param options
  19003. */
  19004. GraphGroup.prototype.setOptions = function(options) {
  19005. if (options !== undefined) {
  19006. var fields = ['sampling','style','sort','yAxisOrientation','barChart'];
  19007. util.selectiveDeepExtend(fields, this.options, options);
  19008. util.mergeOptions(this.options, options,'catmullRom');
  19009. util.mergeOptions(this.options, options,'drawPoints');
  19010. util.mergeOptions(this.options, options,'shaded');
  19011. if (options.catmullRom) {
  19012. if (typeof options.catmullRom == 'object') {
  19013. if (options.catmullRom.parametrization) {
  19014. if (options.catmullRom.parametrization == 'uniform') {
  19015. this.options.catmullRom.alpha = 0;
  19016. }
  19017. else if (options.catmullRom.parametrization == 'chordal') {
  19018. this.options.catmullRom.alpha = 1.0;
  19019. }
  19020. else {
  19021. this.options.catmullRom.parametrization = 'centripetal';
  19022. this.options.catmullRom.alpha = 0.5;
  19023. }
  19024. }
  19025. }
  19026. }
  19027. }
  19028. if (this.options.style == 'line') {
  19029. this.type = new Line(this.id, this.options);
  19030. }
  19031. else if (this.options.style == 'bar') {
  19032. this.type = new Bar(this.id, this.options);
  19033. }
  19034. else if (this.options.style == 'points') {
  19035. this.type = new Points(this.id, this.options);
  19036. }
  19037. };
  19038. /**
  19039. * this updates the current group class with the latest group dataset entree, used in _updateGroup in linegraph
  19040. * @param group
  19041. */
  19042. GraphGroup.prototype.update = function(group) {
  19043. this.group = group;
  19044. this.content = group.content || 'graph';
  19045. this.className = group.className || this.className || "graphGroup" + this.groupsUsingDefaultStyles[0] % 10;
  19046. this.visible = group.visible === undefined ? true : group.visible;
  19047. this.style = group.style;
  19048. this.setOptions(group.options);
  19049. };
  19050. /**
  19051. * draw the icon for the legend.
  19052. *
  19053. * @param x
  19054. * @param y
  19055. * @param JSONcontainer
  19056. * @param SVGcontainer
  19057. * @param iconWidth
  19058. * @param iconHeight
  19059. */
  19060. GraphGroup.prototype.drawIcon = function(x, y, JSONcontainer, SVGcontainer, iconWidth, iconHeight) {
  19061. var fillHeight = iconHeight * 0.5;
  19062. var path, fillPath;
  19063. var outline = DOMutil.getSVGElement("rect", JSONcontainer, SVGcontainer);
  19064. outline.setAttributeNS(null, "x", x);
  19065. outline.setAttributeNS(null, "y", y - fillHeight);
  19066. outline.setAttributeNS(null, "width", iconWidth);
  19067. outline.setAttributeNS(null, "height", 2*fillHeight);
  19068. outline.setAttributeNS(null, "class", "outline");
  19069. if (this.options.style == 'line') {
  19070. path = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
  19071. path.setAttributeNS(null, "class", this.className);
  19072. if(this.style !== undefined) {
  19073. path.setAttributeNS(null, "style", this.style);
  19074. }
  19075. path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+"");
  19076. if (this.options.shaded.enabled == true) {
  19077. fillPath = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
  19078. if (this.options.shaded.orientation == 'top') {
  19079. fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) +
  19080. "L"+x+","+y+" L"+ (x + iconWidth) + ","+y+" L"+ (x + iconWidth) + "," + (y - fillHeight));
  19081. }
  19082. else {
  19083. fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " +
  19084. "L"+x+"," + (y + fillHeight) + " " +
  19085. "L"+ (x + iconWidth) + "," + (y + fillHeight) +
  19086. "L"+ (x + iconWidth) + ","+y);
  19087. }
  19088. fillPath.setAttributeNS(null, "class", this.className + " iconFill");
  19089. }
  19090. if (this.options.drawPoints.enabled == true) {
  19091. DOMutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer);
  19092. }
  19093. }
  19094. else {
  19095. var barWidth = Math.round(0.3 * iconWidth);
  19096. var bar1Height = Math.round(0.4 * iconHeight);
  19097. var bar2Height = Math.round(0.75 * iconHeight);
  19098. var offset = Math.round((iconWidth - (2 * barWidth))/3);
  19099. DOMutil.drawBar(x + 0.5*barWidth + offset , y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' bar', JSONcontainer, SVGcontainer);
  19100. DOMutil.drawBar(x + 1.5*barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' bar', JSONcontainer, SVGcontainer);
  19101. }
  19102. };
  19103. /**
  19104. * return the legend entree for this group.
  19105. *
  19106. * @param iconWidth
  19107. * @param iconHeight
  19108. * @returns {{icon: HTMLElement, label: (group.content|*|string), orientation: (.options.yAxisOrientation|*)}}
  19109. */
  19110. GraphGroup.prototype.getLegend = function(iconWidth, iconHeight) {
  19111. var svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  19112. this.drawIcon(0,0.5*iconHeight,[],svg,iconWidth,iconHeight);
  19113. return {icon: svg, label: this.content, orientation:this.options.yAxisOrientation};
  19114. }
  19115. GraphGroup.prototype.getYRange = function(groupData) {
  19116. return this.type.getYRange(groupData);
  19117. }
  19118. GraphGroup.prototype.draw = function(dataset, group, framework) {
  19119. this.type.draw(dataset, group, framework);
  19120. }
  19121. module.exports = GraphGroup;
  19122. /***/ },
  19123. /* 47 */
  19124. /***/ function(module, exports, __webpack_require__) {
  19125. /**
  19126. * Created by Alex on 11/11/2014.
  19127. */
  19128. var DOMutil = __webpack_require__(6);
  19129. var Points = __webpack_require__(48);
  19130. function Line(groupId, options) {
  19131. this.groupId = groupId;
  19132. this.options = options;
  19133. }
  19134. Line.prototype.getYRange = function(groupData) {
  19135. var yMin = groupData[0].y;
  19136. var yMax = groupData[0].y;
  19137. for (var j = 0; j < groupData.length; j++) {
  19138. yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
  19139. yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
  19140. }
  19141. return {min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation};
  19142. };
  19143. /**
  19144. * draw a line graph
  19145. *
  19146. * @param dataset
  19147. * @param group
  19148. */
  19149. Line.prototype.draw = function (dataset, group, framework) {
  19150. if (dataset != null) {
  19151. if (dataset.length > 0) {
  19152. var path, d;
  19153. var svgHeight = Number(framework.svg.style.height.replace('px',''));
  19154. path = DOMutil.getSVGElement('path', framework.svgElements, framework.svg);
  19155. path.setAttributeNS(null, "class", group.className);
  19156. if(group.style !== undefined) {
  19157. path.setAttributeNS(null, "style", group.style);
  19158. }
  19159. // construct path from dataset
  19160. if (group.options.catmullRom.enabled == true) {
  19161. d = Line._catmullRom(dataset, group);
  19162. }
  19163. else {
  19164. d = Line._linear(dataset);
  19165. }
  19166. // append with points for fill and finalize the path
  19167. if (group.options.shaded.enabled == true) {
  19168. var fillPath = DOMutil.getSVGElement('path', framework.svgElements, framework.svg);
  19169. var dFill;
  19170. if (group.options.shaded.orientation == 'top') {
  19171. dFill = 'M' + dataset[0].x + ',' + 0 + ' ' + d + 'L' + dataset[dataset.length - 1].x + ',' + 0;
  19172. }
  19173. else {
  19174. dFill = 'M' + dataset[0].x + ',' + svgHeight + ' ' + d + 'L' + dataset[dataset.length - 1].x + ',' + svgHeight;
  19175. }
  19176. fillPath.setAttributeNS(null, "class", group.className + " fill");
  19177. if(group.options.shaded.style !== undefined) {
  19178. fillPath.setAttributeNS(null, "style", group.options.shaded.style);
  19179. }
  19180. fillPath.setAttributeNS(null, "d", dFill);
  19181. }
  19182. // copy properties to path for drawing.
  19183. path.setAttributeNS(null, 'd', 'M' + d);
  19184. // draw points
  19185. if (group.options.drawPoints.enabled == true) {
  19186. Points.draw(dataset, group, framework);
  19187. }
  19188. }
  19189. }
  19190. };
  19191. /**
  19192. * This uses an uniform parametrization of the CatmullRom algorithm:
  19193. * 'On the Parameterization of Catmull-Rom Curves' by Cem Yuksel et al.
  19194. * @param data
  19195. * @returns {string}
  19196. * @private
  19197. */
  19198. Line._catmullRomUniform = function(data) {
  19199. // catmull rom
  19200. var p0, p1, p2, p3, bp1, bp2;
  19201. var d = Math.round(data[0].x) + ',' + Math.round(data[0].y) + ' ';
  19202. var normalization = 1/6;
  19203. var length = data.length;
  19204. for (var i = 0; i < length - 1; i++) {
  19205. p0 = (i == 0) ? data[0] : data[i-1];
  19206. p1 = data[i];
  19207. p2 = data[i+1];
  19208. p3 = (i + 2 < length) ? data[i+2] : p2;
  19209. // Catmull-Rom to Cubic Bezier conversion matrix
  19210. // 0 1 0 0
  19211. // -1/6 1 1/6 0
  19212. // 0 1/6 1 -1/6
  19213. // 0 0 1 0
  19214. // bp0 = { x: p1.x, y: p1.y };
  19215. bp1 = { x: ((-p0.x + 6*p1.x + p2.x) *normalization), y: ((-p0.y + 6*p1.y + p2.y) *normalization)};
  19216. bp2 = { x: (( p1.x + 6*p2.x - p3.x) *normalization), y: (( p1.y + 6*p2.y - p3.y) *normalization)};
  19217. // bp0 = { x: p2.x, y: p2.y };
  19218. d += 'C' +
  19219. bp1.x + ',' +
  19220. bp1.y + ' ' +
  19221. bp2.x + ',' +
  19222. bp2.y + ' ' +
  19223. p2.x + ',' +
  19224. p2.y + ' ';
  19225. }
  19226. return d;
  19227. };
  19228. /**
  19229. * This uses either the chordal or centripetal parameterization of the catmull-rom algorithm.
  19230. * By default, the centripetal parameterization is used because this gives the nicest results.
  19231. * These parameterizations are relatively heavy because the distance between 4 points have to be calculated.
  19232. *
  19233. * One optimization can be used to reuse distances since this is a sliding window approach.
  19234. * @param data
  19235. * @param group
  19236. * @returns {string}
  19237. * @private
  19238. */
  19239. Line._catmullRom = function(data, group) {
  19240. var alpha = group.options.catmullRom.alpha;
  19241. if (alpha == 0 || alpha === undefined) {
  19242. return this._catmullRomUniform(data);
  19243. }
  19244. else {
  19245. var p0, p1, p2, p3, bp1, bp2, d1,d2,d3, A, B, N, M;
  19246. var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA;
  19247. var d = Math.round(data[0].x) + ',' + Math.round(data[0].y) + ' ';
  19248. var length = data.length;
  19249. for (var i = 0; i < length - 1; i++) {
  19250. p0 = (i == 0) ? data[0] : data[i-1];
  19251. p1 = data[i];
  19252. p2 = data[i+1];
  19253. p3 = (i + 2 < length) ? data[i+2] : p2;
  19254. d1 = Math.sqrt(Math.pow(p0.x - p1.x,2) + Math.pow(p0.y - p1.y,2));
  19255. d2 = Math.sqrt(Math.pow(p1.x - p2.x,2) + Math.pow(p1.y - p2.y,2));
  19256. d3 = Math.sqrt(Math.pow(p2.x - p3.x,2) + Math.pow(p2.y - p3.y,2));
  19257. // Catmull-Rom to Cubic Bezier conversion matrix
  19258. // A = 2d1^2a + 3d1^a * d2^a + d3^2a
  19259. // B = 2d3^2a + 3d3^a * d2^a + d2^2a
  19260. // [ 0 1 0 0 ]
  19261. // [ -d2^2a /N A/N d1^2a /N 0 ]
  19262. // [ 0 d3^2a /M B/M -d2^2a /M ]
  19263. // [ 0 0 1 0 ]
  19264. d3powA = Math.pow(d3, alpha);
  19265. d3pow2A = Math.pow(d3,2*alpha);
  19266. d2powA = Math.pow(d2, alpha);
  19267. d2pow2A = Math.pow(d2,2*alpha);
  19268. d1powA = Math.pow(d1, alpha);
  19269. d1pow2A = Math.pow(d1,2*alpha);
  19270. A = 2*d1pow2A + 3*d1powA * d2powA + d2pow2A;
  19271. B = 2*d3pow2A + 3*d3powA * d2powA + d2pow2A;
  19272. N = 3*d1powA * (d1powA + d2powA);
  19273. if (N > 0) {N = 1 / N;}
  19274. M = 3*d3powA * (d3powA + d2powA);
  19275. if (M > 0) {M = 1 / M;}
  19276. bp1 = { x: ((-d2pow2A * p0.x + A*p1.x + d1pow2A * p2.x) * N),
  19277. y: ((-d2pow2A * p0.y + A*p1.y + d1pow2A * p2.y) * N)};
  19278. bp2 = { x: (( d3pow2A * p1.x + B*p2.x - d2pow2A * p3.x) * M),
  19279. y: (( d3pow2A * p1.y + B*p2.y - d2pow2A * p3.y) * M)};
  19280. if (bp1.x == 0 && bp1.y == 0) {bp1 = p1;}
  19281. if (bp2.x == 0 && bp2.y == 0) {bp2 = p2;}
  19282. d += 'C' +
  19283. bp1.x + ',' +
  19284. bp1.y + ' ' +
  19285. bp2.x + ',' +
  19286. bp2.y + ' ' +
  19287. p2.x + ',' +
  19288. p2.y + ' ';
  19289. }
  19290. return d;
  19291. }
  19292. };
  19293. /**
  19294. * this generates the SVG path for a linear drawing between datapoints.
  19295. * @param data
  19296. * @returns {string}
  19297. * @private
  19298. */
  19299. Line._linear = function(data) {
  19300. // linear
  19301. var d = '';
  19302. for (var i = 0; i < data.length; i++) {
  19303. if (i == 0) {
  19304. d += data[i].x + ',' + data[i].y;
  19305. }
  19306. else {
  19307. d += ' ' + data[i].x + ',' + data[i].y;
  19308. }
  19309. }
  19310. return d;
  19311. };
  19312. module.exports = Line;
  19313. /***/ },
  19314. /* 48 */
  19315. /***/ function(module, exports, __webpack_require__) {
  19316. /**
  19317. * Created by Alex on 11/11/2014.
  19318. */
  19319. var DOMutil = __webpack_require__(6);
  19320. function Points(groupId, options) {
  19321. this.groupId = groupId;
  19322. this.options = options;
  19323. }
  19324. Points.prototype.getYRange = function(groupData) {
  19325. var yMin = groupData[0].y;
  19326. var yMax = groupData[0].y;
  19327. for (var j = 0; j < groupData.length; j++) {
  19328. yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
  19329. yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
  19330. }
  19331. return {min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation};
  19332. };
  19333. Points.prototype.draw = function(dataset, group, framework, offset) {
  19334. Points.draw(dataset, group, framework, offset);
  19335. }
  19336. /**
  19337. * draw the data points
  19338. *
  19339. * @param {Array} dataset
  19340. * @param {Object} JSONcontainer
  19341. * @param {Object} svg | SVG DOM element
  19342. * @param {GraphGroup} group
  19343. * @param {Number} [offset]
  19344. */
  19345. Points.draw = function (dataset, group, framework, offset) {
  19346. if (offset === undefined) {offset = 0;}
  19347. for (var i = 0; i < dataset.length; i++) {
  19348. DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, group, framework.svgElements, framework.svg);
  19349. }
  19350. };
  19351. module.exports = Points;
  19352. /***/ },
  19353. /* 49 */
  19354. /***/ function(module, exports, __webpack_require__) {
  19355. /**
  19356. * Created by Alex on 11/11/2014.
  19357. */
  19358. var DOMutil = __webpack_require__(6);
  19359. var Points = __webpack_require__(48);
  19360. function Bargraph(groupId, options) {
  19361. this.groupId = groupId;
  19362. this.options = options;
  19363. }
  19364. Bargraph.prototype.getYRange = function(groupData) {
  19365. if (this.options.barChart.handleOverlap != 'stack') {
  19366. var yMin = groupData[0].y;
  19367. var yMax = groupData[0].y;
  19368. for (var j = 0; j < groupData.length; j++) {
  19369. yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
  19370. yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
  19371. }
  19372. return {min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation};
  19373. }
  19374. else {
  19375. var barCombinedData = [];
  19376. for (var j = 0; j < groupData.length; j++) {
  19377. barCombinedData.push({
  19378. x: groupData[j].x,
  19379. y: groupData[j].y,
  19380. groupId: this.groupId
  19381. });
  19382. }
  19383. return barCombinedData;
  19384. }
  19385. };
  19386. /**
  19387. * draw a bar graph
  19388. *
  19389. * @param groupIds
  19390. * @param processedGroupData
  19391. */
  19392. Bargraph.draw = function (groupIds, processedGroupData, framework) {
  19393. var combinedData = [];
  19394. var intersections = {};
  19395. var coreDistance;
  19396. var key, drawData;
  19397. var group;
  19398. var i,j;
  19399. var barPoints = 0;
  19400. // combine all barchart data
  19401. for (i = 0; i < groupIds.length; i++) {
  19402. group = framework.groups[groupIds[i]];
  19403. if (group.options.style == 'bar') {
  19404. if (group.visible == true && (framework.options.groups.visibility[groupIds[i]] === undefined || framework.options.groups.visibility[groupIds[i]] == true)) {
  19405. for (j = 0; j < processedGroupData[groupIds[i]].length; j++) {
  19406. combinedData.push({
  19407. x: processedGroupData[groupIds[i]][j].x,
  19408. y: processedGroupData[groupIds[i]][j].y,
  19409. groupId: groupIds[i]
  19410. });
  19411. barPoints += 1;
  19412. }
  19413. }
  19414. }
  19415. }
  19416. if (barPoints == 0) {return;}
  19417. // sort by time and by group
  19418. combinedData.sort(function (a, b) {
  19419. if (a.x == b.x) {
  19420. return a.groupId - b.groupId;
  19421. } else {
  19422. return a.x - b.x;
  19423. }
  19424. });
  19425. // get intersections
  19426. Bargraph._getDataIntersections(intersections, combinedData);
  19427. // plot barchart
  19428. for (i = 0; i < combinedData.length; i++) {
  19429. group = framework.groups[combinedData[i].groupId];
  19430. var minWidth = 0.1 * group.options.barChart.width;
  19431. key = combinedData[i].x;
  19432. var heightOffset = 0;
  19433. if (intersections[key] === undefined) {
  19434. if (i+1 < combinedData.length) {coreDistance = Math.abs(combinedData[i+1].x - key);}
  19435. if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[i-1].x - key));}
  19436. drawData = Bargraph._getSafeDrawData(coreDistance, group, minWidth);
  19437. }
  19438. else {
  19439. var nextKey = i + (intersections[key].amount - intersections[key].resolved);
  19440. var prevKey = i - (intersections[key].resolved + 1);
  19441. if (nextKey < combinedData.length) {coreDistance = Math.abs(combinedData[nextKey].x - key);}
  19442. if (prevKey > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[prevKey].x - key));}
  19443. drawData = Bargraph._getSafeDrawData(coreDistance, group, minWidth);
  19444. intersections[key].resolved += 1;
  19445. if (group.options.barChart.handleOverlap == 'stack') {
  19446. heightOffset = intersections[key].accumulated;
  19447. intersections[key].accumulated += group.zeroPosition - combinedData[i].y;
  19448. }
  19449. else if (group.options.barChart.handleOverlap == 'sideBySide') {
  19450. drawData.width = drawData.width / intersections[key].amount;
  19451. drawData.offset += (intersections[key].resolved) * drawData.width - (0.5*drawData.width * (intersections[key].amount+1));
  19452. if (group.options.barChart.align == 'left') {drawData.offset -= 0.5*drawData.width;}
  19453. else if (group.options.barChart.align == 'right') {drawData.offset += 0.5*drawData.width;}
  19454. }
  19455. }
  19456. 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);
  19457. // draw points
  19458. if (group.options.drawPoints.enabled == true) {
  19459. DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y, group, framework.svgElements, framework.svg);
  19460. }
  19461. }
  19462. };
  19463. /**
  19464. * Fill the intersections object with counters of how many datapoints share the same x coordinates
  19465. * @param intersections
  19466. * @param combinedData
  19467. * @private
  19468. */
  19469. Bargraph._getDataIntersections = function (intersections, combinedData) {
  19470. // get intersections
  19471. var coreDistance;
  19472. for (var i = 0; i < combinedData.length; i++) {
  19473. if (i + 1 < combinedData.length) {
  19474. coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x);
  19475. }
  19476. if (i > 0) {
  19477. coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x));
  19478. }
  19479. if (coreDistance == 0) {
  19480. if (intersections[combinedData[i].x] === undefined) {
  19481. intersections[combinedData[i].x] = {amount: 0, resolved: 0, accumulated: 0};
  19482. }
  19483. intersections[combinedData[i].x].amount += 1;
  19484. }
  19485. }
  19486. };
  19487. /**
  19488. * Get the width and offset for bargraphs based on the coredistance between datapoints
  19489. *
  19490. * @param coreDistance
  19491. * @param group
  19492. * @param minWidth
  19493. * @returns {{width: Number, offset: Number}}
  19494. * @private
  19495. */
  19496. Bargraph._getSafeDrawData = function (coreDistance, group, minWidth) {
  19497. var width, offset;
  19498. if (coreDistance < group.options.barChart.width && coreDistance > 0) {
  19499. width = coreDistance < minWidth ? minWidth : coreDistance;
  19500. offset = 0; // recalculate offset with the new width;
  19501. if (group.options.barChart.align == 'left') {
  19502. offset -= 0.5 * coreDistance;
  19503. }
  19504. else if (group.options.barChart.align == 'right') {
  19505. offset += 0.5 * coreDistance;
  19506. }
  19507. }
  19508. else {
  19509. // default settings
  19510. width = group.options.barChart.width;
  19511. offset = 0;
  19512. if (group.options.barChart.align == 'left') {
  19513. offset -= 0.5 * group.options.barChart.width;
  19514. }
  19515. else if (group.options.barChart.align == 'right') {
  19516. offset += 0.5 * group.options.barChart.width;
  19517. }
  19518. }
  19519. return {width: width, offset: offset};
  19520. };
  19521. Bargraph.getStackedBarYRange = function(barCombinedData, groupRanges, groupIds, groupLabel, orientation) {
  19522. if (barCombinedData.length > 0) {
  19523. // sort by time and by group
  19524. barCombinedData.sort(function (a, b) {
  19525. if (a.x == b.x) {
  19526. return a.groupId - b.groupId;
  19527. } else {
  19528. return a.x - b.x;
  19529. }
  19530. });
  19531. var intersections = {};
  19532. Bargraph._getDataIntersections(intersections, barCombinedData);
  19533. groupRanges[groupLabel] = Bargraph._getStackedBarYRange(intersections, barCombinedData);
  19534. groupRanges[groupLabel].yAxisOrientation = orientation;
  19535. groupIds.push(groupLabel);
  19536. }
  19537. }
  19538. Bargraph._getStackedBarYRange = function (intersections, combinedData) {
  19539. var key;
  19540. var yMin = combinedData[0].y;
  19541. var yMax = combinedData[0].y;
  19542. for (var i = 0; i < combinedData.length; i++) {
  19543. key = combinedData[i].x;
  19544. if (intersections[key] === undefined) {
  19545. yMin = yMin > combinedData[i].y ? combinedData[i].y : yMin;
  19546. yMax = yMax < combinedData[i].y ? combinedData[i].y : yMax;
  19547. }
  19548. else {
  19549. intersections[key].accumulated += combinedData[i].y;
  19550. }
  19551. }
  19552. for (var xpos in intersections) {
  19553. if (intersections.hasOwnProperty(xpos)) {
  19554. yMin = yMin > intersections[xpos].accumulated ? intersections[xpos].accumulated : yMin;
  19555. yMax = yMax < intersections[xpos].accumulated ? intersections[xpos].accumulated : yMax;
  19556. }
  19557. }
  19558. return {min: yMin, max: yMax};
  19559. };
  19560. module.exports = Bargraph;
  19561. /***/ },
  19562. /* 50 */
  19563. /***/ function(module, exports, __webpack_require__) {
  19564. var util = __webpack_require__(1);
  19565. var DOMutil = __webpack_require__(6);
  19566. var Component = __webpack_require__(23);
  19567. /**
  19568. * Legend for Graph2d
  19569. */
  19570. function Legend(body, options, side, linegraphOptions) {
  19571. this.body = body;
  19572. this.defaultOptions = {
  19573. enabled: true,
  19574. icons: true,
  19575. iconSize: 20,
  19576. iconSpacing: 6,
  19577. left: {
  19578. visible: true,
  19579. position: 'top-left' // top/bottom - left,center,right
  19580. },
  19581. right: {
  19582. visible: true,
  19583. position: 'top-left' // top/bottom - left,center,right
  19584. }
  19585. }
  19586. this.side = side;
  19587. this.options = util.extend({},this.defaultOptions);
  19588. this.linegraphOptions = linegraphOptions;
  19589. this.svgElements = {};
  19590. this.dom = {};
  19591. this.groups = {};
  19592. this.amountOfGroups = 0;
  19593. this._create();
  19594. this.setOptions(options);
  19595. }
  19596. Legend.prototype = new Component();
  19597. Legend.prototype.clear = function() {
  19598. this.groups = {};
  19599. this.amountOfGroups = 0;
  19600. }
  19601. Legend.prototype.addGroup = function(label, graphOptions) {
  19602. if (!this.groups.hasOwnProperty(label)) {
  19603. this.groups[label] = graphOptions;
  19604. }
  19605. this.amountOfGroups += 1;
  19606. };
  19607. Legend.prototype.updateGroup = function(label, graphOptions) {
  19608. this.groups[label] = graphOptions;
  19609. };
  19610. Legend.prototype.removeGroup = function(label) {
  19611. if (this.groups.hasOwnProperty(label)) {
  19612. delete this.groups[label];
  19613. this.amountOfGroups -= 1;
  19614. }
  19615. };
  19616. Legend.prototype._create = function() {
  19617. this.dom.frame = document.createElement('div');
  19618. this.dom.frame.className = 'legend';
  19619. this.dom.frame.style.position = "absolute";
  19620. this.dom.frame.style.top = "10px";
  19621. this.dom.frame.style.display = "block";
  19622. this.dom.textArea = document.createElement('div');
  19623. this.dom.textArea.className = 'legendText';
  19624. this.dom.textArea.style.position = "relative";
  19625. this.dom.textArea.style.top = "0px";
  19626. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  19627. this.svg.style.position = 'absolute';
  19628. this.svg.style.top = 0 +'px';
  19629. this.svg.style.width = this.options.iconSize + 5 + 'px';
  19630. this.svg.style.height = '100%';
  19631. this.dom.frame.appendChild(this.svg);
  19632. this.dom.frame.appendChild(this.dom.textArea);
  19633. };
  19634. /**
  19635. * Hide the component from the DOM
  19636. */
  19637. Legend.prototype.hide = function() {
  19638. // remove the frame containing the items
  19639. if (this.dom.frame.parentNode) {
  19640. this.dom.frame.parentNode.removeChild(this.dom.frame);
  19641. }
  19642. };
  19643. /**
  19644. * Show the component in the DOM (when not already visible).
  19645. * @return {Boolean} changed
  19646. */
  19647. Legend.prototype.show = function() {
  19648. // show frame containing the items
  19649. if (!this.dom.frame.parentNode) {
  19650. this.body.dom.center.appendChild(this.dom.frame);
  19651. }
  19652. };
  19653. Legend.prototype.setOptions = function(options) {
  19654. var fields = ['enabled','orientation','icons','left','right'];
  19655. util.selectiveDeepExtend(fields, this.options, options);
  19656. };
  19657. Legend.prototype.redraw = function() {
  19658. var activeGroups = 0;
  19659. for (var groupId in this.groups) {
  19660. if (this.groups.hasOwnProperty(groupId)) {
  19661. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  19662. activeGroups++;
  19663. }
  19664. }
  19665. }
  19666. if (this.options[this.side].visible == false || this.amountOfGroups == 0 || this.options.enabled == false || activeGroups == 0) {
  19667. this.hide();
  19668. }
  19669. else {
  19670. this.show();
  19671. if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'bottom-left') {
  19672. this.dom.frame.style.left = '4px';
  19673. this.dom.frame.style.textAlign = "left";
  19674. this.dom.textArea.style.textAlign = "left";
  19675. this.dom.textArea.style.left = (this.options.iconSize + 15) + 'px';
  19676. this.dom.textArea.style.right = '';
  19677. this.svg.style.left = 0 +'px';
  19678. this.svg.style.right = '';
  19679. }
  19680. else {
  19681. this.dom.frame.style.right = '4px';
  19682. this.dom.frame.style.textAlign = "right";
  19683. this.dom.textArea.style.textAlign = "right";
  19684. this.dom.textArea.style.right = (this.options.iconSize + 15) + 'px';
  19685. this.dom.textArea.style.left = '';
  19686. this.svg.style.right = 0 +'px';
  19687. this.svg.style.left = '';
  19688. }
  19689. if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'top-right') {
  19690. this.dom.frame.style.top = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
  19691. this.dom.frame.style.bottom = '';
  19692. }
  19693. else {
  19694. var scrollableHeight = this.body.domProps.center.height - this.body.domProps.centerContainer.height;
  19695. this.dom.frame.style.bottom = 4 + scrollableHeight + Number(this.body.dom.center.style.top.replace("px","")) + 'px';
  19696. this.dom.frame.style.top = '';
  19697. }
  19698. if (this.options.icons == false) {
  19699. this.dom.frame.style.width = this.dom.textArea.offsetWidth + 10 + 'px';
  19700. this.dom.textArea.style.right = '';
  19701. this.dom.textArea.style.left = '';
  19702. this.svg.style.width = '0px';
  19703. }
  19704. else {
  19705. this.dom.frame.style.width = this.options.iconSize + 15 + this.dom.textArea.offsetWidth + 10 + 'px'
  19706. this.drawLegendIcons();
  19707. }
  19708. var content = '';
  19709. for (var groupId in this.groups) {
  19710. if (this.groups.hasOwnProperty(groupId)) {
  19711. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  19712. content += this.groups[groupId].content + '<br />';
  19713. }
  19714. }
  19715. }
  19716. this.dom.textArea.innerHTML = content;
  19717. this.dom.textArea.style.lineHeight = ((0.75 * this.options.iconSize) + this.options.iconSpacing) + 'px';
  19718. }
  19719. };
  19720. Legend.prototype.drawLegendIcons = function() {
  19721. if (this.dom.frame.parentNode) {
  19722. DOMutil.prepareElements(this.svgElements);
  19723. var padding = window.getComputedStyle(this.dom.frame).paddingTop;
  19724. var iconOffset = Number(padding.replace('px',''));
  19725. var x = iconOffset;
  19726. var iconWidth = this.options.iconSize;
  19727. var iconHeight = 0.75 * this.options.iconSize;
  19728. var y = iconOffset + 0.5 * iconHeight + 3;
  19729. this.svg.style.width = iconWidth + 5 + iconOffset + 'px';
  19730. for (var groupId in this.groups) {
  19731. if (this.groups.hasOwnProperty(groupId)) {
  19732. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  19733. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  19734. y += iconHeight + this.options.iconSpacing;
  19735. }
  19736. }
  19737. }
  19738. DOMutil.cleanupElements(this.svgElements);
  19739. }
  19740. };
  19741. module.exports = Legend;
  19742. /***/ },
  19743. /* 51 */
  19744. /***/ function(module, exports, __webpack_require__) {
  19745. var Emitter = __webpack_require__(11);
  19746. var Hammer = __webpack_require__(19);
  19747. var keycharm = __webpack_require__(36);
  19748. var util = __webpack_require__(1);
  19749. var hammerUtil = __webpack_require__(22);
  19750. var DataSet = __webpack_require__(7);
  19751. var DataView = __webpack_require__(9);
  19752. var dotparser = __webpack_require__(52);
  19753. var gephiParser = __webpack_require__(53);
  19754. var Groups = __webpack_require__(54);
  19755. var Images = __webpack_require__(55);
  19756. var Node = __webpack_require__(56);
  19757. var Edge = __webpack_require__(57);
  19758. var Popup = __webpack_require__(58);
  19759. var MixinLoader = __webpack_require__(59);
  19760. var Activator = __webpack_require__(35);
  19761. var locales = __webpack_require__(70);
  19762. // Load custom shapes into CanvasRenderingContext2D
  19763. __webpack_require__(71);
  19764. /**
  19765. * @constructor Network
  19766. * Create a network visualization, displaying nodes and edges.
  19767. *
  19768. * @param {Element} container The DOM element in which the Network will
  19769. * be created. Normally a div element.
  19770. * @param {Object} data An object containing parameters
  19771. * {Array} nodes
  19772. * {Array} edges
  19773. * @param {Object} options Options
  19774. */
  19775. function Network (container, data, options) {
  19776. if (!(this instanceof Network)) {
  19777. throw new SyntaxError('Constructor must be called with the new operator');
  19778. }
  19779. this._determineBrowserMethod();
  19780. this._initializeMixinLoaders();
  19781. // create variables and set default values
  19782. this.containerElement = container;
  19783. // render and calculation settings
  19784. this.renderRefreshRate = 60; // hz (fps)
  19785. this.renderTimestep = 1000 / this.renderRefreshRate; // ms -- saves calculation later on
  19786. this.renderTime = 0; // measured time it takes to render a frame
  19787. this.physicsTime = 0; // measured time it takes to render a frame
  19788. this.runDoubleSpeed = false;
  19789. this.physicsDiscreteStepsize = 0.50; // discrete stepsize of the simulation
  19790. this.initializing = true;
  19791. this.triggerFunctions = {add:null,edit:null,editEdge:null,connect:null,del:null};
  19792. var customScalingFunction = function (min,max,total,value) {
  19793. if (max == min) {
  19794. return 0.5;
  19795. }
  19796. else {
  19797. var scale = 1 / (max - min);
  19798. return Math.max(0,(value - min)*scale);
  19799. }
  19800. };
  19801. // set constant values
  19802. this.defaultOptions = {
  19803. nodes: {
  19804. customScalingFunction: customScalingFunction,
  19805. mass: 1,
  19806. radiusMin: 10,
  19807. radiusMax: 30,
  19808. radius: 10,
  19809. shape: 'ellipse',
  19810. image: undefined,
  19811. widthMin: 16, // px
  19812. widthMax: 64, // px
  19813. fontColor: 'black',
  19814. fontSize: 14, // px
  19815. fontFace: 'verdana',
  19816. fontFill: undefined,
  19817. fontStrokeWidth: 0, // px
  19818. fontStrokeColor: '#ffffff',
  19819. fontDrawThreshold: 3,
  19820. scaleFontWithValue: false,
  19821. fontSizeMin: 14,
  19822. fontSizeMax: 30,
  19823. fontSizeMaxVisible: 30,
  19824. level: -1,
  19825. color: {
  19826. border: '#2B7CE9',
  19827. background: '#97C2FC',
  19828. highlight: {
  19829. border: '#2B7CE9',
  19830. background: '#D2E5FF'
  19831. },
  19832. hover: {
  19833. border: '#2B7CE9',
  19834. background: '#D2E5FF'
  19835. }
  19836. },
  19837. group: undefined,
  19838. borderWidth: 1,
  19839. borderWidthSelected: undefined
  19840. },
  19841. edges: {
  19842. customScalingFunction: customScalingFunction,
  19843. widthMin: 1, //
  19844. widthMax: 15,//
  19845. width: 1,
  19846. widthSelectionMultiplier: 2,
  19847. hoverWidth: 1.5,
  19848. style: 'line',
  19849. color: {
  19850. color:'#848484',
  19851. highlight:'#848484',
  19852. hover: '#848484'
  19853. },
  19854. opacity:1.0,
  19855. fontColor: '#343434',
  19856. fontSize: 14, // px
  19857. fontFace: 'arial',
  19858. fontFill: 'white',
  19859. fontStrokeWidth: 0, // px
  19860. fontStrokeColor: 'white',
  19861. labelAlignment:'horizontal',
  19862. arrowScaleFactor: 1,
  19863. dash: {
  19864. length: 10,
  19865. gap: 5,
  19866. altLength: undefined
  19867. },
  19868. inheritColor: "from" // to, from, false, true (== from)
  19869. },
  19870. configurePhysics:false,
  19871. physics: {
  19872. barnesHut: {
  19873. enabled: true,
  19874. thetaInverted: 1 / 0.5, // inverted to save time during calculation
  19875. gravitationalConstant: -2000,
  19876. centralGravity: 0.3,
  19877. springLength: 95,
  19878. springConstant: 0.04,
  19879. damping: 0.09
  19880. },
  19881. repulsion: {
  19882. centralGravity: 0.0,
  19883. springLength: 200,
  19884. springConstant: 0.05,
  19885. nodeDistance: 100,
  19886. damping: 0.09
  19887. },
  19888. hierarchicalRepulsion: {
  19889. enabled: false,
  19890. centralGravity: 0.0,
  19891. springLength: 100,
  19892. springConstant: 0.01,
  19893. nodeDistance: 150,
  19894. damping: 0.09
  19895. },
  19896. damping: null,
  19897. centralGravity: null,
  19898. springLength: null,
  19899. springConstant: null
  19900. },
  19901. clustering: { // Per Node in Cluster = PNiC
  19902. enabled: false, // (Boolean) | global on/off switch for clustering.
  19903. initialMaxNodes: 100, // (# nodes) | if the initial amount of nodes is larger than this, we cluster until the total number is less than this threshold.
  19904. 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
  19905. 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
  19906. chainThreshold: 0.4, // (% of all drawn nodes)| maximum percentage of allowed chainnodes (long strings of connected nodes) within all nodes. (lower means less chains).
  19907. clusterEdgeThreshold: 20, // (px) | edge length threshold. if smaller, this node is clustered.
  19908. sectorThreshold: 100, // (# nodes in cluster) | cluster size threshold. If larger, expanding in own sector.
  19909. 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.
  19910. fontSizeMultiplier: 4.0, // (px PNiC) | how much the cluster font size grows per node in cluster (in px).
  19911. maxFontSize: 1000,
  19912. forceAmplification: 0.1, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster).
  19913. distanceAmplification: 0.1, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster).
  19914. edgeGrowth: 20, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
  19915. nodeScaling: {width: 1, // (px PNiC) | growth of the width per node in cluster.
  19916. height: 1, // (px PNiC) | growth of the height per node in cluster.
  19917. radius: 1}, // (px PNiC) | growth of the radius per node in cluster.
  19918. maxNodeSizeIncrements: 600, // (# increments) | max growth of the width per node in cluster.
  19919. activeAreaBoxSize: 80, // (px) | box area around the curser where clusters are popped open.
  19920. clusterLevelDifference: 2, // used for normalization of the cluster levels
  19921. clusterByZoom: true // enable clustering through zooming in and out
  19922. },
  19923. navigation: {
  19924. enabled: false
  19925. },
  19926. keyboard: {
  19927. enabled: false,
  19928. speed: {x: 10, y: 10, zoom: 0.02},
  19929. bindToWindow: true
  19930. },
  19931. dataManipulation: {
  19932. enabled: false,
  19933. initiallyVisible: false
  19934. },
  19935. hierarchicalLayout: {
  19936. enabled:false,
  19937. levelSeparation: 150,
  19938. nodeSpacing: 100,
  19939. direction: "UD", // UD, DU, LR, RL
  19940. layout: "hubsize" // hubsize, directed
  19941. },
  19942. freezeForStabilization: false,
  19943. smoothCurves: {
  19944. enabled: true,
  19945. dynamic: true,
  19946. type: "continuous",
  19947. roundness: 0.5
  19948. },
  19949. maxVelocity: 50,
  19950. minVelocity: 0.1, // px/s
  19951. stabilize: true, // stabilize before displaying the network
  19952. stabilizationIterations: 1000, // maximum number of iteration to stabilize
  19953. zoomExtentOnStabilize: true,
  19954. locale: 'en',
  19955. locales: locales,
  19956. tooltip: {
  19957. delay: 300,
  19958. fontColor: 'black',
  19959. fontSize: 14, // px
  19960. fontFace: 'verdana',
  19961. color: {
  19962. border: '#666',
  19963. background: '#FFFFC6'
  19964. }
  19965. },
  19966. dragNetwork: true,
  19967. dragNodes: true,
  19968. zoomable: true,
  19969. hover: false,
  19970. hideEdgesOnDrag: false,
  19971. hideNodesOnDrag: false,
  19972. width : '100%',
  19973. height : '100%',
  19974. selectable: true
  19975. };
  19976. this.constants = util.extend({}, this.defaultOptions);
  19977. this.pixelRatio = 1;
  19978. this.hoverObj = {nodes:{},edges:{}};
  19979. this.controlNodesActive = false;
  19980. this.navigationHammers = {existing:[], _new: []};
  19981. // animation properties
  19982. this.animationSpeed = 1/this.renderRefreshRate;
  19983. this.animationEasingFunction = "easeInOutQuint";
  19984. this.animating = false;
  19985. this.easingTime = 0;
  19986. this.sourceScale = 0;
  19987. this.targetScale = 0;
  19988. this.sourceTranslation = 0;
  19989. this.targetTranslation = 0;
  19990. this.lockedOnNodeId = null;
  19991. this.lockedOnNodeOffset = null;
  19992. this.touchTime = 0;
  19993. // Node variables
  19994. var network = this;
  19995. this.groups = new Groups(); // object with groups
  19996. this.images = new Images(); // object with images
  19997. this.images.setOnloadCallback(function (status) {
  19998. network._redraw();
  19999. });
  20000. // keyboard navigation variables
  20001. this.xIncrement = 0;
  20002. this.yIncrement = 0;
  20003. this.zoomIncrement = 0;
  20004. // loading all the mixins:
  20005. // load the force calculation functions, grouped under the physics system.
  20006. this._loadPhysicsSystem();
  20007. // create a frame and canvas
  20008. this._create();
  20009. // load the sector system. (mandatory, fully integrated with Network)
  20010. this._loadSectorSystem();
  20011. // load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it)
  20012. this._loadClusterSystem();
  20013. // load the selection system. (mandatory, required by Network)
  20014. this._loadSelectionSystem();
  20015. // load the selection system. (mandatory, required by Network)
  20016. this._loadHierarchySystem();
  20017. // apply options
  20018. this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
  20019. this._setScale(1);
  20020. this.setOptions(options);
  20021. // other vars
  20022. this.freezeSimulation = false;// freeze the simulation
  20023. this.cachedFunctions = {};
  20024. this.startedStabilization = false;
  20025. this.stabilized = false;
  20026. this.stabilizationIterations = null;
  20027. this.draggingNodes = false;
  20028. // containers for nodes and edges
  20029. this.calculationNodes = {};
  20030. this.calculationNodeIndices = [];
  20031. this.nodeIndices = []; // array with all the indices of the nodes. Used to speed up forces calculation
  20032. this.nodes = {}; // object with Node objects
  20033. this.edges = {}; // object with Edge objects
  20034. // position and scale variables and objects
  20035. this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw.
  20036. this.canvasBottomRight = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  20037. this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  20038. this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action
  20039. this.scale = 1; // defining the global scale variable in the constructor
  20040. this.previousScale = this.scale; // this is used to check if the zoom operation is zooming in or out
  20041. // datasets or dataviews
  20042. this.nodesData = null; // A DataSet or DataView
  20043. this.edgesData = null; // A DataSet or DataView
  20044. // create event listeners used to subscribe on the DataSets of the nodes and edges
  20045. this.nodesListeners = {
  20046. 'add': function (event, params) {
  20047. network._addNodes(params.items);
  20048. network.start();
  20049. },
  20050. 'update': function (event, params) {
  20051. network._updateNodes(params.items, params.data);
  20052. network.start();
  20053. },
  20054. 'remove': function (event, params) {
  20055. network._removeNodes(params.items);
  20056. network.start();
  20057. }
  20058. };
  20059. this.edgesListeners = {
  20060. 'add': function (event, params) {
  20061. network._addEdges(params.items);
  20062. network.start();
  20063. },
  20064. 'update': function (event, params) {
  20065. network._updateEdges(params.items);
  20066. network.start();
  20067. },
  20068. 'remove': function (event, params) {
  20069. network._removeEdges(params.items);
  20070. network.start();
  20071. }
  20072. };
  20073. // properties for the animation
  20074. this.moving = true;
  20075. this.timer = undefined; // Scheduling function. Is definded in this.start();
  20076. // load data (the disable start variable will be the same as the enabled clustering)
  20077. this.setData(data,this.constants.clustering.enabled || this.constants.hierarchicalLayout.enabled);
  20078. // hierarchical layout
  20079. this.initializing = false;
  20080. if (this.constants.hierarchicalLayout.enabled == true) {
  20081. this._setupHierarchicalLayout();
  20082. }
  20083. else {
  20084. // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
  20085. if (this.constants.stabilize == false) {
  20086. this.zoomExtent({duration:0}, true, this.constants.clustering.enabled);
  20087. }
  20088. }
  20089. // if clustering is disabled, the simulation will have started in the setData function
  20090. if (this.constants.clustering.enabled) {
  20091. this.startWithClustering();
  20092. }
  20093. }
  20094. // Extend Network with an Emitter mixin
  20095. Emitter(Network.prototype);
  20096. /**
  20097. * Determine if the browser requires a setTimeout or a requestAnimationFrame. This was required because
  20098. * some implementations (safari and IE9) did not support requestAnimationFrame
  20099. * @private
  20100. */
  20101. Network.prototype._determineBrowserMethod = function() {
  20102. var browserType = navigator.userAgent.toLowerCase();
  20103. this.requiresTimeout = false;
  20104. if (browserType.indexOf('msie 9.0') != -1) { // IE 9
  20105. this.requiresTimeout = true;
  20106. }
  20107. else if (browserType.indexOf('safari') != -1) { // safari
  20108. if (browserType.indexOf('chrome') <= -1) {
  20109. this.requiresTimeout = true;
  20110. }
  20111. }
  20112. }
  20113. /**
  20114. * Get the script path where the vis.js library is located
  20115. *
  20116. * @returns {string | null} path Path or null when not found. Path does not
  20117. * end with a slash.
  20118. * @private
  20119. */
  20120. Network.prototype._getScriptPath = function() {
  20121. var scripts = document.getElementsByTagName( 'script' );
  20122. // find script named vis.js or vis.min.js
  20123. for (var i = 0; i < scripts.length; i++) {
  20124. var src = scripts[i].src;
  20125. var match = src && /\/?vis(.min)?\.js$/.exec(src);
  20126. if (match) {
  20127. // return path without the script name
  20128. return src.substring(0, src.length - match[0].length);
  20129. }
  20130. }
  20131. return null;
  20132. };
  20133. /**
  20134. * Find the center position of the network
  20135. * @private
  20136. */
  20137. Network.prototype._getRange = function(specificNodes) {
  20138. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  20139. if (specificNodes.length > 0) {
  20140. for (var i = 0; i < specificNodes.length; i++) {
  20141. node = this.nodes[specificNodes[i]];
  20142. if (minX > (node.boundingBox.left)) {
  20143. minX = node.boundingBox.left;
  20144. }
  20145. if (maxX < (node.boundingBox.right)) {
  20146. maxX = node.boundingBox.right;
  20147. }
  20148. if (minY > (node.boundingBox.bottom)) {
  20149. minY = node.boundingBox.top;
  20150. } // top is negative, bottom is positive
  20151. if (maxY < (node.boundingBox.top)) {
  20152. maxY = node.boundingBox.bottom;
  20153. } // top is negative, bottom is positive
  20154. }
  20155. }
  20156. else {
  20157. for (var nodeId in this.nodes) {
  20158. if (this.nodes.hasOwnProperty(nodeId)) {
  20159. node = this.nodes[nodeId];
  20160. if (minX > (node.boundingBox.left)) {
  20161. minX = node.boundingBox.left;
  20162. }
  20163. if (maxX < (node.boundingBox.right)) {
  20164. maxX = node.boundingBox.right;
  20165. }
  20166. if (minY > (node.boundingBox.bottom)) {
  20167. minY = node.boundingBox.top;
  20168. } // top is negative, bottom is positive
  20169. if (maxY < (node.boundingBox.top)) {
  20170. maxY = node.boundingBox.bottom;
  20171. } // top is negative, bottom is positive
  20172. }
  20173. }
  20174. }
  20175. if (minX == 1e9 && maxX == -1e9 && minY == 1e9 && maxY == -1e9) {
  20176. minY = 0, maxY = 0, minX = 0, maxX = 0;
  20177. }
  20178. return {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  20179. };
  20180. /**
  20181. * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  20182. * @returns {{x: number, y: number}}
  20183. * @private
  20184. */
  20185. Network.prototype._findCenter = function(range) {
  20186. return {x: (0.5 * (range.maxX + range.minX)),
  20187. y: (0.5 * (range.maxY + range.minY))};
  20188. };
  20189. /**
  20190. * This function zooms out to fit all data on screen based on amount of nodes
  20191. *
  20192. * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
  20193. * @param {Boolean} [disableStart] | If true, start is not called.
  20194. */
  20195. Network.prototype.zoomExtent = function(options, initialZoom, disableStart) {
  20196. this._redraw(true);
  20197. if (initialZoom === undefined) {initialZoom = false;}
  20198. if (disableStart === undefined) {disableStart = false;}
  20199. if (options === undefined) {options = {nodes:[]};}
  20200. if (options.nodes === undefined) {
  20201. options.nodes = [];
  20202. }
  20203. var range;
  20204. var zoomLevel;
  20205. if (initialZoom == true) {
  20206. // check if more than half of the nodes have a predefined position. If so, we use the range, not the approximation.
  20207. var positionDefined = 0;
  20208. for (var nodeId in this.nodes) {
  20209. if (this.nodes.hasOwnProperty(nodeId)) {
  20210. var node = this.nodes[nodeId];
  20211. if (node.predefinedPosition == true) {
  20212. positionDefined += 1;
  20213. }
  20214. }
  20215. }
  20216. if (positionDefined > 0.5 * this.nodeIndices.length) {
  20217. this.zoomExtent(options,false,disableStart);
  20218. return;
  20219. }
  20220. range = this._getRange(options.nodes);
  20221. var numberOfNodes = this.nodeIndices.length;
  20222. if (this.constants.smoothCurves == true) {
  20223. if (this.constants.clustering.enabled == true &&
  20224. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  20225. 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.
  20226. }
  20227. else {
  20228. zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  20229. }
  20230. }
  20231. else {
  20232. if (this.constants.clustering.enabled == true &&
  20233. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  20234. 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.
  20235. }
  20236. else {
  20237. zoomLevel = 30.5062972 / (numberOfNodes + 19.93597763) + 0.08413486; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  20238. }
  20239. }
  20240. // correct for larger canvasses.
  20241. var factor = Math.min(this.frame.canvas.clientWidth / 600, this.frame.canvas.clientHeight / 600);
  20242. zoomLevel *= factor;
  20243. }
  20244. else {
  20245. range = this._getRange(options.nodes);
  20246. var xDistance = Math.abs(range.maxX - range.minX) * 1.1;
  20247. var yDistance = Math.abs(range.maxY - range.minY) * 1.1;
  20248. var xZoomLevel = this.frame.canvas.clientWidth / xDistance;
  20249. var yZoomLevel = this.frame.canvas.clientHeight / yDistance;
  20250. zoomLevel = (xZoomLevel <= yZoomLevel) ? xZoomLevel : yZoomLevel;
  20251. }
  20252. if (zoomLevel > 1.0) {
  20253. zoomLevel = 1.0;
  20254. }
  20255. var center = this._findCenter(range);
  20256. if (disableStart == false) {
  20257. var options = {position: center, scale: zoomLevel, animation: options};
  20258. this.moveTo(options);
  20259. this.moving = true;
  20260. this.start();
  20261. }
  20262. else {
  20263. center.x *= zoomLevel;
  20264. center.y *= zoomLevel;
  20265. center.x -= 0.5 * this.frame.canvas.clientWidth;
  20266. center.y -= 0.5 * this.frame.canvas.clientHeight;
  20267. this._setScale(zoomLevel);
  20268. this._setTranslation(-center.x,-center.y);
  20269. }
  20270. };
  20271. /**
  20272. * Update the this.nodeIndices with the most recent node index list
  20273. * @private
  20274. */
  20275. Network.prototype._updateNodeIndexList = function() {
  20276. this._clearNodeIndexList();
  20277. for (var idx in this.nodes) {
  20278. if (this.nodes.hasOwnProperty(idx)) {
  20279. this.nodeIndices.push(idx);
  20280. }
  20281. }
  20282. };
  20283. /**
  20284. * Set nodes and edges, and optionally options as well.
  20285. *
  20286. * @param {Object} data Object containing parameters:
  20287. * {Array | DataSet | DataView} [nodes] Array with nodes
  20288. * {Array | DataSet | DataView} [edges] Array with edges
  20289. * {String} [dot] String containing data in DOT format
  20290. * {String} [gephi] String containing data in gephi JSON format
  20291. * {Options} [options] Object with options
  20292. * @param {Boolean} [disableStart] | optional: disable the calling of the start function.
  20293. */
  20294. Network.prototype.setData = function(data, disableStart) {
  20295. if (disableStart === undefined) {
  20296. disableStart = false;
  20297. }
  20298. // we set initializing to true to ensure that the hierarchical layout is not performed until both nodes and edges are added.
  20299. this.initializing = true;
  20300. if (data && data.dot && (data.nodes || data.edges)) {
  20301. throw new SyntaxError('Data must contain either parameter "dot" or ' +
  20302. ' parameter pair "nodes" and "edges", but not both.');
  20303. }
  20304. // 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.
  20305. if (this.constants.dataManipulation.enabled == true) {
  20306. this._createManipulatorBar();
  20307. }
  20308. // set options
  20309. this.setOptions(data && data.options);
  20310. // set all data
  20311. if (data && data.dot) {
  20312. // parse DOT file
  20313. if(data && data.dot) {
  20314. var dotData = dotparser.DOTToGraph(data.dot);
  20315. this.setData(dotData);
  20316. return;
  20317. }
  20318. }
  20319. else if (data && data.gephi) {
  20320. // parse DOT file
  20321. if(data && data.gephi) {
  20322. var gephiData = gephiParser.parseGephi(data.gephi);
  20323. this.setData(gephiData);
  20324. return;
  20325. }
  20326. }
  20327. else {
  20328. this._setNodes(data && data.nodes);
  20329. this._setEdges(data && data.edges);
  20330. }
  20331. this._putDataInSector();
  20332. if (disableStart == false) {
  20333. if (this.constants.hierarchicalLayout.enabled == true) {
  20334. this._resetLevels();
  20335. this._setupHierarchicalLayout();
  20336. }
  20337. else {
  20338. // find a stable position or start animating to a stable position
  20339. if (this.constants.stabilize == true) {
  20340. this._stabilize();
  20341. }
  20342. }
  20343. this.start();
  20344. }
  20345. this.initializing = false;
  20346. };
  20347. /**
  20348. * Set options
  20349. * @param {Object} options
  20350. */
  20351. Network.prototype.setOptions = function (options) {
  20352. if (options) {
  20353. var prop;
  20354. var fields = ['nodes','edges','smoothCurves','hierarchicalLayout','clustering','navigation',
  20355. 'keyboard','dataManipulation','onAdd','onEdit','onEditEdge','onConnect','onDelete','clickToUse'
  20356. ];
  20357. // extend all but the values in fields
  20358. util.selectiveNotDeepExtend(fields,this.constants, options);
  20359. util.selectiveNotDeepExtend(['color'],this.constants.nodes, options.nodes);
  20360. util.selectiveNotDeepExtend(['color','length'],this.constants.edges, options.edges);
  20361. if (options.physics) {
  20362. util.mergeOptions(this.constants.physics, options.physics,'barnesHut');
  20363. util.mergeOptions(this.constants.physics, options.physics,'repulsion');
  20364. if (options.physics.hierarchicalRepulsion) {
  20365. this.constants.hierarchicalLayout.enabled = true;
  20366. this.constants.physics.hierarchicalRepulsion.enabled = true;
  20367. this.constants.physics.barnesHut.enabled = false;
  20368. for (prop in options.physics.hierarchicalRepulsion) {
  20369. if (options.physics.hierarchicalRepulsion.hasOwnProperty(prop)) {
  20370. this.constants.physics.hierarchicalRepulsion[prop] = options.physics.hierarchicalRepulsion[prop];
  20371. }
  20372. }
  20373. }
  20374. }
  20375. if (options.onAdd) {this.triggerFunctions.add = options.onAdd;}
  20376. if (options.onEdit) {this.triggerFunctions.edit = options.onEdit;}
  20377. if (options.onEditEdge) {this.triggerFunctions.editEdge = options.onEditEdge;}
  20378. if (options.onConnect) {this.triggerFunctions.connect = options.onConnect;}
  20379. if (options.onDelete) {this.triggerFunctions.del = options.onDelete;}
  20380. util.mergeOptions(this.constants, options,'smoothCurves');
  20381. util.mergeOptions(this.constants, options,'hierarchicalLayout');
  20382. util.mergeOptions(this.constants, options,'clustering');
  20383. util.mergeOptions(this.constants, options,'navigation');
  20384. util.mergeOptions(this.constants, options,'keyboard');
  20385. util.mergeOptions(this.constants, options,'dataManipulation');
  20386. if (options.dataManipulation) {
  20387. this.editMode = this.constants.dataManipulation.initiallyVisible;
  20388. }
  20389. // TODO: work out these options and document them
  20390. if (options.edges) {
  20391. if (options.edges.color !== undefined) {
  20392. if (util.isString(options.edges.color)) {
  20393. this.constants.edges.color = {};
  20394. this.constants.edges.color.color = options.edges.color;
  20395. this.constants.edges.color.highlight = options.edges.color;
  20396. this.constants.edges.color.hover = options.edges.color;
  20397. }
  20398. else {
  20399. if (options.edges.color.color !== undefined) {this.constants.edges.color.color = options.edges.color.color;}
  20400. if (options.edges.color.highlight !== undefined) {this.constants.edges.color.highlight = options.edges.color.highlight;}
  20401. if (options.edges.color.hover !== undefined) {this.constants.edges.color.hover = options.edges.color.hover;}
  20402. }
  20403. this.constants.edges.inheritColor = false;
  20404. }
  20405. if (!options.edges.fontColor) {
  20406. if (options.edges.color !== undefined) {
  20407. if (util.isString(options.edges.color)) {this.constants.edges.fontColor = options.edges.color;}
  20408. else if (options.edges.color.color !== undefined) {this.constants.edges.fontColor = options.edges.color.color;}
  20409. }
  20410. }
  20411. }
  20412. if (options.nodes) {
  20413. if (options.nodes.color) {
  20414. var newColorObj = util.parseColor(options.nodes.color);
  20415. this.constants.nodes.color.background = newColorObj.background;
  20416. this.constants.nodes.color.border = newColorObj.border;
  20417. this.constants.nodes.color.highlight.background = newColorObj.highlight.background;
  20418. this.constants.nodes.color.highlight.border = newColorObj.highlight.border;
  20419. this.constants.nodes.color.hover.background = newColorObj.hover.background;
  20420. this.constants.nodes.color.hover.border = newColorObj.hover.border;
  20421. }
  20422. }
  20423. if (options.groups) {
  20424. for (var groupname in options.groups) {
  20425. if (options.groups.hasOwnProperty(groupname)) {
  20426. var group = options.groups[groupname];
  20427. this.groups.add(groupname, group);
  20428. }
  20429. }
  20430. }
  20431. if (options.tooltip) {
  20432. for (prop in options.tooltip) {
  20433. if (options.tooltip.hasOwnProperty(prop)) {
  20434. this.constants.tooltip[prop] = options.tooltip[prop];
  20435. }
  20436. }
  20437. if (options.tooltip.color) {
  20438. this.constants.tooltip.color = util.parseColor(options.tooltip.color);
  20439. }
  20440. }
  20441. if ('clickToUse' in options) {
  20442. if (options.clickToUse) {
  20443. if (!this.activator) {
  20444. this.activator = new Activator(this.frame);
  20445. this.activator.on('change', this._createKeyBinds.bind(this));
  20446. }
  20447. }
  20448. else {
  20449. if (this.activator) {
  20450. this.activator.destroy();
  20451. delete this.activator;
  20452. }
  20453. }
  20454. }
  20455. if (options.labels) {
  20456. throw new Error('Option "labels" is deprecated. Use options "locale" and "locales" instead.');
  20457. }
  20458. // (Re)loading the mixins that can be enabled or disabled in the options.
  20459. // load the force calculation functions, grouped under the physics system.
  20460. this._loadPhysicsSystem();
  20461. // load the navigation system.
  20462. this._loadNavigationControls();
  20463. // load the data manipulation system
  20464. this._loadManipulationSystem();
  20465. // configure the smooth curves
  20466. this._configureSmoothCurves();
  20467. // bind hammer
  20468. this._bindHammer();
  20469. // bind keys. If disabled, this will not do anything;
  20470. this._createKeyBinds();
  20471. this._markAllEdgesAsDirty();
  20472. this.setSize(this.constants.width, this.constants.height);
  20473. this.moving = true;
  20474. this.start();
  20475. }
  20476. };
  20477. /**
  20478. * Create the main frame for the Network.
  20479. * This function is executed once when a Network object is created. The frame
  20480. * contains a canvas, and this canvas contains all objects like the axis and
  20481. * nodes.
  20482. * @private
  20483. */
  20484. Network.prototype._create = function () {
  20485. // remove all elements from the container element.
  20486. while (this.containerElement.hasChildNodes()) {
  20487. this.containerElement.removeChild(this.containerElement.firstChild);
  20488. }
  20489. this.frame = document.createElement('div');
  20490. this.frame.className = 'vis network-frame';
  20491. this.frame.style.position = 'relative';
  20492. this.frame.style.overflow = 'hidden';
  20493. this.frame.tabIndex = 900;
  20494. //////////////////////////////////////////////////////////////////
  20495. this.frame.canvas = document.createElement("canvas");
  20496. this.frame.canvas.style.position = 'relative';
  20497. this.frame.appendChild(this.frame.canvas);
  20498. if (!this.frame.canvas.getContext) {
  20499. var noCanvas = document.createElement( 'DIV' );
  20500. noCanvas.style.color = 'red';
  20501. noCanvas.style.fontWeight = 'bold' ;
  20502. noCanvas.style.padding = '10px';
  20503. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  20504. this.frame.canvas.appendChild(noCanvas);
  20505. }
  20506. else {
  20507. var ctx = this.frame.canvas.getContext("2d");
  20508. this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio ||
  20509. ctx.mozBackingStorePixelRatio ||
  20510. ctx.msBackingStorePixelRatio ||
  20511. ctx.oBackingStorePixelRatio ||
  20512. ctx.backingStorePixelRatio || 1);
  20513. this.frame.canvas.getContext("2d").setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
  20514. }
  20515. this._bindHammer();
  20516. };
  20517. /**
  20518. * This function binds hammer, it can be repeated over and over due to the uniqueness check.
  20519. * @private
  20520. */
  20521. Network.prototype._bindHammer = function() {
  20522. var me = this;
  20523. if (this.hammer !== undefined) {
  20524. this.hammer.dispose();
  20525. }
  20526. this.drag = {};
  20527. this.pinch = {};
  20528. this.hammer = Hammer(this.frame.canvas, {
  20529. prevent_default: true
  20530. });
  20531. this.hammer.on('tap', me._onTap.bind(me) );
  20532. this.hammer.on('doubletap', me._onDoubleTap.bind(me) );
  20533. this.hammer.on('hold', me._onHold.bind(me) );
  20534. this.hammer.on('touch', me._onTouch.bind(me) );
  20535. this.hammer.on('dragstart', me._onDragStart.bind(me) );
  20536. this.hammer.on('drag', me._onDrag.bind(me) );
  20537. this.hammer.on('dragend', me._onDragEnd.bind(me) );
  20538. if (this.constants.zoomable == true) {
  20539. this.hammer.on('mousewheel', me._onMouseWheel.bind(me));
  20540. this.hammer.on('DOMMouseScroll', me._onMouseWheel.bind(me)); // for FF
  20541. this.hammer.on('pinch', me._onPinch.bind(me) );
  20542. }
  20543. this.hammer.on('mousemove', me._onMouseMoveTitle.bind(me) );
  20544. this.hammerFrame = Hammer(this.frame, {
  20545. prevent_default: true
  20546. });
  20547. this.hammerFrame.on('release', me._onRelease.bind(me) );
  20548. // add the frame to the container element
  20549. this.containerElement.appendChild(this.frame);
  20550. }
  20551. /**
  20552. * Binding the keys for keyboard navigation. These functions are defined in the NavigationMixin
  20553. * @private
  20554. */
  20555. Network.prototype._createKeyBinds = function() {
  20556. var me = this;
  20557. if (this.keycharm !== undefined) {
  20558. this.keycharm.destroy();
  20559. }
  20560. if (this.constants.keyboard.bindToWindow == true) {
  20561. this.keycharm = keycharm({container: window, preventDefault: false});
  20562. }
  20563. else {
  20564. this.keycharm = keycharm({container: this.frame, preventDefault: false});
  20565. }
  20566. this.keycharm.reset();
  20567. if (this.constants.keyboard.enabled && this.isActive()) {
  20568. this.keycharm.bind("up", this._moveUp.bind(me) , "keydown");
  20569. this.keycharm.bind("up", this._yStopMoving.bind(me), "keyup");
  20570. this.keycharm.bind("down", this._moveDown.bind(me) , "keydown");
  20571. this.keycharm.bind("down", this._yStopMoving.bind(me), "keyup");
  20572. this.keycharm.bind("left", this._moveLeft.bind(me) , "keydown");
  20573. this.keycharm.bind("left", this._xStopMoving.bind(me), "keyup");
  20574. this.keycharm.bind("right",this._moveRight.bind(me), "keydown");
  20575. this.keycharm.bind("right",this._xStopMoving.bind(me), "keyup");
  20576. this.keycharm.bind("=", this._zoomIn.bind(me), "keydown");
  20577. this.keycharm.bind("=", this._stopZoom.bind(me), "keyup");
  20578. this.keycharm.bind("num+", this._zoomIn.bind(me), "keydown");
  20579. this.keycharm.bind("num+", this._stopZoom.bind(me), "keyup");
  20580. this.keycharm.bind("num-", this._zoomOut.bind(me), "keydown");
  20581. this.keycharm.bind("num-", this._stopZoom.bind(me), "keyup");
  20582. this.keycharm.bind("-", this._zoomOut.bind(me), "keydown");
  20583. this.keycharm.bind("-", this._stopZoom.bind(me), "keyup");
  20584. this.keycharm.bind("[", this._zoomIn.bind(me), "keydown");
  20585. this.keycharm.bind("[", this._stopZoom.bind(me), "keyup");
  20586. this.keycharm.bind("]", this._zoomOut.bind(me), "keydown");
  20587. this.keycharm.bind("]", this._stopZoom.bind(me), "keyup");
  20588. this.keycharm.bind("pageup",this._zoomIn.bind(me), "keydown");
  20589. this.keycharm.bind("pageup",this._stopZoom.bind(me), "keyup");
  20590. this.keycharm.bind("pagedown",this._zoomOut.bind(me),"keydown");
  20591. this.keycharm.bind("pagedown",this._stopZoom.bind(me), "keyup");
  20592. }
  20593. this.keycharm.bind("1",this.increaseClusterLevel.bind(me), "keydown");
  20594. this.keycharm.bind("2",this.decreaseClusterLevel.bind(me), "keydown");
  20595. this.keycharm.bind("3",this.forceAggregateHubs.bind(me,true),"keydown");
  20596. this.keycharm.bind("4",this.normalizeClusterLevels.bind(me), "keydown");
  20597. if (this.constants.dataManipulation.enabled == true) {
  20598. this.keycharm.bind("esc",this._createManipulatorBar.bind(me));
  20599. this.keycharm.bind("delete",this._deleteSelected.bind(me));
  20600. }
  20601. };
  20602. /**
  20603. * Cleans up all bindings of the network, removing it fully from the memory IF the variable is set to null after calling this function.
  20604. * var network = new vis.Network(..);
  20605. * network.destroy();
  20606. * network = null;
  20607. */
  20608. Network.prototype.destroy = function() {
  20609. this.start = function () {};
  20610. this.redraw = function () {};
  20611. this.timer = false;
  20612. // cleanup physicsConfiguration if it exists
  20613. this._cleanupPhysicsConfiguration();
  20614. // remove keybindings
  20615. this.keycharm.reset();
  20616. // clear hammer bindings
  20617. this.hammer.dispose();
  20618. // clear events
  20619. this.off();
  20620. this._recursiveDOMDelete(this.containerElement);
  20621. }
  20622. Network.prototype._recursiveDOMDelete = function(DOMobject) {
  20623. while (DOMobject.hasChildNodes() == true) {
  20624. this._recursiveDOMDelete(DOMobject.firstChild);
  20625. DOMobject.removeChild(DOMobject.firstChild);
  20626. }
  20627. }
  20628. /**
  20629. * Get the pointer location from a touch location
  20630. * @param {{pageX: Number, pageY: Number}} touch
  20631. * @return {{x: Number, y: Number}} pointer
  20632. * @private
  20633. */
  20634. Network.prototype._getPointer = function (touch) {
  20635. return {
  20636. x: touch.pageX - util.getAbsoluteLeft(this.frame.canvas),
  20637. y: touch.pageY - util.getAbsoluteTop(this.frame.canvas)
  20638. };
  20639. };
  20640. /**
  20641. * On start of a touch gesture, store the pointer
  20642. * @param event
  20643. * @private
  20644. */
  20645. Network.prototype._onTouch = function (event) {
  20646. if (new Date().valueOf() - this.touchTime > 100) {
  20647. this.drag.pointer = this._getPointer(event.gesture.center);
  20648. this.drag.pinched = false;
  20649. this.pinch.scale = this._getScale();
  20650. // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame)
  20651. this.touchTime = new Date().valueOf();
  20652. this._handleTouch(this.drag.pointer);
  20653. }
  20654. };
  20655. /**
  20656. * handle drag start event
  20657. * @private
  20658. */
  20659. Network.prototype._onDragStart = function (event) {
  20660. this._handleDragStart(event);
  20661. };
  20662. /**
  20663. * This function is called by _onDragStart.
  20664. * It is separated out because we can then overload it for the datamanipulation system.
  20665. *
  20666. * @private
  20667. */
  20668. Network.prototype._handleDragStart = function(event) {
  20669. // in case the touch event was triggered on an external div, do the initial touch now.
  20670. if (this.drag.pointer === undefined) {
  20671. this._onTouch(event);
  20672. }
  20673. var node = this._getNodeAt(this.drag.pointer);
  20674. // note: drag.pointer is set in _onTouch to get the initial touch location
  20675. this.drag.dragging = true;
  20676. this.drag.selection = [];
  20677. this.drag.translation = this._getTranslation();
  20678. this.drag.nodeId = null;
  20679. this.draggingNodes = false;
  20680. if (node != null && this.constants.dragNodes == true) {
  20681. this.draggingNodes = true;
  20682. this.drag.nodeId = node.id;
  20683. // select the clicked node if not yet selected
  20684. if (!node.isSelected()) {
  20685. this._selectObject(node,false);
  20686. }
  20687. this.emit("dragStart",{nodeIds:this.getSelection().nodes});
  20688. // create an array with the selected nodes and their original location and status
  20689. for (var objectId in this.selectionObj.nodes) {
  20690. if (this.selectionObj.nodes.hasOwnProperty(objectId)) {
  20691. var object = this.selectionObj.nodes[objectId];
  20692. var s = {
  20693. id: object.id,
  20694. node: object,
  20695. // store original x, y, xFixed and yFixed, make the node temporarily Fixed
  20696. x: object.x,
  20697. y: object.y,
  20698. xFixed: object.xFixed,
  20699. yFixed: object.yFixed
  20700. };
  20701. object.xFixed = true;
  20702. object.yFixed = true;
  20703. this.drag.selection.push(s);
  20704. }
  20705. }
  20706. }
  20707. };
  20708. /**
  20709. * handle drag event
  20710. * @private
  20711. */
  20712. Network.prototype._onDrag = function (event) {
  20713. this._handleOnDrag(event)
  20714. };
  20715. /**
  20716. * This function is called by _onDrag.
  20717. * It is separated out because we can then overload it for the datamanipulation system.
  20718. *
  20719. * @private
  20720. */
  20721. Network.prototype._handleOnDrag = function(event) {
  20722. if (this.drag.pinched) {
  20723. return;
  20724. }
  20725. // remove the focus on node if it is focussed on by the focusOnNode
  20726. this.releaseNode();
  20727. var pointer = this._getPointer(event.gesture.center);
  20728. var me = this;
  20729. var drag = this.drag;
  20730. var selection = drag.selection;
  20731. if (selection && selection.length && this.constants.dragNodes == true) {
  20732. // calculate delta's and new location
  20733. var deltaX = pointer.x - drag.pointer.x;
  20734. var deltaY = pointer.y - drag.pointer.y;
  20735. // update position of all selected nodes
  20736. selection.forEach(function (s) {
  20737. var node = s.node;
  20738. if (!s.xFixed) {
  20739. node.x = me._XconvertDOMtoCanvas(me._XconvertCanvasToDOM(s.x) + deltaX);
  20740. }
  20741. if (!s.yFixed) {
  20742. node.y = me._YconvertDOMtoCanvas(me._YconvertCanvasToDOM(s.y) + deltaY);
  20743. }
  20744. });
  20745. // start _animationStep if not yet running
  20746. if (!this.moving) {
  20747. this.moving = true;
  20748. this.start();
  20749. }
  20750. }
  20751. else {
  20752. // move the network
  20753. if (this.constants.dragNetwork == true) {
  20754. // if the drag was not started properly because the click started outside the network div, start it now.
  20755. if (this.drag.pointer === undefined) {
  20756. this._handleDragStart(event);
  20757. return;
  20758. }
  20759. var diffX = pointer.x - this.drag.pointer.x;
  20760. var diffY = pointer.y - this.drag.pointer.y;
  20761. this._setTranslation(
  20762. this.drag.translation.x + diffX,
  20763. this.drag.translation.y + diffY
  20764. );
  20765. this._redraw();
  20766. }
  20767. }
  20768. };
  20769. /**
  20770. * handle drag start event
  20771. * @private
  20772. */
  20773. Network.prototype._onDragEnd = function (event) {
  20774. this._handleDragEnd(event);
  20775. };
  20776. Network.prototype._handleDragEnd = function(event) {
  20777. this.drag.dragging = false;
  20778. var selection = this.drag.selection;
  20779. if (selection && selection.length) {
  20780. selection.forEach(function (s) {
  20781. // restore original xFixed and yFixed
  20782. s.node.xFixed = s.xFixed;
  20783. s.node.yFixed = s.yFixed;
  20784. });
  20785. this.moving = true;
  20786. this.start();
  20787. }
  20788. else {
  20789. this._redraw();
  20790. }
  20791. if (this.draggingNodes == false) {
  20792. this.emit("dragEnd",{nodeIds:[]});
  20793. }
  20794. else {
  20795. this.emit("dragEnd",{nodeIds:this.getSelection().nodes});
  20796. }
  20797. }
  20798. /**
  20799. * handle tap/click event: select/unselect a node
  20800. * @private
  20801. */
  20802. Network.prototype._onTap = function (event) {
  20803. var pointer = this._getPointer(event.gesture.center);
  20804. this.pointerPosition = pointer;
  20805. this._handleTap(pointer);
  20806. };
  20807. /**
  20808. * handle doubletap event
  20809. * @private
  20810. */
  20811. Network.prototype._onDoubleTap = function (event) {
  20812. var pointer = this._getPointer(event.gesture.center);
  20813. this._handleDoubleTap(pointer);
  20814. };
  20815. /**
  20816. * handle long tap event: multi select nodes
  20817. * @private
  20818. */
  20819. Network.prototype._onHold = function (event) {
  20820. var pointer = this._getPointer(event.gesture.center);
  20821. this.pointerPosition = pointer;
  20822. this._handleOnHold(pointer);
  20823. };
  20824. /**
  20825. * handle the release of the screen
  20826. *
  20827. * @private
  20828. */
  20829. Network.prototype._onRelease = function (event) {
  20830. var pointer = this._getPointer(event.gesture.center);
  20831. this._handleOnRelease(pointer);
  20832. };
  20833. /**
  20834. * Handle pinch event
  20835. * @param event
  20836. * @private
  20837. */
  20838. Network.prototype._onPinch = function (event) {
  20839. var pointer = this._getPointer(event.gesture.center);
  20840. this.drag.pinched = true;
  20841. if (!('scale' in this.pinch)) {
  20842. this.pinch.scale = 1;
  20843. }
  20844. // TODO: enabled moving while pinching?
  20845. var scale = this.pinch.scale * event.gesture.scale;
  20846. this._zoom(scale, pointer)
  20847. };
  20848. /**
  20849. * Zoom the network in or out
  20850. * @param {Number} scale a number around 1, and between 0.01 and 10
  20851. * @param {{x: Number, y: Number}} pointer Position on screen
  20852. * @return {Number} appliedScale scale is limited within the boundaries
  20853. * @private
  20854. */
  20855. Network.prototype._zoom = function(scale, pointer) {
  20856. if (this.constants.zoomable == true) {
  20857. var scaleOld = this._getScale();
  20858. if (scale < 0.00001) {
  20859. scale = 0.00001;
  20860. }
  20861. if (scale > 10) {
  20862. scale = 10;
  20863. }
  20864. var preScaleDragPointer = null;
  20865. if (this.drag !== undefined) {
  20866. if (this.drag.dragging == true) {
  20867. preScaleDragPointer = this.DOMtoCanvas(this.drag.pointer);
  20868. }
  20869. }
  20870. // + this.frame.canvas.clientHeight / 2
  20871. var translation = this._getTranslation();
  20872. var scaleFrac = scale / scaleOld;
  20873. var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
  20874. var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
  20875. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  20876. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  20877. this._setScale(scale);
  20878. this._setTranslation(tx, ty);
  20879. this.updateClustersDefault();
  20880. if (preScaleDragPointer != null) {
  20881. var postScaleDragPointer = this.canvasToDOM(preScaleDragPointer);
  20882. this.drag.pointer.x = postScaleDragPointer.x;
  20883. this.drag.pointer.y = postScaleDragPointer.y;
  20884. }
  20885. this._redraw();
  20886. if (scaleOld < scale) {
  20887. this.emit("zoom", {direction:"+"});
  20888. }
  20889. else {
  20890. this.emit("zoom", {direction:"-"});
  20891. }
  20892. return scale;
  20893. }
  20894. };
  20895. /**
  20896. * Event handler for mouse wheel event, used to zoom the timeline
  20897. * See http://adomas.org/javascript-mouse-wheel/
  20898. * https://github.com/EightMedia/hammer.js/issues/256
  20899. * @param {MouseEvent} event
  20900. * @private
  20901. */
  20902. Network.prototype._onMouseWheel = function(event) {
  20903. // retrieve delta
  20904. var delta = 0;
  20905. if (event.wheelDelta) { /* IE/Opera. */
  20906. delta = event.wheelDelta/120;
  20907. } else if (event.detail) { /* Mozilla case. */
  20908. // In Mozilla, sign of delta is different than in IE.
  20909. // Also, delta is multiple of 3.
  20910. delta = -event.detail/3;
  20911. }
  20912. // If delta is nonzero, handle it.
  20913. // Basically, delta is now positive if wheel was scrolled up,
  20914. // and negative, if wheel was scrolled down.
  20915. if (delta) {
  20916. // calculate the new scale
  20917. var scale = this._getScale();
  20918. var zoom = delta / 10;
  20919. if (delta < 0) {
  20920. zoom = zoom / (1 - zoom);
  20921. }
  20922. scale *= (1 + zoom);
  20923. // calculate the pointer location
  20924. var gesture = hammerUtil.fakeGesture(this, event);
  20925. var pointer = this._getPointer(gesture.center);
  20926. // apply the new scale
  20927. this._zoom(scale, pointer);
  20928. }
  20929. // Prevent default actions caused by mouse wheel.
  20930. event.preventDefault();
  20931. };
  20932. /**
  20933. * Mouse move handler for checking whether the title moves over a node with a title.
  20934. * @param {Event} event
  20935. * @private
  20936. */
  20937. Network.prototype._onMouseMoveTitle = function (event) {
  20938. var gesture = hammerUtil.fakeGesture(this, event);
  20939. var pointer = this._getPointer(gesture.center);
  20940. // check if the previously selected node is still selected
  20941. if (this.popupObj) {
  20942. this._checkHidePopup(pointer);
  20943. }
  20944. // if we bind the keyboard to the div, we have to highlight it to use it. This highlights it on mouse over
  20945. if (this.constants.keyboard.bindToWindow == false && this.constants.keyboard.enabled == true) {
  20946. this.frame.focus();
  20947. }
  20948. // start a timeout that will check if the mouse is positioned above
  20949. // an element
  20950. var me = this;
  20951. var checkShow = function() {
  20952. me._checkShowPopup(pointer);
  20953. };
  20954. if (this.popupTimer) {
  20955. clearInterval(this.popupTimer); // stop any running calculationTimer
  20956. }
  20957. if (!this.drag.dragging) {
  20958. this.popupTimer = setTimeout(checkShow, this.constants.tooltip.delay);
  20959. }
  20960. /**
  20961. * Adding hover highlights
  20962. */
  20963. if (this.constants.hover == true) {
  20964. // removing all hover highlights
  20965. for (var edgeId in this.hoverObj.edges) {
  20966. if (this.hoverObj.edges.hasOwnProperty(edgeId)) {
  20967. this.hoverObj.edges[edgeId].hover = false;
  20968. delete this.hoverObj.edges[edgeId];
  20969. }
  20970. }
  20971. // adding hover highlights
  20972. var obj = this._getNodeAt(pointer);
  20973. if (obj == null) {
  20974. obj = this._getEdgeAt(pointer);
  20975. }
  20976. if (obj != null) {
  20977. this._hoverObject(obj);
  20978. }
  20979. // removing all node hover highlights except for the selected one.
  20980. for (var nodeId in this.hoverObj.nodes) {
  20981. if (this.hoverObj.nodes.hasOwnProperty(nodeId)) {
  20982. if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj == null) {
  20983. this._blurObject(this.hoverObj.nodes[nodeId]);
  20984. delete this.hoverObj.nodes[nodeId];
  20985. }
  20986. }
  20987. }
  20988. this.redraw();
  20989. }
  20990. };
  20991. /**
  20992. * Check if there is an element on the given position in the network
  20993. * (a node or edge). If so, and if this element has a title,
  20994. * show a popup window with its title.
  20995. *
  20996. * @param {{x:Number, y:Number}} pointer
  20997. * @private
  20998. */
  20999. Network.prototype._checkShowPopup = function (pointer) {
  21000. var obj = {
  21001. left: this._XconvertDOMtoCanvas(pointer.x),
  21002. top: this._YconvertDOMtoCanvas(pointer.y),
  21003. right: this._XconvertDOMtoCanvas(pointer.x),
  21004. bottom: this._YconvertDOMtoCanvas(pointer.y)
  21005. };
  21006. var id;
  21007. var lastPopupNode = this.popupObj;
  21008. var nodeUnderCursor = false;
  21009. if (this.popupObj == undefined) {
  21010. // search the nodes for overlap, select the top one in case of multiple nodes
  21011. var nodes = this.nodes;
  21012. var overlappingNodes = [];
  21013. for (id in nodes) {
  21014. if (nodes.hasOwnProperty(id)) {
  21015. var node = nodes[id];
  21016. if (node.isOverlappingWith(obj)) {
  21017. if (node.getTitle() !== undefined) {
  21018. overlappingNodes.push(id);
  21019. }
  21020. }
  21021. }
  21022. }
  21023. if (overlappingNodes.length > 0) {
  21024. // if there are overlapping nodes, select the last one, this is the
  21025. // one which is drawn on top of the others
  21026. this.popupObj = this.nodes[overlappingNodes[overlappingNodes.length - 1]];
  21027. // if you hover over a node, the title of the edge is not supposed to be shown.
  21028. nodeUnderCursor = true;
  21029. }
  21030. }
  21031. if (this.popupObj === undefined && nodeUnderCursor == false) {
  21032. // search the edges for overlap
  21033. var edges = this.edges;
  21034. var overlappingEdges = [];
  21035. for (id in edges) {
  21036. if (edges.hasOwnProperty(id)) {
  21037. var edge = edges[id];
  21038. if (edge.connected && (edge.getTitle() !== undefined) &&
  21039. edge.isOverlappingWith(obj)) {
  21040. overlappingEdges.push(id);
  21041. }
  21042. }
  21043. }
  21044. if (overlappingEdges.length > 0) {
  21045. this.popupObj = this.edges[overlappingEdges[overlappingEdges.length - 1]];
  21046. }
  21047. }
  21048. if (this.popupObj) {
  21049. // show popup message window
  21050. if (this.popupObj != lastPopupNode) {
  21051. var me = this;
  21052. if (!me.popup) {
  21053. me.popup = new Popup(me.frame, me.constants.tooltip);
  21054. }
  21055. // adjust a small offset such that the mouse cursor is located in the
  21056. // bottom left location of the popup, and you can easily move over the
  21057. // popup area
  21058. me.popup.setPosition(pointer.x - 3, pointer.y - 3);
  21059. me.popup.setText(me.popupObj.getTitle());
  21060. me.popup.show();
  21061. }
  21062. }
  21063. else {
  21064. if (this.popup) {
  21065. this.popup.hide();
  21066. }
  21067. }
  21068. };
  21069. /**
  21070. * Check if the popup must be hided, which is the case when the mouse is no
  21071. * longer hovering on the object
  21072. * @param {{x:Number, y:Number}} pointer
  21073. * @private
  21074. */
  21075. Network.prototype._checkHidePopup = function (pointer) {
  21076. if (!this.popupObj || !this._getNodeAt(pointer) ) {
  21077. this.popupObj = undefined;
  21078. if (this.popup) {
  21079. this.popup.hide();
  21080. }
  21081. }
  21082. };
  21083. /**
  21084. * Set a new size for the network
  21085. * @param {string} width Width in pixels or percentage (for example '800px'
  21086. * or '50%')
  21087. * @param {string} height Height in pixels or percentage (for example '400px'
  21088. * or '30%')
  21089. */
  21090. Network.prototype.setSize = function(width, height) {
  21091. var emitEvent = false;
  21092. var oldWidth = this.frame.canvas.width;
  21093. var oldHeight = this.frame.canvas.height;
  21094. if (width != this.constants.width || height != this.constants.height || this.frame.style.width != width || this.frame.style.height != height) {
  21095. this.frame.style.width = width;
  21096. this.frame.style.height = height;
  21097. this.frame.canvas.style.width = '100%';
  21098. this.frame.canvas.style.height = '100%';
  21099. this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio;
  21100. this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio;
  21101. this.constants.width = width;
  21102. this.constants.height = height;
  21103. emitEvent = true;
  21104. }
  21105. else {
  21106. // this would adapt the width of the canvas to the width from 100% if and only if
  21107. // there is a change.
  21108. if (this.frame.canvas.width != this.frame.canvas.clientWidth * this.pixelRatio) {
  21109. this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio;
  21110. emitEvent = true;
  21111. }
  21112. if (this.frame.canvas.height != this.frame.canvas.clientHeight * this.pixelRatio) {
  21113. this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio;
  21114. emitEvent = true;
  21115. }
  21116. }
  21117. if (emitEvent == true) {
  21118. 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});
  21119. }
  21120. };
  21121. /**
  21122. * Set a data set with nodes for the network
  21123. * @param {Array | DataSet | DataView} nodes The data containing the nodes.
  21124. * @private
  21125. */
  21126. Network.prototype._setNodes = function(nodes) {
  21127. var oldNodesData = this.nodesData;
  21128. if (nodes instanceof DataSet || nodes instanceof DataView) {
  21129. this.nodesData = nodes;
  21130. }
  21131. else if (Array.isArray(nodes)) {
  21132. this.nodesData = new DataSet();
  21133. this.nodesData.add(nodes);
  21134. }
  21135. else if (!nodes) {
  21136. this.nodesData = new DataSet();
  21137. }
  21138. else {
  21139. throw new TypeError('Array or DataSet expected');
  21140. }
  21141. if (oldNodesData) {
  21142. // unsubscribe from old dataset
  21143. util.forEach(this.nodesListeners, function (callback, event) {
  21144. oldNodesData.off(event, callback);
  21145. });
  21146. }
  21147. // remove drawn nodes
  21148. this.nodes = {};
  21149. if (this.nodesData) {
  21150. // subscribe to new dataset
  21151. var me = this;
  21152. util.forEach(this.nodesListeners, function (callback, event) {
  21153. me.nodesData.on(event, callback);
  21154. });
  21155. // draw all new nodes
  21156. var ids = this.nodesData.getIds();
  21157. this._addNodes(ids);
  21158. }
  21159. this._updateSelection();
  21160. };
  21161. /**
  21162. * Add nodes
  21163. * @param {Number[] | String[]} ids
  21164. * @private
  21165. */
  21166. Network.prototype._addNodes = function(ids) {
  21167. var id;
  21168. for (var i = 0, len = ids.length; i < len; i++) {
  21169. id = ids[i];
  21170. var data = this.nodesData.get(id);
  21171. var node = new Node(data, this.images, this.groups, this.constants);
  21172. this.nodes[id] = node; // note: this may replace an existing node
  21173. if ((node.xFixed == false || node.yFixed == false) && (node.x === null || node.y === null)) {
  21174. var radius = 10 * 0.1*ids.length + 10;
  21175. var angle = 2 * Math.PI * Math.random();
  21176. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  21177. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  21178. }
  21179. this.moving = true;
  21180. }
  21181. this._updateNodeIndexList();
  21182. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  21183. this._resetLevels();
  21184. this._setupHierarchicalLayout();
  21185. }
  21186. this._updateCalculationNodes();
  21187. this._reconnectEdges();
  21188. this._updateValueRange(this.nodes);
  21189. this.updateLabels();
  21190. };
  21191. /**
  21192. * Update existing nodes, or create them when not yet existing
  21193. * @param {Number[] | String[]} ids
  21194. * @private
  21195. */
  21196. Network.prototype._updateNodes = function(ids,changedData) {
  21197. var nodes = this.nodes;
  21198. for (var i = 0, len = ids.length; i < len; i++) {
  21199. var id = ids[i];
  21200. var node = nodes[id];
  21201. var data = changedData[i];
  21202. if (node) {
  21203. // update node
  21204. node.setProperties(data, this.constants);
  21205. }
  21206. else {
  21207. // create node
  21208. node = new Node(properties, this.images, this.groups, this.constants);
  21209. nodes[id] = node;
  21210. }
  21211. }
  21212. this.moving = true;
  21213. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  21214. this._resetLevels();
  21215. this._setupHierarchicalLayout();
  21216. }
  21217. this._updateNodeIndexList();
  21218. this._updateValueRange(nodes);
  21219. this._markAllEdgesAsDirty();
  21220. };
  21221. Network.prototype._markAllEdgesAsDirty = function() {
  21222. for (var edgeId in this.edges) {
  21223. this.edges[edgeId].colorDirty = true;
  21224. }
  21225. }
  21226. /**
  21227. * Remove existing nodes. If nodes do not exist, the method will just ignore it.
  21228. * @param {Number[] | String[]} ids
  21229. * @private
  21230. */
  21231. Network.prototype._removeNodes = function(ids) {
  21232. var nodes = this.nodes;
  21233. for (var i = 0, len = ids.length; i < len; i++) {
  21234. var id = ids[i];
  21235. delete nodes[id];
  21236. }
  21237. this._updateNodeIndexList();
  21238. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  21239. this._resetLevels();
  21240. this._setupHierarchicalLayout();
  21241. }
  21242. this._updateCalculationNodes();
  21243. this._reconnectEdges();
  21244. this._updateSelection();
  21245. this._updateValueRange(nodes);
  21246. };
  21247. /**
  21248. * Load edges by reading the data table
  21249. * @param {Array | DataSet | DataView} edges The data containing the edges.
  21250. * @private
  21251. * @private
  21252. */
  21253. Network.prototype._setEdges = function(edges) {
  21254. var oldEdgesData = this.edgesData;
  21255. if (edges instanceof DataSet || edges instanceof DataView) {
  21256. this.edgesData = edges;
  21257. }
  21258. else if (Array.isArray(edges)) {
  21259. this.edgesData = new DataSet();
  21260. this.edgesData.add(edges);
  21261. }
  21262. else if (!edges) {
  21263. this.edgesData = new DataSet();
  21264. }
  21265. else {
  21266. throw new TypeError('Array or DataSet expected');
  21267. }
  21268. if (oldEdgesData) {
  21269. // unsubscribe from old dataset
  21270. util.forEach(this.edgesListeners, function (callback, event) {
  21271. oldEdgesData.off(event, callback);
  21272. });
  21273. }
  21274. // remove drawn edges
  21275. this.edges = {};
  21276. if (this.edgesData) {
  21277. // subscribe to new dataset
  21278. var me = this;
  21279. util.forEach(this.edgesListeners, function (callback, event) {
  21280. me.edgesData.on(event, callback);
  21281. });
  21282. // draw all new nodes
  21283. var ids = this.edgesData.getIds();
  21284. this._addEdges(ids);
  21285. }
  21286. this._reconnectEdges();
  21287. };
  21288. /**
  21289. * Add edges
  21290. * @param {Number[] | String[]} ids
  21291. * @private
  21292. */
  21293. Network.prototype._addEdges = function (ids) {
  21294. var edges = this.edges,
  21295. edgesData = this.edgesData;
  21296. for (var i = 0, len = ids.length; i < len; i++) {
  21297. var id = ids[i];
  21298. var oldEdge = edges[id];
  21299. if (oldEdge) {
  21300. oldEdge.disconnect();
  21301. }
  21302. var data = edgesData.get(id, {"showInternalIds" : true});
  21303. edges[id] = new Edge(data, this, this.constants);
  21304. }
  21305. this.moving = true;
  21306. this._updateValueRange(edges);
  21307. this._createBezierNodes();
  21308. this._updateCalculationNodes();
  21309. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  21310. this._resetLevels();
  21311. this._setupHierarchicalLayout();
  21312. }
  21313. };
  21314. /**
  21315. * Update existing edges, or create them when not yet existing
  21316. * @param {Number[] | String[]} ids
  21317. * @private
  21318. */
  21319. Network.prototype._updateEdges = function (ids) {
  21320. var edges = this.edges,
  21321. edgesData = this.edgesData;
  21322. for (var i = 0, len = ids.length; i < len; i++) {
  21323. var id = ids[i];
  21324. var data = edgesData.get(id);
  21325. var edge = edges[id];
  21326. if (edge) {
  21327. // update edge
  21328. edge.disconnect();
  21329. edge.setProperties(data, this.constants);
  21330. edge.connect();
  21331. }
  21332. else {
  21333. // create edge
  21334. edge = new Edge(data, this, this.constants);
  21335. this.edges[id] = edge;
  21336. }
  21337. }
  21338. this._createBezierNodes();
  21339. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  21340. this._resetLevels();
  21341. this._setupHierarchicalLayout();
  21342. }
  21343. this.moving = true;
  21344. this._updateValueRange(edges);
  21345. };
  21346. /**
  21347. * Remove existing edges. Non existing ids will be ignored
  21348. * @param {Number[] | String[]} ids
  21349. * @private
  21350. */
  21351. Network.prototype._removeEdges = function (ids) {
  21352. var edges = this.edges;
  21353. for (var i = 0, len = ids.length; i < len; i++) {
  21354. var id = ids[i];
  21355. var edge = edges[id];
  21356. if (edge) {
  21357. if (edge.via != null) {
  21358. delete this.sectors['support']['nodes'][edge.via.id];
  21359. }
  21360. edge.disconnect();
  21361. delete edges[id];
  21362. }
  21363. }
  21364. this.moving = true;
  21365. this._updateValueRange(edges);
  21366. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  21367. this._resetLevels();
  21368. this._setupHierarchicalLayout();
  21369. }
  21370. this._updateCalculationNodes();
  21371. };
  21372. /**
  21373. * Reconnect all edges
  21374. * @private
  21375. */
  21376. Network.prototype._reconnectEdges = function() {
  21377. var id,
  21378. nodes = this.nodes,
  21379. edges = this.edges;
  21380. for (id in nodes) {
  21381. if (nodes.hasOwnProperty(id)) {
  21382. nodes[id].edges = [];
  21383. nodes[id].dynamicEdges = [];
  21384. }
  21385. }
  21386. for (id in edges) {
  21387. if (edges.hasOwnProperty(id)) {
  21388. var edge = edges[id];
  21389. edge.from = null;
  21390. edge.to = null;
  21391. edge.connect();
  21392. }
  21393. }
  21394. };
  21395. /**
  21396. * Update the values of all object in the given array according to the current
  21397. * value range of the objects in the array.
  21398. * @param {Object} obj An object containing a set of Edges or Nodes
  21399. * The objects must have a method getValue() and
  21400. * setValueRange(min, max).
  21401. * @private
  21402. */
  21403. Network.prototype._updateValueRange = function(obj) {
  21404. var id;
  21405. // determine the range of the objects
  21406. var valueMin = undefined;
  21407. var valueMax = undefined;
  21408. var valueTotal = 0;
  21409. for (id in obj) {
  21410. if (obj.hasOwnProperty(id)) {
  21411. var value = obj[id].getValue();
  21412. if (value !== undefined) {
  21413. valueMin = (valueMin === undefined) ? value : Math.min(value, valueMin);
  21414. valueMax = (valueMax === undefined) ? value : Math.max(value, valueMax);
  21415. valueTotal += value;
  21416. }
  21417. }
  21418. }
  21419. // adjust the range of all objects
  21420. if (valueMin !== undefined && valueMax !== undefined) {
  21421. for (id in obj) {
  21422. if (obj.hasOwnProperty(id)) {
  21423. obj[id].setValueRange(valueMin, valueMax, valueTotal);
  21424. }
  21425. }
  21426. }
  21427. };
  21428. /**
  21429. * Redraw the network with the current data
  21430. * chart will be resized too.
  21431. */
  21432. Network.prototype.redraw = function() {
  21433. this.setSize(this.constants.width, this.constants.height);
  21434. this._redraw();
  21435. };
  21436. /**
  21437. * Redraw the network with the current data
  21438. * @param hidden | used to get the first estimate of the node sizes. only the nodes are drawn after which they are quickly drawn over.
  21439. * @private
  21440. */
  21441. Network.prototype._redraw = function(hidden) {
  21442. var ctx = this.frame.canvas.getContext('2d');
  21443. ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
  21444. // clear the canvas
  21445. var w = this.frame.canvas.width * this.pixelRatio;
  21446. var h = this.frame.canvas.height * this.pixelRatio;
  21447. ctx.clearRect(0, 0, w, h);
  21448. // set scaling and translation
  21449. ctx.save();
  21450. ctx.translate(this.translation.x, this.translation.y);
  21451. ctx.scale(this.scale, this.scale);
  21452. this.canvasTopLeft = {
  21453. "x": this._XconvertDOMtoCanvas(0),
  21454. "y": this._YconvertDOMtoCanvas(0)
  21455. };
  21456. this.canvasBottomRight = {
  21457. "x": this._XconvertDOMtoCanvas(this.frame.canvas.clientWidth * this.pixelRatio),
  21458. "y": this._YconvertDOMtoCanvas(this.frame.canvas.clientHeight * this.pixelRatio)
  21459. };
  21460. if (!(hidden == true)) {
  21461. this._doInAllSectors("_drawAllSectorNodes", ctx);
  21462. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideEdgesOnDrag == false) {
  21463. this._doInAllSectors("_drawEdges", ctx);
  21464. }
  21465. }
  21466. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideNodesOnDrag == false) {
  21467. this._doInAllSectors("_drawNodes",ctx,false);
  21468. }
  21469. if (!(hidden == true)) {
  21470. if (this.controlNodesActive == true) {
  21471. this._doInAllSectors("_drawControlNodes", ctx);
  21472. }
  21473. }
  21474. // this._doInSupportSector("_drawNodes",ctx,true);
  21475. // this._drawTree(ctx,"#F00F0F");
  21476. // restore original scaling and translation
  21477. ctx.restore();
  21478. if (hidden == true) {
  21479. ctx.clearRect(0, 0, w, h);
  21480. }
  21481. };
  21482. /**
  21483. * Set the translation of the network
  21484. * @param {Number} offsetX Horizontal offset
  21485. * @param {Number} offsetY Vertical offset
  21486. * @private
  21487. */
  21488. Network.prototype._setTranslation = function(offsetX, offsetY) {
  21489. if (this.translation === undefined) {
  21490. this.translation = {
  21491. x: 0,
  21492. y: 0
  21493. };
  21494. }
  21495. if (offsetX !== undefined) {
  21496. this.translation.x = offsetX;
  21497. }
  21498. if (offsetY !== undefined) {
  21499. this.translation.y = offsetY;
  21500. }
  21501. this.emit('viewChanged');
  21502. };
  21503. /**
  21504. * Get the translation of the network
  21505. * @return {Object} translation An object with parameters x and y, both a number
  21506. * @private
  21507. */
  21508. Network.prototype._getTranslation = function() {
  21509. return {
  21510. x: this.translation.x,
  21511. y: this.translation.y
  21512. };
  21513. };
  21514. /**
  21515. * Scale the network
  21516. * @param {Number} scale Scaling factor 1.0 is unscaled
  21517. * @private
  21518. */
  21519. Network.prototype._setScale = function(scale) {
  21520. this.scale = scale;
  21521. };
  21522. /**
  21523. * Get the current scale of the network
  21524. * @return {Number} scale Scaling factor 1.0 is unscaled
  21525. * @private
  21526. */
  21527. Network.prototype._getScale = function() {
  21528. return this.scale;
  21529. };
  21530. /**
  21531. * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to
  21532. * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  21533. * @param {number} x
  21534. * @returns {number}
  21535. * @private
  21536. */
  21537. Network.prototype._XconvertDOMtoCanvas = function(x) {
  21538. return (x - this.translation.x) / this.scale;
  21539. };
  21540. /**
  21541. * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  21542. * the X coordinate in DOM-space (coordinate point in browser relative to the container div)
  21543. * @param {number} x
  21544. * @returns {number}
  21545. * @private
  21546. */
  21547. Network.prototype._XconvertCanvasToDOM = function(x) {
  21548. return x * this.scale + this.translation.x;
  21549. };
  21550. /**
  21551. * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to
  21552. * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  21553. * @param {number} y
  21554. * @returns {number}
  21555. * @private
  21556. */
  21557. Network.prototype._YconvertDOMtoCanvas = function(y) {
  21558. return (y - this.translation.y) / this.scale;
  21559. };
  21560. /**
  21561. * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  21562. * the Y coordinate in DOM-space (coordinate point in browser relative to the container div)
  21563. * @param {number} y
  21564. * @returns {number}
  21565. * @private
  21566. */
  21567. Network.prototype._YconvertCanvasToDOM = function(y) {
  21568. return y * this.scale + this.translation.y ;
  21569. };
  21570. /**
  21571. *
  21572. * @param {object} pos = {x: number, y: number}
  21573. * @returns {{x: number, y: number}}
  21574. * @constructor
  21575. */
  21576. Network.prototype.canvasToDOM = function (pos) {
  21577. return {x: this._XconvertCanvasToDOM(pos.x), y: this._YconvertCanvasToDOM(pos.y)};
  21578. };
  21579. /**
  21580. *
  21581. * @param {object} pos = {x: number, y: number}
  21582. * @returns {{x: number, y: number}}
  21583. * @constructor
  21584. */
  21585. Network.prototype.DOMtoCanvas = function (pos) {
  21586. return {x: this._XconvertDOMtoCanvas(pos.x), y: this._YconvertDOMtoCanvas(pos.y)};
  21587. };
  21588. /**
  21589. * Redraw all nodes
  21590. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  21591. * @param {CanvasRenderingContext2D} ctx
  21592. * @param {Boolean} [alwaysShow]
  21593. * @private
  21594. */
  21595. Network.prototype._drawNodes = function(ctx,alwaysShow) {
  21596. if (alwaysShow === undefined) {
  21597. alwaysShow = false;
  21598. }
  21599. // first draw the unselected nodes
  21600. var nodes = this.nodes;
  21601. var selected = [];
  21602. for (var id in nodes) {
  21603. if (nodes.hasOwnProperty(id)) {
  21604. nodes[id].setScaleAndPos(this.scale,this.canvasTopLeft,this.canvasBottomRight);
  21605. if (nodes[id].isSelected()) {
  21606. selected.push(id);
  21607. }
  21608. else {
  21609. if (nodes[id].inArea() || alwaysShow) {
  21610. nodes[id].draw(ctx);
  21611. }
  21612. }
  21613. }
  21614. }
  21615. // draw the selected nodes on top
  21616. for (var s = 0, sMax = selected.length; s < sMax; s++) {
  21617. if (nodes[selected[s]].inArea() || alwaysShow) {
  21618. nodes[selected[s]].draw(ctx);
  21619. }
  21620. }
  21621. };
  21622. /**
  21623. * Redraw all edges
  21624. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  21625. * @param {CanvasRenderingContext2D} ctx
  21626. * @private
  21627. */
  21628. Network.prototype._drawEdges = function(ctx) {
  21629. var edges = this.edges;
  21630. for (var id in edges) {
  21631. if (edges.hasOwnProperty(id)) {
  21632. var edge = edges[id];
  21633. edge.setScale(this.scale);
  21634. if (edge.connected) {
  21635. edges[id].draw(ctx);
  21636. }
  21637. }
  21638. }
  21639. };
  21640. /**
  21641. * Redraw all edges
  21642. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  21643. * @param {CanvasRenderingContext2D} ctx
  21644. * @private
  21645. */
  21646. Network.prototype._drawControlNodes = function(ctx) {
  21647. var edges = this.edges;
  21648. for (var id in edges) {
  21649. if (edges.hasOwnProperty(id)) {
  21650. edges[id]._drawControlNodes(ctx);
  21651. }
  21652. }
  21653. };
  21654. /**
  21655. * Find a stable position for all nodes
  21656. * @private
  21657. */
  21658. Network.prototype._stabilize = function() {
  21659. if (this.constants.freezeForStabilization == true) {
  21660. this._freezeDefinedNodes();
  21661. }
  21662. // find stable position
  21663. var count = 0;
  21664. while (this.moving && count < this.constants.stabilizationIterations) {
  21665. this._physicsTick();
  21666. if (count % 100 == 0) {
  21667. console.log("stabilizationIterations",count);
  21668. }
  21669. count++;
  21670. }
  21671. if (this.constants.zoomExtentOnStabilize == true) {
  21672. this.zoomExtent({duration:0}, false, true);
  21673. }
  21674. if (this.constants.freezeForStabilization == true) {
  21675. this._restoreFrozenNodes();
  21676. }
  21677. this.emit("stabilizationIterationsDone");
  21678. };
  21679. /**
  21680. * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization
  21681. * because only the supportnodes for the smoothCurves have to settle.
  21682. *
  21683. * @private
  21684. */
  21685. Network.prototype._freezeDefinedNodes = function() {
  21686. var nodes = this.nodes;
  21687. for (var id in nodes) {
  21688. if (nodes.hasOwnProperty(id)) {
  21689. if (nodes[id].x != null && nodes[id].y != null) {
  21690. nodes[id].fixedData.x = nodes[id].xFixed;
  21691. nodes[id].fixedData.y = nodes[id].yFixed;
  21692. nodes[id].xFixed = true;
  21693. nodes[id].yFixed = true;
  21694. }
  21695. }
  21696. }
  21697. };
  21698. /**
  21699. * Unfreezes the nodes that have been frozen by _freezeDefinedNodes.
  21700. *
  21701. * @private
  21702. */
  21703. Network.prototype._restoreFrozenNodes = function() {
  21704. var nodes = this.nodes;
  21705. for (var id in nodes) {
  21706. if (nodes.hasOwnProperty(id)) {
  21707. if (nodes[id].fixedData.x != null) {
  21708. nodes[id].xFixed = nodes[id].fixedData.x;
  21709. nodes[id].yFixed = nodes[id].fixedData.y;
  21710. }
  21711. }
  21712. }
  21713. };
  21714. /**
  21715. * Check if any of the nodes is still moving
  21716. * @param {number} vmin the minimum velocity considered as 'moving'
  21717. * @return {boolean} true if moving, false if non of the nodes is moving
  21718. * @private
  21719. */
  21720. Network.prototype._isMoving = function(vmin) {
  21721. var nodes = this.nodes;
  21722. for (var id in nodes) {
  21723. if (nodes[id] !== undefined) {
  21724. if (nodes[id].isMoving(vmin) == true) {
  21725. return true;
  21726. }
  21727. }
  21728. }
  21729. return false;
  21730. };
  21731. /**
  21732. * /**
  21733. * Perform one discrete step for all nodes
  21734. *
  21735. * @private
  21736. */
  21737. Network.prototype._discreteStepNodes = function() {
  21738. var interval = this.physicsDiscreteStepsize;
  21739. var nodes = this.nodes;
  21740. var nodeId;
  21741. var nodesPresent = false;
  21742. if (this.constants.maxVelocity > 0) {
  21743. for (nodeId in nodes) {
  21744. if (nodes.hasOwnProperty(nodeId)) {
  21745. nodes[nodeId].discreteStepLimited(interval, this.constants.maxVelocity);
  21746. nodesPresent = true;
  21747. }
  21748. }
  21749. }
  21750. else {
  21751. for (nodeId in nodes) {
  21752. if (nodes.hasOwnProperty(nodeId)) {
  21753. nodes[nodeId].discreteStep(interval);
  21754. nodesPresent = true;
  21755. }
  21756. }
  21757. }
  21758. if (nodesPresent == true) {
  21759. var vminCorrected = this.constants.minVelocity / Math.max(this.scale,0.05);
  21760. if (vminCorrected > 0.5*this.constants.maxVelocity) {
  21761. return true;
  21762. }
  21763. else {
  21764. return this._isMoving(vminCorrected);
  21765. }
  21766. }
  21767. return false;
  21768. };
  21769. Network.prototype._revertPhysicsState = function() {
  21770. var nodes = this.nodes;
  21771. for (var nodeId in nodes) {
  21772. if (nodes.hasOwnProperty(nodeId)) {
  21773. nodes[nodeId].revertPosition();
  21774. }
  21775. }
  21776. }
  21777. Network.prototype._revertPhysicsTick = function() {
  21778. this._doInAllActiveSectors("_revertPhysicsState");
  21779. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  21780. this._doInSupportSector("_revertPhysicsState");
  21781. }
  21782. }
  21783. /**
  21784. * A single simulation step (or "tick") in the physics simulation
  21785. *
  21786. * @private
  21787. */
  21788. Network.prototype._physicsTick = function() {
  21789. if (!this.freezeSimulation) {
  21790. if (this.moving == true) {
  21791. var mainMovingStatus = false;
  21792. var supportMovingStatus = false;
  21793. this._doInAllActiveSectors("_initializeForceCalculation");
  21794. var mainMoving = this._doInAllActiveSectors("_discreteStepNodes");
  21795. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  21796. supportMovingStatus = this._doInSupportSector("_discreteStepNodes");
  21797. }
  21798. // gather movement data from all sectors, if one moves, we are NOT stabilzied
  21799. for (var i = 0; i < mainMoving.length; i++) {
  21800. mainMovingStatus = mainMoving[i] || mainMovingStatus;
  21801. }
  21802. // determine if the network has stabilzied
  21803. this.moving = mainMovingStatus || supportMovingStatus;
  21804. if (this.moving == false) {
  21805. this._revertPhysicsTick();
  21806. }
  21807. else {
  21808. // this is here to ensure that there is no start event when the network is already stable.
  21809. if (this.startedStabilization == false) {
  21810. this.emit("startStabilization");
  21811. this.startedStabilization = true;
  21812. }
  21813. }
  21814. this.stabilizationIterations++;
  21815. }
  21816. }
  21817. };
  21818. /**
  21819. * This function runs one step of the animation. It calls an x amount of physics ticks and one render tick.
  21820. * It reschedules itself at the beginning of the function
  21821. *
  21822. * @private
  21823. */
  21824. Network.prototype._animationStep = function() {
  21825. // reset the timer so a new scheduled animation step can be set
  21826. this.timer = undefined;
  21827. // handle the keyboad movement
  21828. this._handleNavigation();
  21829. // check if the physics have settled
  21830. if (this.moving == true) {
  21831. var startTime = Date.now();
  21832. this._physicsTick();
  21833. var physicsTime = Date.now() - startTime;
  21834. // run double speed if it is a little graph
  21835. if ((this.renderTimestep - this.renderTime > 2 * physicsTime || this.runDoubleSpeed == true) && this.moving == true) {
  21836. this._physicsTick();
  21837. // this makes sure there is no jitter. The decision is taken once to run it at double speed.
  21838. if (this.renderTime != 0) {
  21839. this.runDoubleSpeed = true
  21840. }
  21841. }
  21842. }
  21843. var renderStartTime = Date.now();
  21844. this._redraw();
  21845. this.renderTime = Date.now() - renderStartTime;
  21846. // this schedules a new animation step
  21847. this.start();
  21848. };
  21849. if (typeof window !== 'undefined') {
  21850. window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
  21851. window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  21852. }
  21853. /**
  21854. * Schedule a animation step with the refreshrate interval.
  21855. */
  21856. Network.prototype.start = function() {
  21857. if (this.moving == true || this.xIncrement != 0 || this.yIncrement != 0 || this.zoomIncrement != 0 || this.animating == true) {
  21858. if (!this.timer) {
  21859. if (this.requiresTimeout == true) {
  21860. this.timer = window.setTimeout(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
  21861. }
  21862. else {
  21863. this.timer = window.requestAnimationFrame(this._animationStep.bind(this)); // wait this.renderTimeStep milliseconds and perform the animation step function
  21864. }
  21865. }
  21866. }
  21867. else {
  21868. this._redraw();
  21869. // 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())
  21870. if (this.stabilizationIterations > 1) {
  21871. // trigger the "stabilized" event.
  21872. // The event is triggered on the next tick, to prevent the case that
  21873. // it is fired while initializing the Network, in which case you would not
  21874. // be able to catch it
  21875. var me = this;
  21876. var params = {
  21877. iterations: me.stabilizationIterations
  21878. };
  21879. this.stabilizationIterations = 0;
  21880. this.startedStabilization = false;
  21881. setTimeout(function () {
  21882. me.emit("stabilized", params);
  21883. }, 0);
  21884. }
  21885. else {
  21886. this.stabilizationIterations = 0;
  21887. }
  21888. }
  21889. };
  21890. /**
  21891. * Move the network according to the keyboard presses.
  21892. *
  21893. * @private
  21894. */
  21895. Network.prototype._handleNavigation = function() {
  21896. if (this.xIncrement != 0 || this.yIncrement != 0) {
  21897. var translation = this._getTranslation();
  21898. this._setTranslation(translation.x+this.xIncrement, translation.y+this.yIncrement);
  21899. }
  21900. if (this.zoomIncrement != 0) {
  21901. var center = {
  21902. x: this.frame.canvas.clientWidth / 2,
  21903. y: this.frame.canvas.clientHeight / 2
  21904. };
  21905. this._zoom(this.scale*(1 + this.zoomIncrement), center);
  21906. }
  21907. };
  21908. /**
  21909. * Freeze the _animationStep
  21910. */
  21911. Network.prototype.setFreezeSimulation = function(freeze) {
  21912. if (freeze == true) {
  21913. this.freezeSimulation = true;
  21914. this.moving = false;
  21915. }
  21916. else {
  21917. this.freezeSimulation = false;
  21918. this.moving = true;
  21919. this.start();
  21920. }
  21921. };
  21922. /**
  21923. * This function cleans the support nodes if they are not needed and adds them when they are.
  21924. *
  21925. * @param {boolean} [disableStart]
  21926. * @private
  21927. */
  21928. Network.prototype._configureSmoothCurves = function(disableStart) {
  21929. if (disableStart === undefined) {
  21930. disableStart = true;
  21931. }
  21932. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  21933. this._createBezierNodes();
  21934. // cleanup unused support nodes
  21935. for (var nodeId in this.sectors['support']['nodes']) {
  21936. if (this.sectors['support']['nodes'].hasOwnProperty(nodeId)) {
  21937. if (this.edges[this.sectors['support']['nodes'][nodeId].parentEdgeId] === undefined) {
  21938. delete this.sectors['support']['nodes'][nodeId];
  21939. }
  21940. }
  21941. }
  21942. }
  21943. else {
  21944. // delete the support nodes
  21945. this.sectors['support']['nodes'] = {};
  21946. for (var edgeId in this.edges) {
  21947. if (this.edges.hasOwnProperty(edgeId)) {
  21948. this.edges[edgeId].via = null;
  21949. }
  21950. }
  21951. }
  21952. this._updateCalculationNodes();
  21953. if (!disableStart) {
  21954. this.moving = true;
  21955. this.start();
  21956. }
  21957. };
  21958. /**
  21959. * Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
  21960. * are used for the force calculation.
  21961. *
  21962. * @private
  21963. */
  21964. Network.prototype._createBezierNodes = function() {
  21965. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  21966. for (var edgeId in this.edges) {
  21967. if (this.edges.hasOwnProperty(edgeId)) {
  21968. var edge = this.edges[edgeId];
  21969. if (edge.via == null) {
  21970. var nodeId = "edgeId:".concat(edge.id);
  21971. this.sectors['support']['nodes'][nodeId] = new Node(
  21972. {id:nodeId,
  21973. mass:1,
  21974. shape:'circle',
  21975. image:"",
  21976. internalMultiplier:1
  21977. },{},{},this.constants);
  21978. edge.via = this.sectors['support']['nodes'][nodeId];
  21979. edge.via.parentEdgeId = edge.id;
  21980. edge.positionBezierNode();
  21981. }
  21982. }
  21983. }
  21984. }
  21985. };
  21986. /**
  21987. * load the functions that load the mixins into the prototype.
  21988. *
  21989. * @private
  21990. */
  21991. Network.prototype._initializeMixinLoaders = function () {
  21992. for (var mixin in MixinLoader) {
  21993. if (MixinLoader.hasOwnProperty(mixin)) {
  21994. Network.prototype[mixin] = MixinLoader[mixin];
  21995. }
  21996. }
  21997. };
  21998. /**
  21999. * Load the XY positions of the nodes into the dataset.
  22000. */
  22001. Network.prototype.storePosition = function() {
  22002. console.log("storePosition is depricated: use .storePositions() from now on.")
  22003. this.storePositions();
  22004. };
  22005. /**
  22006. * Load the XY positions of the nodes into the dataset.
  22007. */
  22008. Network.prototype.storePositions = function() {
  22009. var dataArray = [];
  22010. for (var nodeId in this.nodes) {
  22011. if (this.nodes.hasOwnProperty(nodeId)) {
  22012. var node = this.nodes[nodeId];
  22013. var allowedToMoveX = !this.nodes.xFixed;
  22014. var allowedToMoveY = !this.nodes.yFixed;
  22015. if (this.nodesData._data[nodeId].x != Math.round(node.x) || this.nodesData._data[nodeId].y != Math.round(node.y)) {
  22016. dataArray.push({id:nodeId,x:Math.round(node.x),y:Math.round(node.y),allowedToMoveX:allowedToMoveX,allowedToMoveY:allowedToMoveY});
  22017. }
  22018. }
  22019. }
  22020. this.nodesData.update(dataArray);
  22021. };
  22022. /**
  22023. * Return the positions of the nodes.
  22024. */
  22025. Network.prototype.getPositions = function(ids) {
  22026. var dataArray = {};
  22027. if (ids !== undefined) {
  22028. if (Array.isArray(ids) == true) {
  22029. for (var i = 0; i < ids.length; i++) {
  22030. if (this.nodes[ids[i]] !== undefined) {
  22031. var node = this.nodes[ids[i]];
  22032. dataArray[ids[i]] = {x: Math.round(node.x), y: Math.round(node.y)};
  22033. }
  22034. }
  22035. }
  22036. else {
  22037. if (this.nodes[ids] !== undefined) {
  22038. var node = this.nodes[ids];
  22039. dataArray[ids] = {x: Math.round(node.x), y: Math.round(node.y)};
  22040. }
  22041. }
  22042. }
  22043. else {
  22044. for (var nodeId in this.nodes) {
  22045. if (this.nodes.hasOwnProperty(nodeId)) {
  22046. var node = this.nodes[nodeId];
  22047. dataArray[nodeId] = {x: Math.round(node.x), y: Math.round(node.y)};
  22048. }
  22049. }
  22050. }
  22051. return dataArray;
  22052. };
  22053. /**
  22054. * Center a node in view.
  22055. *
  22056. * @param {Number} nodeId
  22057. * @param {Number} [options]
  22058. */
  22059. Network.prototype.focusOnNode = function (nodeId, options) {
  22060. if (this.nodes.hasOwnProperty(nodeId)) {
  22061. if (options === undefined) {
  22062. options = {};
  22063. }
  22064. var nodePosition = {x: this.nodes[nodeId].x, y: this.nodes[nodeId].y};
  22065. options.position = nodePosition;
  22066. options.lockedOnNode = nodeId;
  22067. this.moveTo(options)
  22068. }
  22069. else {
  22070. console.log("This nodeId cannot be found.");
  22071. }
  22072. };
  22073. /**
  22074. *
  22075. * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels
  22076. * | options.scale = Number // scale to move to
  22077. * | options.position = {x:Number, y:Number} // position to move to
  22078. * | options.animation = {duration:Number, easingFunction:String} || Boolean // position to move to
  22079. */
  22080. Network.prototype.moveTo = function (options) {
  22081. if (options === undefined) {
  22082. options = {};
  22083. return;
  22084. }
  22085. if (options.offset === undefined) {options.offset = {x: 0, y: 0}; }
  22086. if (options.offset.x === undefined) {options.offset.x = 0; }
  22087. if (options.offset.y === undefined) {options.offset.y = 0; }
  22088. if (options.scale === undefined) {options.scale = this._getScale(); }
  22089. if (options.position === undefined) {options.position = this._getTranslation();}
  22090. if (options.animation === undefined) {options.animation = {duration:0}; }
  22091. if (options.animation === false ) {options.animation = {duration:0}; }
  22092. if (options.animation === true ) {options.animation = {}; }
  22093. if (options.animation.duration === undefined) {options.animation.duration = 1000; } // default duration
  22094. if (options.animation.easingFunction === undefined) {options.animation.easingFunction = "easeInOutQuad"; } // default easing function
  22095. this.animateView(options);
  22096. };
  22097. /**
  22098. *
  22099. * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels
  22100. * | options.time = Number // animation time in milliseconds
  22101. * | options.scale = Number // scale to animate to
  22102. * | options.position = {x:Number, y:Number} // position to animate to
  22103. * | options.easingFunction = String // linear, easeInQuad, easeOutQuad, easeInOutQuad,
  22104. * // easeInCubic, easeOutCubic, easeInOutCubic,
  22105. * // easeInQuart, easeOutQuart, easeInOutQuart,
  22106. * // easeInQuint, easeOutQuint, easeInOutQuint
  22107. */
  22108. Network.prototype.animateView = function (options) {
  22109. if (options === undefined) {
  22110. options = {};
  22111. return;
  22112. }
  22113. // release if something focussed on the node
  22114. this.releaseNode();
  22115. if (options.locked == true) {
  22116. this.lockedOnNodeId = options.lockedOnNode;
  22117. this.lockedOnNodeOffset = options.offset;
  22118. }
  22119. // forcefully complete the old animation if it was still running
  22120. if (this.easingTime != 0) {
  22121. this._transitionRedraw(1); // by setting easingtime to 1, we finish the animation.
  22122. }
  22123. this.sourceScale = this._getScale();
  22124. this.sourceTranslation = this._getTranslation();
  22125. this.targetScale = options.scale;
  22126. // set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw
  22127. // but at least then we'll have the target transition
  22128. this._setScale(this.targetScale);
  22129. var viewCenter = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  22130. var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
  22131. x: viewCenter.x - options.position.x,
  22132. y: viewCenter.y - options.position.y
  22133. };
  22134. this.targetTranslation = {
  22135. x: this.sourceTranslation.x + distanceFromCenter.x * this.targetScale + options.offset.x,
  22136. y: this.sourceTranslation.y + distanceFromCenter.y * this.targetScale + options.offset.y
  22137. };
  22138. // if the time is set to 0, don't do an animation
  22139. if (options.animation.duration == 0) {
  22140. if (this.lockedOnNodeId != null) {
  22141. this._classicRedraw = this._redraw;
  22142. this._redraw = this._lockedRedraw;
  22143. }
  22144. else {
  22145. this._setScale(this.targetScale);
  22146. this._setTranslation(this.targetTranslation.x, this.targetTranslation.y);
  22147. this._redraw();
  22148. }
  22149. }
  22150. else {
  22151. this.animating = true;
  22152. this.animationSpeed = 1 / (this.renderRefreshRate * options.animation.duration * 0.001) || 1 / this.renderRefreshRate;
  22153. this.animationEasingFunction = options.animation.easingFunction;
  22154. this._classicRedraw = this._redraw;
  22155. this._redraw = this._transitionRedraw;
  22156. this._redraw();
  22157. this.start();
  22158. }
  22159. };
  22160. /**
  22161. * used to animate smoothly by hijacking the redraw function.
  22162. * @private
  22163. */
  22164. Network.prototype._lockedRedraw = function () {
  22165. var nodePosition = {x: this.nodes[this.lockedOnNodeId].x, y: this.nodes[this.lockedOnNodeId].y};
  22166. var viewCenter = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  22167. var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
  22168. x: viewCenter.x - nodePosition.x,
  22169. y: viewCenter.y - nodePosition.y
  22170. };
  22171. var sourceTranslation = this._getTranslation();
  22172. var targetTranslation = {
  22173. x: sourceTranslation.x + distanceFromCenter.x * this.scale + this.lockedOnNodeOffset.x,
  22174. y: sourceTranslation.y + distanceFromCenter.y * this.scale + this.lockedOnNodeOffset.y
  22175. };
  22176. this._setTranslation(targetTranslation.x,targetTranslation.y);
  22177. this._classicRedraw();
  22178. }
  22179. Network.prototype.releaseNode = function () {
  22180. if (this.lockedOnNodeId != null) {
  22181. this._redraw = this._classicRedraw;
  22182. this.lockedOnNodeId = null;
  22183. this.lockedOnNodeOffset = null;
  22184. }
  22185. }
  22186. /**
  22187. *
  22188. * @param easingTime
  22189. * @private
  22190. */
  22191. Network.prototype._transitionRedraw = function (easingTime) {
  22192. this.easingTime = easingTime || this.easingTime + this.animationSpeed;
  22193. this.easingTime += this.animationSpeed;
  22194. var progress = util.easingFunctions[this.animationEasingFunction](this.easingTime);
  22195. this._setScale(this.sourceScale + (this.targetScale - this.sourceScale) * progress);
  22196. this._setTranslation(
  22197. this.sourceTranslation.x + (this.targetTranslation.x - this.sourceTranslation.x) * progress,
  22198. this.sourceTranslation.y + (this.targetTranslation.y - this.sourceTranslation.y) * progress
  22199. );
  22200. this._classicRedraw();
  22201. // cleanup
  22202. if (this.easingTime >= 1.0) {
  22203. this.animating = false;
  22204. this.easingTime = 0;
  22205. if (this.lockedOnNodeId != null) {
  22206. this._redraw = this._lockedRedraw;
  22207. }
  22208. else {
  22209. this._redraw = this._classicRedraw;
  22210. }
  22211. this.emit("animationFinished");
  22212. }
  22213. };
  22214. Network.prototype._classicRedraw = function () {
  22215. // placeholder function to be overloaded by animations;
  22216. };
  22217. /**
  22218. * Returns true when the Network is active.
  22219. * @returns {boolean}
  22220. */
  22221. Network.prototype.isActive = function () {
  22222. return !this.activator || this.activator.active;
  22223. };
  22224. /**
  22225. * Sets the scale
  22226. * @returns {Number}
  22227. */
  22228. Network.prototype.setScale = function () {
  22229. return this._setScale();
  22230. };
  22231. /**
  22232. * Returns the scale
  22233. * @returns {Number}
  22234. */
  22235. Network.prototype.getScale = function () {
  22236. return this._getScale();
  22237. };
  22238. /**
  22239. * Returns the scale
  22240. * @returns {Number}
  22241. */
  22242. Network.prototype.getCenterCoordinates = function () {
  22243. return this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  22244. };
  22245. Network.prototype.getBoundingBox = function(nodeId) {
  22246. if (this.nodes[nodeId] !== undefined) {
  22247. return this.nodes[nodeId].boundingBox;
  22248. }
  22249. }
  22250. Network.prototype.getConnectedNodes = function(nodeId) {
  22251. var nodeList = [];
  22252. if (this.nodes[nodeId] !== undefined) {
  22253. var node = this.nodes[nodeId];
  22254. var nodeObj = {nodeId : true}; // used to quickly check if node already exists
  22255. for (var i = 0; i < node.edges.length; i++) {
  22256. var edge = node.edges[i];
  22257. if (edge.toId == nodeId) {
  22258. if (nodeObj[edge.fromId] === undefined) {
  22259. nodeList.push(edge.fromId);
  22260. nodeObj[edge.fromId] = true;
  22261. }
  22262. }
  22263. else if (edge.fromId == nodeId) {
  22264. if (nodeObj[edge.toId] === undefined) {
  22265. nodeList.push(edge.toId)
  22266. nodeObj[edge.toId] = true;
  22267. }
  22268. }
  22269. }
  22270. }
  22271. return nodeList;
  22272. }
  22273. module.exports = Network;
  22274. /***/ },
  22275. /* 52 */
  22276. /***/ function(module, exports, __webpack_require__) {
  22277. /**
  22278. * Parse a text source containing data in DOT language into a JSON object.
  22279. * The object contains two lists: one with nodes and one with edges.
  22280. *
  22281. * DOT language reference: http://www.graphviz.org/doc/info/lang.html
  22282. *
  22283. * @param {String} data Text containing a graph in DOT-notation
  22284. * @return {Object} graph An object containing two parameters:
  22285. * {Object[]} nodes
  22286. * {Object[]} edges
  22287. */
  22288. function parseDOT (data) {
  22289. dot = data;
  22290. return parseGraph();
  22291. }
  22292. // token types enumeration
  22293. var TOKENTYPE = {
  22294. NULL : 0,
  22295. DELIMITER : 1,
  22296. IDENTIFIER: 2,
  22297. UNKNOWN : 3
  22298. };
  22299. // map with all delimiters
  22300. var DELIMITERS = {
  22301. '{': true,
  22302. '}': true,
  22303. '[': true,
  22304. ']': true,
  22305. ';': true,
  22306. '=': true,
  22307. ',': true,
  22308. '->': true,
  22309. '--': true
  22310. };
  22311. var dot = ''; // current dot file
  22312. var index = 0; // current index in dot file
  22313. var c = ''; // current token character in expr
  22314. var token = ''; // current token
  22315. var tokenType = TOKENTYPE.NULL; // type of the token
  22316. /**
  22317. * Get the first character from the dot file.
  22318. * The character is stored into the char c. If the end of the dot file is
  22319. * reached, the function puts an empty string in c.
  22320. */
  22321. function first() {
  22322. index = 0;
  22323. c = dot.charAt(0);
  22324. }
  22325. /**
  22326. * Get the next character from the dot file.
  22327. * The character is stored into the char c. If the end of the dot file is
  22328. * reached, the function puts an empty string in c.
  22329. */
  22330. function next() {
  22331. index++;
  22332. c = dot.charAt(index);
  22333. }
  22334. /**
  22335. * Preview the next character from the dot file.
  22336. * @return {String} cNext
  22337. */
  22338. function nextPreview() {
  22339. return dot.charAt(index + 1);
  22340. }
  22341. /**
  22342. * Test whether given character is alphabetic or numeric
  22343. * @param {String} c
  22344. * @return {Boolean} isAlphaNumeric
  22345. */
  22346. var regexAlphaNumeric = /[a-zA-Z_0-9.:#]/;
  22347. function isAlphaNumeric(c) {
  22348. return regexAlphaNumeric.test(c);
  22349. }
  22350. /**
  22351. * Merge all properties of object b into object b
  22352. * @param {Object} a
  22353. * @param {Object} b
  22354. * @return {Object} a
  22355. */
  22356. function merge (a, b) {
  22357. if (!a) {
  22358. a = {};
  22359. }
  22360. if (b) {
  22361. for (var name in b) {
  22362. if (b.hasOwnProperty(name)) {
  22363. a[name] = b[name];
  22364. }
  22365. }
  22366. }
  22367. return a;
  22368. }
  22369. /**
  22370. * Set a value in an object, where the provided parameter name can be a
  22371. * path with nested parameters. For example:
  22372. *
  22373. * var obj = {a: 2};
  22374. * setValue(obj, 'b.c', 3); // obj = {a: 2, b: {c: 3}}
  22375. *
  22376. * @param {Object} obj
  22377. * @param {String} path A parameter name or dot-separated parameter path,
  22378. * like "color.highlight.border".
  22379. * @param {*} value
  22380. */
  22381. function setValue(obj, path, value) {
  22382. var keys = path.split('.');
  22383. var o = obj;
  22384. while (keys.length) {
  22385. var key = keys.shift();
  22386. if (keys.length) {
  22387. // this isn't the end point
  22388. if (!o[key]) {
  22389. o[key] = {};
  22390. }
  22391. o = o[key];
  22392. }
  22393. else {
  22394. // this is the end point
  22395. o[key] = value;
  22396. }
  22397. }
  22398. }
  22399. /**
  22400. * Add a node to a graph object. If there is already a node with
  22401. * the same id, their attributes will be merged.
  22402. * @param {Object} graph
  22403. * @param {Object} node
  22404. */
  22405. function addNode(graph, node) {
  22406. var i, len;
  22407. var current = null;
  22408. // find root graph (in case of subgraph)
  22409. var graphs = [graph]; // list with all graphs from current graph to root graph
  22410. var root = graph;
  22411. while (root.parent) {
  22412. graphs.push(root.parent);
  22413. root = root.parent;
  22414. }
  22415. // find existing node (at root level) by its id
  22416. if (root.nodes) {
  22417. for (i = 0, len = root.nodes.length; i < len; i++) {
  22418. if (node.id === root.nodes[i].id) {
  22419. current = root.nodes[i];
  22420. break;
  22421. }
  22422. }
  22423. }
  22424. if (!current) {
  22425. // this is a new node
  22426. current = {
  22427. id: node.id
  22428. };
  22429. if (graph.node) {
  22430. // clone default attributes
  22431. current.attr = merge(current.attr, graph.node);
  22432. }
  22433. }
  22434. // add node to this (sub)graph and all its parent graphs
  22435. for (i = graphs.length - 1; i >= 0; i--) {
  22436. var g = graphs[i];
  22437. if (!g.nodes) {
  22438. g.nodes = [];
  22439. }
  22440. if (g.nodes.indexOf(current) == -1) {
  22441. g.nodes.push(current);
  22442. }
  22443. }
  22444. // merge attributes
  22445. if (node.attr) {
  22446. current.attr = merge(current.attr, node.attr);
  22447. }
  22448. }
  22449. /**
  22450. * Add an edge to a graph object
  22451. * @param {Object} graph
  22452. * @param {Object} edge
  22453. */
  22454. function addEdge(graph, edge) {
  22455. if (!graph.edges) {
  22456. graph.edges = [];
  22457. }
  22458. graph.edges.push(edge);
  22459. if (graph.edge) {
  22460. var attr = merge({}, graph.edge); // clone default attributes
  22461. edge.attr = merge(attr, edge.attr); // merge attributes
  22462. }
  22463. }
  22464. /**
  22465. * Create an edge to a graph object
  22466. * @param {Object} graph
  22467. * @param {String | Number | Object} from
  22468. * @param {String | Number | Object} to
  22469. * @param {String} type
  22470. * @param {Object | null} attr
  22471. * @return {Object} edge
  22472. */
  22473. function createEdge(graph, from, to, type, attr) {
  22474. var edge = {
  22475. from: from,
  22476. to: to,
  22477. type: type
  22478. };
  22479. if (graph.edge) {
  22480. edge.attr = merge({}, graph.edge); // clone default attributes
  22481. }
  22482. edge.attr = merge(edge.attr || {}, attr); // merge attributes
  22483. return edge;
  22484. }
  22485. /**
  22486. * Get next token in the current dot file.
  22487. * The token and token type are available as token and tokenType
  22488. */
  22489. function getToken() {
  22490. tokenType = TOKENTYPE.NULL;
  22491. token = '';
  22492. // skip over whitespaces
  22493. while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter
  22494. next();
  22495. }
  22496. do {
  22497. var isComment = false;
  22498. // skip comment
  22499. if (c == '#') {
  22500. // find the previous non-space character
  22501. var i = index - 1;
  22502. while (dot.charAt(i) == ' ' || dot.charAt(i) == '\t') {
  22503. i--;
  22504. }
  22505. if (dot.charAt(i) == '\n' || dot.charAt(i) == '') {
  22506. // the # is at the start of a line, this is indeed a line comment
  22507. while (c != '' && c != '\n') {
  22508. next();
  22509. }
  22510. isComment = true;
  22511. }
  22512. }
  22513. if (c == '/' && nextPreview() == '/') {
  22514. // skip line comment
  22515. while (c != '' && c != '\n') {
  22516. next();
  22517. }
  22518. isComment = true;
  22519. }
  22520. if (c == '/' && nextPreview() == '*') {
  22521. // skip block comment
  22522. while (c != '') {
  22523. if (c == '*' && nextPreview() == '/') {
  22524. // end of block comment found. skip these last two characters
  22525. next();
  22526. next();
  22527. break;
  22528. }
  22529. else {
  22530. next();
  22531. }
  22532. }
  22533. isComment = true;
  22534. }
  22535. // skip over whitespaces
  22536. while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter
  22537. next();
  22538. }
  22539. }
  22540. while (isComment);
  22541. // check for end of dot file
  22542. if (c == '') {
  22543. // token is still empty
  22544. tokenType = TOKENTYPE.DELIMITER;
  22545. return;
  22546. }
  22547. // check for delimiters consisting of 2 characters
  22548. var c2 = c + nextPreview();
  22549. if (DELIMITERS[c2]) {
  22550. tokenType = TOKENTYPE.DELIMITER;
  22551. token = c2;
  22552. next();
  22553. next();
  22554. return;
  22555. }
  22556. // check for delimiters consisting of 1 character
  22557. if (DELIMITERS[c]) {
  22558. tokenType = TOKENTYPE.DELIMITER;
  22559. token = c;
  22560. next();
  22561. return;
  22562. }
  22563. // check for an identifier (number or string)
  22564. // TODO: more precise parsing of numbers/strings (and the port separator ':')
  22565. if (isAlphaNumeric(c) || c == '-') {
  22566. token += c;
  22567. next();
  22568. while (isAlphaNumeric(c)) {
  22569. token += c;
  22570. next();
  22571. }
  22572. if (token == 'false') {
  22573. token = false; // convert to boolean
  22574. }
  22575. else if (token == 'true') {
  22576. token = true; // convert to boolean
  22577. }
  22578. else if (!isNaN(Number(token))) {
  22579. token = Number(token); // convert to number
  22580. }
  22581. tokenType = TOKENTYPE.IDENTIFIER;
  22582. return;
  22583. }
  22584. // check for a string enclosed by double quotes
  22585. if (c == '"') {
  22586. next();
  22587. while (c != '' && (c != '"' || (c == '"' && nextPreview() == '"'))) {
  22588. token += c;
  22589. if (c == '"') { // skip the escape character
  22590. next();
  22591. }
  22592. next();
  22593. }
  22594. if (c != '"') {
  22595. throw newSyntaxError('End of string " expected');
  22596. }
  22597. next();
  22598. tokenType = TOKENTYPE.IDENTIFIER;
  22599. return;
  22600. }
  22601. // something unknown is found, wrong characters, a syntax error
  22602. tokenType = TOKENTYPE.UNKNOWN;
  22603. while (c != '') {
  22604. token += c;
  22605. next();
  22606. }
  22607. throw new SyntaxError('Syntax error in part "' + chop(token, 30) + '"');
  22608. }
  22609. /**
  22610. * Parse a graph.
  22611. * @returns {Object} graph
  22612. */
  22613. function parseGraph() {
  22614. var graph = {};
  22615. first();
  22616. getToken();
  22617. // optional strict keyword
  22618. if (token == 'strict') {
  22619. graph.strict = true;
  22620. getToken();
  22621. }
  22622. // graph or digraph keyword
  22623. if (token == 'graph' || token == 'digraph') {
  22624. graph.type = token;
  22625. getToken();
  22626. }
  22627. // optional graph id
  22628. if (tokenType == TOKENTYPE.IDENTIFIER) {
  22629. graph.id = token;
  22630. getToken();
  22631. }
  22632. // open angle bracket
  22633. if (token != '{') {
  22634. throw newSyntaxError('Angle bracket { expected');
  22635. }
  22636. getToken();
  22637. // statements
  22638. parseStatements(graph);
  22639. // close angle bracket
  22640. if (token != '}') {
  22641. throw newSyntaxError('Angle bracket } expected');
  22642. }
  22643. getToken();
  22644. // end of file
  22645. if (token !== '') {
  22646. throw newSyntaxError('End of file expected');
  22647. }
  22648. getToken();
  22649. // remove temporary default properties
  22650. delete graph.node;
  22651. delete graph.edge;
  22652. delete graph.graph;
  22653. return graph;
  22654. }
  22655. /**
  22656. * Parse a list with statements.
  22657. * @param {Object} graph
  22658. */
  22659. function parseStatements (graph) {
  22660. while (token !== '' && token != '}') {
  22661. parseStatement(graph);
  22662. if (token == ';') {
  22663. getToken();
  22664. }
  22665. }
  22666. }
  22667. /**
  22668. * Parse a single statement. Can be a an attribute statement, node
  22669. * statement, a series of node statements and edge statements, or a
  22670. * parameter.
  22671. * @param {Object} graph
  22672. */
  22673. function parseStatement(graph) {
  22674. // parse subgraph
  22675. var subgraph = parseSubgraph(graph);
  22676. if (subgraph) {
  22677. // edge statements
  22678. parseEdge(graph, subgraph);
  22679. return;
  22680. }
  22681. // parse an attribute statement
  22682. var attr = parseAttributeStatement(graph);
  22683. if (attr) {
  22684. return;
  22685. }
  22686. // parse node
  22687. if (tokenType != TOKENTYPE.IDENTIFIER) {
  22688. throw newSyntaxError('Identifier expected');
  22689. }
  22690. var id = token; // id can be a string or a number
  22691. getToken();
  22692. if (token == '=') {
  22693. // id statement
  22694. getToken();
  22695. if (tokenType != TOKENTYPE.IDENTIFIER) {
  22696. throw newSyntaxError('Identifier expected');
  22697. }
  22698. graph[id] = token;
  22699. getToken();
  22700. // TODO: implement comma separated list with "a_list: ID=ID [','] [a_list] "
  22701. }
  22702. else {
  22703. parseNodeStatement(graph, id);
  22704. }
  22705. }
  22706. /**
  22707. * Parse a subgraph
  22708. * @param {Object} graph parent graph object
  22709. * @return {Object | null} subgraph
  22710. */
  22711. function parseSubgraph (graph) {
  22712. var subgraph = null;
  22713. // optional subgraph keyword
  22714. if (token == 'subgraph') {
  22715. subgraph = {};
  22716. subgraph.type = 'subgraph';
  22717. getToken();
  22718. // optional graph id
  22719. if (tokenType == TOKENTYPE.IDENTIFIER) {
  22720. subgraph.id = token;
  22721. getToken();
  22722. }
  22723. }
  22724. // open angle bracket
  22725. if (token == '{') {
  22726. getToken();
  22727. if (!subgraph) {
  22728. subgraph = {};
  22729. }
  22730. subgraph.parent = graph;
  22731. subgraph.node = graph.node;
  22732. subgraph.edge = graph.edge;
  22733. subgraph.graph = graph.graph;
  22734. // statements
  22735. parseStatements(subgraph);
  22736. // close angle bracket
  22737. if (token != '}') {
  22738. throw newSyntaxError('Angle bracket } expected');
  22739. }
  22740. getToken();
  22741. // remove temporary default properties
  22742. delete subgraph.node;
  22743. delete subgraph.edge;
  22744. delete subgraph.graph;
  22745. delete subgraph.parent;
  22746. // register at the parent graph
  22747. if (!graph.subgraphs) {
  22748. graph.subgraphs = [];
  22749. }
  22750. graph.subgraphs.push(subgraph);
  22751. }
  22752. return subgraph;
  22753. }
  22754. /**
  22755. * parse an attribute statement like "node [shape=circle fontSize=16]".
  22756. * Available keywords are 'node', 'edge', 'graph'.
  22757. * The previous list with default attributes will be replaced
  22758. * @param {Object} graph
  22759. * @returns {String | null} keyword Returns the name of the parsed attribute
  22760. * (node, edge, graph), or null if nothing
  22761. * is parsed.
  22762. */
  22763. function parseAttributeStatement (graph) {
  22764. // attribute statements
  22765. if (token == 'node') {
  22766. getToken();
  22767. // node attributes
  22768. graph.node = parseAttributeList();
  22769. return 'node';
  22770. }
  22771. else if (token == 'edge') {
  22772. getToken();
  22773. // edge attributes
  22774. graph.edge = parseAttributeList();
  22775. return 'edge';
  22776. }
  22777. else if (token == 'graph') {
  22778. getToken();
  22779. // graph attributes
  22780. graph.graph = parseAttributeList();
  22781. return 'graph';
  22782. }
  22783. return null;
  22784. }
  22785. /**
  22786. * parse a node statement
  22787. * @param {Object} graph
  22788. * @param {String | Number} id
  22789. */
  22790. function parseNodeStatement(graph, id) {
  22791. // node statement
  22792. var node = {
  22793. id: id
  22794. };
  22795. var attr = parseAttributeList();
  22796. if (attr) {
  22797. node.attr = attr;
  22798. }
  22799. addNode(graph, node);
  22800. // edge statements
  22801. parseEdge(graph, id);
  22802. }
  22803. /**
  22804. * Parse an edge or a series of edges
  22805. * @param {Object} graph
  22806. * @param {String | Number} from Id of the from node
  22807. */
  22808. function parseEdge(graph, from) {
  22809. while (token == '->' || token == '--') {
  22810. var to;
  22811. var type = token;
  22812. getToken();
  22813. var subgraph = parseSubgraph(graph);
  22814. if (subgraph) {
  22815. to = subgraph;
  22816. }
  22817. else {
  22818. if (tokenType != TOKENTYPE.IDENTIFIER) {
  22819. throw newSyntaxError('Identifier or subgraph expected');
  22820. }
  22821. to = token;
  22822. addNode(graph, {
  22823. id: to
  22824. });
  22825. getToken();
  22826. }
  22827. // parse edge attributes
  22828. var attr = parseAttributeList();
  22829. // create edge
  22830. var edge = createEdge(graph, from, to, type, attr);
  22831. addEdge(graph, edge);
  22832. from = to;
  22833. }
  22834. }
  22835. /**
  22836. * Parse a set with attributes,
  22837. * for example [label="1.000", shape=solid]
  22838. * @return {Object | null} attr
  22839. */
  22840. function parseAttributeList() {
  22841. var attr = null;
  22842. while (token == '[') {
  22843. getToken();
  22844. attr = {};
  22845. while (token !== '' && token != ']') {
  22846. if (tokenType != TOKENTYPE.IDENTIFIER) {
  22847. throw newSyntaxError('Attribute name expected');
  22848. }
  22849. var name = token;
  22850. getToken();
  22851. if (token != '=') {
  22852. throw newSyntaxError('Equal sign = expected');
  22853. }
  22854. getToken();
  22855. if (tokenType != TOKENTYPE.IDENTIFIER) {
  22856. throw newSyntaxError('Attribute value expected');
  22857. }
  22858. var value = token;
  22859. setValue(attr, name, value); // name can be a path
  22860. getToken();
  22861. if (token ==',') {
  22862. getToken();
  22863. }
  22864. }
  22865. if (token != ']') {
  22866. throw newSyntaxError('Bracket ] expected');
  22867. }
  22868. getToken();
  22869. }
  22870. return attr;
  22871. }
  22872. /**
  22873. * Create a syntax error with extra information on current token and index.
  22874. * @param {String} message
  22875. * @returns {SyntaxError} err
  22876. */
  22877. function newSyntaxError(message) {
  22878. return new SyntaxError(message + ', got "' + chop(token, 30) + '" (char ' + index + ')');
  22879. }
  22880. /**
  22881. * Chop off text after a maximum length
  22882. * @param {String} text
  22883. * @param {Number} maxLength
  22884. * @returns {String}
  22885. */
  22886. function chop (text, maxLength) {
  22887. return (text.length <= maxLength) ? text : (text.substr(0, 27) + '...');
  22888. }
  22889. /**
  22890. * Execute a function fn for each pair of elements in two arrays
  22891. * @param {Array | *} array1
  22892. * @param {Array | *} array2
  22893. * @param {function} fn
  22894. */
  22895. function forEach2(array1, array2, fn) {
  22896. if (Array.isArray(array1)) {
  22897. array1.forEach(function (elem1) {
  22898. if (Array.isArray(array2)) {
  22899. array2.forEach(function (elem2) {
  22900. fn(elem1, elem2);
  22901. });
  22902. }
  22903. else {
  22904. fn(elem1, array2);
  22905. }
  22906. });
  22907. }
  22908. else {
  22909. if (Array.isArray(array2)) {
  22910. array2.forEach(function (elem2) {
  22911. fn(array1, elem2);
  22912. });
  22913. }
  22914. else {
  22915. fn(array1, array2);
  22916. }
  22917. }
  22918. }
  22919. /**
  22920. * Convert a string containing a graph in DOT language into a map containing
  22921. * with nodes and edges in the format of graph.
  22922. * @param {String} data Text containing a graph in DOT-notation
  22923. * @return {Object} graphData
  22924. */
  22925. function DOTToGraph (data) {
  22926. // parse the DOT file
  22927. var dotData = parseDOT(data);
  22928. var graphData = {
  22929. nodes: [],
  22930. edges: [],
  22931. options: {}
  22932. };
  22933. // copy the nodes
  22934. if (dotData.nodes) {
  22935. dotData.nodes.forEach(function (dotNode) {
  22936. var graphNode = {
  22937. id: dotNode.id,
  22938. label: String(dotNode.label || dotNode.id)
  22939. };
  22940. merge(graphNode, dotNode.attr);
  22941. if (graphNode.image) {
  22942. graphNode.shape = 'image';
  22943. }
  22944. graphData.nodes.push(graphNode);
  22945. });
  22946. }
  22947. // copy the edges
  22948. if (dotData.edges) {
  22949. /**
  22950. * Convert an edge in DOT format to an edge with VisGraph format
  22951. * @param {Object} dotEdge
  22952. * @returns {Object} graphEdge
  22953. */
  22954. var convertEdge = function (dotEdge) {
  22955. var graphEdge = {
  22956. from: dotEdge.from,
  22957. to: dotEdge.to
  22958. };
  22959. merge(graphEdge, dotEdge.attr);
  22960. graphEdge.style = (dotEdge.type == '->') ? 'arrow' : 'line';
  22961. return graphEdge;
  22962. }
  22963. dotData.edges.forEach(function (dotEdge) {
  22964. var from, to;
  22965. if (dotEdge.from instanceof Object) {
  22966. from = dotEdge.from.nodes;
  22967. }
  22968. else {
  22969. from = {
  22970. id: dotEdge.from
  22971. }
  22972. }
  22973. if (dotEdge.to instanceof Object) {
  22974. to = dotEdge.to.nodes;
  22975. }
  22976. else {
  22977. to = {
  22978. id: dotEdge.to
  22979. }
  22980. }
  22981. if (dotEdge.from instanceof Object && dotEdge.from.edges) {
  22982. dotEdge.from.edges.forEach(function (subEdge) {
  22983. var graphEdge = convertEdge(subEdge);
  22984. graphData.edges.push(graphEdge);
  22985. });
  22986. }
  22987. forEach2(from, to, function (from, to) {
  22988. var subEdge = createEdge(graphData, from.id, to.id, dotEdge.type, dotEdge.attr);
  22989. var graphEdge = convertEdge(subEdge);
  22990. graphData.edges.push(graphEdge);
  22991. });
  22992. if (dotEdge.to instanceof Object && dotEdge.to.edges) {
  22993. dotEdge.to.edges.forEach(function (subEdge) {
  22994. var graphEdge = convertEdge(subEdge);
  22995. graphData.edges.push(graphEdge);
  22996. });
  22997. }
  22998. });
  22999. }
  23000. // copy the options
  23001. if (dotData.attr) {
  23002. graphData.options = dotData.attr;
  23003. }
  23004. return graphData;
  23005. }
  23006. // exports
  23007. exports.parseDOT = parseDOT;
  23008. exports.DOTToGraph = DOTToGraph;
  23009. /***/ },
  23010. /* 53 */
  23011. /***/ function(module, exports, __webpack_require__) {
  23012. function parseGephi(gephiJSON, options) {
  23013. var edges = [];
  23014. var nodes = [];
  23015. this.options = {
  23016. edges: {
  23017. inheritColor: true
  23018. },
  23019. nodes: {
  23020. allowedToMove: false,
  23021. parseColor: false
  23022. }
  23023. };
  23024. if (options !== undefined) {
  23025. this.options.nodes['allowedToMove'] = options.allowedToMove | false;
  23026. this.options.nodes['parseColor'] = options.parseColor | false;
  23027. this.options.edges['inheritColor'] = options.inheritColor | true;
  23028. }
  23029. var gEdges = gephiJSON.edges;
  23030. var gNodes = gephiJSON.nodes;
  23031. for (var i = 0; i < gEdges.length; i++) {
  23032. var edge = {};
  23033. var gEdge = gEdges[i];
  23034. edge['id'] = gEdge.id;
  23035. edge['from'] = gEdge.source;
  23036. edge['to'] = gEdge.target;
  23037. edge['attributes'] = gEdge.attributes;
  23038. // edge['value'] = gEdge.attributes !== undefined ? gEdge.attributes.Weight : undefined;
  23039. // edge['width'] = edge['value'] !== undefined ? undefined : edgegEdge.size;
  23040. edge['color'] = gEdge.color;
  23041. edge['inheritColor'] = edge['color'] !== undefined ? false : this.options.inheritColor;
  23042. edges.push(edge);
  23043. }
  23044. for (var i = 0; i < gNodes.length; i++) {
  23045. var node = {};
  23046. var gNode = gNodes[i];
  23047. node['id'] = gNode.id;
  23048. node['attributes'] = gNode.attributes;
  23049. node['x'] = gNode.x;
  23050. node['y'] = gNode.y;
  23051. node['label'] = gNode.label;
  23052. if (this.options.nodes.parseColor == true) {
  23053. node['color'] = gNode.color;
  23054. }
  23055. else {
  23056. node['color'] = gNode.color !== undefined ? {background:gNode.color, border:gNode.color} : undefined;
  23057. }
  23058. node['radius'] = gNode.size;
  23059. node['allowedToMoveX'] = this.options.nodes.allowedToMove;
  23060. node['allowedToMoveY'] = this.options.nodes.allowedToMove;
  23061. nodes.push(node);
  23062. }
  23063. return {nodes:nodes, edges:edges};
  23064. }
  23065. exports.parseGephi = parseGephi;
  23066. /***/ },
  23067. /* 54 */
  23068. /***/ function(module, exports, __webpack_require__) {
  23069. var util = __webpack_require__(1);
  23070. /**
  23071. * @class Groups
  23072. * This class can store groups and properties specific for groups.
  23073. */
  23074. function Groups() {
  23075. this.clear();
  23076. this.defaultIndex = 0;
  23077. }
  23078. /**
  23079. * default constants for group colors
  23080. */
  23081. Groups.DEFAULT = [
  23082. {border: "#2B7CE9", background: "#97C2FC", highlight: {border: "#2B7CE9", background: "#D2E5FF"}, hover: {border: "#2B7CE9", background: "#D2E5FF"}}, // blue
  23083. {border: "#FFA500", background: "#FFFF00", highlight: {border: "#FFA500", background: "#FFFFA3"}, hover: {border: "#FFA500", background: "#FFFFA3"}}, // yellow
  23084. {border: "#FA0A10", background: "#FB7E81", highlight: {border: "#FA0A10", background: "#FFAFB1"}, hover: {border: "#FA0A10", background: "#FFAFB1"}}, // red
  23085. {border: "#41A906", background: "#7BE141", highlight: {border: "#41A906", background: "#A1EC76"}, hover: {border: "#41A906", background: "#A1EC76"}}, // green
  23086. {border: "#E129F0", background: "#EB7DF4", highlight: {border: "#E129F0", background: "#F0B3F5"}, hover: {border: "#E129F0", background: "#F0B3F5"}}, // magenta
  23087. {border: "#7C29F0", background: "#AD85E4", highlight: {border: "#7C29F0", background: "#D3BDF0"}, hover: {border: "#7C29F0", background: "#D3BDF0"}}, // purple
  23088. {border: "#C37F00", background: "#FFA807", highlight: {border: "#C37F00", background: "#FFCA66"}, hover: {border: "#C37F00", background: "#FFCA66"}}, // orange
  23089. {border: "#4220FB", background: "#6E6EFD", highlight: {border: "#4220FB", background: "#9B9BFD"}, hover: {border: "#4220FB", background: "#9B9BFD"}}, // darkblue
  23090. {border: "#FD5A77", background: "#FFC0CB", highlight: {border: "#FD5A77", background: "#FFD1D9"}, hover: {border: "#FD5A77", background: "#FFD1D9"}}, // pink
  23091. {border: "#4AD63A", background: "#C2FABC", highlight: {border: "#4AD63A", background: "#E6FFE3"}, hover: {border: "#4AD63A", background: "#E6FFE3"}} // mint
  23092. ];
  23093. /**
  23094. * Clear all groups
  23095. */
  23096. Groups.prototype.clear = function () {
  23097. this.groups = {};
  23098. this.groups.length = function()
  23099. {
  23100. var i = 0;
  23101. for ( var p in this ) {
  23102. if (this.hasOwnProperty(p)) {
  23103. i++;
  23104. }
  23105. }
  23106. return i;
  23107. }
  23108. };
  23109. /**
  23110. * get group properties of a groupname. If groupname is not found, a new group
  23111. * is added.
  23112. * @param {*} groupname Can be a number, string, Date, etc.
  23113. * @return {Object} group The created group, containing all group properties
  23114. */
  23115. Groups.prototype.get = function (groupname) {
  23116. var group = this.groups[groupname];
  23117. if (group == undefined) {
  23118. // create new group
  23119. var index = this.defaultIndex % Groups.DEFAULT.length;
  23120. this.defaultIndex++;
  23121. group = {};
  23122. group.color = Groups.DEFAULT[index];
  23123. this.groups[groupname] = group;
  23124. }
  23125. return group;
  23126. };
  23127. /**
  23128. * Add a custom group style
  23129. * @param {String} groupname
  23130. * @param {Object} style An object containing borderColor,
  23131. * backgroundColor, etc.
  23132. * @return {Object} group The created group object
  23133. */
  23134. Groups.prototype.add = function (groupname, style) {
  23135. this.groups[groupname] = style;
  23136. return style;
  23137. };
  23138. module.exports = Groups;
  23139. /***/ },
  23140. /* 55 */
  23141. /***/ function(module, exports, __webpack_require__) {
  23142. /**
  23143. * @class Images
  23144. * This class loads images and keeps them stored.
  23145. */
  23146. function Images() {
  23147. this.images = {};
  23148. this.imageBroken = {};
  23149. this.callback = undefined;
  23150. }
  23151. /**
  23152. * Set an onload callback function. This will be called each time an image
  23153. * is loaded
  23154. * @param {function} callback
  23155. */
  23156. Images.prototype.setOnloadCallback = function(callback) {
  23157. this.callback = callback;
  23158. };
  23159. /**
  23160. *
  23161. * @param {string} url Url of the image
  23162. * @param {string} url Url of an image to use if the url image is not found
  23163. * @return {Image} img The image object
  23164. */
  23165. Images.prototype.load = function(url, brokenUrl) {
  23166. var img = this.images[url]; // make a pointer
  23167. if (img === undefined) {
  23168. // create the image
  23169. var me = this;
  23170. img = new Image();
  23171. img.onload = function () {
  23172. // IE11 fix -- thanks dponch!
  23173. if (this.width == 0) {
  23174. document.body.appendChild(this);
  23175. this.width = this.offsetWidth;
  23176. this.height = this.offsetHeight;
  23177. document.body.removeChild(this);
  23178. }
  23179. if (me.callback) {
  23180. me.images[url] = img;
  23181. me.callback(this);
  23182. }
  23183. };
  23184. img.onerror = function () {
  23185. if (brokenUrl === undefined) {
  23186. console.error("Could not load image:", url);
  23187. delete this.src;
  23188. if (me.callback) {
  23189. me.callback(this);
  23190. }
  23191. }
  23192. else {
  23193. if (me.imageBroken[url] === true) {
  23194. if (this.src == brokenUrl) {
  23195. console.error("Could not load brokenImage:", brokenUrl);
  23196. delete this.src;
  23197. if (me.callback) {
  23198. me.callback(this);
  23199. }
  23200. }
  23201. else {
  23202. console.error("Could not load image:", url);
  23203. this.src = brokenUrl;
  23204. }
  23205. }
  23206. else {
  23207. console.error("Could not load image:", url);
  23208. this.src = brokenUrl;
  23209. me.imageBroken[url] = true;
  23210. }
  23211. }
  23212. };
  23213. img.src = url;
  23214. }
  23215. return img;
  23216. };
  23217. module.exports = Images;
  23218. /***/ },
  23219. /* 56 */
  23220. /***/ function(module, exports, __webpack_require__) {
  23221. var util = __webpack_require__(1);
  23222. /**
  23223. * @class Node
  23224. * A node. A node can be connected to other nodes via one or multiple edges.
  23225. * @param {object} properties An object containing properties for the node. All
  23226. * properties are optional, except for the id.
  23227. * {number} id Id of the node. Required
  23228. * {string} label Text label for the node
  23229. * {number} x Horizontal position of the node
  23230. * {number} y Vertical position of the node
  23231. * {string} shape Node shape, available:
  23232. * "database", "circle", "ellipse",
  23233. * "box", "image", "text", "dot",
  23234. * "star", "triangle", "triangleDown",
  23235. * "square"
  23236. * {string} image An image url
  23237. * {string} title An title text, can be HTML
  23238. * {anytype} group A group name or number
  23239. * @param {Network.Images} imagelist A list with images. Only needed
  23240. * when the node has an image
  23241. * @param {Network.Groups} grouplist A list with groups. Needed for
  23242. * retrieving group properties
  23243. * @param {Object} constants An object with default values for
  23244. * example for the color
  23245. *
  23246. */
  23247. function Node(properties, imagelist, grouplist, networkConstants) {
  23248. var constants = util.selectiveBridgeObject(['nodes'],networkConstants);
  23249. this.options = constants.nodes;
  23250. this.selected = false;
  23251. this.hover = false;
  23252. this.edges = []; // all edges connected to this node
  23253. this.dynamicEdges = [];
  23254. this.reroutedEdges = {};
  23255. // set defaults for the properties
  23256. this.id = undefined;
  23257. this.allowedToMoveX = false;
  23258. this.allowedToMoveY = false;
  23259. this.xFixed = false;
  23260. this.yFixed = false;
  23261. this.horizontalAlignLeft = true; // these are for the navigation controls
  23262. this.verticalAlignTop = true; // these are for the navigation controls
  23263. this.baseRadiusValue = networkConstants.nodes.radius;
  23264. this.radiusFixed = false;
  23265. this.level = -1;
  23266. this.preassignedLevel = false;
  23267. this.hierarchyEnumerated = false;
  23268. this.labelDimensions = {top:0, left:0, width:0, height:0, yLine:0}; // could be cached
  23269. this.boundingBox = {top:0, left:0, right:0, bottom:0};
  23270. this.imagelist = imagelist;
  23271. this.grouplist = grouplist;
  23272. // physics properties
  23273. this.fx = 0.0; // external force x
  23274. this.fy = 0.0; // external force y
  23275. this.vx = 0.0; // velocity x
  23276. this.vy = 0.0; // velocity y
  23277. this.x = null;
  23278. this.y = null;
  23279. this.predefinedPosition = false; // used to check if initial zoomExtent should just take the range or approximate
  23280. // used for reverting to previous position on stabilization
  23281. this.previousState = {vx:0,vy:0,x:0,y:0};
  23282. this.damping = networkConstants.physics.damping; // written every time gravity is calculated
  23283. this.fixedData = {x:null,y:null};
  23284. this.setProperties(properties, constants);
  23285. // creating the variables for clustering
  23286. this.resetCluster();
  23287. this.clusterSession = 0;
  23288. this.clusterSizeWidthFactor = networkConstants.clustering.nodeScaling.width;
  23289. this.clusterSizeHeightFactor = networkConstants.clustering.nodeScaling.height;
  23290. this.clusterSizeRadiusFactor = networkConstants.clustering.nodeScaling.radius;
  23291. this.maxNodeSizeIncrements = networkConstants.clustering.maxNodeSizeIncrements;
  23292. this.growthIndicator = 0;
  23293. // variables to tell the node about the network.
  23294. this.networkScaleInv = 1;
  23295. this.networkScale = 1;
  23296. this.canvasTopLeft = {"x": -300, "y": -300};
  23297. this.canvasBottomRight = {"x": 300, "y": 300};
  23298. this.parentEdgeId = null;
  23299. }
  23300. /**
  23301. * Revert the position and velocity of the previous step.
  23302. */
  23303. Node.prototype.revertPosition = function() {
  23304. this.x = this.previousState.x;
  23305. this.y = this.previousState.y;
  23306. this.vx = this.previousState.vx;
  23307. this.vy = this.previousState.vy;
  23308. }
  23309. /**
  23310. * (re)setting the clustering variables and objects
  23311. */
  23312. Node.prototype.resetCluster = function() {
  23313. // clustering variables
  23314. this.formationScale = undefined; // this is used to determine when to open the cluster
  23315. this.clusterSize = 1; // this signifies the total amount of nodes in this cluster
  23316. this.containedNodes = {};
  23317. this.containedEdges = {};
  23318. this.clusterSessions = [];
  23319. };
  23320. /**
  23321. * Attach a edge to the node
  23322. * @param {Edge} edge
  23323. */
  23324. Node.prototype.attachEdge = function(edge) {
  23325. if (this.edges.indexOf(edge) == -1) {
  23326. this.edges.push(edge);
  23327. }
  23328. if (this.dynamicEdges.indexOf(edge) == -1) {
  23329. this.dynamicEdges.push(edge);
  23330. }
  23331. };
  23332. /**
  23333. * Detach a edge from the node
  23334. * @param {Edge} edge
  23335. */
  23336. Node.prototype.detachEdge = function(edge) {
  23337. var index = this.edges.indexOf(edge);
  23338. if (index != -1) {
  23339. this.edges.splice(index, 1);
  23340. }
  23341. index = this.dynamicEdges.indexOf(edge);
  23342. if (index != -1) {
  23343. this.dynamicEdges.splice(index, 1);
  23344. }
  23345. };
  23346. /**
  23347. * Set or overwrite properties for the node
  23348. * @param {Object} properties an object with properties
  23349. * @param {Object} constants and object with default, global properties
  23350. */
  23351. Node.prototype.setProperties = function(properties, constants) {
  23352. if (!properties) {
  23353. return;
  23354. }
  23355. var fields = ['borderWidth','borderWidthSelected','shape','image','brokenImage','radius','fontColor',
  23356. 'fontSize','fontFace','fontFill','fontStrokeWidth','fontStrokeColor','group','mass','fontDrawThreshold',
  23357. 'scaleFontWithValue','fontSizeMaxVisible','customScalingFunction'
  23358. ];
  23359. util.selectiveDeepExtend(fields, this.options, properties);
  23360. // basic properties
  23361. if (properties.id !== undefined) {this.id = properties.id;}
  23362. if (properties.label !== undefined) {this.label = properties.label; this.originalLabel = properties.label;}
  23363. if (properties.title !== undefined) {this.title = properties.title;}
  23364. if (properties.x !== undefined) {this.x = properties.x; this.predefinedPosition = true;}
  23365. if (properties.y !== undefined) {this.y = properties.y; this.predefinedPosition = true;}
  23366. if (properties.value !== undefined) {this.value = properties.value;}
  23367. if (properties.level !== undefined) {this.level = properties.level; this.preassignedLevel = true;}
  23368. // navigation controls properties
  23369. if (properties.horizontalAlignLeft !== undefined) {this.horizontalAlignLeft = properties.horizontalAlignLeft;}
  23370. if (properties.verticalAlignTop !== undefined) {this.verticalAlignTop = properties.verticalAlignTop;}
  23371. if (properties.triggerFunction !== undefined) {this.triggerFunction = properties.triggerFunction;}
  23372. if (this.id === undefined) {
  23373. throw "Node must have an id";
  23374. }
  23375. // copy group properties
  23376. if (typeof properties.group === 'number' || (typeof properties.group === 'string' && properties.group != '')) {
  23377. var groupObj = this.grouplist.get(properties.group);
  23378. util.deepExtend(this.options, groupObj);
  23379. // the color object needs to be completely defined. Since groups can partially overwrite the colors, we parse it again, just in case.
  23380. this.options.color = util.parseColor(this.options.color);
  23381. }
  23382. // individual shape properties
  23383. if (properties.radius !== undefined) {this.baseRadiusValue = this.options.radius;}
  23384. if (properties.color !== undefined) {this.options.color = util.parseColor(properties.color);}
  23385. if (this.options.image !== undefined && this.options.image!= "") {
  23386. if (this.imagelist) {
  23387. this.imageObj = this.imagelist.load(this.options.image, this.options.brokenImage);
  23388. }
  23389. else {
  23390. throw "No imagelist provided";
  23391. }
  23392. }
  23393. if (properties.allowedToMoveX !== undefined) {
  23394. this.xFixed = !properties.allowedToMoveX;
  23395. this.allowedToMoveX = properties.allowedToMoveX;
  23396. }
  23397. else if (properties.x !== undefined && this.allowedToMoveX == false) {
  23398. this.xFixed = true;
  23399. }
  23400. if (properties.allowedToMoveY !== undefined) {
  23401. this.yFixed = !properties.allowedToMoveY;
  23402. this.allowedToMoveY = properties.allowedToMoveY;
  23403. }
  23404. else if (properties.y !== undefined && this.allowedToMoveY == false) {
  23405. this.yFixed = true;
  23406. }
  23407. this.radiusFixed = this.radiusFixed || (properties.radius !== undefined);
  23408. if (this.options.shape === 'image' || this.options.shape === 'circularImage') {
  23409. this.options.radiusMin = constants.nodes.widthMin;
  23410. this.options.radiusMax = constants.nodes.widthMax;
  23411. }
  23412. // choose draw method depending on the shape
  23413. switch (this.options.shape) {
  23414. case 'database': this.draw = this._drawDatabase; this.resize = this._resizeDatabase; break;
  23415. case 'box': this.draw = this._drawBox; this.resize = this._resizeBox; break;
  23416. case 'circle': this.draw = this._drawCircle; this.resize = this._resizeCircle; break;
  23417. case 'ellipse': this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
  23418. // TODO: add diamond shape
  23419. case 'image': this.draw = this._drawImage; this.resize = this._resizeImage; break;
  23420. case 'circularImage': this.draw = this._drawCircularImage; this.resize = this._resizeCircularImage; break;
  23421. case 'text': this.draw = this._drawText; this.resize = this._resizeText; break;
  23422. case 'dot': this.draw = this._drawDot; this.resize = this._resizeShape; break;
  23423. case 'square': this.draw = this._drawSquare; this.resize = this._resizeShape; break;
  23424. case 'triangle': this.draw = this._drawTriangle; this.resize = this._resizeShape; break;
  23425. case 'triangleDown': this.draw = this._drawTriangleDown; this.resize = this._resizeShape; break;
  23426. case 'star': this.draw = this._drawStar; this.resize = this._resizeShape; break;
  23427. default: this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
  23428. }
  23429. // reset the size of the node, this can be changed
  23430. this._reset();
  23431. };
  23432. /**
  23433. * select this node
  23434. */
  23435. Node.prototype.select = function() {
  23436. this.selected = true;
  23437. this._reset();
  23438. };
  23439. /**
  23440. * unselect this node
  23441. */
  23442. Node.prototype.unselect = function() {
  23443. this.selected = false;
  23444. this._reset();
  23445. };
  23446. /**
  23447. * Reset the calculated size of the node, forces it to recalculate its size
  23448. */
  23449. Node.prototype.clearSizeCache = function() {
  23450. this._reset();
  23451. };
  23452. /**
  23453. * Reset the calculated size of the node, forces it to recalculate its size
  23454. * @private
  23455. */
  23456. Node.prototype._reset = function() {
  23457. this.width = undefined;
  23458. this.height = undefined;
  23459. };
  23460. /**
  23461. * get the title of this node.
  23462. * @return {string} title The title of the node, or undefined when no title
  23463. * has been set.
  23464. */
  23465. Node.prototype.getTitle = function() {
  23466. return typeof this.title === "function" ? this.title() : this.title;
  23467. };
  23468. /**
  23469. * Calculate the distance to the border of the Node
  23470. * @param {CanvasRenderingContext2D} ctx
  23471. * @param {Number} angle Angle in radians
  23472. * @returns {number} distance Distance to the border in pixels
  23473. */
  23474. Node.prototype.distanceToBorder = function (ctx, angle) {
  23475. var borderWidth = 1;
  23476. if (!this.width) {
  23477. this.resize(ctx);
  23478. }
  23479. switch (this.options.shape) {
  23480. case 'circle':
  23481. case 'dot':
  23482. return this.options.radius+ borderWidth;
  23483. case 'ellipse':
  23484. var a = this.width / 2;
  23485. var b = this.height / 2;
  23486. var w = (Math.sin(angle) * a);
  23487. var h = (Math.cos(angle) * b);
  23488. return a * b / Math.sqrt(w * w + h * h);
  23489. // TODO: implement distanceToBorder for database
  23490. // TODO: implement distanceToBorder for triangle
  23491. // TODO: implement distanceToBorder for triangleDown
  23492. case 'box':
  23493. case 'image':
  23494. case 'text':
  23495. default:
  23496. if (this.width) {
  23497. return Math.min(
  23498. Math.abs(this.width / 2 / Math.cos(angle)),
  23499. Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth;
  23500. // TODO: reckon with border radius too in case of box
  23501. }
  23502. else {
  23503. return 0;
  23504. }
  23505. }
  23506. // TODO: implement calculation of distance to border for all shapes
  23507. };
  23508. /**
  23509. * Set forces acting on the node
  23510. * @param {number} fx Force in horizontal direction
  23511. * @param {number} fy Force in vertical direction
  23512. */
  23513. Node.prototype._setForce = function(fx, fy) {
  23514. this.fx = fx;
  23515. this.fy = fy;
  23516. };
  23517. /**
  23518. * Add forces acting on the node
  23519. * @param {number} fx Force in horizontal direction
  23520. * @param {number} fy Force in vertical direction
  23521. * @private
  23522. */
  23523. Node.prototype._addForce = function(fx, fy) {
  23524. this.fx += fx;
  23525. this.fy += fy;
  23526. };
  23527. /**
  23528. * Store the state before the next step
  23529. */
  23530. Node.prototype.storeState = function() {
  23531. this.previousState.x = this.x;
  23532. this.previousState.y = this.y;
  23533. this.previousState.vx = this.vx;
  23534. this.previousState.vy = this.vy;
  23535. }
  23536. /**
  23537. * Perform one discrete step for the node
  23538. * @param {number} interval Time interval in seconds
  23539. */
  23540. Node.prototype.discreteStep = function(interval) {
  23541. this.storeState();
  23542. if (!this.xFixed) {
  23543. var dx = this.damping * this.vx; // damping force
  23544. var ax = (this.fx - dx) / this.options.mass; // acceleration
  23545. this.vx += ax * interval; // velocity
  23546. this.x += this.vx * interval; // position
  23547. }
  23548. else {
  23549. this.fx = 0;
  23550. this.vx = 0;
  23551. }
  23552. if (!this.yFixed) {
  23553. var dy = this.damping * this.vy; // damping force
  23554. var ay = (this.fy - dy) / this.options.mass; // acceleration
  23555. this.vy += ay * interval; // velocity
  23556. this.y += this.vy * interval; // position
  23557. }
  23558. else {
  23559. this.fy = 0;
  23560. this.vy = 0;
  23561. }
  23562. };
  23563. /**
  23564. * Perform one discrete step for the node
  23565. * @param {number} interval Time interval in seconds
  23566. * @param {number} maxVelocity The speed limit imposed on the velocity
  23567. */
  23568. Node.prototype.discreteStepLimited = function(interval, maxVelocity) {
  23569. this.storeState();
  23570. if (!this.xFixed) {
  23571. var dx = this.damping * this.vx; // damping force
  23572. var ax = (this.fx - dx) / this.options.mass; // acceleration
  23573. this.vx += ax * interval; // velocity
  23574. this.vx = (Math.abs(this.vx) > maxVelocity) ? ((this.vx > 0) ? maxVelocity : -maxVelocity) : this.vx;
  23575. this.x += this.vx * interval; // position
  23576. }
  23577. else {
  23578. this.fx = 0;
  23579. this.vx = 0;
  23580. }
  23581. if (!this.yFixed) {
  23582. var dy = this.damping * this.vy; // damping force
  23583. var ay = (this.fy - dy) / this.options.mass; // acceleration
  23584. this.vy += ay * interval; // velocity
  23585. this.vy = (Math.abs(this.vy) > maxVelocity) ? ((this.vy > 0) ? maxVelocity : -maxVelocity) : this.vy;
  23586. this.y += this.vy * interval; // position
  23587. }
  23588. else {
  23589. this.fy = 0;
  23590. this.vy = 0;
  23591. }
  23592. };
  23593. /**
  23594. * Check if this node has a fixed x and y position
  23595. * @return {boolean} true if fixed, false if not
  23596. */
  23597. Node.prototype.isFixed = function() {
  23598. return (this.xFixed && this.yFixed);
  23599. };
  23600. /**
  23601. * Check if this node is moving
  23602. * @param {number} vmin the minimum velocity considered as "moving"
  23603. * @return {boolean} true if moving, false if it has no velocity
  23604. */
  23605. Node.prototype.isMoving = function(vmin) {
  23606. var velocity = Math.sqrt(Math.pow(this.vx,2) + Math.pow(this.vy,2));
  23607. // this.velocity = Math.sqrt(Math.pow(this.vx,2) + Math.pow(this.vy,2))
  23608. return (velocity > vmin);
  23609. };
  23610. /**
  23611. * check if this node is selecte
  23612. * @return {boolean} selected True if node is selected, else false
  23613. */
  23614. Node.prototype.isSelected = function() {
  23615. return this.selected;
  23616. };
  23617. /**
  23618. * Retrieve the value of the node. Can be undefined
  23619. * @return {Number} value
  23620. */
  23621. Node.prototype.getValue = function() {
  23622. return this.value;
  23623. };
  23624. /**
  23625. * Calculate the distance from the nodes location to the given location (x,y)
  23626. * @param {Number} x
  23627. * @param {Number} y
  23628. * @return {Number} value
  23629. */
  23630. Node.prototype.getDistance = function(x, y) {
  23631. var dx = this.x - x,
  23632. dy = this.y - y;
  23633. return Math.sqrt(dx * dx + dy * dy);
  23634. };
  23635. /**
  23636. * Adjust the value range of the node. The node will adjust it's radius
  23637. * based on its value.
  23638. * @param {Number} min
  23639. * @param {Number} max
  23640. */
  23641. Node.prototype.setValueRange = function(min, max, total) {
  23642. if (!this.radiusFixed && this.value !== undefined) {
  23643. var scale = this.options.customScalingFunction(min, max, total, this.value);
  23644. var radiusDiff = this.options.radiusMax - this.options.radiusMin;
  23645. if (this.options.scaleFontWithValue == true) {
  23646. var fontDiff = this.options.fontSizeMax - this.options.fontSizeMin;
  23647. this.options.fontSize = this.options.fontSizeMin + scale * fontDiff;
  23648. }
  23649. this.options.radius = this.options.radiusMin + scale * radiusDiff;
  23650. }
  23651. this.baseRadiusValue = this.options.radius;
  23652. };
  23653. /**
  23654. * Draw this node in the given canvas
  23655. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  23656. * @param {CanvasRenderingContext2D} ctx
  23657. */
  23658. Node.prototype.draw = function(ctx) {
  23659. throw "Draw method not initialized for node";
  23660. };
  23661. /**
  23662. * Recalculate the size of this node in the given canvas
  23663. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  23664. * @param {CanvasRenderingContext2D} ctx
  23665. */
  23666. Node.prototype.resize = function(ctx) {
  23667. throw "Resize method not initialized for node";
  23668. };
  23669. /**
  23670. * Check if this object is overlapping with the provided object
  23671. * @param {Object} obj an object with parameters left, top, right, bottom
  23672. * @return {boolean} True if location is located on node
  23673. */
  23674. Node.prototype.isOverlappingWith = function(obj) {
  23675. return (this.left < obj.right &&
  23676. this.left + this.width > obj.left &&
  23677. this.top < obj.bottom &&
  23678. this.top + this.height > obj.top);
  23679. };
  23680. Node.prototype._resizeImage = function (ctx) {
  23681. // TODO: pre calculate the image size
  23682. if (!this.width || !this.height) { // undefined or 0
  23683. var width, height;
  23684. if (this.value) {
  23685. this.options.radius= this.baseRadiusValue;
  23686. var scale = this.imageObj.height / this.imageObj.width;
  23687. if (scale !== undefined) {
  23688. width = this.options.radius|| this.imageObj.width;
  23689. height = this.options.radius* scale || this.imageObj.height;
  23690. }
  23691. else {
  23692. width = 0;
  23693. height = 0;
  23694. }
  23695. }
  23696. else {
  23697. width = this.imageObj.width;
  23698. height = this.imageObj.height;
  23699. }
  23700. this.width = width;
  23701. this.height = height;
  23702. this.growthIndicator = 0;
  23703. if (this.width > 0 && this.height > 0) {
  23704. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  23705. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  23706. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  23707. this.growthIndicator = this.width - width;
  23708. }
  23709. }
  23710. };
  23711. Node.prototype._drawImageAtPosition = function (ctx) {
  23712. if (this.imageObj.width != 0 ) {
  23713. // draw the shade
  23714. if (this.clusterSize > 1) {
  23715. var lineWidth = ((this.clusterSize > 1) ? 10 : 0.0);
  23716. lineWidth *= this.networkScaleInv;
  23717. lineWidth = Math.min(0.2 * this.width,lineWidth);
  23718. ctx.globalAlpha = 0.5;
  23719. ctx.drawImage(this.imageObj, this.left - lineWidth, this.top - lineWidth, this.width + 2*lineWidth, this.height + 2*lineWidth);
  23720. }
  23721. // draw the image
  23722. ctx.globalAlpha = 1.0;
  23723. ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height);
  23724. }
  23725. };
  23726. Node.prototype._drawImageLabel = function (ctx) {
  23727. var yLabel;
  23728. var offset = 0;
  23729. if (this.height){
  23730. offset = this.height / 2;
  23731. var labelDimensions = this.getTextSize(ctx);
  23732. if (labelDimensions.lineCount >= 1){
  23733. offset += labelDimensions.height / 2;
  23734. offset += 3;
  23735. }
  23736. }
  23737. yLabel = this.y + offset;
  23738. this._label(ctx, this.label, this.x, yLabel, undefined);
  23739. };
  23740. Node.prototype._drawImage = function (ctx) {
  23741. this._resizeImage(ctx);
  23742. this.left = this.x - this.width / 2;
  23743. this.top = this.y - this.height / 2;
  23744. this._drawImageAtPosition(ctx);
  23745. this.boundingBox.top = this.top;
  23746. this.boundingBox.left = this.left;
  23747. this.boundingBox.right = this.left + this.width;
  23748. this.boundingBox.bottom = this.top + this.height;
  23749. this._drawImageLabel(ctx);
  23750. this.boundingBox.left = Math.min(this.boundingBox.left, this.labelDimensions.left);
  23751. this.boundingBox.right = Math.max(this.boundingBox.right, this.labelDimensions.left + this.labelDimensions.width);
  23752. this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelDimensions.height);
  23753. };
  23754. Node.prototype._resizeCircularImage = function (ctx) {
  23755. if(!this.imageObj.src || !this.imageObj.width || !this.imageObj.height){
  23756. if (!this.width) {
  23757. var diameter = this.options.radius * 2;
  23758. this.width = diameter;
  23759. this.height = diameter;
  23760. // scaling used for clustering
  23761. //this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  23762. //this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  23763. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  23764. this.growthIndicator = this.options.radius- 0.5*diameter;
  23765. this._swapToImageResizeWhenImageLoaded = true;
  23766. }
  23767. }
  23768. else {
  23769. if (this._swapToImageResizeWhenImageLoaded) {
  23770. this.width = 0;
  23771. this.height = 0;
  23772. delete this._swapToImageResizeWhenImageLoaded;
  23773. }
  23774. this._resizeImage(ctx);
  23775. }
  23776. };
  23777. Node.prototype._drawCircularImage = function (ctx) {
  23778. this._resizeCircularImage(ctx);
  23779. this.left = this.x - this.width / 2;
  23780. this.top = this.y - this.height / 2;
  23781. var centerX = this.left + (this.width / 2);
  23782. var centerY = this.top + (this.height / 2);
  23783. var radius = Math.abs(this.height / 2);
  23784. this._drawRawCircle(ctx, centerX, centerY, radius);
  23785. ctx.save();
  23786. ctx.circle(this.x, this.y, radius);
  23787. ctx.stroke();
  23788. ctx.clip();
  23789. this._drawImageAtPosition(ctx);
  23790. ctx.restore();
  23791. this.boundingBox.top = this.y - this.options.radius;
  23792. this.boundingBox.left = this.x - this.options.radius;
  23793. this.boundingBox.right = this.x + this.options.radius;
  23794. this.boundingBox.bottom = this.y + this.options.radius;
  23795. this._drawImageLabel(ctx);
  23796. this.boundingBox.left = Math.min(this.boundingBox.left, this.labelDimensions.left);
  23797. this.boundingBox.right = Math.max(this.boundingBox.right, this.labelDimensions.left + this.labelDimensions.width);
  23798. this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelDimensions.height);
  23799. };
  23800. Node.prototype._resizeBox = function (ctx) {
  23801. if (!this.width) {
  23802. var margin = 5;
  23803. var textSize = this.getTextSize(ctx);
  23804. this.width = textSize.width + 2 * margin;
  23805. this.height = textSize.height + 2 * margin;
  23806. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  23807. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  23808. this.growthIndicator = this.width - (textSize.width + 2 * margin);
  23809. // this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  23810. }
  23811. };
  23812. Node.prototype._drawBox = function (ctx) {
  23813. this._resizeBox(ctx);
  23814. this.left = this.x - this.width / 2;
  23815. this.top = this.y - this.height / 2;
  23816. var clusterLineWidth = 2.5;
  23817. var borderWidth = this.options.borderWidth;
  23818. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  23819. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  23820. // draw the outer border
  23821. if (this.clusterSize > 1) {
  23822. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  23823. ctx.lineWidth *= this.networkScaleInv;
  23824. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  23825. 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);
  23826. ctx.stroke();
  23827. }
  23828. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  23829. ctx.lineWidth *= this.networkScaleInv;
  23830. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  23831. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  23832. ctx.roundRect(this.left, this.top, this.width, this.height, this.options.radius);
  23833. ctx.fill();
  23834. ctx.stroke();
  23835. this.boundingBox.top = this.top;
  23836. this.boundingBox.left = this.left;
  23837. this.boundingBox.right = this.left + this.width;
  23838. this.boundingBox.bottom = this.top + this.height;
  23839. this._label(ctx, this.label, this.x, this.y);
  23840. };
  23841. Node.prototype._resizeDatabase = function (ctx) {
  23842. if (!this.width) {
  23843. var margin = 5;
  23844. var textSize = this.getTextSize(ctx);
  23845. var size = textSize.width + 2 * margin;
  23846. this.width = size;
  23847. this.height = size;
  23848. // scaling used for clustering
  23849. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  23850. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  23851. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  23852. this.growthIndicator = this.width - size;
  23853. }
  23854. };
  23855. Node.prototype._drawDatabase = function (ctx) {
  23856. this._resizeDatabase(ctx);
  23857. this.left = this.x - this.width / 2;
  23858. this.top = this.y - this.height / 2;
  23859. var clusterLineWidth = 2.5;
  23860. var borderWidth = this.options.borderWidth;
  23861. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  23862. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  23863. // draw the outer border
  23864. if (this.clusterSize > 1) {
  23865. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  23866. ctx.lineWidth *= this.networkScaleInv;
  23867. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  23868. 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);
  23869. ctx.stroke();
  23870. }
  23871. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  23872. ctx.lineWidth *= this.networkScaleInv;
  23873. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  23874. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  23875. ctx.database(this.x - this.width/2, this.y - this.height*0.5, this.width, this.height);
  23876. ctx.fill();
  23877. ctx.stroke();
  23878. this.boundingBox.top = this.top;
  23879. this.boundingBox.left = this.left;
  23880. this.boundingBox.right = this.left + this.width;
  23881. this.boundingBox.bottom = this.top + this.height;
  23882. this._label(ctx, this.label, this.x, this.y);
  23883. };
  23884. Node.prototype._resizeCircle = function (ctx) {
  23885. if (!this.width) {
  23886. var margin = 5;
  23887. var textSize = this.getTextSize(ctx);
  23888. var diameter = Math.max(textSize.width, textSize.height) + 2 * margin;
  23889. this.options.radius = diameter / 2;
  23890. this.width = diameter;
  23891. this.height = diameter;
  23892. // scaling used for clustering
  23893. // this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  23894. // this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  23895. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  23896. this.growthIndicator = this.options.radius- 0.5*diameter;
  23897. }
  23898. };
  23899. Node.prototype._drawRawCircle = function (ctx, x, y, radius) {
  23900. var clusterLineWidth = 2.5;
  23901. var borderWidth = this.options.borderWidth;
  23902. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  23903. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  23904. // draw the outer border
  23905. if (this.clusterSize > 1) {
  23906. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  23907. ctx.lineWidth *= this.networkScaleInv;
  23908. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  23909. ctx.circle(x, y, radius+2*ctx.lineWidth);
  23910. ctx.stroke();
  23911. }
  23912. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  23913. ctx.lineWidth *= this.networkScaleInv;
  23914. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  23915. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  23916. ctx.circle(this.x, this.y, radius);
  23917. ctx.fill();
  23918. ctx.stroke();
  23919. };
  23920. Node.prototype._drawCircle = function (ctx) {
  23921. this._resizeCircle(ctx);
  23922. this.left = this.x - this.width / 2;
  23923. this.top = this.y - this.height / 2;
  23924. this._drawRawCircle(ctx, this.x, this.y, this.options.radius);
  23925. this.boundingBox.top = this.y - this.options.radius;
  23926. this.boundingBox.left = this.x - this.options.radius;
  23927. this.boundingBox.right = this.x + this.options.radius;
  23928. this.boundingBox.bottom = this.y + this.options.radius;
  23929. this._label(ctx, this.label, this.x, this.y);
  23930. };
  23931. Node.prototype._resizeEllipse = function (ctx) {
  23932. if (!this.width) {
  23933. var textSize = this.getTextSize(ctx);
  23934. this.width = textSize.width * 1.5;
  23935. this.height = textSize.height * 2;
  23936. if (this.width < this.height) {
  23937. this.width = this.height;
  23938. }
  23939. var defaultSize = this.width;
  23940. // scaling used for clustering
  23941. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  23942. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  23943. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  23944. this.growthIndicator = this.width - defaultSize;
  23945. }
  23946. };
  23947. Node.prototype._drawEllipse = function (ctx) {
  23948. this._resizeEllipse(ctx);
  23949. this.left = this.x - this.width / 2;
  23950. this.top = this.y - this.height / 2;
  23951. var clusterLineWidth = 2.5;
  23952. var borderWidth = this.options.borderWidth;
  23953. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  23954. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  23955. // draw the outer border
  23956. if (this.clusterSize > 1) {
  23957. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  23958. ctx.lineWidth *= this.networkScaleInv;
  23959. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  23960. ctx.ellipse(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth);
  23961. ctx.stroke();
  23962. }
  23963. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  23964. ctx.lineWidth *= this.networkScaleInv;
  23965. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  23966. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  23967. ctx.ellipse(this.left, this.top, this.width, this.height);
  23968. ctx.fill();
  23969. ctx.stroke();
  23970. this.boundingBox.top = this.top;
  23971. this.boundingBox.left = this.left;
  23972. this.boundingBox.right = this.left + this.width;
  23973. this.boundingBox.bottom = this.top + this.height;
  23974. this._label(ctx, this.label, this.x, this.y);
  23975. };
  23976. Node.prototype._drawDot = function (ctx) {
  23977. this._drawShape(ctx, 'circle');
  23978. };
  23979. Node.prototype._drawTriangle = function (ctx) {
  23980. this._drawShape(ctx, 'triangle');
  23981. };
  23982. Node.prototype._drawTriangleDown = function (ctx) {
  23983. this._drawShape(ctx, 'triangleDown');
  23984. };
  23985. Node.prototype._drawSquare = function (ctx) {
  23986. this._drawShape(ctx, 'square');
  23987. };
  23988. Node.prototype._drawStar = function (ctx) {
  23989. this._drawShape(ctx, 'star');
  23990. };
  23991. Node.prototype._resizeShape = function (ctx) {
  23992. if (!this.width) {
  23993. this.options.radius= this.baseRadiusValue;
  23994. var size = 2 * this.options.radius;
  23995. this.width = size;
  23996. this.height = size;
  23997. // scaling used for clustering
  23998. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  23999. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  24000. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  24001. this.growthIndicator = this.width - size;
  24002. }
  24003. };
  24004. Node.prototype._drawShape = function (ctx, shape) {
  24005. this._resizeShape(ctx);
  24006. this.left = this.x - this.width / 2;
  24007. this.top = this.y - this.height / 2;
  24008. var clusterLineWidth = 2.5;
  24009. var borderWidth = this.options.borderWidth;
  24010. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  24011. var radiusMultiplier = 2;
  24012. // choose draw method depending on the shape
  24013. switch (shape) {
  24014. case 'dot': radiusMultiplier = 2; break;
  24015. case 'square': radiusMultiplier = 2; break;
  24016. case 'triangle': radiusMultiplier = 3; break;
  24017. case 'triangleDown': radiusMultiplier = 3; break;
  24018. case 'star': radiusMultiplier = 4; break;
  24019. }
  24020. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  24021. // draw the outer border
  24022. if (this.clusterSize > 1) {
  24023. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  24024. ctx.lineWidth *= this.networkScaleInv;
  24025. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  24026. ctx[shape](this.x, this.y, this.options.radius+ radiusMultiplier * ctx.lineWidth);
  24027. ctx.stroke();
  24028. }
  24029. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  24030. ctx.lineWidth *= this.networkScaleInv;
  24031. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  24032. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  24033. ctx[shape](this.x, this.y, this.options.radius);
  24034. ctx.fill();
  24035. ctx.stroke();
  24036. this.boundingBox.top = this.y - this.options.radius;
  24037. this.boundingBox.left = this.x - this.options.radius;
  24038. this.boundingBox.right = this.x + this.options.radius;
  24039. this.boundingBox.bottom = this.y + this.options.radius;
  24040. if (this.label) {
  24041. this._label(ctx, this.label, this.x, this.y + this.height / 2, undefined, 'hanging',true);
  24042. this.boundingBox.left = Math.min(this.boundingBox.left, this.labelDimensions.left);
  24043. this.boundingBox.right = Math.max(this.boundingBox.right, this.labelDimensions.left + this.labelDimensions.width);
  24044. this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelDimensions.height);
  24045. }
  24046. };
  24047. Node.prototype._resizeText = function (ctx) {
  24048. if (!this.width) {
  24049. var margin = 5;
  24050. var textSize = this.getTextSize(ctx);
  24051. this.width = textSize.width + 2 * margin;
  24052. this.height = textSize.height + 2 * margin;
  24053. // scaling used for clustering
  24054. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  24055. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  24056. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  24057. this.growthIndicator = this.width - (textSize.width + 2 * margin);
  24058. }
  24059. };
  24060. Node.prototype._drawText = function (ctx) {
  24061. this._resizeText(ctx);
  24062. this.left = this.x - this.width / 2;
  24063. this.top = this.y - this.height / 2;
  24064. this._label(ctx, this.label, this.x, this.y);
  24065. this.boundingBox.top = this.top;
  24066. this.boundingBox.left = this.left;
  24067. this.boundingBox.right = this.left + this.width;
  24068. this.boundingBox.bottom = this.top + this.height;
  24069. };
  24070. Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNode) {
  24071. var relativeFontSize = Number(this.options.fontSize) * this.networkScale;
  24072. if (text && relativeFontSize >= this.options.fontDrawThreshold - 1) {
  24073. var fontSize = Number(this.options.fontSize);
  24074. // this ensures that there will not be HUGE letters on screen by setting an upper limit on the visible text size (regardless of zoomLevel)
  24075. if (relativeFontSize >= this.options.fontSizeMaxVisible) {
  24076. fontSize = Number(this.options.fontSizeMaxVisible) * this.networkScaleInv;
  24077. }
  24078. // fade in when relative scale is between threshold and threshold - 1
  24079. var fontColor = this.options.fontColor || "#000000";
  24080. var strokecolor = this.options.fontStrokeColor;
  24081. if (relativeFontSize <= this.options.fontDrawThreshold) {
  24082. var opacity = Math.max(0,Math.min(1,1 - (this.options.fontDrawThreshold - relativeFontSize)));
  24083. fontColor = util.overrideOpacity(fontColor, opacity);
  24084. strokecolor = util.overrideOpacity(strokecolor, opacity);
  24085. }
  24086. ctx.font = (this.selected ? "bold " : "") + fontSize + "px " + this.options.fontFace;
  24087. var lines = text.split('\n');
  24088. var lineCount = lines.length;
  24089. var yLine = y + (1 - lineCount) / 2 * fontSize;
  24090. if (labelUnderNode == true) {
  24091. yLine = y + (1 - lineCount) / (2 * fontSize);
  24092. }
  24093. // font fill from edges now for nodes!
  24094. var width = ctx.measureText(lines[0]).width;
  24095. for (var i = 1; i < lineCount; i++) {
  24096. var lineWidth = ctx.measureText(lines[i]).width;
  24097. width = lineWidth > width ? lineWidth : width;
  24098. }
  24099. var height = fontSize * lineCount;
  24100. var left = x - width / 2;
  24101. var top = y - height / 2;
  24102. if (baseline == "hanging") {
  24103. top += 0.5 * fontSize;
  24104. top += 4; // distance from node, required because we use hanging. Hanging has less difference between browsers
  24105. yLine += 4; // distance from node
  24106. }
  24107. this.labelDimensions = {top:top,left:left,width:width,height:height,yLine:yLine};
  24108. // create the fontfill background
  24109. if (this.options.fontFill !== undefined && this.options.fontFill !== null && this.options.fontFill !== "none") {
  24110. ctx.fillStyle = this.options.fontFill;
  24111. ctx.fillRect(left, top, width, height);
  24112. }
  24113. // draw text
  24114. ctx.fillStyle = fontColor;
  24115. ctx.textAlign = align || "center";
  24116. ctx.textBaseline = baseline || "middle";
  24117. if (this.options.fontStrokeWidth > 0){
  24118. ctx.lineWidth = this.options.fontStrokeWidth;
  24119. ctx.strokeStyle = strokecolor;
  24120. ctx.lineJoin = 'round';
  24121. }
  24122. for (var i = 0; i < lineCount; i++) {
  24123. if(this.options.fontStrokeWidth){
  24124. ctx.strokeText(lines[i], x, yLine);
  24125. }
  24126. ctx.fillText(lines[i], x, yLine);
  24127. yLine += fontSize;
  24128. }
  24129. }
  24130. };
  24131. Node.prototype.getTextSize = function(ctx) {
  24132. if (this.label !== undefined) {
  24133. var fontSize = Number(this.options.fontSize);
  24134. if (fontSize * this.networkScale > this.options.fontSizeMaxVisible) {
  24135. fontSize = Number(this.options.fontSizeMaxVisible) * this.networkScaleInv;
  24136. }
  24137. ctx.font = (this.selected ? "bold " : "") + fontSize + "px " + this.options.fontFace;
  24138. var lines = this.label.split('\n'),
  24139. height = (fontSize + 4) * lines.length,
  24140. width = 0;
  24141. for (var i = 0, iMax = lines.length; i < iMax; i++) {
  24142. width = Math.max(width, ctx.measureText(lines[i]).width);
  24143. }
  24144. return {"width": width, "height": height, lineCount: lines.length};
  24145. }
  24146. else {
  24147. return {"width": 0, "height": 0, lineCount: 0};
  24148. }
  24149. };
  24150. /**
  24151. * this is used to determine if a node is visible at all. this is used to determine when it needs to be drawn.
  24152. * there is a safety margin of 0.3 * width;
  24153. *
  24154. * @returns {boolean}
  24155. */
  24156. Node.prototype.inArea = function() {
  24157. if (this.width !== undefined) {
  24158. return (this.x + this.width *this.networkScaleInv >= this.canvasTopLeft.x &&
  24159. this.x - this.width *this.networkScaleInv < this.canvasBottomRight.x &&
  24160. this.y + this.height*this.networkScaleInv >= this.canvasTopLeft.y &&
  24161. this.y - this.height*this.networkScaleInv < this.canvasBottomRight.y);
  24162. }
  24163. else {
  24164. return true;
  24165. }
  24166. };
  24167. /**
  24168. * checks if the core of the node is in the display area, this is used for opening clusters around zoom
  24169. * @returns {boolean}
  24170. */
  24171. Node.prototype.inView = function() {
  24172. return (this.x >= this.canvasTopLeft.x &&
  24173. this.x < this.canvasBottomRight.x &&
  24174. this.y >= this.canvasTopLeft.y &&
  24175. this.y < this.canvasBottomRight.y);
  24176. };
  24177. /**
  24178. * This allows the zoom level of the network to influence the rendering
  24179. * We store the inverted scale and the coordinates of the top left, and bottom right points of the canvas
  24180. *
  24181. * @param scale
  24182. * @param canvasTopLeft
  24183. * @param canvasBottomRight
  24184. */
  24185. Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight) {
  24186. this.networkScaleInv = 1.0/scale;
  24187. this.networkScale = scale;
  24188. this.canvasTopLeft = canvasTopLeft;
  24189. this.canvasBottomRight = canvasBottomRight;
  24190. };
  24191. /**
  24192. * This allows the zoom level of the network to influence the rendering
  24193. *
  24194. * @param scale
  24195. */
  24196. Node.prototype.setScale = function(scale) {
  24197. this.networkScaleInv = 1.0/scale;
  24198. this.networkScale = scale;
  24199. };
  24200. /**
  24201. * set the velocity at 0. Is called when this node is contained in another during clustering
  24202. */
  24203. Node.prototype.clearVelocity = function() {
  24204. this.vx = 0;
  24205. this.vy = 0;
  24206. };
  24207. /**
  24208. * Basic preservation of (kinectic) energy
  24209. *
  24210. * @param massBeforeClustering
  24211. */
  24212. Node.prototype.updateVelocity = function(massBeforeClustering) {
  24213. var energyBefore = this.vx * this.vx * massBeforeClustering;
  24214. //this.vx = (this.vx < 0) ? -Math.sqrt(energyBefore/this.options.mass) : Math.sqrt(energyBefore/this.options.mass);
  24215. this.vx = Math.sqrt(energyBefore/this.options.mass);
  24216. energyBefore = this.vy * this.vy * massBeforeClustering;
  24217. //this.vy = (this.vy < 0) ? -Math.sqrt(energyBefore/this.options.mass) : Math.sqrt(energyBefore/this.options.mass);
  24218. this.vy = Math.sqrt(energyBefore/this.options.mass);
  24219. };
  24220. module.exports = Node;
  24221. /***/ },
  24222. /* 57 */
  24223. /***/ function(module, exports, __webpack_require__) {
  24224. var util = __webpack_require__(1);
  24225. var Node = __webpack_require__(56);
  24226. /**
  24227. * @class Edge
  24228. *
  24229. * A edge connects two nodes
  24230. * @param {Object} properties Object with properties. Must contain
  24231. * At least properties from and to.
  24232. * Available properties: from (number),
  24233. * to (number), label (string, color (string),
  24234. * width (number), style (string),
  24235. * length (number), title (string)
  24236. * @param {Network} network A Network object, used to find and edge to
  24237. * nodes.
  24238. * @param {Object} constants An object with default values for
  24239. * example for the color
  24240. */
  24241. function Edge (properties, network, networkConstants) {
  24242. if (!network) {
  24243. throw "No network provided";
  24244. }
  24245. var fields = ['edges','physics'];
  24246. var constants = util.selectiveBridgeObject(fields,networkConstants);
  24247. this.options = constants.edges;
  24248. this.physics = constants.physics;
  24249. this.options['smoothCurves'] = networkConstants['smoothCurves'];
  24250. this.network = network;
  24251. // initialize variables
  24252. this.id = undefined;
  24253. this.fromId = undefined;
  24254. this.toId = undefined;
  24255. this.title = undefined;
  24256. this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
  24257. this.value = undefined;
  24258. this.selected = false;
  24259. this.hover = false;
  24260. this.labelDimensions = {top:0,left:0,width:0,height:0,yLine:0}; // could be cached
  24261. this.dirtyLabel = true;
  24262. this.colorDirty = true;
  24263. this.from = null; // a node
  24264. this.to = null; // a node
  24265. this.via = null; // a temp node
  24266. this.fromBackup = null; // used to clean up after reconnect
  24267. this.toBackup = null;; // used to clean up after reconnect
  24268. // we use this to be able to reconnect the edge to a cluster if its node is put into a cluster
  24269. // by storing the original information we can revert to the original connection when the cluser is opened.
  24270. this.originalFromId = [];
  24271. this.originalToId = [];
  24272. this.connected = false;
  24273. this.widthFixed = false;
  24274. this.lengthFixed = false;
  24275. this.setProperties(properties);
  24276. this.controlNodesEnabled = false;
  24277. this.controlNodes = {from:null, to:null, positions:{}};
  24278. this.connectedNode = null;
  24279. }
  24280. /**
  24281. * Set or overwrite properties for the edge
  24282. * @param {Object} properties an object with properties
  24283. * @param {Object} constants and object with default, global properties
  24284. */
  24285. Edge.prototype.setProperties = function(properties) {
  24286. this.colorDirty = true;
  24287. if (!properties) {
  24288. return;
  24289. }
  24290. var fields = ['style','fontSize','fontFace','fontColor','fontFill','fontStrokeWidth','fontStrokeColor','width',
  24291. 'widthSelectionMultiplier','hoverWidth','arrowScaleFactor','dash','inheritColor','labelAlignment', 'opacity',
  24292. 'customScalingFunction'
  24293. ];
  24294. util.selectiveDeepExtend(fields, this.options, properties);
  24295. if (properties.from !== undefined) {this.fromId = properties.from;}
  24296. if (properties.to !== undefined) {this.toId = properties.to;}
  24297. if (properties.id !== undefined) {this.id = properties.id;}
  24298. if (properties.label !== undefined) {this.label = properties.label; this.dirtyLabel = true;}
  24299. if (properties.title !== undefined) {this.title = properties.title;}
  24300. if (properties.value !== undefined) {this.value = properties.value;}
  24301. if (properties.length !== undefined) {this.physics.springLength = properties.length;}
  24302. if (properties.color !== undefined) {
  24303. this.options.inheritColor = false;
  24304. if (util.isString(properties.color)) {
  24305. this.options.color.color = properties.color;
  24306. this.options.color.highlight = properties.color;
  24307. }
  24308. else {
  24309. if (properties.color.color !== undefined) {this.options.color.color = properties.color.color;}
  24310. if (properties.color.highlight !== undefined) {this.options.color.highlight = properties.color.highlight;}
  24311. if (properties.color.hover !== undefined) {this.options.color.hover = properties.color.hover;}
  24312. }
  24313. }
  24314. // A node is connected when it has a from and to node.
  24315. this.connect();
  24316. this.widthFixed = this.widthFixed || (properties.width !== undefined);
  24317. this.lengthFixed = this.lengthFixed || (properties.length !== undefined);
  24318. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  24319. // set draw method based on style
  24320. switch (this.options.style) {
  24321. case 'line': this.draw = this._drawLine; break;
  24322. case 'arrow': this.draw = this._drawArrow; break;
  24323. case 'arrow-center': this.draw = this._drawArrowCenter; break;
  24324. case 'dash-line': this.draw = this._drawDashLine; break;
  24325. default: this.draw = this._drawLine; break;
  24326. }
  24327. };
  24328. /**
  24329. * Connect an edge to its nodes
  24330. */
  24331. Edge.prototype.connect = function () {
  24332. this.disconnect();
  24333. this.from = this.network.nodes[this.fromId] || null;
  24334. this.to = this.network.nodes[this.toId] || null;
  24335. this.connected = (this.from && this.to);
  24336. if (this.connected) {
  24337. this.from.attachEdge(this);
  24338. this.to.attachEdge(this);
  24339. }
  24340. else {
  24341. if (this.from) {
  24342. this.from.detachEdge(this);
  24343. }
  24344. if (this.to) {
  24345. this.to.detachEdge(this);
  24346. }
  24347. }
  24348. };
  24349. /**
  24350. * Disconnect an edge from its nodes
  24351. */
  24352. Edge.prototype.disconnect = function () {
  24353. if (this.from) {
  24354. this.from.detachEdge(this);
  24355. this.from = null;
  24356. }
  24357. if (this.to) {
  24358. this.to.detachEdge(this);
  24359. this.to = null;
  24360. }
  24361. this.connected = false;
  24362. };
  24363. /**
  24364. * get the title of this edge.
  24365. * @return {string} title The title of the edge, or undefined when no title
  24366. * has been set.
  24367. */
  24368. Edge.prototype.getTitle = function() {
  24369. return typeof this.title === "function" ? this.title() : this.title;
  24370. };
  24371. /**
  24372. * Retrieve the value of the edge. Can be undefined
  24373. * @return {Number} value
  24374. */
  24375. Edge.prototype.getValue = function() {
  24376. return this.value;
  24377. };
  24378. /**
  24379. * Adjust the value range of the edge. The edge will adjust it's width
  24380. * based on its value.
  24381. * @param {Number} min
  24382. * @param {Number} max
  24383. */
  24384. Edge.prototype.setValueRange = function(min, max, total) {
  24385. if (!this.widthFixed && this.value !== undefined) {
  24386. var scale = this.options.customScalingFunction(min, max, total, this.value);
  24387. var widthDiff = this.options.widthMax - this.options.widthMin;
  24388. this.options.width = this.options.widthMin + scale * widthDiff;
  24389. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  24390. }
  24391. };
  24392. /**
  24393. * Redraw a edge
  24394. * Draw this edge in the given canvas
  24395. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  24396. * @param {CanvasRenderingContext2D} ctx
  24397. */
  24398. Edge.prototype.draw = function(ctx) {
  24399. throw "Method draw not initialized in edge";
  24400. };
  24401. /**
  24402. * Check if this object is overlapping with the provided object
  24403. * @param {Object} obj an object with parameters left, top
  24404. * @return {boolean} True if location is located on the edge
  24405. */
  24406. Edge.prototype.isOverlappingWith = function(obj) {
  24407. if (this.connected) {
  24408. var distMax = 10;
  24409. var xFrom = this.from.x;
  24410. var yFrom = this.from.y;
  24411. var xTo = this.to.x;
  24412. var yTo = this.to.y;
  24413. var xObj = obj.left;
  24414. var yObj = obj.top;
  24415. var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  24416. return (dist < distMax);
  24417. }
  24418. else {
  24419. return false
  24420. }
  24421. };
  24422. Edge.prototype._getColor = function() {
  24423. var colorObj = this.options.color;
  24424. if (this.colorDirty === true) {
  24425. if (this.options.inheritColor == "to") {
  24426. colorObj = {
  24427. highlight: this.to.options.color.highlight.border,
  24428. hover: this.to.options.color.hover.border,
  24429. color: util.overrideOpacity(this.from.options.color.border, this.options.opacity)
  24430. };
  24431. }
  24432. else if (this.options.inheritColor == "from" || this.options.inheritColor == true) {
  24433. colorObj = {
  24434. highlight: this.from.options.color.highlight.border,
  24435. hover: this.from.options.color.hover.border,
  24436. color: util.overrideOpacity(this.from.options.color.border, this.options.opacity)
  24437. };
  24438. }
  24439. this.options.color = colorObj;
  24440. this.colorDirty = false;
  24441. }
  24442. if (this.selected == true) {return colorObj.highlight;}
  24443. else if (this.hover == true) {return colorObj.hover;}
  24444. else {return colorObj.color;}
  24445. };
  24446. /**
  24447. * Redraw a edge as a line
  24448. * Draw this edge in the given canvas
  24449. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  24450. * @param {CanvasRenderingContext2D} ctx
  24451. * @private
  24452. */
  24453. Edge.prototype._drawLine = function(ctx) {
  24454. // set style
  24455. ctx.strokeStyle = this._getColor();
  24456. ctx.lineWidth = this._getLineWidth();
  24457. if (this.from != this.to) {
  24458. // draw line
  24459. var via = this._line(ctx);
  24460. // draw label
  24461. var point;
  24462. if (this.label) {
  24463. if (this.options.smoothCurves.enabled == true && via != null) {
  24464. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  24465. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  24466. point = {x:midpointX, y:midpointY};
  24467. }
  24468. else {
  24469. point = this._pointOnLine(0.5);
  24470. }
  24471. this._label(ctx, this.label, point.x, point.y);
  24472. }
  24473. }
  24474. else {
  24475. var x, y;
  24476. var radius = this.physics.springLength / 4;
  24477. var node = this.from;
  24478. if (!node.width) {
  24479. node.resize(ctx);
  24480. }
  24481. if (node.width > node.height) {
  24482. x = node.x + node.width / 2;
  24483. y = node.y - radius;
  24484. }
  24485. else {
  24486. x = node.x + radius;
  24487. y = node.y - node.height / 2;
  24488. }
  24489. this._circle(ctx, x, y, radius);
  24490. point = this._pointOnCircle(x, y, radius, 0.5);
  24491. this._label(ctx, this.label, point.x, point.y);
  24492. }
  24493. };
  24494. /**
  24495. * Get the line width of the edge. Depends on width and whether one of the
  24496. * connected nodes is selected.
  24497. * @return {Number} width
  24498. * @private
  24499. */
  24500. Edge.prototype._getLineWidth = function() {
  24501. if (this.selected == true) {
  24502. return Math.max(Math.min(this.widthSelected, this.options.widthMax), 0.3*this.networkScaleInv);
  24503. }
  24504. else {
  24505. if (this.hover == true) {
  24506. return Math.max(Math.min(this.options.hoverWidth, this.options.widthMax), 0.3*this.networkScaleInv);
  24507. }
  24508. else {
  24509. return Math.max(this.options.width, 0.3*this.networkScaleInv);
  24510. }
  24511. }
  24512. };
  24513. Edge.prototype._getViaCoordinates = function () {
  24514. if (this.options.smoothCurves.dynamic == true && this.options.smoothCurves.enabled == true ) {
  24515. return this.via;
  24516. }
  24517. else if (this.options.smoothCurves.enabled == false) {
  24518. return {x:0,y:0};
  24519. }
  24520. else {
  24521. var xVia = null;
  24522. var yVia = null;
  24523. var factor = this.options.smoothCurves.roundness;
  24524. var type = this.options.smoothCurves.type;
  24525. var dx = Math.abs(this.from.x - this.to.x);
  24526. var dy = Math.abs(this.from.y - this.to.y);
  24527. if (type == 'discrete' || type == 'diagonalCross') {
  24528. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  24529. if (this.from.y > this.to.y) {
  24530. if (this.from.x < this.to.x) {
  24531. xVia = this.from.x + factor * dy;
  24532. yVia = this.from.y - factor * dy;
  24533. }
  24534. else if (this.from.x > this.to.x) {
  24535. xVia = this.from.x - factor * dy;
  24536. yVia = this.from.y - factor * dy;
  24537. }
  24538. }
  24539. else if (this.from.y < this.to.y) {
  24540. if (this.from.x < this.to.x) {
  24541. xVia = this.from.x + factor * dy;
  24542. yVia = this.from.y + factor * dy;
  24543. }
  24544. else if (this.from.x > this.to.x) {
  24545. xVia = this.from.x - factor * dy;
  24546. yVia = this.from.y + factor * dy;
  24547. }
  24548. }
  24549. if (type == "discrete") {
  24550. xVia = dx < factor * dy ? this.from.x : xVia;
  24551. }
  24552. }
  24553. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  24554. if (this.from.y > this.to.y) {
  24555. if (this.from.x < this.to.x) {
  24556. xVia = this.from.x + factor * dx;
  24557. yVia = this.from.y - factor * dx;
  24558. }
  24559. else if (this.from.x > this.to.x) {
  24560. xVia = this.from.x - factor * dx;
  24561. yVia = this.from.y - factor * dx;
  24562. }
  24563. }
  24564. else if (this.from.y < this.to.y) {
  24565. if (this.from.x < this.to.x) {
  24566. xVia = this.from.x + factor * dx;
  24567. yVia = this.from.y + factor * dx;
  24568. }
  24569. else if (this.from.x > this.to.x) {
  24570. xVia = this.from.x - factor * dx;
  24571. yVia = this.from.y + factor * dx;
  24572. }
  24573. }
  24574. if (type == "discrete") {
  24575. yVia = dy < factor * dx ? this.from.y : yVia;
  24576. }
  24577. }
  24578. }
  24579. else if (type == "straightCross") {
  24580. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { // up - down
  24581. xVia = this.from.x;
  24582. if (this.from.y < this.to.y) {
  24583. yVia = this.to.y - (1 - factor) * dy;
  24584. }
  24585. else {
  24586. yVia = this.to.y + (1 - factor) * dy;
  24587. }
  24588. }
  24589. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { // left - right
  24590. if (this.from.x < this.to.x) {
  24591. xVia = this.to.x - (1 - factor) * dx;
  24592. }
  24593. else {
  24594. xVia = this.to.x + (1 - factor) * dx;
  24595. }
  24596. yVia = this.from.y;
  24597. }
  24598. }
  24599. else if (type == 'horizontal') {
  24600. if (this.from.x < this.to.x) {
  24601. xVia = this.to.x - (1 - factor) * dx;
  24602. }
  24603. else {
  24604. xVia = this.to.x + (1 - factor) * dx;
  24605. }
  24606. yVia = this.from.y;
  24607. }
  24608. else if (type == 'vertical') {
  24609. xVia = this.from.x;
  24610. if (this.from.y < this.to.y) {
  24611. yVia = this.to.y - (1 - factor) * dy;
  24612. }
  24613. else {
  24614. yVia = this.to.y + (1 - factor) * dy;
  24615. }
  24616. }
  24617. else { // continuous
  24618. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  24619. if (this.from.y > this.to.y) {
  24620. if (this.from.x < this.to.x) {
  24621. // console.log(1)
  24622. xVia = this.from.x + factor * dy;
  24623. yVia = this.from.y - factor * dy;
  24624. xVia = this.to.x < xVia ? this.to.x : xVia;
  24625. }
  24626. else if (this.from.x > this.to.x) {
  24627. // console.log(2)
  24628. xVia = this.from.x - factor * dy;
  24629. yVia = this.from.y - factor * dy;
  24630. xVia = this.to.x > xVia ? this.to.x : xVia;
  24631. }
  24632. }
  24633. else if (this.from.y < this.to.y) {
  24634. if (this.from.x < this.to.x) {
  24635. // console.log(3)
  24636. xVia = this.from.x + factor * dy;
  24637. yVia = this.from.y + factor * dy;
  24638. xVia = this.to.x < xVia ? this.to.x : xVia;
  24639. }
  24640. else if (this.from.x > this.to.x) {
  24641. // console.log(4, this.from.x, this.to.x)
  24642. xVia = this.from.x - factor * dy;
  24643. yVia = this.from.y + factor * dy;
  24644. xVia = this.to.x > xVia ? this.to.x : xVia;
  24645. }
  24646. }
  24647. }
  24648. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  24649. if (this.from.y > this.to.y) {
  24650. if (this.from.x < this.to.x) {
  24651. // console.log(5)
  24652. xVia = this.from.x + factor * dx;
  24653. yVia = this.from.y - factor * dx;
  24654. yVia = this.to.y > yVia ? this.to.y : yVia;
  24655. }
  24656. else if (this.from.x > this.to.x) {
  24657. // console.log(6)
  24658. xVia = this.from.x - factor * dx;
  24659. yVia = this.from.y - factor * dx;
  24660. yVia = this.to.y > yVia ? this.to.y : yVia;
  24661. }
  24662. }
  24663. else if (this.from.y < this.to.y) {
  24664. if (this.from.x < this.to.x) {
  24665. // console.log(7)
  24666. xVia = this.from.x + factor * dx;
  24667. yVia = this.from.y + factor * dx;
  24668. yVia = this.to.y < yVia ? this.to.y : yVia;
  24669. }
  24670. else if (this.from.x > this.to.x) {
  24671. // console.log(8)
  24672. xVia = this.from.x - factor * dx;
  24673. yVia = this.from.y + factor * dx;
  24674. yVia = this.to.y < yVia ? this.to.y : yVia;
  24675. }
  24676. }
  24677. }
  24678. }
  24679. return {x: xVia, y: yVia};
  24680. }
  24681. };
  24682. /**
  24683. * Draw a line between two nodes
  24684. * @param {CanvasRenderingContext2D} ctx
  24685. * @private
  24686. */
  24687. Edge.prototype._line = function (ctx) {
  24688. // draw a straight line
  24689. ctx.beginPath();
  24690. ctx.moveTo(this.from.x, this.from.y);
  24691. if (this.options.smoothCurves.enabled == true) {
  24692. if (this.options.smoothCurves.dynamic == false) {
  24693. var via = this._getViaCoordinates();
  24694. if (via.x == null) {
  24695. ctx.lineTo(this.to.x, this.to.y);
  24696. ctx.stroke();
  24697. return null;
  24698. }
  24699. else {
  24700. // this.via.x = via.x;
  24701. // this.via.y = via.y;
  24702. ctx.quadraticCurveTo(via.x,via.y,this.to.x, this.to.y);
  24703. ctx.stroke();
  24704. return via;
  24705. }
  24706. }
  24707. else {
  24708. ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
  24709. ctx.stroke();
  24710. return this.via;
  24711. }
  24712. }
  24713. else {
  24714. ctx.lineTo(this.to.x, this.to.y);
  24715. ctx.stroke();
  24716. return null;
  24717. }
  24718. };
  24719. /**
  24720. * Draw a line from a node to itself, a circle
  24721. * @param {CanvasRenderingContext2D} ctx
  24722. * @param {Number} x
  24723. * @param {Number} y
  24724. * @param {Number} radius
  24725. * @private
  24726. */
  24727. Edge.prototype._circle = function (ctx, x, y, radius) {
  24728. // draw a circle
  24729. ctx.beginPath();
  24730. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  24731. ctx.stroke();
  24732. };
  24733. /**
  24734. * Draw label with white background and with the middle at (x, y)
  24735. * @param {CanvasRenderingContext2D} ctx
  24736. * @param {String} text
  24737. * @param {Number} x
  24738. * @param {Number} y
  24739. * @private
  24740. */
  24741. Edge.prototype._label = function (ctx, text, x, y) {
  24742. if (text) {
  24743. ctx.font = ((this.from.selected || this.to.selected) ? "bold " : "") +
  24744. this.options.fontSize + "px " + this.options.fontFace;
  24745. var yLine;
  24746. if (this.dirtyLabel == true) {
  24747. var lines = String(text).split('\n');
  24748. var lineCount = lines.length;
  24749. var fontSize = Number(this.options.fontSize);
  24750. yLine = y + (1 - lineCount) / 2 * fontSize;
  24751. var width = ctx.measureText(lines[0]).width;
  24752. for (var i = 1; i < lineCount; i++) {
  24753. var lineWidth = ctx.measureText(lines[i]).width;
  24754. width = lineWidth > width ? lineWidth : width;
  24755. }
  24756. var height = this.options.fontSize * lineCount;
  24757. var left = x - width / 2;
  24758. var top = y - height / 2;
  24759. // cache
  24760. this.labelDimensions = {top:top,left:left,width:width,height:height,yLine:yLine};
  24761. }
  24762. var yLine = this.labelDimensions.yLine;
  24763. ctx.save();
  24764. if (this.options.labelAlignment != "horizontal"){
  24765. ctx.translate(x, yLine);
  24766. this._rotateForLabelAlignment(ctx);
  24767. x = 0;
  24768. yLine = 0;
  24769. }
  24770. this._drawLabelRect(ctx);
  24771. this._drawLabelText(ctx,x,yLine, lines, lineCount, fontSize);
  24772. ctx.restore();
  24773. }
  24774. };
  24775. /**
  24776. * Rotates the canvas so the text is most readable
  24777. * @param {CanvasRenderingContext2D} ctx
  24778. * @private
  24779. */
  24780. Edge.prototype._rotateForLabelAlignment = function(ctx) {
  24781. var dy = this.from.y - this.to.y;
  24782. var dx = this.from.x - this.to.x;
  24783. var angleInDegrees = Math.atan2(dy, dx);
  24784. // rotate so label it is readable
  24785. if((angleInDegrees < -1 && dx < 0) || (angleInDegrees > 0 && dx < 0)){
  24786. angleInDegrees = angleInDegrees + Math.PI;
  24787. }
  24788. ctx.rotate(angleInDegrees);
  24789. };
  24790. /**
  24791. * Draws the label rectangle
  24792. * @param {CanvasRenderingContext2D} ctx
  24793. * @param {String} labelAlignment
  24794. * @private
  24795. */
  24796. Edge.prototype._drawLabelRect = function(ctx) {
  24797. if (this.options.fontFill !== undefined && this.options.fontFill !== null && this.options.fontFill !== "none") {
  24798. ctx.fillStyle = this.options.fontFill;
  24799. var lineMargin = 2;
  24800. if (this.options.labelAlignment == 'line-center') {
  24801. ctx.fillRect(-this.labelDimensions.width * 0.5, -this.labelDimensions.height * 0.5, this.labelDimensions.width, this.labelDimensions.height);
  24802. }
  24803. else if (this.options.labelAlignment == 'line-above') {
  24804. ctx.fillRect(-this.labelDimensions.width * 0.5, -(this.labelDimensions.height + lineMargin), this.labelDimensions.width, this.labelDimensions.height);
  24805. }
  24806. else if (this.options.labelAlignment == 'line-below') {
  24807. ctx.fillRect(-this.labelDimensions.width * 0.5, lineMargin, this.labelDimensions.width, this.labelDimensions.height);
  24808. }
  24809. else {
  24810. ctx.fillRect(this.labelDimensions.left, this.labelDimensions.top, this.labelDimensions.width, this.labelDimensions.height);
  24811. }
  24812. }
  24813. };
  24814. /**
  24815. * Draws the label text
  24816. * @param {CanvasRenderingContext2D} ctx
  24817. * @param {Number} x
  24818. * @param {Number} yLine
  24819. * @param {Array} lines
  24820. * @param {Number} lineCount
  24821. * @param {Number} fontSize
  24822. * @private
  24823. */
  24824. Edge.prototype._drawLabelText = function(ctx, x, yLine, lines, lineCount, fontSize) {
  24825. // draw text
  24826. ctx.fillStyle = this.options.fontColor || "black";
  24827. ctx.textAlign = "center";
  24828. // check for label alignment
  24829. if (this.options.labelAlignment != 'horizontal') {
  24830. var lineMargin = 2;
  24831. if (this.options.labelAlignment == 'line-above') {
  24832. ctx.textBaseline = "alphabetic";
  24833. yLine -= 2 * lineMargin; // distance from edge, required because we use alphabetic. Alphabetic has less difference between browsers
  24834. }
  24835. else if (this.options.labelAlignment == 'line-below') {
  24836. ctx.textBaseline = "hanging";
  24837. yLine += 2 * lineMargin;// distance from edge, required because we use hanging. Hanging has less difference between browsers
  24838. }
  24839. else {
  24840. ctx.textBaseline = "middle";
  24841. }
  24842. }
  24843. else {
  24844. ctx.textBaseline = "middle";
  24845. }
  24846. // check for strokeWidth
  24847. if (this.options.fontStrokeWidth > 0){
  24848. ctx.lineWidth = this.options.fontStrokeWidth;
  24849. ctx.strokeStyle = this.options.fontStrokeColor;
  24850. ctx.lineJoin = 'round';
  24851. }
  24852. for (var i = 0; i < lineCount; i++) {
  24853. if(this.options.fontStrokeWidth > 0){
  24854. ctx.strokeText(lines[i], x, yLine);
  24855. }
  24856. ctx.fillText(lines[i], x, yLine);
  24857. yLine += fontSize;
  24858. }
  24859. };
  24860. /**
  24861. * Redraw a edge as a dashed line
  24862. * Draw this edge in the given canvas
  24863. * @author David Jordan
  24864. * @date 2012-08-08
  24865. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  24866. * @param {CanvasRenderingContext2D} ctx
  24867. * @private
  24868. */
  24869. Edge.prototype._drawDashLine = function(ctx) {
  24870. // set style
  24871. ctx.strokeStyle = this._getColor();
  24872. ctx.lineWidth = this._getLineWidth();
  24873. var via = null;
  24874. // only firefox and chrome support this method, else we use the legacy one.
  24875. if (ctx.setLineDash !== undefined) {
  24876. ctx.save();
  24877. // configure the dash pattern
  24878. var pattern = [0];
  24879. if (this.options.dash.length !== undefined && this.options.dash.gap !== undefined) {
  24880. pattern = [this.options.dash.length,this.options.dash.gap];
  24881. }
  24882. else {
  24883. pattern = [5,5];
  24884. }
  24885. // set dash settings for chrome or firefox
  24886. ctx.setLineDash(pattern);
  24887. ctx.lineDashOffset = 0;
  24888. // draw the line
  24889. via = this._line(ctx);
  24890. // restore the dash settings.
  24891. ctx.setLineDash([0]);
  24892. ctx.lineDashOffset = 0;
  24893. ctx.restore();
  24894. }
  24895. else { // unsupporting smooth lines
  24896. // draw dashed line
  24897. ctx.beginPath();
  24898. ctx.lineCap = 'round';
  24899. if (this.options.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
  24900. {
  24901. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  24902. [this.options.dash.length,this.options.dash.gap,this.options.dash.altLength,this.options.dash.gap]);
  24903. }
  24904. 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
  24905. {
  24906. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  24907. [this.options.dash.length,this.options.dash.gap]);
  24908. }
  24909. else //If all else fails draw a line
  24910. {
  24911. ctx.moveTo(this.from.x, this.from.y);
  24912. ctx.lineTo(this.to.x, this.to.y);
  24913. }
  24914. ctx.stroke();
  24915. }
  24916. // draw label
  24917. if (this.label) {
  24918. var point;
  24919. if (this.options.smoothCurves.enabled == true && via != null) {
  24920. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  24921. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  24922. point = {x:midpointX, y:midpointY};
  24923. }
  24924. else {
  24925. point = this._pointOnLine(0.5);
  24926. }
  24927. this._label(ctx, this.label, point.x, point.y);
  24928. }
  24929. };
  24930. /**
  24931. * Get a point on a line
  24932. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  24933. * @return {Object} point
  24934. * @private
  24935. */
  24936. Edge.prototype._pointOnLine = function (percentage) {
  24937. return {
  24938. x: (1 - percentage) * this.from.x + percentage * this.to.x,
  24939. y: (1 - percentage) * this.from.y + percentage * this.to.y
  24940. }
  24941. };
  24942. /**
  24943. * Get a point on a circle
  24944. * @param {Number} x
  24945. * @param {Number} y
  24946. * @param {Number} radius
  24947. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  24948. * @return {Object} point
  24949. * @private
  24950. */
  24951. Edge.prototype._pointOnCircle = function (x, y, radius, percentage) {
  24952. var angle = (percentage - 3/8) * 2 * Math.PI;
  24953. return {
  24954. x: x + radius * Math.cos(angle),
  24955. y: y - radius * Math.sin(angle)
  24956. }
  24957. };
  24958. /**
  24959. * Redraw a edge as a line with an arrow halfway the line
  24960. * Draw this edge in the given canvas
  24961. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  24962. * @param {CanvasRenderingContext2D} ctx
  24963. * @private
  24964. */
  24965. Edge.prototype._drawArrowCenter = function(ctx) {
  24966. var point;
  24967. // set style
  24968. ctx.strokeStyle = this._getColor();
  24969. ctx.fillStyle = ctx.strokeStyle;
  24970. ctx.lineWidth = this._getLineWidth();
  24971. if (this.from != this.to) {
  24972. // draw line
  24973. var via = this._line(ctx);
  24974. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  24975. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  24976. // draw an arrow halfway the line
  24977. if (this.options.smoothCurves.enabled == true && via != null) {
  24978. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  24979. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  24980. point = {x:midpointX, y:midpointY};
  24981. }
  24982. else {
  24983. point = this._pointOnLine(0.5);
  24984. }
  24985. ctx.arrow(point.x, point.y, angle, length);
  24986. ctx.fill();
  24987. ctx.stroke();
  24988. // draw label
  24989. if (this.label) {
  24990. this._label(ctx, this.label, point.x, point.y);
  24991. }
  24992. }
  24993. else {
  24994. // draw circle
  24995. var x, y;
  24996. var radius = 0.25 * Math.max(100,this.physics.springLength);
  24997. var node = this.from;
  24998. if (!node.width) {
  24999. node.resize(ctx);
  25000. }
  25001. if (node.width > node.height) {
  25002. x = node.x + node.width * 0.5;
  25003. y = node.y - radius;
  25004. }
  25005. else {
  25006. x = node.x + radius;
  25007. y = node.y - node.height * 0.5;
  25008. }
  25009. this._circle(ctx, x, y, radius);
  25010. // draw all arrows
  25011. var angle = 0.2 * Math.PI;
  25012. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  25013. point = this._pointOnCircle(x, y, radius, 0.5);
  25014. ctx.arrow(point.x, point.y, angle, length);
  25015. ctx.fill();
  25016. ctx.stroke();
  25017. // draw label
  25018. if (this.label) {
  25019. point = this._pointOnCircle(x, y, radius, 0.5);
  25020. this._label(ctx, this.label, point.x, point.y);
  25021. }
  25022. }
  25023. };
  25024. Edge.prototype._pointOnBezier = function(t) {
  25025. var via = this._getViaCoordinates();
  25026. var x = Math.pow(1-t,2)*this.from.x + (2*t*(1 - t))*via.x + Math.pow(t,2)*this.to.x;
  25027. var y = Math.pow(1-t,2)*this.from.y + (2*t*(1 - t))*via.y + Math.pow(t,2)*this.to.y;
  25028. return {x:x,y:y};
  25029. }
  25030. /**
  25031. * This function uses binary search to look for the point where the bezier curve crosses the border of the node.
  25032. *
  25033. * @param from
  25034. * @param ctx
  25035. * @returns {*}
  25036. * @private
  25037. */
  25038. Edge.prototype._findBorderPosition = function(from,ctx) {
  25039. var maxIterations = 10;
  25040. var iteration = 0;
  25041. var low = 0;
  25042. var high = 1;
  25043. var pos,angle,distanceToBorder, distanceToNodes, difference;
  25044. var threshold = 0.2;
  25045. var node = this.to;
  25046. if (from == true) {
  25047. node = this.from;
  25048. }
  25049. while (low <= high && iteration < maxIterations) {
  25050. var middle = (low + high) * 0.5;
  25051. pos = this._pointOnBezier(middle);
  25052. angle = Math.atan2((node.y - pos.y), (node.x - pos.x));
  25053. distanceToBorder = node.distanceToBorder(ctx,angle);
  25054. distanceToNodes = Math.sqrt(Math.pow(pos.x-node.x,2) + Math.pow(pos.y-node.y,2));
  25055. difference = distanceToBorder - distanceToNodes;
  25056. if (Math.abs(difference) < threshold) {
  25057. break; // found
  25058. }
  25059. 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.
  25060. if (from == false) {
  25061. low = middle;
  25062. }
  25063. else {
  25064. high = middle;
  25065. }
  25066. }
  25067. else {
  25068. if (from == false) {
  25069. high = middle;
  25070. }
  25071. else {
  25072. low = middle;
  25073. }
  25074. }
  25075. iteration++;
  25076. }
  25077. pos.t = middle;
  25078. return pos;
  25079. };
  25080. /**
  25081. * Redraw a edge as a line with an arrow
  25082. * Draw this edge in the given canvas
  25083. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  25084. * @param {CanvasRenderingContext2D} ctx
  25085. * @private
  25086. */
  25087. Edge.prototype._drawArrow = function(ctx) {
  25088. // set style
  25089. ctx.strokeStyle = this._getColor();
  25090. ctx.fillStyle = ctx.strokeStyle;
  25091. ctx.lineWidth = this._getLineWidth();
  25092. // set vars
  25093. var angle, length, arrowPos;
  25094. // if not connected to itself
  25095. if (this.from != this.to) {
  25096. // draw line
  25097. this._line(ctx);
  25098. // draw arrow head
  25099. if (this.options.smoothCurves.enabled == true) {
  25100. var via = this._getViaCoordinates();
  25101. arrowPos = this._findBorderPosition(false, ctx);
  25102. var guidePos = this._pointOnBezier(Math.max(0.0, arrowPos.t - 0.1))
  25103. angle = Math.atan2((arrowPos.y - guidePos.y), (arrowPos.x - guidePos.x));
  25104. }
  25105. else {
  25106. angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  25107. var dx = (this.to.x - this.from.x);
  25108. var dy = (this.to.y - this.from.y);
  25109. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  25110. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  25111. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  25112. arrowPos = {};
  25113. arrowPos.x = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  25114. arrowPos.y = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  25115. }
  25116. // draw arrow at the end of the line
  25117. length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  25118. ctx.arrow(arrowPos.x,arrowPos.y, angle, length);
  25119. ctx.fill();
  25120. ctx.stroke();
  25121. // draw label
  25122. if (this.label) {
  25123. var point;
  25124. if (this.options.smoothCurves.enabled == true && via != null) {
  25125. point = this._pointOnBezier(0.5);
  25126. }
  25127. else {
  25128. point = this._pointOnLine(0.5);
  25129. }
  25130. this._label(ctx, this.label, point.x, point.y);
  25131. }
  25132. }
  25133. else {
  25134. // draw circle
  25135. var node = this.from;
  25136. var x, y, arrow;
  25137. var radius = 0.25 * Math.max(100,this.physics.springLength);
  25138. if (!node.width) {
  25139. node.resize(ctx);
  25140. }
  25141. if (node.width > node.height) {
  25142. x = node.x + node.width * 0.5;
  25143. y = node.y - radius;
  25144. arrow = {
  25145. x: x,
  25146. y: node.y,
  25147. angle: 0.9 * Math.PI
  25148. };
  25149. }
  25150. else {
  25151. x = node.x + radius;
  25152. y = node.y - node.height * 0.5;
  25153. arrow = {
  25154. x: node.x,
  25155. y: y,
  25156. angle: 0.6 * Math.PI
  25157. };
  25158. }
  25159. ctx.beginPath();
  25160. // TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
  25161. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  25162. ctx.stroke();
  25163. // draw all arrows
  25164. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  25165. ctx.arrow(arrow.x, arrow.y, arrow.angle, length);
  25166. ctx.fill();
  25167. ctx.stroke();
  25168. // draw label
  25169. if (this.label) {
  25170. point = this._pointOnCircle(x, y, radius, 0.5);
  25171. this._label(ctx, this.label, point.x, point.y);
  25172. }
  25173. }
  25174. };
  25175. /**
  25176. * Calculate the distance between a point (x3,y3) and a line segment from
  25177. * (x1,y1) to (x2,y2).
  25178. * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
  25179. * @param {number} x1
  25180. * @param {number} y1
  25181. * @param {number} x2
  25182. * @param {number} y2
  25183. * @param {number} x3
  25184. * @param {number} y3
  25185. * @private
  25186. */
  25187. Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
  25188. var returnValue = 0;
  25189. if (this.from != this.to) {
  25190. if (this.options.smoothCurves.enabled == true) {
  25191. var xVia, yVia;
  25192. if (this.options.smoothCurves.enabled == true && this.options.smoothCurves.dynamic == true) {
  25193. xVia = this.via.x;
  25194. yVia = this.via.y;
  25195. }
  25196. else {
  25197. var via = this._getViaCoordinates();
  25198. xVia = via.x;
  25199. yVia = via.y;
  25200. }
  25201. var minDistance = 1e9;
  25202. var distance;
  25203. var i,t,x,y, lastX, lastY;
  25204. for (i = 0; i < 10; i++) {
  25205. t = 0.1*i;
  25206. x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*xVia + Math.pow(t,2)*x2;
  25207. y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*yVia + Math.pow(t,2)*y2;
  25208. if (i > 0) {
  25209. distance = this._getDistanceToLine(lastX,lastY,x,y, x3,y3);
  25210. minDistance = distance < minDistance ? distance : minDistance;
  25211. }
  25212. lastX = x; lastY = y;
  25213. }
  25214. returnValue = minDistance;
  25215. }
  25216. else {
  25217. returnValue = this._getDistanceToLine(x1,y1,x2,y2,x3,y3);
  25218. }
  25219. }
  25220. else {
  25221. var x, y, dx, dy;
  25222. var radius = 0.25 * this.physics.springLength;
  25223. var node = this.from;
  25224. if (node.width > node.height) {
  25225. x = node.x + 0.5 * node.width;
  25226. y = node.y - radius;
  25227. }
  25228. else {
  25229. x = node.x + radius;
  25230. y = node.y - 0.5 * node.height;
  25231. }
  25232. dx = x - x3;
  25233. dy = y - y3;
  25234. returnValue = Math.abs(Math.sqrt(dx*dx + dy*dy) - radius);
  25235. }
  25236. if (this.labelDimensions.left < x3 &&
  25237. this.labelDimensions.left + this.labelDimensions.width > x3 &&
  25238. this.labelDimensions.top < y3 &&
  25239. this.labelDimensions.top + this.labelDimensions.height > y3) {
  25240. return 0;
  25241. }
  25242. else {
  25243. return returnValue;
  25244. }
  25245. };
  25246. Edge.prototype._getDistanceToLine = function(x1,y1,x2,y2,x3,y3) {
  25247. var px = x2-x1,
  25248. py = y2-y1,
  25249. something = px*px + py*py,
  25250. u = ((x3 - x1) * px + (y3 - y1) * py) / something;
  25251. if (u > 1) {
  25252. u = 1;
  25253. }
  25254. else if (u < 0) {
  25255. u = 0;
  25256. }
  25257. var x = x1 + u * px,
  25258. y = y1 + u * py,
  25259. dx = x - x3,
  25260. dy = y - y3;
  25261. //# Note: If the actual distance does not matter,
  25262. //# if you only want to compare what this function
  25263. //# returns to other results of this function, you
  25264. //# can just return the squared distance instead
  25265. //# (i.e. remove the sqrt) to gain a little performance
  25266. return Math.sqrt(dx*dx + dy*dy);
  25267. };
  25268. /**
  25269. * This allows the zoom level of the network to influence the rendering
  25270. *
  25271. * @param scale
  25272. */
  25273. Edge.prototype.setScale = function(scale) {
  25274. this.networkScaleInv = 1.0/scale;
  25275. };
  25276. Edge.prototype.select = function() {
  25277. this.selected = true;
  25278. };
  25279. Edge.prototype.unselect = function() {
  25280. this.selected = false;
  25281. };
  25282. Edge.prototype.positionBezierNode = function() {
  25283. if (this.via !== null && this.from !== null && this.to !== null) {
  25284. this.via.x = 0.5 * (this.from.x + this.to.x);
  25285. this.via.y = 0.5 * (this.from.y + this.to.y);
  25286. }
  25287. else if (this.via !== null) {
  25288. this.via.x = 0;
  25289. this.via.y = 0;
  25290. }
  25291. };
  25292. /**
  25293. * This function draws the control nodes for the manipulator.
  25294. * In order to enable this, only set the this.controlNodesEnabled to true.
  25295. * @param ctx
  25296. */
  25297. Edge.prototype._drawControlNodes = function(ctx) {
  25298. if (this.controlNodesEnabled == true) {
  25299. if (this.controlNodes.from === null && this.controlNodes.to === null) {
  25300. var nodeIdFrom = "edgeIdFrom:".concat(this.id);
  25301. var nodeIdTo = "edgeIdTo:".concat(this.id);
  25302. var constants = {
  25303. nodes:{group:'', radius:7, borderWidth:2, borderWidthSelected: 2},
  25304. physics:{damping:0},
  25305. clustering: {maxNodeSizeIncrements: 0 ,nodeScaling: {width:0, height: 0, radius:0}}
  25306. };
  25307. this.controlNodes.from = new Node(
  25308. {id:nodeIdFrom,
  25309. shape:'dot',
  25310. color:{background:'#ff0000', border:'#3c3c3c', highlight: {background:'#07f968'}}
  25311. },{},{},constants);
  25312. this.controlNodes.to = new Node(
  25313. {id:nodeIdTo,
  25314. shape:'dot',
  25315. color:{background:'#ff0000', border:'#3c3c3c', highlight: {background:'#07f968'}}
  25316. },{},{},constants);
  25317. }
  25318. this.controlNodes.positions = {};
  25319. if (this.controlNodes.from.selected == false) {
  25320. this.controlNodes.positions.from = this.getControlNodeFromPosition(ctx);
  25321. this.controlNodes.from.x = this.controlNodes.positions.from.x;
  25322. this.controlNodes.from.y = this.controlNodes.positions.from.y;
  25323. }
  25324. if (this.controlNodes.to.selected == false) {
  25325. this.controlNodes.positions.to = this.getControlNodeToPosition(ctx);
  25326. this.controlNodes.to.x = this.controlNodes.positions.to.x;
  25327. this.controlNodes.to.y = this.controlNodes.positions.to.y;
  25328. }
  25329. this.controlNodes.from.draw(ctx);
  25330. this.controlNodes.to.draw(ctx);
  25331. }
  25332. else {
  25333. this.controlNodes = {from:null, to:null, positions:{}};
  25334. }
  25335. };
  25336. /**
  25337. * Enable control nodes.
  25338. * @private
  25339. */
  25340. Edge.prototype._enableControlNodes = function() {
  25341. this.fromBackup = this.from;
  25342. this.toBackup = this.to;
  25343. this.controlNodesEnabled = true;
  25344. };
  25345. /**
  25346. * disable control nodes and remove from dynamicEdges from old node
  25347. * @private
  25348. */
  25349. Edge.prototype._disableControlNodes = function() {
  25350. this.fromId = this.from.id;
  25351. this.toId = this.to.id;
  25352. if (this.fromId != this.fromBackup.id) { // from was changed, remove edge from old 'from' node dynamic edges
  25353. this.fromBackup.detachEdge(this);
  25354. }
  25355. else if (this.toId != this.toBackup.id) { // to was changed, remove edge from old 'to' node dynamic edges
  25356. this.toBackup.detachEdge(this);
  25357. }
  25358. this.fromBackup = null;
  25359. this.toBackup = null;
  25360. this.controlNodesEnabled = false;
  25361. };
  25362. /**
  25363. * This checks if one of the control nodes is selected and if so, returns the control node object. Else it returns null.
  25364. * @param x
  25365. * @param y
  25366. * @returns {null}
  25367. * @private
  25368. */
  25369. Edge.prototype._getSelectedControlNode = function(x,y) {
  25370. var positions = this.controlNodes.positions;
  25371. var fromDistance = Math.sqrt(Math.pow(x - positions.from.x,2) + Math.pow(y - positions.from.y,2));
  25372. var toDistance = Math.sqrt(Math.pow(x - positions.to.x ,2) + Math.pow(y - positions.to.y ,2));
  25373. if (fromDistance < 15) {
  25374. this.connectedNode = this.from;
  25375. this.from = this.controlNodes.from;
  25376. return this.controlNodes.from;
  25377. }
  25378. else if (toDistance < 15) {
  25379. this.connectedNode = this.to;
  25380. this.to = this.controlNodes.to;
  25381. return this.controlNodes.to;
  25382. }
  25383. else {
  25384. return null;
  25385. }
  25386. };
  25387. /**
  25388. * this resets the control nodes to their original position.
  25389. * @private
  25390. */
  25391. Edge.prototype._restoreControlNodes = function() {
  25392. if (this.controlNodes.from.selected == true) {
  25393. this.from = this.connectedNode;
  25394. this.connectedNode = null;
  25395. this.controlNodes.from.unselect();
  25396. }
  25397. else if (this.controlNodes.to.selected == true) {
  25398. this.to = this.connectedNode;
  25399. this.connectedNode = null;
  25400. this.controlNodes.to.unselect();
  25401. }
  25402. };
  25403. /**
  25404. * this calculates the position of the control nodes on the edges of the parent nodes.
  25405. *
  25406. * @param ctx
  25407. * @returns {x: *, y: *}
  25408. */
  25409. Edge.prototype.getControlNodeFromPosition = function(ctx) {
  25410. // draw arrow head
  25411. var controlnodeFromPos;
  25412. if (this.options.smoothCurves.enabled == true) {
  25413. controlnodeFromPos = this._findBorderPosition(true, ctx);
  25414. }
  25415. else {
  25416. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  25417. var dx = (this.to.x - this.from.x);
  25418. var dy = (this.to.y - this.from.y);
  25419. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  25420. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  25421. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  25422. controlnodeFromPos = {};
  25423. controlnodeFromPos.x = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  25424. controlnodeFromPos.y = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  25425. }
  25426. return controlnodeFromPos;
  25427. };
  25428. /**
  25429. * this calculates the position of the control nodes on the edges of the parent nodes.
  25430. *
  25431. * @param ctx
  25432. * @returns {{from: {x: number, y: number}, to: {x: *, y: *}}}
  25433. */
  25434. Edge.prototype.getControlNodeToPosition = function(ctx) {
  25435. // draw arrow head
  25436. var controlnodeFromPos,controlnodeToPos;
  25437. if (this.options.smoothCurves.enabled == true) {
  25438. controlnodeToPos = this._findBorderPosition(false, ctx);
  25439. }
  25440. else {
  25441. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  25442. var dx = (this.to.x - this.from.x);
  25443. var dy = (this.to.y - this.from.y);
  25444. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  25445. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  25446. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  25447. controlnodeToPos = {};
  25448. controlnodeToPos.x = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  25449. controlnodeToPos.y = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  25450. }
  25451. return controlnodeToPos;
  25452. };
  25453. module.exports = Edge;
  25454. /***/ },
  25455. /* 58 */
  25456. /***/ function(module, exports, __webpack_require__) {
  25457. /**
  25458. * Popup is a class to create a popup window with some text
  25459. * @param {Element} container The container object.
  25460. * @param {Number} [x]
  25461. * @param {Number} [y]
  25462. * @param {String} [text]
  25463. * @param {Object} [style] An object containing borderColor,
  25464. * backgroundColor, etc.
  25465. */
  25466. function Popup(container, x, y, text, style) {
  25467. if (container) {
  25468. this.container = container;
  25469. }
  25470. else {
  25471. this.container = document.body;
  25472. }
  25473. // x, y and text are optional, see if a style object was passed in their place
  25474. if (style === undefined) {
  25475. if (typeof x === "object") {
  25476. style = x;
  25477. x = undefined;
  25478. } else if (typeof text === "object") {
  25479. style = text;
  25480. text = undefined;
  25481. } else {
  25482. // for backwards compatibility, in case clients other than Network are creating Popup directly
  25483. style = {
  25484. fontColor: 'black',
  25485. fontSize: 14, // px
  25486. fontFace: 'verdana',
  25487. color: {
  25488. border: '#666',
  25489. background: '#FFFFC6'
  25490. }
  25491. }
  25492. }
  25493. }
  25494. this.x = 0;
  25495. this.y = 0;
  25496. this.padding = 5;
  25497. if (x !== undefined && y !== undefined ) {
  25498. this.setPosition(x, y);
  25499. }
  25500. if (text !== undefined) {
  25501. this.setText(text);
  25502. }
  25503. // create the frame
  25504. this.frame = document.createElement("div");
  25505. var styleAttr = this.frame.style;
  25506. styleAttr.position = "absolute";
  25507. styleAttr.visibility = "hidden";
  25508. styleAttr.border = "1px solid " + style.color.border;
  25509. styleAttr.color = style.fontColor;
  25510. styleAttr.fontSize = style.fontSize + "px";
  25511. styleAttr.fontFamily = style.fontFace;
  25512. styleAttr.padding = this.padding + "px";
  25513. styleAttr.backgroundColor = style.color.background;
  25514. styleAttr.borderRadius = "3px";
  25515. styleAttr.MozBorderRadius = "3px";
  25516. styleAttr.WebkitBorderRadius = "3px";
  25517. styleAttr.boxShadow = "3px 3px 10px rgba(128, 128, 128, 0.5)";
  25518. styleAttr.whiteSpace = "nowrap";
  25519. this.container.appendChild(this.frame);
  25520. }
  25521. /**
  25522. * @param {number} x Horizontal position of the popup window
  25523. * @param {number} y Vertical position of the popup window
  25524. */
  25525. Popup.prototype.setPosition = function(x, y) {
  25526. this.x = parseInt(x);
  25527. this.y = parseInt(y);
  25528. };
  25529. /**
  25530. * Set the content for the popup window. This can be HTML code or text.
  25531. * @param {string | Element} content
  25532. */
  25533. Popup.prototype.setText = function(content) {
  25534. if (content instanceof Element) {
  25535. this.frame.innerHTML = '';
  25536. this.frame.appendChild(content);
  25537. }
  25538. else {
  25539. this.frame.innerHTML = content; // string containing text or HTML
  25540. }
  25541. };
  25542. /**
  25543. * Show the popup window
  25544. * @param {boolean} show Optional. Show or hide the window
  25545. */
  25546. Popup.prototype.show = function (show) {
  25547. if (show === undefined) {
  25548. show = true;
  25549. }
  25550. if (show) {
  25551. var height = this.frame.clientHeight;
  25552. var width = this.frame.clientWidth;
  25553. var maxHeight = this.frame.parentNode.clientHeight;
  25554. var maxWidth = this.frame.parentNode.clientWidth;
  25555. var top = (this.y - height);
  25556. if (top + height + this.padding > maxHeight) {
  25557. top = maxHeight - height - this.padding;
  25558. }
  25559. if (top < this.padding) {
  25560. top = this.padding;
  25561. }
  25562. var left = this.x;
  25563. if (left + width + this.padding > maxWidth) {
  25564. left = maxWidth - width - this.padding;
  25565. }
  25566. if (left < this.padding) {
  25567. left = this.padding;
  25568. }
  25569. this.frame.style.left = left + "px";
  25570. this.frame.style.top = top + "px";
  25571. this.frame.style.visibility = "visible";
  25572. }
  25573. else {
  25574. this.hide();
  25575. }
  25576. };
  25577. /**
  25578. * Hide the popup window
  25579. */
  25580. Popup.prototype.hide = function () {
  25581. this.frame.style.visibility = "hidden";
  25582. };
  25583. module.exports = Popup;
  25584. /***/ },
  25585. /* 59 */
  25586. /***/ function(module, exports, __webpack_require__) {
  25587. var PhysicsMixin = __webpack_require__(60);
  25588. var ClusterMixin = __webpack_require__(64);
  25589. var SectorsMixin = __webpack_require__(65);
  25590. var SelectionMixin = __webpack_require__(66);
  25591. var ManipulationMixin = __webpack_require__(67);
  25592. var NavigationMixin = __webpack_require__(68);
  25593. var HierarchicalLayoutMixin = __webpack_require__(69);
  25594. /**
  25595. * Load a mixin into the network object
  25596. *
  25597. * @param {Object} sourceVariable | this object has to contain functions.
  25598. * @private
  25599. */
  25600. exports._loadMixin = function (sourceVariable) {
  25601. for (var mixinFunction in sourceVariable) {
  25602. if (sourceVariable.hasOwnProperty(mixinFunction)) {
  25603. this[mixinFunction] = sourceVariable[mixinFunction];
  25604. }
  25605. }
  25606. };
  25607. /**
  25608. * removes a mixin from the network object.
  25609. *
  25610. * @param {Object} sourceVariable | this object has to contain functions.
  25611. * @private
  25612. */
  25613. exports._clearMixin = function (sourceVariable) {
  25614. for (var mixinFunction in sourceVariable) {
  25615. if (sourceVariable.hasOwnProperty(mixinFunction)) {
  25616. this[mixinFunction] = undefined;
  25617. }
  25618. }
  25619. };
  25620. /**
  25621. * Mixin the physics system and initialize the parameters required.
  25622. *
  25623. * @private
  25624. */
  25625. exports._loadPhysicsSystem = function () {
  25626. this._loadMixin(PhysicsMixin);
  25627. this._loadSelectedForceSolver();
  25628. if (this.constants.configurePhysics == true) {
  25629. this._loadPhysicsConfiguration();
  25630. }
  25631. else {
  25632. this._cleanupPhysicsConfiguration();
  25633. }
  25634. };
  25635. /**
  25636. * Mixin the cluster system and initialize the parameters required.
  25637. *
  25638. * @private
  25639. */
  25640. exports._loadClusterSystem = function () {
  25641. this.clusterSession = 0;
  25642. this.hubThreshold = 5;
  25643. this._loadMixin(ClusterMixin);
  25644. };
  25645. /**
  25646. * Mixin the sector system and initialize the parameters required
  25647. *
  25648. * @private
  25649. */
  25650. exports._loadSectorSystem = function () {
  25651. this.sectors = {};
  25652. this.activeSector = ["default"];
  25653. this.sectors["active"] = {};
  25654. this.sectors["active"]["default"] = {"nodes": {},
  25655. "edges": {},
  25656. "nodeIndices": [],
  25657. "formationScale": 1.0,
  25658. "drawingNode": undefined };
  25659. this.sectors["frozen"] = {};
  25660. this.sectors["support"] = {"nodes": {},
  25661. "edges": {},
  25662. "nodeIndices": [],
  25663. "formationScale": 1.0,
  25664. "drawingNode": undefined };
  25665. this.nodeIndices = this.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields
  25666. this._loadMixin(SectorsMixin);
  25667. };
  25668. /**
  25669. * Mixin the selection system and initialize the parameters required
  25670. *
  25671. * @private
  25672. */
  25673. exports._loadSelectionSystem = function () {
  25674. this.selectionObj = {nodes: {}, edges: {}};
  25675. this._loadMixin(SelectionMixin);
  25676. };
  25677. /**
  25678. * Mixin the navigationUI (User Interface) system and initialize the parameters required
  25679. *
  25680. * @private
  25681. */
  25682. exports._loadManipulationSystem = function () {
  25683. // reset global variables -- these are used by the selection of nodes and edges.
  25684. this.blockConnectingEdgeSelection = false;
  25685. this.forceAppendSelection = false;
  25686. if (this.constants.dataManipulation.enabled == true) {
  25687. // load the manipulator HTML elements. All styling done in css.
  25688. if (this.manipulationDiv === undefined) {
  25689. this.manipulationDiv = document.createElement('div');
  25690. this.manipulationDiv.className = 'network-manipulationDiv';
  25691. if (this.editMode == true) {
  25692. this.manipulationDiv.style.display = "block";
  25693. }
  25694. else {
  25695. this.manipulationDiv.style.display = "none";
  25696. }
  25697. this.frame.appendChild(this.manipulationDiv);
  25698. }
  25699. if (this.editModeDiv === undefined) {
  25700. this.editModeDiv = document.createElement('div');
  25701. this.editModeDiv.className = 'network-manipulation-editMode';
  25702. if (this.editMode == true) {
  25703. this.editModeDiv.style.display = "none";
  25704. }
  25705. else {
  25706. this.editModeDiv.style.display = "block";
  25707. }
  25708. this.frame.appendChild(this.editModeDiv);
  25709. }
  25710. if (this.closeDiv === undefined) {
  25711. this.closeDiv = document.createElement('div');
  25712. this.closeDiv.className = 'network-manipulation-closeDiv';
  25713. this.closeDiv.style.display = this.manipulationDiv.style.display;
  25714. this.frame.appendChild(this.closeDiv);
  25715. }
  25716. // load the manipulation functions
  25717. this._loadMixin(ManipulationMixin);
  25718. // create the manipulator toolbar
  25719. this._createManipulatorBar();
  25720. }
  25721. else {
  25722. if (this.manipulationDiv !== undefined) {
  25723. // removes all the bindings and overloads
  25724. this._createManipulatorBar();
  25725. // remove the manipulation divs
  25726. this.frame.removeChild(this.manipulationDiv);
  25727. this.frame.removeChild(this.editModeDiv);
  25728. this.frame.removeChild(this.closeDiv);
  25729. this.manipulationDiv = undefined;
  25730. this.editModeDiv = undefined;
  25731. this.closeDiv = undefined;
  25732. // remove the mixin functions
  25733. this._clearMixin(ManipulationMixin);
  25734. }
  25735. }
  25736. };
  25737. /**
  25738. * Mixin the navigation (User Interface) system and initialize the parameters required
  25739. *
  25740. * @private
  25741. */
  25742. exports._loadNavigationControls = function () {
  25743. this._loadMixin(NavigationMixin);
  25744. // the clean function removes the button divs, this is done to remove the bindings.
  25745. this._cleanNavigation();
  25746. if (this.constants.navigation.enabled == true) {
  25747. this._loadNavigationElements();
  25748. }
  25749. };
  25750. /**
  25751. * Mixin the hierarchical layout system.
  25752. *
  25753. * @private
  25754. */
  25755. exports._loadHierarchySystem = function () {
  25756. this._loadMixin(HierarchicalLayoutMixin);
  25757. };
  25758. /***/ },
  25759. /* 60 */
  25760. /***/ function(module, exports, __webpack_require__) {
  25761. var util = __webpack_require__(1);
  25762. var RepulsionMixin = __webpack_require__(61);
  25763. var HierarchialRepulsionMixin = __webpack_require__(62);
  25764. var BarnesHutMixin = __webpack_require__(63);
  25765. /**
  25766. * Toggling barnes Hut calculation on and off.
  25767. *
  25768. * @private
  25769. */
  25770. exports._toggleBarnesHut = function () {
  25771. this.constants.physics.barnesHut.enabled = !this.constants.physics.barnesHut.enabled;
  25772. this._loadSelectedForceSolver();
  25773. this.moving = true;
  25774. this.start();
  25775. };
  25776. /**
  25777. * This loads the node force solver based on the barnes hut or repulsion algorithm
  25778. *
  25779. * @private
  25780. */
  25781. exports._loadSelectedForceSolver = function () {
  25782. // this overloads the this._calculateNodeForces
  25783. if (this.constants.physics.barnesHut.enabled == true) {
  25784. this._clearMixin(RepulsionMixin);
  25785. this._clearMixin(HierarchialRepulsionMixin);
  25786. this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity;
  25787. this.constants.physics.springLength = this.constants.physics.barnesHut.springLength;
  25788. this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant;
  25789. this.constants.physics.damping = this.constants.physics.barnesHut.damping;
  25790. this._loadMixin(BarnesHutMixin);
  25791. }
  25792. else if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  25793. this._clearMixin(BarnesHutMixin);
  25794. this._clearMixin(RepulsionMixin);
  25795. this.constants.physics.centralGravity = this.constants.physics.hierarchicalRepulsion.centralGravity;
  25796. this.constants.physics.springLength = this.constants.physics.hierarchicalRepulsion.springLength;
  25797. this.constants.physics.springConstant = this.constants.physics.hierarchicalRepulsion.springConstant;
  25798. this.constants.physics.damping = this.constants.physics.hierarchicalRepulsion.damping;
  25799. this._loadMixin(HierarchialRepulsionMixin);
  25800. }
  25801. else {
  25802. this._clearMixin(BarnesHutMixin);
  25803. this._clearMixin(HierarchialRepulsionMixin);
  25804. this.barnesHutTree = undefined;
  25805. this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity;
  25806. this.constants.physics.springLength = this.constants.physics.repulsion.springLength;
  25807. this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant;
  25808. this.constants.physics.damping = this.constants.physics.repulsion.damping;
  25809. this._loadMixin(RepulsionMixin);
  25810. }
  25811. };
  25812. /**
  25813. * Before calculating the forces, we check if we need to cluster to keep up performance and we check
  25814. * if there is more than one node. If it is just one node, we dont calculate anything.
  25815. *
  25816. * @private
  25817. */
  25818. exports._initializeForceCalculation = function () {
  25819. // stop calculation if there is only one node
  25820. if (this.nodeIndices.length == 1) {
  25821. this.nodes[this.nodeIndices[0]]._setForce(0, 0);
  25822. }
  25823. else {
  25824. // if there are too many nodes on screen, we cluster without repositioning
  25825. if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
  25826. this.clusterToFit(this.constants.clustering.reduceToNodes, false);
  25827. }
  25828. // we now start the force calculation
  25829. this._calculateForces();
  25830. }
  25831. };
  25832. /**
  25833. * Calculate the external forces acting on the nodes
  25834. * Forces are caused by: edges, repulsing forces between nodes, gravity
  25835. * @private
  25836. */
  25837. exports._calculateForces = function () {
  25838. // Gravity is required to keep separated groups from floating off
  25839. // the forces are reset to zero in this loop by using _setForce instead
  25840. // of _addForce
  25841. this._calculateGravitationalForces();
  25842. this._calculateNodeForces();
  25843. if (this.constants.physics.springConstant > 0) {
  25844. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  25845. this._calculateSpringForcesWithSupport();
  25846. }
  25847. else {
  25848. if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  25849. this._calculateHierarchicalSpringForces();
  25850. }
  25851. else {
  25852. this._calculateSpringForces();
  25853. }
  25854. }
  25855. }
  25856. };
  25857. /**
  25858. * Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also
  25859. * handled in the calculateForces function. We then use a quadratic curve with the center node as control.
  25860. * This function joins the datanodes and invisible (called support) nodes into one object.
  25861. * We do this so we do not contaminate this.nodes with the support nodes.
  25862. *
  25863. * @private
  25864. */
  25865. exports._updateCalculationNodes = function () {
  25866. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  25867. this.calculationNodes = {};
  25868. this.calculationNodeIndices = [];
  25869. for (var nodeId in this.nodes) {
  25870. if (this.nodes.hasOwnProperty(nodeId)) {
  25871. this.calculationNodes[nodeId] = this.nodes[nodeId];
  25872. }
  25873. }
  25874. var supportNodes = this.sectors['support']['nodes'];
  25875. for (var supportNodeId in supportNodes) {
  25876. if (supportNodes.hasOwnProperty(supportNodeId)) {
  25877. if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) {
  25878. this.calculationNodes[supportNodeId] = supportNodes[supportNodeId];
  25879. }
  25880. else {
  25881. supportNodes[supportNodeId]._setForce(0, 0);
  25882. }
  25883. }
  25884. }
  25885. for (var idx in this.calculationNodes) {
  25886. if (this.calculationNodes.hasOwnProperty(idx)) {
  25887. this.calculationNodeIndices.push(idx);
  25888. }
  25889. }
  25890. }
  25891. else {
  25892. this.calculationNodes = this.nodes;
  25893. this.calculationNodeIndices = this.nodeIndices;
  25894. }
  25895. };
  25896. /**
  25897. * this function applies the central gravity effect to keep groups from floating off
  25898. *
  25899. * @private
  25900. */
  25901. exports._calculateGravitationalForces = function () {
  25902. var dx, dy, distance, node, i;
  25903. var nodes = this.calculationNodes;
  25904. var gravity = this.constants.physics.centralGravity;
  25905. var gravityForce = 0;
  25906. for (i = 0; i < this.calculationNodeIndices.length; i++) {
  25907. node = nodes[this.calculationNodeIndices[i]];
  25908. node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters.
  25909. // gravity does not apply when we are in a pocket sector
  25910. if (this._sector() == "default" && gravity != 0) {
  25911. dx = -node.x;
  25912. dy = -node.y;
  25913. distance = Math.sqrt(dx * dx + dy * dy);
  25914. gravityForce = (distance == 0) ? 0 : (gravity / distance);
  25915. node.fx = dx * gravityForce;
  25916. node.fy = dy * gravityForce;
  25917. }
  25918. else {
  25919. node.fx = 0;
  25920. node.fy = 0;
  25921. }
  25922. }
  25923. };
  25924. /**
  25925. * this function calculates the effects of the springs in the case of unsmooth curves.
  25926. *
  25927. * @private
  25928. */
  25929. exports._calculateSpringForces = function () {
  25930. var edgeLength, edge, edgeId;
  25931. var dx, dy, fx, fy, springForce, distance;
  25932. var edges = this.edges;
  25933. // forces caused by the edges, modelled as springs
  25934. for (edgeId in edges) {
  25935. if (edges.hasOwnProperty(edgeId)) {
  25936. edge = edges[edgeId];
  25937. if (edge.connected) {
  25938. // only calculate forces if nodes are in the same sector
  25939. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  25940. edgeLength = edge.physics.springLength;
  25941. // this implies that the edges between big clusters are longer
  25942. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  25943. dx = (edge.from.x - edge.to.x);
  25944. dy = (edge.from.y - edge.to.y);
  25945. distance = Math.sqrt(dx * dx + dy * dy);
  25946. if (distance == 0) {
  25947. distance = 0.01;
  25948. }
  25949. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  25950. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  25951. fx = dx * springForce;
  25952. fy = dy * springForce;
  25953. edge.from.fx += fx;
  25954. edge.from.fy += fy;
  25955. edge.to.fx -= fx;
  25956. edge.to.fy -= fy;
  25957. }
  25958. }
  25959. }
  25960. }
  25961. };
  25962. /**
  25963. * This function calculates the springforces on the nodes, accounting for the support nodes.
  25964. *
  25965. * @private
  25966. */
  25967. exports._calculateSpringForcesWithSupport = function () {
  25968. var edgeLength, edge, edgeId, combinedClusterSize;
  25969. var edges = this.edges;
  25970. // forces caused by the edges, modelled as springs
  25971. for (edgeId in edges) {
  25972. if (edges.hasOwnProperty(edgeId)) {
  25973. edge = edges[edgeId];
  25974. if (edge.connected) {
  25975. // only calculate forces if nodes are in the same sector
  25976. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  25977. if (edge.via != null) {
  25978. var node1 = edge.to;
  25979. var node2 = edge.via;
  25980. var node3 = edge.from;
  25981. edgeLength = edge.physics.springLength;
  25982. combinedClusterSize = node1.clusterSize + node3.clusterSize - 2;
  25983. // this implies that the edges between big clusters are longer
  25984. edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth;
  25985. this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
  25986. this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
  25987. }
  25988. }
  25989. }
  25990. }
  25991. }
  25992. };
  25993. /**
  25994. * This is the code actually performing the calculation for the function above. It is split out to avoid repetition.
  25995. *
  25996. * @param node1
  25997. * @param node2
  25998. * @param edgeLength
  25999. * @private
  26000. */
  26001. exports._calculateSpringForce = function (node1, node2, edgeLength) {
  26002. var dx, dy, fx, fy, springForce, distance;
  26003. dx = (node1.x - node2.x);
  26004. dy = (node1.y - node2.y);
  26005. distance = Math.sqrt(dx * dx + dy * dy);
  26006. if (distance == 0) {
  26007. distance = 0.01;
  26008. }
  26009. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  26010. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  26011. fx = dx * springForce;
  26012. fy = dy * springForce;
  26013. node1.fx += fx;
  26014. node1.fy += fy;
  26015. node2.fx -= fx;
  26016. node2.fy -= fy;
  26017. };
  26018. exports._cleanupPhysicsConfiguration = function() {
  26019. if (this.physicsConfiguration !== undefined) {
  26020. while (this.physicsConfiguration.hasChildNodes()) {
  26021. this.physicsConfiguration.removeChild(this.physicsConfiguration.firstChild);
  26022. }
  26023. this.physicsConfiguration.parentNode.removeChild(this.physicsConfiguration);
  26024. this.physicsConfiguration = undefined;
  26025. }
  26026. }
  26027. /**
  26028. * Load the HTML for the physics config and bind it
  26029. * @private
  26030. */
  26031. exports._loadPhysicsConfiguration = function () {
  26032. if (this.physicsConfiguration === undefined) {
  26033. this.backupConstants = {};
  26034. util.deepExtend(this.backupConstants,this.constants);
  26035. var maxGravitational = Math.max(20000, (-1 * this.constants.physics.barnesHut.gravitationalConstant) * 10);
  26036. var maxSpring = Math.min(0.05, this.constants.physics.barnesHut.springConstant * 10)
  26037. var hierarchicalLayoutDirections = ["LR", "RL", "UD", "DU"];
  26038. this.physicsConfiguration = document.createElement('div');
  26039. this.physicsConfiguration.className = "PhysicsConfiguration";
  26040. this.physicsConfiguration.innerHTML = '' +
  26041. '<table><tr><td><b>Simulation Mode:</b></td></tr>' +
  26042. '<tr>' +
  26043. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod1" value="BH" checked="checked">Barnes Hut</td>' +
  26044. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod2" value="R">Repulsion</td>' +
  26045. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod3" value="H">Hierarchical</td>' +
  26046. '</tr>' +
  26047. '</table>' +
  26048. '<table id="graph_BH_table" style="display:none">' +
  26049. '<tr><td><b>Barnes Hut</b></td></tr>' +
  26050. '<tr>' +
  26051. '<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>' +
  26052. '</tr>' +
  26053. '<tr>' +
  26054. '<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>' +
  26055. '</tr>' +
  26056. '<tr>' +
  26057. '<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>' +
  26058. '</tr>' +
  26059. '<tr>' +
  26060. '<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>' +
  26061. '</tr>' +
  26062. '<tr>' +
  26063. '<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>' +
  26064. '</tr>' +
  26065. '</table>' +
  26066. '<table id="graph_R_table" style="display:none">' +
  26067. '<tr><td><b>Repulsion</b></td></tr>' +
  26068. '<tr>' +
  26069. '<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>' +
  26070. '</tr>' +
  26071. '<tr>' +
  26072. '<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>' +
  26073. '</tr>' +
  26074. '<tr>' +
  26075. '<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>' +
  26076. '</tr>' +
  26077. '<tr>' +
  26078. '<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>' +
  26079. '</tr>' +
  26080. '<tr>' +
  26081. '<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>' +
  26082. '</tr>' +
  26083. '</table>' +
  26084. '<table id="graph_H_table" style="display:none">' +
  26085. '<tr><td width="150"><b>Hierarchical</b></td></tr>' +
  26086. '<tr>' +
  26087. '<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>' +
  26088. '</tr>' +
  26089. '<tr>' +
  26090. '<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>' +
  26091. '</tr>' +
  26092. '<tr>' +
  26093. '<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>' +
  26094. '</tr>' +
  26095. '<tr>' +
  26096. '<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>' +
  26097. '</tr>' +
  26098. '<tr>' +
  26099. '<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>' +
  26100. '</tr>' +
  26101. '<tr>' +
  26102. '<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>' +
  26103. '</tr>' +
  26104. '<tr>' +
  26105. '<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>' +
  26106. '</tr>' +
  26107. '<tr>' +
  26108. '<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>' +
  26109. '</tr>' +
  26110. '</table>' +
  26111. '<table><tr><td><b>Options:</b></td></tr>' +
  26112. '<tr>' +
  26113. '<td width="180px"><input type="button" id="graph_toggleSmooth" value="Toggle smoothCurves" style="width:150px"></td>' +
  26114. '<td width="180px"><input type="button" id="graph_repositionNodes" value="Reinitialize" style="width:150px"></td>' +
  26115. '<td width="180px"><input type="button" id="graph_generateOptions" value="Generate Options" style="width:150px"></td>' +
  26116. '</tr>' +
  26117. '</table>'
  26118. this.containerElement.parentElement.insertBefore(this.physicsConfiguration, this.containerElement);
  26119. this.optionsDiv = document.createElement("div");
  26120. this.optionsDiv.style.fontSize = "14px";
  26121. this.optionsDiv.style.fontFamily = "verdana";
  26122. this.containerElement.parentElement.insertBefore(this.optionsDiv, this.containerElement);
  26123. var rangeElement;
  26124. rangeElement = document.getElementById('graph_BH_gc');
  26125. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_gc', -1, "physics_barnesHut_gravitationalConstant");
  26126. rangeElement = document.getElementById('graph_BH_cg');
  26127. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_cg', 1, "physics_centralGravity");
  26128. rangeElement = document.getElementById('graph_BH_sc');
  26129. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sc', 1, "physics_springConstant");
  26130. rangeElement = document.getElementById('graph_BH_sl');
  26131. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sl', 1, "physics_springLength");
  26132. rangeElement = document.getElementById('graph_BH_damp');
  26133. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_damp', 1, "physics_damping");
  26134. rangeElement = document.getElementById('graph_R_nd');
  26135. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_nd', 1, "physics_repulsion_nodeDistance");
  26136. rangeElement = document.getElementById('graph_R_cg');
  26137. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_cg', 1, "physics_centralGravity");
  26138. rangeElement = document.getElementById('graph_R_sc');
  26139. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sc', 1, "physics_springConstant");
  26140. rangeElement = document.getElementById('graph_R_sl');
  26141. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sl', 1, "physics_springLength");
  26142. rangeElement = document.getElementById('graph_R_damp');
  26143. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_damp', 1, "physics_damping");
  26144. rangeElement = document.getElementById('graph_H_nd');
  26145. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  26146. rangeElement = document.getElementById('graph_H_cg');
  26147. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_cg', 1, "physics_centralGravity");
  26148. rangeElement = document.getElementById('graph_H_sc');
  26149. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sc', 1, "physics_springConstant");
  26150. rangeElement = document.getElementById('graph_H_sl');
  26151. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sl', 1, "physics_springLength");
  26152. rangeElement = document.getElementById('graph_H_damp');
  26153. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_damp', 1, "physics_damping");
  26154. rangeElement = document.getElementById('graph_H_direction');
  26155. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_direction', hierarchicalLayoutDirections, "hierarchicalLayout_direction");
  26156. rangeElement = document.getElementById('graph_H_levsep');
  26157. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_levsep', 1, "hierarchicalLayout_levelSeparation");
  26158. rangeElement = document.getElementById('graph_H_nspac');
  26159. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nspac', 1, "hierarchicalLayout_nodeSpacing");
  26160. var radioButton1 = document.getElementById("graph_physicsMethod1");
  26161. var radioButton2 = document.getElementById("graph_physicsMethod2");
  26162. var radioButton3 = document.getElementById("graph_physicsMethod3");
  26163. radioButton2.checked = true;
  26164. if (this.constants.physics.barnesHut.enabled) {
  26165. radioButton1.checked = true;
  26166. }
  26167. if (this.constants.hierarchicalLayout.enabled) {
  26168. radioButton3.checked = true;
  26169. }
  26170. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  26171. var graph_repositionNodes = document.getElementById("graph_repositionNodes");
  26172. var graph_generateOptions = document.getElementById("graph_generateOptions");
  26173. graph_toggleSmooth.onclick = graphToggleSmoothCurves.bind(this);
  26174. graph_repositionNodes.onclick = graphRepositionNodes.bind(this);
  26175. graph_generateOptions.onclick = graphGenerateOptions.bind(this);
  26176. if (this.constants.smoothCurves == true && this.constants.dynamicSmoothCurves == false) {
  26177. graph_toggleSmooth.style.background = "#A4FF56";
  26178. }
  26179. else {
  26180. graph_toggleSmooth.style.background = "#FF8532";
  26181. }
  26182. switchConfigurations.apply(this);
  26183. radioButton1.onchange = switchConfigurations.bind(this);
  26184. radioButton2.onchange = switchConfigurations.bind(this);
  26185. radioButton3.onchange = switchConfigurations.bind(this);
  26186. }
  26187. };
  26188. /**
  26189. * This overwrites the this.constants.
  26190. *
  26191. * @param constantsVariableName
  26192. * @param value
  26193. * @private
  26194. */
  26195. exports._overWriteGraphConstants = function (constantsVariableName, value) {
  26196. var nameArray = constantsVariableName.split("_");
  26197. if (nameArray.length == 1) {
  26198. this.constants[nameArray[0]] = value;
  26199. }
  26200. else if (nameArray.length == 2) {
  26201. this.constants[nameArray[0]][nameArray[1]] = value;
  26202. }
  26203. else if (nameArray.length == 3) {
  26204. this.constants[nameArray[0]][nameArray[1]][nameArray[2]] = value;
  26205. }
  26206. };
  26207. /**
  26208. * this function is bound to the toggle smooth curves button. That is also why it is not in the prototype.
  26209. */
  26210. function graphToggleSmoothCurves () {
  26211. this.constants.smoothCurves.enabled = !this.constants.smoothCurves.enabled;
  26212. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  26213. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  26214. else {graph_toggleSmooth.style.background = "#FF8532";}
  26215. this._configureSmoothCurves(false);
  26216. }
  26217. /**
  26218. * this function is used to scramble the nodes
  26219. *
  26220. */
  26221. function graphRepositionNodes () {
  26222. for (var nodeId in this.calculationNodes) {
  26223. if (this.calculationNodes.hasOwnProperty(nodeId)) {
  26224. this.calculationNodes[nodeId].vx = 0; this.calculationNodes[nodeId].vy = 0;
  26225. this.calculationNodes[nodeId].fx = 0; this.calculationNodes[nodeId].fy = 0;
  26226. }
  26227. }
  26228. if (this.constants.hierarchicalLayout.enabled == true) {
  26229. this._setupHierarchicalLayout();
  26230. showValueOfRange.call(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  26231. showValueOfRange.call(this, 'graph_H_cg', 1, "physics_centralGravity");
  26232. showValueOfRange.call(this, 'graph_H_sc', 1, "physics_springConstant");
  26233. showValueOfRange.call(this, 'graph_H_sl', 1, "physics_springLength");
  26234. showValueOfRange.call(this, 'graph_H_damp', 1, "physics_damping");
  26235. }
  26236. else {
  26237. this.repositionNodes();
  26238. }
  26239. this.moving = true;
  26240. this.start();
  26241. }
  26242. /**
  26243. * this is used to generate an options file from the playing with physics system.
  26244. */
  26245. function graphGenerateOptions () {
  26246. var options = "No options are required, default values used.";
  26247. var optionsSpecific = [];
  26248. var radioButton1 = document.getElementById("graph_physicsMethod1");
  26249. var radioButton2 = document.getElementById("graph_physicsMethod2");
  26250. if (radioButton1.checked == true) {
  26251. if (this.constants.physics.barnesHut.gravitationalConstant != this.backupConstants.physics.barnesHut.gravitationalConstant) {optionsSpecific.push("gravitationalConstant: " + this.constants.physics.barnesHut.gravitationalConstant);}
  26252. if (this.constants.physics.centralGravity != this.backupConstants.physics.barnesHut.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  26253. if (this.constants.physics.springLength != this.backupConstants.physics.barnesHut.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  26254. if (this.constants.physics.springConstant != this.backupConstants.physics.barnesHut.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  26255. if (this.constants.physics.damping != this.backupConstants.physics.barnesHut.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  26256. if (optionsSpecific.length != 0) {
  26257. options = "var options = {";
  26258. options += "physics: {barnesHut: {";
  26259. for (var i = 0; i < optionsSpecific.length; i++) {
  26260. options += optionsSpecific[i];
  26261. if (i < optionsSpecific.length - 1) {
  26262. options += ", "
  26263. }
  26264. }
  26265. options += '}}'
  26266. }
  26267. if (this.constants.smoothCurves.enabled != this.backupConstants.smoothCurves.enabled) {
  26268. if (optionsSpecific.length == 0) {options = "var options = {";}
  26269. else {options += ", "}
  26270. options += "smoothCurves: " + this.constants.smoothCurves.enabled;
  26271. }
  26272. if (options != "No options are required, default values used.") {
  26273. options += '};'
  26274. }
  26275. }
  26276. else if (radioButton2.checked == true) {
  26277. options = "var options = {";
  26278. options += "physics: {barnesHut: {enabled: false}";
  26279. if (this.constants.physics.repulsion.nodeDistance != this.backupConstants.physics.repulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.repulsion.nodeDistance);}
  26280. if (this.constants.physics.centralGravity != this.backupConstants.physics.repulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  26281. if (this.constants.physics.springLength != this.backupConstants.physics.repulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  26282. if (this.constants.physics.springConstant != this.backupConstants.physics.repulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  26283. if (this.constants.physics.damping != this.backupConstants.physics.repulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  26284. if (optionsSpecific.length != 0) {
  26285. options += ", repulsion: {";
  26286. for (var i = 0; i < optionsSpecific.length; i++) {
  26287. options += optionsSpecific[i];
  26288. if (i < optionsSpecific.length - 1) {
  26289. options += ", "
  26290. }
  26291. }
  26292. options += '}}'
  26293. }
  26294. if (optionsSpecific.length == 0) {options += "}"}
  26295. if (this.constants.smoothCurves != this.backupConstants.smoothCurves) {
  26296. options += ", smoothCurves: " + this.constants.smoothCurves;
  26297. }
  26298. options += '};'
  26299. }
  26300. else {
  26301. options = "var options = {";
  26302. if (this.constants.physics.hierarchicalRepulsion.nodeDistance != this.backupConstants.physics.hierarchicalRepulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.hierarchicalRepulsion.nodeDistance);}
  26303. if (this.constants.physics.centralGravity != this.backupConstants.physics.hierarchicalRepulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  26304. if (this.constants.physics.springLength != this.backupConstants.physics.hierarchicalRepulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  26305. if (this.constants.physics.springConstant != this.backupConstants.physics.hierarchicalRepulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  26306. if (this.constants.physics.damping != this.backupConstants.physics.hierarchicalRepulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  26307. if (optionsSpecific.length != 0) {
  26308. options += "physics: {hierarchicalRepulsion: {";
  26309. for (var i = 0; i < optionsSpecific.length; i++) {
  26310. options += optionsSpecific[i];
  26311. if (i < optionsSpecific.length - 1) {
  26312. options += ", ";
  26313. }
  26314. }
  26315. options += '}},';
  26316. }
  26317. options += 'hierarchicalLayout: {';
  26318. optionsSpecific = [];
  26319. if (this.constants.hierarchicalLayout.direction != this.backupConstants.hierarchicalLayout.direction) {optionsSpecific.push("direction: " + this.constants.hierarchicalLayout.direction);}
  26320. if (Math.abs(this.constants.hierarchicalLayout.levelSeparation) != this.backupConstants.hierarchicalLayout.levelSeparation) {optionsSpecific.push("levelSeparation: " + this.constants.hierarchicalLayout.levelSeparation);}
  26321. if (this.constants.hierarchicalLayout.nodeSpacing != this.backupConstants.hierarchicalLayout.nodeSpacing) {optionsSpecific.push("nodeSpacing: " + this.constants.hierarchicalLayout.nodeSpacing);}
  26322. if (optionsSpecific.length != 0) {
  26323. for (var i = 0; i < optionsSpecific.length; i++) {
  26324. options += optionsSpecific[i];
  26325. if (i < optionsSpecific.length - 1) {
  26326. options += ", "
  26327. }
  26328. }
  26329. options += '}'
  26330. }
  26331. else {
  26332. options += "enabled:true}";
  26333. }
  26334. options += '};'
  26335. }
  26336. this.optionsDiv.innerHTML = options;
  26337. }
  26338. /**
  26339. * this is used to switch between barnesHut, repulsion and hierarchical.
  26340. *
  26341. */
  26342. function switchConfigurations () {
  26343. var ids = ["graph_BH_table", "graph_R_table", "graph_H_table"];
  26344. var radioButton = document.querySelector('input[name="graph_physicsMethod"]:checked').value;
  26345. var tableId = "graph_" + radioButton + "_table";
  26346. var table = document.getElementById(tableId);
  26347. table.style.display = "block";
  26348. for (var i = 0; i < ids.length; i++) {
  26349. if (ids[i] != tableId) {
  26350. table = document.getElementById(ids[i]);
  26351. table.style.display = "none";
  26352. }
  26353. }
  26354. this._restoreNodes();
  26355. if (radioButton == "R") {
  26356. this.constants.hierarchicalLayout.enabled = false;
  26357. this.constants.physics.hierarchicalRepulsion.enabled = false;
  26358. this.constants.physics.barnesHut.enabled = false;
  26359. }
  26360. else if (radioButton == "H") {
  26361. if (this.constants.hierarchicalLayout.enabled == false) {
  26362. this.constants.hierarchicalLayout.enabled = true;
  26363. this.constants.physics.hierarchicalRepulsion.enabled = true;
  26364. this.constants.physics.barnesHut.enabled = false;
  26365. this.constants.smoothCurves.enabled = false;
  26366. this._setupHierarchicalLayout();
  26367. }
  26368. }
  26369. else {
  26370. this.constants.hierarchicalLayout.enabled = false;
  26371. this.constants.physics.hierarchicalRepulsion.enabled = false;
  26372. this.constants.physics.barnesHut.enabled = true;
  26373. }
  26374. this._loadSelectedForceSolver();
  26375. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  26376. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  26377. else {graph_toggleSmooth.style.background = "#FF8532";}
  26378. this.moving = true;
  26379. this.start();
  26380. }
  26381. /**
  26382. * this generates the ranges depending on the iniital values.
  26383. *
  26384. * @param id
  26385. * @param map
  26386. * @param constantsVariableName
  26387. */
  26388. function showValueOfRange (id,map,constantsVariableName) {
  26389. var valueId = id + "_value";
  26390. var rangeValue = document.getElementById(id).value;
  26391. if (Array.isArray(map)) {
  26392. document.getElementById(valueId).value = map[parseInt(rangeValue)];
  26393. this._overWriteGraphConstants(constantsVariableName,map[parseInt(rangeValue)]);
  26394. }
  26395. else {
  26396. document.getElementById(valueId).value = parseInt(map) * parseFloat(rangeValue);
  26397. this._overWriteGraphConstants(constantsVariableName, parseInt(map) * parseFloat(rangeValue));
  26398. }
  26399. if (constantsVariableName == "hierarchicalLayout_direction" ||
  26400. constantsVariableName == "hierarchicalLayout_levelSeparation" ||
  26401. constantsVariableName == "hierarchicalLayout_nodeSpacing") {
  26402. this._setupHierarchicalLayout();
  26403. }
  26404. this.moving = true;
  26405. this.start();
  26406. }
  26407. /***/ },
  26408. /* 61 */
  26409. /***/ function(module, exports, __webpack_require__) {
  26410. /**
  26411. * Calculate the forces the nodes apply on each other based on a repulsion field.
  26412. * This field is linearly approximated.
  26413. *
  26414. * @private
  26415. */
  26416. exports._calculateNodeForces = function () {
  26417. var dx, dy, angle, distance, fx, fy, combinedClusterSize,
  26418. repulsingForce, node1, node2, i, j;
  26419. var nodes = this.calculationNodes;
  26420. var nodeIndices = this.calculationNodeIndices;
  26421. // approximation constants
  26422. var a_base = -2 / 3;
  26423. var b = 4 / 3;
  26424. // repulsing forces between nodes
  26425. var nodeDistance = this.constants.physics.repulsion.nodeDistance;
  26426. var minimumDistance = nodeDistance;
  26427. // we loop from i over all but the last entree in the array
  26428. // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  26429. for (i = 0; i < nodeIndices.length - 1; i++) {
  26430. node1 = nodes[nodeIndices[i]];
  26431. for (j = i + 1; j < nodeIndices.length; j++) {
  26432. node2 = nodes[nodeIndices[j]];
  26433. combinedClusterSize = node1.clusterSize + node2.clusterSize - 2;
  26434. dx = node2.x - node1.x;
  26435. dy = node2.y - node1.y;
  26436. distance = Math.sqrt(dx * dx + dy * dy);
  26437. // same condition as BarnesHut, making sure nodes are never 100% overlapping.
  26438. if (distance == 0) {
  26439. distance = 0.1*Math.random();
  26440. dx = distance;
  26441. }
  26442. minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification));
  26443. var a = a_base / minimumDistance;
  26444. if (distance < 2 * minimumDistance) {
  26445. if (distance < 0.5 * minimumDistance) {
  26446. repulsingForce = 1.0;
  26447. }
  26448. else {
  26449. repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness))
  26450. }
  26451. // amplify the repulsion for clusters.
  26452. repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification;
  26453. repulsingForce = repulsingForce / Math.max(distance,0.01*minimumDistance);
  26454. fx = dx * repulsingForce;
  26455. fy = dy * repulsingForce;
  26456. node1.fx -= fx;
  26457. node1.fy -= fy;
  26458. node2.fx += fx;
  26459. node2.fy += fy;
  26460. }
  26461. }
  26462. }
  26463. };
  26464. /***/ },
  26465. /* 62 */
  26466. /***/ function(module, exports, __webpack_require__) {
  26467. /**
  26468. * Calculate the forces the nodes apply on eachother based on a repulsion field.
  26469. * This field is linearly approximated.
  26470. *
  26471. * @private
  26472. */
  26473. exports._calculateNodeForces = function () {
  26474. var dx, dy, distance, fx, fy,
  26475. repulsingForce, node1, node2, i, j;
  26476. var nodes = this.calculationNodes;
  26477. var nodeIndices = this.calculationNodeIndices;
  26478. // repulsing forces between nodes
  26479. var nodeDistance = this.constants.physics.hierarchicalRepulsion.nodeDistance;
  26480. // we loop from i over all but the last entree in the array
  26481. // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  26482. for (i = 0; i < nodeIndices.length - 1; i++) {
  26483. node1 = nodes[nodeIndices[i]];
  26484. for (j = i + 1; j < nodeIndices.length; j++) {
  26485. node2 = nodes[nodeIndices[j]];
  26486. // nodes only affect nodes on their level
  26487. if (node1.level == node2.level) {
  26488. dx = node2.x - node1.x;
  26489. dy = node2.y - node1.y;
  26490. distance = Math.sqrt(dx * dx + dy * dy);
  26491. var steepness = 0.05;
  26492. if (distance < nodeDistance) {
  26493. repulsingForce = -Math.pow(steepness*distance,2) + Math.pow(steepness*nodeDistance,2);
  26494. }
  26495. else {
  26496. repulsingForce = 0;
  26497. }
  26498. // normalize force with
  26499. if (distance == 0) {
  26500. distance = 0.01;
  26501. }
  26502. else {
  26503. repulsingForce = repulsingForce / distance;
  26504. }
  26505. fx = dx * repulsingForce;
  26506. fy = dy * repulsingForce;
  26507. node1.fx -= fx;
  26508. node1.fy -= fy;
  26509. node2.fx += fx;
  26510. node2.fy += fy;
  26511. }
  26512. }
  26513. }
  26514. };
  26515. /**
  26516. * this function calculates the effects of the springs in the case of unsmooth curves.
  26517. *
  26518. * @private
  26519. */
  26520. exports._calculateHierarchicalSpringForces = function () {
  26521. var edgeLength, edge, edgeId;
  26522. var dx, dy, fx, fy, springForce, distance;
  26523. var edges = this.edges;
  26524. var nodes = this.calculationNodes;
  26525. var nodeIndices = this.calculationNodeIndices;
  26526. for (var i = 0; i < nodeIndices.length; i++) {
  26527. var node1 = nodes[nodeIndices[i]];
  26528. node1.springFx = 0;
  26529. node1.springFy = 0;
  26530. }
  26531. // forces caused by the edges, modelled as springs
  26532. for (edgeId in edges) {
  26533. if (edges.hasOwnProperty(edgeId)) {
  26534. edge = edges[edgeId];
  26535. if (edge.connected) {
  26536. // only calculate forces if nodes are in the same sector
  26537. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  26538. edgeLength = edge.physics.springLength;
  26539. // this implies that the edges between big clusters are longer
  26540. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  26541. dx = (edge.from.x - edge.to.x);
  26542. dy = (edge.from.y - edge.to.y);
  26543. distance = Math.sqrt(dx * dx + dy * dy);
  26544. if (distance == 0) {
  26545. distance = 0.01;
  26546. }
  26547. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  26548. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  26549. fx = dx * springForce;
  26550. fy = dy * springForce;
  26551. if (edge.to.level != edge.from.level) {
  26552. edge.to.springFx -= fx;
  26553. edge.to.springFy -= fy;
  26554. edge.from.springFx += fx;
  26555. edge.from.springFy += fy;
  26556. }
  26557. else {
  26558. var factor = 0.5;
  26559. edge.to.fx -= factor*fx;
  26560. edge.to.fy -= factor*fy;
  26561. edge.from.fx += factor*fx;
  26562. edge.from.fy += factor*fy;
  26563. }
  26564. }
  26565. }
  26566. }
  26567. }
  26568. // normalize spring forces
  26569. var springForce = 1;
  26570. var springFx, springFy;
  26571. for (i = 0; i < nodeIndices.length; i++) {
  26572. var node = nodes[nodeIndices[i]];
  26573. springFx = Math.min(springForce,Math.max(-springForce,node.springFx));
  26574. springFy = Math.min(springForce,Math.max(-springForce,node.springFy));
  26575. node.fx += springFx;
  26576. node.fy += springFy;
  26577. }
  26578. // retain energy balance
  26579. var totalFx = 0;
  26580. var totalFy = 0;
  26581. for (i = 0; i < nodeIndices.length; i++) {
  26582. var node = nodes[nodeIndices[i]];
  26583. totalFx += node.fx;
  26584. totalFy += node.fy;
  26585. }
  26586. var correctionFx = totalFx / nodeIndices.length;
  26587. var correctionFy = totalFy / nodeIndices.length;
  26588. for (i = 0; i < nodeIndices.length; i++) {
  26589. var node = nodes[nodeIndices[i]];
  26590. node.fx -= correctionFx;
  26591. node.fy -= correctionFy;
  26592. }
  26593. };
  26594. /***/ },
  26595. /* 63 */
  26596. /***/ function(module, exports, __webpack_require__) {
  26597. /**
  26598. * This function calculates the forces the nodes apply on eachother based on a gravitational model.
  26599. * The Barnes Hut method is used to speed up this N-body simulation.
  26600. *
  26601. * @private
  26602. */
  26603. exports._calculateNodeForces = function() {
  26604. if (this.constants.physics.barnesHut.gravitationalConstant != 0) {
  26605. var node;
  26606. var nodes = this.calculationNodes;
  26607. var nodeIndices = this.calculationNodeIndices;
  26608. var nodeCount = nodeIndices.length;
  26609. this._formBarnesHutTree(nodes,nodeIndices);
  26610. var barnesHutTree = this.barnesHutTree;
  26611. // place the nodes one by one recursively
  26612. for (var i = 0; i < nodeCount; i++) {
  26613. node = nodes[nodeIndices[i]];
  26614. if (node.options.mass > 0) {
  26615. // starting with root is irrelevant, it never passes the BarnesHut condition
  26616. this._getForceContribution(barnesHutTree.root.children.NW,node);
  26617. this._getForceContribution(barnesHutTree.root.children.NE,node);
  26618. this._getForceContribution(barnesHutTree.root.children.SW,node);
  26619. this._getForceContribution(barnesHutTree.root.children.SE,node);
  26620. }
  26621. }
  26622. }
  26623. };
  26624. /**
  26625. * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass.
  26626. * If a region contains a single node, we check if it is not itself, then we apply the force.
  26627. *
  26628. * @param parentBranch
  26629. * @param node
  26630. * @private
  26631. */
  26632. exports._getForceContribution = function(parentBranch,node) {
  26633. // we get no force contribution from an empty region
  26634. if (parentBranch.childrenCount > 0) {
  26635. var dx,dy,distance;
  26636. // get the distance from the center of mass to the node.
  26637. dx = parentBranch.centerOfMass.x - node.x;
  26638. dy = parentBranch.centerOfMass.y - node.y;
  26639. distance = Math.sqrt(dx * dx + dy * dy);
  26640. // BarnesHut condition
  26641. // original condition : s/d < thetaInverted = passed === d/s > 1/theta = passed
  26642. // calcSize = 1/s --> d * 1/s > 1/theta = passed
  26643. if (distance * parentBranch.calcSize > this.constants.physics.barnesHut.thetaInverted) {
  26644. // duplicate code to reduce function calls to speed up program
  26645. if (distance == 0) {
  26646. distance = 0.1*Math.random();
  26647. dx = distance;
  26648. }
  26649. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
  26650. var fx = dx * gravityForce;
  26651. var fy = dy * gravityForce;
  26652. node.fx += fx;
  26653. node.fy += fy;
  26654. }
  26655. else {
  26656. // Did not pass the condition, go into children if available
  26657. if (parentBranch.childrenCount == 4) {
  26658. this._getForceContribution(parentBranch.children.NW,node);
  26659. this._getForceContribution(parentBranch.children.NE,node);
  26660. this._getForceContribution(parentBranch.children.SW,node);
  26661. this._getForceContribution(parentBranch.children.SE,node);
  26662. }
  26663. else { // parentBranch must have only one node, if it was empty we wouldnt be here
  26664. if (parentBranch.children.data.id != node.id) { // if it is not self
  26665. // duplicate code to reduce function calls to speed up program
  26666. if (distance == 0) {
  26667. distance = 0.5*Math.random();
  26668. dx = distance;
  26669. }
  26670. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
  26671. var fx = dx * gravityForce;
  26672. var fy = dy * gravityForce;
  26673. node.fx += fx;
  26674. node.fy += fy;
  26675. }
  26676. }
  26677. }
  26678. }
  26679. };
  26680. /**
  26681. * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes.
  26682. *
  26683. * @param nodes
  26684. * @param nodeIndices
  26685. * @private
  26686. */
  26687. exports._formBarnesHutTree = function(nodes,nodeIndices) {
  26688. var node;
  26689. var nodeCount = nodeIndices.length;
  26690. var minX = Number.MAX_VALUE,
  26691. minY = Number.MAX_VALUE,
  26692. maxX =-Number.MAX_VALUE,
  26693. maxY =-Number.MAX_VALUE;
  26694. // get the range of the nodes
  26695. for (var i = 0; i < nodeCount; i++) {
  26696. var x = nodes[nodeIndices[i]].x;
  26697. var y = nodes[nodeIndices[i]].y;
  26698. if (nodes[nodeIndices[i]].options.mass > 0) {
  26699. if (x < minX) { minX = x; }
  26700. if (x > maxX) { maxX = x; }
  26701. if (y < minY) { minY = y; }
  26702. if (y > maxY) { maxY = y; }
  26703. }
  26704. }
  26705. // make the range a square
  26706. var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
  26707. if (sizeDiff > 0) {minY -= 0.5 * sizeDiff; maxY += 0.5 * sizeDiff;} // xSize > ySize
  26708. else {minX += 0.5 * sizeDiff; maxX -= 0.5 * sizeDiff;} // xSize < ySize
  26709. var minimumTreeSize = 1e-5;
  26710. var rootSize = Math.max(minimumTreeSize,Math.abs(maxX - minX));
  26711. var halfRootSize = 0.5 * rootSize;
  26712. var centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY);
  26713. // construct the barnesHutTree
  26714. var barnesHutTree = {
  26715. root:{
  26716. centerOfMass: {x:0, y:0},
  26717. mass:0,
  26718. range: {
  26719. minX: centerX-halfRootSize,maxX:centerX+halfRootSize,
  26720. minY: centerY-halfRootSize,maxY:centerY+halfRootSize
  26721. },
  26722. size: rootSize,
  26723. calcSize: 1 / rootSize,
  26724. children: { data:null},
  26725. maxWidth: 0,
  26726. level: 0,
  26727. childrenCount: 4
  26728. }
  26729. };
  26730. this._splitBranch(barnesHutTree.root);
  26731. // place the nodes one by one recursively
  26732. for (i = 0; i < nodeCount; i++) {
  26733. node = nodes[nodeIndices[i]];
  26734. if (node.options.mass > 0) {
  26735. this._placeInTree(barnesHutTree.root,node);
  26736. }
  26737. }
  26738. // make global
  26739. this.barnesHutTree = barnesHutTree
  26740. };
  26741. /**
  26742. * this updates the mass of a branch. this is increased by adding a node.
  26743. *
  26744. * @param parentBranch
  26745. * @param node
  26746. * @private
  26747. */
  26748. exports._updateBranchMass = function(parentBranch, node) {
  26749. var totalMass = parentBranch.mass + node.options.mass;
  26750. var totalMassInv = 1/totalMass;
  26751. parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass;
  26752. parentBranch.centerOfMass.x *= totalMassInv;
  26753. parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass;
  26754. parentBranch.centerOfMass.y *= totalMassInv;
  26755. parentBranch.mass = totalMass;
  26756. var biggestSize = Math.max(Math.max(node.height,node.radius),node.width);
  26757. parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth;
  26758. };
  26759. /**
  26760. * determine in which branch the node will be placed.
  26761. *
  26762. * @param parentBranch
  26763. * @param node
  26764. * @param skipMassUpdate
  26765. * @private
  26766. */
  26767. exports._placeInTree = function(parentBranch,node,skipMassUpdate) {
  26768. if (skipMassUpdate != true || skipMassUpdate === undefined) {
  26769. // update the mass of the branch.
  26770. this._updateBranchMass(parentBranch,node);
  26771. }
  26772. if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW
  26773. if (parentBranch.children.NW.range.maxY > node.y) { // in NW
  26774. this._placeInRegion(parentBranch,node,"NW");
  26775. }
  26776. else { // in SW
  26777. this._placeInRegion(parentBranch,node,"SW");
  26778. }
  26779. }
  26780. else { // in NE or SE
  26781. if (parentBranch.children.NW.range.maxY > node.y) { // in NE
  26782. this._placeInRegion(parentBranch,node,"NE");
  26783. }
  26784. else { // in SE
  26785. this._placeInRegion(parentBranch,node,"SE");
  26786. }
  26787. }
  26788. };
  26789. /**
  26790. * actually place the node in a region (or branch)
  26791. *
  26792. * @param parentBranch
  26793. * @param node
  26794. * @param region
  26795. * @private
  26796. */
  26797. exports._placeInRegion = function(parentBranch,node,region) {
  26798. switch (parentBranch.children[region].childrenCount) {
  26799. case 0: // place node here
  26800. parentBranch.children[region].children.data = node;
  26801. parentBranch.children[region].childrenCount = 1;
  26802. this._updateBranchMass(parentBranch.children[region],node);
  26803. break;
  26804. case 1: // convert into children
  26805. // if there are two nodes exactly overlapping (on init, on opening of cluster etc.)
  26806. // we move one node a pixel and we do not put it in the tree.
  26807. if (parentBranch.children[region].children.data.x == node.x &&
  26808. parentBranch.children[region].children.data.y == node.y) {
  26809. node.x += Math.random();
  26810. node.y += Math.random();
  26811. }
  26812. else {
  26813. this._splitBranch(parentBranch.children[region]);
  26814. this._placeInTree(parentBranch.children[region],node);
  26815. }
  26816. break;
  26817. case 4: // place in branch
  26818. this._placeInTree(parentBranch.children[region],node);
  26819. break;
  26820. }
  26821. };
  26822. /**
  26823. * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch
  26824. * after the split is complete.
  26825. *
  26826. * @param parentBranch
  26827. * @private
  26828. */
  26829. exports._splitBranch = function(parentBranch) {
  26830. // if the branch is shaded with a node, replace the node in the new subset.
  26831. var containedNode = null;
  26832. if (parentBranch.childrenCount == 1) {
  26833. containedNode = parentBranch.children.data;
  26834. parentBranch.mass = 0; parentBranch.centerOfMass.x = 0; parentBranch.centerOfMass.y = 0;
  26835. }
  26836. parentBranch.childrenCount = 4;
  26837. parentBranch.children.data = null;
  26838. this._insertRegion(parentBranch,"NW");
  26839. this._insertRegion(parentBranch,"NE");
  26840. this._insertRegion(parentBranch,"SW");
  26841. this._insertRegion(parentBranch,"SE");
  26842. if (containedNode != null) {
  26843. this._placeInTree(parentBranch,containedNode);
  26844. }
  26845. };
  26846. /**
  26847. * This function subdivides the region into four new segments.
  26848. * Specifically, this inserts a single new segment.
  26849. * It fills the children section of the parentBranch
  26850. *
  26851. * @param parentBranch
  26852. * @param region
  26853. * @param parentRange
  26854. * @private
  26855. */
  26856. exports._insertRegion = function(parentBranch, region) {
  26857. var minX,maxX,minY,maxY;
  26858. var childSize = 0.5 * parentBranch.size;
  26859. switch (region) {
  26860. case "NW":
  26861. minX = parentBranch.range.minX;
  26862. maxX = parentBranch.range.minX + childSize;
  26863. minY = parentBranch.range.minY;
  26864. maxY = parentBranch.range.minY + childSize;
  26865. break;
  26866. case "NE":
  26867. minX = parentBranch.range.minX + childSize;
  26868. maxX = parentBranch.range.maxX;
  26869. minY = parentBranch.range.minY;
  26870. maxY = parentBranch.range.minY + childSize;
  26871. break;
  26872. case "SW":
  26873. minX = parentBranch.range.minX;
  26874. maxX = parentBranch.range.minX + childSize;
  26875. minY = parentBranch.range.minY + childSize;
  26876. maxY = parentBranch.range.maxY;
  26877. break;
  26878. case "SE":
  26879. minX = parentBranch.range.minX + childSize;
  26880. maxX = parentBranch.range.maxX;
  26881. minY = parentBranch.range.minY + childSize;
  26882. maxY = parentBranch.range.maxY;
  26883. break;
  26884. }
  26885. parentBranch.children[region] = {
  26886. centerOfMass:{x:0,y:0},
  26887. mass:0,
  26888. range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY},
  26889. size: 0.5 * parentBranch.size,
  26890. calcSize: 2 * parentBranch.calcSize,
  26891. children: {data:null},
  26892. maxWidth: 0,
  26893. level: parentBranch.level+1,
  26894. childrenCount: 0
  26895. };
  26896. };
  26897. /**
  26898. * This function is for debugging purposed, it draws the tree.
  26899. *
  26900. * @param ctx
  26901. * @param color
  26902. * @private
  26903. */
  26904. exports._drawTree = function(ctx,color) {
  26905. if (this.barnesHutTree !== undefined) {
  26906. ctx.lineWidth = 1;
  26907. this._drawBranch(this.barnesHutTree.root,ctx,color);
  26908. }
  26909. };
  26910. /**
  26911. * This function is for debugging purposes. It draws the branches recursively.
  26912. *
  26913. * @param branch
  26914. * @param ctx
  26915. * @param color
  26916. * @private
  26917. */
  26918. exports._drawBranch = function(branch,ctx,color) {
  26919. if (color === undefined) {
  26920. color = "#FF0000";
  26921. }
  26922. if (branch.childrenCount == 4) {
  26923. this._drawBranch(branch.children.NW,ctx);
  26924. this._drawBranch(branch.children.NE,ctx);
  26925. this._drawBranch(branch.children.SE,ctx);
  26926. this._drawBranch(branch.children.SW,ctx);
  26927. }
  26928. ctx.strokeStyle = color;
  26929. ctx.beginPath();
  26930. ctx.moveTo(branch.range.minX,branch.range.minY);
  26931. ctx.lineTo(branch.range.maxX,branch.range.minY);
  26932. ctx.stroke();
  26933. ctx.beginPath();
  26934. ctx.moveTo(branch.range.maxX,branch.range.minY);
  26935. ctx.lineTo(branch.range.maxX,branch.range.maxY);
  26936. ctx.stroke();
  26937. ctx.beginPath();
  26938. ctx.moveTo(branch.range.maxX,branch.range.maxY);
  26939. ctx.lineTo(branch.range.minX,branch.range.maxY);
  26940. ctx.stroke();
  26941. ctx.beginPath();
  26942. ctx.moveTo(branch.range.minX,branch.range.maxY);
  26943. ctx.lineTo(branch.range.minX,branch.range.minY);
  26944. ctx.stroke();
  26945. /*
  26946. if (branch.mass > 0) {
  26947. ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass);
  26948. ctx.stroke();
  26949. }
  26950. */
  26951. };
  26952. /***/ },
  26953. /* 64 */
  26954. /***/ function(module, exports, __webpack_require__) {
  26955. /**
  26956. * Creation of the ClusterMixin var.
  26957. *
  26958. * This contains all the functions the Network object can use to employ clustering
  26959. */
  26960. /**
  26961. * This is only called in the constructor of the network object
  26962. *
  26963. */
  26964. exports.startWithClustering = function() {
  26965. // cluster if the data set is big
  26966. this.clusterToFit(this.constants.clustering.initialMaxNodes, true);
  26967. // updates the lables after clustering
  26968. this.updateLabels();
  26969. // this is called here because if clusterin is disabled, the start and stabilize are called in
  26970. // the setData function.
  26971. if (this.constants.stabilize == true) {
  26972. this._stabilize();
  26973. }
  26974. this.start();
  26975. };
  26976. /**
  26977. * This function clusters until the initialMaxNodes has been reached
  26978. *
  26979. * @param {Number} maxNumberOfNodes
  26980. * @param {Boolean} reposition
  26981. */
  26982. exports.clusterToFit = function(maxNumberOfNodes, reposition) {
  26983. var numberOfNodes = this.nodeIndices.length;
  26984. var maxLevels = 50;
  26985. var level = 0;
  26986. // we first cluster the hubs, then we pull in the outliers, repeat
  26987. while (numberOfNodes > maxNumberOfNodes && level < maxLevels) {
  26988. if (level % 3 == 0.0) {
  26989. this.forceAggregateHubs(true);
  26990. this.normalizeClusterLevels();
  26991. }
  26992. else {
  26993. this.increaseClusterLevel(); // this also includes a cluster normalization
  26994. }
  26995. this.forceAggregateHubs(true);
  26996. numberOfNodes = this.nodeIndices.length;
  26997. level += 1;
  26998. }
  26999. // after the clustering we reposition the nodes to reduce the initial chaos
  27000. if (level > 0 && reposition == true) {
  27001. this.repositionNodes();
  27002. }
  27003. this._updateCalculationNodes();
  27004. };
  27005. /**
  27006. * This function can be called to open up a specific cluster.
  27007. * It will unpack the cluster back one level.
  27008. *
  27009. * @param node | Node object: cluster to open.
  27010. */
  27011. exports.openCluster = function(node) {
  27012. var isMovingBeforeClustering = this.moving;
  27013. if (node.clusterSize > this.constants.clustering.sectorThreshold && this._nodeInActiveArea(node) &&
  27014. !(this._sector() == "default" && this.nodeIndices.length == 1)) {
  27015. // this loads a new sector, loads the nodes and edges and nodeIndices of it.
  27016. this._addSector(node);
  27017. var level = 0;
  27018. // we decluster until we reach a decent number of nodes
  27019. while ((this.nodeIndices.length < this.constants.clustering.initialMaxNodes) && (level < 10)) {
  27020. this.decreaseClusterLevel();
  27021. level += 1;
  27022. }
  27023. }
  27024. else {
  27025. this._expandClusterNode(node,false,true);
  27026. // update the index list and labels
  27027. this._updateNodeIndexList();
  27028. this._updateCalculationNodes();
  27029. this.updateLabels();
  27030. }
  27031. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  27032. if (this.moving != isMovingBeforeClustering) {
  27033. this.start();
  27034. }
  27035. };
  27036. /**
  27037. * This calls the updateClustes with default arguments
  27038. */
  27039. exports.updateClustersDefault = function() {
  27040. if (this.constants.clustering.enabled == true && this.constants.clustering.clusterByZoom == true) {
  27041. this.updateClusters(0,false,false);
  27042. }
  27043. };
  27044. /**
  27045. * This function can be called to increase the cluster level. This means that the nodes with only one edge connection will
  27046. * be clustered with their connected node. This can be repeated as many times as needed.
  27047. * This can be called externally (by a keybind for instance) to reduce the complexity of big datasets.
  27048. */
  27049. exports.increaseClusterLevel = function() {
  27050. this.updateClusters(-1,false,true);
  27051. };
  27052. /**
  27053. * This function can be called to decrease the cluster level. This means that the nodes with only one edge connection will
  27054. * be unpacked if they are a cluster. This can be repeated as many times as needed.
  27055. * This can be called externally (by a key-bind for instance) to look into clusters without zooming.
  27056. */
  27057. exports.decreaseClusterLevel = function() {
  27058. this.updateClusters(1,false,true);
  27059. };
  27060. /**
  27061. * This is the main clustering function. It clusters and declusters on zoom or forced
  27062. * This function clusters on zoom, it can be called with a predefined zoom direction
  27063. * If out, check if we can form clusters, if in, check if we can open clusters.
  27064. * This function is only called from _zoom()
  27065. *
  27066. * @param {Number} zoomDirection | -1 / 0 / +1 for zoomOut / determineByZoom / zoomIn
  27067. * @param {Boolean} recursive | enabled or disable recursive calling of the opening of clusters
  27068. * @param {Boolean} force | enabled or disable forcing
  27069. * @param {Boolean} doNotStart | if true do not call start
  27070. *
  27071. */
  27072. exports.updateClusters = function(zoomDirection,recursive,force,doNotStart) {
  27073. var isMovingBeforeClustering = this.moving;
  27074. var amountOfNodes = this.nodeIndices.length;
  27075. var detectedZoomingIn = (this.previousScale < this.scale && zoomDirection == 0);
  27076. var detectedZoomingOut = (this.previousScale > this.scale && zoomDirection == 0);
  27077. // on zoom out collapse the sector if the scale is at the level the sector was made
  27078. if (detectedZoomingOut == true) {
  27079. this._collapseSector();
  27080. }
  27081. // check if we zoom in or out
  27082. if (detectedZoomingOut == true || zoomDirection == -1) { // zoom out
  27083. // forming clusters when forced pulls outliers in. When not forced, the edge length of the
  27084. // outer nodes determines if it is being clustered
  27085. this._formClusters(force);
  27086. }
  27087. else if (detectedZoomingIn == true || zoomDirection == 1) { // zoom in
  27088. if (force == true) {
  27089. // _openClusters checks for each node if the formationScale of the cluster is smaller than
  27090. // the current scale and if so, declusters. When forced, all clusters are reduced by one step
  27091. this._openClusters(recursive,force);
  27092. }
  27093. else {
  27094. // if a cluster takes up a set percentage of the active window
  27095. //this._openClustersBySize();
  27096. this._openClusters(recursive, false);
  27097. }
  27098. }
  27099. this._updateNodeIndexList();
  27100. // if a cluster was NOT formed and the user zoomed out, we try clustering by hubs
  27101. if (this.nodeIndices.length == amountOfNodes && (detectedZoomingOut == true || zoomDirection == -1)) {
  27102. this._aggregateHubs(force);
  27103. this._updateNodeIndexList();
  27104. }
  27105. // we now reduce chains.
  27106. if (detectedZoomingOut == true || zoomDirection == -1) { // zoom out
  27107. this.handleChains();
  27108. this._updateNodeIndexList();
  27109. }
  27110. this.previousScale = this.scale;
  27111. // update labels
  27112. this.updateLabels();
  27113. // if a cluster was formed, we increase the clusterSession
  27114. if (this.nodeIndices.length < amountOfNodes) { // this means a clustering operation has taken place
  27115. this.clusterSession += 1;
  27116. // if clusters have been made, we normalize the cluster level
  27117. this.normalizeClusterLevels();
  27118. }
  27119. if (doNotStart == false || doNotStart === undefined) {
  27120. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  27121. if (this.moving != isMovingBeforeClustering) {
  27122. this.start();
  27123. }
  27124. }
  27125. this._updateCalculationNodes();
  27126. };
  27127. /**
  27128. * This function handles the chains. It is called on every updateClusters().
  27129. */
  27130. exports.handleChains = function() {
  27131. // after clustering we check how many chains there are
  27132. var chainPercentage = this._getChainFraction();
  27133. if (chainPercentage > this.constants.clustering.chainThreshold) {
  27134. this._reduceAmountOfChains(1 - this.constants.clustering.chainThreshold / chainPercentage)
  27135. }
  27136. };
  27137. /**
  27138. * this functions starts clustering by hubs
  27139. * The minimum hub threshold is set globally
  27140. *
  27141. * @private
  27142. */
  27143. exports._aggregateHubs = function(force) {
  27144. this._getHubSize();
  27145. this._formClustersByHub(force,false);
  27146. };
  27147. /**
  27148. * This function forces hubs to form.
  27149. *
  27150. */
  27151. exports.forceAggregateHubs = function(doNotStart) {
  27152. var isMovingBeforeClustering = this.moving;
  27153. var amountOfNodes = this.nodeIndices.length;
  27154. this._aggregateHubs(true);
  27155. // update the index list, dynamic edges and labels
  27156. this._updateNodeIndexList();
  27157. this.updateLabels();
  27158. this._updateCalculationNodes();
  27159. // if a cluster was formed, we increase the clusterSession
  27160. if (this.nodeIndices.length != amountOfNodes) {
  27161. this.clusterSession += 1;
  27162. }
  27163. if (doNotStart == false || doNotStart === undefined) {
  27164. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  27165. if (this.moving != isMovingBeforeClustering) {
  27166. this.start();
  27167. }
  27168. }
  27169. };
  27170. /**
  27171. * If a cluster takes up more than a set percentage of the screen, open the cluster
  27172. *
  27173. * @private
  27174. */
  27175. exports._openClustersBySize = function() {
  27176. if (this.constants.clustering.clusterByZoom == true) {
  27177. for (var nodeId in this.nodes) {
  27178. if (this.nodes.hasOwnProperty(nodeId)) {
  27179. var node = this.nodes[nodeId];
  27180. if (node.inView() == true) {
  27181. if ((node.width * this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  27182. (node.height * this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  27183. this.openCluster(node);
  27184. }
  27185. }
  27186. }
  27187. }
  27188. }
  27189. };
  27190. /**
  27191. * This function loops over all nodes in the nodeIndices list. For each node it checks if it is a cluster and if it
  27192. * has to be opened based on the current zoom level.
  27193. *
  27194. * @private
  27195. */
  27196. exports._openClusters = function(recursive,force) {
  27197. for (var i = 0; i < this.nodeIndices.length; i++) {
  27198. var node = this.nodes[this.nodeIndices[i]];
  27199. this._expandClusterNode(node,recursive,force);
  27200. this._updateCalculationNodes();
  27201. }
  27202. };
  27203. /**
  27204. * This function checks if a node has to be opened. This is done by checking the zoom level.
  27205. * If the node contains child nodes, this function is recursively called on the child nodes as well.
  27206. * This recursive behaviour is optional and can be set by the recursive argument.
  27207. *
  27208. * @param {Node} parentNode | to check for cluster and expand
  27209. * @param {Boolean} recursive | enabled or disable recursive calling
  27210. * @param {Boolean} force | enabled or disable forcing
  27211. * @param {Boolean} [openAll] | This will recursively force all nodes in the parent to be released
  27212. * @private
  27213. */
  27214. exports._expandClusterNode = function(parentNode, recursive, force, openAll) {
  27215. // first check if node is a cluster
  27216. if (parentNode.clusterSize > 1) {
  27217. if (openAll === undefined) {
  27218. openAll = false;
  27219. }
  27220. // this means that on a double tap event or a zoom event, the cluster fully unpacks if it is smaller than 20
  27221. recursive = openAll || recursive;
  27222. // if the last child has been added on a smaller scale than current scale decluster
  27223. if (parentNode.formationScale < this.scale || force == true) {
  27224. // we will check if any of the contained child nodes should be removed from the cluster
  27225. for (var containedNodeId in parentNode.containedNodes) {
  27226. if (parentNode.containedNodes.hasOwnProperty(containedNodeId)) {
  27227. var childNode = parentNode.containedNodes[containedNodeId];
  27228. // force expand will expand the largest cluster size clusters. Since we cluster from outside in, we assume that
  27229. // the largest cluster is the one that comes from outside
  27230. if (force == true) {
  27231. if (childNode.clusterSession == parentNode.clusterSessions[parentNode.clusterSessions.length-1]
  27232. || openAll) {
  27233. this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
  27234. }
  27235. }
  27236. else {
  27237. if (this._nodeInActiveArea(parentNode)) {
  27238. this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
  27239. }
  27240. }
  27241. }
  27242. }
  27243. }
  27244. }
  27245. };
  27246. /**
  27247. * ONLY CALLED FROM _expandClusterNode
  27248. *
  27249. * This function will expel a child_node from a parent_node. This is to de-cluster the node. This function will remove
  27250. * the child node from the parent contained_node object and put it back into the global nodes object.
  27251. * The same holds for the edge that was connected to the child node. It is moved back into the global edges object.
  27252. *
  27253. * @param {Node} parentNode | the parent node
  27254. * @param {String} containedNodeId | child_node id as it is contained in the containedNodes object of the parent node
  27255. * @param {Boolean} recursive | This will also check if the child needs to be expanded.
  27256. * With force and recursive both true, the entire cluster is unpacked
  27257. * @param {Boolean} force | This will disregard the zoom level and will expel this child from the parent
  27258. * @param {Boolean} openAll | This will recursively force all nodes in the parent to be released
  27259. * @private
  27260. */
  27261. exports._expelChildFromParent = function(parentNode, containedNodeId, recursive, force, openAll) {
  27262. var childNode = parentNode.containedNodes[containedNodeId]
  27263. // if child node has been added on smaller scale than current, kick out
  27264. if (childNode.formationScale < this.scale || force == true) {
  27265. // unselect all selected items
  27266. this._unselectAll();
  27267. // put the child node back in the global nodes object
  27268. this.nodes[containedNodeId] = childNode;
  27269. // release the contained edges from this childNode back into the global edges
  27270. this._releaseContainedEdges(parentNode,childNode);
  27271. // reconnect rerouted edges to the childNode
  27272. this._connectEdgeBackToChild(parentNode,childNode);
  27273. // validate all edges in dynamicEdges
  27274. this._validateEdges(parentNode);
  27275. // undo the changes from the clustering operation on the parent node
  27276. parentNode.options.mass -= childNode.options.mass;
  27277. parentNode.clusterSize -= childNode.clusterSize;
  27278. parentNode.options.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*(parentNode.clusterSize-1));
  27279. // place the child node near the parent, not at the exact same location to avoid chaos in the system
  27280. childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random());
  27281. childNode.y = parentNode.y + parentNode.growthIndicator * (0.5 - Math.random());
  27282. // remove node from the list
  27283. delete parentNode.containedNodes[containedNodeId];
  27284. // check if there are other childs with this clusterSession in the parent.
  27285. var othersPresent = false;
  27286. for (var childNodeId in parentNode.containedNodes) {
  27287. if (parentNode.containedNodes.hasOwnProperty(childNodeId)) {
  27288. if (parentNode.containedNodes[childNodeId].clusterSession == childNode.clusterSession) {
  27289. othersPresent = true;
  27290. break;
  27291. }
  27292. }
  27293. }
  27294. // if there are no others, remove the cluster session from the list
  27295. if (othersPresent == false) {
  27296. parentNode.clusterSessions.pop();
  27297. }
  27298. this._repositionBezierNodes(childNode);
  27299. // this._repositionBezierNodes(parentNode);
  27300. // remove the clusterSession from the child node
  27301. childNode.clusterSession = 0;
  27302. // recalculate the size of the node on the next time the node is rendered
  27303. parentNode.clearSizeCache();
  27304. // restart the simulation to reorganise all nodes
  27305. this.moving = true;
  27306. }
  27307. // check if a further expansion step is possible if recursivity is enabled
  27308. if (recursive == true) {
  27309. this._expandClusterNode(childNode,recursive,force,openAll);
  27310. }
  27311. };
  27312. /**
  27313. * position the bezier nodes at the center of the edges
  27314. *
  27315. * @param node
  27316. * @private
  27317. */
  27318. exports._repositionBezierNodes = function(node) {
  27319. for (var i = 0; i < node.dynamicEdges.length; i++) {
  27320. node.dynamicEdges[i].positionBezierNode();
  27321. }
  27322. };
  27323. /**
  27324. * This function checks if any nodes at the end of their trees have edges below a threshold length
  27325. * This function is called only from updateClusters()
  27326. * forceLevelCollapse ignores the length of the edge and collapses one level
  27327. * This means that a node with only one edge will be clustered with its connected node
  27328. *
  27329. * @private
  27330. * @param {Boolean} force
  27331. */
  27332. exports._formClusters = function(force) {
  27333. if (force == false) {
  27334. if (this.constants.clustering.clusterByZoom == true) {
  27335. this._formClustersByZoom();
  27336. }
  27337. }
  27338. else {
  27339. this._forceClustersByZoom();
  27340. }
  27341. };
  27342. /**
  27343. * This function handles the clustering by zooming out, this is based on a minimum edge distance
  27344. *
  27345. * @private
  27346. */
  27347. exports._formClustersByZoom = function() {
  27348. var dx,dy,length;
  27349. var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
  27350. // check if any edges are shorter than minLength and start the clustering
  27351. // the clustering favours the node with the larger mass
  27352. for (var edgeId in this.edges) {
  27353. if (this.edges.hasOwnProperty(edgeId)) {
  27354. var edge = this.edges[edgeId];
  27355. if (edge.connected) {
  27356. if (edge.toId != edge.fromId) {
  27357. dx = (edge.to.x - edge.from.x);
  27358. dy = (edge.to.y - edge.from.y);
  27359. length = Math.sqrt(dx * dx + dy * dy);
  27360. if (length < minLength) {
  27361. // first check which node is larger
  27362. var parentNode = edge.from;
  27363. var childNode = edge.to;
  27364. if (edge.to.options.mass > edge.from.options.mass) {
  27365. parentNode = edge.to;
  27366. childNode = edge.from;
  27367. }
  27368. if (childNode.dynamicEdges.length == 1) {
  27369. this._addToCluster(parentNode,childNode,false);
  27370. }
  27371. else if (parentNode.dynamicEdges.length == 1) {
  27372. this._addToCluster(childNode,parentNode,false);
  27373. }
  27374. }
  27375. }
  27376. }
  27377. }
  27378. }
  27379. };
  27380. /**
  27381. * This function forces the network to cluster all nodes with only one connecting edge to their
  27382. * connected node.
  27383. *
  27384. * @private
  27385. */
  27386. exports._forceClustersByZoom = function() {
  27387. for (var nodeId in this.nodes) {
  27388. // another node could have absorbed this child.
  27389. if (this.nodes.hasOwnProperty(nodeId)) {
  27390. var childNode = this.nodes[nodeId];
  27391. // the edges can be swallowed by another decrease
  27392. if (childNode.dynamicEdges.length == 1) {
  27393. var edge = childNode.dynamicEdges[0];
  27394. var parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId];
  27395. // group to the largest node
  27396. if (childNode.id != parentNode.id) {
  27397. if (parentNode.options.mass > childNode.options.mass) {
  27398. this._addToCluster(parentNode,childNode,true);
  27399. }
  27400. else {
  27401. this._addToCluster(childNode,parentNode,true);
  27402. }
  27403. }
  27404. }
  27405. }
  27406. }
  27407. };
  27408. /**
  27409. * To keep the nodes of roughly equal size we normalize the cluster levels.
  27410. * This function clusters a node to its smallest connected neighbour.
  27411. *
  27412. * @param node
  27413. * @private
  27414. */
  27415. exports._clusterToSmallestNeighbour = function(node) {
  27416. var smallestNeighbour = -1;
  27417. var smallestNeighbourNode = null;
  27418. for (var i = 0; i < node.dynamicEdges.length; i++) {
  27419. if (node.dynamicEdges[i] !== undefined) {
  27420. var neighbour = null;
  27421. if (node.dynamicEdges[i].fromId != node.id) {
  27422. neighbour = node.dynamicEdges[i].from;
  27423. }
  27424. else if (node.dynamicEdges[i].toId != node.id) {
  27425. neighbour = node.dynamicEdges[i].to;
  27426. }
  27427. if (neighbour != null && smallestNeighbour > neighbour.clusterSessions.length) {
  27428. smallestNeighbour = neighbour.clusterSessions.length;
  27429. smallestNeighbourNode = neighbour;
  27430. }
  27431. }
  27432. }
  27433. if (neighbour != null && this.nodes[neighbour.id] !== undefined) {
  27434. this._addToCluster(neighbour, node, true);
  27435. }
  27436. };
  27437. /**
  27438. * This function forms clusters from hubs, it loops over all nodes
  27439. *
  27440. * @param {Boolean} force | Disregard zoom level
  27441. * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
  27442. * @private
  27443. */
  27444. exports._formClustersByHub = function(force, onlyEqual) {
  27445. // we loop over all nodes in the list
  27446. for (var nodeId in this.nodes) {
  27447. // we check if it is still available since it can be used by the clustering in this loop
  27448. if (this.nodes.hasOwnProperty(nodeId)) {
  27449. this._formClusterFromHub(this.nodes[nodeId],force,onlyEqual);
  27450. }
  27451. }
  27452. };
  27453. /**
  27454. * This function forms a cluster from a specific preselected hub node
  27455. *
  27456. * @param {Node} hubNode | the node we will cluster as a hub
  27457. * @param {Boolean} force | Disregard zoom level
  27458. * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
  27459. * @param {Number} [absorptionSizeOffset] |
  27460. * @private
  27461. */
  27462. exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSizeOffset) {
  27463. if (absorptionSizeOffset === undefined) {
  27464. absorptionSizeOffset = 0;
  27465. }
  27466. //this.hubThreshold = 43
  27467. //if (hubNode.dynamicEdgesLength < 0) {
  27468. // console.error(hubNode.dynamicEdgesLength, this.hubThreshold, onlyEqual)
  27469. //}
  27470. // we decide if the node is a hub
  27471. if ((hubNode.dynamicEdges.length >= this.hubThreshold && onlyEqual == false) ||
  27472. (hubNode.dynamicEdges.length == this.hubThreshold && onlyEqual == true)) {
  27473. // initialize variables
  27474. var dx,dy,length;
  27475. var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
  27476. var allowCluster = false;
  27477. // we create a list of edges because the dynamicEdges change over the course of this loop
  27478. var edgesIdarray = [];
  27479. var amountOfInitialEdges = hubNode.dynamicEdges.length;
  27480. for (var j = 0; j < amountOfInitialEdges; j++) {
  27481. edgesIdarray.push(hubNode.dynamicEdges[j].id);
  27482. }
  27483. // if the hub clustering is not forced, we check if one of the edges connected
  27484. // to a cluster is small enough based on the constants.clustering.clusterEdgeThreshold
  27485. if (force == false) {
  27486. allowCluster = false;
  27487. for (j = 0; j < amountOfInitialEdges; j++) {
  27488. var edge = this.edges[edgesIdarray[j]];
  27489. if (edge !== undefined) {
  27490. if (edge.connected) {
  27491. if (edge.toId != edge.fromId) {
  27492. dx = (edge.to.x - edge.from.x);
  27493. dy = (edge.to.y - edge.from.y);
  27494. length = Math.sqrt(dx * dx + dy * dy);
  27495. if (length < minLength) {
  27496. allowCluster = true;
  27497. break;
  27498. }
  27499. }
  27500. }
  27501. }
  27502. }
  27503. }
  27504. // start the clustering if allowed
  27505. if ((!force && allowCluster) || force) {
  27506. var children = [];
  27507. var childrenIds = {};
  27508. // we loop over all edges INITIALLY connected to this hub to get a list of the childNodes
  27509. for (j = 0; j < amountOfInitialEdges; j++) {
  27510. edge = this.edges[edgesIdarray[j]];
  27511. var childNode = this.nodes[(edge.fromId == hubNode.id) ? edge.toId : edge.fromId];
  27512. if (childrenIds[childNode.id] === undefined) {
  27513. childrenIds[childNode.id] = true;
  27514. children.push(childNode);
  27515. }
  27516. }
  27517. for (j = 0; j < children.length; j++) {
  27518. var childNode = children[j];
  27519. // we do not want hubs to merge with other hubs nor do we want to cluster itself.
  27520. if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) &&
  27521. (childNode.id != hubNode.id)) {
  27522. this._addToCluster(hubNode,childNode,force);
  27523. }
  27524. else {
  27525. //console.log("WILL NOT MERGE:",childNode.dynamicEdges.length , (this.hubThreshold + absorptionSizeOffset))
  27526. }
  27527. }
  27528. }
  27529. }
  27530. };
  27531. /**
  27532. * This function adds the child node to the parent node, creating a cluster if it is not already.
  27533. *
  27534. * @param {Node} parentNode | this is the node that will house the child node
  27535. * @param {Node} childNode | this node will be deleted from the global this.nodes and stored in the parent node
  27536. * @param {Boolean} force | true will only update the remainingEdges at the very end of the clustering, ensuring single level collapse
  27537. * @private
  27538. */
  27539. exports._addToCluster = function(parentNode, childNode, force) {
  27540. // join child node in the parent node
  27541. parentNode.containedNodes[childNode.id] = childNode;
  27542. //console.log(parentNode.id, childNode.id)
  27543. // manage all the edges connected to the child and parent nodes
  27544. for (var i = 0; i < childNode.dynamicEdges.length; i++) {
  27545. var edge = childNode.dynamicEdges[i];
  27546. if (edge.toId == parentNode.id || edge.fromId == parentNode.id) { // edge connected to parentNode
  27547. //console.log("COLLECT",parentNode.id, childNode.id, edge.toId, edge.fromId)
  27548. this._addToContainedEdges(parentNode,childNode,edge);
  27549. }
  27550. else {
  27551. //console.log("REWIRE",parentNode.id, childNode.id, edge.toId, edge.fromId)
  27552. this._connectEdgeToCluster(parentNode,childNode,edge);
  27553. }
  27554. }
  27555. // a contained node has no dynamic edges.
  27556. childNode.dynamicEdges = [];
  27557. // remove circular edges from clusters
  27558. this._containCircularEdgesFromNode(parentNode,childNode);
  27559. // remove the childNode from the global nodes object
  27560. delete this.nodes[childNode.id];
  27561. // update the properties of the child and parent
  27562. var massBefore = parentNode.options.mass;
  27563. childNode.clusterSession = this.clusterSession;
  27564. parentNode.options.mass += childNode.options.mass;
  27565. parentNode.clusterSize += childNode.clusterSize;
  27566. parentNode.options.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
  27567. // keep track of the clustersessions so we can open the cluster up as it has been formed.
  27568. if (parentNode.clusterSessions[parentNode.clusterSessions.length - 1] != this.clusterSession) {
  27569. parentNode.clusterSessions.push(this.clusterSession);
  27570. }
  27571. // forced clusters only open from screen size and double tap
  27572. if (force == true) {
  27573. parentNode.formationScale = 0;
  27574. }
  27575. else {
  27576. parentNode.formationScale = this.scale; // The latest child has been added on this scale
  27577. }
  27578. // recalculate the size of the node on the next time the node is rendered
  27579. parentNode.clearSizeCache();
  27580. // set the pop-out scale for the childnode
  27581. parentNode.containedNodes[childNode.id].formationScale = parentNode.formationScale;
  27582. // nullify the movement velocity of the child, this is to avoid hectic behaviour
  27583. childNode.clearVelocity();
  27584. // the mass has altered, preservation of energy dictates the velocity to be updated
  27585. parentNode.updateVelocity(massBefore);
  27586. // restart the simulation to reorganise all nodes
  27587. this.moving = true;
  27588. };
  27589. /**
  27590. * This adds an edge from the childNode to the contained edges of the parent node
  27591. *
  27592. * @param parentNode | Node object
  27593. * @param childNode | Node object
  27594. * @param edge | Edge object
  27595. * @private
  27596. */
  27597. exports._addToContainedEdges = function(parentNode, childNode, edge) {
  27598. // create an array object if it does not yet exist for this childNode
  27599. if (parentNode.containedEdges[childNode.id] === undefined) {
  27600. parentNode.containedEdges[childNode.id] = []
  27601. }
  27602. // add this edge to the list
  27603. parentNode.containedEdges[childNode.id].push(edge);
  27604. // remove the edge from the global edges object
  27605. delete this.edges[edge.id];
  27606. // remove the edge from the parent object
  27607. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  27608. if (parentNode.dynamicEdges[i].id == edge.id) {
  27609. parentNode.dynamicEdges.splice(i,1);
  27610. break;
  27611. }
  27612. }
  27613. };
  27614. /**
  27615. * This function connects an edge that was connected to a child node to the parent node.
  27616. * It keeps track of which nodes it has been connected to with the originalId array.
  27617. *
  27618. * @param {Node} parentNode | Node object
  27619. * @param {Node} childNode | Node object
  27620. * @param {Edge} edge | Edge object
  27621. * @private
  27622. */
  27623. exports._connectEdgeToCluster = function(parentNode, childNode, edge) {
  27624. // handle circular edges
  27625. if (edge.toId == edge.fromId) {
  27626. this._addToContainedEdges(parentNode, childNode, edge);
  27627. }
  27628. else {
  27629. if (edge.toId == childNode.id) { // edge connected to other node on the "to" side
  27630. edge.originalToId.push(childNode.id);
  27631. edge.to = parentNode;
  27632. edge.toId = parentNode.id;
  27633. }
  27634. else { // edge connected to other node with the "from" side
  27635. edge.originalFromId.push(childNode.id);
  27636. edge.from = parentNode;
  27637. edge.fromId = parentNode.id;
  27638. }
  27639. this._addToReroutedEdges(parentNode,childNode,edge);
  27640. }
  27641. };
  27642. /**
  27643. * If a node is connected to itself, a circular edge is drawn. When clustering we want to contain
  27644. * these edges inside of the cluster.
  27645. *
  27646. * @param parentNode
  27647. * @param childNode
  27648. * @private
  27649. */
  27650. exports._containCircularEdgesFromNode = function(parentNode, childNode) {
  27651. // manage all the edges connected to the child and parent nodes
  27652. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  27653. var edge = parentNode.dynamicEdges[i];
  27654. // handle circular edges
  27655. if (edge.toId == edge.fromId) {
  27656. this._addToContainedEdges(parentNode, childNode, edge);
  27657. }
  27658. }
  27659. };
  27660. /**
  27661. * This adds an edge from the childNode to the rerouted edges of the parent node
  27662. *
  27663. * @param parentNode | Node object
  27664. * @param childNode | Node object
  27665. * @param edge | Edge object
  27666. * @private
  27667. */
  27668. exports._addToReroutedEdges = function(parentNode, childNode, edge) {
  27669. // create an array object if it does not yet exist for this childNode
  27670. // we store the edge in the rerouted edges so we can restore it when the cluster pops open
  27671. if (!(parentNode.reroutedEdges.hasOwnProperty(childNode.id))) {
  27672. parentNode.reroutedEdges[childNode.id] = [];
  27673. }
  27674. parentNode.reroutedEdges[childNode.id].push(edge);
  27675. // this edge becomes part of the dynamicEdges of the cluster node
  27676. parentNode.dynamicEdges.push(edge);
  27677. };
  27678. /**
  27679. * This function connects an edge that was connected to a cluster node back to the child node.
  27680. *
  27681. * @param parentNode | Node object
  27682. * @param childNode | Node object
  27683. * @private
  27684. */
  27685. exports._connectEdgeBackToChild = function(parentNode, childNode) {
  27686. if (parentNode.reroutedEdges.hasOwnProperty(childNode.id)) {
  27687. for (var i = 0; i < parentNode.reroutedEdges[childNode.id].length; i++) {
  27688. var edge = parentNode.reroutedEdges[childNode.id][i];
  27689. if (edge.originalFromId[edge.originalFromId.length-1] == childNode.id) {
  27690. edge.originalFromId.pop();
  27691. edge.fromId = childNode.id;
  27692. edge.from = childNode;
  27693. }
  27694. else {
  27695. edge.originalToId.pop();
  27696. edge.toId = childNode.id;
  27697. edge.to = childNode;
  27698. }
  27699. // append this edge to the list of edges connecting to the childnode
  27700. childNode.dynamicEdges.push(edge);
  27701. // remove the edge from the parent object
  27702. for (var j = 0; j < parentNode.dynamicEdges.length; j++) {
  27703. if (parentNode.dynamicEdges[j].id == edge.id) {
  27704. parentNode.dynamicEdges.splice(j,1);
  27705. break;
  27706. }
  27707. }
  27708. }
  27709. // remove the entry from the rerouted edges
  27710. delete parentNode.reroutedEdges[childNode.id];
  27711. }
  27712. };
  27713. /**
  27714. * When loops are clustered, an edge can be both in the rerouted array and the contained array.
  27715. * This function is called last to verify that all edges in dynamicEdges are in fact connected to the
  27716. * parentNode
  27717. *
  27718. * @param parentNode | Node object
  27719. * @private
  27720. */
  27721. exports._validateEdges = function(parentNode) {
  27722. var dynamicEdges = []
  27723. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  27724. var edge = parentNode.dynamicEdges[i];
  27725. if (parentNode.id == edge.toId || parentNode.id == edge.fromId) {
  27726. dynamicEdges.push(edge);
  27727. }
  27728. }
  27729. parentNode.dynamicEdges = dynamicEdges;
  27730. };
  27731. /**
  27732. * This function released the contained edges back into the global domain and puts them back into the
  27733. * dynamic edges of both parent and child.
  27734. *
  27735. * @param {Node} parentNode |
  27736. * @param {Node} childNode |
  27737. * @private
  27738. */
  27739. exports._releaseContainedEdges = function(parentNode, childNode) {
  27740. for (var i = 0; i < parentNode.containedEdges[childNode.id].length; i++) {
  27741. var edge = parentNode.containedEdges[childNode.id][i];
  27742. // put the edge back in the global edges object
  27743. this.edges[edge.id] = edge;
  27744. // put the edge back in the dynamic edges of the child and parent
  27745. childNode.dynamicEdges.push(edge);
  27746. parentNode.dynamicEdges.push(edge);
  27747. }
  27748. // remove the entry from the contained edges
  27749. delete parentNode.containedEdges[childNode.id];
  27750. };
  27751. // ------------------- UTILITY FUNCTIONS ---------------------------- //
  27752. /**
  27753. * This updates the node labels for all nodes (for debugging purposes)
  27754. */
  27755. exports.updateLabels = function() {
  27756. var nodeId;
  27757. // update node labels
  27758. for (nodeId in this.nodes) {
  27759. if (this.nodes.hasOwnProperty(nodeId)) {
  27760. var node = this.nodes[nodeId];
  27761. if (node.clusterSize > 1) {
  27762. node.label = "[".concat(String(node.clusterSize),"]");
  27763. }
  27764. }
  27765. }
  27766. // update node labels
  27767. for (nodeId in this.nodes) {
  27768. if (this.nodes.hasOwnProperty(nodeId)) {
  27769. node = this.nodes[nodeId];
  27770. if (node.clusterSize == 1) {
  27771. if (node.originalLabel !== undefined) {
  27772. node.label = node.originalLabel;
  27773. }
  27774. else {
  27775. node.label = String(node.id);
  27776. }
  27777. }
  27778. }
  27779. }
  27780. // /* Debug Override */
  27781. // for (nodeId in this.nodes) {
  27782. // if (this.nodes.hasOwnProperty(nodeId)) {
  27783. // node = this.nodes[nodeId];
  27784. // node.label = String(node.clusterSize + ":" + node.dynamicEdges.length);
  27785. // }
  27786. // }
  27787. };
  27788. /**
  27789. * We want to keep the cluster level distribution rather small. This means we do not want unclustered nodes
  27790. * if the rest of the nodes are already a few cluster levels in.
  27791. * To fix this we use this function. It determines the min and max cluster level and sends nodes that have not
  27792. * clustered enough to the clusterToSmallestNeighbours function.
  27793. */
  27794. exports.normalizeClusterLevels = function() {
  27795. var maxLevel = 0;
  27796. var minLevel = 1e9;
  27797. var clusterLevel = 0;
  27798. var nodeId;
  27799. // we loop over all nodes in the list
  27800. for (nodeId in this.nodes) {
  27801. if (this.nodes.hasOwnProperty(nodeId)) {
  27802. clusterLevel = this.nodes[nodeId].clusterSessions.length;
  27803. if (maxLevel < clusterLevel) {maxLevel = clusterLevel;}
  27804. if (minLevel > clusterLevel) {minLevel = clusterLevel;}
  27805. }
  27806. }
  27807. if (maxLevel - minLevel > this.constants.clustering.clusterLevelDifference) {
  27808. var amountOfNodes = this.nodeIndices.length;
  27809. var targetLevel = maxLevel - this.constants.clustering.clusterLevelDifference;
  27810. // we loop over all nodes in the list
  27811. for (nodeId in this.nodes) {
  27812. if (this.nodes.hasOwnProperty(nodeId)) {
  27813. if (this.nodes[nodeId].clusterSessions.length < targetLevel) {
  27814. this._clusterToSmallestNeighbour(this.nodes[nodeId]);
  27815. }
  27816. }
  27817. }
  27818. this._updateNodeIndexList();
  27819. // if a cluster was formed, we increase the clusterSession
  27820. if (this.nodeIndices.length != amountOfNodes) {
  27821. this.clusterSession += 1;
  27822. }
  27823. }
  27824. };
  27825. /**
  27826. * This function determines if the cluster we want to decluster is in the active area
  27827. * this means around the zoom center
  27828. *
  27829. * @param {Node} node
  27830. * @returns {boolean}
  27831. * @private
  27832. */
  27833. exports._nodeInActiveArea = function(node) {
  27834. return (
  27835. Math.abs(node.x - this.areaCenter.x) <= this.constants.clustering.activeAreaBoxSize/this.scale
  27836. &&
  27837. Math.abs(node.y - this.areaCenter.y) <= this.constants.clustering.activeAreaBoxSize/this.scale
  27838. )
  27839. };
  27840. /**
  27841. * This is an adaptation of the original repositioning function. This is called if the system is clustered initially
  27842. * It puts large clusters away from the center and randomizes the order.
  27843. *
  27844. */
  27845. exports.repositionNodes = function() {
  27846. for (var i = 0; i < this.nodeIndices.length; i++) {
  27847. var node = this.nodes[this.nodeIndices[i]];
  27848. if ((node.xFixed == false || node.yFixed == false)) {
  27849. var radius = 10 * 0.1*this.nodeIndices.length * Math.min(100,node.options.mass);
  27850. var angle = 2 * Math.PI * Math.random();
  27851. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  27852. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  27853. this._repositionBezierNodes(node);
  27854. }
  27855. }
  27856. };
  27857. /**
  27858. * We determine how many connections denote an important hub.
  27859. * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
  27860. *
  27861. * @private
  27862. */
  27863. exports._getHubSize = function() {
  27864. var average = 0;
  27865. var averageSquared = 0;
  27866. var hubCounter = 0;
  27867. var largestHub = 0;
  27868. for (var i = 0; i < this.nodeIndices.length; i++) {
  27869. var node = this.nodes[this.nodeIndices[i]];
  27870. if (node.dynamicEdges.length > largestHub) {
  27871. largestHub = node.dynamicEdges.length;
  27872. }
  27873. average += node.dynamicEdges.length;
  27874. averageSquared += Math.pow(node.dynamicEdges.length,2);
  27875. hubCounter += 1;
  27876. }
  27877. average = average / hubCounter;
  27878. averageSquared = averageSquared / hubCounter;
  27879. var variance = averageSquared - Math.pow(average,2);
  27880. var standardDeviation = Math.sqrt(variance);
  27881. this.hubThreshold = Math.floor(average + 2*standardDeviation);
  27882. // always have at least one to cluster
  27883. if (this.hubThreshold > largestHub) {
  27884. this.hubThreshold = largestHub;
  27885. }
  27886. // console.log("average",average,"averageSQ",averageSquared,"var",variance,"std",standardDeviation);
  27887. // console.log("hubThreshold:",this.hubThreshold);
  27888. };
  27889. /**
  27890. * We reduce the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
  27891. * with this amount we can cluster specifically on these chains.
  27892. *
  27893. * @param {Number} fraction | between 0 and 1, the percentage of chains to reduce
  27894. * @private
  27895. */
  27896. exports._reduceAmountOfChains = function(fraction) {
  27897. this.hubThreshold = 2;
  27898. var reduceAmount = Math.floor(this.nodeIndices.length * fraction);
  27899. for (var nodeId in this.nodes) {
  27900. if (this.nodes.hasOwnProperty(nodeId)) {
  27901. if (this.nodes[nodeId].dynamicEdges.length == 2) {
  27902. if (reduceAmount > 0) {
  27903. this._formClusterFromHub(this.nodes[nodeId],true,true,1);
  27904. reduceAmount -= 1;
  27905. }
  27906. }
  27907. }
  27908. }
  27909. };
  27910. /**
  27911. * We get the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
  27912. * with this amount we can cluster specifically on these chains.
  27913. *
  27914. * @private
  27915. */
  27916. exports._getChainFraction = function() {
  27917. var chains = 0;
  27918. var total = 0;
  27919. for (var nodeId in this.nodes) {
  27920. if (this.nodes.hasOwnProperty(nodeId)) {
  27921. if (this.nodes[nodeId].dynamicEdges.length == 2) {
  27922. chains += 1;
  27923. }
  27924. total += 1;
  27925. }
  27926. }
  27927. return chains/total;
  27928. };
  27929. /***/ },
  27930. /* 65 */
  27931. /***/ function(module, exports, __webpack_require__) {
  27932. var util = __webpack_require__(1);
  27933. var Node = __webpack_require__(56);
  27934. /**
  27935. * Creation of the SectorMixin var.
  27936. *
  27937. * This contains all the functions the Network object can use to employ the sector system.
  27938. * The sector system is always used by Network, though the benefits only apply to the use of clustering.
  27939. * If clustering is not used, there is no overhead except for a duplicate object with references to nodes and edges.
  27940. */
  27941. /**
  27942. * This function is only called by the setData function of the Network object.
  27943. * This loads the global references into the active sector. This initializes the sector.
  27944. *
  27945. * @private
  27946. */
  27947. exports._putDataInSector = function() {
  27948. this.sectors["active"][this._sector()].nodes = this.nodes;
  27949. this.sectors["active"][this._sector()].edges = this.edges;
  27950. this.sectors["active"][this._sector()].nodeIndices = this.nodeIndices;
  27951. };
  27952. /**
  27953. * /**
  27954. * This function sets the global references to nodes, edges and nodeIndices back to
  27955. * those of the supplied (active) sector. If a type is defined, do the specific type
  27956. *
  27957. * @param {String} sectorId
  27958. * @param {String} [sectorType] | "active" or "frozen"
  27959. * @private
  27960. */
  27961. exports._switchToSector = function(sectorId, sectorType) {
  27962. if (sectorType === undefined || sectorType == "active") {
  27963. this._switchToActiveSector(sectorId);
  27964. }
  27965. else {
  27966. this._switchToFrozenSector(sectorId);
  27967. }
  27968. };
  27969. /**
  27970. * This function sets the global references to nodes, edges and nodeIndices back to
  27971. * those of the supplied active sector.
  27972. *
  27973. * @param sectorId
  27974. * @private
  27975. */
  27976. exports._switchToActiveSector = function(sectorId) {
  27977. this.nodeIndices = this.sectors["active"][sectorId]["nodeIndices"];
  27978. this.nodes = this.sectors["active"][sectorId]["nodes"];
  27979. this.edges = this.sectors["active"][sectorId]["edges"];
  27980. };
  27981. /**
  27982. * This function sets the global references to nodes, edges and nodeIndices back to
  27983. * those of the supplied active sector.
  27984. *
  27985. * @private
  27986. */
  27987. exports._switchToSupportSector = function() {
  27988. this.nodeIndices = this.sectors["support"]["nodeIndices"];
  27989. this.nodes = this.sectors["support"]["nodes"];
  27990. this.edges = this.sectors["support"]["edges"];
  27991. };
  27992. /**
  27993. * This function sets the global references to nodes, edges and nodeIndices back to
  27994. * those of the supplied frozen sector.
  27995. *
  27996. * @param sectorId
  27997. * @private
  27998. */
  27999. exports._switchToFrozenSector = function(sectorId) {
  28000. this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"];
  28001. this.nodes = this.sectors["frozen"][sectorId]["nodes"];
  28002. this.edges = this.sectors["frozen"][sectorId]["edges"];
  28003. };
  28004. /**
  28005. * This function sets the global references to nodes, edges and nodeIndices back to
  28006. * those of the currently active sector.
  28007. *
  28008. * @private
  28009. */
  28010. exports._loadLatestSector = function() {
  28011. this._switchToSector(this._sector());
  28012. };
  28013. /**
  28014. * This function returns the currently active sector Id
  28015. *
  28016. * @returns {String}
  28017. * @private
  28018. */
  28019. exports._sector = function() {
  28020. return this.activeSector[this.activeSector.length-1];
  28021. };
  28022. /**
  28023. * This function returns the previously active sector Id
  28024. *
  28025. * @returns {String}
  28026. * @private
  28027. */
  28028. exports._previousSector = function() {
  28029. if (this.activeSector.length > 1) {
  28030. return this.activeSector[this.activeSector.length-2];
  28031. }
  28032. else {
  28033. throw new TypeError('there are not enough sectors in the this.activeSector array.');
  28034. }
  28035. };
  28036. /**
  28037. * We add the active sector at the end of the this.activeSector array
  28038. * This ensures it is the currently active sector returned by _sector() and it reaches the top
  28039. * of the activeSector stack. When we reverse our steps we move from the end to the beginning of this stack.
  28040. *
  28041. * @param newId
  28042. * @private
  28043. */
  28044. exports._setActiveSector = function(newId) {
  28045. this.activeSector.push(newId);
  28046. };
  28047. /**
  28048. * We remove the currently active sector id from the active sector stack. This happens when
  28049. * we reactivate the previously active sector
  28050. *
  28051. * @private
  28052. */
  28053. exports._forgetLastSector = function() {
  28054. this.activeSector.pop();
  28055. };
  28056. /**
  28057. * This function creates a new active sector with the supplied newId. This newId
  28058. * is the expanding node id.
  28059. *
  28060. * @param {String} newId | Id of the new active sector
  28061. * @private
  28062. */
  28063. exports._createNewSector = function(newId) {
  28064. // create the new sector
  28065. this.sectors["active"][newId] = {"nodes":{},
  28066. "edges":{},
  28067. "nodeIndices":[],
  28068. "formationScale": this.scale,
  28069. "drawingNode": undefined};
  28070. // create the new sector render node. This gives visual feedback that you are in a new sector.
  28071. this.sectors["active"][newId]['drawingNode'] = new Node(
  28072. {id:newId,
  28073. color: {
  28074. background: "#eaefef",
  28075. border: "495c5e"
  28076. }
  28077. },{},{},this.constants);
  28078. this.sectors["active"][newId]['drawingNode'].clusterSize = 2;
  28079. };
  28080. /**
  28081. * This function removes the currently active sector. This is called when we create a new
  28082. * active sector.
  28083. *
  28084. * @param {String} sectorId | Id of the active sector that will be removed
  28085. * @private
  28086. */
  28087. exports._deleteActiveSector = function(sectorId) {
  28088. delete this.sectors["active"][sectorId];
  28089. };
  28090. /**
  28091. * This function removes the currently active sector. This is called when we reactivate
  28092. * the previously active sector.
  28093. *
  28094. * @param {String} sectorId | Id of the active sector that will be removed
  28095. * @private
  28096. */
  28097. exports._deleteFrozenSector = function(sectorId) {
  28098. delete this.sectors["frozen"][sectorId];
  28099. };
  28100. /**
  28101. * Freezing an active sector means moving it from the "active" object to the "frozen" object.
  28102. * We copy the references, then delete the active entree.
  28103. *
  28104. * @param sectorId
  28105. * @private
  28106. */
  28107. exports._freezeSector = function(sectorId) {
  28108. // we move the set references from the active to the frozen stack.
  28109. this.sectors["frozen"][sectorId] = this.sectors["active"][sectorId];
  28110. // we have moved the sector data into the frozen set, we now remove it from the active set
  28111. this._deleteActiveSector(sectorId);
  28112. };
  28113. /**
  28114. * This is the reverse operation of _freezeSector. Activating means moving the sector from the "frozen"
  28115. * object to the "active" object.
  28116. *
  28117. * @param sectorId
  28118. * @private
  28119. */
  28120. exports._activateSector = function(sectorId) {
  28121. // we move the set references from the frozen to the active stack.
  28122. this.sectors["active"][sectorId] = this.sectors["frozen"][sectorId];
  28123. // we have moved the sector data into the active set, we now remove it from the frozen stack
  28124. this._deleteFrozenSector(sectorId);
  28125. };
  28126. /**
  28127. * This function merges the data from the currently active sector with a frozen sector. This is used
  28128. * in the process of reverting back to the previously active sector.
  28129. * The data that is placed in the frozen (the previously active) sector is the node that has been removed from it
  28130. * upon the creation of a new active sector.
  28131. *
  28132. * @param sectorId
  28133. * @private
  28134. */
  28135. exports._mergeThisWithFrozen = function(sectorId) {
  28136. // copy all nodes
  28137. for (var nodeId in this.nodes) {
  28138. if (this.nodes.hasOwnProperty(nodeId)) {
  28139. this.sectors["frozen"][sectorId]["nodes"][nodeId] = this.nodes[nodeId];
  28140. }
  28141. }
  28142. // copy all edges (if not fully clustered, else there are no edges)
  28143. for (var edgeId in this.edges) {
  28144. if (this.edges.hasOwnProperty(edgeId)) {
  28145. this.sectors["frozen"][sectorId]["edges"][edgeId] = this.edges[edgeId];
  28146. }
  28147. }
  28148. // merge the nodeIndices
  28149. for (var i = 0; i < this.nodeIndices.length; i++) {
  28150. this.sectors["frozen"][sectorId]["nodeIndices"].push(this.nodeIndices[i]);
  28151. }
  28152. };
  28153. /**
  28154. * This clusters the sector to one cluster. It was a single cluster before this process started so
  28155. * we revert to that state. The clusterToFit function with a maximum size of 1 node does this.
  28156. *
  28157. * @private
  28158. */
  28159. exports._collapseThisToSingleCluster = function() {
  28160. this.clusterToFit(1,false);
  28161. };
  28162. /**
  28163. * We create a new active sector from the node that we want to open.
  28164. *
  28165. * @param node
  28166. * @private
  28167. */
  28168. exports._addSector = function(node) {
  28169. // this is the currently active sector
  28170. var sector = this._sector();
  28171. // // this should allow me to select nodes from a frozen set.
  28172. // if (this.sectors['active'][sector]["nodes"].hasOwnProperty(node.id)) {
  28173. // console.log("the node is part of the active sector");
  28174. // }
  28175. // else {
  28176. // console.log("I dont know what the fuck happened!!");
  28177. // }
  28178. // when we switch to a new sector, we remove the node that will be expanded from the current nodes list.
  28179. delete this.nodes[node.id];
  28180. var unqiueIdentifier = util.randomUUID();
  28181. // we fully freeze the currently active sector
  28182. this._freezeSector(sector);
  28183. // we create a new active sector. This sector has the Id of the node to ensure uniqueness
  28184. this._createNewSector(unqiueIdentifier);
  28185. // we add the active sector to the sectors array to be able to revert these steps later on
  28186. this._setActiveSector(unqiueIdentifier);
  28187. // we redirect the global references to the new sector's references. this._sector() now returns unqiueIdentifier
  28188. this._switchToSector(this._sector());
  28189. // finally we add the node we removed from our previous active sector to the new active sector
  28190. this.nodes[node.id] = node;
  28191. };
  28192. /**
  28193. * We close the sector that is currently open and revert back to the one before.
  28194. * If the active sector is the "default" sector, nothing happens.
  28195. *
  28196. * @private
  28197. */
  28198. exports._collapseSector = function() {
  28199. // the currently active sector
  28200. var sector = this._sector();
  28201. // we cannot collapse the default sector
  28202. if (sector != "default") {
  28203. if ((this.nodeIndices.length == 1) ||
  28204. (this.sectors["active"][sector]["drawingNode"].width*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  28205. (this.sectors["active"][sector]["drawingNode"].height*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  28206. var previousSector = this._previousSector();
  28207. // we collapse the sector back to a single cluster
  28208. this._collapseThisToSingleCluster();
  28209. // we move the remaining nodes, edges and nodeIndices to the previous sector.
  28210. // This previous sector is the one we will reactivate
  28211. this._mergeThisWithFrozen(previousSector);
  28212. // the previously active (frozen) sector now has all the data from the currently active sector.
  28213. // we can now delete the active sector.
  28214. this._deleteActiveSector(sector);
  28215. // we activate the previously active (and currently frozen) sector.
  28216. this._activateSector(previousSector);
  28217. // we load the references from the newly active sector into the global references
  28218. this._switchToSector(previousSector);
  28219. // we forget the previously active sector because we reverted to the one before
  28220. this._forgetLastSector();
  28221. // finally, we update the node index list.
  28222. this._updateNodeIndexList();
  28223. // we refresh the list with calulation nodes and calculation node indices.
  28224. this._updateCalculationNodes();
  28225. }
  28226. }
  28227. };
  28228. /**
  28229. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  28230. *
  28231. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  28232. * | we dont pass the function itself because then the "this" is the window object
  28233. * | instead of the Network object
  28234. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  28235. * @private
  28236. */
  28237. exports._doInAllActiveSectors = function(runFunction,argument) {
  28238. var returnValues = [];
  28239. if (argument === undefined) {
  28240. for (var sector in this.sectors["active"]) {
  28241. if (this.sectors["active"].hasOwnProperty(sector)) {
  28242. // switch the global references to those of this sector
  28243. this._switchToActiveSector(sector);
  28244. returnValues.push( this[runFunction]() );
  28245. }
  28246. }
  28247. }
  28248. else {
  28249. for (var sector in this.sectors["active"]) {
  28250. if (this.sectors["active"].hasOwnProperty(sector)) {
  28251. // switch the global references to those of this sector
  28252. this._switchToActiveSector(sector);
  28253. var args = Array.prototype.splice.call(arguments, 1);
  28254. if (args.length > 1) {
  28255. returnValues.push( this[runFunction](args[0],args[1]) );
  28256. }
  28257. else {
  28258. returnValues.push( this[runFunction](argument) );
  28259. }
  28260. }
  28261. }
  28262. }
  28263. // we revert the global references back to our active sector
  28264. this._loadLatestSector();
  28265. return returnValues;
  28266. };
  28267. /**
  28268. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  28269. *
  28270. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  28271. * | we dont pass the function itself because then the "this" is the window object
  28272. * | instead of the Network object
  28273. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  28274. * @private
  28275. */
  28276. exports._doInSupportSector = function(runFunction,argument) {
  28277. var returnValues = false;
  28278. if (argument === undefined) {
  28279. this._switchToSupportSector();
  28280. returnValues = this[runFunction]();
  28281. }
  28282. else {
  28283. this._switchToSupportSector();
  28284. var args = Array.prototype.splice.call(arguments, 1);
  28285. if (args.length > 1) {
  28286. returnValues = this[runFunction](args[0],args[1]);
  28287. }
  28288. else {
  28289. returnValues = this[runFunction](argument);
  28290. }
  28291. }
  28292. // we revert the global references back to our active sector
  28293. this._loadLatestSector();
  28294. return returnValues;
  28295. };
  28296. /**
  28297. * This runs a function in all frozen sectors. This is used in the _redraw().
  28298. *
  28299. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  28300. * | we don't pass the function itself because then the "this" is the window object
  28301. * | instead of the Network object
  28302. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  28303. * @private
  28304. */
  28305. exports._doInAllFrozenSectors = function(runFunction,argument) {
  28306. if (argument === undefined) {
  28307. for (var sector in this.sectors["frozen"]) {
  28308. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  28309. // switch the global references to those of this sector
  28310. this._switchToFrozenSector(sector);
  28311. this[runFunction]();
  28312. }
  28313. }
  28314. }
  28315. else {
  28316. for (var sector in this.sectors["frozen"]) {
  28317. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  28318. // switch the global references to those of this sector
  28319. this._switchToFrozenSector(sector);
  28320. var args = Array.prototype.splice.call(arguments, 1);
  28321. if (args.length > 1) {
  28322. this[runFunction](args[0],args[1]);
  28323. }
  28324. else {
  28325. this[runFunction](argument);
  28326. }
  28327. }
  28328. }
  28329. }
  28330. this._loadLatestSector();
  28331. };
  28332. /**
  28333. * This runs a function in all sectors. This is used in the _redraw().
  28334. *
  28335. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  28336. * | we don't pass the function itself because then the "this" is the window object
  28337. * | instead of the Network object
  28338. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  28339. * @private
  28340. */
  28341. exports._doInAllSectors = function(runFunction,argument) {
  28342. var args = Array.prototype.splice.call(arguments, 1);
  28343. if (argument === undefined) {
  28344. this._doInAllActiveSectors(runFunction);
  28345. this._doInAllFrozenSectors(runFunction);
  28346. }
  28347. else {
  28348. if (args.length > 1) {
  28349. this._doInAllActiveSectors(runFunction,args[0],args[1]);
  28350. this._doInAllFrozenSectors(runFunction,args[0],args[1]);
  28351. }
  28352. else {
  28353. this._doInAllActiveSectors(runFunction,argument);
  28354. this._doInAllFrozenSectors(runFunction,argument);
  28355. }
  28356. }
  28357. };
  28358. /**
  28359. * This clears the nodeIndices list. We cannot use this.nodeIndices = [] because we would break the link with the
  28360. * active sector. Thus we clear the nodeIndices in the active sector, then reconnect the this.nodeIndices to it.
  28361. *
  28362. * @private
  28363. */
  28364. exports._clearNodeIndexList = function() {
  28365. var sector = this._sector();
  28366. this.sectors["active"][sector]["nodeIndices"] = [];
  28367. this.nodeIndices = this.sectors["active"][sector]["nodeIndices"];
  28368. };
  28369. /**
  28370. * Draw the encompassing sector node
  28371. *
  28372. * @param ctx
  28373. * @param sectorType
  28374. * @private
  28375. */
  28376. exports._drawSectorNodes = function(ctx,sectorType) {
  28377. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  28378. for (var sector in this.sectors[sectorType]) {
  28379. if (this.sectors[sectorType].hasOwnProperty(sector)) {
  28380. if (this.sectors[sectorType][sector]["drawingNode"] !== undefined) {
  28381. this._switchToSector(sector,sectorType);
  28382. minY = 1e9; maxY = -1e9; minX = 1e9; maxX = -1e9;
  28383. for (var nodeId in this.nodes) {
  28384. if (this.nodes.hasOwnProperty(nodeId)) {
  28385. node = this.nodes[nodeId];
  28386. node.resize(ctx);
  28387. if (minX > node.x - 0.5 * node.width) {minX = node.x - 0.5 * node.width;}
  28388. if (maxX < node.x + 0.5 * node.width) {maxX = node.x + 0.5 * node.width;}
  28389. if (minY > node.y - 0.5 * node.height) {minY = node.y - 0.5 * node.height;}
  28390. if (maxY < node.y + 0.5 * node.height) {maxY = node.y + 0.5 * node.height;}
  28391. }
  28392. }
  28393. node = this.sectors[sectorType][sector]["drawingNode"];
  28394. node.x = 0.5 * (maxX + minX);
  28395. node.y = 0.5 * (maxY + minY);
  28396. node.width = 2 * (node.x - minX);
  28397. node.height = 2 * (node.y - minY);
  28398. node.options.radius = Math.sqrt(Math.pow(0.5*node.width,2) + Math.pow(0.5*node.height,2));
  28399. node.setScale(this.scale);
  28400. node._drawCircle(ctx);
  28401. }
  28402. }
  28403. }
  28404. };
  28405. exports._drawAllSectorNodes = function(ctx) {
  28406. this._drawSectorNodes(ctx,"frozen");
  28407. this._drawSectorNodes(ctx,"active");
  28408. this._loadLatestSector();
  28409. };
  28410. /***/ },
  28411. /* 66 */
  28412. /***/ function(module, exports, __webpack_require__) {
  28413. var Node = __webpack_require__(56);
  28414. /**
  28415. * This function can be called from the _doInAllSectors function
  28416. *
  28417. * @param object
  28418. * @param overlappingNodes
  28419. * @private
  28420. */
  28421. exports._getNodesOverlappingWith = function(object, overlappingNodes) {
  28422. var nodes = this.nodes;
  28423. for (var nodeId in nodes) {
  28424. if (nodes.hasOwnProperty(nodeId)) {
  28425. if (nodes[nodeId].isOverlappingWith(object)) {
  28426. overlappingNodes.push(nodeId);
  28427. }
  28428. }
  28429. }
  28430. };
  28431. /**
  28432. * retrieve all nodes overlapping with given object
  28433. * @param {Object} object An object with parameters left, top, right, bottom
  28434. * @return {Number[]} An array with id's of the overlapping nodes
  28435. * @private
  28436. */
  28437. exports._getAllNodesOverlappingWith = function (object) {
  28438. var overlappingNodes = [];
  28439. this._doInAllActiveSectors("_getNodesOverlappingWith",object,overlappingNodes);
  28440. return overlappingNodes;
  28441. };
  28442. /**
  28443. * Return a position object in canvasspace from a single point in screenspace
  28444. *
  28445. * @param pointer
  28446. * @returns {{left: number, top: number, right: number, bottom: number}}
  28447. * @private
  28448. */
  28449. exports._pointerToPositionObject = function(pointer) {
  28450. var x = this._XconvertDOMtoCanvas(pointer.x);
  28451. var y = this._YconvertDOMtoCanvas(pointer.y);
  28452. return {
  28453. left: x,
  28454. top: y,
  28455. right: x,
  28456. bottom: y
  28457. };
  28458. };
  28459. /**
  28460. * Get the top node at the a specific point (like a click)
  28461. *
  28462. * @param {{x: Number, y: Number}} pointer
  28463. * @return {Node | null} node
  28464. * @private
  28465. */
  28466. exports._getNodeAt = function (pointer) {
  28467. // we first check if this is an navigation controls element
  28468. var positionObject = this._pointerToPositionObject(pointer);
  28469. var overlappingNodes = this._getAllNodesOverlappingWith(positionObject);
  28470. // if there are overlapping nodes, select the last one, this is the
  28471. // one which is drawn on top of the others
  28472. if (overlappingNodes.length > 0) {
  28473. return this.nodes[overlappingNodes[overlappingNodes.length - 1]];
  28474. }
  28475. else {
  28476. return null;
  28477. }
  28478. };
  28479. /**
  28480. * retrieve all edges overlapping with given object, selector is around center
  28481. * @param {Object} object An object with parameters left, top, right, bottom
  28482. * @return {Number[]} An array with id's of the overlapping nodes
  28483. * @private
  28484. */
  28485. exports._getEdgesOverlappingWith = function (object, overlappingEdges) {
  28486. var edges = this.edges;
  28487. for (var edgeId in edges) {
  28488. if (edges.hasOwnProperty(edgeId)) {
  28489. if (edges[edgeId].isOverlappingWith(object)) {
  28490. overlappingEdges.push(edgeId);
  28491. }
  28492. }
  28493. }
  28494. };
  28495. /**
  28496. * retrieve all nodes overlapping with given object
  28497. * @param {Object} object An object with parameters left, top, right, bottom
  28498. * @return {Number[]} An array with id's of the overlapping nodes
  28499. * @private
  28500. */
  28501. exports._getAllEdgesOverlappingWith = function (object) {
  28502. var overlappingEdges = [];
  28503. this._doInAllActiveSectors("_getEdgesOverlappingWith",object,overlappingEdges);
  28504. return overlappingEdges;
  28505. };
  28506. /**
  28507. * Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call
  28508. * _getNodeAt and _getEdgesAt, then priortize the selection to user preferences.
  28509. *
  28510. * @param pointer
  28511. * @returns {null}
  28512. * @private
  28513. */
  28514. exports._getEdgeAt = function(pointer) {
  28515. var positionObject = this._pointerToPositionObject(pointer);
  28516. var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject);
  28517. if (overlappingEdges.length > 0) {
  28518. return this.edges[overlappingEdges[overlappingEdges.length - 1]];
  28519. }
  28520. else {
  28521. return null;
  28522. }
  28523. };
  28524. /**
  28525. * Add object to the selection array.
  28526. *
  28527. * @param obj
  28528. * @private
  28529. */
  28530. exports._addToSelection = function(obj) {
  28531. if (obj instanceof Node) {
  28532. this.selectionObj.nodes[obj.id] = obj;
  28533. }
  28534. else {
  28535. this.selectionObj.edges[obj.id] = obj;
  28536. }
  28537. };
  28538. /**
  28539. * Add object to the selection array.
  28540. *
  28541. * @param obj
  28542. * @private
  28543. */
  28544. exports._addToHover = function(obj) {
  28545. if (obj instanceof Node) {
  28546. this.hoverObj.nodes[obj.id] = obj;
  28547. }
  28548. else {
  28549. this.hoverObj.edges[obj.id] = obj;
  28550. }
  28551. };
  28552. /**
  28553. * Remove a single option from selection.
  28554. *
  28555. * @param {Object} obj
  28556. * @private
  28557. */
  28558. exports._removeFromSelection = function(obj) {
  28559. if (obj instanceof Node) {
  28560. delete this.selectionObj.nodes[obj.id];
  28561. }
  28562. else {
  28563. delete this.selectionObj.edges[obj.id];
  28564. }
  28565. };
  28566. /**
  28567. * Unselect all. The selectionObj is useful for this.
  28568. *
  28569. * @param {Boolean} [doNotTrigger] | ignore trigger
  28570. * @private
  28571. */
  28572. exports._unselectAll = function(doNotTrigger) {
  28573. if (doNotTrigger === undefined) {
  28574. doNotTrigger = false;
  28575. }
  28576. for(var nodeId in this.selectionObj.nodes) {
  28577. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28578. this.selectionObj.nodes[nodeId].unselect();
  28579. }
  28580. }
  28581. for(var edgeId in this.selectionObj.edges) {
  28582. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  28583. this.selectionObj.edges[edgeId].unselect();
  28584. }
  28585. }
  28586. this.selectionObj = {nodes:{},edges:{}};
  28587. if (doNotTrigger == false) {
  28588. this.emit('select', this.getSelection());
  28589. }
  28590. };
  28591. /**
  28592. * Unselect all clusters. The selectionObj is useful for this.
  28593. *
  28594. * @param {Boolean} [doNotTrigger] | ignore trigger
  28595. * @private
  28596. */
  28597. exports._unselectClusters = function(doNotTrigger) {
  28598. if (doNotTrigger === undefined) {
  28599. doNotTrigger = false;
  28600. }
  28601. for (var nodeId in this.selectionObj.nodes) {
  28602. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28603. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  28604. this.selectionObj.nodes[nodeId].unselect();
  28605. this._removeFromSelection(this.selectionObj.nodes[nodeId]);
  28606. }
  28607. }
  28608. }
  28609. if (doNotTrigger == false) {
  28610. this.emit('select', this.getSelection());
  28611. }
  28612. };
  28613. /**
  28614. * return the number of selected nodes
  28615. *
  28616. * @returns {number}
  28617. * @private
  28618. */
  28619. exports._getSelectedNodeCount = function() {
  28620. var count = 0;
  28621. for (var nodeId in this.selectionObj.nodes) {
  28622. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28623. count += 1;
  28624. }
  28625. }
  28626. return count;
  28627. };
  28628. /**
  28629. * return the selected node
  28630. *
  28631. * @returns {number}
  28632. * @private
  28633. */
  28634. exports._getSelectedNode = function() {
  28635. for (var nodeId in this.selectionObj.nodes) {
  28636. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28637. return this.selectionObj.nodes[nodeId];
  28638. }
  28639. }
  28640. return null;
  28641. };
  28642. /**
  28643. * return the selected edge
  28644. *
  28645. * @returns {number}
  28646. * @private
  28647. */
  28648. exports._getSelectedEdge = function() {
  28649. for (var edgeId in this.selectionObj.edges) {
  28650. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  28651. return this.selectionObj.edges[edgeId];
  28652. }
  28653. }
  28654. return null;
  28655. };
  28656. /**
  28657. * return the number of selected edges
  28658. *
  28659. * @returns {number}
  28660. * @private
  28661. */
  28662. exports._getSelectedEdgeCount = function() {
  28663. var count = 0;
  28664. for (var edgeId in this.selectionObj.edges) {
  28665. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  28666. count += 1;
  28667. }
  28668. }
  28669. return count;
  28670. };
  28671. /**
  28672. * return the number of selected objects.
  28673. *
  28674. * @returns {number}
  28675. * @private
  28676. */
  28677. exports._getSelectedObjectCount = function() {
  28678. var count = 0;
  28679. for(var nodeId in this.selectionObj.nodes) {
  28680. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28681. count += 1;
  28682. }
  28683. }
  28684. for(var edgeId in this.selectionObj.edges) {
  28685. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  28686. count += 1;
  28687. }
  28688. }
  28689. return count;
  28690. };
  28691. /**
  28692. * Check if anything is selected
  28693. *
  28694. * @returns {boolean}
  28695. * @private
  28696. */
  28697. exports._selectionIsEmpty = function() {
  28698. for(var nodeId in this.selectionObj.nodes) {
  28699. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28700. return false;
  28701. }
  28702. }
  28703. for(var edgeId in this.selectionObj.edges) {
  28704. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  28705. return false;
  28706. }
  28707. }
  28708. return true;
  28709. };
  28710. /**
  28711. * check if one of the selected nodes is a cluster.
  28712. *
  28713. * @returns {boolean}
  28714. * @private
  28715. */
  28716. exports._clusterInSelection = function() {
  28717. for(var nodeId in this.selectionObj.nodes) {
  28718. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28719. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  28720. return true;
  28721. }
  28722. }
  28723. }
  28724. return false;
  28725. };
  28726. /**
  28727. * select the edges connected to the node that is being selected
  28728. *
  28729. * @param {Node} node
  28730. * @private
  28731. */
  28732. exports._selectConnectedEdges = function(node) {
  28733. for (var i = 0; i < node.dynamicEdges.length; i++) {
  28734. var edge = node.dynamicEdges[i];
  28735. edge.select();
  28736. this._addToSelection(edge);
  28737. }
  28738. };
  28739. /**
  28740. * select the edges connected to the node that is being selected
  28741. *
  28742. * @param {Node} node
  28743. * @private
  28744. */
  28745. exports._hoverConnectedEdges = function(node) {
  28746. for (var i = 0; i < node.dynamicEdges.length; i++) {
  28747. var edge = node.dynamicEdges[i];
  28748. edge.hover = true;
  28749. this._addToHover(edge);
  28750. }
  28751. };
  28752. /**
  28753. * unselect the edges connected to the node that is being selected
  28754. *
  28755. * @param {Node} node
  28756. * @private
  28757. */
  28758. exports._unselectConnectedEdges = function(node) {
  28759. for (var i = 0; i < node.dynamicEdges.length; i++) {
  28760. var edge = node.dynamicEdges[i];
  28761. edge.unselect();
  28762. this._removeFromSelection(edge);
  28763. }
  28764. };
  28765. /**
  28766. * This is called when someone clicks on a node. either select or deselect it.
  28767. * If there is an existing selection and we don't want to append to it, clear the existing selection
  28768. *
  28769. * @param {Node || Edge} object
  28770. * @param {Boolean} append
  28771. * @param {Boolean} [doNotTrigger] | ignore trigger
  28772. * @private
  28773. */
  28774. exports._selectObject = function(object, append, doNotTrigger, highlightEdges, overrideSelectable) {
  28775. if (doNotTrigger === undefined) {
  28776. doNotTrigger = false;
  28777. }
  28778. if (highlightEdges === undefined) {
  28779. highlightEdges = true;
  28780. }
  28781. if (this._selectionIsEmpty() == false && append == false && this.forceAppendSelection == false) {
  28782. this._unselectAll(true);
  28783. }
  28784. // selectable allows the object to be selected. Override can be used if needed to bypass this.
  28785. if (object.selected == false && (this.constants.selectable == true || overrideSelectable)) {
  28786. object.select();
  28787. this._addToSelection(object);
  28788. if (object instanceof Node && this.blockConnectingEdgeSelection == false && highlightEdges == true) {
  28789. this._selectConnectedEdges(object);
  28790. }
  28791. }
  28792. // do not select the object if selectable is false, only add it to selection to allow drag to work
  28793. else if (object.selected == false) {
  28794. this._addToSelection(object);
  28795. doNotTrigger = true;
  28796. }
  28797. else {
  28798. object.unselect();
  28799. this._removeFromSelection(object);
  28800. }
  28801. if (doNotTrigger == false) {
  28802. this.emit('select', this.getSelection());
  28803. }
  28804. };
  28805. /**
  28806. * This is called when someone clicks on a node. either select or deselect it.
  28807. * If there is an existing selection and we don't want to append to it, clear the existing selection
  28808. *
  28809. * @param {Node || Edge} object
  28810. * @private
  28811. */
  28812. exports._blurObject = function(object) {
  28813. if (object.hover == true) {
  28814. object.hover = false;
  28815. this.emit("blurNode",{node:object.id});
  28816. }
  28817. };
  28818. /**
  28819. * This is called when someone clicks on a node. either select or deselect it.
  28820. * If there is an existing selection and we don't want to append to it, clear the existing selection
  28821. *
  28822. * @param {Node || Edge} object
  28823. * @private
  28824. */
  28825. exports._hoverObject = function(object) {
  28826. if (object.hover == false) {
  28827. object.hover = true;
  28828. this._addToHover(object);
  28829. if (object instanceof Node) {
  28830. this.emit("hoverNode",{node:object.id});
  28831. }
  28832. }
  28833. if (object instanceof Node) {
  28834. this._hoverConnectedEdges(object);
  28835. }
  28836. };
  28837. /**
  28838. * handles the selection part of the touch, only for navigation controls elements;
  28839. * Touch is triggered before tap, also before hold. Hold triggers after a while.
  28840. * This is the most responsive solution
  28841. *
  28842. * @param {Object} pointer
  28843. * @private
  28844. */
  28845. exports._handleTouch = function(pointer) {
  28846. };
  28847. /**
  28848. * handles the selection part of the tap;
  28849. *
  28850. * @param {Object} pointer
  28851. * @private
  28852. */
  28853. exports._handleTap = function(pointer) {
  28854. var node = this._getNodeAt(pointer);
  28855. if (node != null) {
  28856. this._selectObject(node, false);
  28857. }
  28858. else {
  28859. var edge = this._getEdgeAt(pointer);
  28860. if (edge != null) {
  28861. this._selectObject(edge, false);
  28862. }
  28863. else {
  28864. this._unselectAll();
  28865. }
  28866. }
  28867. var properties = this.getSelection();
  28868. properties['pointer'] = {
  28869. DOM: {x: pointer.x, y: pointer.y},
  28870. canvas: {x: this._XconvertDOMtoCanvas(pointer.x), y: this._YconvertDOMtoCanvas(pointer.y)}
  28871. }
  28872. this.emit("click", properties);
  28873. this._redraw();
  28874. };
  28875. /**
  28876. * handles the selection part of the double tap and opens a cluster if needed
  28877. *
  28878. * @param {Object} pointer
  28879. * @private
  28880. */
  28881. exports._handleDoubleTap = function(pointer) {
  28882. var node = this._getNodeAt(pointer);
  28883. if (node != null && node !== undefined) {
  28884. // we reset the areaCenter here so the opening of the node will occur
  28885. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  28886. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  28887. this.openCluster(node);
  28888. }
  28889. var properties = this.getSelection();
  28890. properties['pointer'] = {
  28891. DOM: {x: pointer.x, y: pointer.y},
  28892. canvas: {x: this._XconvertDOMtoCanvas(pointer.x), y: this._YconvertDOMtoCanvas(pointer.y)}
  28893. }
  28894. this.emit("doubleClick", properties);
  28895. };
  28896. /**
  28897. * Handle the onHold selection part
  28898. *
  28899. * @param pointer
  28900. * @private
  28901. */
  28902. exports._handleOnHold = function(pointer) {
  28903. var node = this._getNodeAt(pointer);
  28904. if (node != null) {
  28905. this._selectObject(node,true);
  28906. }
  28907. else {
  28908. var edge = this._getEdgeAt(pointer);
  28909. if (edge != null) {
  28910. this._selectObject(edge,true);
  28911. }
  28912. }
  28913. this._redraw();
  28914. };
  28915. /**
  28916. * handle the onRelease event. These functions are here for the navigation controls module
  28917. * and data manipulation module.
  28918. *
  28919. * @private
  28920. */
  28921. exports._handleOnRelease = function(pointer) {
  28922. this._manipulationReleaseOverload(pointer);
  28923. this._navigationReleaseOverload(pointer);
  28924. };
  28925. exports._manipulationReleaseOverload = function (pointer) {};
  28926. exports._navigationReleaseOverload = function (pointer) {};
  28927. /**
  28928. *
  28929. * retrieve the currently selected objects
  28930. * @return {{nodes: Array.<String>, edges: Array.<String>}} selection
  28931. */
  28932. exports.getSelection = function() {
  28933. var nodeIds = this.getSelectedNodes();
  28934. var edgeIds = this.getSelectedEdges();
  28935. return {nodes:nodeIds, edges:edgeIds};
  28936. };
  28937. /**
  28938. *
  28939. * retrieve the currently selected nodes
  28940. * @return {String[]} selection An array with the ids of the
  28941. * selected nodes.
  28942. */
  28943. exports.getSelectedNodes = function() {
  28944. var idArray = [];
  28945. if (this.constants.selectable == true) {
  28946. for (var nodeId in this.selectionObj.nodes) {
  28947. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  28948. idArray.push(nodeId);
  28949. }
  28950. }
  28951. }
  28952. return idArray
  28953. };
  28954. /**
  28955. *
  28956. * retrieve the currently selected edges
  28957. * @return {Array} selection An array with the ids of the
  28958. * selected nodes.
  28959. */
  28960. exports.getSelectedEdges = function() {
  28961. var idArray = [];
  28962. if (this.constants.selectable == true) {
  28963. for (var edgeId in this.selectionObj.edges) {
  28964. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  28965. idArray.push(edgeId);
  28966. }
  28967. }
  28968. }
  28969. return idArray;
  28970. };
  28971. /**
  28972. * select zero or more nodes DEPRICATED
  28973. * @param {Number[] | String[]} selection An array with the ids of the
  28974. * selected nodes.
  28975. */
  28976. exports.setSelection = function() {
  28977. console.log("setSelection is deprecated. Please use selectNodes instead.")
  28978. };
  28979. /**
  28980. * select zero or more nodes with the option to highlight edges
  28981. * @param {Number[] | String[]} selection An array with the ids of the
  28982. * selected nodes.
  28983. * @param {boolean} [highlightEdges]
  28984. */
  28985. exports.selectNodes = function(selection, highlightEdges) {
  28986. var i, iMax, id;
  28987. if (!selection || (selection.length == undefined))
  28988. throw 'Selection must be an array with ids';
  28989. // first unselect any selected node
  28990. this._unselectAll(true);
  28991. for (i = 0, iMax = selection.length; i < iMax; i++) {
  28992. id = selection[i];
  28993. var node = this.nodes[id];
  28994. if (!node) {
  28995. throw new RangeError('Node with id "' + id + '" not found');
  28996. }
  28997. this._selectObject(node,true,true,highlightEdges,true);
  28998. }
  28999. this.redraw();
  29000. };
  29001. /**
  29002. * select zero or more edges
  29003. * @param {Number[] | String[]} selection An array with the ids of the
  29004. * selected nodes.
  29005. */
  29006. exports.selectEdges = function(selection) {
  29007. var i, iMax, id;
  29008. if (!selection || (selection.length == undefined))
  29009. throw 'Selection must be an array with ids';
  29010. // first unselect any selected node
  29011. this._unselectAll(true);
  29012. for (i = 0, iMax = selection.length; i < iMax; i++) {
  29013. id = selection[i];
  29014. var edge = this.edges[id];
  29015. if (!edge) {
  29016. throw new RangeError('Edge with id "' + id + '" not found');
  29017. }
  29018. this._selectObject(edge,true,true,false,true);
  29019. }
  29020. this.redraw();
  29021. };
  29022. /**
  29023. * Validate the selection: remove ids of nodes which no longer exist
  29024. * @private
  29025. */
  29026. exports._updateSelection = function () {
  29027. for(var nodeId in this.selectionObj.nodes) {
  29028. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  29029. if (!this.nodes.hasOwnProperty(nodeId)) {
  29030. delete this.selectionObj.nodes[nodeId];
  29031. }
  29032. }
  29033. }
  29034. for(var edgeId in this.selectionObj.edges) {
  29035. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  29036. if (!this.edges.hasOwnProperty(edgeId)) {
  29037. delete this.selectionObj.edges[edgeId];
  29038. }
  29039. }
  29040. }
  29041. };
  29042. /***/ },
  29043. /* 67 */
  29044. /***/ function(module, exports, __webpack_require__) {
  29045. var util = __webpack_require__(1);
  29046. var Node = __webpack_require__(56);
  29047. var Edge = __webpack_require__(57);
  29048. /**
  29049. * clears the toolbar div element of children
  29050. *
  29051. * @private
  29052. */
  29053. exports._clearManipulatorBar = function() {
  29054. this._recursiveDOMDelete(this.manipulationDiv);
  29055. this.manipulationDOM = {};
  29056. this._manipulationReleaseOverload = function () {};
  29057. delete this.sectors['support']['nodes']['targetNode'];
  29058. delete this.sectors['support']['nodes']['targetViaNode'];
  29059. this.controlNodesActive = false;
  29060. this.freezeSimulation = false;
  29061. };
  29062. /**
  29063. * Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore
  29064. * these functions to their original functionality, we saved them in this.cachedFunctions.
  29065. * This function restores these functions to their original function.
  29066. *
  29067. * @private
  29068. */
  29069. exports._restoreOverloadedFunctions = function() {
  29070. for (var functionName in this.cachedFunctions) {
  29071. if (this.cachedFunctions.hasOwnProperty(functionName)) {
  29072. this[functionName] = this.cachedFunctions[functionName];
  29073. delete this.cachedFunctions[functionName];
  29074. }
  29075. }
  29076. };
  29077. /**
  29078. * Enable or disable edit-mode.
  29079. *
  29080. * @private
  29081. */
  29082. exports._toggleEditMode = function() {
  29083. this.editMode = !this.editMode;
  29084. var toolbar = this.manipulationDiv;
  29085. var closeDiv = this.closeDiv;
  29086. var editModeDiv = this.editModeDiv;
  29087. if (this.editMode == true) {
  29088. toolbar.style.display="block";
  29089. closeDiv.style.display="block";
  29090. editModeDiv.style.display="none";
  29091. closeDiv.onclick = this._toggleEditMode.bind(this);
  29092. }
  29093. else {
  29094. toolbar.style.display="none";
  29095. closeDiv.style.display="none";
  29096. editModeDiv.style.display="block";
  29097. closeDiv.onclick = null;
  29098. }
  29099. this._createManipulatorBar()
  29100. };
  29101. /**
  29102. * main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
  29103. *
  29104. * @private
  29105. */
  29106. exports._createManipulatorBar = function() {
  29107. // remove bound functions
  29108. if (this.boundFunction) {
  29109. this.off('select', this.boundFunction);
  29110. }
  29111. var locale = this.constants.locales[this.constants.locale];
  29112. if (this.edgeBeingEdited !== undefined) {
  29113. this.edgeBeingEdited._disableControlNodes();
  29114. this.edgeBeingEdited = undefined;
  29115. this.selectedControlNode = null;
  29116. this.controlNodesActive = false;
  29117. this._redraw();
  29118. }
  29119. // restore overloaded functions
  29120. this._restoreOverloadedFunctions();
  29121. // resume calculation
  29122. this.freezeSimulation = false;
  29123. // reset global variables
  29124. this.blockConnectingEdgeSelection = false;
  29125. this.forceAppendSelection = false;
  29126. this.manipulationDOM = {};
  29127. if (this.editMode == true) {
  29128. while (this.manipulationDiv.hasChildNodes()) {
  29129. this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
  29130. }
  29131. this.manipulationDOM['addNodeSpan'] = document.createElement('span');
  29132. this.manipulationDOM['addNodeSpan'].className = 'network-manipulationUI add';
  29133. this.manipulationDOM['addNodeLabelSpan'] = document.createElement('span');
  29134. this.manipulationDOM['addNodeLabelSpan'].className = 'network-manipulationLabel';
  29135. this.manipulationDOM['addNodeLabelSpan'].innerHTML = locale['addNode'];
  29136. this.manipulationDOM['addNodeSpan'].appendChild(this.manipulationDOM['addNodeLabelSpan']);
  29137. this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
  29138. this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
  29139. this.manipulationDOM['addEdgeSpan'] = document.createElement('span');
  29140. this.manipulationDOM['addEdgeSpan'].className = 'network-manipulationUI connect';
  29141. this.manipulationDOM['addEdgeLabelSpan'] = document.createElement('span');
  29142. this.manipulationDOM['addEdgeLabelSpan'].className = 'network-manipulationLabel';
  29143. this.manipulationDOM['addEdgeLabelSpan'].innerHTML = locale['addEdge'];
  29144. this.manipulationDOM['addEdgeSpan'].appendChild(this.manipulationDOM['addEdgeLabelSpan']);
  29145. this.manipulationDiv.appendChild(this.manipulationDOM['addNodeSpan']);
  29146. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
  29147. this.manipulationDiv.appendChild(this.manipulationDOM['addEdgeSpan']);
  29148. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  29149. this.manipulationDOM['seperatorLineDiv2'] = document.createElement('div');
  29150. this.manipulationDOM['seperatorLineDiv2'].className = 'network-seperatorLine';
  29151. this.manipulationDOM['editNodeSpan'] = document.createElement('span');
  29152. this.manipulationDOM['editNodeSpan'].className = 'network-manipulationUI edit';
  29153. this.manipulationDOM['editNodeLabelSpan'] = document.createElement('span');
  29154. this.manipulationDOM['editNodeLabelSpan'].className = 'network-manipulationLabel';
  29155. this.manipulationDOM['editNodeLabelSpan'].innerHTML = locale['editNode'];
  29156. this.manipulationDOM['editNodeSpan'].appendChild(this.manipulationDOM['editNodeLabelSpan']);
  29157. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv2']);
  29158. this.manipulationDiv.appendChild(this.manipulationDOM['editNodeSpan']);
  29159. }
  29160. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  29161. this.manipulationDOM['seperatorLineDiv3'] = document.createElement('div');
  29162. this.manipulationDOM['seperatorLineDiv3'].className = 'network-seperatorLine';
  29163. this.manipulationDOM['editEdgeSpan'] = document.createElement('span');
  29164. this.manipulationDOM['editEdgeSpan'].className = 'network-manipulationUI edit';
  29165. this.manipulationDOM['editEdgeLabelSpan'] = document.createElement('span');
  29166. this.manipulationDOM['editEdgeLabelSpan'].className = 'network-manipulationLabel';
  29167. this.manipulationDOM['editEdgeLabelSpan'].innerHTML = locale['editEdge'];
  29168. this.manipulationDOM['editEdgeSpan'].appendChild(this.manipulationDOM['editEdgeLabelSpan']);
  29169. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv3']);
  29170. this.manipulationDiv.appendChild(this.manipulationDOM['editEdgeSpan']);
  29171. }
  29172. if (this._selectionIsEmpty() == false) {
  29173. this.manipulationDOM['seperatorLineDiv4'] = document.createElement('div');
  29174. this.manipulationDOM['seperatorLineDiv4'].className = 'network-seperatorLine';
  29175. this.manipulationDOM['deleteSpan'] = document.createElement('span');
  29176. this.manipulationDOM['deleteSpan'].className = 'network-manipulationUI delete';
  29177. this.manipulationDOM['deleteLabelSpan'] = document.createElement('span');
  29178. this.manipulationDOM['deleteLabelSpan'].className = 'network-manipulationLabel';
  29179. this.manipulationDOM['deleteLabelSpan'].innerHTML = locale['del'];
  29180. this.manipulationDOM['deleteSpan'].appendChild(this.manipulationDOM['deleteLabelSpan']);
  29181. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv4']);
  29182. this.manipulationDiv.appendChild(this.manipulationDOM['deleteSpan']);
  29183. }
  29184. // bind the icons
  29185. this.manipulationDOM['addNodeSpan'].onclick = this._createAddNodeToolbar.bind(this);
  29186. this.manipulationDOM['addEdgeSpan'].onclick = this._createAddEdgeToolbar.bind(this);
  29187. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  29188. this.manipulationDOM['editNodeSpan'].onclick = this._editNode.bind(this);
  29189. }
  29190. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  29191. this.manipulationDOM['editEdgeSpan'].onclick = this._createEditEdgeToolbar.bind(this);
  29192. }
  29193. if (this._selectionIsEmpty() == false) {
  29194. this.manipulationDOM['deleteSpan'].onclick = this._deleteSelected.bind(this);
  29195. }
  29196. this.closeDiv.onclick = this._toggleEditMode.bind(this);
  29197. var me = this;
  29198. this.boundFunction = me._createManipulatorBar;
  29199. this.on('select', this.boundFunction);
  29200. }
  29201. else {
  29202. while (this.editModeDiv.hasChildNodes()) {
  29203. this.editModeDiv.removeChild(this.editModeDiv.firstChild);
  29204. }
  29205. this.manipulationDOM['editModeSpan'] = document.createElement('span');
  29206. this.manipulationDOM['editModeSpan'].className = 'network-manipulationUI edit editmode';
  29207. this.manipulationDOM['editModeLabelSpan'] = document.createElement('span');
  29208. this.manipulationDOM['editModeLabelSpan'].className = 'network-manipulationLabel';
  29209. this.manipulationDOM['editModeLabelSpan'].innerHTML = locale['edit'];
  29210. this.manipulationDOM['editModeSpan'].appendChild(this.manipulationDOM['editModeLabelSpan']);
  29211. this.editModeDiv.appendChild(this.manipulationDOM['editModeSpan']);
  29212. this.manipulationDOM['editModeSpan'].onclick = this._toggleEditMode.bind(this);
  29213. }
  29214. };
  29215. /**
  29216. * Create the toolbar for adding Nodes
  29217. *
  29218. * @private
  29219. */
  29220. exports._createAddNodeToolbar = function() {
  29221. // clear the toolbar
  29222. this._clearManipulatorBar();
  29223. if (this.boundFunction) {
  29224. this.off('select', this.boundFunction);
  29225. }
  29226. var locale = this.constants.locales[this.constants.locale];
  29227. this.manipulationDOM = {};
  29228. this.manipulationDOM['backSpan'] = document.createElement('span');
  29229. this.manipulationDOM['backSpan'].className = 'network-manipulationUI back';
  29230. this.manipulationDOM['backLabelSpan'] = document.createElement('span');
  29231. this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel';
  29232. this.manipulationDOM['backLabelSpan'].innerHTML = locale['back'];
  29233. this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']);
  29234. this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
  29235. this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
  29236. this.manipulationDOM['descriptionSpan'] = document.createElement('span');
  29237. this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none';
  29238. this.manipulationDOM['descriptionLabelSpan'] = document.createElement('span');
  29239. this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel';
  29240. this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['addDescription'];
  29241. this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']);
  29242. this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']);
  29243. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
  29244. this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']);
  29245. // bind the icon
  29246. this.manipulationDOM['backSpan'].onclick = this._createManipulatorBar.bind(this);
  29247. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  29248. var me = this;
  29249. this.boundFunction = me._addNode;
  29250. this.on('select', this.boundFunction);
  29251. };
  29252. /**
  29253. * create the toolbar to connect nodes
  29254. *
  29255. * @private
  29256. */
  29257. exports._createAddEdgeToolbar = function() {
  29258. // clear the toolbar
  29259. this._clearManipulatorBar();
  29260. this._unselectAll(true);
  29261. this.freezeSimulation = true;
  29262. if (this.boundFunction) {
  29263. this.off('select', this.boundFunction);
  29264. }
  29265. var locale = this.constants.locales[this.constants.locale];
  29266. this._unselectAll();
  29267. this.forceAppendSelection = false;
  29268. this.blockConnectingEdgeSelection = true;
  29269. this.manipulationDOM = {};
  29270. this.manipulationDOM['backSpan'] = document.createElement('span');
  29271. this.manipulationDOM['backSpan'].className = 'network-manipulationUI back';
  29272. this.manipulationDOM['backLabelSpan'] = document.createElement('span');
  29273. this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel';
  29274. this.manipulationDOM['backLabelSpan'].innerHTML = locale['back'];
  29275. this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']);
  29276. this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
  29277. this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
  29278. this.manipulationDOM['descriptionSpan'] = document.createElement('span');
  29279. this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none';
  29280. this.manipulationDOM['descriptionLabelSpan'] = document.createElement('span');
  29281. this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel';
  29282. this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['edgeDescription'];
  29283. this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']);
  29284. this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']);
  29285. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
  29286. this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']);
  29287. // bind the icon
  29288. this.manipulationDOM['backSpan'].onclick = this._createManipulatorBar.bind(this);
  29289. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  29290. var me = this;
  29291. this.boundFunction = me._handleConnect;
  29292. this.on('select', this.boundFunction);
  29293. // temporarily overload functions
  29294. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  29295. this.cachedFunctions["_manipulationReleaseOverload"] = this._manipulationReleaseOverload;
  29296. this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
  29297. this.cachedFunctions["_handleDragEnd"] = this._handleDragEnd;
  29298. this.cachedFunctions["_handleOnHold"] = this._handleOnHold;
  29299. this._handleTouch = this._handleConnect;
  29300. this._manipulationReleaseOverload = function () {};
  29301. this._handleOnHold = function () {};
  29302. this._handleDragStart = function () {};
  29303. this._handleDragEnd = this._finishConnect;
  29304. // redraw to show the unselect
  29305. this._redraw();
  29306. };
  29307. /**
  29308. * create the toolbar to edit edges
  29309. *
  29310. * @private
  29311. */
  29312. exports._createEditEdgeToolbar = function() {
  29313. // clear the toolbar
  29314. this._clearManipulatorBar();
  29315. this.controlNodesActive = true;
  29316. if (this.boundFunction) {
  29317. this.off('select', this.boundFunction);
  29318. }
  29319. this.edgeBeingEdited = this._getSelectedEdge();
  29320. this.edgeBeingEdited._enableControlNodes();
  29321. var locale = this.constants.locales[this.constants.locale];
  29322. this.manipulationDOM = {};
  29323. this.manipulationDOM['backSpan'] = document.createElement('span');
  29324. this.manipulationDOM['backSpan'].className = 'network-manipulationUI back';
  29325. this.manipulationDOM['backLabelSpan'] = document.createElement('span');
  29326. this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel';
  29327. this.manipulationDOM['backLabelSpan'].innerHTML = locale['back'];
  29328. this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']);
  29329. this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div');
  29330. this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine';
  29331. this.manipulationDOM['descriptionSpan'] = document.createElement('span');
  29332. this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none';
  29333. this.manipulationDOM['descriptionLabelSpan'] = document.createElement('span');
  29334. this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel';
  29335. this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['editEdgeDescription'];
  29336. this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']);
  29337. this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']);
  29338. this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']);
  29339. this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']);
  29340. // bind the icon
  29341. this.manipulationDOM['backSpan'].onclick = this._createManipulatorBar.bind(this);
  29342. // temporarily overload functions
  29343. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  29344. this.cachedFunctions["_manipulationReleaseOverload"] = this._manipulationReleaseOverload;
  29345. this.cachedFunctions["_handleTap"] = this._handleTap;
  29346. this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
  29347. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  29348. this._handleTouch = this._selectControlNode;
  29349. this._handleTap = function () {};
  29350. this._handleOnDrag = this._controlNodeDrag;
  29351. this._handleDragStart = function () {}
  29352. this._manipulationReleaseOverload = this._releaseControlNode;
  29353. // redraw to show the unselect
  29354. this._redraw();
  29355. };
  29356. /**
  29357. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  29358. * to walk the user through the process.
  29359. *
  29360. * @private
  29361. */
  29362. exports._selectControlNode = function(pointer) {
  29363. this.edgeBeingEdited.controlNodes.from.unselect();
  29364. this.edgeBeingEdited.controlNodes.to.unselect();
  29365. this.selectedControlNode = this.edgeBeingEdited._getSelectedControlNode(this._XconvertDOMtoCanvas(pointer.x),this._YconvertDOMtoCanvas(pointer.y));
  29366. if (this.selectedControlNode !== null) {
  29367. this.selectedControlNode.select();
  29368. this.freezeSimulation = true;
  29369. }
  29370. this._redraw();
  29371. };
  29372. /**
  29373. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  29374. * to walk the user through the process.
  29375. *
  29376. * @private
  29377. */
  29378. exports._controlNodeDrag = function(event) {
  29379. var pointer = this._getPointer(event.gesture.center);
  29380. if (this.selectedControlNode !== null && this.selectedControlNode !== undefined) {
  29381. this.selectedControlNode.x = this._XconvertDOMtoCanvas(pointer.x);
  29382. this.selectedControlNode.y = this._YconvertDOMtoCanvas(pointer.y);
  29383. }
  29384. this._redraw();
  29385. };
  29386. /**
  29387. *
  29388. * @param pointer
  29389. * @private
  29390. */
  29391. exports._releaseControlNode = function(pointer) {
  29392. var newNode = this._getNodeAt(pointer);
  29393. if (newNode !== null) {
  29394. if (this.edgeBeingEdited.controlNodes.from.selected == true) {
  29395. this.edgeBeingEdited._restoreControlNodes();
  29396. this._editEdge(newNode.id, this.edgeBeingEdited.to.id);
  29397. this.edgeBeingEdited.controlNodes.from.unselect();
  29398. }
  29399. if (this.edgeBeingEdited.controlNodes.to.selected == true) {
  29400. this.edgeBeingEdited._restoreControlNodes();
  29401. this._editEdge(this.edgeBeingEdited.from.id, newNode.id);
  29402. this.edgeBeingEdited.controlNodes.to.unselect();
  29403. }
  29404. }
  29405. else {
  29406. this.edgeBeingEdited._restoreControlNodes();
  29407. }
  29408. this.freezeSimulation = false;
  29409. this._redraw();
  29410. };
  29411. /**
  29412. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  29413. * to walk the user through the process.
  29414. *
  29415. * @private
  29416. */
  29417. exports._handleConnect = function(pointer) {
  29418. if (this._getSelectedNodeCount() == 0) {
  29419. var node = this._getNodeAt(pointer);
  29420. if (node != null) {
  29421. if (node.clusterSize > 1) {
  29422. alert(this.constants.locales[this.constants.locale]['createEdgeError'])
  29423. }
  29424. else {
  29425. this._selectObject(node,false);
  29426. var supportNodes = this.sectors['support']['nodes'];
  29427. // create a node the temporary line can look at
  29428. supportNodes['targetNode'] = new Node({id:'targetNode'},{},{},this.constants);
  29429. var targetNode = supportNodes['targetNode'];
  29430. targetNode.x = node.x;
  29431. targetNode.y = node.y;
  29432. // create a temporary edge
  29433. this.edges['connectionEdge'] = new Edge({id:"connectionEdge",from:node.id,to:targetNode.id}, this, this.constants);
  29434. var connectionEdge = this.edges['connectionEdge'];
  29435. connectionEdge.from = node;
  29436. connectionEdge.connected = true;
  29437. connectionEdge.options.smoothCurves = {enabled: true,
  29438. dynamic: false,
  29439. type: "continuous",
  29440. roundness: 0.5
  29441. };
  29442. connectionEdge.selected = true;
  29443. connectionEdge.to = targetNode;
  29444. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  29445. this._handleOnDrag = function(event) {
  29446. var pointer = this._getPointer(event.gesture.center);
  29447. var connectionEdge = this.edges['connectionEdge'];
  29448. connectionEdge.to.x = this._XconvertDOMtoCanvas(pointer.x);
  29449. connectionEdge.to.y = this._YconvertDOMtoCanvas(pointer.y);
  29450. };
  29451. this.moving = true;
  29452. this.start();
  29453. }
  29454. }
  29455. }
  29456. };
  29457. exports._finishConnect = function(event) {
  29458. if (this._getSelectedNodeCount() == 1) {
  29459. var pointer = this._getPointer(event.gesture.center);
  29460. // restore the drag function
  29461. this._handleOnDrag = this.cachedFunctions["_handleOnDrag"];
  29462. delete this.cachedFunctions["_handleOnDrag"];
  29463. // remember the edge id
  29464. var connectFromId = this.edges['connectionEdge'].fromId;
  29465. // remove the temporary nodes and edge
  29466. delete this.edges['connectionEdge'];
  29467. delete this.sectors['support']['nodes']['targetNode'];
  29468. delete this.sectors['support']['nodes']['targetViaNode'];
  29469. var node = this._getNodeAt(pointer);
  29470. if (node != null) {
  29471. if (node.clusterSize > 1) {
  29472. alert(this.constants.locales[this.constants.locale]["createEdgeError"])
  29473. }
  29474. else {
  29475. this._createEdge(connectFromId,node.id);
  29476. this._createManipulatorBar();
  29477. }
  29478. }
  29479. this._unselectAll();
  29480. }
  29481. };
  29482. /**
  29483. * Adds a node on the specified location
  29484. */
  29485. exports._addNode = function() {
  29486. if (this._selectionIsEmpty() && this.editMode == true) {
  29487. var positionObject = this._pointerToPositionObject(this.pointerPosition);
  29488. var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",allowedToMoveX:true,allowedToMoveY:true};
  29489. if (this.triggerFunctions.add) {
  29490. if (this.triggerFunctions.add.length == 2) {
  29491. var me = this;
  29492. this.triggerFunctions.add(defaultData, function(finalizedData) {
  29493. me.nodesData.add(finalizedData);
  29494. me._createManipulatorBar();
  29495. me.moving = true;
  29496. me.start();
  29497. });
  29498. }
  29499. else {
  29500. throw new Error('The function for add does not support two arguments (data,callback)');
  29501. this._createManipulatorBar();
  29502. this.moving = true;
  29503. this.start();
  29504. }
  29505. }
  29506. else {
  29507. this.nodesData.add(defaultData);
  29508. this._createManipulatorBar();
  29509. this.moving = true;
  29510. this.start();
  29511. }
  29512. }
  29513. };
  29514. /**
  29515. * connect two nodes with a new edge.
  29516. *
  29517. * @private
  29518. */
  29519. exports._createEdge = function(sourceNodeId,targetNodeId) {
  29520. if (this.editMode == true) {
  29521. var defaultData = {from:sourceNodeId, to:targetNodeId};
  29522. if (this.triggerFunctions.connect) {
  29523. if (this.triggerFunctions.connect.length == 2) {
  29524. var me = this;
  29525. this.triggerFunctions.connect(defaultData, function(finalizedData) {
  29526. me.edgesData.add(finalizedData);
  29527. me.moving = true;
  29528. me.start();
  29529. });
  29530. }
  29531. else {
  29532. throw new Error('The function for connect does not support two arguments (data,callback)');
  29533. this.moving = true;
  29534. this.start();
  29535. }
  29536. }
  29537. else {
  29538. this.edgesData.add(defaultData);
  29539. this.moving = true;
  29540. this.start();
  29541. }
  29542. }
  29543. };
  29544. /**
  29545. * connect two nodes with a new edge.
  29546. *
  29547. * @private
  29548. */
  29549. exports._editEdge = function(sourceNodeId,targetNodeId) {
  29550. if (this.editMode == true) {
  29551. var defaultData = {id: this.edgeBeingEdited.id, from:sourceNodeId, to:targetNodeId};
  29552. if (this.triggerFunctions.editEdge) {
  29553. if (this.triggerFunctions.editEdge.length == 2) {
  29554. var me = this;
  29555. this.triggerFunctions.editEdge(defaultData, function(finalizedData) {
  29556. me.edgesData.update(finalizedData);
  29557. me.moving = true;
  29558. me.start();
  29559. });
  29560. }
  29561. else {
  29562. throw new Error('The function for edit does not support two arguments (data, callback)');
  29563. this.moving = true;
  29564. this.start();
  29565. }
  29566. }
  29567. else {
  29568. this.edgesData.update(defaultData);
  29569. this.moving = true;
  29570. this.start();
  29571. }
  29572. }
  29573. };
  29574. /**
  29575. * Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color.
  29576. *
  29577. * @private
  29578. */
  29579. exports._editNode = function() {
  29580. if (this.triggerFunctions.edit && this.editMode == true) {
  29581. var node = this._getSelectedNode();
  29582. var data = {id:node.id,
  29583. label: node.label,
  29584. group: node.options.group,
  29585. shape: node.options.shape,
  29586. color: {
  29587. background:node.options.color.background,
  29588. border:node.options.color.border,
  29589. highlight: {
  29590. background:node.options.color.highlight.background,
  29591. border:node.options.color.highlight.border
  29592. }
  29593. }};
  29594. if (this.triggerFunctions.edit.length == 2) {
  29595. var me = this;
  29596. this.triggerFunctions.edit(data, function (finalizedData) {
  29597. me.nodesData.update(finalizedData);
  29598. me._createManipulatorBar();
  29599. me.moving = true;
  29600. me.start();
  29601. });
  29602. }
  29603. else {
  29604. throw new Error('The function for edit does not support two arguments (data, callback)');
  29605. }
  29606. }
  29607. else {
  29608. throw new Error('No edit function has been bound to this button');
  29609. }
  29610. };
  29611. /**
  29612. * delete everything in the selection
  29613. *
  29614. * @private
  29615. */
  29616. exports._deleteSelected = function() {
  29617. if (!this._selectionIsEmpty() && this.editMode == true) {
  29618. if (!this._clusterInSelection()) {
  29619. var selectedNodes = this.getSelectedNodes();
  29620. var selectedEdges = this.getSelectedEdges();
  29621. if (this.triggerFunctions.del) {
  29622. var me = this;
  29623. var data = {nodes: selectedNodes, edges: selectedEdges};
  29624. if (this.triggerFunctions.del.length == 2) {
  29625. this.triggerFunctions.del(data, function (finalizedData) {
  29626. me.edgesData.remove(finalizedData.edges);
  29627. me.nodesData.remove(finalizedData.nodes);
  29628. me._unselectAll();
  29629. me.moving = true;
  29630. me.start();
  29631. });
  29632. }
  29633. else {
  29634. throw new Error('The function for delete does not support two arguments (data, callback)')
  29635. }
  29636. }
  29637. else {
  29638. this.edgesData.remove(selectedEdges);
  29639. this.nodesData.remove(selectedNodes);
  29640. this._unselectAll();
  29641. this.moving = true;
  29642. this.start();
  29643. }
  29644. }
  29645. else {
  29646. alert(this.constants.locales[this.constants.locale]["deleteClusterError"]);
  29647. }
  29648. }
  29649. };
  29650. /***/ },
  29651. /* 68 */
  29652. /***/ function(module, exports, __webpack_require__) {
  29653. var util = __webpack_require__(1);
  29654. var Hammer = __webpack_require__(19);
  29655. exports._cleanNavigation = function() {
  29656. // clean hammer bindings
  29657. if (this.navigationHammers.existing.length != 0) {
  29658. for (var i = 0; i < this.navigationHammers.existing.length; i++) {
  29659. this.navigationHammers.existing[i].dispose();
  29660. }
  29661. this.navigationHammers.existing = [];
  29662. }
  29663. this._navigationReleaseOverload = function () {};
  29664. // clean up previous navigation items
  29665. if (this.navigationDivs && this.navigationDivs['wrapper'] && this.navigationDivs['wrapper'].parentNode) {
  29666. this.navigationDivs['wrapper'].parentNode.removeChild(this.navigationDivs['wrapper']);
  29667. }
  29668. };
  29669. /**
  29670. * Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation
  29671. * they have a triggerFunction which is called on click. If the position of the navigation controls is dependent
  29672. * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false.
  29673. * This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas.
  29674. *
  29675. * @private
  29676. */
  29677. exports._loadNavigationElements = function() {
  29678. this._cleanNavigation();
  29679. this.navigationDivs = {};
  29680. var navigationDivs = ['up','down','left','right','zoomIn','zoomOut','zoomExtends'];
  29681. var navigationDivActions = ['_moveUp','_moveDown','_moveLeft','_moveRight','_zoomIn','_zoomOut','_zoomExtent'];
  29682. this.navigationDivs['wrapper'] = document.createElement('div');
  29683. this.frame.appendChild(this.navigationDivs['wrapper']);
  29684. for (var i = 0; i < navigationDivs.length; i++) {
  29685. this.navigationDivs[navigationDivs[i]] = document.createElement('div');
  29686. this.navigationDivs[navigationDivs[i]].className = 'network-navigation ' + navigationDivs[i];
  29687. this.navigationDivs['wrapper'].appendChild(this.navigationDivs[navigationDivs[i]]);
  29688. var hammer = Hammer(this.navigationDivs[navigationDivs[i]], {prevent_default: true});
  29689. hammer.on('touch', this[navigationDivActions[i]].bind(this));
  29690. this.navigationHammers._new.push(hammer);
  29691. }
  29692. this._navigationReleaseOverload = this._stopMovement;
  29693. this.navigationHammers.existing = this.navigationHammers._new;
  29694. };
  29695. /**
  29696. * this stops all movement induced by the navigation buttons
  29697. *
  29698. * @private
  29699. */
  29700. exports._zoomExtent = function(event) {
  29701. this.zoomExtent({duration:700});
  29702. event.stopPropagation();
  29703. };
  29704. /**
  29705. * this stops all movement induced by the navigation buttons
  29706. *
  29707. * @private
  29708. */
  29709. exports._stopMovement = function() {
  29710. this._xStopMoving();
  29711. this._yStopMoving();
  29712. this._stopZoom();
  29713. };
  29714. /**
  29715. * move the screen up
  29716. * By using the increments, instead of adding a fixed number to the translation, we keep fluent and
  29717. * instant movement. The onKeypress event triggers immediately, then pauses, then triggers frequently
  29718. * To avoid this behaviour, we do the translation in the start loop.
  29719. *
  29720. * @private
  29721. */
  29722. exports._moveUp = function(event) {
  29723. this.yIncrement = this.constants.keyboard.speed.y;
  29724. this.start(); // if there is no node movement, the calculation wont be done
  29725. event.preventDefault();
  29726. };
  29727. /**
  29728. * move the screen down
  29729. * @private
  29730. */
  29731. exports._moveDown = function(event) {
  29732. this.yIncrement = -this.constants.keyboard.speed.y;
  29733. this.start(); // if there is no node movement, the calculation wont be done
  29734. event.preventDefault();
  29735. };
  29736. /**
  29737. * move the screen left
  29738. * @private
  29739. */
  29740. exports._moveLeft = function(event) {
  29741. this.xIncrement = this.constants.keyboard.speed.x;
  29742. this.start(); // if there is no node movement, the calculation wont be done
  29743. event.preventDefault();
  29744. };
  29745. /**
  29746. * move the screen right
  29747. * @private
  29748. */
  29749. exports._moveRight = function(event) {
  29750. this.xIncrement = -this.constants.keyboard.speed.y;
  29751. this.start(); // if there is no node movement, the calculation wont be done
  29752. event.preventDefault();
  29753. };
  29754. /**
  29755. * Zoom in, using the same method as the movement.
  29756. * @private
  29757. */
  29758. exports._zoomIn = function(event) {
  29759. this.zoomIncrement = this.constants.keyboard.speed.zoom;
  29760. this.start(); // if there is no node movement, the calculation wont be done
  29761. event.preventDefault();
  29762. };
  29763. /**
  29764. * Zoom out
  29765. * @private
  29766. */
  29767. exports._zoomOut = function(event) {
  29768. this.zoomIncrement = -this.constants.keyboard.speed.zoom;
  29769. this.start(); // if there is no node movement, the calculation wont be done
  29770. event.preventDefault();
  29771. };
  29772. /**
  29773. * Stop zooming and unhighlight the zoom controls
  29774. * @private
  29775. */
  29776. exports._stopZoom = function(event) {
  29777. this.zoomIncrement = 0;
  29778. event && event.preventDefault();
  29779. };
  29780. /**
  29781. * Stop moving in the Y direction and unHighlight the up and down
  29782. * @private
  29783. */
  29784. exports._yStopMoving = function(event) {
  29785. this.yIncrement = 0;
  29786. event && event.preventDefault();
  29787. };
  29788. /**
  29789. * Stop moving in the X direction and unHighlight left and right.
  29790. * @private
  29791. */
  29792. exports._xStopMoving = function(event) {
  29793. this.xIncrement = 0;
  29794. event && event.preventDefault();
  29795. };
  29796. /***/ },
  29797. /* 69 */
  29798. /***/ function(module, exports, __webpack_require__) {
  29799. exports._resetLevels = function() {
  29800. for (var nodeId in this.nodes) {
  29801. if (this.nodes.hasOwnProperty(nodeId)) {
  29802. var node = this.nodes[nodeId];
  29803. if (node.preassignedLevel == false) {
  29804. node.level = -1;
  29805. node.hierarchyEnumerated = false;
  29806. }
  29807. }
  29808. }
  29809. };
  29810. /**
  29811. * This is the main function to layout the nodes in a hierarchical way.
  29812. * It checks if the node details are supplied correctly
  29813. *
  29814. * @private
  29815. */
  29816. exports._setupHierarchicalLayout = function() {
  29817. if (this.constants.hierarchicalLayout.enabled == true && this.nodeIndices.length > 0) {
  29818. // get the size of the largest hubs and check if the user has defined a level for a node.
  29819. var hubsize = 0;
  29820. var node, nodeId;
  29821. var definedLevel = false;
  29822. var undefinedLevel = false;
  29823. for (nodeId in this.nodes) {
  29824. if (this.nodes.hasOwnProperty(nodeId)) {
  29825. node = this.nodes[nodeId];
  29826. if (node.level != -1) {
  29827. definedLevel = true;
  29828. }
  29829. else {
  29830. undefinedLevel = true;
  29831. }
  29832. if (hubsize < node.edges.length) {
  29833. hubsize = node.edges.length;
  29834. }
  29835. }
  29836. }
  29837. // if the user defined some levels but not all, alert and run without hierarchical layout
  29838. if (undefinedLevel == true && definedLevel == true) {
  29839. throw new Error("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.");
  29840. this.zoomExtent({duration:0},true,this.constants.clustering.enabled);
  29841. if (!this.constants.clustering.enabled) {
  29842. this.start();
  29843. }
  29844. }
  29845. else {
  29846. // setup the system to use hierarchical method.
  29847. this._changeConstants();
  29848. // define levels if undefined by the users. Based on hubsize
  29849. if (undefinedLevel == true) {
  29850. if (this.constants.hierarchicalLayout.layout == "hubsize") {
  29851. this._determineLevels(hubsize);
  29852. }
  29853. else {
  29854. this._determineLevelsDirected(false);
  29855. }
  29856. }
  29857. // check the distribution of the nodes per level.
  29858. var distribution = this._getDistribution();
  29859. // place the nodes on the canvas. This also stablilizes the system.
  29860. this._placeNodesByHierarchy(distribution);
  29861. // start the simulation.
  29862. this.start();
  29863. }
  29864. }
  29865. };
  29866. /**
  29867. * This function places the nodes on the canvas based on the hierarchial distribution.
  29868. *
  29869. * @param {Object} distribution | obtained by the function this._getDistribution()
  29870. * @private
  29871. */
  29872. exports._placeNodesByHierarchy = function(distribution) {
  29873. var nodeId, node;
  29874. // start placing all the level 0 nodes first. Then recursively position their branches.
  29875. for (var level in distribution) {
  29876. if (distribution.hasOwnProperty(level)) {
  29877. for (nodeId in distribution[level].nodes) {
  29878. if (distribution[level].nodes.hasOwnProperty(nodeId)) {
  29879. node = distribution[level].nodes[nodeId];
  29880. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  29881. if (node.xFixed) {
  29882. node.x = distribution[level].minPos;
  29883. node.xFixed = false;
  29884. distribution[level].minPos += distribution[level].nodeSpacing;
  29885. }
  29886. }
  29887. else {
  29888. if (node.yFixed) {
  29889. node.y = distribution[level].minPos;
  29890. node.yFixed = false;
  29891. distribution[level].minPos += distribution[level].nodeSpacing;
  29892. }
  29893. }
  29894. this._placeBranchNodes(node.edges,node.id,distribution,node.level);
  29895. }
  29896. }
  29897. }
  29898. }
  29899. // stabilize the system after positioning. This function calls zoomExtent.
  29900. this._stabilize();
  29901. };
  29902. /**
  29903. * This function get the distribution of levels based on hubsize
  29904. *
  29905. * @returns {Object}
  29906. * @private
  29907. */
  29908. exports._getDistribution = function() {
  29909. var distribution = {};
  29910. var nodeId, node, level;
  29911. // 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.
  29912. // the fix of X is removed after the x value has been set.
  29913. for (nodeId in this.nodes) {
  29914. if (this.nodes.hasOwnProperty(nodeId)) {
  29915. node = this.nodes[nodeId];
  29916. node.xFixed = true;
  29917. node.yFixed = true;
  29918. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  29919. node.y = this.constants.hierarchicalLayout.levelSeparation*node.level;
  29920. }
  29921. else {
  29922. node.x = this.constants.hierarchicalLayout.levelSeparation*node.level;
  29923. }
  29924. if (distribution[node.level] === undefined) {
  29925. distribution[node.level] = {amount: 0, nodes: {}, minPos:0, nodeSpacing:0};
  29926. }
  29927. distribution[node.level].amount += 1;
  29928. distribution[node.level].nodes[nodeId] = node;
  29929. }
  29930. }
  29931. // determine the largest amount of nodes of all levels
  29932. var maxCount = 0;
  29933. for (level in distribution) {
  29934. if (distribution.hasOwnProperty(level)) {
  29935. if (maxCount < distribution[level].amount) {
  29936. maxCount = distribution[level].amount;
  29937. }
  29938. }
  29939. }
  29940. // set the initial position and spacing of each nodes accordingly
  29941. for (level in distribution) {
  29942. if (distribution.hasOwnProperty(level)) {
  29943. distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing;
  29944. distribution[level].nodeSpacing /= (distribution[level].amount + 1);
  29945. distribution[level].minPos = distribution[level].nodeSpacing - (0.5 * (distribution[level].amount + 1) * distribution[level].nodeSpacing);
  29946. }
  29947. }
  29948. return distribution;
  29949. };
  29950. /**
  29951. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  29952. *
  29953. * @param hubsize
  29954. * @private
  29955. */
  29956. exports._determineLevels = function(hubsize) {
  29957. var nodeId, node;
  29958. // determine hubs
  29959. for (nodeId in this.nodes) {
  29960. if (this.nodes.hasOwnProperty(nodeId)) {
  29961. node = this.nodes[nodeId];
  29962. if (node.edges.length == hubsize) {
  29963. node.level = 0;
  29964. }
  29965. }
  29966. }
  29967. // branch from hubs
  29968. for (nodeId in this.nodes) {
  29969. if (this.nodes.hasOwnProperty(nodeId)) {
  29970. node = this.nodes[nodeId];
  29971. if (node.level == 0) {
  29972. this._setLevel(1,node.edges,node.id);
  29973. }
  29974. }
  29975. }
  29976. };
  29977. /**
  29978. * this function allocates nodes in levels based on the direction of the edges
  29979. *
  29980. * @param hubsize
  29981. * @private
  29982. */
  29983. exports._determineLevelsDirected = function() {
  29984. var nodeId, node, firstNode;
  29985. var minLevel = 10000;
  29986. // set first node to source
  29987. firstNode = this.nodes[this.nodeIndices[0]];
  29988. firstNode.level = minLevel;
  29989. this._setLevelDirected(minLevel,firstNode.edges,firstNode.id);
  29990. // get the minimum level
  29991. for (nodeId in this.nodes) {
  29992. if (this.nodes.hasOwnProperty(nodeId)) {
  29993. node = this.nodes[nodeId];
  29994. minLevel = node.level < minLevel ? node.level : minLevel;
  29995. }
  29996. }
  29997. // subtract the minimum from the set so we have a range starting from 0
  29998. for (nodeId in this.nodes) {
  29999. if (this.nodes.hasOwnProperty(nodeId)) {
  30000. node = this.nodes[nodeId];
  30001. node.level -= minLevel;
  30002. }
  30003. }
  30004. };
  30005. /**
  30006. * Since hierarchical layout does not support:
  30007. * - smooth curves (based on the physics),
  30008. * - clustering (based on dynamic node counts)
  30009. *
  30010. * We disable both features so there will be no problems.
  30011. *
  30012. * @private
  30013. */
  30014. exports._changeConstants = function() {
  30015. this.constants.clustering.enabled = false;
  30016. this.constants.physics.barnesHut.enabled = false;
  30017. this.constants.physics.hierarchicalRepulsion.enabled = true;
  30018. this._loadSelectedForceSolver();
  30019. if (this.constants.smoothCurves.enabled == true) {
  30020. this.constants.smoothCurves.dynamic = false;
  30021. }
  30022. this._configureSmoothCurves();
  30023. var config = this.constants.hierarchicalLayout;
  30024. config.levelSeparation = Math.abs(config.levelSeparation);
  30025. if (config.direction == "RL" || config.direction == "DU") {
  30026. config.levelSeparation *= -1;
  30027. }
  30028. if (config.direction == "RL" || config.direction == "LR") {
  30029. if (this.constants.smoothCurves.enabled == true) {
  30030. this.constants.smoothCurves.type = "vertical";
  30031. }
  30032. }
  30033. else {
  30034. if (this.constants.smoothCurves.enabled == true) {
  30035. this.constants.smoothCurves.type = "horizontal";
  30036. }
  30037. }
  30038. };
  30039. /**
  30040. * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
  30041. * on a X position that ensures there will be no overlap.
  30042. *
  30043. * @param edges
  30044. * @param parentId
  30045. * @param distribution
  30046. * @param parentLevel
  30047. * @private
  30048. */
  30049. exports._placeBranchNodes = function(edges, parentId, distribution, parentLevel) {
  30050. for (var i = 0; i < edges.length; i++) {
  30051. var childNode = null;
  30052. if (edges[i].toId == parentId) {
  30053. childNode = edges[i].from;
  30054. }
  30055. else {
  30056. childNode = edges[i].to;
  30057. }
  30058. // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
  30059. var nodeMoved = false;
  30060. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  30061. if (childNode.xFixed && childNode.level > parentLevel) {
  30062. childNode.xFixed = false;
  30063. childNode.x = distribution[childNode.level].minPos;
  30064. nodeMoved = true;
  30065. }
  30066. }
  30067. else {
  30068. if (childNode.yFixed && childNode.level > parentLevel) {
  30069. childNode.yFixed = false;
  30070. childNode.y = distribution[childNode.level].minPos;
  30071. nodeMoved = true;
  30072. }
  30073. }
  30074. if (nodeMoved == true) {
  30075. distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing;
  30076. if (childNode.edges.length > 1) {
  30077. this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level);
  30078. }
  30079. }
  30080. }
  30081. };
  30082. /**
  30083. * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
  30084. *
  30085. * @param level
  30086. * @param edges
  30087. * @param parentId
  30088. * @private
  30089. */
  30090. exports._setLevel = function(level, edges, parentId) {
  30091. for (var i = 0; i < edges.length; i++) {
  30092. var childNode = null;
  30093. if (edges[i].toId == parentId) {
  30094. childNode = edges[i].from;
  30095. }
  30096. else {
  30097. childNode = edges[i].to;
  30098. }
  30099. if (childNode.level == -1 || childNode.level > level) {
  30100. childNode.level = level;
  30101. if (childNode.edges.length > 1) {
  30102. this._setLevel(level+1, childNode.edges, childNode.id);
  30103. }
  30104. }
  30105. }
  30106. };
  30107. /**
  30108. * this function is called recursively to enumerate the branched of the first node and give each node a level based on edge direction
  30109. *
  30110. * @param level
  30111. * @param edges
  30112. * @param parentId
  30113. * @private
  30114. */
  30115. exports._setLevelDirected = function(level, edges, parentId) {
  30116. this.nodes[parentId].hierarchyEnumerated = true;
  30117. var childNode, direction;
  30118. for (var i = 0; i < edges.length; i++) {
  30119. direction = 1;
  30120. if (edges[i].toId == parentId) {
  30121. childNode = edges[i].from;
  30122. direction = -1;
  30123. }
  30124. else {
  30125. childNode = edges[i].to;
  30126. }
  30127. if (childNode.level == -1) {
  30128. childNode.level = level + direction;
  30129. }
  30130. }
  30131. for (var i = 0; i < edges.length; i++) {
  30132. if (edges[i].toId == parentId) {childNode = edges[i].from;}
  30133. else {childNode = edges[i].to;}
  30134. if (childNode.edges.length > 1 && childNode.hierarchyEnumerated === false) {
  30135. this._setLevelDirected(childNode.level, childNode.edges, childNode.id);
  30136. }
  30137. }
  30138. };
  30139. /**
  30140. * Unfix nodes
  30141. *
  30142. * @private
  30143. */
  30144. exports._restoreNodes = function() {
  30145. for (var nodeId in this.nodes) {
  30146. if (this.nodes.hasOwnProperty(nodeId)) {
  30147. this.nodes[nodeId].xFixed = false;
  30148. this.nodes[nodeId].yFixed = false;
  30149. }
  30150. }
  30151. };
  30152. /***/ },
  30153. /* 70 */
  30154. /***/ function(module, exports, __webpack_require__) {
  30155. // English
  30156. exports['en'] = {
  30157. edit: 'Edit',
  30158. del: 'Delete selected',
  30159. back: 'Back',
  30160. addNode: 'Add Node',
  30161. addEdge: 'Add Edge',
  30162. editNode: 'Edit Node',
  30163. editEdge: 'Edit Edge',
  30164. addDescription: 'Click in an empty space to place a new node.',
  30165. edgeDescription: 'Click on a node and drag the edge to another node to connect them.',
  30166. editEdgeDescription: 'Click on the control points and drag them to a node to connect to it.',
  30167. createEdgeError: 'Cannot link edges to a cluster.',
  30168. deleteClusterError: 'Clusters cannot be deleted.'
  30169. };
  30170. exports['en_EN'] = exports['en'];
  30171. exports['en_US'] = exports['en'];
  30172. // Dutch
  30173. exports['nl'] = {
  30174. edit: 'Wijzigen',
  30175. del: 'Selectie verwijderen',
  30176. back: 'Terug',
  30177. addNode: 'Node toevoegen',
  30178. addEdge: 'Link toevoegen',
  30179. editNode: 'Node wijzigen',
  30180. editEdge: 'Link wijzigen',
  30181. addDescription: 'Klik op een leeg gebied om een nieuwe node te maken.',
  30182. edgeDescription: 'Klik op een node en sleep de link naar een andere node om ze te verbinden.',
  30183. editEdgeDescription: 'Klik op de verbindingspunten en sleep ze naar een node om daarmee te verbinden.',
  30184. createEdgeError: 'Kan geen link maken naar een cluster.',
  30185. deleteClusterError: 'Clusters kunnen niet worden verwijderd.'
  30186. };
  30187. exports['nl_NL'] = exports['nl'];
  30188. exports['nl_BE'] = exports['nl'];
  30189. /***/ },
  30190. /* 71 */
  30191. /***/ function(module, exports, __webpack_require__) {
  30192. /**
  30193. * Canvas shapes used by Network
  30194. */
  30195. if (typeof CanvasRenderingContext2D !== 'undefined') {
  30196. /**
  30197. * Draw a circle shape
  30198. */
  30199. CanvasRenderingContext2D.prototype.circle = function(x, y, r) {
  30200. this.beginPath();
  30201. this.arc(x, y, r, 0, 2*Math.PI, false);
  30202. };
  30203. /**
  30204. * Draw a square shape
  30205. * @param {Number} x horizontal center
  30206. * @param {Number} y vertical center
  30207. * @param {Number} r size, width and height of the square
  30208. */
  30209. CanvasRenderingContext2D.prototype.square = function(x, y, r) {
  30210. this.beginPath();
  30211. this.rect(x - r, y - r, r * 2, r * 2);
  30212. };
  30213. /**
  30214. * Draw a triangle shape
  30215. * @param {Number} x horizontal center
  30216. * @param {Number} y vertical center
  30217. * @param {Number} r radius, half the length of the sides of the triangle
  30218. */
  30219. CanvasRenderingContext2D.prototype.triangle = function(x, y, r) {
  30220. // http://en.wikipedia.org/wiki/Equilateral_triangle
  30221. this.beginPath();
  30222. var s = r * 2;
  30223. var s2 = s / 2;
  30224. var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
  30225. var h = Math.sqrt(s * s - s2 * s2); // height
  30226. this.moveTo(x, y - (h - ir));
  30227. this.lineTo(x + s2, y + ir);
  30228. this.lineTo(x - s2, y + ir);
  30229. this.lineTo(x, y - (h - ir));
  30230. this.closePath();
  30231. };
  30232. /**
  30233. * Draw a triangle shape in downward orientation
  30234. * @param {Number} x horizontal center
  30235. * @param {Number} y vertical center
  30236. * @param {Number} r radius
  30237. */
  30238. CanvasRenderingContext2D.prototype.triangleDown = function(x, y, r) {
  30239. // http://en.wikipedia.org/wiki/Equilateral_triangle
  30240. this.beginPath();
  30241. var s = r * 2;
  30242. var s2 = s / 2;
  30243. var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
  30244. var h = Math.sqrt(s * s - s2 * s2); // height
  30245. this.moveTo(x, y + (h - ir));
  30246. this.lineTo(x + s2, y - ir);
  30247. this.lineTo(x - s2, y - ir);
  30248. this.lineTo(x, y + (h - ir));
  30249. this.closePath();
  30250. };
  30251. /**
  30252. * Draw a star shape, a star with 5 points
  30253. * @param {Number} x horizontal center
  30254. * @param {Number} y vertical center
  30255. * @param {Number} r radius, half the length of the sides of the triangle
  30256. */
  30257. CanvasRenderingContext2D.prototype.star = function(x, y, r) {
  30258. // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/
  30259. this.beginPath();
  30260. for (var n = 0; n < 10; n++) {
  30261. var radius = (n % 2 === 0) ? r * 1.3 : r * 0.5;
  30262. this.lineTo(
  30263. x + radius * Math.sin(n * 2 * Math.PI / 10),
  30264. y - radius * Math.cos(n * 2 * Math.PI / 10)
  30265. );
  30266. }
  30267. this.closePath();
  30268. };
  30269. /**
  30270. * http://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas
  30271. */
  30272. CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
  30273. var r2d = Math.PI/180;
  30274. if( w - ( 2 * r ) < 0 ) { r = ( w / 2 ); } //ensure that the radius isn't too large for x
  30275. if( h - ( 2 * r ) < 0 ) { r = ( h / 2 ); } //ensure that the radius isn't too large for y
  30276. this.beginPath();
  30277. this.moveTo(x+r,y);
  30278. this.lineTo(x+w-r,y);
  30279. this.arc(x+w-r,y+r,r,r2d*270,r2d*360,false);
  30280. this.lineTo(x+w,y+h-r);
  30281. this.arc(x+w-r,y+h-r,r,0,r2d*90,false);
  30282. this.lineTo(x+r,y+h);
  30283. this.arc(x+r,y+h-r,r,r2d*90,r2d*180,false);
  30284. this.lineTo(x,y+r);
  30285. this.arc(x+r,y+r,r,r2d*180,r2d*270,false);
  30286. };
  30287. /**
  30288. * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  30289. */
  30290. CanvasRenderingContext2D.prototype.ellipse = function(x, y, w, h) {
  30291. var kappa = .5522848,
  30292. ox = (w / 2) * kappa, // control point offset horizontal
  30293. oy = (h / 2) * kappa, // control point offset vertical
  30294. xe = x + w, // x-end
  30295. ye = y + h, // y-end
  30296. xm = x + w / 2, // x-middle
  30297. ym = y + h / 2; // y-middle
  30298. this.beginPath();
  30299. this.moveTo(x, ym);
  30300. this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  30301. this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  30302. this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  30303. this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  30304. };
  30305. /**
  30306. * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  30307. */
  30308. CanvasRenderingContext2D.prototype.database = function(x, y, w, h) {
  30309. var f = 1/3;
  30310. var wEllipse = w;
  30311. var hEllipse = h * f;
  30312. var kappa = .5522848,
  30313. ox = (wEllipse / 2) * kappa, // control point offset horizontal
  30314. oy = (hEllipse / 2) * kappa, // control point offset vertical
  30315. xe = x + wEllipse, // x-end
  30316. ye = y + hEllipse, // y-end
  30317. xm = x + wEllipse / 2, // x-middle
  30318. ym = y + hEllipse / 2, // y-middle
  30319. ymb = y + (h - hEllipse/2), // y-midlle, bottom ellipse
  30320. yeb = y + h; // y-end, bottom ellipse
  30321. this.beginPath();
  30322. this.moveTo(xe, ym);
  30323. this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  30324. this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  30325. this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  30326. this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  30327. this.lineTo(xe, ymb);
  30328. this.bezierCurveTo(xe, ymb + oy, xm + ox, yeb, xm, yeb);
  30329. this.bezierCurveTo(xm - ox, yeb, x, ymb + oy, x, ymb);
  30330. this.lineTo(x, ym);
  30331. };
  30332. /**
  30333. * Draw an arrow point (no line)
  30334. */
  30335. CanvasRenderingContext2D.prototype.arrow = function(x, y, angle, length) {
  30336. // tail
  30337. var xt = x - length * Math.cos(angle);
  30338. var yt = y - length * Math.sin(angle);
  30339. // inner tail
  30340. // TODO: allow to customize different shapes
  30341. var xi = x - length * 0.9 * Math.cos(angle);
  30342. var yi = y - length * 0.9 * Math.sin(angle);
  30343. // left
  30344. var xl = xt + length / 3 * Math.cos(angle + 0.5 * Math.PI);
  30345. var yl = yt + length / 3 * Math.sin(angle + 0.5 * Math.PI);
  30346. // right
  30347. var xr = xt + length / 3 * Math.cos(angle - 0.5 * Math.PI);
  30348. var yr = yt + length / 3 * Math.sin(angle - 0.5 * Math.PI);
  30349. this.beginPath();
  30350. this.moveTo(x, y);
  30351. this.lineTo(xl, yl);
  30352. this.lineTo(xi, yi);
  30353. this.lineTo(xr, yr);
  30354. this.closePath();
  30355. };
  30356. /**
  30357. * Sets up the dashedLine functionality for drawing
  30358. * Original code came from http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
  30359. * @author David Jordan
  30360. * @date 2012-08-08
  30361. */
  30362. CanvasRenderingContext2D.prototype.dashedLine = function(x,y,x2,y2,dashArray){
  30363. if (!dashArray) dashArray=[10,5];
  30364. if (dashLength==0) dashLength = 0.001; // Hack for Safari
  30365. var dashCount = dashArray.length;
  30366. this.moveTo(x, y);
  30367. var dx = (x2-x), dy = (y2-y);
  30368. var slope = dy/dx;
  30369. var distRemaining = Math.sqrt( dx*dx + dy*dy );
  30370. var dashIndex=0, draw=true;
  30371. while (distRemaining>=0.1){
  30372. var dashLength = dashArray[dashIndex++%dashCount];
  30373. if (dashLength > distRemaining) dashLength = distRemaining;
  30374. var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) );
  30375. if (dx<0) xStep = -xStep;
  30376. x += xStep;
  30377. y += slope*xStep;
  30378. this[draw ? 'lineTo' : 'moveTo'](x,y);
  30379. distRemaining -= dashLength;
  30380. draw = !draw;
  30381. }
  30382. };
  30383. // TODO: add diamond shape
  30384. }
  30385. /***/ }
  30386. /******/ ])
  30387. });