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.

32338 lines
1010 KiB

  1. /**
  2. * vis.js
  3. * https://github.com/almende/vis
  4. *
  5. * A dynamic, browser-based visualization library.
  6. *
  7. * @version 3.4.0
  8. * @date 2014-09-10
  9. *
  10. * @license
  11. * Copyright (C) 2011-2014 Almende B.V, http://almende.com
  12. *
  13. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  14. * use this file except in compliance with the License. You may obtain a copy
  15. * of the License at
  16. *
  17. * http://www.apache.org/licenses/LICENSE-2.0
  18. *
  19. * Unless required by applicable law or agreed to in writing, software
  20. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  21. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  22. * License for the specific language governing permissions and limitations under
  23. * the License.
  24. */
  25. (function webpackUniversalModuleDefinition(root, factory) {
  26. if(typeof exports === 'object' && typeof module === 'object')
  27. module.exports = factory();
  28. else if(typeof define === 'function' && define.amd)
  29. define(factory);
  30. else if(typeof exports === 'object')
  31. exports["vis"] = factory();
  32. else
  33. root["vis"] = factory();
  34. })(this, function() {
  35. return /******/ (function(modules) { // webpackBootstrap
  36. /******/ // The module cache
  37. /******/ var installedModules = {};
  38. /******/
  39. /******/ // The require function
  40. /******/ function __webpack_require__(moduleId) {
  41. /******/
  42. /******/ // Check if module is in cache
  43. /******/ if(installedModules[moduleId])
  44. /******/ return installedModules[moduleId].exports;
  45. /******/
  46. /******/ // Create a new module (and put it into the cache)
  47. /******/ var module = installedModules[moduleId] = {
  48. /******/ exports: {},
  49. /******/ id: moduleId,
  50. /******/ loaded: false
  51. /******/ };
  52. /******/
  53. /******/ // Execute the module function
  54. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  55. /******/
  56. /******/ // Flag the module as loaded
  57. /******/ module.loaded = true;
  58. /******/
  59. /******/ // Return the exports of the module
  60. /******/ return module.exports;
  61. /******/ }
  62. /******/
  63. /******/
  64. /******/ // expose the modules object (__webpack_modules__)
  65. /******/ __webpack_require__.m = modules;
  66. /******/
  67. /******/ // expose the module cache
  68. /******/ __webpack_require__.c = installedModules;
  69. /******/
  70. /******/ // __webpack_public_path__
  71. /******/ __webpack_require__.p = "";
  72. /******/
  73. /******/ // Load entry module and return exports
  74. /******/ return __webpack_require__(0);
  75. /******/ })
  76. /************************************************************************/
  77. /******/ ([
  78. /* 0 */
  79. /***/ function(module, exports, __webpack_require__) {
  80. // utils
  81. exports.util = __webpack_require__(1);
  82. exports.DOMutil = __webpack_require__(2);
  83. // data
  84. exports.DataSet = __webpack_require__(3);
  85. exports.DataView = __webpack_require__(4);
  86. // Graph3d
  87. exports.Graph3d = __webpack_require__(5);
  88. exports.graph3d = {
  89. Camera: __webpack_require__(6),
  90. Filter: __webpack_require__(7),
  91. Point2d: __webpack_require__(8),
  92. Point3d: __webpack_require__(9),
  93. Slider: __webpack_require__(10),
  94. StepNumber: __webpack_require__(11)
  95. };
  96. // Timeline
  97. exports.Timeline = __webpack_require__(12);
  98. exports.Graph2d = __webpack_require__(13);
  99. exports.timeline = {
  100. DataStep: __webpack_require__(14),
  101. Range: __webpack_require__(15),
  102. stack: __webpack_require__(16),
  103. TimeStep: __webpack_require__(17),
  104. components: {
  105. items: {
  106. Item: __webpack_require__(28),
  107. BackgroundItem: __webpack_require__(29),
  108. BoxItem: __webpack_require__(30),
  109. PointItem: __webpack_require__(31),
  110. RangeItem: __webpack_require__(32)
  111. },
  112. Component: __webpack_require__(18),
  113. CurrentTime: __webpack_require__(19),
  114. CustomTime: __webpack_require__(20),
  115. DataAxis: __webpack_require__(21),
  116. GraphGroup: __webpack_require__(22),
  117. Group: __webpack_require__(23),
  118. ItemSet: __webpack_require__(24),
  119. Legend: __webpack_require__(25),
  120. LineGraph: __webpack_require__(26),
  121. TimeAxis: __webpack_require__(27)
  122. }
  123. };
  124. // Network
  125. exports.Network = __webpack_require__(33);
  126. exports.network = {
  127. Edge: __webpack_require__(34),
  128. Groups: __webpack_require__(35),
  129. Images: __webpack_require__(36),
  130. Node: __webpack_require__(37),
  131. Popup: __webpack_require__(38),
  132. dotparser: __webpack_require__(39),
  133. gephiParser: __webpack_require__(40)
  134. };
  135. // Deprecated since v3.0.0
  136. exports.Graph = function () {
  137. throw new Error('Graph is renamed to Network. Please create a graph as new vis.Network(...)');
  138. };
  139. // bundled external libraries
  140. exports.moment = __webpack_require__(41);
  141. exports.hammer = __webpack_require__(42);
  142. /***/ },
  143. /* 1 */
  144. /***/ function(module, exports, __webpack_require__) {
  145. // utility functions
  146. // first check if moment.js is already loaded in the browser window, if so,
  147. // use this instance. Else, load via commonjs.
  148. var moment = __webpack_require__(41);
  149. /**
  150. * Test whether given object is a number
  151. * @param {*} object
  152. * @return {Boolean} isNumber
  153. */
  154. exports.isNumber = function(object) {
  155. return (object instanceof Number || typeof object == 'number');
  156. };
  157. /**
  158. * Test whether given object is a string
  159. * @param {*} object
  160. * @return {Boolean} isString
  161. */
  162. exports.isString = function(object) {
  163. return (object instanceof String || typeof object == 'string');
  164. };
  165. /**
  166. * Test whether given object is a Date, or a String containing a Date
  167. * @param {Date | String} object
  168. * @return {Boolean} isDate
  169. */
  170. exports.isDate = function(object) {
  171. if (object instanceof Date) {
  172. return true;
  173. }
  174. else if (exports.isString(object)) {
  175. // test whether this string contains a date
  176. var match = ASPDateRegex.exec(object);
  177. if (match) {
  178. return true;
  179. }
  180. else if (!isNaN(Date.parse(object))) {
  181. return true;
  182. }
  183. }
  184. return false;
  185. };
  186. /**
  187. * Test whether given object is an instance of google.visualization.DataTable
  188. * @param {*} object
  189. * @return {Boolean} isDataTable
  190. */
  191. exports.isDataTable = function(object) {
  192. return (typeof (google) !== 'undefined') &&
  193. (google.visualization) &&
  194. (google.visualization.DataTable) &&
  195. (object instanceof google.visualization.DataTable);
  196. };
  197. /**
  198. * Create a semi UUID
  199. * source: http://stackoverflow.com/a/105074/1262753
  200. * @return {String} uuid
  201. */
  202. exports.randomUUID = function() {
  203. var S4 = function () {
  204. return Math.floor(
  205. Math.random() * 0x10000 /* 65536 */
  206. ).toString(16);
  207. };
  208. return (
  209. S4() + S4() + '-' +
  210. S4() + '-' +
  211. S4() + '-' +
  212. S4() + '-' +
  213. S4() + S4() + S4()
  214. );
  215. };
  216. /**
  217. * Extend object a with the properties of object b or a series of objects
  218. * Only properties with defined values are copied
  219. * @param {Object} a
  220. * @param {... Object} b
  221. * @return {Object} a
  222. */
  223. exports.extend = function (a, b) {
  224. for (var i = 1, len = arguments.length; i < len; i++) {
  225. var other = arguments[i];
  226. for (var prop in other) {
  227. if (other.hasOwnProperty(prop)) {
  228. a[prop] = other[prop];
  229. }
  230. }
  231. }
  232. return a;
  233. };
  234. /**
  235. * Extend object a with selected properties of object b or a series of objects
  236. * Only properties with defined values are copied
  237. * @param {Array.<String>} props
  238. * @param {Object} a
  239. * @param {... Object} b
  240. * @return {Object} a
  241. */
  242. exports.selectiveExtend = function (props, a, b) {
  243. if (!Array.isArray(props)) {
  244. throw new Error('Array with property names expected as first argument');
  245. }
  246. for (var i = 2; i < arguments.length; i++) {
  247. var other = arguments[i];
  248. for (var p = 0; p < props.length; p++) {
  249. var prop = props[p];
  250. if (other.hasOwnProperty(prop)) {
  251. a[prop] = other[prop];
  252. }
  253. }
  254. }
  255. return a;
  256. };
  257. /**
  258. * Extend object a with selected properties of object b or a series of objects
  259. * Only properties with defined values are copied
  260. * @param {Array.<String>} props
  261. * @param {Object} a
  262. * @param {... Object} b
  263. * @return {Object} a
  264. */
  265. exports.selectiveDeepExtend = function (props, a, b) {
  266. // TODO: add support for Arrays to deepExtend
  267. if (Array.isArray(b)) {
  268. throw new TypeError('Arrays are not supported by deepExtend');
  269. }
  270. for (var i = 2; i < arguments.length; i++) {
  271. var other = arguments[i];
  272. for (var p = 0; p < props.length; p++) {
  273. var prop = props[p];
  274. if (other.hasOwnProperty(prop)) {
  275. if (b[prop] && b[prop].constructor === Object) {
  276. if (a[prop] === undefined) {
  277. a[prop] = {};
  278. }
  279. if (a[prop].constructor === Object) {
  280. exports.deepExtend(a[prop], b[prop]);
  281. }
  282. else {
  283. a[prop] = b[prop];
  284. }
  285. } else if (Array.isArray(b[prop])) {
  286. throw new TypeError('Arrays are not supported by deepExtend');
  287. } else {
  288. a[prop] = b[prop];
  289. }
  290. }
  291. }
  292. }
  293. return a;
  294. };
  295. /**
  296. * Extend object a with selected properties of object b or a series of objects
  297. * Only properties with defined values are copied
  298. * @param {Array.<String>} props
  299. * @param {Object} a
  300. * @param {... Object} b
  301. * @return {Object} a
  302. */
  303. exports.selectiveNotDeepExtend = function (props, a, b) {
  304. // TODO: add support for Arrays to deepExtend
  305. if (Array.isArray(b)) {
  306. throw new TypeError('Arrays are not supported by deepExtend');
  307. }
  308. for (var prop in b) {
  309. if (b.hasOwnProperty(prop)) {
  310. if (props.indexOf(prop) == -1) {
  311. if (b[prop] && b[prop].constructor === Object) {
  312. if (a[prop] === undefined) {
  313. a[prop] = {};
  314. }
  315. if (a[prop].constructor === Object) {
  316. exports.deepExtend(a[prop], b[prop]);
  317. }
  318. else {
  319. a[prop] = b[prop];
  320. }
  321. } else if (Array.isArray(b[prop])) {
  322. throw new TypeError('Arrays are not supported by deepExtend');
  323. } else {
  324. a[prop] = b[prop];
  325. }
  326. }
  327. }
  328. }
  329. return a;
  330. };
  331. /**
  332. * Deep extend an object a with the properties of object b
  333. * @param {Object} a
  334. * @param {Object} b
  335. * @returns {Object}
  336. */
  337. exports.deepExtend = function(a, b) {
  338. // TODO: add support for Arrays to deepExtend
  339. if (Array.isArray(b)) {
  340. throw new TypeError('Arrays are not supported by deepExtend');
  341. }
  342. for (var prop in b) {
  343. if (b.hasOwnProperty(prop)) {
  344. if (b[prop] && b[prop].constructor === Object) {
  345. if (a[prop] === undefined) {
  346. a[prop] = {};
  347. }
  348. if (a[prop].constructor === Object) {
  349. exports.deepExtend(a[prop], b[prop]);
  350. }
  351. else {
  352. a[prop] = b[prop];
  353. }
  354. } else if (Array.isArray(b[prop])) {
  355. throw new TypeError('Arrays are not supported by deepExtend');
  356. } else {
  357. a[prop] = b[prop];
  358. }
  359. }
  360. }
  361. return a;
  362. };
  363. /**
  364. * Test whether all elements in two arrays are equal.
  365. * @param {Array} a
  366. * @param {Array} b
  367. * @return {boolean} Returns true if both arrays have the same length and same
  368. * elements.
  369. */
  370. exports.equalArray = function (a, b) {
  371. if (a.length != b.length) return false;
  372. for (var i = 0, len = a.length; i < len; i++) {
  373. if (a[i] != b[i]) return false;
  374. }
  375. return true;
  376. };
  377. /**
  378. * Convert an object to another type
  379. * @param {Boolean | Number | String | Date | Moment | Null | undefined} object
  380. * @param {String | undefined} type Name of the type. Available types:
  381. * 'Boolean', 'Number', 'String',
  382. * 'Date', 'Moment', ISODate', 'ASPDate'.
  383. * @return {*} object
  384. * @throws Error
  385. */
  386. exports.convert = function(object, type) {
  387. var match;
  388. if (object === undefined) {
  389. return undefined;
  390. }
  391. if (object === null) {
  392. return null;
  393. }
  394. if (!type) {
  395. return object;
  396. }
  397. if (!(typeof type === 'string') && !(type instanceof String)) {
  398. throw new Error('Type must be a string');
  399. }
  400. //noinspection FallthroughInSwitchStatementJS
  401. switch (type) {
  402. case 'boolean':
  403. case 'Boolean':
  404. return Boolean(object);
  405. case 'number':
  406. case 'Number':
  407. return Number(object.valueOf());
  408. case 'string':
  409. case 'String':
  410. return String(object);
  411. case 'Date':
  412. if (exports.isNumber(object)) {
  413. return new Date(object);
  414. }
  415. if (object instanceof Date) {
  416. return new Date(object.valueOf());
  417. }
  418. else if (moment.isMoment(object)) {
  419. return new Date(object.valueOf());
  420. }
  421. if (exports.isString(object)) {
  422. match = ASPDateRegex.exec(object);
  423. if (match) {
  424. // object is an ASP date
  425. return new Date(Number(match[1])); // parse number
  426. }
  427. else {
  428. return moment(object).toDate(); // parse string
  429. }
  430. }
  431. else {
  432. throw new Error(
  433. 'Cannot convert object of type ' + exports.getType(object) +
  434. ' to type Date');
  435. }
  436. case 'Moment':
  437. if (exports.isNumber(object)) {
  438. return moment(object);
  439. }
  440. if (object instanceof Date) {
  441. return moment(object.valueOf());
  442. }
  443. else if (moment.isMoment(object)) {
  444. return moment(object);
  445. }
  446. if (exports.isString(object)) {
  447. match = ASPDateRegex.exec(object);
  448. if (match) {
  449. // object is an ASP date
  450. return moment(Number(match[1])); // parse number
  451. }
  452. else {
  453. return moment(object); // parse string
  454. }
  455. }
  456. else {
  457. throw new Error(
  458. 'Cannot convert object of type ' + exports.getType(object) +
  459. ' to type Date');
  460. }
  461. case 'ISODate':
  462. if (exports.isNumber(object)) {
  463. return new Date(object);
  464. }
  465. else if (object instanceof Date) {
  466. return object.toISOString();
  467. }
  468. else if (moment.isMoment(object)) {
  469. return object.toDate().toISOString();
  470. }
  471. else if (exports.isString(object)) {
  472. match = ASPDateRegex.exec(object);
  473. if (match) {
  474. // object is an ASP date
  475. return new Date(Number(match[1])).toISOString(); // parse number
  476. }
  477. else {
  478. return new Date(object).toISOString(); // parse string
  479. }
  480. }
  481. else {
  482. throw new Error(
  483. 'Cannot convert object of type ' + exports.getType(object) +
  484. ' to type ISODate');
  485. }
  486. case 'ASPDate':
  487. if (exports.isNumber(object)) {
  488. return '/Date(' + object + ')/';
  489. }
  490. else if (object instanceof Date) {
  491. return '/Date(' + object.valueOf() + ')/';
  492. }
  493. else if (exports.isString(object)) {
  494. match = ASPDateRegex.exec(object);
  495. var value;
  496. if (match) {
  497. // object is an ASP date
  498. value = new Date(Number(match[1])).valueOf(); // parse number
  499. }
  500. else {
  501. value = new Date(object).valueOf(); // parse string
  502. }
  503. return '/Date(' + value + ')/';
  504. }
  505. else {
  506. throw new Error(
  507. 'Cannot convert object of type ' + exports.getType(object) +
  508. ' to type ASPDate');
  509. }
  510. default:
  511. throw new Error('Unknown type "' + type + '"');
  512. }
  513. };
  514. // parse ASP.Net Date pattern,
  515. // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
  516. // code from http://momentjs.com/
  517. var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
  518. /**
  519. * Get the type of an object, for example exports.getType([]) returns 'Array'
  520. * @param {*} object
  521. * @return {String} type
  522. */
  523. exports.getType = function(object) {
  524. var type = typeof object;
  525. if (type == 'object') {
  526. if (object == null) {
  527. return 'null';
  528. }
  529. if (object instanceof Boolean) {
  530. return 'Boolean';
  531. }
  532. if (object instanceof Number) {
  533. return 'Number';
  534. }
  535. if (object instanceof String) {
  536. return 'String';
  537. }
  538. if (object instanceof Array) {
  539. return 'Array';
  540. }
  541. if (object instanceof Date) {
  542. return 'Date';
  543. }
  544. return 'Object';
  545. }
  546. else if (type == 'number') {
  547. return 'Number';
  548. }
  549. else if (type == 'boolean') {
  550. return 'Boolean';
  551. }
  552. else if (type == 'string') {
  553. return 'String';
  554. }
  555. return type;
  556. };
  557. /**
  558. * Retrieve the absolute left value of a DOM element
  559. * @param {Element} elem A dom element, for example a div
  560. * @return {number} left The absolute left position of this element
  561. * in the browser page.
  562. */
  563. exports.getAbsoluteLeft = function(elem) {
  564. return elem.getBoundingClientRect().left + window.pageXOffset;
  565. };
  566. /**
  567. * Retrieve the absolute top value of a DOM element
  568. * @param {Element} elem A dom element, for example a div
  569. * @return {number} top The absolute top position of this element
  570. * in the browser page.
  571. */
  572. exports.getAbsoluteTop = function(elem) {
  573. return elem.getBoundingClientRect().top + window.pageYOffset;
  574. };
  575. /**
  576. * add a className to the given elements style
  577. * @param {Element} elem
  578. * @param {String} className
  579. */
  580. exports.addClassName = function(elem, className) {
  581. var classes = elem.className.split(' ');
  582. if (classes.indexOf(className) == -1) {
  583. classes.push(className); // add the class to the array
  584. elem.className = classes.join(' ');
  585. }
  586. };
  587. /**
  588. * add a className to the given elements style
  589. * @param {Element} elem
  590. * @param {String} className
  591. */
  592. exports.removeClassName = function(elem, className) {
  593. var classes = elem.className.split(' ');
  594. var index = classes.indexOf(className);
  595. if (index != -1) {
  596. classes.splice(index, 1); // remove the class from the array
  597. elem.className = classes.join(' ');
  598. }
  599. };
  600. /**
  601. * For each method for both arrays and objects.
  602. * In case of an array, the built-in Array.forEach() is applied.
  603. * In case of an Object, the method loops over all properties of the object.
  604. * @param {Object | Array} object An Object or Array
  605. * @param {function} callback Callback method, called for each item in
  606. * the object or array with three parameters:
  607. * callback(value, index, object)
  608. */
  609. exports.forEach = function(object, callback) {
  610. var i,
  611. len;
  612. if (object instanceof Array) {
  613. // array
  614. for (i = 0, len = object.length; i < len; i++) {
  615. callback(object[i], i, object);
  616. }
  617. }
  618. else {
  619. // object
  620. for (i in object) {
  621. if (object.hasOwnProperty(i)) {
  622. callback(object[i], i, object);
  623. }
  624. }
  625. }
  626. };
  627. /**
  628. * Convert an object into an array: all objects properties are put into the
  629. * array. The resulting array is unordered.
  630. * @param {Object} object
  631. * @param {Array} array
  632. */
  633. exports.toArray = function(object) {
  634. var array = [];
  635. for (var prop in object) {
  636. if (object.hasOwnProperty(prop)) array.push(object[prop]);
  637. }
  638. return array;
  639. }
  640. /**
  641. * Update a property in an object
  642. * @param {Object} object
  643. * @param {String} key
  644. * @param {*} value
  645. * @return {Boolean} changed
  646. */
  647. exports.updateProperty = function(object, key, value) {
  648. if (object[key] !== value) {
  649. object[key] = value;
  650. return true;
  651. }
  652. else {
  653. return false;
  654. }
  655. };
  656. /**
  657. * Add and event listener. Works for all browsers
  658. * @param {Element} element An html element
  659. * @param {string} action The action, for example "click",
  660. * without the prefix "on"
  661. * @param {function} listener The callback function to be executed
  662. * @param {boolean} [useCapture]
  663. */
  664. exports.addEventListener = function(element, action, listener, useCapture) {
  665. if (element.addEventListener) {
  666. if (useCapture === undefined)
  667. useCapture = false;
  668. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  669. action = "DOMMouseScroll"; // For Firefox
  670. }
  671. element.addEventListener(action, listener, useCapture);
  672. } else {
  673. element.attachEvent("on" + action, listener); // IE browsers
  674. }
  675. };
  676. /**
  677. * Remove an event listener from an element
  678. * @param {Element} element An html dom element
  679. * @param {string} action The name of the event, for example "mousedown"
  680. * @param {function} listener The listener function
  681. * @param {boolean} [useCapture]
  682. */
  683. exports.removeEventListener = function(element, action, listener, useCapture) {
  684. if (element.removeEventListener) {
  685. // non-IE browsers
  686. if (useCapture === undefined)
  687. useCapture = false;
  688. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  689. action = "DOMMouseScroll"; // For Firefox
  690. }
  691. element.removeEventListener(action, listener, useCapture);
  692. } else {
  693. // IE browsers
  694. element.detachEvent("on" + action, listener);
  695. }
  696. };
  697. /**
  698. * Cancels the event if it is cancelable, without stopping further propagation of the event.
  699. */
  700. exports.preventDefault = function (event) {
  701. if (!event)
  702. event = window.event;
  703. if (event.preventDefault) {
  704. event.preventDefault(); // non-IE browsers
  705. }
  706. else {
  707. event.returnValue = false; // IE browsers
  708. }
  709. };
  710. /**
  711. * Get HTML element which is the target of the event
  712. * @param {Event} event
  713. * @return {Element} target element
  714. */
  715. exports.getTarget = function(event) {
  716. // code from http://www.quirksmode.org/js/events_properties.html
  717. if (!event) {
  718. event = window.event;
  719. }
  720. var target;
  721. if (event.target) {
  722. target = event.target;
  723. }
  724. else if (event.srcElement) {
  725. target = event.srcElement;
  726. }
  727. if (target.nodeType != undefined && target.nodeType == 3) {
  728. // defeat Safari bug
  729. target = target.parentNode;
  730. }
  731. return target;
  732. };
  733. exports.option = {};
  734. /**
  735. * Convert a value into a boolean
  736. * @param {Boolean | function | undefined} value
  737. * @param {Boolean} [defaultValue]
  738. * @returns {Boolean} bool
  739. */
  740. exports.option.asBoolean = function (value, defaultValue) {
  741. if (typeof value == 'function') {
  742. value = value();
  743. }
  744. if (value != null) {
  745. return (value != false);
  746. }
  747. return defaultValue || null;
  748. };
  749. /**
  750. * Convert a value into a number
  751. * @param {Boolean | function | undefined} value
  752. * @param {Number} [defaultValue]
  753. * @returns {Number} number
  754. */
  755. exports.option.asNumber = function (value, defaultValue) {
  756. if (typeof value == 'function') {
  757. value = value();
  758. }
  759. if (value != null) {
  760. return Number(value) || defaultValue || null;
  761. }
  762. return defaultValue || null;
  763. };
  764. /**
  765. * Convert a value into a string
  766. * @param {String | function | undefined} value
  767. * @param {String} [defaultValue]
  768. * @returns {String} str
  769. */
  770. exports.option.asString = function (value, defaultValue) {
  771. if (typeof value == 'function') {
  772. value = value();
  773. }
  774. if (value != null) {
  775. return String(value);
  776. }
  777. return defaultValue || null;
  778. };
  779. /**
  780. * Convert a size or location into a string with pixels or a percentage
  781. * @param {String | Number | function | undefined} value
  782. * @param {String} [defaultValue]
  783. * @returns {String} size
  784. */
  785. exports.option.asSize = function (value, defaultValue) {
  786. if (typeof value == 'function') {
  787. value = value();
  788. }
  789. if (exports.isString(value)) {
  790. return value;
  791. }
  792. else if (exports.isNumber(value)) {
  793. return value + 'px';
  794. }
  795. else {
  796. return defaultValue || null;
  797. }
  798. };
  799. /**
  800. * Convert a value into a DOM element
  801. * @param {HTMLElement | function | undefined} value
  802. * @param {HTMLElement} [defaultValue]
  803. * @returns {HTMLElement | null} dom
  804. */
  805. exports.option.asElement = function (value, defaultValue) {
  806. if (typeof value == 'function') {
  807. value = value();
  808. }
  809. return value || defaultValue || null;
  810. };
  811. exports.GiveDec = function(Hex) {
  812. var Value;
  813. if (Hex == "A")
  814. Value = 10;
  815. else if (Hex == "B")
  816. Value = 11;
  817. else if (Hex == "C")
  818. Value = 12;
  819. else if (Hex == "D")
  820. Value = 13;
  821. else if (Hex == "E")
  822. Value = 14;
  823. else if (Hex == "F")
  824. Value = 15;
  825. else
  826. Value = eval(Hex);
  827. return Value;
  828. };
  829. exports.GiveHex = function(Dec) {
  830. var Value;
  831. if(Dec == 10)
  832. Value = "A";
  833. else if (Dec == 11)
  834. Value = "B";
  835. else if (Dec == 12)
  836. Value = "C";
  837. else if (Dec == 13)
  838. Value = "D";
  839. else if (Dec == 14)
  840. Value = "E";
  841. else if (Dec == 15)
  842. Value = "F";
  843. else
  844. Value = "" + Dec;
  845. return Value;
  846. };
  847. /**
  848. * Parse a color property into an object with border, background, and
  849. * highlight colors
  850. * @param {Object | String} color
  851. * @return {Object} colorObject
  852. */
  853. exports.parseColor = function(color) {
  854. var c;
  855. if (exports.isString(color)) {
  856. if (exports.isValidRGB(color)) {
  857. var rgb = color.substr(4).substr(0,color.length-5).split(',');
  858. color = exports.RGBToHex(rgb[0],rgb[1],rgb[2]);
  859. }
  860. if (exports.isValidHex(color)) {
  861. var hsv = exports.hexToHSV(color);
  862. var lighterColorHSV = {h:hsv.h,s:hsv.s * 0.45,v:Math.min(1,hsv.v * 1.05)};
  863. var darkerColorHSV = {h:hsv.h,s:Math.min(1,hsv.v * 1.25),v:hsv.v*0.6};
  864. var darkerColorHex = exports.HSVToHex(darkerColorHSV.h ,darkerColorHSV.h ,darkerColorHSV.v);
  865. var lighterColorHex = exports.HSVToHex(lighterColorHSV.h,lighterColorHSV.s,lighterColorHSV.v);
  866. c = {
  867. background: color,
  868. border:darkerColorHex,
  869. highlight: {
  870. background:lighterColorHex,
  871. border:darkerColorHex
  872. },
  873. hover: {
  874. background:lighterColorHex,
  875. border:darkerColorHex
  876. }
  877. };
  878. }
  879. else {
  880. c = {
  881. background:color,
  882. border:color,
  883. highlight: {
  884. background:color,
  885. border:color
  886. },
  887. hover: {
  888. background:color,
  889. border:color
  890. }
  891. };
  892. }
  893. }
  894. else {
  895. c = {};
  896. c.background = color.background || 'white';
  897. c.border = color.border || c.background;
  898. if (exports.isString(color.highlight)) {
  899. c.highlight = {
  900. border: color.highlight,
  901. background: color.highlight
  902. }
  903. }
  904. else {
  905. c.highlight = {};
  906. c.highlight.background = color.highlight && color.highlight.background || c.background;
  907. c.highlight.border = color.highlight && color.highlight.border || c.border;
  908. }
  909. if (exports.isString(color.hover)) {
  910. c.hover = {
  911. border: color.hover,
  912. background: color.hover
  913. }
  914. }
  915. else {
  916. c.hover = {};
  917. c.hover.background = color.hover && color.hover.background || c.background;
  918. c.hover.border = color.hover && color.hover.border || c.border;
  919. }
  920. }
  921. return c;
  922. };
  923. /**
  924. * http://www.yellowpipe.com/yis/tools/hex-to-rgb/color-converter.php
  925. *
  926. * @param {String} hex
  927. * @returns {{r: *, g: *, b: *}}
  928. */
  929. exports.hexToRGB = function(hex) {
  930. hex = hex.replace("#","").toUpperCase();
  931. var a = exports.GiveDec(hex.substring(0, 1));
  932. var b = exports.GiveDec(hex.substring(1, 2));
  933. var c = exports.GiveDec(hex.substring(2, 3));
  934. var d = exports.GiveDec(hex.substring(3, 4));
  935. var e = exports.GiveDec(hex.substring(4, 5));
  936. var f = exports.GiveDec(hex.substring(5, 6));
  937. var r = (a * 16) + b;
  938. var g = (c * 16) + d;
  939. var b = (e * 16) + f;
  940. return {r:r,g:g,b:b};
  941. };
  942. exports.RGBToHex = function(red,green,blue) {
  943. var a = exports.GiveHex(Math.floor(red / 16));
  944. var b = exports.GiveHex(red % 16);
  945. var c = exports.GiveHex(Math.floor(green / 16));
  946. var d = exports.GiveHex(green % 16);
  947. var e = exports.GiveHex(Math.floor(blue / 16));
  948. var f = exports.GiveHex(blue % 16);
  949. var hex = a + b + c + d + e + f;
  950. return "#" + hex;
  951. };
  952. /**
  953. * http://www.javascripter.net/faq/rgb2hsv.htm
  954. *
  955. * @param red
  956. * @param green
  957. * @param blue
  958. * @returns {*}
  959. * @constructor
  960. */
  961. exports.RGBToHSV = function(red,green,blue) {
  962. red=red/255; green=green/255; blue=blue/255;
  963. var minRGB = Math.min(red,Math.min(green,blue));
  964. var maxRGB = Math.max(red,Math.max(green,blue));
  965. // Black-gray-white
  966. if (minRGB == maxRGB) {
  967. return {h:0,s:0,v:minRGB};
  968. }
  969. // Colors other than black-gray-white:
  970. var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
  971. var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
  972. var hue = 60*(h - d/(maxRGB - minRGB))/360;
  973. var saturation = (maxRGB - minRGB)/maxRGB;
  974. var value = maxRGB;
  975. return {h:hue,s:saturation,v:value};
  976. };
  977. /**
  978. * https://gist.github.com/mjijackson/5311256
  979. * @param h
  980. * @param s
  981. * @param v
  982. * @returns {{r: number, g: number, b: number}}
  983. * @constructor
  984. */
  985. exports.HSVToRGB = function(h, s, v) {
  986. var r, g, b;
  987. var i = Math.floor(h * 6);
  988. var f = h * 6 - i;
  989. var p = v * (1 - s);
  990. var q = v * (1 - f * s);
  991. var t = v * (1 - (1 - f) * s);
  992. switch (i % 6) {
  993. case 0: r = v, g = t, b = p; break;
  994. case 1: r = q, g = v, b = p; break;
  995. case 2: r = p, g = v, b = t; break;
  996. case 3: r = p, g = q, b = v; break;
  997. case 4: r = t, g = p, b = v; break;
  998. case 5: r = v, g = p, b = q; break;
  999. }
  1000. return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
  1001. };
  1002. exports.HSVToHex = function(h, s, v) {
  1003. var rgb = exports.HSVToRGB(h, s, v);
  1004. return exports.RGBToHex(rgb.r, rgb.g, rgb.b);
  1005. };
  1006. exports.hexToHSV = function(hex) {
  1007. var rgb = exports.hexToRGB(hex);
  1008. return exports.RGBToHSV(rgb.r, rgb.g, rgb.b);
  1009. };
  1010. exports.isValidHex = function(hex) {
  1011. var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
  1012. return isOk;
  1013. };
  1014. exports.isValidRGB = function(rgb) {
  1015. rgb = rgb.replace(" ","");
  1016. var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb);
  1017. return isOk;
  1018. }
  1019. /**
  1020. * This recursively redirects the prototype of JSON objects to the referenceObject
  1021. * This is used for default options.
  1022. *
  1023. * @param referenceObject
  1024. * @returns {*}
  1025. */
  1026. exports.selectiveBridgeObject = function(fields, referenceObject) {
  1027. if (typeof referenceObject == "object") {
  1028. var objectTo = Object.create(referenceObject);
  1029. for (var i = 0; i < fields.length; i++) {
  1030. if (referenceObject.hasOwnProperty(fields[i])) {
  1031. if (typeof referenceObject[fields[i]] == "object") {
  1032. objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]);
  1033. }
  1034. }
  1035. }
  1036. return objectTo;
  1037. }
  1038. else {
  1039. return null;
  1040. }
  1041. };
  1042. /**
  1043. * This recursively redirects the prototype of JSON objects to the referenceObject
  1044. * This is used for default options.
  1045. *
  1046. * @param referenceObject
  1047. * @returns {*}
  1048. */
  1049. exports.bridgeObject = function(referenceObject) {
  1050. if (typeof referenceObject == "object") {
  1051. var objectTo = Object.create(referenceObject);
  1052. for (var i in referenceObject) {
  1053. if (referenceObject.hasOwnProperty(i)) {
  1054. if (typeof referenceObject[i] == "object") {
  1055. objectTo[i] = exports.bridgeObject(referenceObject[i]);
  1056. }
  1057. }
  1058. }
  1059. return objectTo;
  1060. }
  1061. else {
  1062. return null;
  1063. }
  1064. };
  1065. /**
  1066. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  1067. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  1068. *
  1069. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  1070. * @param [object] options | options
  1071. * @param [String] option | this is the option key in the options argument
  1072. * @private
  1073. */
  1074. exports.mergeOptions = function (mergeTarget, options, option) {
  1075. if (options[option] !== undefined) {
  1076. if (typeof options[option] == 'boolean') {
  1077. mergeTarget[option].enabled = options[option];
  1078. }
  1079. else {
  1080. mergeTarget[option].enabled = true;
  1081. for (prop in options[option]) {
  1082. if (options[option].hasOwnProperty(prop)) {
  1083. mergeTarget[option][prop] = options[option][prop];
  1084. }
  1085. }
  1086. }
  1087. }
  1088. }
  1089. /**
  1090. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  1091. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  1092. *
  1093. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  1094. * @param [object] options | options
  1095. * @param [String] option | this is the option key in the options argument
  1096. * @private
  1097. */
  1098. exports.mergeOptions = function (mergeTarget, options, option) {
  1099. if (options[option] !== undefined) {
  1100. if (typeof options[option] == 'boolean') {
  1101. mergeTarget[option].enabled = options[option];
  1102. }
  1103. else {
  1104. mergeTarget[option].enabled = true;
  1105. for (prop in options[option]) {
  1106. if (options[option].hasOwnProperty(prop)) {
  1107. mergeTarget[option][prop] = options[option][prop];
  1108. }
  1109. }
  1110. }
  1111. }
  1112. }
  1113. /**
  1114. * This function does a binary search for a visible item. The user can select either the this.orderedItems.byStart or .byEnd
  1115. * arrays. This is done by giving a boolean value true if you want to use the byEnd.
  1116. * This is done to be able to select the correct if statement (we do not want to check if an item is visible, we want to check
  1117. * if the time we selected (start or end) is within the current range).
  1118. *
  1119. * The trick is that every interval has to either enter the screen at the initial load or by dragging. The case of the RangeItem that is
  1120. * before and after the current range is handled by simply checking if it was in view before and if it is again. For all the rest,
  1121. * either the start OR end time has to be in the range.
  1122. *
  1123. * @param {Item[]} orderedItems Items ordered by start
  1124. * @param {{start: number, end: number}} range
  1125. * @param {String} field
  1126. * @param {String} field2
  1127. * @returns {number}
  1128. * @private
  1129. */
  1130. exports.binarySearch = function(orderedItems, range, field, field2) {
  1131. var array = orderedItems;
  1132. var maxIterations = 10000;
  1133. var iteration = 0;
  1134. var found = false;
  1135. var low = 0;
  1136. var high = array.length;
  1137. var newLow = low;
  1138. var newHigh = high;
  1139. var guess = Math.floor(0.5*(high+low));
  1140. var value;
  1141. if (high == 0) {
  1142. guess = -1;
  1143. }
  1144. else if (high == 1) {
  1145. if (array[guess].isVisible(range)) {
  1146. guess = 0;
  1147. }
  1148. else {
  1149. guess = -1;
  1150. }
  1151. }
  1152. else {
  1153. high -= 1;
  1154. while (found == false && iteration < maxIterations) {
  1155. value = field2 === undefined ? array[guess][field] : array[guess][field][field2];
  1156. if (array[guess].isVisible(range)) {
  1157. found = true;
  1158. }
  1159. else {
  1160. if (value < range.start) { // it is too small --> increase low
  1161. newLow = Math.floor(0.5*(high+low));
  1162. }
  1163. else { // it is too big --> decrease high
  1164. newHigh = Math.floor(0.5*(high+low));
  1165. }
  1166. // not in list;
  1167. if (low == newLow && high == newHigh) {
  1168. guess = -1;
  1169. found = true;
  1170. }
  1171. else {
  1172. high = newHigh; low = newLow;
  1173. guess = Math.floor(0.5*(high+low));
  1174. }
  1175. }
  1176. iteration++;
  1177. }
  1178. if (iteration >= maxIterations) {
  1179. console.log("BinarySearch too many iterations. Aborting.");
  1180. }
  1181. }
  1182. return guess;
  1183. };
  1184. /**
  1185. * This function does a binary search for a visible item. The user can select either the this.orderedItems.byStart or .byEnd
  1186. * arrays. This is done by giving a boolean value true if you want to use the byEnd.
  1187. * This is done to be able to select the correct if statement (we do not want to check if an item is visible, we want to check
  1188. * if the time we selected (start or end) is within the current range).
  1189. *
  1190. * The trick is that every interval has to either enter the screen at the initial load or by dragging. The case of the RangeItem that is
  1191. * before and after the current range is handled by simply checking if it was in view before and if it is again. For all the rest,
  1192. * either the start OR end time has to be in the range.
  1193. *
  1194. * @param {Array} orderedItems
  1195. * @param {{start: number, end: number}} target
  1196. * @param {String} field
  1197. * @param {String} sidePreference 'before' or 'after'
  1198. * @returns {number}
  1199. * @private
  1200. */
  1201. exports.binarySearchGeneric = function(orderedItems, target, field, sidePreference) {
  1202. var maxIterations = 10000;
  1203. var iteration = 0;
  1204. var array = orderedItems;
  1205. var found = false;
  1206. var low = 0;
  1207. var high = array.length;
  1208. var newLow = low;
  1209. var newHigh = high;
  1210. var guess = Math.floor(0.5*(high+low));
  1211. var newGuess;
  1212. var prevValue, value, nextValue;
  1213. if (high == 0) {guess = -1;}
  1214. else if (high == 1) {
  1215. value = array[guess][field];
  1216. if (value == target) {
  1217. guess = 0;
  1218. }
  1219. else {
  1220. guess = -1;
  1221. }
  1222. }
  1223. else {
  1224. high -= 1;
  1225. while (found == false && iteration < maxIterations) {
  1226. prevValue = array[Math.max(0,guess - 1)][field];
  1227. value = array[guess][field];
  1228. nextValue = array[Math.min(array.length-1,guess + 1)][field];
  1229. if (value == target || prevValue < target && value > target || value < target && nextValue > target) {
  1230. found = true;
  1231. if (value != target) {
  1232. if (sidePreference == 'before') {
  1233. if (prevValue < target && value > target) {
  1234. guess = Math.max(0,guess - 1);
  1235. }
  1236. }
  1237. else {
  1238. if (value < target && nextValue > target) {
  1239. guess = Math.min(array.length-1,guess + 1);
  1240. }
  1241. }
  1242. }
  1243. }
  1244. else {
  1245. if (value < target) { // it is too small --> increase low
  1246. newLow = Math.floor(0.5*(high+low));
  1247. }
  1248. else { // it is too big --> decrease high
  1249. newHigh = Math.floor(0.5*(high+low));
  1250. }
  1251. newGuess = Math.floor(0.5*(high+low));
  1252. // not in list;
  1253. if (low == newLow && high == newHigh) {
  1254. guess = -1;
  1255. found = true;
  1256. }
  1257. else {
  1258. high = newHigh; low = newLow;
  1259. guess = Math.floor(0.5*(high+low));
  1260. }
  1261. }
  1262. iteration++;
  1263. }
  1264. if (iteration >= maxIterations) {
  1265. console.log("BinarySearch too many iterations. Aborting.");
  1266. }
  1267. }
  1268. return guess;
  1269. };
  1270. /**
  1271. * Quadratic ease-in-out
  1272. * http://gizma.com/easing/
  1273. * @param {number} t Current time
  1274. * @param {number} start Start value
  1275. * @param {number} end End value
  1276. * @param {number} duration Duration
  1277. * @returns {number} Value corresponding with current time
  1278. */
  1279. exports.easeInOutQuad = function (t, start, end, duration) {
  1280. var change = end - start;
  1281. t /= duration/2;
  1282. if (t < 1) return change/2*t*t + start;
  1283. t--;
  1284. return -change/2 * (t*(t-2) - 1) + start;
  1285. };
  1286. /***/ },
  1287. /* 2 */
  1288. /***/ function(module, exports, __webpack_require__) {
  1289. // DOM utility methods
  1290. /**
  1291. * this prepares the JSON container for allocating SVG elements
  1292. * @param JSONcontainer
  1293. * @private
  1294. */
  1295. exports.prepareElements = function(JSONcontainer) {
  1296. // cleanup the redundant svgElements;
  1297. for (var elementType in JSONcontainer) {
  1298. if (JSONcontainer.hasOwnProperty(elementType)) {
  1299. JSONcontainer[elementType].redundant = JSONcontainer[elementType].used;
  1300. JSONcontainer[elementType].used = [];
  1301. }
  1302. }
  1303. };
  1304. /**
  1305. * this cleans up all the unused SVG elements. By asking for the parentNode, we only need to supply the JSON container from
  1306. * which to remove the redundant elements.
  1307. *
  1308. * @param JSONcontainer
  1309. * @private
  1310. */
  1311. exports.cleanupElements = function(JSONcontainer) {
  1312. // cleanup the redundant svgElements;
  1313. for (var elementType in JSONcontainer) {
  1314. if (JSONcontainer.hasOwnProperty(elementType)) {
  1315. if (JSONcontainer[elementType].redundant) {
  1316. for (var i = 0; i < JSONcontainer[elementType].redundant.length; i++) {
  1317. JSONcontainer[elementType].redundant[i].parentNode.removeChild(JSONcontainer[elementType].redundant[i]);
  1318. }
  1319. JSONcontainer[elementType].redundant = [];
  1320. }
  1321. }
  1322. }
  1323. };
  1324. /**
  1325. * Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer
  1326. * the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this.
  1327. *
  1328. * @param elementType
  1329. * @param JSONcontainer
  1330. * @param svgContainer
  1331. * @returns {*}
  1332. * @private
  1333. */
  1334. exports.getSVGElement = function (elementType, JSONcontainer, svgContainer) {
  1335. var element;
  1336. // allocate SVG element, if it doesnt yet exist, create one.
  1337. if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
  1338. // check if there is an redundant element
  1339. if (JSONcontainer[elementType].redundant.length > 0) {
  1340. element = JSONcontainer[elementType].redundant[0];
  1341. JSONcontainer[elementType].redundant.shift();
  1342. }
  1343. else {
  1344. // create a new element and add it to the SVG
  1345. element = document.createElementNS('http://www.w3.org/2000/svg', elementType);
  1346. svgContainer.appendChild(element);
  1347. }
  1348. }
  1349. else {
  1350. // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it.
  1351. element = document.createElementNS('http://www.w3.org/2000/svg', elementType);
  1352. JSONcontainer[elementType] = {used: [], redundant: []};
  1353. svgContainer.appendChild(element);
  1354. }
  1355. JSONcontainer[elementType].used.push(element);
  1356. return element;
  1357. };
  1358. /**
  1359. * Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer
  1360. * the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this.
  1361. *
  1362. * @param elementType
  1363. * @param JSONcontainer
  1364. * @param DOMContainer
  1365. * @returns {*}
  1366. * @private
  1367. */
  1368. exports.getDOMElement = function (elementType, JSONcontainer, DOMContainer, insertBefore) {
  1369. var element;
  1370. // allocate DOM element, if it doesnt yet exist, create one.
  1371. if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
  1372. // check if there is an redundant element
  1373. if (JSONcontainer[elementType].redundant.length > 0) {
  1374. element = JSONcontainer[elementType].redundant[0];
  1375. JSONcontainer[elementType].redundant.shift();
  1376. }
  1377. else {
  1378. // create a new element and add it to the SVG
  1379. element = document.createElement(elementType);
  1380. if (insertBefore !== undefined) {
  1381. DOMContainer.insertBefore(element, insertBefore);
  1382. }
  1383. else {
  1384. DOMContainer.appendChild(element);
  1385. }
  1386. }
  1387. }
  1388. else {
  1389. // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it.
  1390. element = document.createElement(elementType);
  1391. JSONcontainer[elementType] = {used: [], redundant: []};
  1392. if (insertBefore !== undefined) {
  1393. DOMContainer.insertBefore(element, insertBefore);
  1394. }
  1395. else {
  1396. DOMContainer.appendChild(element);
  1397. }
  1398. }
  1399. JSONcontainer[elementType].used.push(element);
  1400. return element;
  1401. };
  1402. /**
  1403. * draw a point object. this is a seperate function because it can also be called by the legend.
  1404. * The reason the JSONcontainer and the target SVG svgContainer have to be supplied is so the legend can use these functions
  1405. * as well.
  1406. *
  1407. * @param x
  1408. * @param y
  1409. * @param group
  1410. * @param JSONcontainer
  1411. * @param svgContainer
  1412. * @returns {*}
  1413. */
  1414. exports.drawPoint = function(x, y, group, JSONcontainer, svgContainer) {
  1415. var point;
  1416. if (group.options.drawPoints.style == 'circle') {
  1417. point = exports.getSVGElement('circle',JSONcontainer,svgContainer);
  1418. point.setAttributeNS(null, "cx", x);
  1419. point.setAttributeNS(null, "cy", y);
  1420. point.setAttributeNS(null, "r", 0.5 * group.options.drawPoints.size);
  1421. point.setAttributeNS(null, "class", group.className + " point");
  1422. }
  1423. else {
  1424. point = exports.getSVGElement('rect',JSONcontainer,svgContainer);
  1425. point.setAttributeNS(null, "x", x - 0.5*group.options.drawPoints.size);
  1426. point.setAttributeNS(null, "y", y - 0.5*group.options.drawPoints.size);
  1427. point.setAttributeNS(null, "width", group.options.drawPoints.size);
  1428. point.setAttributeNS(null, "height", group.options.drawPoints.size);
  1429. point.setAttributeNS(null, "class", group.className + " point");
  1430. }
  1431. return point;
  1432. };
  1433. /**
  1434. * draw a bar SVG element centered on the X coordinate
  1435. *
  1436. * @param x
  1437. * @param y
  1438. * @param className
  1439. */
  1440. exports.drawBar = function (x, y, width, height, className, JSONcontainer, svgContainer) {
  1441. // if (height != 0) {
  1442. var rect = exports.getSVGElement('rect',JSONcontainer, svgContainer);
  1443. rect.setAttributeNS(null, "x", x - 0.5 * width);
  1444. rect.setAttributeNS(null, "y", y);
  1445. rect.setAttributeNS(null, "width", width);
  1446. rect.setAttributeNS(null, "height", height);
  1447. rect.setAttributeNS(null, "class", className);
  1448. // }
  1449. };
  1450. /***/ },
  1451. /* 3 */
  1452. /***/ function(module, exports, __webpack_require__) {
  1453. var util = __webpack_require__(1);
  1454. /**
  1455. * DataSet
  1456. *
  1457. * Usage:
  1458. * var dataSet = new DataSet({
  1459. * fieldId: '_id',
  1460. * type: {
  1461. * // ...
  1462. * }
  1463. * });
  1464. *
  1465. * dataSet.add(item);
  1466. * dataSet.add(data);
  1467. * dataSet.update(item);
  1468. * dataSet.update(data);
  1469. * dataSet.remove(id);
  1470. * dataSet.remove(ids);
  1471. * var data = dataSet.get();
  1472. * var data = dataSet.get(id);
  1473. * var data = dataSet.get(ids);
  1474. * var data = dataSet.get(ids, options, data);
  1475. * dataSet.clear();
  1476. *
  1477. * A data set can:
  1478. * - add/remove/update data
  1479. * - gives triggers upon changes in the data
  1480. * - can import/export data in various data formats
  1481. *
  1482. * @param {Array | DataTable} [data] Optional array with initial data
  1483. * @param {Object} [options] Available options:
  1484. * {String} fieldId Field name of the id in the
  1485. * items, 'id' by default.
  1486. * {Object.<String, String} type
  1487. * A map with field names as key,
  1488. * and the field type as value.
  1489. * @constructor DataSet
  1490. */
  1491. // TODO: add a DataSet constructor DataSet(data, options)
  1492. function DataSet (data, options) {
  1493. // correctly read optional arguments
  1494. if (data && !Array.isArray(data) && !util.isDataTable(data)) {
  1495. options = data;
  1496. data = null;
  1497. }
  1498. this._options = options || {};
  1499. this._data = {}; // map with data indexed by id
  1500. this._fieldId = this._options.fieldId || 'id'; // name of the field containing id
  1501. this._type = {}; // internal field types (NOTE: this can differ from this._options.type)
  1502. // all variants of a Date are internally stored as Date, so we can convert
  1503. // from everything to everything (also from ISODate to Number for example)
  1504. if (this._options.type) {
  1505. for (var field in this._options.type) {
  1506. if (this._options.type.hasOwnProperty(field)) {
  1507. var value = this._options.type[field];
  1508. if (value == 'Date' || value == 'ISODate' || value == 'ASPDate') {
  1509. this._type[field] = 'Date';
  1510. }
  1511. else {
  1512. this._type[field] = value;
  1513. }
  1514. }
  1515. }
  1516. }
  1517. // TODO: deprecated since version 1.1.1 (or 2.0.0?)
  1518. if (this._options.convert) {
  1519. throw new Error('Option "convert" is deprecated. Use "type" instead.');
  1520. }
  1521. this._subscribers = {}; // event subscribers
  1522. // add initial data when provided
  1523. if (data) {
  1524. this.add(data);
  1525. }
  1526. }
  1527. /**
  1528. * Subscribe to an event, add an event listener
  1529. * @param {String} event Event name. Available events: 'put', 'update',
  1530. * 'remove'
  1531. * @param {function} callback Callback method. Called with three parameters:
  1532. * {String} event
  1533. * {Object | null} params
  1534. * {String | Number} senderId
  1535. */
  1536. DataSet.prototype.on = function(event, callback) {
  1537. var subscribers = this._subscribers[event];
  1538. if (!subscribers) {
  1539. subscribers = [];
  1540. this._subscribers[event] = subscribers;
  1541. }
  1542. subscribers.push({
  1543. callback: callback
  1544. });
  1545. };
  1546. // TODO: make this function deprecated (replaced with `on` since version 0.5)
  1547. DataSet.prototype.subscribe = DataSet.prototype.on;
  1548. /**
  1549. * Unsubscribe from an event, remove an event listener
  1550. * @param {String} event
  1551. * @param {function} callback
  1552. */
  1553. DataSet.prototype.off = function(event, callback) {
  1554. var subscribers = this._subscribers[event];
  1555. if (subscribers) {
  1556. this._subscribers[event] = subscribers.filter(function (listener) {
  1557. return (listener.callback != callback);
  1558. });
  1559. }
  1560. };
  1561. // TODO: make this function deprecated (replaced with `on` since version 0.5)
  1562. DataSet.prototype.unsubscribe = DataSet.prototype.off;
  1563. /**
  1564. * Trigger an event
  1565. * @param {String} event
  1566. * @param {Object | null} params
  1567. * @param {String} [senderId] Optional id of the sender.
  1568. * @private
  1569. */
  1570. DataSet.prototype._trigger = function (event, params, senderId) {
  1571. if (event == '*') {
  1572. throw new Error('Cannot trigger event *');
  1573. }
  1574. var subscribers = [];
  1575. if (event in this._subscribers) {
  1576. subscribers = subscribers.concat(this._subscribers[event]);
  1577. }
  1578. if ('*' in this._subscribers) {
  1579. subscribers = subscribers.concat(this._subscribers['*']);
  1580. }
  1581. for (var i = 0; i < subscribers.length; i++) {
  1582. var subscriber = subscribers[i];
  1583. if (subscriber.callback) {
  1584. subscriber.callback(event, params, senderId || null);
  1585. }
  1586. }
  1587. };
  1588. /**
  1589. * Add data.
  1590. * Adding an item will fail when there already is an item with the same id.
  1591. * @param {Object | Array | DataTable} data
  1592. * @param {String} [senderId] Optional sender id
  1593. * @return {Array} addedIds Array with the ids of the added items
  1594. */
  1595. DataSet.prototype.add = function (data, senderId) {
  1596. var addedIds = [],
  1597. id,
  1598. me = this;
  1599. if (Array.isArray(data)) {
  1600. // Array
  1601. for (var i = 0, len = data.length; i < len; i++) {
  1602. id = me._addItem(data[i]);
  1603. addedIds.push(id);
  1604. }
  1605. }
  1606. else if (util.isDataTable(data)) {
  1607. // Google DataTable
  1608. var columns = this._getColumnNames(data);
  1609. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  1610. var item = {};
  1611. for (var col = 0, cols = columns.length; col < cols; col++) {
  1612. var field = columns[col];
  1613. item[field] = data.getValue(row, col);
  1614. }
  1615. id = me._addItem(item);
  1616. addedIds.push(id);
  1617. }
  1618. }
  1619. else if (data instanceof Object) {
  1620. // Single item
  1621. id = me._addItem(data);
  1622. addedIds.push(id);
  1623. }
  1624. else {
  1625. throw new Error('Unknown dataType');
  1626. }
  1627. if (addedIds.length) {
  1628. this._trigger('add', {items: addedIds}, senderId);
  1629. }
  1630. return addedIds;
  1631. };
  1632. /**
  1633. * Update existing items. When an item does not exist, it will be created
  1634. * @param {Object | Array | DataTable} data
  1635. * @param {String} [senderId] Optional sender id
  1636. * @return {Array} updatedIds The ids of the added or updated items
  1637. */
  1638. DataSet.prototype.update = function (data, senderId) {
  1639. var addedIds = [],
  1640. updatedIds = [],
  1641. me = this,
  1642. fieldId = me._fieldId;
  1643. var addOrUpdate = function (item) {
  1644. var id = item[fieldId];
  1645. if (me._data[id]) {
  1646. // update item
  1647. id = me._updateItem(item);
  1648. updatedIds.push(id);
  1649. }
  1650. else {
  1651. // add new item
  1652. id = me._addItem(item);
  1653. addedIds.push(id);
  1654. }
  1655. };
  1656. if (Array.isArray(data)) {
  1657. // Array
  1658. for (var i = 0, len = data.length; i < len; i++) {
  1659. addOrUpdate(data[i]);
  1660. }
  1661. }
  1662. else if (util.isDataTable(data)) {
  1663. // Google DataTable
  1664. var columns = this._getColumnNames(data);
  1665. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  1666. var item = {};
  1667. for (var col = 0, cols = columns.length; col < cols; col++) {
  1668. var field = columns[col];
  1669. item[field] = data.getValue(row, col);
  1670. }
  1671. addOrUpdate(item);
  1672. }
  1673. }
  1674. else if (data instanceof Object) {
  1675. // Single item
  1676. addOrUpdate(data);
  1677. }
  1678. else {
  1679. throw new Error('Unknown dataType');
  1680. }
  1681. if (addedIds.length) {
  1682. this._trigger('add', {items: addedIds}, senderId);
  1683. }
  1684. if (updatedIds.length) {
  1685. this._trigger('update', {items: updatedIds}, senderId);
  1686. }
  1687. return addedIds.concat(updatedIds);
  1688. };
  1689. /**
  1690. * Get a data item or multiple items.
  1691. *
  1692. * Usage:
  1693. *
  1694. * get()
  1695. * get(options: Object)
  1696. * get(options: Object, data: Array | DataTable)
  1697. *
  1698. * get(id: Number | String)
  1699. * get(id: Number | String, options: Object)
  1700. * get(id: Number | String, options: Object, data: Array | DataTable)
  1701. *
  1702. * get(ids: Number[] | String[])
  1703. * get(ids: Number[] | String[], options: Object)
  1704. * get(ids: Number[] | String[], options: Object, data: Array | DataTable)
  1705. *
  1706. * Where:
  1707. *
  1708. * {Number | String} id The id of an item
  1709. * {Number[] | String{}} ids An array with ids of items
  1710. * {Object} options An Object with options. Available options:
  1711. * {String} [returnType] Type of data to be
  1712. * returned. Can be 'DataTable' or 'Array' (default)
  1713. * {Object.<String, String>} [type]
  1714. * {String[]} [fields] field names to be returned
  1715. * {function} [filter] filter items
  1716. * {String | function} [order] Order the items by
  1717. * a field name or custom sort function.
  1718. * {Array | DataTable} [data] If provided, items will be appended to this
  1719. * array or table. Required in case of Google
  1720. * DataTable.
  1721. *
  1722. * @throws Error
  1723. */
  1724. DataSet.prototype.get = function (args) {
  1725. var me = this;
  1726. // parse the arguments
  1727. var id, ids, options, data;
  1728. var firstType = util.getType(arguments[0]);
  1729. if (firstType == 'String' || firstType == 'Number') {
  1730. // get(id [, options] [, data])
  1731. id = arguments[0];
  1732. options = arguments[1];
  1733. data = arguments[2];
  1734. }
  1735. else if (firstType == 'Array') {
  1736. // get(ids [, options] [, data])
  1737. ids = arguments[0];
  1738. options = arguments[1];
  1739. data = arguments[2];
  1740. }
  1741. else {
  1742. // get([, options] [, data])
  1743. options = arguments[0];
  1744. data = arguments[1];
  1745. }
  1746. // determine the return type
  1747. var returnType;
  1748. if (options && options.returnType) {
  1749. var allowedValues = ["DataTable", "Array", "Object"];
  1750. returnType = allowedValues.indexOf(options.returnType) == -1 ? "Array" : options.returnType;
  1751. if (data && (returnType != util.getType(data))) {
  1752. throw new Error('Type of parameter "data" (' + util.getType(data) + ') ' +
  1753. 'does not correspond with specified options.type (' + options.type + ')');
  1754. }
  1755. if (returnType == 'DataTable' && !util.isDataTable(data)) {
  1756. throw new Error('Parameter "data" must be a DataTable ' +
  1757. 'when options.type is "DataTable"');
  1758. }
  1759. }
  1760. else if (data) {
  1761. returnType = (util.getType(data) == 'DataTable') ? 'DataTable' : 'Array';
  1762. }
  1763. else {
  1764. returnType = 'Array';
  1765. }
  1766. // build options
  1767. var type = options && options.type || this._options.type;
  1768. var filter = options && options.filter;
  1769. var items = [], item, itemId, i, len;
  1770. // convert items
  1771. if (id != undefined) {
  1772. // return a single item
  1773. item = me._getItem(id, type);
  1774. if (filter && !filter(item)) {
  1775. item = null;
  1776. }
  1777. }
  1778. else if (ids != undefined) {
  1779. // return a subset of items
  1780. for (i = 0, len = ids.length; i < len; i++) {
  1781. item = me._getItem(ids[i], type);
  1782. if (!filter || filter(item)) {
  1783. items.push(item);
  1784. }
  1785. }
  1786. }
  1787. else {
  1788. // return all items
  1789. for (itemId in this._data) {
  1790. if (this._data.hasOwnProperty(itemId)) {
  1791. item = me._getItem(itemId, type);
  1792. if (!filter || filter(item)) {
  1793. items.push(item);
  1794. }
  1795. }
  1796. }
  1797. }
  1798. // order the results
  1799. if (options && options.order && id == undefined) {
  1800. this._sort(items, options.order);
  1801. }
  1802. // filter fields of the items
  1803. if (options && options.fields) {
  1804. var fields = options.fields;
  1805. if (id != undefined) {
  1806. item = this._filterFields(item, fields);
  1807. }
  1808. else {
  1809. for (i = 0, len = items.length; i < len; i++) {
  1810. items[i] = this._filterFields(items[i], fields);
  1811. }
  1812. }
  1813. }
  1814. // return the results
  1815. if (returnType == 'DataTable') {
  1816. var columns = this._getColumnNames(data);
  1817. if (id != undefined) {
  1818. // append a single item to the data table
  1819. me._appendRow(data, columns, item);
  1820. }
  1821. else {
  1822. // copy the items to the provided data table
  1823. for (i = 0; i < items.length; i++) {
  1824. me._appendRow(data, columns, items[i]);
  1825. }
  1826. }
  1827. return data;
  1828. }
  1829. else if (returnType == "Object") {
  1830. var result = {};
  1831. for (i = 0; i < items.length; i++) {
  1832. result[items[i].id] = items[i];
  1833. }
  1834. return result;
  1835. }
  1836. else {
  1837. // return an array
  1838. if (id != undefined) {
  1839. // a single item
  1840. return item;
  1841. }
  1842. else {
  1843. // multiple items
  1844. if (data) {
  1845. // copy the items to the provided array
  1846. for (i = 0, len = items.length; i < len; i++) {
  1847. data.push(items[i]);
  1848. }
  1849. return data;
  1850. }
  1851. else {
  1852. // just return our array
  1853. return items;
  1854. }
  1855. }
  1856. }
  1857. };
  1858. /**
  1859. * Get ids of all items or from a filtered set of items.
  1860. * @param {Object} [options] An Object with options. Available options:
  1861. * {function} [filter] filter items
  1862. * {String | function} [order] Order the items by
  1863. * a field name or custom sort function.
  1864. * @return {Array} ids
  1865. */
  1866. DataSet.prototype.getIds = function (options) {
  1867. var data = this._data,
  1868. filter = options && options.filter,
  1869. order = options && options.order,
  1870. type = options && options.type || this._options.type,
  1871. i,
  1872. len,
  1873. id,
  1874. item,
  1875. items,
  1876. ids = [];
  1877. if (filter) {
  1878. // get filtered items
  1879. if (order) {
  1880. // create ordered list
  1881. items = [];
  1882. for (id in data) {
  1883. if (data.hasOwnProperty(id)) {
  1884. item = this._getItem(id, type);
  1885. if (filter(item)) {
  1886. items.push(item);
  1887. }
  1888. }
  1889. }
  1890. this._sort(items, order);
  1891. for (i = 0, len = items.length; i < len; i++) {
  1892. ids[i] = items[i][this._fieldId];
  1893. }
  1894. }
  1895. else {
  1896. // create unordered list
  1897. for (id in data) {
  1898. if (data.hasOwnProperty(id)) {
  1899. item = this._getItem(id, type);
  1900. if (filter(item)) {
  1901. ids.push(item[this._fieldId]);
  1902. }
  1903. }
  1904. }
  1905. }
  1906. }
  1907. else {
  1908. // get all items
  1909. if (order) {
  1910. // create an ordered list
  1911. items = [];
  1912. for (id in data) {
  1913. if (data.hasOwnProperty(id)) {
  1914. items.push(data[id]);
  1915. }
  1916. }
  1917. this._sort(items, order);
  1918. for (i = 0, len = items.length; i < len; i++) {
  1919. ids[i] = items[i][this._fieldId];
  1920. }
  1921. }
  1922. else {
  1923. // create unordered list
  1924. for (id in data) {
  1925. if (data.hasOwnProperty(id)) {
  1926. item = data[id];
  1927. ids.push(item[this._fieldId]);
  1928. }
  1929. }
  1930. }
  1931. }
  1932. return ids;
  1933. };
  1934. /**
  1935. * Returns the DataSet itself. Is overwritten for example by the DataView,
  1936. * which returns the DataSet it is connected to instead.
  1937. */
  1938. DataSet.prototype.getDataSet = function () {
  1939. return this;
  1940. };
  1941. /**
  1942. * Execute a callback function for every item in the dataset.
  1943. * @param {function} callback
  1944. * @param {Object} [options] Available options:
  1945. * {Object.<String, String>} [type]
  1946. * {String[]} [fields] filter fields
  1947. * {function} [filter] filter items
  1948. * {String | function} [order] Order the items by
  1949. * a field name or custom sort function.
  1950. */
  1951. DataSet.prototype.forEach = function (callback, options) {
  1952. var filter = options && options.filter,
  1953. type = options && options.type || this._options.type,
  1954. data = this._data,
  1955. item,
  1956. id;
  1957. if (options && options.order) {
  1958. // execute forEach on ordered list
  1959. var items = this.get(options);
  1960. for (var i = 0, len = items.length; i < len; i++) {
  1961. item = items[i];
  1962. id = item[this._fieldId];
  1963. callback(item, id);
  1964. }
  1965. }
  1966. else {
  1967. // unordered
  1968. for (id in data) {
  1969. if (data.hasOwnProperty(id)) {
  1970. item = this._getItem(id, type);
  1971. if (!filter || filter(item)) {
  1972. callback(item, id);
  1973. }
  1974. }
  1975. }
  1976. }
  1977. };
  1978. /**
  1979. * Map every item in the dataset.
  1980. * @param {function} callback
  1981. * @param {Object} [options] Available options:
  1982. * {Object.<String, String>} [type]
  1983. * {String[]} [fields] filter fields
  1984. * {function} [filter] filter items
  1985. * {String | function} [order] Order the items by
  1986. * a field name or custom sort function.
  1987. * @return {Object[]} mappedItems
  1988. */
  1989. DataSet.prototype.map = function (callback, options) {
  1990. var filter = options && options.filter,
  1991. type = options && options.type || this._options.type,
  1992. mappedItems = [],
  1993. data = this._data,
  1994. item;
  1995. // convert and filter items
  1996. for (var id in data) {
  1997. if (data.hasOwnProperty(id)) {
  1998. item = this._getItem(id, type);
  1999. if (!filter || filter(item)) {
  2000. mappedItems.push(callback(item, id));
  2001. }
  2002. }
  2003. }
  2004. // order items
  2005. if (options && options.order) {
  2006. this._sort(mappedItems, options.order);
  2007. }
  2008. return mappedItems;
  2009. };
  2010. /**
  2011. * Filter the fields of an item
  2012. * @param {Object} item
  2013. * @param {String[]} fields Field names
  2014. * @return {Object} filteredItem
  2015. * @private
  2016. */
  2017. DataSet.prototype._filterFields = function (item, fields) {
  2018. var filteredItem = {};
  2019. for (var field in item) {
  2020. if (item.hasOwnProperty(field) && (fields.indexOf(field) != -1)) {
  2021. filteredItem[field] = item[field];
  2022. }
  2023. }
  2024. return filteredItem;
  2025. };
  2026. /**
  2027. * Sort the provided array with items
  2028. * @param {Object[]} items
  2029. * @param {String | function} order A field name or custom sort function.
  2030. * @private
  2031. */
  2032. DataSet.prototype._sort = function (items, order) {
  2033. if (util.isString(order)) {
  2034. // order by provided field name
  2035. var name = order; // field name
  2036. items.sort(function (a, b) {
  2037. var av = a[name];
  2038. var bv = b[name];
  2039. return (av > bv) ? 1 : ((av < bv) ? -1 : 0);
  2040. });
  2041. }
  2042. else if (typeof order === 'function') {
  2043. // order by sort function
  2044. items.sort(order);
  2045. }
  2046. // TODO: extend order by an Object {field:String, direction:String}
  2047. // where direction can be 'asc' or 'desc'
  2048. else {
  2049. throw new TypeError('Order must be a function or a string');
  2050. }
  2051. };
  2052. /**
  2053. * Remove an object by pointer or by id
  2054. * @param {String | Number | Object | Array} id Object or id, or an array with
  2055. * objects or ids to be removed
  2056. * @param {String} [senderId] Optional sender id
  2057. * @return {Array} removedIds
  2058. */
  2059. DataSet.prototype.remove = function (id, senderId) {
  2060. var removedIds = [],
  2061. i, len, removedId;
  2062. if (Array.isArray(id)) {
  2063. for (i = 0, len = id.length; i < len; i++) {
  2064. removedId = this._remove(id[i]);
  2065. if (removedId != null) {
  2066. removedIds.push(removedId);
  2067. }
  2068. }
  2069. }
  2070. else {
  2071. removedId = this._remove(id);
  2072. if (removedId != null) {
  2073. removedIds.push(removedId);
  2074. }
  2075. }
  2076. if (removedIds.length) {
  2077. this._trigger('remove', {items: removedIds}, senderId);
  2078. }
  2079. return removedIds;
  2080. };
  2081. /**
  2082. * Remove an item by its id
  2083. * @param {Number | String | Object} id id or item
  2084. * @returns {Number | String | null} id
  2085. * @private
  2086. */
  2087. DataSet.prototype._remove = function (id) {
  2088. if (util.isNumber(id) || util.isString(id)) {
  2089. if (this._data[id]) {
  2090. delete this._data[id];
  2091. return id;
  2092. }
  2093. }
  2094. else if (id instanceof Object) {
  2095. var itemId = id[this._fieldId];
  2096. if (itemId && this._data[itemId]) {
  2097. delete this._data[itemId];
  2098. return itemId;
  2099. }
  2100. }
  2101. return null;
  2102. };
  2103. /**
  2104. * Clear the data
  2105. * @param {String} [senderId] Optional sender id
  2106. * @return {Array} removedIds The ids of all removed items
  2107. */
  2108. DataSet.prototype.clear = function (senderId) {
  2109. var ids = Object.keys(this._data);
  2110. this._data = {};
  2111. this._trigger('remove', {items: ids}, senderId);
  2112. return ids;
  2113. };
  2114. /**
  2115. * Find the item with maximum value of a specified field
  2116. * @param {String} field
  2117. * @return {Object | null} item Item containing max value, or null if no items
  2118. */
  2119. DataSet.prototype.max = function (field) {
  2120. var data = this._data,
  2121. max = null,
  2122. maxField = null;
  2123. for (var id in data) {
  2124. if (data.hasOwnProperty(id)) {
  2125. var item = data[id];
  2126. var itemField = item[field];
  2127. if (itemField != null && (!max || itemField > maxField)) {
  2128. max = item;
  2129. maxField = itemField;
  2130. }
  2131. }
  2132. }
  2133. return max;
  2134. };
  2135. /**
  2136. * Find the item with minimum value of a specified field
  2137. * @param {String} field
  2138. * @return {Object | null} item Item containing max value, or null if no items
  2139. */
  2140. DataSet.prototype.min = function (field) {
  2141. var data = this._data,
  2142. min = null,
  2143. minField = null;
  2144. for (var id in data) {
  2145. if (data.hasOwnProperty(id)) {
  2146. var item = data[id];
  2147. var itemField = item[field];
  2148. if (itemField != null && (!min || itemField < minField)) {
  2149. min = item;
  2150. minField = itemField;
  2151. }
  2152. }
  2153. }
  2154. return min;
  2155. };
  2156. /**
  2157. * Find all distinct values of a specified field
  2158. * @param {String} field
  2159. * @return {Array} values Array containing all distinct values. If data items
  2160. * do not contain the specified field are ignored.
  2161. * The returned array is unordered.
  2162. */
  2163. DataSet.prototype.distinct = function (field) {
  2164. var data = this._data;
  2165. var values = [];
  2166. var fieldType = this._options.type && this._options.type[field] || null;
  2167. var count = 0;
  2168. var i;
  2169. for (var prop in data) {
  2170. if (data.hasOwnProperty(prop)) {
  2171. var item = data[prop];
  2172. var value = item[field];
  2173. var exists = false;
  2174. for (i = 0; i < count; i++) {
  2175. if (values[i] == value) {
  2176. exists = true;
  2177. break;
  2178. }
  2179. }
  2180. if (!exists && (value !== undefined)) {
  2181. values[count] = value;
  2182. count++;
  2183. }
  2184. }
  2185. }
  2186. if (fieldType) {
  2187. for (i = 0; i < values.length; i++) {
  2188. values[i] = util.convert(values[i], fieldType);
  2189. }
  2190. }
  2191. return values;
  2192. };
  2193. /**
  2194. * Add a single item. Will fail when an item with the same id already exists.
  2195. * @param {Object} item
  2196. * @return {String} id
  2197. * @private
  2198. */
  2199. DataSet.prototype._addItem = function (item) {
  2200. var id = item[this._fieldId];
  2201. if (id != undefined) {
  2202. // check whether this id is already taken
  2203. if (this._data[id]) {
  2204. // item already exists
  2205. throw new Error('Cannot add item: item with id ' + id + ' already exists');
  2206. }
  2207. }
  2208. else {
  2209. // generate an id
  2210. id = util.randomUUID();
  2211. item[this._fieldId] = id;
  2212. }
  2213. var d = {};
  2214. for (var field in item) {
  2215. if (item.hasOwnProperty(field)) {
  2216. var fieldType = this._type[field]; // type may be undefined
  2217. d[field] = util.convert(item[field], fieldType);
  2218. }
  2219. }
  2220. this._data[id] = d;
  2221. return id;
  2222. };
  2223. /**
  2224. * Get an item. Fields can be converted to a specific type
  2225. * @param {String} id
  2226. * @param {Object.<String, String>} [types] field types to convert
  2227. * @return {Object | null} item
  2228. * @private
  2229. */
  2230. DataSet.prototype._getItem = function (id, types) {
  2231. var field, value;
  2232. // get the item from the dataset
  2233. var raw = this._data[id];
  2234. if (!raw) {
  2235. return null;
  2236. }
  2237. // convert the items field types
  2238. var converted = {};
  2239. if (types) {
  2240. for (field in raw) {
  2241. if (raw.hasOwnProperty(field)) {
  2242. value = raw[field];
  2243. converted[field] = util.convert(value, types[field]);
  2244. }
  2245. }
  2246. }
  2247. else {
  2248. // no field types specified, no converting needed
  2249. for (field in raw) {
  2250. if (raw.hasOwnProperty(field)) {
  2251. value = raw[field];
  2252. converted[field] = value;
  2253. }
  2254. }
  2255. }
  2256. return converted;
  2257. };
  2258. /**
  2259. * Update a single item: merge with existing item.
  2260. * Will fail when the item has no id, or when there does not exist an item
  2261. * with the same id.
  2262. * @param {Object} item
  2263. * @return {String} id
  2264. * @private
  2265. */
  2266. DataSet.prototype._updateItem = function (item) {
  2267. var id = item[this._fieldId];
  2268. if (id == undefined) {
  2269. throw new Error('Cannot update item: item has no id (item: ' + JSON.stringify(item) + ')');
  2270. }
  2271. var d = this._data[id];
  2272. if (!d) {
  2273. // item doesn't exist
  2274. throw new Error('Cannot update item: no item with id ' + id + ' found');
  2275. }
  2276. // merge with current item
  2277. for (var field in item) {
  2278. if (item.hasOwnProperty(field)) {
  2279. var fieldType = this._type[field]; // type may be undefined
  2280. d[field] = util.convert(item[field], fieldType);
  2281. }
  2282. }
  2283. return id;
  2284. };
  2285. /**
  2286. * Get an array with the column names of a Google DataTable
  2287. * @param {DataTable} dataTable
  2288. * @return {String[]} columnNames
  2289. * @private
  2290. */
  2291. DataSet.prototype._getColumnNames = function (dataTable) {
  2292. var columns = [];
  2293. for (var col = 0, cols = dataTable.getNumberOfColumns(); col < cols; col++) {
  2294. columns[col] = dataTable.getColumnId(col) || dataTable.getColumnLabel(col);
  2295. }
  2296. return columns;
  2297. };
  2298. /**
  2299. * Append an item as a row to the dataTable
  2300. * @param dataTable
  2301. * @param columns
  2302. * @param item
  2303. * @private
  2304. */
  2305. DataSet.prototype._appendRow = function (dataTable, columns, item) {
  2306. var row = dataTable.addRow();
  2307. for (var col = 0, cols = columns.length; col < cols; col++) {
  2308. var field = columns[col];
  2309. dataTable.setValue(row, col, item[field]);
  2310. }
  2311. };
  2312. module.exports = DataSet;
  2313. /***/ },
  2314. /* 4 */
  2315. /***/ function(module, exports, __webpack_require__) {
  2316. var util = __webpack_require__(1);
  2317. var DataSet = __webpack_require__(3);
  2318. /**
  2319. * DataView
  2320. *
  2321. * a dataview offers a filtered view on a dataset or an other dataview.
  2322. *
  2323. * @param {DataSet | DataView} data
  2324. * @param {Object} [options] Available options: see method get
  2325. *
  2326. * @constructor DataView
  2327. */
  2328. function DataView (data, options) {
  2329. this._data = null;
  2330. this._ids = {}; // ids of the items currently in memory (just contains a boolean true)
  2331. this._options = options || {};
  2332. this._fieldId = 'id'; // name of the field containing id
  2333. this._subscribers = {}; // event subscribers
  2334. var me = this;
  2335. this.listener = function () {
  2336. me._onEvent.apply(me, arguments);
  2337. };
  2338. this.setData(data);
  2339. }
  2340. // TODO: implement a function .config() to dynamically update things like configured filter
  2341. // and trigger changes accordingly
  2342. /**
  2343. * Set a data source for the view
  2344. * @param {DataSet | DataView} data
  2345. */
  2346. DataView.prototype.setData = function (data) {
  2347. var ids, i, len;
  2348. if (this._data) {
  2349. // unsubscribe from current dataset
  2350. if (this._data.unsubscribe) {
  2351. this._data.unsubscribe('*', this.listener);
  2352. }
  2353. // trigger a remove of all items in memory
  2354. ids = [];
  2355. for (var id in this._ids) {
  2356. if (this._ids.hasOwnProperty(id)) {
  2357. ids.push(id);
  2358. }
  2359. }
  2360. this._ids = {};
  2361. this._trigger('remove', {items: ids});
  2362. }
  2363. this._data = data;
  2364. if (this._data) {
  2365. // update fieldId
  2366. this._fieldId = this._options.fieldId ||
  2367. (this._data && this._data.options && this._data.options.fieldId) ||
  2368. 'id';
  2369. // trigger an add of all added items
  2370. ids = this._data.getIds({filter: this._options && this._options.filter});
  2371. for (i = 0, len = ids.length; i < len; i++) {
  2372. id = ids[i];
  2373. this._ids[id] = true;
  2374. }
  2375. this._trigger('add', {items: ids});
  2376. // subscribe to new dataset
  2377. if (this._data.on) {
  2378. this._data.on('*', this.listener);
  2379. }
  2380. }
  2381. };
  2382. /**
  2383. * Get data from the data view
  2384. *
  2385. * Usage:
  2386. *
  2387. * get()
  2388. * get(options: Object)
  2389. * get(options: Object, data: Array | DataTable)
  2390. *
  2391. * get(id: Number)
  2392. * get(id: Number, options: Object)
  2393. * get(id: Number, options: Object, data: Array | DataTable)
  2394. *
  2395. * get(ids: Number[])
  2396. * get(ids: Number[], options: Object)
  2397. * get(ids: Number[], options: Object, data: Array | DataTable)
  2398. *
  2399. * Where:
  2400. *
  2401. * {Number | String} id The id of an item
  2402. * {Number[] | String{}} ids An array with ids of items
  2403. * {Object} options An Object with options. Available options:
  2404. * {String} [type] Type of data to be returned. Can
  2405. * be 'DataTable' or 'Array' (default)
  2406. * {Object.<String, String>} [convert]
  2407. * {String[]} [fields] field names to be returned
  2408. * {function} [filter] filter items
  2409. * {String | function} [order] Order the items by
  2410. * a field name or custom sort function.
  2411. * {Array | DataTable} [data] If provided, items will be appended to this
  2412. * array or table. Required in case of Google
  2413. * DataTable.
  2414. * @param args
  2415. */
  2416. DataView.prototype.get = function (args) {
  2417. var me = this;
  2418. // parse the arguments
  2419. var ids, options, data;
  2420. var firstType = util.getType(arguments[0]);
  2421. if (firstType == 'String' || firstType == 'Number' || firstType == 'Array') {
  2422. // get(id(s) [, options] [, data])
  2423. ids = arguments[0]; // can be a single id or an array with ids
  2424. options = arguments[1];
  2425. data = arguments[2];
  2426. }
  2427. else {
  2428. // get([, options] [, data])
  2429. options = arguments[0];
  2430. data = arguments[1];
  2431. }
  2432. // extend the options with the default options and provided options
  2433. var viewOptions = util.extend({}, this._options, options);
  2434. // create a combined filter method when needed
  2435. if (this._options.filter && options && options.filter) {
  2436. viewOptions.filter = function (item) {
  2437. return me._options.filter(item) && options.filter(item);
  2438. }
  2439. }
  2440. // build up the call to the linked data set
  2441. var getArguments = [];
  2442. if (ids != undefined) {
  2443. getArguments.push(ids);
  2444. }
  2445. getArguments.push(viewOptions);
  2446. getArguments.push(data);
  2447. return this._data && this._data.get.apply(this._data, getArguments);
  2448. };
  2449. /**
  2450. * Get ids of all items or from a filtered set of items.
  2451. * @param {Object} [options] An Object with options. Available options:
  2452. * {function} [filter] filter items
  2453. * {String | function} [order] Order the items by
  2454. * a field name or custom sort function.
  2455. * @return {Array} ids
  2456. */
  2457. DataView.prototype.getIds = function (options) {
  2458. var ids;
  2459. if (this._data) {
  2460. var defaultFilter = this._options.filter;
  2461. var filter;
  2462. if (options && options.filter) {
  2463. if (defaultFilter) {
  2464. filter = function (item) {
  2465. return defaultFilter(item) && options.filter(item);
  2466. }
  2467. }
  2468. else {
  2469. filter = options.filter;
  2470. }
  2471. }
  2472. else {
  2473. filter = defaultFilter;
  2474. }
  2475. ids = this._data.getIds({
  2476. filter: filter,
  2477. order: options && options.order
  2478. });
  2479. }
  2480. else {
  2481. ids = [];
  2482. }
  2483. return ids;
  2484. };
  2485. /**
  2486. * Get the DataSet to which this DataView is connected. In case there is a chain
  2487. * of multiple DataViews, the root DataSet of this chain is returned.
  2488. * @return {DataSet} dataSet
  2489. */
  2490. DataView.prototype.getDataSet = function () {
  2491. var dataSet = this;
  2492. while (dataSet instanceof DataView) {
  2493. dataSet = dataSet._data;
  2494. }
  2495. return dataSet || null;
  2496. };
  2497. /**
  2498. * Event listener. Will propagate all events from the connected data set to
  2499. * the subscribers of the DataView, but will filter the items and only trigger
  2500. * when there are changes in the filtered data set.
  2501. * @param {String} event
  2502. * @param {Object | null} params
  2503. * @param {String} senderId
  2504. * @private
  2505. */
  2506. DataView.prototype._onEvent = function (event, params, senderId) {
  2507. var i, len, id, item,
  2508. ids = params && params.items,
  2509. data = this._data,
  2510. added = [],
  2511. updated = [],
  2512. removed = [];
  2513. if (ids && data) {
  2514. switch (event) {
  2515. case 'add':
  2516. // filter the ids of the added items
  2517. for (i = 0, len = ids.length; i < len; i++) {
  2518. id = ids[i];
  2519. item = this.get(id);
  2520. if (item) {
  2521. this._ids[id] = true;
  2522. added.push(id);
  2523. }
  2524. }
  2525. break;
  2526. case 'update':
  2527. // determine the event from the views viewpoint: an updated
  2528. // item can be added, updated, or removed from this view.
  2529. for (i = 0, len = ids.length; i < len; i++) {
  2530. id = ids[i];
  2531. item = this.get(id);
  2532. if (item) {
  2533. if (this._ids[id]) {
  2534. updated.push(id);
  2535. }
  2536. else {
  2537. this._ids[id] = true;
  2538. added.push(id);
  2539. }
  2540. }
  2541. else {
  2542. if (this._ids[id]) {
  2543. delete this._ids[id];
  2544. removed.push(id);
  2545. }
  2546. else {
  2547. // nothing interesting for me :-(
  2548. }
  2549. }
  2550. }
  2551. break;
  2552. case 'remove':
  2553. // filter the ids of the removed items
  2554. for (i = 0, len = ids.length; i < len; i++) {
  2555. id = ids[i];
  2556. if (this._ids[id]) {
  2557. delete this._ids[id];
  2558. removed.push(id);
  2559. }
  2560. }
  2561. break;
  2562. }
  2563. if (added.length) {
  2564. this._trigger('add', {items: added}, senderId);
  2565. }
  2566. if (updated.length) {
  2567. this._trigger('update', {items: updated}, senderId);
  2568. }
  2569. if (removed.length) {
  2570. this._trigger('remove', {items: removed}, senderId);
  2571. }
  2572. }
  2573. };
  2574. // copy subscription functionality from DataSet
  2575. DataView.prototype.on = DataSet.prototype.on;
  2576. DataView.prototype.off = DataSet.prototype.off;
  2577. DataView.prototype._trigger = DataSet.prototype._trigger;
  2578. // TODO: make these functions deprecated (replaced with `on` and `off` since version 0.5)
  2579. DataView.prototype.subscribe = DataView.prototype.on;
  2580. DataView.prototype.unsubscribe = DataView.prototype.off;
  2581. module.exports = DataView;
  2582. /***/ },
  2583. /* 5 */
  2584. /***/ function(module, exports, __webpack_require__) {
  2585. var Emitter = __webpack_require__(50);
  2586. var DataSet = __webpack_require__(3);
  2587. var DataView = __webpack_require__(4);
  2588. var util = __webpack_require__(1);
  2589. var Point3d = __webpack_require__(9);
  2590. var Point2d = __webpack_require__(8);
  2591. var Camera = __webpack_require__(6);
  2592. var Filter = __webpack_require__(7);
  2593. var Slider = __webpack_require__(10);
  2594. var StepNumber = __webpack_require__(11);
  2595. /**
  2596. * @constructor Graph3d
  2597. * Graph3d displays data in 3d.
  2598. *
  2599. * Graph3d is developed in javascript as a Google Visualization Chart.
  2600. *
  2601. * @param {Element} container The DOM element in which the Graph3d will
  2602. * be created. Normally a div element.
  2603. * @param {DataSet | DataView | Array} [data]
  2604. * @param {Object} [options]
  2605. */
  2606. function Graph3d(container, data, options) {
  2607. if (!(this instanceof Graph3d)) {
  2608. throw new SyntaxError('Constructor must be called with the new operator');
  2609. }
  2610. // create variables and set default values
  2611. this.containerElement = container;
  2612. this.width = '400px';
  2613. this.height = '400px';
  2614. this.margin = 10; // px
  2615. this.defaultXCenter = '55%';
  2616. this.defaultYCenter = '50%';
  2617. this.xLabel = 'x';
  2618. this.yLabel = 'y';
  2619. this.zLabel = 'z';
  2620. this.filterLabel = 'time';
  2621. this.legendLabel = 'value';
  2622. this.style = Graph3d.STYLE.DOT;
  2623. this.showPerspective = true;
  2624. this.showGrid = true;
  2625. this.keepAspectRatio = true;
  2626. this.showShadow = false;
  2627. this.showGrayBottom = false; // TODO: this does not work correctly
  2628. this.showTooltip = false;
  2629. this.verticalRatio = 0.5; // 0.1 to 1.0, where 1.0 results in a 'cube'
  2630. this.animationInterval = 1000; // milliseconds
  2631. this.animationPreload = false;
  2632. this.camera = new Camera();
  2633. this.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?
  2634. this.dataTable = null; // The original data table
  2635. this.dataPoints = null; // The table with point objects
  2636. // the column indexes
  2637. this.colX = undefined;
  2638. this.colY = undefined;
  2639. this.colZ = undefined;
  2640. this.colValue = undefined;
  2641. this.colFilter = undefined;
  2642. this.xMin = 0;
  2643. this.xStep = undefined; // auto by default
  2644. this.xMax = 1;
  2645. this.yMin = 0;
  2646. this.yStep = undefined; // auto by default
  2647. this.yMax = 1;
  2648. this.zMin = 0;
  2649. this.zStep = undefined; // auto by default
  2650. this.zMax = 1;
  2651. this.valueMin = 0;
  2652. this.valueMax = 1;
  2653. this.xBarWidth = 1;
  2654. this.yBarWidth = 1;
  2655. // TODO: customize axis range
  2656. // constants
  2657. this.colorAxis = '#4D4D4D';
  2658. this.colorGrid = '#D3D3D3';
  2659. this.colorDot = '#7DC1FF';
  2660. this.colorDotBorder = '#3267D2';
  2661. // create a frame and canvas
  2662. this.create();
  2663. // apply options (also when undefined)
  2664. this.setOptions(options);
  2665. // apply data
  2666. if (data) {
  2667. this.setData(data);
  2668. }
  2669. }
  2670. // Extend Graph3d with an Emitter mixin
  2671. Emitter(Graph3d.prototype);
  2672. /**
  2673. * Calculate the scaling values, dependent on the range in x, y, and z direction
  2674. */
  2675. Graph3d.prototype._setScale = function() {
  2676. this.scale = new Point3d(1 / (this.xMax - this.xMin),
  2677. 1 / (this.yMax - this.yMin),
  2678. 1 / (this.zMax - this.zMin));
  2679. // keep aspect ration between x and y scale if desired
  2680. if (this.keepAspectRatio) {
  2681. if (this.scale.x < this.scale.y) {
  2682. //noinspection JSSuspiciousNameCombination
  2683. this.scale.y = this.scale.x;
  2684. }
  2685. else {
  2686. //noinspection JSSuspiciousNameCombination
  2687. this.scale.x = this.scale.y;
  2688. }
  2689. }
  2690. // scale the vertical axis
  2691. this.scale.z *= this.verticalRatio;
  2692. // TODO: can this be automated? verticalRatio?
  2693. // determine scale for (optional) value
  2694. this.scale.value = 1 / (this.valueMax - this.valueMin);
  2695. // position the camera arm
  2696. var xCenter = (this.xMax + this.xMin) / 2 * this.scale.x;
  2697. var yCenter = (this.yMax + this.yMin) / 2 * this.scale.y;
  2698. var zCenter = (this.zMax + this.zMin) / 2 * this.scale.z;
  2699. this.camera.setArmLocation(xCenter, yCenter, zCenter);
  2700. };
  2701. /**
  2702. * Convert a 3D location to a 2D location on screen
  2703. * http://en.wikipedia.org/wiki/3D_projection
  2704. * @param {Point3d} point3d A 3D point with parameters x, y, z
  2705. * @return {Point2d} point2d A 2D point with parameters x, y
  2706. */
  2707. Graph3d.prototype._convert3Dto2D = function(point3d) {
  2708. var translation = this._convertPointToTranslation(point3d);
  2709. return this._convertTranslationToScreen(translation);
  2710. };
  2711. /**
  2712. * Convert a 3D location its translation seen from the camera
  2713. * http://en.wikipedia.org/wiki/3D_projection
  2714. * @param {Point3d} point3d A 3D point with parameters x, y, z
  2715. * @return {Point3d} translation A 3D point with parameters x, y, z This is
  2716. * the translation of the point, seen from the
  2717. * camera
  2718. */
  2719. Graph3d.prototype._convertPointToTranslation = function(point3d) {
  2720. var ax = point3d.x * this.scale.x,
  2721. ay = point3d.y * this.scale.y,
  2722. az = point3d.z * this.scale.z,
  2723. cx = this.camera.getCameraLocation().x,
  2724. cy = this.camera.getCameraLocation().y,
  2725. cz = this.camera.getCameraLocation().z,
  2726. // calculate angles
  2727. sinTx = Math.sin(this.camera.getCameraRotation().x),
  2728. cosTx = Math.cos(this.camera.getCameraRotation().x),
  2729. sinTy = Math.sin(this.camera.getCameraRotation().y),
  2730. cosTy = Math.cos(this.camera.getCameraRotation().y),
  2731. sinTz = Math.sin(this.camera.getCameraRotation().z),
  2732. cosTz = Math.cos(this.camera.getCameraRotation().z),
  2733. // calculate translation
  2734. dx = cosTy * (sinTz * (ay - cy) + cosTz * (ax - cx)) - sinTy * (az - cz),
  2735. dy = sinTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) + cosTx * (cosTz * (ay - cy) - sinTz * (ax-cx)),
  2736. dz = cosTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) - sinTx * (cosTz * (ay - cy) - sinTz * (ax-cx));
  2737. return new Point3d(dx, dy, dz);
  2738. };
  2739. /**
  2740. * Convert a translation point to a point on the screen
  2741. * @param {Point3d} translation A 3D point with parameters x, y, z This is
  2742. * the translation of the point, seen from the
  2743. * camera
  2744. * @return {Point2d} point2d A 2D point with parameters x, y
  2745. */
  2746. Graph3d.prototype._convertTranslationToScreen = function(translation) {
  2747. var ex = this.eye.x,
  2748. ey = this.eye.y,
  2749. ez = this.eye.z,
  2750. dx = translation.x,
  2751. dy = translation.y,
  2752. dz = translation.z;
  2753. // calculate position on screen from translation
  2754. var bx;
  2755. var by;
  2756. if (this.showPerspective) {
  2757. bx = (dx - ex) * (ez / dz);
  2758. by = (dy - ey) * (ez / dz);
  2759. }
  2760. else {
  2761. bx = dx * -(ez / this.camera.getArmLength());
  2762. by = dy * -(ez / this.camera.getArmLength());
  2763. }
  2764. // shift and scale the point to the center of the screen
  2765. // use the width of the graph to scale both horizontally and vertically.
  2766. return new Point2d(
  2767. this.xcenter + bx * this.frame.canvas.clientWidth,
  2768. this.ycenter - by * this.frame.canvas.clientWidth);
  2769. };
  2770. /**
  2771. * Set the background styling for the graph
  2772. * @param {string | {fill: string, stroke: string, strokeWidth: string}} backgroundColor
  2773. */
  2774. Graph3d.prototype._setBackgroundColor = function(backgroundColor) {
  2775. var fill = 'white';
  2776. var stroke = 'gray';
  2777. var strokeWidth = 1;
  2778. if (typeof(backgroundColor) === 'string') {
  2779. fill = backgroundColor;
  2780. stroke = 'none';
  2781. strokeWidth = 0;
  2782. }
  2783. else if (typeof(backgroundColor) === 'object') {
  2784. if (backgroundColor.fill !== undefined) fill = backgroundColor.fill;
  2785. if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke;
  2786. if (backgroundColor.strokeWidth !== undefined) strokeWidth = backgroundColor.strokeWidth;
  2787. }
  2788. else if (backgroundColor === undefined) {
  2789. // use use defaults
  2790. }
  2791. else {
  2792. throw 'Unsupported type of backgroundColor';
  2793. }
  2794. this.frame.style.backgroundColor = fill;
  2795. this.frame.style.borderColor = stroke;
  2796. this.frame.style.borderWidth = strokeWidth + 'px';
  2797. this.frame.style.borderStyle = 'solid';
  2798. };
  2799. /// enumerate the available styles
  2800. Graph3d.STYLE = {
  2801. BAR: 0,
  2802. BARCOLOR: 1,
  2803. BARSIZE: 2,
  2804. DOT : 3,
  2805. DOTLINE : 4,
  2806. DOTCOLOR: 5,
  2807. DOTSIZE: 6,
  2808. GRID : 7,
  2809. LINE: 8,
  2810. SURFACE : 9
  2811. };
  2812. /**
  2813. * Retrieve the style index from given styleName
  2814. * @param {string} styleName Style name such as 'dot', 'grid', 'dot-line'
  2815. * @return {Number} styleNumber Enumeration value representing the style, or -1
  2816. * when not found
  2817. */
  2818. Graph3d.prototype._getStyleNumber = function(styleName) {
  2819. switch (styleName) {
  2820. case 'dot': return Graph3d.STYLE.DOT;
  2821. case 'dot-line': return Graph3d.STYLE.DOTLINE;
  2822. case 'dot-color': return Graph3d.STYLE.DOTCOLOR;
  2823. case 'dot-size': return Graph3d.STYLE.DOTSIZE;
  2824. case 'line': return Graph3d.STYLE.LINE;
  2825. case 'grid': return Graph3d.STYLE.GRID;
  2826. case 'surface': return Graph3d.STYLE.SURFACE;
  2827. case 'bar': return Graph3d.STYLE.BAR;
  2828. case 'bar-color': return Graph3d.STYLE.BARCOLOR;
  2829. case 'bar-size': return Graph3d.STYLE.BARSIZE;
  2830. }
  2831. return -1;
  2832. };
  2833. /**
  2834. * Determine the indexes of the data columns, based on the given style and data
  2835. * @param {DataSet} data
  2836. * @param {Number} style
  2837. */
  2838. Graph3d.prototype._determineColumnIndexes = function(data, style) {
  2839. if (this.style === Graph3d.STYLE.DOT ||
  2840. this.style === Graph3d.STYLE.DOTLINE ||
  2841. this.style === Graph3d.STYLE.LINE ||
  2842. this.style === Graph3d.STYLE.GRID ||
  2843. this.style === Graph3d.STYLE.SURFACE ||
  2844. this.style === Graph3d.STYLE.BAR) {
  2845. // 3 columns expected, and optionally a 4th with filter values
  2846. this.colX = 0;
  2847. this.colY = 1;
  2848. this.colZ = 2;
  2849. this.colValue = undefined;
  2850. if (data.getNumberOfColumns() > 3) {
  2851. this.colFilter = 3;
  2852. }
  2853. }
  2854. else if (this.style === Graph3d.STYLE.DOTCOLOR ||
  2855. this.style === Graph3d.STYLE.DOTSIZE ||
  2856. this.style === Graph3d.STYLE.BARCOLOR ||
  2857. this.style === Graph3d.STYLE.BARSIZE) {
  2858. // 4 columns expected, and optionally a 5th with filter values
  2859. this.colX = 0;
  2860. this.colY = 1;
  2861. this.colZ = 2;
  2862. this.colValue = 3;
  2863. if (data.getNumberOfColumns() > 4) {
  2864. this.colFilter = 4;
  2865. }
  2866. }
  2867. else {
  2868. throw 'Unknown style "' + this.style + '"';
  2869. }
  2870. };
  2871. Graph3d.prototype.getNumberOfRows = function(data) {
  2872. return data.length;
  2873. }
  2874. Graph3d.prototype.getNumberOfColumns = function(data) {
  2875. var counter = 0;
  2876. for (var column in data[0]) {
  2877. if (data[0].hasOwnProperty(column)) {
  2878. counter++;
  2879. }
  2880. }
  2881. return counter;
  2882. }
  2883. Graph3d.prototype.getDistinctValues = function(data, column) {
  2884. var distinctValues = [];
  2885. for (var i = 0; i < data.length; i++) {
  2886. if (distinctValues.indexOf(data[i][column]) == -1) {
  2887. distinctValues.push(data[i][column]);
  2888. }
  2889. }
  2890. return distinctValues;
  2891. }
  2892. Graph3d.prototype.getColumnRange = function(data,column) {
  2893. var minMax = {min:data[0][column],max:data[0][column]};
  2894. for (var i = 0; i < data.length; i++) {
  2895. if (minMax.min > data[i][column]) { minMax.min = data[i][column]; }
  2896. if (minMax.max < data[i][column]) { minMax.max = data[i][column]; }
  2897. }
  2898. return minMax;
  2899. };
  2900. /**
  2901. * Initialize the data from the data table. Calculate minimum and maximum values
  2902. * and column index values
  2903. * @param {Array | DataSet | DataView} rawData The data containing the items for the Graph.
  2904. * @param {Number} style Style Number
  2905. */
  2906. Graph3d.prototype._dataInitialize = function (rawData, style) {
  2907. var me = this;
  2908. // unsubscribe from the dataTable
  2909. if (this.dataSet) {
  2910. this.dataSet.off('*', this._onChange);
  2911. }
  2912. if (rawData === undefined)
  2913. return;
  2914. if (Array.isArray(rawData)) {
  2915. rawData = new DataSet(rawData);
  2916. }
  2917. var data;
  2918. if (rawData instanceof DataSet || rawData instanceof DataView) {
  2919. data = rawData.get();
  2920. }
  2921. else {
  2922. throw new Error('Array, DataSet, or DataView expected');
  2923. }
  2924. if (data.length == 0)
  2925. return;
  2926. this.dataSet = rawData;
  2927. this.dataTable = data;
  2928. // subscribe to changes in the dataset
  2929. this._onChange = function () {
  2930. me.setData(me.dataSet);
  2931. };
  2932. this.dataSet.on('*', this._onChange);
  2933. // _determineColumnIndexes
  2934. // getNumberOfRows (points)
  2935. // getNumberOfColumns (x,y,z,v,t,t1,t2...)
  2936. // getDistinctValues (unique values?)
  2937. // getColumnRange
  2938. // determine the location of x,y,z,value,filter columns
  2939. this.colX = 'x';
  2940. this.colY = 'y';
  2941. this.colZ = 'z';
  2942. this.colValue = 'style';
  2943. this.colFilter = 'filter';
  2944. // check if a filter column is provided
  2945. if (data[0].hasOwnProperty('filter')) {
  2946. if (this.dataFilter === undefined) {
  2947. this.dataFilter = new Filter(rawData, this.colFilter, this);
  2948. this.dataFilter.setOnLoadCallback(function() {me.redraw();});
  2949. }
  2950. }
  2951. var withBars = this.style == Graph3d.STYLE.BAR ||
  2952. this.style == Graph3d.STYLE.BARCOLOR ||
  2953. this.style == Graph3d.STYLE.BARSIZE;
  2954. // determine barWidth from data
  2955. if (withBars) {
  2956. if (this.defaultXBarWidth !== undefined) {
  2957. this.xBarWidth = this.defaultXBarWidth;
  2958. }
  2959. else {
  2960. var dataX = this.getDistinctValues(data,this.colX);
  2961. this.xBarWidth = (dataX[1] - dataX[0]) || 1;
  2962. }
  2963. if (this.defaultYBarWidth !== undefined) {
  2964. this.yBarWidth = this.defaultYBarWidth;
  2965. }
  2966. else {
  2967. var dataY = this.getDistinctValues(data,this.colY);
  2968. this.yBarWidth = (dataY[1] - dataY[0]) || 1;
  2969. }
  2970. }
  2971. // calculate minimums and maximums
  2972. var xRange = this.getColumnRange(data,this.colX);
  2973. if (withBars) {
  2974. xRange.min -= this.xBarWidth / 2;
  2975. xRange.max += this.xBarWidth / 2;
  2976. }
  2977. this.xMin = (this.defaultXMin !== undefined) ? this.defaultXMin : xRange.min;
  2978. this.xMax = (this.defaultXMax !== undefined) ? this.defaultXMax : xRange.max;
  2979. if (this.xMax <= this.xMin) this.xMax = this.xMin + 1;
  2980. this.xStep = (this.defaultXStep !== undefined) ? this.defaultXStep : (this.xMax-this.xMin)/5;
  2981. var yRange = this.getColumnRange(data,this.colY);
  2982. if (withBars) {
  2983. yRange.min -= this.yBarWidth / 2;
  2984. yRange.max += this.yBarWidth / 2;
  2985. }
  2986. this.yMin = (this.defaultYMin !== undefined) ? this.defaultYMin : yRange.min;
  2987. this.yMax = (this.defaultYMax !== undefined) ? this.defaultYMax : yRange.max;
  2988. if (this.yMax <= this.yMin) this.yMax = this.yMin + 1;
  2989. this.yStep = (this.defaultYStep !== undefined) ? this.defaultYStep : (this.yMax-this.yMin)/5;
  2990. var zRange = this.getColumnRange(data,this.colZ);
  2991. this.zMin = (this.defaultZMin !== undefined) ? this.defaultZMin : zRange.min;
  2992. this.zMax = (this.defaultZMax !== undefined) ? this.defaultZMax : zRange.max;
  2993. if (this.zMax <= this.zMin) this.zMax = this.zMin + 1;
  2994. this.zStep = (this.defaultZStep !== undefined) ? this.defaultZStep : (this.zMax-this.zMin)/5;
  2995. if (this.colValue !== undefined) {
  2996. var valueRange = this.getColumnRange(data,this.colValue);
  2997. this.valueMin = (this.defaultValueMin !== undefined) ? this.defaultValueMin : valueRange.min;
  2998. this.valueMax = (this.defaultValueMax !== undefined) ? this.defaultValueMax : valueRange.max;
  2999. if (this.valueMax <= this.valueMin) this.valueMax = this.valueMin + 1;
  3000. }
  3001. // set the scale dependent on the ranges.
  3002. this._setScale();
  3003. };
  3004. /**
  3005. * Filter the data based on the current filter
  3006. * @param {Array} data
  3007. * @return {Array} dataPoints Array with point objects which can be drawn on screen
  3008. */
  3009. Graph3d.prototype._getDataPoints = function (data) {
  3010. // TODO: store the created matrix dataPoints in the filters instead of reloading each time
  3011. var x, y, i, z, obj, point;
  3012. var dataPoints = [];
  3013. if (this.style === Graph3d.STYLE.GRID ||
  3014. this.style === Graph3d.STYLE.SURFACE) {
  3015. // copy all values from the google data table to a matrix
  3016. // the provided values are supposed to form a grid of (x,y) positions
  3017. // create two lists with all present x and y values
  3018. var dataX = [];
  3019. var dataY = [];
  3020. for (i = 0; i < this.getNumberOfRows(data); i++) {
  3021. x = data[i][this.colX] || 0;
  3022. y = data[i][this.colY] || 0;
  3023. if (dataX.indexOf(x) === -1) {
  3024. dataX.push(x);
  3025. }
  3026. if (dataY.indexOf(y) === -1) {
  3027. dataY.push(y);
  3028. }
  3029. }
  3030. function sortNumber(a, b) {
  3031. return a - b;
  3032. }
  3033. dataX.sort(sortNumber);
  3034. dataY.sort(sortNumber);
  3035. // create a grid, a 2d matrix, with all values.
  3036. var dataMatrix = []; // temporary data matrix
  3037. for (i = 0; i < data.length; i++) {
  3038. x = data[i][this.colX] || 0;
  3039. y = data[i][this.colY] || 0;
  3040. z = data[i][this.colZ] || 0;
  3041. var xIndex = dataX.indexOf(x); // TODO: implement Array().indexOf() for Internet Explorer
  3042. var yIndex = dataY.indexOf(y);
  3043. if (dataMatrix[xIndex] === undefined) {
  3044. dataMatrix[xIndex] = [];
  3045. }
  3046. var point3d = new Point3d();
  3047. point3d.x = x;
  3048. point3d.y = y;
  3049. point3d.z = z;
  3050. obj = {};
  3051. obj.point = point3d;
  3052. obj.trans = undefined;
  3053. obj.screen = undefined;
  3054. obj.bottom = new Point3d(x, y, this.zMin);
  3055. dataMatrix[xIndex][yIndex] = obj;
  3056. dataPoints.push(obj);
  3057. }
  3058. // fill in the pointers to the neighbors.
  3059. for (x = 0; x < dataMatrix.length; x++) {
  3060. for (y = 0; y < dataMatrix[x].length; y++) {
  3061. if (dataMatrix[x][y]) {
  3062. dataMatrix[x][y].pointRight = (x < dataMatrix.length-1) ? dataMatrix[x+1][y] : undefined;
  3063. dataMatrix[x][y].pointTop = (y < dataMatrix[x].length-1) ? dataMatrix[x][y+1] : undefined;
  3064. dataMatrix[x][y].pointCross =
  3065. (x < dataMatrix.length-1 && y < dataMatrix[x].length-1) ?
  3066. dataMatrix[x+1][y+1] :
  3067. undefined;
  3068. }
  3069. }
  3070. }
  3071. }
  3072. else { // 'dot', 'dot-line', etc.
  3073. // copy all values from the google data table to a list with Point3d objects
  3074. for (i = 0; i < data.length; i++) {
  3075. point = new Point3d();
  3076. point.x = data[i][this.colX] || 0;
  3077. point.y = data[i][this.colY] || 0;
  3078. point.z = data[i][this.colZ] || 0;
  3079. if (this.colValue !== undefined) {
  3080. point.value = data[i][this.colValue] || 0;
  3081. }
  3082. obj = {};
  3083. obj.point = point;
  3084. obj.bottom = new Point3d(point.x, point.y, this.zMin);
  3085. obj.trans = undefined;
  3086. obj.screen = undefined;
  3087. dataPoints.push(obj);
  3088. }
  3089. }
  3090. return dataPoints;
  3091. };
  3092. /**
  3093. * Create the main frame for the Graph3d.
  3094. * This function is executed once when a Graph3d object is created. The frame
  3095. * contains a canvas, and this canvas contains all objects like the axis and
  3096. * nodes.
  3097. */
  3098. Graph3d.prototype.create = function () {
  3099. // remove all elements from the container element.
  3100. while (this.containerElement.hasChildNodes()) {
  3101. this.containerElement.removeChild(this.containerElement.firstChild);
  3102. }
  3103. this.frame = document.createElement('div');
  3104. this.frame.style.position = 'relative';
  3105. this.frame.style.overflow = 'hidden';
  3106. // create the graph canvas (HTML canvas element)
  3107. this.frame.canvas = document.createElement( 'canvas' );
  3108. this.frame.canvas.style.position = 'relative';
  3109. this.frame.appendChild(this.frame.canvas);
  3110. //if (!this.frame.canvas.getContext) {
  3111. {
  3112. var noCanvas = document.createElement( 'DIV' );
  3113. noCanvas.style.color = 'red';
  3114. noCanvas.style.fontWeight = 'bold' ;
  3115. noCanvas.style.padding = '10px';
  3116. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  3117. this.frame.canvas.appendChild(noCanvas);
  3118. }
  3119. this.frame.filter = document.createElement( 'div' );
  3120. this.frame.filter.style.position = 'absolute';
  3121. this.frame.filter.style.bottom = '0px';
  3122. this.frame.filter.style.left = '0px';
  3123. this.frame.filter.style.width = '100%';
  3124. this.frame.appendChild(this.frame.filter);
  3125. // add event listeners to handle moving and zooming the contents
  3126. var me = this;
  3127. var onmousedown = function (event) {me._onMouseDown(event);};
  3128. var ontouchstart = function (event) {me._onTouchStart(event);};
  3129. var onmousewheel = function (event) {me._onWheel(event);};
  3130. var ontooltip = function (event) {me._onTooltip(event);};
  3131. // TODO: these events are never cleaned up... can give a 'memory leakage'
  3132. util.addEventListener(this.frame.canvas, 'keydown', onkeydown);
  3133. util.addEventListener(this.frame.canvas, 'mousedown', onmousedown);
  3134. util.addEventListener(this.frame.canvas, 'touchstart', ontouchstart);
  3135. util.addEventListener(this.frame.canvas, 'mousewheel', onmousewheel);
  3136. util.addEventListener(this.frame.canvas, 'mousemove', ontooltip);
  3137. // add the new graph to the container element
  3138. this.containerElement.appendChild(this.frame);
  3139. };
  3140. /**
  3141. * Set a new size for the graph
  3142. * @param {string} width Width in pixels or percentage (for example '800px'
  3143. * or '50%')
  3144. * @param {string} height Height in pixels or percentage (for example '400px'
  3145. * or '30%')
  3146. */
  3147. Graph3d.prototype.setSize = function(width, height) {
  3148. this.frame.style.width = width;
  3149. this.frame.style.height = height;
  3150. this._resizeCanvas();
  3151. };
  3152. /**
  3153. * Resize the canvas to the current size of the frame
  3154. */
  3155. Graph3d.prototype._resizeCanvas = function() {
  3156. this.frame.canvas.style.width = '100%';
  3157. this.frame.canvas.style.height = '100%';
  3158. this.frame.canvas.width = this.frame.canvas.clientWidth;
  3159. this.frame.canvas.height = this.frame.canvas.clientHeight;
  3160. // adjust with for margin
  3161. this.frame.filter.style.width = (this.frame.canvas.clientWidth - 2 * 10) + 'px';
  3162. };
  3163. /**
  3164. * Start animation
  3165. */
  3166. Graph3d.prototype.animationStart = function() {
  3167. if (!this.frame.filter || !this.frame.filter.slider)
  3168. throw 'No animation available';
  3169. this.frame.filter.slider.play();
  3170. };
  3171. /**
  3172. * Stop animation
  3173. */
  3174. Graph3d.prototype.animationStop = function() {
  3175. if (!this.frame.filter || !this.frame.filter.slider) return;
  3176. this.frame.filter.slider.stop();
  3177. };
  3178. /**
  3179. * Resize the center position based on the current values in this.defaultXCenter
  3180. * and this.defaultYCenter (which are strings with a percentage or a value
  3181. * in pixels). The center positions are the variables this.xCenter
  3182. * and this.yCenter
  3183. */
  3184. Graph3d.prototype._resizeCenter = function() {
  3185. // calculate the horizontal center position
  3186. if (this.defaultXCenter.charAt(this.defaultXCenter.length-1) === '%') {
  3187. this.xcenter =
  3188. parseFloat(this.defaultXCenter) / 100 *
  3189. this.frame.canvas.clientWidth;
  3190. }
  3191. else {
  3192. this.xcenter = parseFloat(this.defaultXCenter); // supposed to be in px
  3193. }
  3194. // calculate the vertical center position
  3195. if (this.defaultYCenter.charAt(this.defaultYCenter.length-1) === '%') {
  3196. this.ycenter =
  3197. parseFloat(this.defaultYCenter) / 100 *
  3198. (this.frame.canvas.clientHeight - this.frame.filter.clientHeight);
  3199. }
  3200. else {
  3201. this.ycenter = parseFloat(this.defaultYCenter); // supposed to be in px
  3202. }
  3203. };
  3204. /**
  3205. * Set the rotation and distance of the camera
  3206. * @param {Object} pos An object with the camera position. The object
  3207. * contains three parameters:
  3208. * - horizontal {Number}
  3209. * The horizontal rotation, between 0 and 2*PI.
  3210. * Optional, can be left undefined.
  3211. * - vertical {Number}
  3212. * The vertical rotation, between 0 and 0.5*PI
  3213. * if vertical=0.5*PI, the graph is shown from the
  3214. * top. Optional, can be left undefined.
  3215. * - distance {Number}
  3216. * The (normalized) distance of the camera to the
  3217. * center of the graph, a value between 0.71 and 5.0.
  3218. * Optional, can be left undefined.
  3219. */
  3220. Graph3d.prototype.setCameraPosition = function(pos) {
  3221. if (pos === undefined) {
  3222. return;
  3223. }
  3224. if (pos.horizontal !== undefined && pos.vertical !== undefined) {
  3225. this.camera.setArmRotation(pos.horizontal, pos.vertical);
  3226. }
  3227. if (pos.distance !== undefined) {
  3228. this.camera.setArmLength(pos.distance);
  3229. }
  3230. this.redraw();
  3231. };
  3232. /**
  3233. * Retrieve the current camera rotation
  3234. * @return {object} An object with parameters horizontal, vertical, and
  3235. * distance
  3236. */
  3237. Graph3d.prototype.getCameraPosition = function() {
  3238. var pos = this.camera.getArmRotation();
  3239. pos.distance = this.camera.getArmLength();
  3240. return pos;
  3241. };
  3242. /**
  3243. * Load data into the 3D Graph
  3244. */
  3245. Graph3d.prototype._readData = function(data) {
  3246. // read the data
  3247. this._dataInitialize(data, this.style);
  3248. if (this.dataFilter) {
  3249. // apply filtering
  3250. this.dataPoints = this.dataFilter._getDataPoints();
  3251. }
  3252. else {
  3253. // no filtering. load all data
  3254. this.dataPoints = this._getDataPoints(this.dataTable);
  3255. }
  3256. // draw the filter
  3257. this._redrawFilter();
  3258. };
  3259. /**
  3260. * Replace the dataset of the Graph3d
  3261. * @param {Array | DataSet | DataView} data
  3262. */
  3263. Graph3d.prototype.setData = function (data) {
  3264. this._readData(data);
  3265. this.redraw();
  3266. // start animation when option is true
  3267. if (this.animationAutoStart && this.dataFilter) {
  3268. this.animationStart();
  3269. }
  3270. };
  3271. /**
  3272. * Update the options. Options will be merged with current options
  3273. * @param {Object} options
  3274. */
  3275. Graph3d.prototype.setOptions = function (options) {
  3276. var cameraPosition = undefined;
  3277. this.animationStop();
  3278. if (options !== undefined) {
  3279. // retrieve parameter values
  3280. if (options.width !== undefined) this.width = options.width;
  3281. if (options.height !== undefined) this.height = options.height;
  3282. if (options.xCenter !== undefined) this.defaultXCenter = options.xCenter;
  3283. if (options.yCenter !== undefined) this.defaultYCenter = options.yCenter;
  3284. if (options.filterLabel !== undefined) this.filterLabel = options.filterLabel;
  3285. if (options.legendLabel !== undefined) this.legendLabel = options.legendLabel;
  3286. if (options.xLabel !== undefined) this.xLabel = options.xLabel;
  3287. if (options.yLabel !== undefined) this.yLabel = options.yLabel;
  3288. if (options.zLabel !== undefined) this.zLabel = options.zLabel;
  3289. if (options.style !== undefined) {
  3290. var styleNumber = this._getStyleNumber(options.style);
  3291. if (styleNumber !== -1) {
  3292. this.style = styleNumber;
  3293. }
  3294. }
  3295. if (options.showGrid !== undefined) this.showGrid = options.showGrid;
  3296. if (options.showPerspective !== undefined) this.showPerspective = options.showPerspective;
  3297. if (options.showShadow !== undefined) this.showShadow = options.showShadow;
  3298. if (options.tooltip !== undefined) this.showTooltip = options.tooltip;
  3299. if (options.showAnimationControls !== undefined) this.showAnimationControls = options.showAnimationControls;
  3300. if (options.keepAspectRatio !== undefined) this.keepAspectRatio = options.keepAspectRatio;
  3301. if (options.verticalRatio !== undefined) this.verticalRatio = options.verticalRatio;
  3302. if (options.animationInterval !== undefined) this.animationInterval = options.animationInterval;
  3303. if (options.animationPreload !== undefined) this.animationPreload = options.animationPreload;
  3304. if (options.animationAutoStart !== undefined)this.animationAutoStart = options.animationAutoStart;
  3305. if (options.xBarWidth !== undefined) this.defaultXBarWidth = options.xBarWidth;
  3306. if (options.yBarWidth !== undefined) this.defaultYBarWidth = options.yBarWidth;
  3307. if (options.xMin !== undefined) this.defaultXMin = options.xMin;
  3308. if (options.xStep !== undefined) this.defaultXStep = options.xStep;
  3309. if (options.xMax !== undefined) this.defaultXMax = options.xMax;
  3310. if (options.yMin !== undefined) this.defaultYMin = options.yMin;
  3311. if (options.yStep !== undefined) this.defaultYStep = options.yStep;
  3312. if (options.yMax !== undefined) this.defaultYMax = options.yMax;
  3313. if (options.zMin !== undefined) this.defaultZMin = options.zMin;
  3314. if (options.zStep !== undefined) this.defaultZStep = options.zStep;
  3315. if (options.zMax !== undefined) this.defaultZMax = options.zMax;
  3316. if (options.valueMin !== undefined) this.defaultValueMin = options.valueMin;
  3317. if (options.valueMax !== undefined) this.defaultValueMax = options.valueMax;
  3318. if (options.cameraPosition !== undefined) cameraPosition = options.cameraPosition;
  3319. if (cameraPosition !== undefined) {
  3320. this.camera.setArmRotation(cameraPosition.horizontal, cameraPosition.vertical);
  3321. this.camera.setArmLength(cameraPosition.distance);
  3322. }
  3323. else {
  3324. this.camera.setArmRotation(1.0, 0.5);
  3325. this.camera.setArmLength(1.7);
  3326. }
  3327. }
  3328. this._setBackgroundColor(options && options.backgroundColor);
  3329. this.setSize(this.width, this.height);
  3330. // re-load the data
  3331. if (this.dataTable) {
  3332. this.setData(this.dataTable);
  3333. }
  3334. // start animation when option is true
  3335. if (this.animationAutoStart && this.dataFilter) {
  3336. this.animationStart();
  3337. }
  3338. };
  3339. /**
  3340. * Redraw the Graph.
  3341. */
  3342. Graph3d.prototype.redraw = function() {
  3343. if (this.dataPoints === undefined) {
  3344. throw 'Error: graph data not initialized';
  3345. }
  3346. this._resizeCanvas();
  3347. this._resizeCenter();
  3348. this._redrawSlider();
  3349. this._redrawClear();
  3350. this._redrawAxis();
  3351. if (this.style === Graph3d.STYLE.GRID ||
  3352. this.style === Graph3d.STYLE.SURFACE) {
  3353. this._redrawDataGrid();
  3354. }
  3355. else if (this.style === Graph3d.STYLE.LINE) {
  3356. this._redrawDataLine();
  3357. }
  3358. else if (this.style === Graph3d.STYLE.BAR ||
  3359. this.style === Graph3d.STYLE.BARCOLOR ||
  3360. this.style === Graph3d.STYLE.BARSIZE) {
  3361. this._redrawDataBar();
  3362. }
  3363. else {
  3364. // style is DOT, DOTLINE, DOTCOLOR, DOTSIZE
  3365. this._redrawDataDot();
  3366. }
  3367. this._redrawInfo();
  3368. this._redrawLegend();
  3369. };
  3370. /**
  3371. * Clear the canvas before redrawing
  3372. */
  3373. Graph3d.prototype._redrawClear = function() {
  3374. var canvas = this.frame.canvas;
  3375. var ctx = canvas.getContext('2d');
  3376. ctx.clearRect(0, 0, canvas.width, canvas.height);
  3377. };
  3378. /**
  3379. * Redraw the legend showing the colors
  3380. */
  3381. Graph3d.prototype._redrawLegend = function() {
  3382. var y;
  3383. if (this.style === Graph3d.STYLE.DOTCOLOR ||
  3384. this.style === Graph3d.STYLE.DOTSIZE) {
  3385. var dotSize = this.frame.clientWidth * 0.02;
  3386. var widthMin, widthMax;
  3387. if (this.style === Graph3d.STYLE.DOTSIZE) {
  3388. widthMin = dotSize / 2; // px
  3389. widthMax = dotSize / 2 + dotSize * 2; // Todo: put this in one function
  3390. }
  3391. else {
  3392. widthMin = 20; // px
  3393. widthMax = 20; // px
  3394. }
  3395. var height = Math.max(this.frame.clientHeight * 0.25, 100);
  3396. var top = this.margin;
  3397. var right = this.frame.clientWidth - this.margin;
  3398. var left = right - widthMax;
  3399. var bottom = top + height;
  3400. }
  3401. var canvas = this.frame.canvas;
  3402. var ctx = canvas.getContext('2d');
  3403. ctx.lineWidth = 1;
  3404. ctx.font = '14px arial'; // TODO: put in options
  3405. if (this.style === Graph3d.STYLE.DOTCOLOR) {
  3406. // draw the color bar
  3407. var ymin = 0;
  3408. var ymax = height; // Todo: make height customizable
  3409. for (y = ymin; y < ymax; y++) {
  3410. var f = (y - ymin) / (ymax - ymin);
  3411. //var width = (dotSize / 2 + (1-f) * dotSize * 2); // Todo: put this in one function
  3412. var hue = f * 240;
  3413. var color = this._hsv2rgb(hue, 1, 1);
  3414. ctx.strokeStyle = color;
  3415. ctx.beginPath();
  3416. ctx.moveTo(left, top + y);
  3417. ctx.lineTo(right, top + y);
  3418. ctx.stroke();
  3419. }
  3420. ctx.strokeStyle = this.colorAxis;
  3421. ctx.strokeRect(left, top, widthMax, height);
  3422. }
  3423. if (this.style === Graph3d.STYLE.DOTSIZE) {
  3424. // draw border around color bar
  3425. ctx.strokeStyle = this.colorAxis;
  3426. ctx.fillStyle = this.colorDot;
  3427. ctx.beginPath();
  3428. ctx.moveTo(left, top);
  3429. ctx.lineTo(right, top);
  3430. ctx.lineTo(right - widthMax + widthMin, bottom);
  3431. ctx.lineTo(left, bottom);
  3432. ctx.closePath();
  3433. ctx.fill();
  3434. ctx.stroke();
  3435. }
  3436. if (this.style === Graph3d.STYLE.DOTCOLOR ||
  3437. this.style === Graph3d.STYLE.DOTSIZE) {
  3438. // print values along the color bar
  3439. var gridLineLen = 5; // px
  3440. var step = new StepNumber(this.valueMin, this.valueMax, (this.valueMax-this.valueMin)/5, true);
  3441. step.start();
  3442. if (step.getCurrent() < this.valueMin) {
  3443. step.next();
  3444. }
  3445. while (!step.end()) {
  3446. y = bottom - (step.getCurrent() - this.valueMin) / (this.valueMax - this.valueMin) * height;
  3447. ctx.beginPath();
  3448. ctx.moveTo(left - gridLineLen, y);
  3449. ctx.lineTo(left, y);
  3450. ctx.stroke();
  3451. ctx.textAlign = 'right';
  3452. ctx.textBaseline = 'middle';
  3453. ctx.fillStyle = this.colorAxis;
  3454. ctx.fillText(step.getCurrent(), left - 2 * gridLineLen, y);
  3455. step.next();
  3456. }
  3457. ctx.textAlign = 'right';
  3458. ctx.textBaseline = 'top';
  3459. var label = this.legendLabel;
  3460. ctx.fillText(label, right, bottom + this.margin);
  3461. }
  3462. };
  3463. /**
  3464. * Redraw the filter
  3465. */
  3466. Graph3d.prototype._redrawFilter = function() {
  3467. this.frame.filter.innerHTML = '';
  3468. if (this.dataFilter) {
  3469. var options = {
  3470. 'visible': this.showAnimationControls
  3471. };
  3472. var slider = new Slider(this.frame.filter, options);
  3473. this.frame.filter.slider = slider;
  3474. // TODO: css here is not nice here...
  3475. this.frame.filter.style.padding = '10px';
  3476. //this.frame.filter.style.backgroundColor = '#EFEFEF';
  3477. slider.setValues(this.dataFilter.values);
  3478. slider.setPlayInterval(this.animationInterval);
  3479. // create an event handler
  3480. var me = this;
  3481. var onchange = function () {
  3482. var index = slider.getIndex();
  3483. me.dataFilter.selectValue(index);
  3484. me.dataPoints = me.dataFilter._getDataPoints();
  3485. me.redraw();
  3486. };
  3487. slider.setOnChangeCallback(onchange);
  3488. }
  3489. else {
  3490. this.frame.filter.slider = undefined;
  3491. }
  3492. };
  3493. /**
  3494. * Redraw the slider
  3495. */
  3496. Graph3d.prototype._redrawSlider = function() {
  3497. if ( this.frame.filter.slider !== undefined) {
  3498. this.frame.filter.slider.redraw();
  3499. }
  3500. };
  3501. /**
  3502. * Redraw common information
  3503. */
  3504. Graph3d.prototype._redrawInfo = function() {
  3505. if (this.dataFilter) {
  3506. var canvas = this.frame.canvas;
  3507. var ctx = canvas.getContext('2d');
  3508. ctx.font = '14px arial'; // TODO: put in options
  3509. ctx.lineStyle = 'gray';
  3510. ctx.fillStyle = 'gray';
  3511. ctx.textAlign = 'left';
  3512. ctx.textBaseline = 'top';
  3513. var x = this.margin;
  3514. var y = this.margin;
  3515. ctx.fillText(this.dataFilter.getLabel() + ': ' + this.dataFilter.getSelectedValue(), x, y);
  3516. }
  3517. };
  3518. /**
  3519. * Redraw the axis
  3520. */
  3521. Graph3d.prototype._redrawAxis = function() {
  3522. var canvas = this.frame.canvas,
  3523. ctx = canvas.getContext('2d'),
  3524. from, to, step, prettyStep,
  3525. text, xText, yText, zText,
  3526. offset, xOffset, yOffset,
  3527. xMin2d, xMax2d;
  3528. // TODO: get the actual rendered style of the containerElement
  3529. //ctx.font = this.containerElement.style.font;
  3530. ctx.font = 24 / this.camera.getArmLength() + 'px arial';
  3531. // calculate the length for the short grid lines
  3532. var gridLenX = 0.025 / this.scale.x;
  3533. var gridLenY = 0.025 / this.scale.y;
  3534. var textMargin = 5 / this.camera.getArmLength(); // px
  3535. var armAngle = this.camera.getArmRotation().horizontal;
  3536. // draw x-grid lines
  3537. ctx.lineWidth = 1;
  3538. prettyStep = (this.defaultXStep === undefined);
  3539. step = new StepNumber(this.xMin, this.xMax, this.xStep, prettyStep);
  3540. step.start();
  3541. if (step.getCurrent() < this.xMin) {
  3542. step.next();
  3543. }
  3544. while (!step.end()) {
  3545. var x = step.getCurrent();
  3546. if (this.showGrid) {
  3547. from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin));
  3548. to = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin));
  3549. ctx.strokeStyle = this.colorGrid;
  3550. ctx.beginPath();
  3551. ctx.moveTo(from.x, from.y);
  3552. ctx.lineTo(to.x, to.y);
  3553. ctx.stroke();
  3554. }
  3555. else {
  3556. from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin));
  3557. to = this._convert3Dto2D(new Point3d(x, this.yMin+gridLenX, this.zMin));
  3558. ctx.strokeStyle = this.colorAxis;
  3559. ctx.beginPath();
  3560. ctx.moveTo(from.x, from.y);
  3561. ctx.lineTo(to.x, to.y);
  3562. ctx.stroke();
  3563. from = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin));
  3564. to = this._convert3Dto2D(new Point3d(x, this.yMax-gridLenX, this.zMin));
  3565. ctx.strokeStyle = this.colorAxis;
  3566. ctx.beginPath();
  3567. ctx.moveTo(from.x, from.y);
  3568. ctx.lineTo(to.x, to.y);
  3569. ctx.stroke();
  3570. }
  3571. yText = (Math.cos(armAngle) > 0) ? this.yMin : this.yMax;
  3572. text = this._convert3Dto2D(new Point3d(x, yText, this.zMin));
  3573. if (Math.cos(armAngle * 2) > 0) {
  3574. ctx.textAlign = 'center';
  3575. ctx.textBaseline = 'top';
  3576. text.y += textMargin;
  3577. }
  3578. else if (Math.sin(armAngle * 2) < 0){
  3579. ctx.textAlign = 'right';
  3580. ctx.textBaseline = 'middle';
  3581. }
  3582. else {
  3583. ctx.textAlign = 'left';
  3584. ctx.textBaseline = 'middle';
  3585. }
  3586. ctx.fillStyle = this.colorAxis;
  3587. ctx.fillText(' ' + step.getCurrent() + ' ', text.x, text.y);
  3588. step.next();
  3589. }
  3590. // draw y-grid lines
  3591. ctx.lineWidth = 1;
  3592. prettyStep = (this.defaultYStep === undefined);
  3593. step = new StepNumber(this.yMin, this.yMax, this.yStep, prettyStep);
  3594. step.start();
  3595. if (step.getCurrent() < this.yMin) {
  3596. step.next();
  3597. }
  3598. while (!step.end()) {
  3599. if (this.showGrid) {
  3600. from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin));
  3601. to = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin));
  3602. ctx.strokeStyle = this.colorGrid;
  3603. ctx.beginPath();
  3604. ctx.moveTo(from.x, from.y);
  3605. ctx.lineTo(to.x, to.y);
  3606. ctx.stroke();
  3607. }
  3608. else {
  3609. from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin));
  3610. to = this._convert3Dto2D(new Point3d(this.xMin+gridLenY, step.getCurrent(), this.zMin));
  3611. ctx.strokeStyle = this.colorAxis;
  3612. ctx.beginPath();
  3613. ctx.moveTo(from.x, from.y);
  3614. ctx.lineTo(to.x, to.y);
  3615. ctx.stroke();
  3616. from = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin));
  3617. to = this._convert3Dto2D(new Point3d(this.xMax-gridLenY, step.getCurrent(), this.zMin));
  3618. ctx.strokeStyle = this.colorAxis;
  3619. ctx.beginPath();
  3620. ctx.moveTo(from.x, from.y);
  3621. ctx.lineTo(to.x, to.y);
  3622. ctx.stroke();
  3623. }
  3624. xText = (Math.sin(armAngle ) > 0) ? this.xMin : this.xMax;
  3625. text = this._convert3Dto2D(new Point3d(xText, step.getCurrent(), this.zMin));
  3626. if (Math.cos(armAngle * 2) < 0) {
  3627. ctx.textAlign = 'center';
  3628. ctx.textBaseline = 'top';
  3629. text.y += textMargin;
  3630. }
  3631. else if (Math.sin(armAngle * 2) > 0){
  3632. ctx.textAlign = 'right';
  3633. ctx.textBaseline = 'middle';
  3634. }
  3635. else {
  3636. ctx.textAlign = 'left';
  3637. ctx.textBaseline = 'middle';
  3638. }
  3639. ctx.fillStyle = this.colorAxis;
  3640. ctx.fillText(' ' + step.getCurrent() + ' ', text.x, text.y);
  3641. step.next();
  3642. }
  3643. // draw z-grid lines and axis
  3644. ctx.lineWidth = 1;
  3645. prettyStep = (this.defaultZStep === undefined);
  3646. step = new StepNumber(this.zMin, this.zMax, this.zStep, prettyStep);
  3647. step.start();
  3648. if (step.getCurrent() < this.zMin) {
  3649. step.next();
  3650. }
  3651. xText = (Math.cos(armAngle ) > 0) ? this.xMin : this.xMax;
  3652. yText = (Math.sin(armAngle ) < 0) ? this.yMin : this.yMax;
  3653. while (!step.end()) {
  3654. // TODO: make z-grid lines really 3d?
  3655. from = this._convert3Dto2D(new Point3d(xText, yText, step.getCurrent()));
  3656. ctx.strokeStyle = this.colorAxis;
  3657. ctx.beginPath();
  3658. ctx.moveTo(from.x, from.y);
  3659. ctx.lineTo(from.x - textMargin, from.y);
  3660. ctx.stroke();
  3661. ctx.textAlign = 'right';
  3662. ctx.textBaseline = 'middle';
  3663. ctx.fillStyle = this.colorAxis;
  3664. ctx.fillText(step.getCurrent() + ' ', from.x - 5, from.y);
  3665. step.next();
  3666. }
  3667. ctx.lineWidth = 1;
  3668. from = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  3669. to = this._convert3Dto2D(new Point3d(xText, yText, this.zMax));
  3670. ctx.strokeStyle = this.colorAxis;
  3671. ctx.beginPath();
  3672. ctx.moveTo(from.x, from.y);
  3673. ctx.lineTo(to.x, to.y);
  3674. ctx.stroke();
  3675. // draw x-axis
  3676. ctx.lineWidth = 1;
  3677. // line at yMin
  3678. xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin));
  3679. xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin));
  3680. ctx.strokeStyle = this.colorAxis;
  3681. ctx.beginPath();
  3682. ctx.moveTo(xMin2d.x, xMin2d.y);
  3683. ctx.lineTo(xMax2d.x, xMax2d.y);
  3684. ctx.stroke();
  3685. // line at ymax
  3686. xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin));
  3687. xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin));
  3688. ctx.strokeStyle = this.colorAxis;
  3689. ctx.beginPath();
  3690. ctx.moveTo(xMin2d.x, xMin2d.y);
  3691. ctx.lineTo(xMax2d.x, xMax2d.y);
  3692. ctx.stroke();
  3693. // draw y-axis
  3694. ctx.lineWidth = 1;
  3695. // line at xMin
  3696. from = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin));
  3697. to = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin));
  3698. ctx.strokeStyle = this.colorAxis;
  3699. ctx.beginPath();
  3700. ctx.moveTo(from.x, from.y);
  3701. ctx.lineTo(to.x, to.y);
  3702. ctx.stroke();
  3703. // line at xMax
  3704. from = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin));
  3705. to = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin));
  3706. ctx.strokeStyle = this.colorAxis;
  3707. ctx.beginPath();
  3708. ctx.moveTo(from.x, from.y);
  3709. ctx.lineTo(to.x, to.y);
  3710. ctx.stroke();
  3711. // draw x-label
  3712. var xLabel = this.xLabel;
  3713. if (xLabel.length > 0) {
  3714. yOffset = 0.1 / this.scale.y;
  3715. xText = (this.xMin + this.xMax) / 2;
  3716. yText = (Math.cos(armAngle) > 0) ? this.yMin - yOffset: this.yMax + yOffset;
  3717. text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  3718. if (Math.cos(armAngle * 2) > 0) {
  3719. ctx.textAlign = 'center';
  3720. ctx.textBaseline = 'top';
  3721. }
  3722. else if (Math.sin(armAngle * 2) < 0){
  3723. ctx.textAlign = 'right';
  3724. ctx.textBaseline = 'middle';
  3725. }
  3726. else {
  3727. ctx.textAlign = 'left';
  3728. ctx.textBaseline = 'middle';
  3729. }
  3730. ctx.fillStyle = this.colorAxis;
  3731. ctx.fillText(xLabel, text.x, text.y);
  3732. }
  3733. // draw y-label
  3734. var yLabel = this.yLabel;
  3735. if (yLabel.length > 0) {
  3736. xOffset = 0.1 / this.scale.x;
  3737. xText = (Math.sin(armAngle ) > 0) ? this.xMin - xOffset : this.xMax + xOffset;
  3738. yText = (this.yMin + this.yMax) / 2;
  3739. text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  3740. if (Math.cos(armAngle * 2) < 0) {
  3741. ctx.textAlign = 'center';
  3742. ctx.textBaseline = 'top';
  3743. }
  3744. else if (Math.sin(armAngle * 2) > 0){
  3745. ctx.textAlign = 'right';
  3746. ctx.textBaseline = 'middle';
  3747. }
  3748. else {
  3749. ctx.textAlign = 'left';
  3750. ctx.textBaseline = 'middle';
  3751. }
  3752. ctx.fillStyle = this.colorAxis;
  3753. ctx.fillText(yLabel, text.x, text.y);
  3754. }
  3755. // draw z-label
  3756. var zLabel = this.zLabel;
  3757. if (zLabel.length > 0) {
  3758. offset = 30; // pixels. // TODO: relate to the max width of the values on the z axis?
  3759. xText = (Math.cos(armAngle ) > 0) ? this.xMin : this.xMax;
  3760. yText = (Math.sin(armAngle ) < 0) ? this.yMin : this.yMax;
  3761. zText = (this.zMin + this.zMax) / 2;
  3762. text = this._convert3Dto2D(new Point3d(xText, yText, zText));
  3763. ctx.textAlign = 'right';
  3764. ctx.textBaseline = 'middle';
  3765. ctx.fillStyle = this.colorAxis;
  3766. ctx.fillText(zLabel, text.x - offset, text.y);
  3767. }
  3768. };
  3769. /**
  3770. * Calculate the color based on the given value.
  3771. * @param {Number} H Hue, a value be between 0 and 360
  3772. * @param {Number} S Saturation, a value between 0 and 1
  3773. * @param {Number} V Value, a value between 0 and 1
  3774. */
  3775. Graph3d.prototype._hsv2rgb = function(H, S, V) {
  3776. var R, G, B, C, Hi, X;
  3777. C = V * S;
  3778. Hi = Math.floor(H/60); // hi = 0,1,2,3,4,5
  3779. X = C * (1 - Math.abs(((H/60) % 2) - 1));
  3780. switch (Hi) {
  3781. case 0: R = C; G = X; B = 0; break;
  3782. case 1: R = X; G = C; B = 0; break;
  3783. case 2: R = 0; G = C; B = X; break;
  3784. case 3: R = 0; G = X; B = C; break;
  3785. case 4: R = X; G = 0; B = C; break;
  3786. case 5: R = C; G = 0; B = X; break;
  3787. default: R = 0; G = 0; B = 0; break;
  3788. }
  3789. return 'RGB(' + parseInt(R*255) + ',' + parseInt(G*255) + ',' + parseInt(B*255) + ')';
  3790. };
  3791. /**
  3792. * Draw all datapoints as a grid
  3793. * This function can be used when the style is 'grid'
  3794. */
  3795. Graph3d.prototype._redrawDataGrid = function() {
  3796. var canvas = this.frame.canvas,
  3797. ctx = canvas.getContext('2d'),
  3798. point, right, top, cross,
  3799. i,
  3800. topSideVisible, fillStyle, strokeStyle, lineWidth,
  3801. h, s, v, zAvg;
  3802. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  3803. return; // TODO: throw exception?
  3804. // calculate the translations and screen position of all points
  3805. for (i = 0; i < this.dataPoints.length; i++) {
  3806. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  3807. var screen = this._convertTranslationToScreen(trans);
  3808. this.dataPoints[i].trans = trans;
  3809. this.dataPoints[i].screen = screen;
  3810. // calculate the translation of the point at the bottom (needed for sorting)
  3811. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  3812. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  3813. }
  3814. // sort the points on depth of their (x,y) position (not on z)
  3815. var sortDepth = function (a, b) {
  3816. return b.dist - a.dist;
  3817. };
  3818. this.dataPoints.sort(sortDepth);
  3819. if (this.style === Graph3d.STYLE.SURFACE) {
  3820. for (i = 0; i < this.dataPoints.length; i++) {
  3821. point = this.dataPoints[i];
  3822. right = this.dataPoints[i].pointRight;
  3823. top = this.dataPoints[i].pointTop;
  3824. cross = this.dataPoints[i].pointCross;
  3825. if (point !== undefined && right !== undefined && top !== undefined && cross !== undefined) {
  3826. if (this.showGrayBottom || this.showShadow) {
  3827. // calculate the cross product of the two vectors from center
  3828. // to left and right, in order to know whether we are looking at the
  3829. // bottom or at the top side. We can also use the cross product
  3830. // for calculating light intensity
  3831. var aDiff = Point3d.subtract(cross.trans, point.trans);
  3832. var bDiff = Point3d.subtract(top.trans, right.trans);
  3833. var crossproduct = Point3d.crossProduct(aDiff, bDiff);
  3834. var len = crossproduct.length();
  3835. // FIXME: there is a bug with determining the surface side (shadow or colored)
  3836. topSideVisible = (crossproduct.z > 0);
  3837. }
  3838. else {
  3839. topSideVisible = true;
  3840. }
  3841. if (topSideVisible) {
  3842. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  3843. zAvg = (point.point.z + right.point.z + top.point.z + cross.point.z) / 4;
  3844. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  3845. s = 1; // saturation
  3846. if (this.showShadow) {
  3847. v = Math.min(1 + (crossproduct.x / len) / 2, 1); // value. TODO: scale
  3848. fillStyle = this._hsv2rgb(h, s, v);
  3849. strokeStyle = fillStyle;
  3850. }
  3851. else {
  3852. v = 1;
  3853. fillStyle = this._hsv2rgb(h, s, v);
  3854. strokeStyle = this.colorAxis;
  3855. }
  3856. }
  3857. else {
  3858. fillStyle = 'gray';
  3859. strokeStyle = this.colorAxis;
  3860. }
  3861. lineWidth = 0.5;
  3862. ctx.lineWidth = lineWidth;
  3863. ctx.fillStyle = fillStyle;
  3864. ctx.strokeStyle = strokeStyle;
  3865. ctx.beginPath();
  3866. ctx.moveTo(point.screen.x, point.screen.y);
  3867. ctx.lineTo(right.screen.x, right.screen.y);
  3868. ctx.lineTo(cross.screen.x, cross.screen.y);
  3869. ctx.lineTo(top.screen.x, top.screen.y);
  3870. ctx.closePath();
  3871. ctx.fill();
  3872. ctx.stroke();
  3873. }
  3874. }
  3875. }
  3876. else { // grid style
  3877. for (i = 0; i < this.dataPoints.length; i++) {
  3878. point = this.dataPoints[i];
  3879. right = this.dataPoints[i].pointRight;
  3880. top = this.dataPoints[i].pointTop;
  3881. if (point !== undefined) {
  3882. if (this.showPerspective) {
  3883. lineWidth = 2 / -point.trans.z;
  3884. }
  3885. else {
  3886. lineWidth = 2 * -(this.eye.z / this.camera.getArmLength());
  3887. }
  3888. }
  3889. if (point !== undefined && right !== undefined) {
  3890. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  3891. zAvg = (point.point.z + right.point.z) / 2;
  3892. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  3893. ctx.lineWidth = lineWidth;
  3894. ctx.strokeStyle = this._hsv2rgb(h, 1, 1);
  3895. ctx.beginPath();
  3896. ctx.moveTo(point.screen.x, point.screen.y);
  3897. ctx.lineTo(right.screen.x, right.screen.y);
  3898. ctx.stroke();
  3899. }
  3900. if (point !== undefined && top !== undefined) {
  3901. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  3902. zAvg = (point.point.z + top.point.z) / 2;
  3903. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  3904. ctx.lineWidth = lineWidth;
  3905. ctx.strokeStyle = this._hsv2rgb(h, 1, 1);
  3906. ctx.beginPath();
  3907. ctx.moveTo(point.screen.x, point.screen.y);
  3908. ctx.lineTo(top.screen.x, top.screen.y);
  3909. ctx.stroke();
  3910. }
  3911. }
  3912. }
  3913. };
  3914. /**
  3915. * Draw all datapoints as dots.
  3916. * This function can be used when the style is 'dot' or 'dot-line'
  3917. */
  3918. Graph3d.prototype._redrawDataDot = function() {
  3919. var canvas = this.frame.canvas;
  3920. var ctx = canvas.getContext('2d');
  3921. var i;
  3922. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  3923. return; // TODO: throw exception?
  3924. // calculate the translations of all points
  3925. for (i = 0; i < this.dataPoints.length; i++) {
  3926. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  3927. var screen = this._convertTranslationToScreen(trans);
  3928. this.dataPoints[i].trans = trans;
  3929. this.dataPoints[i].screen = screen;
  3930. // calculate the distance from the point at the bottom to the camera
  3931. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  3932. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  3933. }
  3934. // order the translated points by depth
  3935. var sortDepth = function (a, b) {
  3936. return b.dist - a.dist;
  3937. };
  3938. this.dataPoints.sort(sortDepth);
  3939. // draw the datapoints as colored circles
  3940. var dotSize = this.frame.clientWidth * 0.02; // px
  3941. for (i = 0; i < this.dataPoints.length; i++) {
  3942. var point = this.dataPoints[i];
  3943. if (this.style === Graph3d.STYLE.DOTLINE) {
  3944. // draw a vertical line from the bottom to the graph value
  3945. //var from = this._convert3Dto2D(new Point3d(point.point.x, point.point.y, this.zMin));
  3946. var from = this._convert3Dto2D(point.bottom);
  3947. ctx.lineWidth = 1;
  3948. ctx.strokeStyle = this.colorGrid;
  3949. ctx.beginPath();
  3950. ctx.moveTo(from.x, from.y);
  3951. ctx.lineTo(point.screen.x, point.screen.y);
  3952. ctx.stroke();
  3953. }
  3954. // calculate radius for the circle
  3955. var size;
  3956. if (this.style === Graph3d.STYLE.DOTSIZE) {
  3957. size = dotSize/2 + 2*dotSize * (point.point.value - this.valueMin) / (this.valueMax - this.valueMin);
  3958. }
  3959. else {
  3960. size = dotSize;
  3961. }
  3962. var radius;
  3963. if (this.showPerspective) {
  3964. radius = size / -point.trans.z;
  3965. }
  3966. else {
  3967. radius = size * -(this.eye.z / this.camera.getArmLength());
  3968. }
  3969. if (radius < 0) {
  3970. radius = 0;
  3971. }
  3972. var hue, color, borderColor;
  3973. if (this.style === Graph3d.STYLE.DOTCOLOR ) {
  3974. // calculate the color based on the value
  3975. hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240;
  3976. color = this._hsv2rgb(hue, 1, 1);
  3977. borderColor = this._hsv2rgb(hue, 1, 0.8);
  3978. }
  3979. else if (this.style === Graph3d.STYLE.DOTSIZE) {
  3980. color = this.colorDot;
  3981. borderColor = this.colorDotBorder;
  3982. }
  3983. else {
  3984. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  3985. hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  3986. color = this._hsv2rgb(hue, 1, 1);
  3987. borderColor = this._hsv2rgb(hue, 1, 0.8);
  3988. }
  3989. // draw the circle
  3990. ctx.lineWidth = 1.0;
  3991. ctx.strokeStyle = borderColor;
  3992. ctx.fillStyle = color;
  3993. ctx.beginPath();
  3994. ctx.arc(point.screen.x, point.screen.y, radius, 0, Math.PI*2, true);
  3995. ctx.fill();
  3996. ctx.stroke();
  3997. }
  3998. };
  3999. /**
  4000. * Draw all datapoints as bars.
  4001. * This function can be used when the style is 'bar', 'bar-color', or 'bar-size'
  4002. */
  4003. Graph3d.prototype._redrawDataBar = function() {
  4004. var canvas = this.frame.canvas;
  4005. var ctx = canvas.getContext('2d');
  4006. var i, j, surface, corners;
  4007. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  4008. return; // TODO: throw exception?
  4009. // calculate the translations of all points
  4010. for (i = 0; i < this.dataPoints.length; i++) {
  4011. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  4012. var screen = this._convertTranslationToScreen(trans);
  4013. this.dataPoints[i].trans = trans;
  4014. this.dataPoints[i].screen = screen;
  4015. // calculate the distance from the point at the bottom to the camera
  4016. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  4017. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  4018. }
  4019. // order the translated points by depth
  4020. var sortDepth = function (a, b) {
  4021. return b.dist - a.dist;
  4022. };
  4023. this.dataPoints.sort(sortDepth);
  4024. // draw the datapoints as bars
  4025. var xWidth = this.xBarWidth / 2;
  4026. var yWidth = this.yBarWidth / 2;
  4027. for (i = 0; i < this.dataPoints.length; i++) {
  4028. var point = this.dataPoints[i];
  4029. // determine color
  4030. var hue, color, borderColor;
  4031. if (this.style === Graph3d.STYLE.BARCOLOR ) {
  4032. // calculate the color based on the value
  4033. hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240;
  4034. color = this._hsv2rgb(hue, 1, 1);
  4035. borderColor = this._hsv2rgb(hue, 1, 0.8);
  4036. }
  4037. else if (this.style === Graph3d.STYLE.BARSIZE) {
  4038. color = this.colorDot;
  4039. borderColor = this.colorDotBorder;
  4040. }
  4041. else {
  4042. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  4043. hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  4044. color = this._hsv2rgb(hue, 1, 1);
  4045. borderColor = this._hsv2rgb(hue, 1, 0.8);
  4046. }
  4047. // calculate size for the bar
  4048. if (this.style === Graph3d.STYLE.BARSIZE) {
  4049. xWidth = (this.xBarWidth / 2) * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2);
  4050. yWidth = (this.yBarWidth / 2) * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2);
  4051. }
  4052. // calculate all corner points
  4053. var me = this;
  4054. var point3d = point.point;
  4055. var top = [
  4056. {point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, point3d.z)},
  4057. {point: new Point3d(point3d.x + xWidth, point3d.y - yWidth, point3d.z)},
  4058. {point: new Point3d(point3d.x + xWidth, point3d.y + yWidth, point3d.z)},
  4059. {point: new Point3d(point3d.x - xWidth, point3d.y + yWidth, point3d.z)}
  4060. ];
  4061. var bottom = [
  4062. {point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, this.zMin)},
  4063. {point: new Point3d(point3d.x + xWidth, point3d.y - yWidth, this.zMin)},
  4064. {point: new Point3d(point3d.x + xWidth, point3d.y + yWidth, this.zMin)},
  4065. {point: new Point3d(point3d.x - xWidth, point3d.y + yWidth, this.zMin)}
  4066. ];
  4067. // calculate screen location of the points
  4068. top.forEach(function (obj) {
  4069. obj.screen = me._convert3Dto2D(obj.point);
  4070. });
  4071. bottom.forEach(function (obj) {
  4072. obj.screen = me._convert3Dto2D(obj.point);
  4073. });
  4074. // create five sides, calculate both corner points and center points
  4075. var surfaces = [
  4076. {corners: top, center: Point3d.avg(bottom[0].point, bottom[2].point)},
  4077. {corners: [top[0], top[1], bottom[1], bottom[0]], center: Point3d.avg(bottom[1].point, bottom[0].point)},
  4078. {corners: [top[1], top[2], bottom[2], bottom[1]], center: Point3d.avg(bottom[2].point, bottom[1].point)},
  4079. {corners: [top[2], top[3], bottom[3], bottom[2]], center: Point3d.avg(bottom[3].point, bottom[2].point)},
  4080. {corners: [top[3], top[0], bottom[0], bottom[3]], center: Point3d.avg(bottom[0].point, bottom[3].point)}
  4081. ];
  4082. point.surfaces = surfaces;
  4083. // calculate the distance of each of the surface centers to the camera
  4084. for (j = 0; j < surfaces.length; j++) {
  4085. surface = surfaces[j];
  4086. var transCenter = this._convertPointToTranslation(surface.center);
  4087. surface.dist = this.showPerspective ? transCenter.length() : -transCenter.z;
  4088. // TODO: this dept calculation doesn't work 100% of the cases due to perspective,
  4089. // but the current solution is fast/simple and works in 99.9% of all cases
  4090. // the issue is visible in example 14, with graph.setCameraPosition({horizontal: 2.97, vertical: 0.5, distance: 0.9})
  4091. }
  4092. // order the surfaces by their (translated) depth
  4093. surfaces.sort(function (a, b) {
  4094. var diff = b.dist - a.dist;
  4095. if (diff) return diff;
  4096. // if equal depth, sort the top surface last
  4097. if (a.corners === top) return 1;
  4098. if (b.corners === top) return -1;
  4099. // both are equal
  4100. return 0;
  4101. });
  4102. // draw the ordered surfaces
  4103. ctx.lineWidth = 1;
  4104. ctx.strokeStyle = borderColor;
  4105. ctx.fillStyle = color;
  4106. // NOTE: we start at j=2 instead of j=0 as we don't need to draw the two surfaces at the backside
  4107. for (j = 2; j < surfaces.length; j++) {
  4108. surface = surfaces[j];
  4109. corners = surface.corners;
  4110. ctx.beginPath();
  4111. ctx.moveTo(corners[3].screen.x, corners[3].screen.y);
  4112. ctx.lineTo(corners[0].screen.x, corners[0].screen.y);
  4113. ctx.lineTo(corners[1].screen.x, corners[1].screen.y);
  4114. ctx.lineTo(corners[2].screen.x, corners[2].screen.y);
  4115. ctx.lineTo(corners[3].screen.x, corners[3].screen.y);
  4116. ctx.fill();
  4117. ctx.stroke();
  4118. }
  4119. }
  4120. };
  4121. /**
  4122. * Draw a line through all datapoints.
  4123. * This function can be used when the style is 'line'
  4124. */
  4125. Graph3d.prototype._redrawDataLine = function() {
  4126. var canvas = this.frame.canvas,
  4127. ctx = canvas.getContext('2d'),
  4128. point, i;
  4129. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  4130. return; // TODO: throw exception?
  4131. // calculate the translations of all points
  4132. for (i = 0; i < this.dataPoints.length; i++) {
  4133. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  4134. var screen = this._convertTranslationToScreen(trans);
  4135. this.dataPoints[i].trans = trans;
  4136. this.dataPoints[i].screen = screen;
  4137. }
  4138. // start the line
  4139. if (this.dataPoints.length > 0) {
  4140. point = this.dataPoints[0];
  4141. ctx.lineWidth = 1; // TODO: make customizable
  4142. ctx.strokeStyle = 'blue'; // TODO: make customizable
  4143. ctx.beginPath();
  4144. ctx.moveTo(point.screen.x, point.screen.y);
  4145. }
  4146. // draw the datapoints as colored circles
  4147. for (i = 1; i < this.dataPoints.length; i++) {
  4148. point = this.dataPoints[i];
  4149. ctx.lineTo(point.screen.x, point.screen.y);
  4150. }
  4151. // finish the line
  4152. if (this.dataPoints.length > 0) {
  4153. ctx.stroke();
  4154. }
  4155. };
  4156. /**
  4157. * Start a moving operation inside the provided parent element
  4158. * @param {Event} event The event that occurred (required for
  4159. * retrieving the mouse position)
  4160. */
  4161. Graph3d.prototype._onMouseDown = function(event) {
  4162. event = event || window.event;
  4163. // check if mouse is still down (may be up when focus is lost for example
  4164. // in an iframe)
  4165. if (this.leftButtonDown) {
  4166. this._onMouseUp(event);
  4167. }
  4168. // only react on left mouse button down
  4169. this.leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
  4170. if (!this.leftButtonDown && !this.touchDown) return;
  4171. // get mouse position (different code for IE and all other browsers)
  4172. this.startMouseX = getMouseX(event);
  4173. this.startMouseY = getMouseY(event);
  4174. this.startStart = new Date(this.start);
  4175. this.startEnd = new Date(this.end);
  4176. this.startArmRotation = this.camera.getArmRotation();
  4177. this.frame.style.cursor = 'move';
  4178. // add event listeners to handle moving the contents
  4179. // we store the function onmousemove and onmouseup in the graph, so we can
  4180. // remove the eventlisteners lateron in the function mouseUp()
  4181. var me = this;
  4182. this.onmousemove = function (event) {me._onMouseMove(event);};
  4183. this.onmouseup = function (event) {me._onMouseUp(event);};
  4184. util.addEventListener(document, 'mousemove', me.onmousemove);
  4185. util.addEventListener(document, 'mouseup', me.onmouseup);
  4186. util.preventDefault(event);
  4187. };
  4188. /**
  4189. * Perform moving operating.
  4190. * This function activated from within the funcion Graph.mouseDown().
  4191. * @param {Event} event Well, eehh, the event
  4192. */
  4193. Graph3d.prototype._onMouseMove = function (event) {
  4194. event = event || window.event;
  4195. // calculate change in mouse position
  4196. var diffX = parseFloat(getMouseX(event)) - this.startMouseX;
  4197. var diffY = parseFloat(getMouseY(event)) - this.startMouseY;
  4198. var horizontalNew = this.startArmRotation.horizontal + diffX / 200;
  4199. var verticalNew = this.startArmRotation.vertical + diffY / 200;
  4200. var snapAngle = 4; // degrees
  4201. var snapValue = Math.sin(snapAngle / 360 * 2 * Math.PI);
  4202. // snap horizontally to nice angles at 0pi, 0.5pi, 1pi, 1.5pi, etc...
  4203. // the -0.001 is to take care that the vertical axis is always drawn at the left front corner
  4204. if (Math.abs(Math.sin(horizontalNew)) < snapValue) {
  4205. horizontalNew = Math.round((horizontalNew / Math.PI)) * Math.PI - 0.001;
  4206. }
  4207. if (Math.abs(Math.cos(horizontalNew)) < snapValue) {
  4208. horizontalNew = (Math.round((horizontalNew/ Math.PI - 0.5)) + 0.5) * Math.PI - 0.001;
  4209. }
  4210. // snap vertically to nice angles
  4211. if (Math.abs(Math.sin(verticalNew)) < snapValue) {
  4212. verticalNew = Math.round((verticalNew / Math.PI)) * Math.PI;
  4213. }
  4214. if (Math.abs(Math.cos(verticalNew)) < snapValue) {
  4215. verticalNew = (Math.round((verticalNew/ Math.PI - 0.5)) + 0.5) * Math.PI;
  4216. }
  4217. this.camera.setArmRotation(horizontalNew, verticalNew);
  4218. this.redraw();
  4219. // fire a cameraPositionChange event
  4220. var parameters = this.getCameraPosition();
  4221. this.emit('cameraPositionChange', parameters);
  4222. util.preventDefault(event);
  4223. };
  4224. /**
  4225. * Stop moving operating.
  4226. * This function activated from within the funcion Graph.mouseDown().
  4227. * @param {event} event The event
  4228. */
  4229. Graph3d.prototype._onMouseUp = function (event) {
  4230. this.frame.style.cursor = 'auto';
  4231. this.leftButtonDown = false;
  4232. // remove event listeners here
  4233. util.removeEventListener(document, 'mousemove', this.onmousemove);
  4234. util.removeEventListener(document, 'mouseup', this.onmouseup);
  4235. util.preventDefault(event);
  4236. };
  4237. /**
  4238. * After having moved the mouse, a tooltip should pop up when the mouse is resting on a data point
  4239. * @param {Event} event A mouse move event
  4240. */
  4241. Graph3d.prototype._onTooltip = function (event) {
  4242. var delay = 300; // ms
  4243. var mouseX = getMouseX(event) - util.getAbsoluteLeft(this.frame);
  4244. var mouseY = getMouseY(event) - util.getAbsoluteTop(this.frame);
  4245. if (!this.showTooltip) {
  4246. return;
  4247. }
  4248. if (this.tooltipTimeout) {
  4249. clearTimeout(this.tooltipTimeout);
  4250. }
  4251. // (delayed) display of a tooltip only if no mouse button is down
  4252. if (this.leftButtonDown) {
  4253. this._hideTooltip();
  4254. return;
  4255. }
  4256. if (this.tooltip && this.tooltip.dataPoint) {
  4257. // tooltip is currently visible
  4258. var dataPoint = this._dataPointFromXY(mouseX, mouseY);
  4259. if (dataPoint !== this.tooltip.dataPoint) {
  4260. // datapoint changed
  4261. if (dataPoint) {
  4262. this._showTooltip(dataPoint);
  4263. }
  4264. else {
  4265. this._hideTooltip();
  4266. }
  4267. }
  4268. }
  4269. else {
  4270. // tooltip is currently not visible
  4271. var me = this;
  4272. this.tooltipTimeout = setTimeout(function () {
  4273. me.tooltipTimeout = null;
  4274. // show a tooltip if we have a data point
  4275. var dataPoint = me._dataPointFromXY(mouseX, mouseY);
  4276. if (dataPoint) {
  4277. me._showTooltip(dataPoint);
  4278. }
  4279. }, delay);
  4280. }
  4281. };
  4282. /**
  4283. * Event handler for touchstart event on mobile devices
  4284. */
  4285. Graph3d.prototype._onTouchStart = function(event) {
  4286. this.touchDown = true;
  4287. var me = this;
  4288. this.ontouchmove = function (event) {me._onTouchMove(event);};
  4289. this.ontouchend = function (event) {me._onTouchEnd(event);};
  4290. util.addEventListener(document, 'touchmove', me.ontouchmove);
  4291. util.addEventListener(document, 'touchend', me.ontouchend);
  4292. this._onMouseDown(event);
  4293. };
  4294. /**
  4295. * Event handler for touchmove event on mobile devices
  4296. */
  4297. Graph3d.prototype._onTouchMove = function(event) {
  4298. this._onMouseMove(event);
  4299. };
  4300. /**
  4301. * Event handler for touchend event on mobile devices
  4302. */
  4303. Graph3d.prototype._onTouchEnd = function(event) {
  4304. this.touchDown = false;
  4305. util.removeEventListener(document, 'touchmove', this.ontouchmove);
  4306. util.removeEventListener(document, 'touchend', this.ontouchend);
  4307. this._onMouseUp(event);
  4308. };
  4309. /**
  4310. * Event handler for mouse wheel event, used to zoom the graph
  4311. * Code from http://adomas.org/javascript-mouse-wheel/
  4312. * @param {event} event The event
  4313. */
  4314. Graph3d.prototype._onWheel = function(event) {
  4315. if (!event) /* For IE. */
  4316. event = window.event;
  4317. // retrieve delta
  4318. var delta = 0;
  4319. if (event.wheelDelta) { /* IE/Opera. */
  4320. delta = event.wheelDelta/120;
  4321. } else if (event.detail) { /* Mozilla case. */
  4322. // In Mozilla, sign of delta is different than in IE.
  4323. // Also, delta is multiple of 3.
  4324. delta = -event.detail/3;
  4325. }
  4326. // If delta is nonzero, handle it.
  4327. // Basically, delta is now positive if wheel was scrolled up,
  4328. // and negative, if wheel was scrolled down.
  4329. if (delta) {
  4330. var oldLength = this.camera.getArmLength();
  4331. var newLength = oldLength * (1 - delta / 10);
  4332. this.camera.setArmLength(newLength);
  4333. this.redraw();
  4334. this._hideTooltip();
  4335. }
  4336. // fire a cameraPositionChange event
  4337. var parameters = this.getCameraPosition();
  4338. this.emit('cameraPositionChange', parameters);
  4339. // Prevent default actions caused by mouse wheel.
  4340. // That might be ugly, but we handle scrolls somehow
  4341. // anyway, so don't bother here..
  4342. util.preventDefault(event);
  4343. };
  4344. /**
  4345. * Test whether a point lies inside given 2D triangle
  4346. * @param {Point2d} point
  4347. * @param {Point2d[]} triangle
  4348. * @return {boolean} Returns true if given point lies inside or on the edge of the triangle
  4349. * @private
  4350. */
  4351. Graph3d.prototype._insideTriangle = function (point, triangle) {
  4352. var a = triangle[0],
  4353. b = triangle[1],
  4354. c = triangle[2];
  4355. function sign (x) {
  4356. return x > 0 ? 1 : x < 0 ? -1 : 0;
  4357. }
  4358. var as = sign((b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x));
  4359. var bs = sign((c.x - b.x) * (point.y - b.y) - (c.y - b.y) * (point.x - b.x));
  4360. var cs = sign((a.x - c.x) * (point.y - c.y) - (a.y - c.y) * (point.x - c.x));
  4361. // each of the three signs must be either equal to each other or zero
  4362. return (as == 0 || bs == 0 || as == bs) &&
  4363. (bs == 0 || cs == 0 || bs == cs) &&
  4364. (as == 0 || cs == 0 || as == cs);
  4365. };
  4366. /**
  4367. * Find a data point close to given screen position (x, y)
  4368. * @param {Number} x
  4369. * @param {Number} y
  4370. * @return {Object | null} The closest data point or null if not close to any data point
  4371. * @private
  4372. */
  4373. Graph3d.prototype._dataPointFromXY = function (x, y) {
  4374. var i,
  4375. distMax = 100, // px
  4376. dataPoint = null,
  4377. closestDataPoint = null,
  4378. closestDist = null,
  4379. center = new Point2d(x, y);
  4380. if (this.style === Graph3d.STYLE.BAR ||
  4381. this.style === Graph3d.STYLE.BARCOLOR ||
  4382. this.style === Graph3d.STYLE.BARSIZE) {
  4383. // the data points are ordered from far away to closest
  4384. for (i = this.dataPoints.length - 1; i >= 0; i--) {
  4385. dataPoint = this.dataPoints[i];
  4386. var surfaces = dataPoint.surfaces;
  4387. if (surfaces) {
  4388. for (var s = surfaces.length - 1; s >= 0; s--) {
  4389. // split each surface in two triangles, and see if the center point is inside one of these
  4390. var surface = surfaces[s];
  4391. var corners = surface.corners;
  4392. var triangle1 = [corners[0].screen, corners[1].screen, corners[2].screen];
  4393. var triangle2 = [corners[2].screen, corners[3].screen, corners[0].screen];
  4394. if (this._insideTriangle(center, triangle1) ||
  4395. this._insideTriangle(center, triangle2)) {
  4396. // return immediately at the first hit
  4397. return dataPoint;
  4398. }
  4399. }
  4400. }
  4401. }
  4402. }
  4403. else {
  4404. // find the closest data point, using distance to the center of the point on 2d screen
  4405. for (i = 0; i < this.dataPoints.length; i++) {
  4406. dataPoint = this.dataPoints[i];
  4407. var point = dataPoint.screen;
  4408. if (point) {
  4409. var distX = Math.abs(x - point.x);
  4410. var distY = Math.abs(y - point.y);
  4411. var dist = Math.sqrt(distX * distX + distY * distY);
  4412. if ((closestDist === null || dist < closestDist) && dist < distMax) {
  4413. closestDist = dist;
  4414. closestDataPoint = dataPoint;
  4415. }
  4416. }
  4417. }
  4418. }
  4419. return closestDataPoint;
  4420. };
  4421. /**
  4422. * Display a tooltip for given data point
  4423. * @param {Object} dataPoint
  4424. * @private
  4425. */
  4426. Graph3d.prototype._showTooltip = function (dataPoint) {
  4427. var content, line, dot;
  4428. if (!this.tooltip) {
  4429. content = document.createElement('div');
  4430. content.style.position = 'absolute';
  4431. content.style.padding = '10px';
  4432. content.style.border = '1px solid #4d4d4d';
  4433. content.style.color = '#1a1a1a';
  4434. content.style.background = 'rgba(255,255,255,0.7)';
  4435. content.style.borderRadius = '2px';
  4436. content.style.boxShadow = '5px 5px 10px rgba(128,128,128,0.5)';
  4437. line = document.createElement('div');
  4438. line.style.position = 'absolute';
  4439. line.style.height = '40px';
  4440. line.style.width = '0';
  4441. line.style.borderLeft = '1px solid #4d4d4d';
  4442. dot = document.createElement('div');
  4443. dot.style.position = 'absolute';
  4444. dot.style.height = '0';
  4445. dot.style.width = '0';
  4446. dot.style.border = '5px solid #4d4d4d';
  4447. dot.style.borderRadius = '5px';
  4448. this.tooltip = {
  4449. dataPoint: null,
  4450. dom: {
  4451. content: content,
  4452. line: line,
  4453. dot: dot
  4454. }
  4455. };
  4456. }
  4457. else {
  4458. content = this.tooltip.dom.content;
  4459. line = this.tooltip.dom.line;
  4460. dot = this.tooltip.dom.dot;
  4461. }
  4462. this._hideTooltip();
  4463. this.tooltip.dataPoint = dataPoint;
  4464. if (typeof this.showTooltip === 'function') {
  4465. content.innerHTML = this.showTooltip(dataPoint.point);
  4466. }
  4467. else {
  4468. content.innerHTML = '<table>' +
  4469. '<tr><td>x:</td><td>' + dataPoint.point.x + '</td></tr>' +
  4470. '<tr><td>y:</td><td>' + dataPoint.point.y + '</td></tr>' +
  4471. '<tr><td>z:</td><td>' + dataPoint.point.z + '</td></tr>' +
  4472. '</table>';
  4473. }
  4474. content.style.left = '0';
  4475. content.style.top = '0';
  4476. this.frame.appendChild(content);
  4477. this.frame.appendChild(line);
  4478. this.frame.appendChild(dot);
  4479. // calculate sizes
  4480. var contentWidth = content.offsetWidth;
  4481. var contentHeight = content.offsetHeight;
  4482. var lineHeight = line.offsetHeight;
  4483. var dotWidth = dot.offsetWidth;
  4484. var dotHeight = dot.offsetHeight;
  4485. var left = dataPoint.screen.x - contentWidth / 2;
  4486. left = Math.min(Math.max(left, 10), this.frame.clientWidth - 10 - contentWidth);
  4487. line.style.left = dataPoint.screen.x + 'px';
  4488. line.style.top = (dataPoint.screen.y - lineHeight) + 'px';
  4489. content.style.left = left + 'px';
  4490. content.style.top = (dataPoint.screen.y - lineHeight - contentHeight) + 'px';
  4491. dot.style.left = (dataPoint.screen.x - dotWidth / 2) + 'px';
  4492. dot.style.top = (dataPoint.screen.y - dotHeight / 2) + 'px';
  4493. };
  4494. /**
  4495. * Hide the tooltip when displayed
  4496. * @private
  4497. */
  4498. Graph3d.prototype._hideTooltip = function () {
  4499. if (this.tooltip) {
  4500. this.tooltip.dataPoint = null;
  4501. for (var prop in this.tooltip.dom) {
  4502. if (this.tooltip.dom.hasOwnProperty(prop)) {
  4503. var elem = this.tooltip.dom[prop];
  4504. if (elem && elem.parentNode) {
  4505. elem.parentNode.removeChild(elem);
  4506. }
  4507. }
  4508. }
  4509. }
  4510. };
  4511. /**--------------------------------------------------------------------------**/
  4512. /**
  4513. * Get the horizontal mouse position from a mouse event
  4514. * @param {Event} event
  4515. * @return {Number} mouse x
  4516. */
  4517. getMouseX = function(event) {
  4518. if ('clientX' in event) return event.clientX;
  4519. return event.targetTouches[0] && event.targetTouches[0].clientX || 0;
  4520. };
  4521. /**
  4522. * Get the vertical mouse position from a mouse event
  4523. * @param {Event} event
  4524. * @return {Number} mouse y
  4525. */
  4526. getMouseY = function(event) {
  4527. if ('clientY' in event) return event.clientY;
  4528. return event.targetTouches[0] && event.targetTouches[0].clientY || 0;
  4529. };
  4530. module.exports = Graph3d;
  4531. /***/ },
  4532. /* 6 */
  4533. /***/ function(module, exports, __webpack_require__) {
  4534. var Point3d = __webpack_require__(9);
  4535. /**
  4536. * @class Camera
  4537. * The camera is mounted on a (virtual) camera arm. The camera arm can rotate
  4538. * The camera is always looking in the direction of the origin of the arm.
  4539. * This way, the camera always rotates around one fixed point, the location
  4540. * of the camera arm.
  4541. *
  4542. * Documentation:
  4543. * http://en.wikipedia.org/wiki/3D_projection
  4544. */
  4545. Camera = function () {
  4546. this.armLocation = new Point3d();
  4547. this.armRotation = {};
  4548. this.armRotation.horizontal = 0;
  4549. this.armRotation.vertical = 0;
  4550. this.armLength = 1.7;
  4551. this.cameraLocation = new Point3d();
  4552. this.cameraRotation = new Point3d(0.5*Math.PI, 0, 0);
  4553. this.calculateCameraOrientation();
  4554. };
  4555. /**
  4556. * Set the location (origin) of the arm
  4557. * @param {Number} x Normalized value of x
  4558. * @param {Number} y Normalized value of y
  4559. * @param {Number} z Normalized value of z
  4560. */
  4561. Camera.prototype.setArmLocation = function(x, y, z) {
  4562. this.armLocation.x = x;
  4563. this.armLocation.y = y;
  4564. this.armLocation.z = z;
  4565. this.calculateCameraOrientation();
  4566. };
  4567. /**
  4568. * Set the rotation of the camera arm
  4569. * @param {Number} horizontal The horizontal rotation, between 0 and 2*PI.
  4570. * Optional, can be left undefined.
  4571. * @param {Number} vertical The vertical rotation, between 0 and 0.5*PI
  4572. * if vertical=0.5*PI, the graph is shown from the
  4573. * top. Optional, can be left undefined.
  4574. */
  4575. Camera.prototype.setArmRotation = function(horizontal, vertical) {
  4576. if (horizontal !== undefined) {
  4577. this.armRotation.horizontal = horizontal;
  4578. }
  4579. if (vertical !== undefined) {
  4580. this.armRotation.vertical = vertical;
  4581. if (this.armRotation.vertical < 0) this.armRotation.vertical = 0;
  4582. if (this.armRotation.vertical > 0.5*Math.PI) this.armRotation.vertical = 0.5*Math.PI;
  4583. }
  4584. if (horizontal !== undefined || vertical !== undefined) {
  4585. this.calculateCameraOrientation();
  4586. }
  4587. };
  4588. /**
  4589. * Retrieve the current arm rotation
  4590. * @return {object} An object with parameters horizontal and vertical
  4591. */
  4592. Camera.prototype.getArmRotation = function() {
  4593. var rot = {};
  4594. rot.horizontal = this.armRotation.horizontal;
  4595. rot.vertical = this.armRotation.vertical;
  4596. return rot;
  4597. };
  4598. /**
  4599. * Set the (normalized) length of the camera arm.
  4600. * @param {Number} length A length between 0.71 and 5.0
  4601. */
  4602. Camera.prototype.setArmLength = function(length) {
  4603. if (length === undefined)
  4604. return;
  4605. this.armLength = length;
  4606. // Radius must be larger than the corner of the graph,
  4607. // which has a distance of sqrt(0.5^2+0.5^2) = 0.71 from the center of the
  4608. // graph
  4609. if (this.armLength < 0.71) this.armLength = 0.71;
  4610. if (this.armLength > 5.0) this.armLength = 5.0;
  4611. this.calculateCameraOrientation();
  4612. };
  4613. /**
  4614. * Retrieve the arm length
  4615. * @return {Number} length
  4616. */
  4617. Camera.prototype.getArmLength = function() {
  4618. return this.armLength;
  4619. };
  4620. /**
  4621. * Retrieve the camera location
  4622. * @return {Point3d} cameraLocation
  4623. */
  4624. Camera.prototype.getCameraLocation = function() {
  4625. return this.cameraLocation;
  4626. };
  4627. /**
  4628. * Retrieve the camera rotation
  4629. * @return {Point3d} cameraRotation
  4630. */
  4631. Camera.prototype.getCameraRotation = function() {
  4632. return this.cameraRotation;
  4633. };
  4634. /**
  4635. * Calculate the location and rotation of the camera based on the
  4636. * position and orientation of the camera arm
  4637. */
  4638. Camera.prototype.calculateCameraOrientation = function() {
  4639. // calculate location of the camera
  4640. this.cameraLocation.x = this.armLocation.x - this.armLength * Math.sin(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical);
  4641. this.cameraLocation.y = this.armLocation.y - this.armLength * Math.cos(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical);
  4642. this.cameraLocation.z = this.armLocation.z + this.armLength * Math.sin(this.armRotation.vertical);
  4643. // calculate rotation of the camera
  4644. this.cameraRotation.x = Math.PI/2 - this.armRotation.vertical;
  4645. this.cameraRotation.y = 0;
  4646. this.cameraRotation.z = -this.armRotation.horizontal;
  4647. };
  4648. module.exports = Camera;
  4649. /***/ },
  4650. /* 7 */
  4651. /***/ function(module, exports, __webpack_require__) {
  4652. var DataView = __webpack_require__(4);
  4653. /**
  4654. * @class Filter
  4655. *
  4656. * @param {DataSet} data The google data table
  4657. * @param {Number} column The index of the column to be filtered
  4658. * @param {Graph} graph The graph
  4659. */
  4660. function Filter (data, column, graph) {
  4661. this.data = data;
  4662. this.column = column;
  4663. this.graph = graph; // the parent graph
  4664. this.index = undefined;
  4665. this.value = undefined;
  4666. // read all distinct values and select the first one
  4667. this.values = graph.getDistinctValues(data.get(), this.column);
  4668. // sort both numeric and string values correctly
  4669. this.values.sort(function (a, b) {
  4670. return a > b ? 1 : a < b ? -1 : 0;
  4671. });
  4672. if (this.values.length > 0) {
  4673. this.selectValue(0);
  4674. }
  4675. // create an array with the filtered datapoints. this will be loaded afterwards
  4676. this.dataPoints = [];
  4677. this.loaded = false;
  4678. this.onLoadCallback = undefined;
  4679. if (graph.animationPreload) {
  4680. this.loaded = false;
  4681. this.loadInBackground();
  4682. }
  4683. else {
  4684. this.loaded = true;
  4685. }
  4686. };
  4687. /**
  4688. * Return the label
  4689. * @return {string} label
  4690. */
  4691. Filter.prototype.isLoaded = function() {
  4692. return this.loaded;
  4693. };
  4694. /**
  4695. * Return the loaded progress
  4696. * @return {Number} percentage between 0 and 100
  4697. */
  4698. Filter.prototype.getLoadedProgress = function() {
  4699. var len = this.values.length;
  4700. var i = 0;
  4701. while (this.dataPoints[i]) {
  4702. i++;
  4703. }
  4704. return Math.round(i / len * 100);
  4705. };
  4706. /**
  4707. * Return the label
  4708. * @return {string} label
  4709. */
  4710. Filter.prototype.getLabel = function() {
  4711. return this.graph.filterLabel;
  4712. };
  4713. /**
  4714. * Return the columnIndex of the filter
  4715. * @return {Number} columnIndex
  4716. */
  4717. Filter.prototype.getColumn = function() {
  4718. return this.column;
  4719. };
  4720. /**
  4721. * Return the currently selected value. Returns undefined if there is no selection
  4722. * @return {*} value
  4723. */
  4724. Filter.prototype.getSelectedValue = function() {
  4725. if (this.index === undefined)
  4726. return undefined;
  4727. return this.values[this.index];
  4728. };
  4729. /**
  4730. * Retrieve all values of the filter
  4731. * @return {Array} values
  4732. */
  4733. Filter.prototype.getValues = function() {
  4734. return this.values;
  4735. };
  4736. /**
  4737. * Retrieve one value of the filter
  4738. * @param {Number} index
  4739. * @return {*} value
  4740. */
  4741. Filter.prototype.getValue = function(index) {
  4742. if (index >= this.values.length)
  4743. throw 'Error: index out of range';
  4744. return this.values[index];
  4745. };
  4746. /**
  4747. * Retrieve the (filtered) dataPoints for the currently selected filter index
  4748. * @param {Number} [index] (optional)
  4749. * @return {Array} dataPoints
  4750. */
  4751. Filter.prototype._getDataPoints = function(index) {
  4752. if (index === undefined)
  4753. index = this.index;
  4754. if (index === undefined)
  4755. return [];
  4756. var dataPoints;
  4757. if (this.dataPoints[index]) {
  4758. dataPoints = this.dataPoints[index];
  4759. }
  4760. else {
  4761. var f = {};
  4762. f.column = this.column;
  4763. f.value = this.values[index];
  4764. var dataView = new DataView(this.data,{filter: function (item) {return (item[f.column] == f.value);}}).get();
  4765. dataPoints = this.graph._getDataPoints(dataView);
  4766. this.dataPoints[index] = dataPoints;
  4767. }
  4768. return dataPoints;
  4769. };
  4770. /**
  4771. * Set a callback function when the filter is fully loaded.
  4772. */
  4773. Filter.prototype.setOnLoadCallback = function(callback) {
  4774. this.onLoadCallback = callback;
  4775. };
  4776. /**
  4777. * Add a value to the list with available values for this filter
  4778. * No double entries will be created.
  4779. * @param {Number} index
  4780. */
  4781. Filter.prototype.selectValue = function(index) {
  4782. if (index >= this.values.length)
  4783. throw 'Error: index out of range';
  4784. this.index = index;
  4785. this.value = this.values[index];
  4786. };
  4787. /**
  4788. * Load all filtered rows in the background one by one
  4789. * Start this method without providing an index!
  4790. */
  4791. Filter.prototype.loadInBackground = function(index) {
  4792. if (index === undefined)
  4793. index = 0;
  4794. var frame = this.graph.frame;
  4795. if (index < this.values.length) {
  4796. var dataPointsTemp = this._getDataPoints(index);
  4797. //this.graph.redrawInfo(); // TODO: not neat
  4798. // create a progress box
  4799. if (frame.progress === undefined) {
  4800. frame.progress = document.createElement('DIV');
  4801. frame.progress.style.position = 'absolute';
  4802. frame.progress.style.color = 'gray';
  4803. frame.appendChild(frame.progress);
  4804. }
  4805. var progress = this.getLoadedProgress();
  4806. frame.progress.innerHTML = 'Loading animation... ' + progress + '%';
  4807. // TODO: this is no nice solution...
  4808. frame.progress.style.bottom = 60 + 'px'; // TODO: use height of slider
  4809. frame.progress.style.left = 10 + 'px';
  4810. var me = this;
  4811. setTimeout(function() {me.loadInBackground(index+1);}, 10);
  4812. this.loaded = false;
  4813. }
  4814. else {
  4815. this.loaded = true;
  4816. // remove the progress box
  4817. if (frame.progress !== undefined) {
  4818. frame.removeChild(frame.progress);
  4819. frame.progress = undefined;
  4820. }
  4821. if (this.onLoadCallback)
  4822. this.onLoadCallback();
  4823. }
  4824. };
  4825. module.exports = Filter;
  4826. /***/ },
  4827. /* 8 */
  4828. /***/ function(module, exports, __webpack_require__) {
  4829. /**
  4830. * @prototype Point2d
  4831. * @param {Number} [x]
  4832. * @param {Number} [y]
  4833. */
  4834. Point2d = function (x, y) {
  4835. this.x = x !== undefined ? x : 0;
  4836. this.y = y !== undefined ? y : 0;
  4837. };
  4838. module.exports = Point2d;
  4839. /***/ },
  4840. /* 9 */
  4841. /***/ function(module, exports, __webpack_require__) {
  4842. /**
  4843. * @prototype Point3d
  4844. * @param {Number} [x]
  4845. * @param {Number} [y]
  4846. * @param {Number} [z]
  4847. */
  4848. function Point3d(x, y, z) {
  4849. this.x = x !== undefined ? x : 0;
  4850. this.y = y !== undefined ? y : 0;
  4851. this.z = z !== undefined ? z : 0;
  4852. };
  4853. /**
  4854. * Subtract the two provided points, returns a-b
  4855. * @param {Point3d} a
  4856. * @param {Point3d} b
  4857. * @return {Point3d} a-b
  4858. */
  4859. Point3d.subtract = function(a, b) {
  4860. var sub = new Point3d();
  4861. sub.x = a.x - b.x;
  4862. sub.y = a.y - b.y;
  4863. sub.z = a.z - b.z;
  4864. return sub;
  4865. };
  4866. /**
  4867. * Add the two provided points, returns a+b
  4868. * @param {Point3d} a
  4869. * @param {Point3d} b
  4870. * @return {Point3d} a+b
  4871. */
  4872. Point3d.add = function(a, b) {
  4873. var sum = new Point3d();
  4874. sum.x = a.x + b.x;
  4875. sum.y = a.y + b.y;
  4876. sum.z = a.z + b.z;
  4877. return sum;
  4878. };
  4879. /**
  4880. * Calculate the average of two 3d points
  4881. * @param {Point3d} a
  4882. * @param {Point3d} b
  4883. * @return {Point3d} The average, (a+b)/2
  4884. */
  4885. Point3d.avg = function(a, b) {
  4886. return new Point3d(
  4887. (a.x + b.x) / 2,
  4888. (a.y + b.y) / 2,
  4889. (a.z + b.z) / 2
  4890. );
  4891. };
  4892. /**
  4893. * Calculate the cross product of the two provided points, returns axb
  4894. * Documentation: http://en.wikipedia.org/wiki/Cross_product
  4895. * @param {Point3d} a
  4896. * @param {Point3d} b
  4897. * @return {Point3d} cross product axb
  4898. */
  4899. Point3d.crossProduct = function(a, b) {
  4900. var crossproduct = new Point3d();
  4901. crossproduct.x = a.y * b.z - a.z * b.y;
  4902. crossproduct.y = a.z * b.x - a.x * b.z;
  4903. crossproduct.z = a.x * b.y - a.y * b.x;
  4904. return crossproduct;
  4905. };
  4906. /**
  4907. * Rtrieve the length of the vector (or the distance from this point to the origin
  4908. * @return {Number} length
  4909. */
  4910. Point3d.prototype.length = function() {
  4911. return Math.sqrt(
  4912. this.x * this.x +
  4913. this.y * this.y +
  4914. this.z * this.z
  4915. );
  4916. };
  4917. module.exports = Point3d;
  4918. /***/ },
  4919. /* 10 */
  4920. /***/ function(module, exports, __webpack_require__) {
  4921. var util = __webpack_require__(1);
  4922. /**
  4923. * @constructor Slider
  4924. *
  4925. * An html slider control with start/stop/prev/next buttons
  4926. * @param {Element} container The element where the slider will be created
  4927. * @param {Object} options Available options:
  4928. * {boolean} visible If true (default) the
  4929. * slider is visible.
  4930. */
  4931. function Slider(container, options) {
  4932. if (container === undefined) {
  4933. throw 'Error: No container element defined';
  4934. }
  4935. this.container = container;
  4936. this.visible = (options && options.visible != undefined) ? options.visible : true;
  4937. if (this.visible) {
  4938. this.frame = document.createElement('DIV');
  4939. //this.frame.style.backgroundColor = '#E5E5E5';
  4940. this.frame.style.width = '100%';
  4941. this.frame.style.position = 'relative';
  4942. this.container.appendChild(this.frame);
  4943. this.frame.prev = document.createElement('INPUT');
  4944. this.frame.prev.type = 'BUTTON';
  4945. this.frame.prev.value = 'Prev';
  4946. this.frame.appendChild(this.frame.prev);
  4947. this.frame.play = document.createElement('INPUT');
  4948. this.frame.play.type = 'BUTTON';
  4949. this.frame.play.value = 'Play';
  4950. this.frame.appendChild(this.frame.play);
  4951. this.frame.next = document.createElement('INPUT');
  4952. this.frame.next.type = 'BUTTON';
  4953. this.frame.next.value = 'Next';
  4954. this.frame.appendChild(this.frame.next);
  4955. this.frame.bar = document.createElement('INPUT');
  4956. this.frame.bar.type = 'BUTTON';
  4957. this.frame.bar.style.position = 'absolute';
  4958. this.frame.bar.style.border = '1px solid red';
  4959. this.frame.bar.style.width = '100px';
  4960. this.frame.bar.style.height = '6px';
  4961. this.frame.bar.style.borderRadius = '2px';
  4962. this.frame.bar.style.MozBorderRadius = '2px';
  4963. this.frame.bar.style.border = '1px solid #7F7F7F';
  4964. this.frame.bar.style.backgroundColor = '#E5E5E5';
  4965. this.frame.appendChild(this.frame.bar);
  4966. this.frame.slide = document.createElement('INPUT');
  4967. this.frame.slide.type = 'BUTTON';
  4968. this.frame.slide.style.margin = '0px';
  4969. this.frame.slide.value = ' ';
  4970. this.frame.slide.style.position = 'relative';
  4971. this.frame.slide.style.left = '-100px';
  4972. this.frame.appendChild(this.frame.slide);
  4973. // create events
  4974. var me = this;
  4975. this.frame.slide.onmousedown = function (event) {me._onMouseDown(event);};
  4976. this.frame.prev.onclick = function (event) {me.prev(event);};
  4977. this.frame.play.onclick = function (event) {me.togglePlay(event);};
  4978. this.frame.next.onclick = function (event) {me.next(event);};
  4979. }
  4980. this.onChangeCallback = undefined;
  4981. this.values = [];
  4982. this.index = undefined;
  4983. this.playTimeout = undefined;
  4984. this.playInterval = 1000; // milliseconds
  4985. this.playLoop = true;
  4986. }
  4987. /**
  4988. * Select the previous index
  4989. */
  4990. Slider.prototype.prev = function() {
  4991. var index = this.getIndex();
  4992. if (index > 0) {
  4993. index--;
  4994. this.setIndex(index);
  4995. }
  4996. };
  4997. /**
  4998. * Select the next index
  4999. */
  5000. Slider.prototype.next = function() {
  5001. var index = this.getIndex();
  5002. if (index < this.values.length - 1) {
  5003. index++;
  5004. this.setIndex(index);
  5005. }
  5006. };
  5007. /**
  5008. * Select the next index
  5009. */
  5010. Slider.prototype.playNext = function() {
  5011. var start = new Date();
  5012. var index = this.getIndex();
  5013. if (index < this.values.length - 1) {
  5014. index++;
  5015. this.setIndex(index);
  5016. }
  5017. else if (this.playLoop) {
  5018. // jump to the start
  5019. index = 0;
  5020. this.setIndex(index);
  5021. }
  5022. var end = new Date();
  5023. var diff = (end - start);
  5024. // calculate how much time it to to set the index and to execute the callback
  5025. // function.
  5026. var interval = Math.max(this.playInterval - diff, 0);
  5027. // document.title = diff // TODO: cleanup
  5028. var me = this;
  5029. this.playTimeout = setTimeout(function() {me.playNext();}, interval);
  5030. };
  5031. /**
  5032. * Toggle start or stop playing
  5033. */
  5034. Slider.prototype.togglePlay = function() {
  5035. if (this.playTimeout === undefined) {
  5036. this.play();
  5037. } else {
  5038. this.stop();
  5039. }
  5040. };
  5041. /**
  5042. * Start playing
  5043. */
  5044. Slider.prototype.play = function() {
  5045. // Test whether already playing
  5046. if (this.playTimeout) return;
  5047. this.playNext();
  5048. if (this.frame) {
  5049. this.frame.play.value = 'Stop';
  5050. }
  5051. };
  5052. /**
  5053. * Stop playing
  5054. */
  5055. Slider.prototype.stop = function() {
  5056. clearInterval(this.playTimeout);
  5057. this.playTimeout = undefined;
  5058. if (this.frame) {
  5059. this.frame.play.value = 'Play';
  5060. }
  5061. };
  5062. /**
  5063. * Set a callback function which will be triggered when the value of the
  5064. * slider bar has changed.
  5065. */
  5066. Slider.prototype.setOnChangeCallback = function(callback) {
  5067. this.onChangeCallback = callback;
  5068. };
  5069. /**
  5070. * Set the interval for playing the list
  5071. * @param {Number} interval The interval in milliseconds
  5072. */
  5073. Slider.prototype.setPlayInterval = function(interval) {
  5074. this.playInterval = interval;
  5075. };
  5076. /**
  5077. * Retrieve the current play interval
  5078. * @return {Number} interval The interval in milliseconds
  5079. */
  5080. Slider.prototype.getPlayInterval = function(interval) {
  5081. return this.playInterval;
  5082. };
  5083. /**
  5084. * Set looping on or off
  5085. * @pararm {boolean} doLoop If true, the slider will jump to the start when
  5086. * the end is passed, and will jump to the end
  5087. * when the start is passed.
  5088. */
  5089. Slider.prototype.setPlayLoop = function(doLoop) {
  5090. this.playLoop = doLoop;
  5091. };
  5092. /**
  5093. * Execute the onchange callback function
  5094. */
  5095. Slider.prototype.onChange = function() {
  5096. if (this.onChangeCallback !== undefined) {
  5097. this.onChangeCallback();
  5098. }
  5099. };
  5100. /**
  5101. * redraw the slider on the correct place
  5102. */
  5103. Slider.prototype.redraw = function() {
  5104. if (this.frame) {
  5105. // resize the bar
  5106. this.frame.bar.style.top = (this.frame.clientHeight/2 -
  5107. this.frame.bar.offsetHeight/2) + 'px';
  5108. this.frame.bar.style.width = (this.frame.clientWidth -
  5109. this.frame.prev.clientWidth -
  5110. this.frame.play.clientWidth -
  5111. this.frame.next.clientWidth - 30) + 'px';
  5112. // position the slider button
  5113. var left = this.indexToLeft(this.index);
  5114. this.frame.slide.style.left = (left) + 'px';
  5115. }
  5116. };
  5117. /**
  5118. * Set the list with values for the slider
  5119. * @param {Array} values A javascript array with values (any type)
  5120. */
  5121. Slider.prototype.setValues = function(values) {
  5122. this.values = values;
  5123. if (this.values.length > 0)
  5124. this.setIndex(0);
  5125. else
  5126. this.index = undefined;
  5127. };
  5128. /**
  5129. * Select a value by its index
  5130. * @param {Number} index
  5131. */
  5132. Slider.prototype.setIndex = function(index) {
  5133. if (index < this.values.length) {
  5134. this.index = index;
  5135. this.redraw();
  5136. this.onChange();
  5137. }
  5138. else {
  5139. throw 'Error: index out of range';
  5140. }
  5141. };
  5142. /**
  5143. * retrieve the index of the currently selected vaue
  5144. * @return {Number} index
  5145. */
  5146. Slider.prototype.getIndex = function() {
  5147. return this.index;
  5148. };
  5149. /**
  5150. * retrieve the currently selected value
  5151. * @return {*} value
  5152. */
  5153. Slider.prototype.get = function() {
  5154. return this.values[this.index];
  5155. };
  5156. Slider.prototype._onMouseDown = function(event) {
  5157. // only react on left mouse button down
  5158. var leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
  5159. if (!leftButtonDown) return;
  5160. this.startClientX = event.clientX;
  5161. this.startSlideX = parseFloat(this.frame.slide.style.left);
  5162. this.frame.style.cursor = 'move';
  5163. // add event listeners to handle moving the contents
  5164. // we store the function onmousemove and onmouseup in the graph, so we can
  5165. // remove the eventlisteners lateron in the function mouseUp()
  5166. var me = this;
  5167. this.onmousemove = function (event) {me._onMouseMove(event);};
  5168. this.onmouseup = function (event) {me._onMouseUp(event);};
  5169. util.addEventListener(document, 'mousemove', this.onmousemove);
  5170. util.addEventListener(document, 'mouseup', this.onmouseup);
  5171. util.preventDefault(event);
  5172. };
  5173. Slider.prototype.leftToIndex = function (left) {
  5174. var width = parseFloat(this.frame.bar.style.width) -
  5175. this.frame.slide.clientWidth - 10;
  5176. var x = left - 3;
  5177. var index = Math.round(x / width * (this.values.length-1));
  5178. if (index < 0) index = 0;
  5179. if (index > this.values.length-1) index = this.values.length-1;
  5180. return index;
  5181. };
  5182. Slider.prototype.indexToLeft = function (index) {
  5183. var width = parseFloat(this.frame.bar.style.width) -
  5184. this.frame.slide.clientWidth - 10;
  5185. var x = index / (this.values.length-1) * width;
  5186. var left = x + 3;
  5187. return left;
  5188. };
  5189. Slider.prototype._onMouseMove = function (event) {
  5190. var diff = event.clientX - this.startClientX;
  5191. var x = this.startSlideX + diff;
  5192. var index = this.leftToIndex(x);
  5193. this.setIndex(index);
  5194. util.preventDefault();
  5195. };
  5196. Slider.prototype._onMouseUp = function (event) {
  5197. this.frame.style.cursor = 'auto';
  5198. // remove event listeners
  5199. util.removeEventListener(document, 'mousemove', this.onmousemove);
  5200. util.removeEventListener(document, 'mouseup', this.onmouseup);
  5201. util.preventDefault();
  5202. };
  5203. module.exports = Slider;
  5204. /***/ },
  5205. /* 11 */
  5206. /***/ function(module, exports, __webpack_require__) {
  5207. /**
  5208. * @prototype StepNumber
  5209. * The class StepNumber is an iterator for Numbers. You provide a start and end
  5210. * value, and a best step size. StepNumber itself rounds to fixed values and
  5211. * a finds the step that best fits the provided step.
  5212. *
  5213. * If prettyStep is true, the step size is chosen as close as possible to the
  5214. * provided step, but being a round value like 1, 2, 5, 10, 20, 50, ....
  5215. *
  5216. * Example usage:
  5217. * var step = new StepNumber(0, 10, 2.5, true);
  5218. * step.start();
  5219. * while (!step.end()) {
  5220. * alert(step.getCurrent());
  5221. * step.next();
  5222. * }
  5223. *
  5224. * Version: 1.0
  5225. *
  5226. * @param {Number} start The start value
  5227. * @param {Number} end The end value
  5228. * @param {Number} step Optional. Step size. Must be a positive value.
  5229. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  5230. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  5231. */
  5232. function StepNumber(start, end, step, prettyStep) {
  5233. // set default values
  5234. this._start = 0;
  5235. this._end = 0;
  5236. this._step = 1;
  5237. this.prettyStep = true;
  5238. this.precision = 5;
  5239. this._current = 0;
  5240. this.setRange(start, end, step, prettyStep);
  5241. };
  5242. /**
  5243. * Set a new range: start, end and step.
  5244. *
  5245. * @param {Number} start The start value
  5246. * @param {Number} end The end value
  5247. * @param {Number} step Optional. Step size. Must be a positive value.
  5248. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  5249. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  5250. */
  5251. StepNumber.prototype.setRange = function(start, end, step, prettyStep) {
  5252. this._start = start ? start : 0;
  5253. this._end = end ? end : 0;
  5254. this.setStep(step, prettyStep);
  5255. };
  5256. /**
  5257. * Set a new step size
  5258. * @param {Number} step New step size. Must be a positive value
  5259. * @param {boolean} prettyStep Optional. If true, the provided step is rounded
  5260. * to a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  5261. */
  5262. StepNumber.prototype.setStep = function(step, prettyStep) {
  5263. if (step === undefined || step <= 0)
  5264. return;
  5265. if (prettyStep !== undefined)
  5266. this.prettyStep = prettyStep;
  5267. if (this.prettyStep === true)
  5268. this._step = StepNumber.calculatePrettyStep(step);
  5269. else
  5270. this._step = step;
  5271. };
  5272. /**
  5273. * Calculate a nice step size, closest to the desired step size.
  5274. * Returns a value in one of the ranges 1*10^n, 2*10^n, or 5*10^n, where n is an
  5275. * integer Number. For example 1, 2, 5, 10, 20, 50, etc...
  5276. * @param {Number} step Desired step size
  5277. * @return {Number} Nice step size
  5278. */
  5279. StepNumber.calculatePrettyStep = function (step) {
  5280. var log10 = function (x) {return Math.log(x) / Math.LN10;};
  5281. // try three steps (multiple of 1, 2, or 5
  5282. var step1 = Math.pow(10, Math.round(log10(step))),
  5283. step2 = 2 * Math.pow(10, Math.round(log10(step / 2))),
  5284. step5 = 5 * Math.pow(10, Math.round(log10(step / 5)));
  5285. // choose the best step (closest to minimum step)
  5286. var prettyStep = step1;
  5287. if (Math.abs(step2 - step) <= Math.abs(prettyStep - step)) prettyStep = step2;
  5288. if (Math.abs(step5 - step) <= Math.abs(prettyStep - step)) prettyStep = step5;
  5289. // for safety
  5290. if (prettyStep <= 0) {
  5291. prettyStep = 1;
  5292. }
  5293. return prettyStep;
  5294. };
  5295. /**
  5296. * returns the current value of the step
  5297. * @return {Number} current value
  5298. */
  5299. StepNumber.prototype.getCurrent = function () {
  5300. return parseFloat(this._current.toPrecision(this.precision));
  5301. };
  5302. /**
  5303. * returns the current step size
  5304. * @return {Number} current step size
  5305. */
  5306. StepNumber.prototype.getStep = function () {
  5307. return this._step;
  5308. };
  5309. /**
  5310. * Set the current value to the largest value smaller than start, which
  5311. * is a multiple of the step size
  5312. */
  5313. StepNumber.prototype.start = function() {
  5314. this._current = this._start - this._start % this._step;
  5315. };
  5316. /**
  5317. * Do a step, add the step size to the current value
  5318. */
  5319. StepNumber.prototype.next = function () {
  5320. this._current += this._step;
  5321. };
  5322. /**
  5323. * Returns true whether the end is reached
  5324. * @return {boolean} True if the current value has passed the end value.
  5325. */
  5326. StepNumber.prototype.end = function () {
  5327. return (this._current > this._end);
  5328. };
  5329. module.exports = StepNumber;
  5330. /***/ },
  5331. /* 12 */
  5332. /***/ function(module, exports, __webpack_require__) {
  5333. var Emitter = __webpack_require__(50);
  5334. var Hammer = __webpack_require__(42);
  5335. var util = __webpack_require__(1);
  5336. var DataSet = __webpack_require__(3);
  5337. var DataView = __webpack_require__(4);
  5338. var Range = __webpack_require__(15);
  5339. var Core = __webpack_require__(43);
  5340. var TimeAxis = __webpack_require__(27);
  5341. var CurrentTime = __webpack_require__(19);
  5342. var CustomTime = __webpack_require__(20);
  5343. var ItemSet = __webpack_require__(24);
  5344. /**
  5345. * Create a timeline visualization
  5346. * @param {HTMLElement} container
  5347. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  5348. * @param {Object} [options] See Timeline.setOptions for the available options.
  5349. * @constructor
  5350. * @extends Core
  5351. */
  5352. function Timeline (container, items, options) {
  5353. if (!(this instanceof Timeline)) {
  5354. throw new SyntaxError('Constructor must be called with the new operator');
  5355. }
  5356. var me = this;
  5357. this.defaultOptions = {
  5358. start: null,
  5359. end: null,
  5360. autoResize: true,
  5361. orientation: 'bottom',
  5362. width: null,
  5363. height: null,
  5364. maxHeight: null,
  5365. minHeight: null
  5366. };
  5367. this.options = util.deepExtend({}, this.defaultOptions);
  5368. // Create the DOM, props, and emitter
  5369. this._create(container);
  5370. // all components listed here will be repainted automatically
  5371. this.components = [];
  5372. this.body = {
  5373. dom: this.dom,
  5374. domProps: this.props,
  5375. emitter: {
  5376. on: this.on.bind(this),
  5377. off: this.off.bind(this),
  5378. emit: this.emit.bind(this)
  5379. },
  5380. util: {
  5381. snap: null, // will be specified after TimeAxis is created
  5382. toScreen: me._toScreen.bind(me),
  5383. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  5384. toTime: me._toTime.bind(me),
  5385. toGlobalTime : me._toGlobalTime.bind(me)
  5386. }
  5387. };
  5388. // range
  5389. this.range = new Range(this.body);
  5390. this.components.push(this.range);
  5391. this.body.range = this.range;
  5392. // time axis
  5393. this.timeAxis = new TimeAxis(this.body);
  5394. this.components.push(this.timeAxis);
  5395. this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
  5396. // current time bar
  5397. this.currentTime = new CurrentTime(this.body);
  5398. this.components.push(this.currentTime);
  5399. // custom time bar
  5400. // Note: time bar will be attached in this.setOptions when selected
  5401. this.customTime = new CustomTime(this.body);
  5402. this.components.push(this.customTime);
  5403. // item set
  5404. this.itemSet = new ItemSet(this.body);
  5405. this.components.push(this.itemSet);
  5406. this.itemsData = null; // DataSet
  5407. this.groupsData = null; // DataSet
  5408. // apply options
  5409. if (options) {
  5410. this.setOptions(options);
  5411. }
  5412. // create itemset
  5413. if (items) {
  5414. this.setItems(items);
  5415. }
  5416. else {
  5417. this.redraw();
  5418. }
  5419. }
  5420. // Extend the functionality from Core
  5421. Timeline.prototype = new Core();
  5422. /**
  5423. * Set items
  5424. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  5425. */
  5426. Timeline.prototype.setItems = function(items) {
  5427. var initialLoad = (this.itemsData == null);
  5428. // convert to type DataSet when needed
  5429. var newDataSet;
  5430. if (!items) {
  5431. newDataSet = null;
  5432. }
  5433. else if (items instanceof DataSet || items instanceof DataView) {
  5434. newDataSet = items;
  5435. }
  5436. else {
  5437. // turn an array into a dataset
  5438. newDataSet = new DataSet(items, {
  5439. type: {
  5440. start: 'Date',
  5441. end: 'Date'
  5442. }
  5443. });
  5444. }
  5445. // set items
  5446. this.itemsData = newDataSet;
  5447. this.itemSet && this.itemSet.setItems(newDataSet);
  5448. if (initialLoad) {
  5449. if (this.options.start != undefined || this.options.end != undefined) {
  5450. var start = this.options.start != undefined ? this.options.start : null;
  5451. var end = this.options.end != undefined ? this.options.end : null;
  5452. this.setWindow(start, end, {animate: false});
  5453. }
  5454. else {
  5455. this.fit({animate: false});
  5456. }
  5457. }
  5458. };
  5459. /**
  5460. * Set groups
  5461. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  5462. */
  5463. Timeline.prototype.setGroups = function(groups) {
  5464. // convert to type DataSet when needed
  5465. var newDataSet;
  5466. if (!groups) {
  5467. newDataSet = null;
  5468. }
  5469. else if (groups instanceof DataSet || groups instanceof DataView) {
  5470. newDataSet = groups;
  5471. }
  5472. else {
  5473. // turn an array into a dataset
  5474. newDataSet = new DataSet(groups);
  5475. }
  5476. this.groupsData = newDataSet;
  5477. this.itemSet.setGroups(newDataSet);
  5478. };
  5479. /**
  5480. * Set selected items by their id. Replaces the current selection
  5481. * Unknown id's are silently ignored.
  5482. * @param {string[] | string} [ids] An array with zero or more id's of the items to be
  5483. * selected. If ids is an empty array, all items will be
  5484. * unselected.
  5485. * @param {Object} [options] Available options:
  5486. * `focus: boolean`
  5487. * If true, focus will be set to the selected item(s)
  5488. * `animate: boolean | number`
  5489. * If true (default), the range is animated
  5490. * smoothly to the new window.
  5491. * If a number, the number is taken as duration
  5492. * for the animation. Default duration is 500 ms.
  5493. * Only applicable when option focus is true.
  5494. */
  5495. Timeline.prototype.setSelection = function(ids, options) {
  5496. this.itemSet && this.itemSet.setSelection(ids);
  5497. if (options && options.focus) {
  5498. this.focus(ids, options);
  5499. }
  5500. };
  5501. /**
  5502. * Get the selected items by their id
  5503. * @return {Array} ids The ids of the selected items
  5504. */
  5505. Timeline.prototype.getSelection = function() {
  5506. return this.itemSet && this.itemSet.getSelection() || [];
  5507. };
  5508. /**
  5509. * Adjust the visible window such that the selected item (or multiple items)
  5510. * are centered on screen.
  5511. * @param {String | String[]} id An item id or array with item ids
  5512. * @param {Object} [options] Available options:
  5513. * `animate: boolean | number`
  5514. * If true (default), the range is animated
  5515. * smoothly to the new window.
  5516. * If a number, the number is taken as duration
  5517. * for the animation. Default duration is 500 ms.
  5518. * Only applicable when option focus is true
  5519. */
  5520. Timeline.prototype.focus = function(id, options) {
  5521. if (!this.itemsData || id == undefined) return;
  5522. var ids = Array.isArray(id) ? id : [id];
  5523. // get the specified item(s)
  5524. var itemsData = this.itemsData.getDataSet().get(ids, {
  5525. type: {
  5526. start: 'Date',
  5527. end: 'Date'
  5528. }
  5529. });
  5530. // calculate minimum start and maximum end of specified items
  5531. var start = null;
  5532. var end = null;
  5533. itemsData.forEach(function (itemData) {
  5534. var s = itemData.start.valueOf();
  5535. var e = 'end' in itemData ? itemData.end.valueOf() : itemData.start.valueOf();
  5536. if (start === null || s < start) {
  5537. start = s;
  5538. }
  5539. if (end === null || e > end) {
  5540. end = e;
  5541. }
  5542. });
  5543. if (start !== null && end !== null) {
  5544. // calculate the new middle and interval for the window
  5545. var middle = (start + end) / 2;
  5546. var interval = Math.max((this.range.end - this.range.start), (end - start) * 1.1);
  5547. var animate = (options && options.animate !== undefined) ? options.animate : true;
  5548. this.range.setRange(middle - interval / 2, middle + interval / 2, animate);
  5549. }
  5550. };
  5551. /**
  5552. * Get the data range of the item set.
  5553. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  5554. * When no minimum is found, min==null
  5555. * When no maximum is found, max==null
  5556. */
  5557. Timeline.prototype.getItemRange = function() {
  5558. // calculate min from start filed
  5559. var dataset = this.itemsData.getDataSet(),
  5560. min = null,
  5561. max = null;
  5562. if (dataset) {
  5563. // calculate the minimum value of the field 'start'
  5564. var minItem = dataset.min('start');
  5565. min = minItem ? util.convert(minItem.start, 'Date').valueOf() : null;
  5566. // Note: we convert first to Date and then to number because else
  5567. // a conversion from ISODate to Number will fail
  5568. // calculate maximum value of fields 'start' and 'end'
  5569. var maxStartItem = dataset.max('start');
  5570. if (maxStartItem) {
  5571. max = util.convert(maxStartItem.start, 'Date').valueOf();
  5572. }
  5573. var maxEndItem = dataset.max('end');
  5574. if (maxEndItem) {
  5575. if (max == null) {
  5576. max = util.convert(maxEndItem.end, 'Date').valueOf();
  5577. }
  5578. else {
  5579. max = Math.max(max, util.convert(maxEndItem.end, 'Date').valueOf());
  5580. }
  5581. }
  5582. }
  5583. return {
  5584. min: (min != null) ? new Date(min) : null,
  5585. max: (max != null) ? new Date(max) : null
  5586. };
  5587. };
  5588. module.exports = Timeline;
  5589. /***/ },
  5590. /* 13 */
  5591. /***/ function(module, exports, __webpack_require__) {
  5592. var Emitter = __webpack_require__(50);
  5593. var Hammer = __webpack_require__(42);
  5594. var util = __webpack_require__(1);
  5595. var DataSet = __webpack_require__(3);
  5596. var DataView = __webpack_require__(4);
  5597. var Range = __webpack_require__(15);
  5598. var Core = __webpack_require__(43);
  5599. var TimeAxis = __webpack_require__(27);
  5600. var CurrentTime = __webpack_require__(19);
  5601. var CustomTime = __webpack_require__(20);
  5602. var LineGraph = __webpack_require__(26);
  5603. /**
  5604. * Create a timeline visualization
  5605. * @param {HTMLElement} container
  5606. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  5607. * @param {Object} [options] See Graph2d.setOptions for the available options.
  5608. * @constructor
  5609. * @extends Core
  5610. */
  5611. function Graph2d (container, items, options, groups) {
  5612. var me = this;
  5613. this.defaultOptions = {
  5614. start: null,
  5615. end: null,
  5616. autoResize: true,
  5617. orientation: 'bottom',
  5618. width: null,
  5619. height: null,
  5620. maxHeight: null,
  5621. minHeight: null
  5622. };
  5623. this.options = util.deepExtend({}, this.defaultOptions);
  5624. // Create the DOM, props, and emitter
  5625. this._create(container);
  5626. // all components listed here will be repainted automatically
  5627. this.components = [];
  5628. this.body = {
  5629. dom: this.dom,
  5630. domProps: this.props,
  5631. emitter: {
  5632. on: this.on.bind(this),
  5633. off: this.off.bind(this),
  5634. emit: this.emit.bind(this)
  5635. },
  5636. util: {
  5637. snap: null, // will be specified after TimeAxis is created
  5638. toScreen: me._toScreen.bind(me),
  5639. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  5640. toTime: me._toTime.bind(me),
  5641. toGlobalTime : me._toGlobalTime.bind(me)
  5642. }
  5643. };
  5644. // range
  5645. this.range = new Range(this.body);
  5646. this.components.push(this.range);
  5647. this.body.range = this.range;
  5648. // time axis
  5649. this.timeAxis = new TimeAxis(this.body);
  5650. this.components.push(this.timeAxis);
  5651. this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
  5652. // current time bar
  5653. this.currentTime = new CurrentTime(this.body);
  5654. this.components.push(this.currentTime);
  5655. // custom time bar
  5656. // Note: time bar will be attached in this.setOptions when selected
  5657. this.customTime = new CustomTime(this.body);
  5658. this.components.push(this.customTime);
  5659. // item set
  5660. this.linegraph = new LineGraph(this.body);
  5661. this.components.push(this.linegraph);
  5662. this.itemsData = null; // DataSet
  5663. this.groupsData = null; // DataSet
  5664. // apply options
  5665. if (options) {
  5666. this.setOptions(options);
  5667. }
  5668. // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
  5669. if (groups) {
  5670. this.setGroups(groups);
  5671. }
  5672. // create itemset
  5673. if (items) {
  5674. this.setItems(items);
  5675. }
  5676. else {
  5677. this.redraw();
  5678. }
  5679. }
  5680. // Extend the functionality from Core
  5681. Graph2d.prototype = new Core();
  5682. /**
  5683. * Set items
  5684. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  5685. */
  5686. Graph2d.prototype.setItems = function(items) {
  5687. var initialLoad = (this.itemsData == null);
  5688. // convert to type DataSet when needed
  5689. var newDataSet;
  5690. if (!items) {
  5691. newDataSet = null;
  5692. }
  5693. else if (items instanceof DataSet || items instanceof DataView) {
  5694. newDataSet = items;
  5695. }
  5696. else {
  5697. // turn an array into a dataset
  5698. newDataSet = new DataSet(items, {
  5699. type: {
  5700. start: 'Date',
  5701. end: 'Date'
  5702. }
  5703. });
  5704. }
  5705. // set items
  5706. this.itemsData = newDataSet;
  5707. this.linegraph && this.linegraph.setItems(newDataSet);
  5708. if (initialLoad && ('start' in this.options || 'end' in this.options)) {
  5709. this.fit();
  5710. var start = ('start' in this.options) ? util.convert(this.options.start, 'Date') : null;
  5711. var end = ('end' in this.options) ? util.convert(this.options.end, 'Date') : null;
  5712. this.setWindow(start, end);
  5713. }
  5714. };
  5715. /**
  5716. * Set groups
  5717. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  5718. */
  5719. Graph2d.prototype.setGroups = function(groups) {
  5720. // convert to type DataSet when needed
  5721. var newDataSet;
  5722. if (!groups) {
  5723. newDataSet = null;
  5724. }
  5725. else if (groups instanceof DataSet || groups instanceof DataView) {
  5726. newDataSet = groups;
  5727. }
  5728. else {
  5729. // turn an array into a dataset
  5730. newDataSet = new DataSet(groups);
  5731. }
  5732. this.groupsData = newDataSet;
  5733. this.linegraph.setGroups(newDataSet);
  5734. };
  5735. /**
  5736. * 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).
  5737. * @param groupId
  5738. * @param width
  5739. * @param height
  5740. */
  5741. Graph2d.prototype.getLegend = function(groupId, width, height) {
  5742. if (width === undefined) {width = 15;}
  5743. if (height === undefined) {height = 15;}
  5744. if (this.linegraph.groups[groupId] !== undefined) {
  5745. return this.linegraph.groups[groupId].getLegend(width,height);
  5746. }
  5747. else {
  5748. return "cannot find group:" + groupId;
  5749. }
  5750. }
  5751. /**
  5752. * This checks if the visible option of the supplied group (by ID) is true or false.
  5753. * @param groupId
  5754. * @returns {*}
  5755. */
  5756. Graph2d.prototype.isGroupVisible = function(groupId) {
  5757. if (this.linegraph.groups[groupId] !== undefined) {
  5758. return (this.linegraph.groups[groupId].visible && (this.linegraph.options.groups.visibility[groupId] === undefined || this.linegraph.options.groups.visibility[groupId] == true));
  5759. }
  5760. else {
  5761. return false;
  5762. }
  5763. }
  5764. /**
  5765. * Get the data range of the item set.
  5766. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  5767. * When no minimum is found, min==null
  5768. * When no maximum is found, max==null
  5769. */
  5770. Graph2d.prototype.getItemRange = function() {
  5771. var min = null;
  5772. var max = null;
  5773. // calculate min from start filed
  5774. for (var groupId in this.linegraph.groups) {
  5775. if (this.linegraph.groups.hasOwnProperty(groupId)) {
  5776. if (this.linegraph.groups[groupId].visible == true) {
  5777. for (var i = 0; i < this.linegraph.groups[groupId].itemsData.length; i++) {
  5778. var item = this.linegraph.groups[groupId].itemsData[i];
  5779. var value = util.convert(item.x, 'Date').valueOf();
  5780. min = min == null ? value : min > value ? value : min;
  5781. max = max == null ? value : max < value ? value : max;
  5782. }
  5783. }
  5784. }
  5785. }
  5786. return {
  5787. min: (min != null) ? new Date(min) : null,
  5788. max: (max != null) ? new Date(max) : null
  5789. };
  5790. };
  5791. module.exports = Graph2d;
  5792. /***/ },
  5793. /* 14 */
  5794. /***/ function(module, exports, __webpack_require__) {
  5795. /**
  5796. * @constructor DataStep
  5797. * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an
  5798. * end data point. The class itself determines the best scale (step size) based on the
  5799. * provided start Date, end Date, and minimumStep.
  5800. *
  5801. * If minimumStep is provided, the step size is chosen as close as possible
  5802. * to the minimumStep but larger than minimumStep. If minimumStep is not
  5803. * provided, the scale is set to 1 DAY.
  5804. * The minimumStep should correspond with the onscreen size of about 6 characters
  5805. *
  5806. * Alternatively, you can set a scale by hand.
  5807. * After creation, you can initialize the class by executing first(). Then you
  5808. * can iterate from the start date to the end date via next(). You can check if
  5809. * the end date is reached with the function hasNext(). After each step, you can
  5810. * retrieve the current date via getCurrent().
  5811. * The DataStep has scales ranging from milliseconds, seconds, minutes, hours,
  5812. * days, to years.
  5813. *
  5814. * Version: 1.2
  5815. *
  5816. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  5817. * or new Date(2010, 9, 21, 23, 45, 00)
  5818. * @param {Date} [end] The end date
  5819. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  5820. */
  5821. function DataStep(start, end, minimumStep, containerHeight, customRange) {
  5822. // variables
  5823. this.current = 0;
  5824. this.autoScale = true;
  5825. this.stepIndex = 0;
  5826. this.step = 1;
  5827. this.scale = 1;
  5828. this.marginStart;
  5829. this.marginEnd;
  5830. this.deadSpace = 0;
  5831. this.majorSteps = [1, 2, 5, 10];
  5832. this.minorSteps = [0.25, 0.5, 1, 2];
  5833. this.setRange(start, end, minimumStep, containerHeight, customRange);
  5834. }
  5835. /**
  5836. * Set a new range
  5837. * If minimumStep is provided, the step size is chosen as close as possible
  5838. * to the minimumStep but larger than minimumStep. If minimumStep is not
  5839. * provided, the scale is set to 1 DAY.
  5840. * The minimumStep should correspond with the onscreen size of about 6 characters
  5841. * @param {Number} [start] The start date and time.
  5842. * @param {Number} [end] The end date and time.
  5843. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  5844. */
  5845. DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, customRange) {
  5846. this._start = customRange.min === undefined ? start : customRange.min;
  5847. this._end = customRange.max === undefined ? end : customRange.max;
  5848. if (this._start == this._end) {
  5849. this._start -= 0.75;
  5850. this._end += 1;
  5851. }
  5852. if (this.autoScale) {
  5853. this.setMinimumStep(minimumStep, containerHeight);
  5854. }
  5855. this.setFirst(customRange);
  5856. };
  5857. /**
  5858. * Automatically determine the scale that bests fits the provided minimum step
  5859. * @param {Number} [minimumStep] The minimum step size in milliseconds
  5860. */
  5861. DataStep.prototype.setMinimumStep = function(minimumStep, containerHeight) {
  5862. // round to floor
  5863. var size = this._end - this._start;
  5864. var safeSize = size * 1.2;
  5865. var minimumStepValue = minimumStep * (safeSize / containerHeight);
  5866. var orderOfMagnitude = Math.round(Math.log(safeSize)/Math.LN10);
  5867. var minorStepIdx = -1;
  5868. var magnitudefactor = Math.pow(10,orderOfMagnitude);
  5869. var start = 0;
  5870. if (orderOfMagnitude < 0) {
  5871. start = orderOfMagnitude;
  5872. }
  5873. var solutionFound = false;
  5874. for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) {
  5875. magnitudefactor = Math.pow(10,i);
  5876. for (var j = 0; j < this.minorSteps.length; j++) {
  5877. var stepSize = magnitudefactor * this.minorSteps[j];
  5878. if (stepSize >= minimumStepValue) {
  5879. solutionFound = true;
  5880. minorStepIdx = j;
  5881. break;
  5882. }
  5883. }
  5884. if (solutionFound == true) {
  5885. break;
  5886. }
  5887. }
  5888. this.stepIndex = minorStepIdx;
  5889. this.scale = magnitudefactor;
  5890. this.step = magnitudefactor * this.minorSteps[minorStepIdx];
  5891. };
  5892. /**
  5893. * Round the current date to the first minor date value
  5894. * This must be executed once when the current date is set to start Date
  5895. */
  5896. DataStep.prototype.setFirst = function(customRange) {
  5897. if (customRange === undefined) {
  5898. customRange = {};
  5899. }
  5900. var niceStart = customRange.min === undefined ? this._start - (this.scale * 2 * this.minorSteps[this.stepIndex]) : customRange.min;
  5901. var niceEnd = customRange.max === undefined ? this._end + (this.scale * this.minorSteps[this.stepIndex]) : customRange.max;
  5902. this.marginEnd = customRange.max === undefined ? this.roundToMinor(niceEnd) : customRange.max;
  5903. this.marginStart = customRange.min === undefined ? this.roundToMinor(niceStart) : customRange.min;
  5904. this.deadSpace = this.roundToMinor(niceEnd) - niceEnd + this.roundToMinor(niceStart) - niceStart;
  5905. this.marginRange = this.marginEnd - this.marginStart;
  5906. this.current = this.marginEnd;
  5907. };
  5908. DataStep.prototype.roundToMinor = function(value) {
  5909. var rounded = value - (value % (this.scale * this.minorSteps[this.stepIndex]));
  5910. if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) {
  5911. return rounded + (this.scale * this.minorSteps[this.stepIndex]);
  5912. }
  5913. else {
  5914. return rounded;
  5915. }
  5916. }
  5917. /**
  5918. * Check if the there is a next step
  5919. * @return {boolean} true if the current date has not passed the end date
  5920. */
  5921. DataStep.prototype.hasNext = function () {
  5922. return (this.current >= this.marginStart);
  5923. };
  5924. /**
  5925. * Do the next step
  5926. */
  5927. DataStep.prototype.next = function() {
  5928. var prev = this.current;
  5929. this.current -= this.step;
  5930. // safety mechanism: if current time is still unchanged, move to the end
  5931. if (this.current == prev) {
  5932. this.current = this._end;
  5933. }
  5934. };
  5935. /**
  5936. * Do the next step
  5937. */
  5938. DataStep.prototype.previous = function() {
  5939. this.current += this.step;
  5940. this.marginEnd += this.step;
  5941. this.marginRange = this.marginEnd - this.marginStart;
  5942. };
  5943. /**
  5944. * Get the current datetime
  5945. * @return {String} current The current date
  5946. */
  5947. DataStep.prototype.getCurrent = function() {
  5948. var toPrecision = '' + Number(this.current).toPrecision(5);
  5949. for (var i = toPrecision.length-1; i > 0; i--) {
  5950. if (toPrecision[i] == "0") {
  5951. toPrecision = toPrecision.slice(0,i);
  5952. }
  5953. else if (toPrecision[i] == "." || toPrecision[i] == ",") {
  5954. toPrecision = toPrecision.slice(0,i);
  5955. break;
  5956. }
  5957. else{
  5958. break;
  5959. }
  5960. }
  5961. return toPrecision;
  5962. };
  5963. /**
  5964. * Snap a date to a rounded value.
  5965. * The snap intervals are dependent on the current scale and step.
  5966. * @param {Date} date the date to be snapped.
  5967. * @return {Date} snappedDate
  5968. */
  5969. DataStep.prototype.snap = function(date) {
  5970. };
  5971. /**
  5972. * Check if the current value is a major value (for example when the step
  5973. * is DAY, a major value is each first day of the MONTH)
  5974. * @return {boolean} true if current date is major, else false.
  5975. */
  5976. DataStep.prototype.isMajor = function() {
  5977. return (this.current % (this.scale * this.majorSteps[this.stepIndex]) == 0);
  5978. };
  5979. module.exports = DataStep;
  5980. /***/ },
  5981. /* 15 */
  5982. /***/ function(module, exports, __webpack_require__) {
  5983. var util = __webpack_require__(1);
  5984. var hammerUtil = __webpack_require__(44);
  5985. var moment = __webpack_require__(41);
  5986. var Component = __webpack_require__(18);
  5987. /**
  5988. * @constructor Range
  5989. * A Range controls a numeric range with a start and end value.
  5990. * The Range adjusts the range based on mouse events or programmatic changes,
  5991. * and triggers events when the range is changing or has been changed.
  5992. * @param {{dom: Object, domProps: Object, emitter: Emitter}} body
  5993. * @param {Object} [options] See description at Range.setOptions
  5994. */
  5995. function Range(body, options) {
  5996. var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
  5997. this.start = now.clone().add('days', -3).valueOf(); // Number
  5998. this.end = now.clone().add('days', 4).valueOf(); // Number
  5999. this.body = body;
  6000. // default options
  6001. this.defaultOptions = {
  6002. start: null,
  6003. end: null,
  6004. direction: 'horizontal', // 'horizontal' or 'vertical'
  6005. moveable: true,
  6006. zoomable: true,
  6007. min: null,
  6008. max: null,
  6009. zoomMin: 10, // milliseconds
  6010. zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000 // milliseconds
  6011. };
  6012. this.options = util.extend({}, this.defaultOptions);
  6013. this.props = {
  6014. touch: {}
  6015. };
  6016. this.animateTimer = null;
  6017. // drag listeners for dragging
  6018. this.body.emitter.on('dragstart', this._onDragStart.bind(this));
  6019. this.body.emitter.on('drag', this._onDrag.bind(this));
  6020. this.body.emitter.on('dragend', this._onDragEnd.bind(this));
  6021. // ignore dragging when holding
  6022. this.body.emitter.on('hold', this._onHold.bind(this));
  6023. // mouse wheel for zooming
  6024. this.body.emitter.on('mousewheel', this._onMouseWheel.bind(this));
  6025. this.body.emitter.on('DOMMouseScroll', this._onMouseWheel.bind(this)); // For FF
  6026. // pinch to zoom
  6027. this.body.emitter.on('touch', this._onTouch.bind(this));
  6028. this.body.emitter.on('pinch', this._onPinch.bind(this));
  6029. this.setOptions(options);
  6030. }
  6031. Range.prototype = new Component();
  6032. /**
  6033. * Set options for the range controller
  6034. * @param {Object} options Available options:
  6035. * {Number | Date | String} start Start date for the range
  6036. * {Number | Date | String} end End date for the range
  6037. * {Number} min Minimum value for start
  6038. * {Number} max Maximum value for end
  6039. * {Number} zoomMin Set a minimum value for
  6040. * (end - start).
  6041. * {Number} zoomMax Set a maximum value for
  6042. * (end - start).
  6043. * {Boolean} moveable Enable moving of the range
  6044. * by dragging. True by default
  6045. * {Boolean} zoomable Enable zooming of the range
  6046. * by pinching/scrolling. True by default
  6047. */
  6048. Range.prototype.setOptions = function (options) {
  6049. if (options) {
  6050. // copy the options that we know
  6051. var fields = ['direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable', 'activate'];
  6052. util.selectiveExtend(fields, this.options, options);
  6053. if ('start' in options || 'end' in options) {
  6054. // apply a new range. both start and end are optional
  6055. this.setRange(options.start, options.end);
  6056. }
  6057. }
  6058. };
  6059. /**
  6060. * Test whether direction has a valid value
  6061. * @param {String} direction 'horizontal' or 'vertical'
  6062. */
  6063. function validateDirection (direction) {
  6064. if (direction != 'horizontal' && direction != 'vertical') {
  6065. throw new TypeError('Unknown direction "' + direction + '". ' +
  6066. 'Choose "horizontal" or "vertical".');
  6067. }
  6068. }
  6069. /**
  6070. * Set a new start and end range
  6071. * @param {Date | Number | String} [start]
  6072. * @param {Date | Number | String} [end]
  6073. * @param {boolean | number} [animate=false] If true, the range is animated
  6074. * smoothly to the new window.
  6075. * If animate is a number, the
  6076. * number is taken as duration
  6077. * Default duration is 500 ms.
  6078. *
  6079. */
  6080. Range.prototype.setRange = function(start, end, animate) {
  6081. var _start = start != undefined ? util.convert(start, 'Date').valueOf() : null;
  6082. var _end = end != undefined ? util.convert(end, 'Date').valueOf() : null;
  6083. this._cancelAnimation();
  6084. if (animate) {
  6085. var me = this;
  6086. var initStart = this.start;
  6087. var initEnd = this.end;
  6088. var duration = typeof animate === 'number' ? animate : 500;
  6089. var initTime = new Date().valueOf();
  6090. var anyChanged = false;
  6091. function next() {
  6092. if (!me.props.touch.dragging) {
  6093. var now = new Date().valueOf();
  6094. var time = now - initTime;
  6095. var done = time > duration;
  6096. var s = (done || _start === null) ? _start : util.easeInOutQuad(time, initStart, _start, duration);
  6097. var e = (done || _end === null) ? _end : util.easeInOutQuad(time, initEnd, _end, duration);
  6098. changed = me._applyRange(s, e);
  6099. anyChanged = anyChanged || changed;
  6100. if (changed) {
  6101. me.body.emitter.emit('rangechange', {start: new Date(me.start), end: new Date(me.end)});
  6102. }
  6103. if (done) {
  6104. if (anyChanged) {
  6105. me.body.emitter.emit('rangechanged', {start: new Date(me.start), end: new Date(me.end)});
  6106. }
  6107. }
  6108. else {
  6109. // animate with as high as possible frame rate, leave 20 ms in between
  6110. // each to prevent the browser from blocking
  6111. me.animateTimer = setTimeout(next, 20);
  6112. }
  6113. }
  6114. }
  6115. return next();
  6116. }
  6117. else {
  6118. var changed = this._applyRange(_start, _end);
  6119. if (changed) {
  6120. var params = {start: new Date(this.start), end: new Date(this.end)};
  6121. this.body.emitter.emit('rangechange', params);
  6122. this.body.emitter.emit('rangechanged', params);
  6123. }
  6124. }
  6125. };
  6126. /**
  6127. * Stop an animation
  6128. * @private
  6129. */
  6130. Range.prototype._cancelAnimation = function () {
  6131. if (this.animateTimer) {
  6132. clearTimeout(this.animateTimer);
  6133. this.animateTimer = null;
  6134. }
  6135. };
  6136. /**
  6137. * Set a new start and end range. This method is the same as setRange, but
  6138. * does not trigger a range change and range changed event, and it returns
  6139. * true when the range is changed
  6140. * @param {Number} [start]
  6141. * @param {Number} [end]
  6142. * @return {Boolean} changed
  6143. * @private
  6144. */
  6145. Range.prototype._applyRange = function(start, end) {
  6146. var newStart = (start != null) ? util.convert(start, 'Date').valueOf() : this.start,
  6147. newEnd = (end != null) ? util.convert(end, 'Date').valueOf() : this.end,
  6148. max = (this.options.max != null) ? util.convert(this.options.max, 'Date').valueOf() : null,
  6149. min = (this.options.min != null) ? util.convert(this.options.min, 'Date').valueOf() : null,
  6150. diff;
  6151. // check for valid number
  6152. if (isNaN(newStart) || newStart === null) {
  6153. throw new Error('Invalid start "' + start + '"');
  6154. }
  6155. if (isNaN(newEnd) || newEnd === null) {
  6156. throw new Error('Invalid end "' + end + '"');
  6157. }
  6158. // prevent start < end
  6159. if (newEnd < newStart) {
  6160. newEnd = newStart;
  6161. }
  6162. // prevent start < min
  6163. if (min !== null) {
  6164. if (newStart < min) {
  6165. diff = (min - newStart);
  6166. newStart += diff;
  6167. newEnd += diff;
  6168. // prevent end > max
  6169. if (max != null) {
  6170. if (newEnd > max) {
  6171. newEnd = max;
  6172. }
  6173. }
  6174. }
  6175. }
  6176. // prevent end > max
  6177. if (max !== null) {
  6178. if (newEnd > max) {
  6179. diff = (newEnd - max);
  6180. newStart -= diff;
  6181. newEnd -= diff;
  6182. // prevent start < min
  6183. if (min != null) {
  6184. if (newStart < min) {
  6185. newStart = min;
  6186. }
  6187. }
  6188. }
  6189. }
  6190. // prevent (end-start) < zoomMin
  6191. if (this.options.zoomMin !== null) {
  6192. var zoomMin = parseFloat(this.options.zoomMin);
  6193. if (zoomMin < 0) {
  6194. zoomMin = 0;
  6195. }
  6196. if ((newEnd - newStart) < zoomMin) {
  6197. if ((this.end - this.start) === zoomMin) {
  6198. // ignore this action, we are already zoomed to the minimum
  6199. newStart = this.start;
  6200. newEnd = this.end;
  6201. }
  6202. else {
  6203. // zoom to the minimum
  6204. diff = (zoomMin - (newEnd - newStart));
  6205. newStart -= diff / 2;
  6206. newEnd += diff / 2;
  6207. }
  6208. }
  6209. }
  6210. // prevent (end-start) > zoomMax
  6211. if (this.options.zoomMax !== null) {
  6212. var zoomMax = parseFloat(this.options.zoomMax);
  6213. if (zoomMax < 0) {
  6214. zoomMax = 0;
  6215. }
  6216. if ((newEnd - newStart) > zoomMax) {
  6217. if ((this.end - this.start) === zoomMax) {
  6218. // ignore this action, we are already zoomed to the maximum
  6219. newStart = this.start;
  6220. newEnd = this.end;
  6221. }
  6222. else {
  6223. // zoom to the maximum
  6224. diff = ((newEnd - newStart) - zoomMax);
  6225. newStart += diff / 2;
  6226. newEnd -= diff / 2;
  6227. }
  6228. }
  6229. }
  6230. var changed = (this.start != newStart || this.end != newEnd);
  6231. this.start = newStart;
  6232. this.end = newEnd;
  6233. return changed;
  6234. };
  6235. /**
  6236. * Retrieve the current range.
  6237. * @return {Object} An object with start and end properties
  6238. */
  6239. Range.prototype.getRange = function() {
  6240. return {
  6241. start: this.start,
  6242. end: this.end
  6243. };
  6244. };
  6245. /**
  6246. * Calculate the conversion offset and scale for current range, based on
  6247. * the provided width
  6248. * @param {Number} width
  6249. * @returns {{offset: number, scale: number}} conversion
  6250. */
  6251. Range.prototype.conversion = function (width) {
  6252. return Range.conversion(this.start, this.end, width);
  6253. };
  6254. /**
  6255. * Static method to calculate the conversion offset and scale for a range,
  6256. * based on the provided start, end, and width
  6257. * @param {Number} start
  6258. * @param {Number} end
  6259. * @param {Number} width
  6260. * @returns {{offset: number, scale: number}} conversion
  6261. */
  6262. Range.conversion = function (start, end, width) {
  6263. if (width != 0 && (end - start != 0)) {
  6264. return {
  6265. offset: start,
  6266. scale: width / (end - start)
  6267. }
  6268. }
  6269. else {
  6270. return {
  6271. offset: 0,
  6272. scale: 1
  6273. };
  6274. }
  6275. };
  6276. /**
  6277. * Start dragging horizontally or vertically
  6278. * @param {Event} event
  6279. * @private
  6280. */
  6281. Range.prototype._onDragStart = function(event) {
  6282. // only allow dragging when configured as movable
  6283. if (!this.options.moveable) return;
  6284. // refuse to drag when we where pinching to prevent the timeline make a jump
  6285. // when releasing the fingers in opposite order from the touch screen
  6286. if (!this.props.touch.allowDragging) return;
  6287. this.props.touch.start = this.start;
  6288. this.props.touch.end = this.end;
  6289. this.props.touch.dragging = true;
  6290. if (this.body.dom.root) {
  6291. this.body.dom.root.style.cursor = 'move';
  6292. }
  6293. };
  6294. /**
  6295. * Perform dragging operation
  6296. * @param {Event} event
  6297. * @private
  6298. */
  6299. Range.prototype._onDrag = function (event) {
  6300. // only allow dragging when configured as movable
  6301. if (!this.options.moveable) return;
  6302. var direction = this.options.direction;
  6303. validateDirection(direction);
  6304. // refuse to drag when we where pinching to prevent the timeline make a jump
  6305. // when releasing the fingers in opposite order from the touch screen
  6306. if (!this.props.touch.allowDragging) return;
  6307. var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY;
  6308. var interval = (this.props.touch.end - this.props.touch.start);
  6309. var width = (direction == 'horizontal') ? this.body.domProps.center.width : this.body.domProps.center.height;
  6310. var diffRange = -delta / width * interval;
  6311. this._applyRange(this.props.touch.start + diffRange, this.props.touch.end + diffRange);
  6312. // fire a rangechange event
  6313. this.body.emitter.emit('rangechange', {
  6314. start: new Date(this.start),
  6315. end: new Date(this.end)
  6316. });
  6317. };
  6318. /**
  6319. * Stop dragging operation
  6320. * @param {event} event
  6321. * @private
  6322. */
  6323. Range.prototype._onDragEnd = function (event) {
  6324. // only allow dragging when configured as movable
  6325. if (!this.options.moveable) return;
  6326. // refuse to drag when we where pinching to prevent the timeline make a jump
  6327. // when releasing the fingers in opposite order from the touch screen
  6328. if (!this.props.touch.allowDragging) return;
  6329. this.props.touch.dragging = false;
  6330. if (this.body.dom.root) {
  6331. this.body.dom.root.style.cursor = 'auto';
  6332. }
  6333. // fire a rangechanged event
  6334. this.body.emitter.emit('rangechanged', {
  6335. start: new Date(this.start),
  6336. end: new Date(this.end)
  6337. });
  6338. };
  6339. /**
  6340. * Event handler for mouse wheel event, used to zoom
  6341. * Code from http://adomas.org/javascript-mouse-wheel/
  6342. * @param {Event} event
  6343. * @private
  6344. */
  6345. Range.prototype._onMouseWheel = function(event) {
  6346. // only allow zooming when configured as zoomable and moveable
  6347. if (!(this.options.zoomable && this.options.moveable)) return;
  6348. // retrieve delta
  6349. var delta = 0;
  6350. if (event.wheelDelta) { /* IE/Opera. */
  6351. delta = event.wheelDelta / 120;
  6352. } else if (event.detail) { /* Mozilla case. */
  6353. // In Mozilla, sign of delta is different than in IE.
  6354. // Also, delta is multiple of 3.
  6355. delta = -event.detail / 3;
  6356. }
  6357. // If delta is nonzero, handle it.
  6358. // Basically, delta is now positive if wheel was scrolled up,
  6359. // and negative, if wheel was scrolled down.
  6360. if (delta) {
  6361. // perform the zoom action. Delta is normally 1 or -1
  6362. // adjust a negative delta such that zooming in with delta 0.1
  6363. // equals zooming out with a delta -0.1
  6364. var scale;
  6365. if (delta < 0) {
  6366. scale = 1 - (delta / 5);
  6367. }
  6368. else {
  6369. scale = 1 / (1 + (delta / 5)) ;
  6370. }
  6371. // calculate center, the date to zoom around
  6372. var gesture = hammerUtil.fakeGesture(this, event),
  6373. pointer = getPointer(gesture.center, this.body.dom.center),
  6374. pointerDate = this._pointerToDate(pointer);
  6375. this.zoom(scale, pointerDate);
  6376. }
  6377. // Prevent default actions caused by mouse wheel
  6378. // (else the page and timeline both zoom and scroll)
  6379. event.preventDefault();
  6380. };
  6381. /**
  6382. * Start of a touch gesture
  6383. * @private
  6384. */
  6385. Range.prototype._onTouch = function (event) {
  6386. this.props.touch.start = this.start;
  6387. this.props.touch.end = this.end;
  6388. this.props.touch.allowDragging = true;
  6389. this.props.touch.center = null;
  6390. };
  6391. /**
  6392. * On start of a hold gesture
  6393. * @private
  6394. */
  6395. Range.prototype._onHold = function () {
  6396. this.props.touch.allowDragging = false;
  6397. };
  6398. /**
  6399. * Handle pinch event
  6400. * @param {Event} event
  6401. * @private
  6402. */
  6403. Range.prototype._onPinch = function (event) {
  6404. // only allow zooming when configured as zoomable and moveable
  6405. if (!(this.options.zoomable && this.options.moveable)) return;
  6406. this.props.touch.allowDragging = false;
  6407. if (event.gesture.touches.length > 1) {
  6408. if (!this.props.touch.center) {
  6409. this.props.touch.center = getPointer(event.gesture.center, this.body.dom.center);
  6410. }
  6411. var scale = 1 / event.gesture.scale,
  6412. initDate = this._pointerToDate(this.props.touch.center);
  6413. // calculate new start and end
  6414. var newStart = parseInt(initDate + (this.props.touch.start - initDate) * scale);
  6415. var newEnd = parseInt(initDate + (this.props.touch.end - initDate) * scale);
  6416. // apply new range
  6417. this.setRange(newStart, newEnd);
  6418. }
  6419. };
  6420. /**
  6421. * Helper function to calculate the center date for zooming
  6422. * @param {{x: Number, y: Number}} pointer
  6423. * @return {number} date
  6424. * @private
  6425. */
  6426. Range.prototype._pointerToDate = function (pointer) {
  6427. var conversion;
  6428. var direction = this.options.direction;
  6429. validateDirection(direction);
  6430. if (direction == 'horizontal') {
  6431. var width = this.body.domProps.center.width;
  6432. conversion = this.conversion(width);
  6433. return pointer.x / conversion.scale + conversion.offset;
  6434. }
  6435. else {
  6436. var height = this.body.domProps.center.height;
  6437. conversion = this.conversion(height);
  6438. return pointer.y / conversion.scale + conversion.offset;
  6439. }
  6440. };
  6441. /**
  6442. * Get the pointer location relative to the location of the dom element
  6443. * @param {{pageX: Number, pageY: Number}} touch
  6444. * @param {Element} element HTML DOM element
  6445. * @return {{x: Number, y: Number}} pointer
  6446. * @private
  6447. */
  6448. function getPointer (touch, element) {
  6449. return {
  6450. x: touch.pageX - util.getAbsoluteLeft(element),
  6451. y: touch.pageY - util.getAbsoluteTop(element)
  6452. };
  6453. }
  6454. /**
  6455. * Zoom the range the given scale in or out. Start and end date will
  6456. * be adjusted, and the timeline will be redrawn. You can optionally give a
  6457. * date around which to zoom.
  6458. * For example, try scale = 0.9 or 1.1
  6459. * @param {Number} scale Scaling factor. Values above 1 will zoom out,
  6460. * values below 1 will zoom in.
  6461. * @param {Number} [center] Value representing a date around which will
  6462. * be zoomed.
  6463. */
  6464. Range.prototype.zoom = function(scale, center) {
  6465. // if centerDate is not provided, take it half between start Date and end Date
  6466. if (center == null) {
  6467. center = (this.start + this.end) / 2;
  6468. }
  6469. // calculate new start and end
  6470. var newStart = center + (this.start - center) * scale;
  6471. var newEnd = center + (this.end - center) * scale;
  6472. this.setRange(newStart, newEnd);
  6473. };
  6474. /**
  6475. * Move the range with a given delta to the left or right. Start and end
  6476. * value will be adjusted. For example, try delta = 0.1 or -0.1
  6477. * @param {Number} delta Moving amount. Positive value will move right,
  6478. * negative value will move left
  6479. */
  6480. Range.prototype.move = function(delta) {
  6481. // zoom start Date and end Date relative to the centerDate
  6482. var diff = (this.end - this.start);
  6483. // apply new values
  6484. var newStart = this.start + diff * delta;
  6485. var newEnd = this.end + diff * delta;
  6486. // TODO: reckon with min and max range
  6487. this.start = newStart;
  6488. this.end = newEnd;
  6489. };
  6490. /**
  6491. * Move the range to a new center point
  6492. * @param {Number} moveTo New center point of the range
  6493. */
  6494. Range.prototype.moveTo = function(moveTo) {
  6495. var center = (this.start + this.end) / 2;
  6496. var diff = center - moveTo;
  6497. // calculate new start and end
  6498. var newStart = this.start - diff;
  6499. var newEnd = this.end - diff;
  6500. this.setRange(newStart, newEnd);
  6501. };
  6502. module.exports = Range;
  6503. /***/ },
  6504. /* 16 */
  6505. /***/ function(module, exports, __webpack_require__) {
  6506. // Utility functions for ordering and stacking of items
  6507. var EPSILON = 0.001; // used when checking collisions, to prevent round-off errors
  6508. /**
  6509. * Order items by their start data
  6510. * @param {Item[]} items
  6511. */
  6512. exports.orderByStart = function(items) {
  6513. items.sort(function (a, b) {
  6514. return a.data.start - b.data.start;
  6515. });
  6516. };
  6517. /**
  6518. * Order items by their end date. If they have no end date, their start date
  6519. * is used.
  6520. * @param {Item[]} items
  6521. */
  6522. exports.orderByEnd = function(items) {
  6523. items.sort(function (a, b) {
  6524. var aTime = ('end' in a.data) ? a.data.end : a.data.start,
  6525. bTime = ('end' in b.data) ? b.data.end : b.data.start;
  6526. return aTime - bTime;
  6527. });
  6528. };
  6529. /**
  6530. * Adjust vertical positions of the items such that they don't overlap each
  6531. * other.
  6532. * @param {Item[]} items
  6533. * All visible items
  6534. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  6535. * Margins between items and between items and the axis.
  6536. * @param {boolean} [force=false]
  6537. * If true, all items will be repositioned. If false (default), only
  6538. * items having a top===null will be re-stacked
  6539. */
  6540. exports.stack = function(items, margin, force) {
  6541. var i, iMax;
  6542. if (force) {
  6543. // reset top position of all items
  6544. for (i = 0, iMax = items.length; i < iMax; i++) {
  6545. items[i].top = null;
  6546. }
  6547. }
  6548. // calculate new, non-overlapping positions
  6549. for (i = 0, iMax = items.length; i < iMax; i++) {
  6550. var item = items[i];
  6551. if (item.top === null) {
  6552. // initialize top position
  6553. item.top = margin.axis;
  6554. do {
  6555. // TODO: optimize checking for overlap. when there is a gap without items,
  6556. // you only need to check for items from the next item on, not from zero
  6557. var collidingItem = null;
  6558. for (var j = 0, jj = items.length; j < jj; j++) {
  6559. var other = items[j];
  6560. if (other.top !== null && other !== item && exports.collision(item, other, margin.item)) {
  6561. collidingItem = other;
  6562. break;
  6563. }
  6564. }
  6565. if (collidingItem != null) {
  6566. // There is a collision. Reposition the items above the colliding element
  6567. item.top = collidingItem.top + collidingItem.height + margin.item.vertical;
  6568. }
  6569. } while (collidingItem);
  6570. }
  6571. }
  6572. };
  6573. /**
  6574. * Adjust vertical positions of the items without stacking them
  6575. * @param {Item[]} items
  6576. * All visible items
  6577. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  6578. * Margins between items and between items and the axis.
  6579. */
  6580. exports.nostack = function(items, margin) {
  6581. var i, iMax;
  6582. // reset top position of all items
  6583. for (i = 0, iMax = items.length; i < iMax; i++) {
  6584. items[i].top = margin.axis;
  6585. }
  6586. };
  6587. /**
  6588. * Test if the two provided items collide
  6589. * The items must have parameters left, width, top, and height.
  6590. * @param {Item} a The first item
  6591. * @param {Item} b The second item
  6592. * @param {{horizontal: number, vertical: number}} margin
  6593. * An object containing a horizontal and vertical
  6594. * minimum required margin.
  6595. * @return {boolean} true if a and b collide, else false
  6596. */
  6597. exports.collision = function(a, b, margin) {
  6598. return ((a.left - margin.horizontal + EPSILON) < (b.left + b.width) &&
  6599. (a.left + a.width + margin.horizontal - EPSILON) > b.left &&
  6600. (a.top - margin.vertical + EPSILON) < (b.top + b.height) &&
  6601. (a.top + a.height + margin.vertical - EPSILON) > b.top);
  6602. };
  6603. /***/ },
  6604. /* 17 */
  6605. /***/ function(module, exports, __webpack_require__) {
  6606. var moment = __webpack_require__(41);
  6607. /**
  6608. * @constructor TimeStep
  6609. * The class TimeStep is an iterator for dates. You provide a start date and an
  6610. * end date. The class itself determines the best scale (step size) based on the
  6611. * provided start Date, end Date, and minimumStep.
  6612. *
  6613. * If minimumStep is provided, the step size is chosen as close as possible
  6614. * to the minimumStep but larger than minimumStep. If minimumStep is not
  6615. * provided, the scale is set to 1 DAY.
  6616. * The minimumStep should correspond with the onscreen size of about 6 characters
  6617. *
  6618. * Alternatively, you can set a scale by hand.
  6619. * After creation, you can initialize the class by executing first(). Then you
  6620. * can iterate from the start date to the end date via next(). You can check if
  6621. * the end date is reached with the function hasNext(). After each step, you can
  6622. * retrieve the current date via getCurrent().
  6623. * The TimeStep has scales ranging from milliseconds, seconds, minutes, hours,
  6624. * days, to years.
  6625. *
  6626. * Version: 1.2
  6627. *
  6628. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  6629. * or new Date(2010, 9, 21, 23, 45, 00)
  6630. * @param {Date} [end] The end date
  6631. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  6632. */
  6633. function TimeStep(start, end, minimumStep) {
  6634. // variables
  6635. this.current = new Date();
  6636. this._start = new Date();
  6637. this._end = new Date();
  6638. this.autoScale = true;
  6639. this.scale = TimeStep.SCALE.DAY;
  6640. this.step = 1;
  6641. // initialize the range
  6642. this.setRange(start, end, minimumStep);
  6643. }
  6644. /// enum scale
  6645. TimeStep.SCALE = {
  6646. MILLISECOND: 1,
  6647. SECOND: 2,
  6648. MINUTE: 3,
  6649. HOUR: 4,
  6650. DAY: 5,
  6651. WEEKDAY: 6,
  6652. MONTH: 7,
  6653. YEAR: 8
  6654. };
  6655. /**
  6656. * Set a new range
  6657. * If minimumStep is provided, the step size is chosen as close as possible
  6658. * to the minimumStep but larger than minimumStep. If minimumStep is not
  6659. * provided, the scale is set to 1 DAY.
  6660. * The minimumStep should correspond with the onscreen size of about 6 characters
  6661. * @param {Date} [start] The start date and time.
  6662. * @param {Date} [end] The end date and time.
  6663. * @param {int} [minimumStep] Optional. Minimum step size in milliseconds
  6664. */
  6665. TimeStep.prototype.setRange = function(start, end, minimumStep) {
  6666. if (!(start instanceof Date) || !(end instanceof Date)) {
  6667. throw "No legal start or end date in method setRange";
  6668. }
  6669. this._start = (start != undefined) ? new Date(start.valueOf()) : new Date();
  6670. this._end = (end != undefined) ? new Date(end.valueOf()) : new Date();
  6671. if (this.autoScale) {
  6672. this.setMinimumStep(minimumStep);
  6673. }
  6674. };
  6675. /**
  6676. * Set the range iterator to the start date.
  6677. */
  6678. TimeStep.prototype.first = function() {
  6679. this.current = new Date(this._start.valueOf());
  6680. this.roundToMinor();
  6681. };
  6682. /**
  6683. * Round the current date to the first minor date value
  6684. * This must be executed once when the current date is set to start Date
  6685. */
  6686. TimeStep.prototype.roundToMinor = function() {
  6687. // round to floor
  6688. // IMPORTANT: we have no breaks in this switch! (this is no bug)
  6689. //noinspection FallthroughInSwitchStatementJS
  6690. switch (this.scale) {
  6691. case TimeStep.SCALE.YEAR:
  6692. this.current.setFullYear(this.step * Math.floor(this.current.getFullYear() / this.step));
  6693. this.current.setMonth(0);
  6694. case TimeStep.SCALE.MONTH: this.current.setDate(1);
  6695. case TimeStep.SCALE.DAY: // intentional fall through
  6696. case TimeStep.SCALE.WEEKDAY: this.current.setHours(0);
  6697. case TimeStep.SCALE.HOUR: this.current.setMinutes(0);
  6698. case TimeStep.SCALE.MINUTE: this.current.setSeconds(0);
  6699. case TimeStep.SCALE.SECOND: this.current.setMilliseconds(0);
  6700. //case TimeStep.SCALE.MILLISECOND: // nothing to do for milliseconds
  6701. }
  6702. if (this.step != 1) {
  6703. // round down to the first minor value that is a multiple of the current step size
  6704. switch (this.scale) {
  6705. case TimeStep.SCALE.MILLISECOND: this.current.setMilliseconds(this.current.getMilliseconds() - this.current.getMilliseconds() % this.step); break;
  6706. case TimeStep.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() - this.current.getSeconds() % this.step); break;
  6707. case TimeStep.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() - this.current.getMinutes() % this.step); break;
  6708. case TimeStep.SCALE.HOUR: this.current.setHours(this.current.getHours() - this.current.getHours() % this.step); break;
  6709. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  6710. case TimeStep.SCALE.DAY: this.current.setDate((this.current.getDate()-1) - (this.current.getDate()-1) % this.step + 1); break;
  6711. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() - this.current.getMonth() % this.step); break;
  6712. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() - this.current.getFullYear() % this.step); break;
  6713. default: break;
  6714. }
  6715. }
  6716. };
  6717. /**
  6718. * Check if the there is a next step
  6719. * @return {boolean} true if the current date has not passed the end date
  6720. */
  6721. TimeStep.prototype.hasNext = function () {
  6722. return (this.current.valueOf() <= this._end.valueOf());
  6723. };
  6724. /**
  6725. * Do the next step
  6726. */
  6727. TimeStep.prototype.next = function() {
  6728. var prev = this.current.valueOf();
  6729. // Two cases, needed to prevent issues with switching daylight savings
  6730. // (end of March and end of October)
  6731. if (this.current.getMonth() < 6) {
  6732. switch (this.scale) {
  6733. case TimeStep.SCALE.MILLISECOND:
  6734. this.current = new Date(this.current.valueOf() + this.step); break;
  6735. case TimeStep.SCALE.SECOND: this.current = new Date(this.current.valueOf() + this.step * 1000); break;
  6736. case TimeStep.SCALE.MINUTE: this.current = new Date(this.current.valueOf() + this.step * 1000 * 60); break;
  6737. case TimeStep.SCALE.HOUR:
  6738. this.current = new Date(this.current.valueOf() + this.step * 1000 * 60 * 60);
  6739. // in case of skipping an hour for daylight savings, adjust the hour again (else you get: 0h 5h 9h ... instead of 0h 4h 8h ...)
  6740. var h = this.current.getHours();
  6741. this.current.setHours(h - (h % this.step));
  6742. break;
  6743. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  6744. case TimeStep.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break;
  6745. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break;
  6746. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break;
  6747. default: break;
  6748. }
  6749. }
  6750. else {
  6751. switch (this.scale) {
  6752. case TimeStep.SCALE.MILLISECOND: this.current = new Date(this.current.valueOf() + this.step); break;
  6753. case TimeStep.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() + this.step); break;
  6754. case TimeStep.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() + this.step); break;
  6755. case TimeStep.SCALE.HOUR: this.current.setHours(this.current.getHours() + this.step); break;
  6756. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  6757. case TimeStep.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break;
  6758. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break;
  6759. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break;
  6760. default: break;
  6761. }
  6762. }
  6763. if (this.step != 1) {
  6764. // round down to the correct major value
  6765. switch (this.scale) {
  6766. case TimeStep.SCALE.MILLISECOND: if(this.current.getMilliseconds() < this.step) this.current.setMilliseconds(0); break;
  6767. case TimeStep.SCALE.SECOND: if(this.current.getSeconds() < this.step) this.current.setSeconds(0); break;
  6768. case TimeStep.SCALE.MINUTE: if(this.current.getMinutes() < this.step) this.current.setMinutes(0); break;
  6769. case TimeStep.SCALE.HOUR: if(this.current.getHours() < this.step) this.current.setHours(0); break;
  6770. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  6771. case TimeStep.SCALE.DAY: if(this.current.getDate() < this.step+1) this.current.setDate(1); break;
  6772. case TimeStep.SCALE.MONTH: if(this.current.getMonth() < this.step) this.current.setMonth(0); break;
  6773. case TimeStep.SCALE.YEAR: break; // nothing to do for year
  6774. default: break;
  6775. }
  6776. }
  6777. // safety mechanism: if current time is still unchanged, move to the end
  6778. if (this.current.valueOf() == prev) {
  6779. this.current = new Date(this._end.valueOf());
  6780. }
  6781. };
  6782. /**
  6783. * Get the current datetime
  6784. * @return {Date} current The current date
  6785. */
  6786. TimeStep.prototype.getCurrent = function() {
  6787. return this.current;
  6788. };
  6789. /**
  6790. * Set a custom scale. Autoscaling will be disabled.
  6791. * For example setScale(SCALE.MINUTES, 5) will result
  6792. * in minor steps of 5 minutes, and major steps of an hour.
  6793. *
  6794. * @param {TimeStep.SCALE} newScale
  6795. * A scale. Choose from SCALE.MILLISECOND,
  6796. * SCALE.SECOND, SCALE.MINUTE, SCALE.HOUR,
  6797. * SCALE.WEEKDAY, SCALE.DAY, SCALE.MONTH,
  6798. * SCALE.YEAR.
  6799. * @param {Number} newStep A step size, by default 1. Choose for
  6800. * example 1, 2, 5, or 10.
  6801. */
  6802. TimeStep.prototype.setScale = function(newScale, newStep) {
  6803. this.scale = newScale;
  6804. if (newStep > 0) {
  6805. this.step = newStep;
  6806. }
  6807. this.autoScale = false;
  6808. };
  6809. /**
  6810. * Enable or disable autoscaling
  6811. * @param {boolean} enable If true, autoascaling is set true
  6812. */
  6813. TimeStep.prototype.setAutoScale = function (enable) {
  6814. this.autoScale = enable;
  6815. };
  6816. /**
  6817. * Automatically determine the scale that bests fits the provided minimum step
  6818. * @param {Number} [minimumStep] The minimum step size in milliseconds
  6819. */
  6820. TimeStep.prototype.setMinimumStep = function(minimumStep) {
  6821. if (minimumStep == undefined) {
  6822. return;
  6823. }
  6824. var stepYear = (1000 * 60 * 60 * 24 * 30 * 12);
  6825. var stepMonth = (1000 * 60 * 60 * 24 * 30);
  6826. var stepDay = (1000 * 60 * 60 * 24);
  6827. var stepHour = (1000 * 60 * 60);
  6828. var stepMinute = (1000 * 60);
  6829. var stepSecond = (1000);
  6830. var stepMillisecond= (1);
  6831. // find the smallest step that is larger than the provided minimumStep
  6832. if (stepYear*1000 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1000;}
  6833. if (stepYear*500 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 500;}
  6834. if (stepYear*100 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 100;}
  6835. if (stepYear*50 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 50;}
  6836. if (stepYear*10 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 10;}
  6837. if (stepYear*5 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 5;}
  6838. if (stepYear > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1;}
  6839. if (stepMonth*3 > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 3;}
  6840. if (stepMonth > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 1;}
  6841. if (stepDay*5 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 5;}
  6842. if (stepDay*2 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 2;}
  6843. if (stepDay > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 1;}
  6844. if (stepDay/2 > minimumStep) {this.scale = TimeStep.SCALE.WEEKDAY; this.step = 1;}
  6845. if (stepHour*4 > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 4;}
  6846. if (stepHour > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 1;}
  6847. if (stepMinute*15 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 15;}
  6848. if (stepMinute*10 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 10;}
  6849. if (stepMinute*5 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 5;}
  6850. if (stepMinute > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 1;}
  6851. if (stepSecond*15 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 15;}
  6852. if (stepSecond*10 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 10;}
  6853. if (stepSecond*5 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 5;}
  6854. if (stepSecond > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 1;}
  6855. if (stepMillisecond*200 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 200;}
  6856. if (stepMillisecond*100 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 100;}
  6857. if (stepMillisecond*50 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 50;}
  6858. if (stepMillisecond*10 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 10;}
  6859. if (stepMillisecond*5 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 5;}
  6860. if (stepMillisecond > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 1;}
  6861. };
  6862. /**
  6863. * Snap a date to a rounded value.
  6864. * The snap intervals are dependent on the current scale and step.
  6865. * @param {Date} date the date to be snapped.
  6866. * @return {Date} snappedDate
  6867. */
  6868. TimeStep.prototype.snap = function(date) {
  6869. var clone = new Date(date.valueOf());
  6870. if (this.scale == TimeStep.SCALE.YEAR) {
  6871. var year = clone.getFullYear() + Math.round(clone.getMonth() / 12);
  6872. clone.setFullYear(Math.round(year / this.step) * this.step);
  6873. clone.setMonth(0);
  6874. clone.setDate(0);
  6875. clone.setHours(0);
  6876. clone.setMinutes(0);
  6877. clone.setSeconds(0);
  6878. clone.setMilliseconds(0);
  6879. }
  6880. else if (this.scale == TimeStep.SCALE.MONTH) {
  6881. if (clone.getDate() > 15) {
  6882. clone.setDate(1);
  6883. clone.setMonth(clone.getMonth() + 1);
  6884. // important: first set Date to 1, after that change the month.
  6885. }
  6886. else {
  6887. clone.setDate(1);
  6888. }
  6889. clone.setHours(0);
  6890. clone.setMinutes(0);
  6891. clone.setSeconds(0);
  6892. clone.setMilliseconds(0);
  6893. }
  6894. else if (this.scale == TimeStep.SCALE.DAY) {
  6895. //noinspection FallthroughInSwitchStatementJS
  6896. switch (this.step) {
  6897. case 5:
  6898. case 2:
  6899. clone.setHours(Math.round(clone.getHours() / 24) * 24); break;
  6900. default:
  6901. clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
  6902. }
  6903. clone.setMinutes(0);
  6904. clone.setSeconds(0);
  6905. clone.setMilliseconds(0);
  6906. }
  6907. else if (this.scale == TimeStep.SCALE.WEEKDAY) {
  6908. //noinspection FallthroughInSwitchStatementJS
  6909. switch (this.step) {
  6910. case 5:
  6911. case 2:
  6912. clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
  6913. default:
  6914. clone.setHours(Math.round(clone.getHours() / 6) * 6); break;
  6915. }
  6916. clone.setMinutes(0);
  6917. clone.setSeconds(0);
  6918. clone.setMilliseconds(0);
  6919. }
  6920. else if (this.scale == TimeStep.SCALE.HOUR) {
  6921. switch (this.step) {
  6922. case 4:
  6923. clone.setMinutes(Math.round(clone.getMinutes() / 60) * 60); break;
  6924. default:
  6925. clone.setMinutes(Math.round(clone.getMinutes() / 30) * 30); break;
  6926. }
  6927. clone.setSeconds(0);
  6928. clone.setMilliseconds(0);
  6929. } else if (this.scale == TimeStep.SCALE.MINUTE) {
  6930. //noinspection FallthroughInSwitchStatementJS
  6931. switch (this.step) {
  6932. case 15:
  6933. case 10:
  6934. clone.setMinutes(Math.round(clone.getMinutes() / 5) * 5);
  6935. clone.setSeconds(0);
  6936. break;
  6937. case 5:
  6938. clone.setSeconds(Math.round(clone.getSeconds() / 60) * 60); break;
  6939. default:
  6940. clone.setSeconds(Math.round(clone.getSeconds() / 30) * 30); break;
  6941. }
  6942. clone.setMilliseconds(0);
  6943. }
  6944. else if (this.scale == TimeStep.SCALE.SECOND) {
  6945. //noinspection FallthroughInSwitchStatementJS
  6946. switch (this.step) {
  6947. case 15:
  6948. case 10:
  6949. clone.setSeconds(Math.round(clone.getSeconds() / 5) * 5);
  6950. clone.setMilliseconds(0);
  6951. break;
  6952. case 5:
  6953. clone.setMilliseconds(Math.round(clone.getMilliseconds() / 1000) * 1000); break;
  6954. default:
  6955. clone.setMilliseconds(Math.round(clone.getMilliseconds() / 500) * 500); break;
  6956. }
  6957. }
  6958. else if (this.scale == TimeStep.SCALE.MILLISECOND) {
  6959. var step = this.step > 5 ? this.step / 2 : 1;
  6960. clone.setMilliseconds(Math.round(clone.getMilliseconds() / step) * step);
  6961. }
  6962. return clone;
  6963. };
  6964. /**
  6965. * Check if the current value is a major value (for example when the step
  6966. * is DAY, a major value is each first day of the MONTH)
  6967. * @return {boolean} true if current date is major, else false.
  6968. */
  6969. TimeStep.prototype.isMajor = function() {
  6970. switch (this.scale) {
  6971. case TimeStep.SCALE.MILLISECOND:
  6972. return (this.current.getMilliseconds() == 0);
  6973. case TimeStep.SCALE.SECOND:
  6974. return (this.current.getSeconds() == 0);
  6975. case TimeStep.SCALE.MINUTE:
  6976. return (this.current.getHours() == 0) && (this.current.getMinutes() == 0);
  6977. // Note: this is no bug. Major label is equal for both minute and hour scale
  6978. case TimeStep.SCALE.HOUR:
  6979. return (this.current.getHours() == 0);
  6980. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  6981. case TimeStep.SCALE.DAY:
  6982. return (this.current.getDate() == 1);
  6983. case TimeStep.SCALE.MONTH:
  6984. return (this.current.getMonth() == 0);
  6985. case TimeStep.SCALE.YEAR:
  6986. return false;
  6987. default:
  6988. return false;
  6989. }
  6990. };
  6991. /**
  6992. * Returns formatted text for the minor axislabel, depending on the current
  6993. * date and the scale. For example when scale is MINUTE, the current time is
  6994. * formatted as "hh:mm".
  6995. * @param {Date} [date] custom date. if not provided, current date is taken
  6996. */
  6997. TimeStep.prototype.getLabelMinor = function(date) {
  6998. if (date == undefined) {
  6999. date = this.current;
  7000. }
  7001. switch (this.scale) {
  7002. case TimeStep.SCALE.MILLISECOND: return moment(date).format('SSS');
  7003. case TimeStep.SCALE.SECOND: return moment(date).format('s');
  7004. case TimeStep.SCALE.MINUTE: return moment(date).format('HH:mm');
  7005. case TimeStep.SCALE.HOUR: return moment(date).format('HH:mm');
  7006. case TimeStep.SCALE.WEEKDAY: return moment(date).format('ddd D');
  7007. case TimeStep.SCALE.DAY: return moment(date).format('D');
  7008. case TimeStep.SCALE.MONTH: return moment(date).format('MMM');
  7009. case TimeStep.SCALE.YEAR: return moment(date).format('YYYY');
  7010. default: return '';
  7011. }
  7012. };
  7013. /**
  7014. * Returns formatted text for the major axis label, depending on the current
  7015. * date and the scale. For example when scale is MINUTE, the major scale is
  7016. * hours, and the hour will be formatted as "hh".
  7017. * @param {Date} [date] custom date. if not provided, current date is taken
  7018. */
  7019. TimeStep.prototype.getLabelMajor = function(date) {
  7020. if (date == undefined) {
  7021. date = this.current;
  7022. }
  7023. //noinspection FallthroughInSwitchStatementJS
  7024. switch (this.scale) {
  7025. case TimeStep.SCALE.MILLISECOND:return moment(date).format('HH:mm:ss');
  7026. case TimeStep.SCALE.SECOND: return moment(date).format('D MMMM HH:mm');
  7027. case TimeStep.SCALE.MINUTE:
  7028. case TimeStep.SCALE.HOUR: return moment(date).format('ddd D MMMM');
  7029. case TimeStep.SCALE.WEEKDAY:
  7030. case TimeStep.SCALE.DAY: return moment(date).format('MMMM YYYY');
  7031. case TimeStep.SCALE.MONTH: return moment(date).format('YYYY');
  7032. case TimeStep.SCALE.YEAR: return '';
  7033. default: return '';
  7034. }
  7035. };
  7036. module.exports = TimeStep;
  7037. /***/ },
  7038. /* 18 */
  7039. /***/ function(module, exports, __webpack_require__) {
  7040. /**
  7041. * Prototype for visual components
  7042. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} [body]
  7043. * @param {Object} [options]
  7044. */
  7045. function Component (body, options) {
  7046. this.options = null;
  7047. this.props = null;
  7048. }
  7049. /**
  7050. * Set options for the component. The new options will be merged into the
  7051. * current options.
  7052. * @param {Object} options
  7053. */
  7054. Component.prototype.setOptions = function(options) {
  7055. if (options) {
  7056. util.extend(this.options, options);
  7057. }
  7058. };
  7059. /**
  7060. * Repaint the component
  7061. * @return {boolean} Returns true if the component is resized
  7062. */
  7063. Component.prototype.redraw = function() {
  7064. // should be implemented by the component
  7065. return false;
  7066. };
  7067. /**
  7068. * Destroy the component. Cleanup DOM and event listeners
  7069. */
  7070. Component.prototype.destroy = function() {
  7071. // should be implemented by the component
  7072. };
  7073. /**
  7074. * Test whether the component is resized since the last time _isResized() was
  7075. * called.
  7076. * @return {Boolean} Returns true if the component is resized
  7077. * @protected
  7078. */
  7079. Component.prototype._isResized = function() {
  7080. var resized = (this.props._previousWidth !== this.props.width ||
  7081. this.props._previousHeight !== this.props.height);
  7082. this.props._previousWidth = this.props.width;
  7083. this.props._previousHeight = this.props.height;
  7084. return resized;
  7085. };
  7086. module.exports = Component;
  7087. /***/ },
  7088. /* 19 */
  7089. /***/ function(module, exports, __webpack_require__) {
  7090. var util = __webpack_require__(1);
  7091. var Component = __webpack_require__(18);
  7092. var moment = __webpack_require__(41);
  7093. var locales = __webpack_require__(45);
  7094. /**
  7095. * A current time bar
  7096. * @param {{range: Range, dom: Object, domProps: Object}} body
  7097. * @param {Object} [options] Available parameters:
  7098. * {Boolean} [showCurrentTime]
  7099. * @constructor CurrentTime
  7100. * @extends Component
  7101. */
  7102. function CurrentTime (body, options) {
  7103. this.body = body;
  7104. // default options
  7105. this.defaultOptions = {
  7106. showCurrentTime: true,
  7107. locales: locales,
  7108. locale: 'en'
  7109. };
  7110. this.options = util.extend({}, this.defaultOptions);
  7111. this.offset = 0;
  7112. this._create();
  7113. this.setOptions(options);
  7114. }
  7115. CurrentTime.prototype = new Component();
  7116. /**
  7117. * Create the HTML DOM for the current time bar
  7118. * @private
  7119. */
  7120. CurrentTime.prototype._create = function() {
  7121. var bar = document.createElement('div');
  7122. bar.className = 'currenttime';
  7123. bar.style.position = 'absolute';
  7124. bar.style.top = '0px';
  7125. bar.style.height = '100%';
  7126. this.bar = bar;
  7127. };
  7128. /**
  7129. * Destroy the CurrentTime bar
  7130. */
  7131. CurrentTime.prototype.destroy = function () {
  7132. this.options.showCurrentTime = false;
  7133. this.redraw(); // will remove the bar from the DOM and stop refreshing
  7134. this.body = null;
  7135. };
  7136. /**
  7137. * Set options for the component. Options will be merged in current options.
  7138. * @param {Object} options Available parameters:
  7139. * {boolean} [showCurrentTime]
  7140. */
  7141. CurrentTime.prototype.setOptions = function(options) {
  7142. if (options) {
  7143. // copy all options that we know
  7144. util.selectiveExtend(['showCurrentTime', 'locale', 'locales'], this.options, options);
  7145. }
  7146. };
  7147. /**
  7148. * Repaint the component
  7149. * @return {boolean} Returns true if the component is resized
  7150. */
  7151. CurrentTime.prototype.redraw = function() {
  7152. if (this.options.showCurrentTime) {
  7153. var parent = this.body.dom.backgroundVertical;
  7154. if (this.bar.parentNode != parent) {
  7155. // attach to the dom
  7156. if (this.bar.parentNode) {
  7157. this.bar.parentNode.removeChild(this.bar);
  7158. }
  7159. parent.appendChild(this.bar);
  7160. this.start();
  7161. }
  7162. var now = new Date(new Date().valueOf() + this.offset);
  7163. var x = this.body.util.toScreen(now);
  7164. var locale = this.options.locales[this.options.locale];
  7165. var title = locale.current + ' ' + locale.time + ': ' + moment(now).format('dddd, MMMM Do YYYY, H:mm:ss');
  7166. title = title.charAt(0).toUpperCase() + title.substring(1);
  7167. this.bar.style.left = x + 'px';
  7168. this.bar.title = title;
  7169. }
  7170. else {
  7171. // remove the line from the DOM
  7172. if (this.bar.parentNode) {
  7173. this.bar.parentNode.removeChild(this.bar);
  7174. }
  7175. this.stop();
  7176. }
  7177. return false;
  7178. };
  7179. /**
  7180. * Start auto refreshing the current time bar
  7181. */
  7182. CurrentTime.prototype.start = function() {
  7183. var me = this;
  7184. function update () {
  7185. me.stop();
  7186. // determine interval to refresh
  7187. var scale = me.body.range.conversion(me.body.domProps.center.width).scale;
  7188. var interval = 1 / scale / 10;
  7189. if (interval < 30) interval = 30;
  7190. if (interval > 1000) interval = 1000;
  7191. me.redraw();
  7192. // start a timer to adjust for the new time
  7193. me.currentTimeTimer = setTimeout(update, interval);
  7194. }
  7195. update();
  7196. };
  7197. /**
  7198. * Stop auto refreshing the current time bar
  7199. */
  7200. CurrentTime.prototype.stop = function() {
  7201. if (this.currentTimeTimer !== undefined) {
  7202. clearTimeout(this.currentTimeTimer);
  7203. delete this.currentTimeTimer;
  7204. }
  7205. };
  7206. /**
  7207. * Set a current time. This can be used for example to ensure that a client's
  7208. * time is synchronized with a shared server time.
  7209. * @param {Date | String | Number} time A Date, unix timestamp, or
  7210. * ISO date string.
  7211. */
  7212. CurrentTime.prototype.setCurrentTime = function(time) {
  7213. var t = util.convert(time, 'Date').valueOf();
  7214. var now = new Date().valueOf();
  7215. this.offset = t - now;
  7216. this.redraw();
  7217. };
  7218. /**
  7219. * Get the current time.
  7220. * @return {Date} Returns the current time.
  7221. */
  7222. CurrentTime.prototype.getCurrentTime = function() {
  7223. return new Date(new Date().valueOf() + this.offset);
  7224. };
  7225. module.exports = CurrentTime;
  7226. /***/ },
  7227. /* 20 */
  7228. /***/ function(module, exports, __webpack_require__) {
  7229. var Hammer = __webpack_require__(42);
  7230. var util = __webpack_require__(1);
  7231. var Component = __webpack_require__(18);
  7232. var moment = __webpack_require__(41);
  7233. var locales = __webpack_require__(45);
  7234. /**
  7235. * A custom time bar
  7236. * @param {{range: Range, dom: Object}} body
  7237. * @param {Object} [options] Available parameters:
  7238. * {Boolean} [showCustomTime]
  7239. * @constructor CustomTime
  7240. * @extends Component
  7241. */
  7242. function CustomTime (body, options) {
  7243. this.body = body;
  7244. // default options
  7245. this.defaultOptions = {
  7246. showCustomTime: false,
  7247. locales: locales,
  7248. locale: 'en'
  7249. };
  7250. this.options = util.extend({}, this.defaultOptions);
  7251. this.customTime = new Date();
  7252. this.eventParams = {}; // stores state parameters while dragging the bar
  7253. // create the DOM
  7254. this._create();
  7255. this.setOptions(options);
  7256. }
  7257. CustomTime.prototype = new Component();
  7258. /**
  7259. * Set options for the component. Options will be merged in current options.
  7260. * @param {Object} options Available parameters:
  7261. * {boolean} [showCustomTime]
  7262. */
  7263. CustomTime.prototype.setOptions = function(options) {
  7264. if (options) {
  7265. // copy all options that we know
  7266. util.selectiveExtend(['showCustomTime', 'locale', 'locales'], this.options, options);
  7267. }
  7268. };
  7269. /**
  7270. * Create the DOM for the custom time
  7271. * @private
  7272. */
  7273. CustomTime.prototype._create = function() {
  7274. var bar = document.createElement('div');
  7275. bar.className = 'customtime';
  7276. bar.style.position = 'absolute';
  7277. bar.style.top = '0px';
  7278. bar.style.height = '100%';
  7279. this.bar = bar;
  7280. var drag = document.createElement('div');
  7281. drag.style.position = 'relative';
  7282. drag.style.top = '0px';
  7283. drag.style.left = '-10px';
  7284. drag.style.height = '100%';
  7285. drag.style.width = '20px';
  7286. bar.appendChild(drag);
  7287. // attach event listeners
  7288. this.hammer = Hammer(bar, {
  7289. prevent_default: true
  7290. });
  7291. this.hammer.on('dragstart', this._onDragStart.bind(this));
  7292. this.hammer.on('drag', this._onDrag.bind(this));
  7293. this.hammer.on('dragend', this._onDragEnd.bind(this));
  7294. };
  7295. /**
  7296. * Destroy the CustomTime bar
  7297. */
  7298. CustomTime.prototype.destroy = function () {
  7299. this.options.showCustomTime = false;
  7300. this.redraw(); // will remove the bar from the DOM
  7301. this.hammer.enable(false);
  7302. this.hammer = null;
  7303. this.body = null;
  7304. };
  7305. /**
  7306. * Repaint the component
  7307. * @return {boolean} Returns true if the component is resized
  7308. */
  7309. CustomTime.prototype.redraw = function () {
  7310. if (this.options.showCustomTime) {
  7311. var parent = this.body.dom.backgroundVertical;
  7312. if (this.bar.parentNode != parent) {
  7313. // attach to the dom
  7314. if (this.bar.parentNode) {
  7315. this.bar.parentNode.removeChild(this.bar);
  7316. }
  7317. parent.appendChild(this.bar);
  7318. }
  7319. var x = this.body.util.toScreen(this.customTime);
  7320. var locale = this.options.locales[this.options.locale];
  7321. var title = locale.time + ': ' + moment(this.customTime).format('dddd, MMMM Do YYYY, H:mm:ss');
  7322. title = title.charAt(0).toUpperCase() + title.substring(1);
  7323. this.bar.style.left = x + 'px';
  7324. this.bar.title = title;
  7325. }
  7326. else {
  7327. // remove the line from the DOM
  7328. if (this.bar.parentNode) {
  7329. this.bar.parentNode.removeChild(this.bar);
  7330. }
  7331. }
  7332. return false;
  7333. };
  7334. /**
  7335. * Set custom time.
  7336. * @param {Date | number | string} time
  7337. */
  7338. CustomTime.prototype.setCustomTime = function(time) {
  7339. this.customTime = util.convert(time, 'Date');
  7340. this.redraw();
  7341. };
  7342. /**
  7343. * Retrieve the current custom time.
  7344. * @return {Date} customTime
  7345. */
  7346. CustomTime.prototype.getCustomTime = function() {
  7347. return new Date(this.customTime.valueOf());
  7348. };
  7349. /**
  7350. * Start moving horizontally
  7351. * @param {Event} event
  7352. * @private
  7353. */
  7354. CustomTime.prototype._onDragStart = function(event) {
  7355. this.eventParams.dragging = true;
  7356. this.eventParams.customTime = this.customTime;
  7357. event.stopPropagation();
  7358. event.preventDefault();
  7359. };
  7360. /**
  7361. * Perform moving operating.
  7362. * @param {Event} event
  7363. * @private
  7364. */
  7365. CustomTime.prototype._onDrag = function (event) {
  7366. if (!this.eventParams.dragging) return;
  7367. var deltaX = event.gesture.deltaX,
  7368. x = this.body.util.toScreen(this.eventParams.customTime) + deltaX,
  7369. time = this.body.util.toTime(x);
  7370. this.setCustomTime(time);
  7371. // fire a timechange event
  7372. this.body.emitter.emit('timechange', {
  7373. time: new Date(this.customTime.valueOf())
  7374. });
  7375. event.stopPropagation();
  7376. event.preventDefault();
  7377. };
  7378. /**
  7379. * Stop moving operating.
  7380. * @param {event} event
  7381. * @private
  7382. */
  7383. CustomTime.prototype._onDragEnd = function (event) {
  7384. if (!this.eventParams.dragging) return;
  7385. // fire a timechanged event
  7386. this.body.emitter.emit('timechanged', {
  7387. time: new Date(this.customTime.valueOf())
  7388. });
  7389. event.stopPropagation();
  7390. event.preventDefault();
  7391. };
  7392. module.exports = CustomTime;
  7393. /***/ },
  7394. /* 21 */
  7395. /***/ function(module, exports, __webpack_require__) {
  7396. var util = __webpack_require__(1);
  7397. var DOMutil = __webpack_require__(2);
  7398. var Component = __webpack_require__(18);
  7399. var DataStep = __webpack_require__(14);
  7400. /**
  7401. * A horizontal time axis
  7402. * @param {Object} [options] See DataAxis.setOptions for the available
  7403. * options.
  7404. * @constructor DataAxis
  7405. * @extends Component
  7406. * @param body
  7407. */
  7408. function DataAxis (body, options, svg, linegraphOptions) {
  7409. this.id = util.randomUUID();
  7410. this.body = body;
  7411. this.defaultOptions = {
  7412. orientation: 'left', // supported: 'left', 'right'
  7413. showMinorLabels: true,
  7414. showMajorLabels: true,
  7415. icons: true,
  7416. majorLinesOffset: 7,
  7417. minorLinesOffset: 4,
  7418. labelOffsetX: 10,
  7419. labelOffsetY: 2,
  7420. iconWidth: 20,
  7421. width: '40px',
  7422. visible: true,
  7423. customRange: {
  7424. left: {min:undefined, max:undefined},
  7425. right: {min:undefined, max:undefined}
  7426. }
  7427. };
  7428. this.linegraphOptions = linegraphOptions;
  7429. this.linegraphSVG = svg;
  7430. this.props = {};
  7431. this.DOMelements = { // dynamic elements
  7432. lines: {},
  7433. labels: {}
  7434. };
  7435. this.dom = {};
  7436. this.range = {start:0, end:0};
  7437. this.options = util.extend({}, this.defaultOptions);
  7438. this.conversionFactor = 1;
  7439. this.setOptions(options);
  7440. this.width = Number(('' + this.options.width).replace("px",""));
  7441. this.minWidth = this.width;
  7442. this.height = this.linegraphSVG.offsetHeight;
  7443. this.stepPixels = 25;
  7444. this.stepPixelsForced = 25;
  7445. this.lineOffset = 0;
  7446. this.master = true;
  7447. this.svgElements = {};
  7448. this.groups = {};
  7449. this.amountOfGroups = 0;
  7450. // create the HTML DOM
  7451. this._create();
  7452. }
  7453. DataAxis.prototype = new Component();
  7454. DataAxis.prototype.addGroup = function(label, graphOptions) {
  7455. if (!this.groups.hasOwnProperty(label)) {
  7456. this.groups[label] = graphOptions;
  7457. }
  7458. this.amountOfGroups += 1;
  7459. };
  7460. DataAxis.prototype.updateGroup = function(label, graphOptions) {
  7461. this.groups[label] = graphOptions;
  7462. };
  7463. DataAxis.prototype.removeGroup = function(label) {
  7464. if (this.groups.hasOwnProperty(label)) {
  7465. delete this.groups[label];
  7466. this.amountOfGroups -= 1;
  7467. }
  7468. };
  7469. DataAxis.prototype.setOptions = function (options) {
  7470. if (options) {
  7471. var redraw = false;
  7472. if (this.options.orientation != options.orientation && options.orientation !== undefined) {
  7473. redraw = true;
  7474. }
  7475. var fields = [
  7476. 'orientation',
  7477. 'showMinorLabels',
  7478. 'showMajorLabels',
  7479. 'icons',
  7480. 'majorLinesOffset',
  7481. 'minorLinesOffset',
  7482. 'labelOffsetX',
  7483. 'labelOffsetY',
  7484. 'iconWidth',
  7485. 'width',
  7486. 'visible',
  7487. 'customRange'
  7488. ];
  7489. util.selectiveExtend(fields, this.options, options);
  7490. this.minWidth = Number(('' + this.options.width).replace("px",""));
  7491. if (redraw == true && this.dom.frame) {
  7492. this.hide();
  7493. this.show();
  7494. }
  7495. }
  7496. };
  7497. /**
  7498. * Create the HTML DOM for the DataAxis
  7499. */
  7500. DataAxis.prototype._create = function() {
  7501. this.dom.frame = document.createElement('div');
  7502. this.dom.frame.style.width = this.options.width;
  7503. this.dom.frame.style.height = this.height;
  7504. this.dom.lineContainer = document.createElement('div');
  7505. this.dom.lineContainer.style.width = '100%';
  7506. this.dom.lineContainer.style.height = this.height;
  7507. // create svg element for graph drawing.
  7508. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  7509. this.svg.style.position = "absolute";
  7510. this.svg.style.top = '0px';
  7511. this.svg.style.height = '100%';
  7512. this.svg.style.width = '100%';
  7513. this.svg.style.display = "block";
  7514. this.dom.frame.appendChild(this.svg);
  7515. };
  7516. DataAxis.prototype._redrawGroupIcons = function () {
  7517. DOMutil.prepareElements(this.svgElements);
  7518. var x;
  7519. var iconWidth = this.options.iconWidth;
  7520. var iconHeight = 15;
  7521. var iconOffset = 4;
  7522. var y = iconOffset + 0.5 * iconHeight;
  7523. if (this.options.orientation == 'left') {
  7524. x = iconOffset;
  7525. }
  7526. else {
  7527. x = this.width - iconWidth - iconOffset;
  7528. }
  7529. for (var groupId in this.groups) {
  7530. if (this.groups.hasOwnProperty(groupId)) {
  7531. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  7532. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  7533. y += iconHeight + iconOffset;
  7534. }
  7535. }
  7536. }
  7537. DOMutil.cleanupElements(this.svgElements);
  7538. };
  7539. /**
  7540. * Create the HTML DOM for the DataAxis
  7541. */
  7542. DataAxis.prototype.show = function() {
  7543. if (!this.dom.frame.parentNode) {
  7544. if (this.options.orientation == 'left') {
  7545. this.body.dom.left.appendChild(this.dom.frame);
  7546. }
  7547. else {
  7548. this.body.dom.right.appendChild(this.dom.frame);
  7549. }
  7550. }
  7551. if (!this.dom.lineContainer.parentNode) {
  7552. this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer);
  7553. }
  7554. };
  7555. /**
  7556. * Create the HTML DOM for the DataAxis
  7557. */
  7558. DataAxis.prototype.hide = function() {
  7559. if (this.dom.frame.parentNode) {
  7560. this.dom.frame.parentNode.removeChild(this.dom.frame);
  7561. }
  7562. if (this.dom.lineContainer.parentNode) {
  7563. this.dom.lineContainer.parentNode.removeChild(this.dom.lineContainer);
  7564. }
  7565. };
  7566. /**
  7567. * Set a range (start and end)
  7568. * @param end
  7569. * @param start
  7570. * @param end
  7571. */
  7572. DataAxis.prototype.setRange = function (start, end) {
  7573. this.range.start = start;
  7574. this.range.end = end;
  7575. };
  7576. /**
  7577. * Repaint the component
  7578. * @return {boolean} Returns true if the component is resized
  7579. */
  7580. DataAxis.prototype.redraw = function () {
  7581. var changeCalled = false;
  7582. var activeGroups = 0;
  7583. for (var groupId in this.groups) {
  7584. if (this.groups.hasOwnProperty(groupId)) {
  7585. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  7586. activeGroups++;
  7587. }
  7588. }
  7589. }
  7590. if (this.amountOfGroups == 0 || activeGroups == 0) {
  7591. this.hide();
  7592. }
  7593. else {
  7594. this.show();
  7595. this.height = Number(this.linegraphSVG.style.height.replace("px",""));
  7596. // svg offsetheight did not work in firefox and explorer...
  7597. this.dom.lineContainer.style.height = this.height + 'px';
  7598. this.width = this.options.visible == true ? Number(('' + this.options.width).replace("px","")) : 0;
  7599. var props = this.props;
  7600. var frame = this.dom.frame;
  7601. // update classname
  7602. frame.className = 'dataaxis';
  7603. // calculate character width and height
  7604. this._calculateCharSize();
  7605. var orientation = this.options.orientation;
  7606. var showMinorLabels = this.options.showMinorLabels;
  7607. var showMajorLabels = this.options.showMajorLabels;
  7608. // determine the width and height of the elemens for the axis
  7609. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  7610. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  7611. props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset;
  7612. props.minorLineHeight = 1;
  7613. props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset;
  7614. props.majorLineHeight = 1;
  7615. // take frame offline while updating (is almost twice as fast)
  7616. if (orientation == 'left') {
  7617. frame.style.top = '0';
  7618. frame.style.left = '0';
  7619. frame.style.bottom = '';
  7620. frame.style.width = this.width + 'px';
  7621. frame.style.height = this.height + "px";
  7622. }
  7623. else { // right
  7624. frame.style.top = '';
  7625. frame.style.bottom = '0';
  7626. frame.style.left = '0';
  7627. frame.style.width = this.width + 'px';
  7628. frame.style.height = this.height + "px";
  7629. }
  7630. changeCalled = this._redrawLabels();
  7631. if (this.options.icons == true) {
  7632. this._redrawGroupIcons();
  7633. }
  7634. }
  7635. return changeCalled;
  7636. };
  7637. /**
  7638. * Repaint major and minor text labels and vertical grid lines
  7639. * @private
  7640. */
  7641. DataAxis.prototype._redrawLabels = function () {
  7642. DOMutil.prepareElements(this.DOMelements.lines);
  7643. DOMutil.prepareElements(this.DOMelements.labels);
  7644. var orientation = this.options['orientation'];
  7645. // calculate range and step (step such that we have space for 7 characters per label)
  7646. var minimumStep = this.master ? this.props.majorCharHeight || 10 : this.stepPixelsForced;
  7647. var step = new DataStep(this.range.start, this.range.end, minimumStep, this.dom.frame.offsetHeight, this.options.customRange[this.options.orientation]);
  7648. this.step = step;
  7649. // get the distance in pixels for a step
  7650. // dead space is space that is "left over" after a step
  7651. var stepPixels = (this.dom.frame.offsetHeight - (step.deadSpace * (this.dom.frame.offsetHeight / step.marginRange))) / (((step.marginRange - step.deadSpace) / step.step));
  7652. this.stepPixels = stepPixels;
  7653. var amountOfSteps = this.height / stepPixels;
  7654. var stepDifference = 0;
  7655. if (this.master == false) {
  7656. stepPixels = this.stepPixelsForced;
  7657. stepDifference = Math.round((this.dom.frame.offsetHeight / stepPixels) - amountOfSteps);
  7658. for (var i = 0; i < 0.5 * stepDifference; i++) {
  7659. step.previous();
  7660. }
  7661. amountOfSteps = this.height / stepPixels;
  7662. }
  7663. else {
  7664. amountOfSteps += 0.25;
  7665. }
  7666. this.valueAtZero = step.marginEnd;
  7667. var marginStartPos = 0;
  7668. // do not draw the first label
  7669. var max = 1;
  7670. this.maxLabelSize = 0;
  7671. var y = 0;
  7672. while (max < Math.round(amountOfSteps)) {
  7673. step.next();
  7674. y = Math.round(max * stepPixels);
  7675. marginStartPos = max * stepPixels;
  7676. var isMajor = step.isMajor();
  7677. if (this.options['showMinorLabels'] && isMajor == false || this.master == false && this.options['showMinorLabels'] == true) {
  7678. this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis minor', this.props.minorCharHeight);
  7679. }
  7680. if (isMajor && this.options['showMajorLabels'] && this.master == true ||
  7681. this.options['showMinorLabels'] == false && this.master == false && isMajor == true) {
  7682. if (y >= 0) {
  7683. this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis major', this.props.majorCharHeight);
  7684. }
  7685. this._redrawLine(y, orientation, 'grid horizontal major', this.options.majorLinesOffset, this.props.majorLineWidth);
  7686. }
  7687. else {
  7688. this._redrawLine(y, orientation, 'grid horizontal minor', this.options.minorLinesOffset, this.props.minorLineWidth);
  7689. }
  7690. max++;
  7691. }
  7692. if (this.master == false) {
  7693. this.conversionFactor = y / (this.valueAtZero - step.current);
  7694. }
  7695. else {
  7696. this.conversionFactor = this.dom.frame.offsetHeight / step.marginRange;
  7697. }
  7698. var offset = this.options.icons == true ? this.options.iconWidth + this.options.labelOffsetX + 15 : this.options.labelOffsetX + 15;
  7699. // this will resize the yAxis to accomodate the labels.
  7700. if (this.maxLabelSize > (this.width - offset) && this.options.visible == true) {
  7701. this.width = this.maxLabelSize + offset;
  7702. this.options.width = this.width + "px";
  7703. DOMutil.cleanupElements(this.DOMelements.lines);
  7704. DOMutil.cleanupElements(this.DOMelements.labels);
  7705. this.redraw();
  7706. return true;
  7707. }
  7708. // this will resize the yAxis if it is too big for the labels.
  7709. else if (this.maxLabelSize < (this.width - offset) && this.options.visible == true && this.width > this.minWidth) {
  7710. this.width = Math.max(this.minWidth,this.maxLabelSize + offset);
  7711. this.options.width = this.width + "px";
  7712. DOMutil.cleanupElements(this.DOMelements.lines);
  7713. DOMutil.cleanupElements(this.DOMelements.labels);
  7714. this.redraw();
  7715. return true;
  7716. }
  7717. else {
  7718. DOMutil.cleanupElements(this.DOMelements.lines);
  7719. DOMutil.cleanupElements(this.DOMelements.labels);
  7720. return false;
  7721. }
  7722. };
  7723. DataAxis.prototype.convertValue = function (value) {
  7724. var invertedValue = this.valueAtZero - value;
  7725. var convertedValue = invertedValue * this.conversionFactor;
  7726. return convertedValue;
  7727. };
  7728. /**
  7729. * Create a label for the axis at position x
  7730. * @private
  7731. * @param y
  7732. * @param text
  7733. * @param orientation
  7734. * @param className
  7735. * @param characterHeight
  7736. */
  7737. DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) {
  7738. // reuse redundant label
  7739. var label = DOMutil.getDOMElement('div',this.DOMelements.labels, this.dom.frame); //this.dom.redundant.labels.shift();
  7740. label.className = className;
  7741. label.innerHTML = text;
  7742. if (orientation == 'left') {
  7743. label.style.left = '-' + this.options.labelOffsetX + 'px';
  7744. label.style.textAlign = "right";
  7745. }
  7746. else {
  7747. label.style.right = '-' + this.options.labelOffsetX + 'px';
  7748. label.style.textAlign = "left";
  7749. }
  7750. label.style.top = y - 0.5 * characterHeight + this.options.labelOffsetY + 'px';
  7751. text += '';
  7752. var largestWidth = Math.max(this.props.majorCharWidth,this.props.minorCharWidth);
  7753. if (this.maxLabelSize < text.length * largestWidth) {
  7754. this.maxLabelSize = text.length * largestWidth;
  7755. }
  7756. };
  7757. /**
  7758. * Create a minor line for the axis at position y
  7759. * @param y
  7760. * @param orientation
  7761. * @param className
  7762. * @param offset
  7763. * @param width
  7764. */
  7765. DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) {
  7766. if (this.master == true) {
  7767. var line = DOMutil.getDOMElement('div',this.DOMelements.lines, this.dom.lineContainer);//this.dom.redundant.lines.shift();
  7768. line.className = className;
  7769. line.innerHTML = '';
  7770. if (orientation == 'left') {
  7771. line.style.left = (this.width - offset) + 'px';
  7772. }
  7773. else {
  7774. line.style.right = (this.width - offset) + 'px';
  7775. }
  7776. line.style.width = width + 'px';
  7777. line.style.top = y + 'px';
  7778. }
  7779. };
  7780. /**
  7781. * Determine the size of text on the axis (both major and minor axis).
  7782. * The size is calculated only once and then cached in this.props.
  7783. * @private
  7784. */
  7785. DataAxis.prototype._calculateCharSize = function () {
  7786. // determine the char width and height on the minor axis
  7787. if (!('minorCharHeight' in this.props)) {
  7788. var textMinor = document.createTextNode('0');
  7789. var measureCharMinor = document.createElement('DIV');
  7790. measureCharMinor.className = 'yAxis minor measure';
  7791. measureCharMinor.appendChild(textMinor);
  7792. this.dom.frame.appendChild(measureCharMinor);
  7793. this.props.minorCharHeight = measureCharMinor.clientHeight;
  7794. this.props.minorCharWidth = measureCharMinor.clientWidth;
  7795. this.dom.frame.removeChild(measureCharMinor);
  7796. }
  7797. if (!('majorCharHeight' in this.props)) {
  7798. var textMajor = document.createTextNode('0');
  7799. var measureCharMajor = document.createElement('DIV');
  7800. measureCharMajor.className = 'yAxis major measure';
  7801. measureCharMajor.appendChild(textMajor);
  7802. this.dom.frame.appendChild(measureCharMajor);
  7803. this.props.majorCharHeight = measureCharMajor.clientHeight;
  7804. this.props.majorCharWidth = measureCharMajor.clientWidth;
  7805. this.dom.frame.removeChild(measureCharMajor);
  7806. }
  7807. };
  7808. /**
  7809. * Snap a date to a rounded value.
  7810. * The snap intervals are dependent on the current scale and step.
  7811. * @param {Date} date the date to be snapped.
  7812. * @return {Date} snappedDate
  7813. */
  7814. DataAxis.prototype.snap = function(date) {
  7815. return this.step.snap(date);
  7816. };
  7817. module.exports = DataAxis;
  7818. /***/ },
  7819. /* 22 */
  7820. /***/ function(module, exports, __webpack_require__) {
  7821. var util = __webpack_require__(1);
  7822. var DOMutil = __webpack_require__(2);
  7823. /**
  7824. * @constructor Group
  7825. * @param {Number | String} groupId
  7826. * @param {Object} data
  7827. * @param {ItemSet} itemSet
  7828. */
  7829. function GraphGroup (group, groupId, options, groupsUsingDefaultStyles) {
  7830. this.id = groupId;
  7831. var fields = ['sampling','style','sort','yAxisOrientation','barChart','drawPoints','shaded','catmullRom']
  7832. this.options = util.selectiveBridgeObject(fields,options);
  7833. this.usingDefaultStyle = group.className === undefined;
  7834. this.groupsUsingDefaultStyles = groupsUsingDefaultStyles;
  7835. this.zeroPosition = 0;
  7836. this.update(group);
  7837. if (this.usingDefaultStyle == true) {
  7838. this.groupsUsingDefaultStyles[0] += 1;
  7839. }
  7840. this.itemsData = [];
  7841. this.visible = group.visible === undefined ? true : group.visible;
  7842. }
  7843. GraphGroup.prototype.setItems = function(items) {
  7844. if (items != null) {
  7845. this.itemsData = items;
  7846. if (this.options.sort == true) {
  7847. this.itemsData.sort(function (a,b) {return a.x - b.x;})
  7848. }
  7849. }
  7850. else {
  7851. this.itemsData = [];
  7852. }
  7853. };
  7854. GraphGroup.prototype.setZeroPosition = function(pos) {
  7855. this.zeroPosition = pos;
  7856. };
  7857. GraphGroup.prototype.setOptions = function(options) {
  7858. if (options !== undefined) {
  7859. var fields = ['sampling','style','sort','yAxisOrientation','barChart'];
  7860. util.selectiveDeepExtend(fields, this.options, options);
  7861. util.mergeOptions(this.options, options,'catmullRom');
  7862. util.mergeOptions(this.options, options,'drawPoints');
  7863. util.mergeOptions(this.options, options,'shaded');
  7864. if (options.catmullRom) {
  7865. if (typeof options.catmullRom == 'object') {
  7866. if (options.catmullRom.parametrization) {
  7867. if (options.catmullRom.parametrization == 'uniform') {
  7868. this.options.catmullRom.alpha = 0;
  7869. }
  7870. else if (options.catmullRom.parametrization == 'chordal') {
  7871. this.options.catmullRom.alpha = 1.0;
  7872. }
  7873. else {
  7874. this.options.catmullRom.parametrization = 'centripetal';
  7875. this.options.catmullRom.alpha = 0.5;
  7876. }
  7877. }
  7878. }
  7879. }
  7880. }
  7881. };
  7882. GraphGroup.prototype.update = function(group) {
  7883. this.group = group;
  7884. this.content = group.content || 'graph';
  7885. this.className = group.className || this.className || "graphGroup" + this.groupsUsingDefaultStyles[0] % 10;
  7886. this.visible = group.visible === undefined ? true : group.visible;
  7887. this.setOptions(group.options);
  7888. };
  7889. GraphGroup.prototype.drawIcon = function(x, y, JSONcontainer, SVGcontainer, iconWidth, iconHeight) {
  7890. var fillHeight = iconHeight * 0.5;
  7891. var path, fillPath;
  7892. var outline = DOMutil.getSVGElement("rect", JSONcontainer, SVGcontainer);
  7893. outline.setAttributeNS(null, "x", x);
  7894. outline.setAttributeNS(null, "y", y - fillHeight);
  7895. outline.setAttributeNS(null, "width", iconWidth);
  7896. outline.setAttributeNS(null, "height", 2*fillHeight);
  7897. outline.setAttributeNS(null, "class", "outline");
  7898. if (this.options.style == 'line') {
  7899. path = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
  7900. path.setAttributeNS(null, "class", this.className);
  7901. path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+"");
  7902. if (this.options.shaded.enabled == true) {
  7903. fillPath = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
  7904. if (this.options.shaded.orientation == 'top') {
  7905. fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) +
  7906. "L"+x+","+y+" L"+ (x + iconWidth) + ","+y+" L"+ (x + iconWidth) + "," + (y - fillHeight));
  7907. }
  7908. else {
  7909. fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " +
  7910. "L"+x+"," + (y + fillHeight) + " " +
  7911. "L"+ (x + iconWidth) + "," + (y + fillHeight) +
  7912. "L"+ (x + iconWidth) + ","+y);
  7913. }
  7914. fillPath.setAttributeNS(null, "class", this.className + " iconFill");
  7915. }
  7916. if (this.options.drawPoints.enabled == true) {
  7917. DOMutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer);
  7918. }
  7919. }
  7920. else {
  7921. var barWidth = Math.round(0.3 * iconWidth);
  7922. var bar1Height = Math.round(0.4 * iconHeight);
  7923. var bar2Height = Math.round(0.75 * iconHeight);
  7924. var offset = Math.round((iconWidth - (2 * barWidth))/3);
  7925. DOMutil.drawBar(x + 0.5*barWidth + offset , y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' bar', JSONcontainer, SVGcontainer);
  7926. DOMutil.drawBar(x + 1.5*barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' bar', JSONcontainer, SVGcontainer);
  7927. }
  7928. };
  7929. /**
  7930. *
  7931. * @param iconWidth
  7932. * @param iconHeight
  7933. * @returns {{icon: HTMLElement, label: (group.content|*|string), orientation: (.options.yAxisOrientation|*)}}
  7934. */
  7935. GraphGroup.prototype.getLegend = function(iconWidth, iconHeight) {
  7936. var svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  7937. this.drawIcon(0,0.5*iconHeight,[],svg,iconWidth,iconHeight);
  7938. return {icon: svg, label: this.content, orientation:this.options.yAxisOrientation};
  7939. }
  7940. module.exports = GraphGroup;
  7941. /***/ },
  7942. /* 23 */
  7943. /***/ function(module, exports, __webpack_require__) {
  7944. var util = __webpack_require__(1);
  7945. var stack = __webpack_require__(16);
  7946. var RangeItem = __webpack_require__(32);
  7947. /**
  7948. * @constructor Group
  7949. * @param {Number | String} groupId
  7950. * @param {Object} data
  7951. * @param {ItemSet} itemSet
  7952. */
  7953. function Group (groupId, data, itemSet) {
  7954. this.groupId = groupId;
  7955. this.itemSet = itemSet;
  7956. this.dom = {};
  7957. this.props = {
  7958. label: {
  7959. width: 0,
  7960. height: 0
  7961. }
  7962. };
  7963. this.className = null;
  7964. this.items = {}; // items filtered by groupId of this group
  7965. this.visibleItems = []; // items currently visible in window
  7966. this.orderedItems = { // items sorted by start and by end
  7967. byStart: [],
  7968. byEnd: []
  7969. };
  7970. this._create();
  7971. this.setData(data);
  7972. }
  7973. /**
  7974. * Create DOM elements for the group
  7975. * @private
  7976. */
  7977. Group.prototype._create = function() {
  7978. var label = document.createElement('div');
  7979. label.className = 'vlabel';
  7980. this.dom.label = label;
  7981. var inner = document.createElement('div');
  7982. inner.className = 'inner';
  7983. label.appendChild(inner);
  7984. this.dom.inner = inner;
  7985. var foreground = document.createElement('div');
  7986. foreground.className = 'group';
  7987. foreground['timeline-group'] = this;
  7988. this.dom.foreground = foreground;
  7989. this.dom.background = document.createElement('div');
  7990. this.dom.background.className = 'group';
  7991. this.dom.axis = document.createElement('div');
  7992. this.dom.axis.className = 'group';
  7993. // create a hidden marker to detect when the Timelines container is attached
  7994. // to the DOM, or the style of a parent of the Timeline is changed from
  7995. // display:none is changed to visible.
  7996. this.dom.marker = document.createElement('div');
  7997. this.dom.marker.style.visibility = 'hidden';
  7998. this.dom.marker.innerHTML = '?';
  7999. this.dom.background.appendChild(this.dom.marker);
  8000. };
  8001. /**
  8002. * Set the group data for this group
  8003. * @param {Object} data Group data, can contain properties content and className
  8004. */
  8005. Group.prototype.setData = function(data) {
  8006. // update contents
  8007. var content = data && data.content;
  8008. if (content instanceof Element) {
  8009. this.dom.inner.appendChild(content);
  8010. }
  8011. else if (content !== undefined && content !== null) {
  8012. this.dom.inner.innerHTML = content;
  8013. }
  8014. else {
  8015. this.dom.inner.innerHTML = this.groupId || ''; // groupId can be null
  8016. }
  8017. // update title
  8018. this.dom.label.title = data && data.title || '';
  8019. if (!this.dom.inner.firstChild) {
  8020. util.addClassName(this.dom.inner, 'hidden');
  8021. }
  8022. else {
  8023. util.removeClassName(this.dom.inner, 'hidden');
  8024. }
  8025. // update className
  8026. var className = data && data.className || null;
  8027. if (className != this.className) {
  8028. if (this.className) {
  8029. util.removeClassName(this.dom.label, this.className);
  8030. util.removeClassName(this.dom.foreground, this.className);
  8031. util.removeClassName(this.dom.background, this.className);
  8032. util.removeClassName(this.dom.axis, this.className);
  8033. }
  8034. util.addClassName(this.dom.label, className);
  8035. util.addClassName(this.dom.foreground, className);
  8036. util.addClassName(this.dom.background, className);
  8037. util.addClassName(this.dom.axis, className);
  8038. this.className = className;
  8039. }
  8040. };
  8041. /**
  8042. * Get the width of the group label
  8043. * @return {number} width
  8044. */
  8045. Group.prototype.getLabelWidth = function() {
  8046. return this.props.label.width;
  8047. };
  8048. /**
  8049. * Repaint this group
  8050. * @param {{start: number, end: number}} range
  8051. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  8052. * @param {boolean} [restack=false] Force restacking of all items
  8053. * @return {boolean} Returns true if the group is resized
  8054. */
  8055. Group.prototype.redraw = function(range, margin, restack) {
  8056. var resized = false;
  8057. this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
  8058. // force recalculation of the height of the items when the marker height changed
  8059. // (due to the Timeline being attached to the DOM or changed from display:none to visible)
  8060. var markerHeight = this.dom.marker.clientHeight;
  8061. if (markerHeight != this.lastMarkerHeight) {
  8062. this.lastMarkerHeight = markerHeight;
  8063. util.forEach(this.items, function (item) {
  8064. item.dirty = true;
  8065. if (item.displayed) item.redraw();
  8066. });
  8067. restack = true;
  8068. }
  8069. // reposition visible items vertically
  8070. if (this.itemSet.options.stack) { // TODO: ugly way to access options...
  8071. stack.stack(this.visibleItems, margin, restack);
  8072. }
  8073. else { // no stacking
  8074. stack.nostack(this.visibleItems, margin);
  8075. }
  8076. // recalculate the height of the group
  8077. var height;
  8078. var visibleItems = this.visibleItems;
  8079. if (visibleItems.length) {
  8080. var min = visibleItems[0].top;
  8081. var max = visibleItems[0].top + visibleItems[0].height;
  8082. util.forEach(visibleItems, function (item) {
  8083. min = Math.min(min, item.top);
  8084. max = Math.max(max, (item.top + item.height));
  8085. });
  8086. if (min > margin.axis) {
  8087. // there is an empty gap between the lowest item and the axis
  8088. var offset = min - margin.axis;
  8089. max -= offset;
  8090. util.forEach(visibleItems, function (item) {
  8091. item.top -= offset;
  8092. });
  8093. }
  8094. height = max + margin.item.vertical / 2;
  8095. }
  8096. else {
  8097. height = margin.axis + margin.item.vertical;
  8098. }
  8099. height = Math.max(height, this.props.label.height);
  8100. // calculate actual size and position
  8101. var foreground = this.dom.foreground;
  8102. this.top = foreground.offsetTop;
  8103. this.left = foreground.offsetLeft;
  8104. this.width = foreground.offsetWidth;
  8105. resized = util.updateProperty(this, 'height', height) || resized;
  8106. // recalculate size of label
  8107. resized = util.updateProperty(this.props.label, 'width', this.dom.inner.clientWidth) || resized;
  8108. resized = util.updateProperty(this.props.label, 'height', this.dom.inner.clientHeight) || resized;
  8109. // apply new height
  8110. this.dom.background.style.height = height + 'px';
  8111. this.dom.foreground.style.height = height + 'px';
  8112. this.dom.label.style.height = height + 'px';
  8113. // update vertical position of items after they are re-stacked and the height of the group is calculated
  8114. for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
  8115. var item = this.visibleItems[i];
  8116. item.repositionY();
  8117. }
  8118. return resized;
  8119. };
  8120. /**
  8121. * Show this group: attach to the DOM
  8122. */
  8123. Group.prototype.show = function() {
  8124. if (!this.dom.label.parentNode) {
  8125. this.itemSet.dom.labelSet.appendChild(this.dom.label);
  8126. }
  8127. if (!this.dom.foreground.parentNode) {
  8128. this.itemSet.dom.foreground.appendChild(this.dom.foreground);
  8129. }
  8130. if (!this.dom.background.parentNode) {
  8131. this.itemSet.dom.background.appendChild(this.dom.background);
  8132. }
  8133. if (!this.dom.axis.parentNode) {
  8134. this.itemSet.dom.axis.appendChild(this.dom.axis);
  8135. }
  8136. };
  8137. /**
  8138. * Hide this group: remove from the DOM
  8139. */
  8140. Group.prototype.hide = function() {
  8141. var label = this.dom.label;
  8142. if (label.parentNode) {
  8143. label.parentNode.removeChild(label);
  8144. }
  8145. var foreground = this.dom.foreground;
  8146. if (foreground.parentNode) {
  8147. foreground.parentNode.removeChild(foreground);
  8148. }
  8149. var background = this.dom.background;
  8150. if (background.parentNode) {
  8151. background.parentNode.removeChild(background);
  8152. }
  8153. var axis = this.dom.axis;
  8154. if (axis.parentNode) {
  8155. axis.parentNode.removeChild(axis);
  8156. }
  8157. };
  8158. /**
  8159. * Add an item to the group
  8160. * @param {Item} item
  8161. */
  8162. Group.prototype.add = function(item) {
  8163. this.items[item.id] = item;
  8164. item.setParent(this);
  8165. if (this.visibleItems.indexOf(item) == -1) {
  8166. var range = this.itemSet.body.range; // TODO: not nice accessing the range like this
  8167. this._checkIfVisible(item, this.visibleItems, range);
  8168. }
  8169. };
  8170. /**
  8171. * Remove an item from the group
  8172. * @param {Item} item
  8173. */
  8174. Group.prototype.remove = function(item) {
  8175. delete this.items[item.id];
  8176. item.setParent(this.itemSet);
  8177. // remove from visible items
  8178. var index = this.visibleItems.indexOf(item);
  8179. if (index != -1) this.visibleItems.splice(index, 1);
  8180. // TODO: also remove from ordered items?
  8181. };
  8182. /**
  8183. * Remove an item from the corresponding DataSet
  8184. * @param {Item} item
  8185. */
  8186. Group.prototype.removeFromDataSet = function(item) {
  8187. this.itemSet.removeItem(item.id);
  8188. };
  8189. /**
  8190. * Reorder the items
  8191. */
  8192. Group.prototype.order = function() {
  8193. var array = util.toArray(this.items);
  8194. this.orderedItems.byStart = array;
  8195. this.orderedItems.byEnd = this._constructByEndArray(array);
  8196. stack.orderByStart(this.orderedItems.byStart);
  8197. stack.orderByEnd(this.orderedItems.byEnd);
  8198. };
  8199. /**
  8200. * Create an array containing all items being a range (having an end date)
  8201. * @param {Item[]} array
  8202. * @returns {RangeItem[]}
  8203. * @private
  8204. */
  8205. Group.prototype._constructByEndArray = function(array) {
  8206. var endArray = [];
  8207. for (var i = 0; i < array.length; i++) {
  8208. if (array[i] instanceof RangeItem) {
  8209. endArray.push(array[i]);
  8210. }
  8211. }
  8212. return endArray;
  8213. };
  8214. /**
  8215. * Update the visible items
  8216. * @param {{byStart: Item[], byEnd: Item[]}} orderedItems All items ordered by start date and by end date
  8217. * @param {Item[]} visibleItems The previously visible items.
  8218. * @param {{start: number, end: number}} range Visible range
  8219. * @return {Item[]} visibleItems The new visible items.
  8220. * @private
  8221. */
  8222. Group.prototype._updateVisibleItems = function(orderedItems, visibleItems, range) {
  8223. var initialPosByStart,
  8224. newVisibleItems = [],
  8225. i;
  8226. // first check if the items that were in view previously are still in view.
  8227. // this handles the case for the RangeItem that is both before and after the current one.
  8228. if (visibleItems.length > 0) {
  8229. for (i = 0; i < visibleItems.length; i++) {
  8230. this._checkIfVisible(visibleItems[i], newVisibleItems, range);
  8231. }
  8232. }
  8233. // If there were no visible items previously, use binarySearch to find a visible PointItem or RangeItem (based on startTime)
  8234. if (newVisibleItems.length == 0) {
  8235. initialPosByStart = util.binarySearch(orderedItems.byStart, range, 'data','start');
  8236. }
  8237. else {
  8238. initialPosByStart = orderedItems.byStart.indexOf(newVisibleItems[0]);
  8239. }
  8240. // use visible search to find a visible RangeItem (only based on endTime)
  8241. var initialPosByEnd = util.binarySearch(orderedItems.byEnd, range, 'data','end');
  8242. // if we found a initial ID to use, trace it up and down until we meet an invisible item.
  8243. if (initialPosByStart != -1) {
  8244. for (i = initialPosByStart; i >= 0; i--) {
  8245. if (this._checkIfInvisible(orderedItems.byStart[i], newVisibleItems, range)) {break;}
  8246. }
  8247. for (i = initialPosByStart + 1; i < orderedItems.byStart.length; i++) {
  8248. if (this._checkIfInvisible(orderedItems.byStart[i], newVisibleItems, range)) {break;}
  8249. }
  8250. }
  8251. // if we found a initial ID to use, trace it up and down until we meet an invisible item.
  8252. if (initialPosByEnd != -1) {
  8253. for (i = initialPosByEnd; i >= 0; i--) {
  8254. if (this._checkIfInvisible(orderedItems.byEnd[i], newVisibleItems, range)) {break;}
  8255. }
  8256. for (i = initialPosByEnd + 1; i < orderedItems.byEnd.length; i++) {
  8257. if (this._checkIfInvisible(orderedItems.byEnd[i], newVisibleItems, range)) {break;}
  8258. }
  8259. }
  8260. return newVisibleItems;
  8261. };
  8262. /**
  8263. * this function checks if an item is invisible. If it is NOT we make it visible
  8264. * and add it to the global visible items. If it is, return true.
  8265. *
  8266. * @param {Item} item
  8267. * @param {Item[]} visibleItems
  8268. * @param {{start:number, end:number}} range
  8269. * @returns {boolean}
  8270. * @private
  8271. */
  8272. Group.prototype._checkIfInvisible = function(item, visibleItems, range) {
  8273. if (item.isVisible(range)) {
  8274. if (!item.displayed) item.show();
  8275. item.repositionX();
  8276. if (visibleItems.indexOf(item) == -1) {
  8277. visibleItems.push(item);
  8278. }
  8279. return false;
  8280. }
  8281. else {
  8282. if (item.displayed) item.hide();
  8283. return true;
  8284. }
  8285. };
  8286. /**
  8287. * this function is very similar to the _checkIfInvisible() but it does not
  8288. * return booleans, hides the item if it should not be seen and always adds to
  8289. * the visibleItems.
  8290. * this one is for brute forcing and hiding.
  8291. *
  8292. * @param {Item} item
  8293. * @param {Array} visibleItems
  8294. * @param {{start:number, end:number}} range
  8295. * @private
  8296. */
  8297. Group.prototype._checkIfVisible = function(item, visibleItems, range) {
  8298. if (item.isVisible(range)) {
  8299. if (!item.displayed) item.show();
  8300. // reposition item horizontally
  8301. item.repositionX();
  8302. visibleItems.push(item);
  8303. }
  8304. else {
  8305. if (item.displayed) item.hide();
  8306. }
  8307. };
  8308. module.exports = Group;
  8309. /***/ },
  8310. /* 24 */
  8311. /***/ function(module, exports, __webpack_require__) {
  8312. var Hammer = __webpack_require__(42);
  8313. var util = __webpack_require__(1);
  8314. var DataSet = __webpack_require__(3);
  8315. var DataView = __webpack_require__(4);
  8316. var Component = __webpack_require__(18);
  8317. var Group = __webpack_require__(23);
  8318. var BoxItem = __webpack_require__(30);
  8319. var PointItem = __webpack_require__(31);
  8320. var RangeItem = __webpack_require__(32);
  8321. var BackgroundItem = __webpack_require__(29);
  8322. var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
  8323. /**
  8324. * An ItemSet holds a set of items and ranges which can be displayed in a
  8325. * range. The width is determined by the parent of the ItemSet, and the height
  8326. * is determined by the size of the items.
  8327. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
  8328. * @param {Object} [options] See ItemSet.setOptions for the available options.
  8329. * @constructor ItemSet
  8330. * @extends Component
  8331. */
  8332. function ItemSet(body, options) {
  8333. this.body = body;
  8334. this.defaultOptions = {
  8335. type: null, // 'box', 'point', 'range', 'background'
  8336. orientation: 'bottom', // 'top' or 'bottom'
  8337. align: 'auto', // alignment of box items
  8338. stack: true,
  8339. groupOrder: null,
  8340. selectable: true,
  8341. editable: {
  8342. updateTime: false,
  8343. updateGroup: false,
  8344. add: false,
  8345. remove: false
  8346. },
  8347. onAdd: function (item, callback) {
  8348. callback(item);
  8349. },
  8350. onUpdate: function (item, callback) {
  8351. callback(item);
  8352. },
  8353. onMove: function (item, callback) {
  8354. callback(item);
  8355. },
  8356. onRemove: function (item, callback) {
  8357. callback(item);
  8358. },
  8359. onMoving: function (item, callback) {
  8360. callback(item);
  8361. },
  8362. margin: {
  8363. item: {
  8364. horizontal: 10,
  8365. vertical: 10
  8366. },
  8367. axis: 20
  8368. },
  8369. padding: 5
  8370. };
  8371. // options is shared by this ItemSet and all its items
  8372. this.options = util.extend({}, this.defaultOptions);
  8373. // options for getting items from the DataSet with the correct type
  8374. this.itemOptions = {
  8375. type: {start: 'Date', end: 'Date'}
  8376. };
  8377. this.conversion = {
  8378. toScreen: body.util.toScreen,
  8379. toTime: body.util.toTime
  8380. };
  8381. this.dom = {};
  8382. this.props = {};
  8383. this.hammer = null;
  8384. var me = this;
  8385. this.itemsData = null; // DataSet
  8386. this.groupsData = null; // DataSet
  8387. // listeners for the DataSet of the items
  8388. this.itemListeners = {
  8389. 'add': function (event, params, senderId) {
  8390. me._onAdd(params.items);
  8391. },
  8392. 'update': function (event, params, senderId) {
  8393. me._onUpdate(params.items);
  8394. },
  8395. 'remove': function (event, params, senderId) {
  8396. me._onRemove(params.items);
  8397. }
  8398. };
  8399. // listeners for the DataSet of the groups
  8400. this.groupListeners = {
  8401. 'add': function (event, params, senderId) {
  8402. me._onAddGroups(params.items);
  8403. },
  8404. 'update': function (event, params, senderId) {
  8405. me._onUpdateGroups(params.items);
  8406. },
  8407. 'remove': function (event, params, senderId) {
  8408. me._onRemoveGroups(params.items);
  8409. }
  8410. };
  8411. this.items = {}; // object with an Item for every data item
  8412. this.groups = {}; // Group object for every group
  8413. this.groupIds = [];
  8414. this.selection = []; // list with the ids of all selected nodes
  8415. this.stackDirty = true; // if true, all items will be restacked on next redraw
  8416. this.touchParams = {}; // stores properties while dragging
  8417. // create the HTML DOM
  8418. this._create();
  8419. this.setOptions(options);
  8420. }
  8421. ItemSet.prototype = new Component();
  8422. // available item types will be registered here
  8423. ItemSet.types = {
  8424. background: BackgroundItem,
  8425. box: BoxItem,
  8426. range: RangeItem,
  8427. point: PointItem
  8428. };
  8429. /**
  8430. * Create the HTML DOM for the ItemSet
  8431. */
  8432. ItemSet.prototype._create = function(){
  8433. var frame = document.createElement('div');
  8434. frame.className = 'itemset';
  8435. frame['timeline-itemset'] = this;
  8436. this.dom.frame = frame;
  8437. // create background panel
  8438. var background = document.createElement('div');
  8439. background.className = 'background';
  8440. frame.appendChild(background);
  8441. this.dom.background = background;
  8442. // create foreground panel
  8443. var foreground = document.createElement('div');
  8444. foreground.className = 'foreground';
  8445. frame.appendChild(foreground);
  8446. this.dom.foreground = foreground;
  8447. // create axis panel
  8448. var axis = document.createElement('div');
  8449. axis.className = 'axis';
  8450. this.dom.axis = axis;
  8451. // create labelset
  8452. var labelSet = document.createElement('div');
  8453. labelSet.className = 'labelset';
  8454. this.dom.labelSet = labelSet;
  8455. // create ungrouped Group
  8456. this._updateUngrouped();
  8457. // attach event listeners
  8458. // Note: we bind to the centerContainer for the case where the height
  8459. // of the center container is larger than of the ItemSet, so we
  8460. // can click in the empty area to create a new item or deselect an item.
  8461. this.hammer = Hammer(this.body.dom.centerContainer, {
  8462. prevent_default: true
  8463. });
  8464. // drag items when selected
  8465. this.hammer.on('touch', this._onTouch.bind(this));
  8466. this.hammer.on('dragstart', this._onDragStart.bind(this));
  8467. this.hammer.on('drag', this._onDrag.bind(this));
  8468. this.hammer.on('dragend', this._onDragEnd.bind(this));
  8469. // single select (or unselect) when tapping an item
  8470. this.hammer.on('tap', this._onSelectItem.bind(this));
  8471. // multi select when holding mouse/touch, or on ctrl+click
  8472. this.hammer.on('hold', this._onMultiSelectItem.bind(this));
  8473. // add item on doubletap
  8474. this.hammer.on('doubletap', this._onAddItem.bind(this));
  8475. // attach to the DOM
  8476. this.show();
  8477. };
  8478. /**
  8479. * Set options for the ItemSet. Existing options will be extended/overwritten.
  8480. * @param {Object} [options] The following options are available:
  8481. * {String} type
  8482. * Default type for the items. Choose from 'box'
  8483. * (default), 'point', 'range', or 'background'.
  8484. * The default style can be overwritten by
  8485. * individual items.
  8486. * {String} align
  8487. * Alignment for the items, only applicable for
  8488. * BoxItem. Choose 'center' (default), 'left', or
  8489. * 'right'.
  8490. * {String} orientation
  8491. * Orientation of the item set. Choose 'top' or
  8492. * 'bottom' (default).
  8493. * {Function} groupOrder
  8494. * A sorting function for ordering groups
  8495. * {Boolean} stack
  8496. * If true (deafult), items will be stacked on
  8497. * top of each other.
  8498. * {Number} margin.axis
  8499. * Margin between the axis and the items in pixels.
  8500. * Default is 20.
  8501. * {Number} margin.item.horizontal
  8502. * Horizontal margin between items in pixels.
  8503. * Default is 10.
  8504. * {Number} margin.item.vertical
  8505. * Vertical Margin between items in pixels.
  8506. * Default is 10.
  8507. * {Number} margin.item
  8508. * Margin between items in pixels in both horizontal
  8509. * and vertical direction. Default is 10.
  8510. * {Number} margin
  8511. * Set margin for both axis and items in pixels.
  8512. * {Number} padding
  8513. * Padding of the contents of an item in pixels.
  8514. * Must correspond with the items css. Default is 5.
  8515. * {Boolean} selectable
  8516. * If true (default), items can be selected.
  8517. * {Boolean} editable
  8518. * Set all editable options to true or false
  8519. * {Boolean} editable.updateTime
  8520. * Allow dragging an item to an other moment in time
  8521. * {Boolean} editable.updateGroup
  8522. * Allow dragging an item to an other group
  8523. * {Boolean} editable.add
  8524. * Allow creating new items on double tap
  8525. * {Boolean} editable.remove
  8526. * Allow removing items by clicking the delete button
  8527. * top right of a selected item.
  8528. * {Function(item: Item, callback: Function)} onAdd
  8529. * Callback function triggered when an item is about to be added:
  8530. * when the user double taps an empty space in the Timeline.
  8531. * {Function(item: Item, callback: Function)} onUpdate
  8532. * Callback function fired when an item is about to be updated.
  8533. * This function typically has to show a dialog where the user
  8534. * change the item. If not implemented, nothing happens.
  8535. * {Function(item: Item, callback: Function)} onMove
  8536. * Fired when an item has been moved. If not implemented,
  8537. * the move action will be accepted.
  8538. * {Function(item: Item, callback: Function)} onRemove
  8539. * Fired when an item is about to be deleted.
  8540. * If not implemented, the item will be always removed.
  8541. */
  8542. ItemSet.prototype.setOptions = function(options) {
  8543. if (options) {
  8544. // copy all options that we know
  8545. var fields = ['type', 'align', 'orientation', 'padding', 'stack', 'selectable', 'groupOrder', 'dataAttributes', 'template'];
  8546. util.selectiveExtend(fields, this.options, options);
  8547. if ('margin' in options) {
  8548. if (typeof options.margin === 'number') {
  8549. this.options.margin.axis = options.margin;
  8550. this.options.margin.item.horizontal = options.margin;
  8551. this.options.margin.item.vertical = options.margin;
  8552. }
  8553. else if (typeof options.margin === 'object') {
  8554. util.selectiveExtend(['axis'], this.options.margin, options.margin);
  8555. if ('item' in options.margin) {
  8556. if (typeof options.margin.item === 'number') {
  8557. this.options.margin.item.horizontal = options.margin.item;
  8558. this.options.margin.item.vertical = options.margin.item;
  8559. }
  8560. else if (typeof options.margin.item === 'object') {
  8561. util.selectiveExtend(['horizontal', 'vertical'], this.options.margin.item, options.margin.item);
  8562. }
  8563. }
  8564. }
  8565. }
  8566. if ('editable' in options) {
  8567. if (typeof options.editable === 'boolean') {
  8568. this.options.editable.updateTime = options.editable;
  8569. this.options.editable.updateGroup = options.editable;
  8570. this.options.editable.add = options.editable;
  8571. this.options.editable.remove = options.editable;
  8572. }
  8573. else if (typeof options.editable === 'object') {
  8574. util.selectiveExtend(['updateTime', 'updateGroup', 'add', 'remove'], this.options.editable, options.editable);
  8575. }
  8576. }
  8577. // callback functions
  8578. var addCallback = (function (name) {
  8579. var fn = options[name];
  8580. if (fn) {
  8581. if (!(fn instanceof Function)) {
  8582. throw new Error('option ' + name + ' must be a function ' + name + '(item, callback)');
  8583. }
  8584. this.options[name] = fn;
  8585. }
  8586. }).bind(this);
  8587. ['onAdd', 'onUpdate', 'onRemove', 'onMove', 'onMoving'].forEach(addCallback);
  8588. // force the itemSet to refresh: options like orientation and margins may be changed
  8589. this.markDirty();
  8590. }
  8591. };
  8592. /**
  8593. * Mark the ItemSet dirty so it will refresh everything with next redraw
  8594. */
  8595. ItemSet.prototype.markDirty = function() {
  8596. this.groupIds = [];
  8597. this.stackDirty = true;
  8598. };
  8599. /**
  8600. * Destroy the ItemSet
  8601. */
  8602. ItemSet.prototype.destroy = function() {
  8603. this.hide();
  8604. this.setItems(null);
  8605. this.setGroups(null);
  8606. this.hammer = null;
  8607. this.body = null;
  8608. this.conversion = null;
  8609. };
  8610. /**
  8611. * Hide the component from the DOM
  8612. */
  8613. ItemSet.prototype.hide = function() {
  8614. // remove the frame containing the items
  8615. if (this.dom.frame.parentNode) {
  8616. this.dom.frame.parentNode.removeChild(this.dom.frame);
  8617. }
  8618. // remove the axis with dots
  8619. if (this.dom.axis.parentNode) {
  8620. this.dom.axis.parentNode.removeChild(this.dom.axis);
  8621. }
  8622. // remove the labelset containing all group labels
  8623. if (this.dom.labelSet.parentNode) {
  8624. this.dom.labelSet.parentNode.removeChild(this.dom.labelSet);
  8625. }
  8626. };
  8627. /**
  8628. * Show the component in the DOM (when not already visible).
  8629. * @return {Boolean} changed
  8630. */
  8631. ItemSet.prototype.show = function() {
  8632. // show frame containing the items
  8633. if (!this.dom.frame.parentNode) {
  8634. this.body.dom.center.appendChild(this.dom.frame);
  8635. }
  8636. // show axis with dots
  8637. if (!this.dom.axis.parentNode) {
  8638. this.body.dom.backgroundVertical.appendChild(this.dom.axis);
  8639. }
  8640. // show labelset containing labels
  8641. if (!this.dom.labelSet.parentNode) {
  8642. this.body.dom.left.appendChild(this.dom.labelSet);
  8643. }
  8644. };
  8645. /**
  8646. * Set selected items by their id. Replaces the current selection
  8647. * Unknown id's are silently ignored.
  8648. * @param {string[] | string} [ids] An array with zero or more id's of the items to be
  8649. * selected, or a single item id. If ids is undefined
  8650. * or an empty array, all items will be unselected.
  8651. */
  8652. ItemSet.prototype.setSelection = function(ids) {
  8653. var i, ii, id, item;
  8654. if (ids == undefined) ids = [];
  8655. if (!Array.isArray(ids)) ids = [ids];
  8656. // unselect currently selected items
  8657. for (i = 0, ii = this.selection.length; i < ii; i++) {
  8658. id = this.selection[i];
  8659. item = this.items[id];
  8660. if (item) item.unselect();
  8661. }
  8662. // select items
  8663. this.selection = [];
  8664. for (i = 0, ii = ids.length; i < ii; i++) {
  8665. id = ids[i];
  8666. item = this.items[id];
  8667. if (item) {
  8668. this.selection.push(id);
  8669. item.select();
  8670. }
  8671. }
  8672. };
  8673. /**
  8674. * Get the selected items by their id
  8675. * @return {Array} ids The ids of the selected items
  8676. */
  8677. ItemSet.prototype.getSelection = function() {
  8678. return this.selection.concat([]);
  8679. };
  8680. /**
  8681. * Get the id's of the currently visible items.
  8682. * @returns {Array} The ids of the visible items
  8683. */
  8684. ItemSet.prototype.getVisibleItems = function() {
  8685. var range = this.body.range.getRange();
  8686. var left = this.body.util.toScreen(range.start);
  8687. var right = this.body.util.toScreen(range.end);
  8688. var ids = [];
  8689. for (var groupId in this.groups) {
  8690. if (this.groups.hasOwnProperty(groupId)) {
  8691. var group = this.groups[groupId];
  8692. var rawVisibleItems = group.visibleItems;
  8693. // filter the "raw" set with visibleItems into a set which is really
  8694. // visible by pixels
  8695. for (var i = 0; i < rawVisibleItems.length; i++) {
  8696. var item = rawVisibleItems[i];
  8697. // TODO: also check whether visible vertically
  8698. if ((item.left < right) && (item.left + item.width > left)) {
  8699. ids.push(item.id);
  8700. }
  8701. }
  8702. }
  8703. }
  8704. return ids;
  8705. };
  8706. /**
  8707. * Deselect a selected item
  8708. * @param {String | Number} id
  8709. * @private
  8710. */
  8711. ItemSet.prototype._deselect = function(id) {
  8712. var selection = this.selection;
  8713. for (var i = 0, ii = selection.length; i < ii; i++) {
  8714. if (selection[i] == id) { // non-strict comparison!
  8715. selection.splice(i, 1);
  8716. break;
  8717. }
  8718. }
  8719. };
  8720. /**
  8721. * Repaint the component
  8722. * @return {boolean} Returns true if the component is resized
  8723. */
  8724. ItemSet.prototype.redraw = function() {
  8725. var margin = this.options.margin,
  8726. range = this.body.range,
  8727. asSize = util.option.asSize,
  8728. options = this.options,
  8729. orientation = options.orientation,
  8730. resized = false,
  8731. frame = this.dom.frame,
  8732. editable = options.editable.updateTime || options.editable.updateGroup;
  8733. // recalculate absolute position (before redrawing groups)
  8734. this.props.top = this.body.domProps.top.height + this.body.domProps.border.top;
  8735. this.props.left = this.body.domProps.left.width + this.body.domProps.border.left;
  8736. // update class name
  8737. frame.className = 'itemset' + (editable ? ' editable' : '');
  8738. // reorder the groups (if needed)
  8739. resized = this._orderGroups() || resized;
  8740. // check whether zoomed (in that case we need to re-stack everything)
  8741. // TODO: would be nicer to get this as a trigger from Range
  8742. var visibleInterval = range.end - range.start;
  8743. var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.props.width != this.props.lastWidth);
  8744. if (zoomed) this.stackDirty = true;
  8745. this.lastVisibleInterval = visibleInterval;
  8746. this.props.lastWidth = this.props.width;
  8747. // redraw all groups
  8748. var restack = this.stackDirty,
  8749. firstGroup = this._firstGroup(),
  8750. firstMargin = {
  8751. item: margin.item,
  8752. axis: margin.axis
  8753. },
  8754. nonFirstMargin = {
  8755. item: margin.item,
  8756. axis: margin.item.vertical / 2
  8757. },
  8758. height = 0,
  8759. minHeight = margin.axis + margin.item.vertical;
  8760. util.forEach(this.groups, function (group) {
  8761. var groupMargin = (group == firstGroup) ? firstMargin : nonFirstMargin;
  8762. var groupResized = group.redraw(range, groupMargin, restack);
  8763. resized = groupResized || resized;
  8764. height += group.height;
  8765. });
  8766. height = Math.max(height, minHeight);
  8767. this.stackDirty = false;
  8768. // update frame height
  8769. frame.style.height = asSize(height);
  8770. // calculate actual size
  8771. this.props.width = frame.offsetWidth;
  8772. this.props.height = height;
  8773. // reposition axis
  8774. // reposition axis
  8775. this.dom.axis.style.top = asSize((orientation == 'top') ?
  8776. (this.body.domProps.top.height + this.body.domProps.border.top) :
  8777. (this.body.domProps.top.height + this.body.domProps.centerContainer.height));
  8778. this.dom.axis.style.left = '0';
  8779. // check if this component is resized
  8780. resized = this._isResized() || resized;
  8781. return resized;
  8782. };
  8783. /**
  8784. * Get the first group, aligned with the axis
  8785. * @return {Group | null} firstGroup
  8786. * @private
  8787. */
  8788. ItemSet.prototype._firstGroup = function() {
  8789. var firstGroupIndex = (this.options.orientation == 'top') ? 0 : (this.groupIds.length - 1);
  8790. var firstGroupId = this.groupIds[firstGroupIndex];
  8791. var firstGroup = this.groups[firstGroupId] || this.groups[UNGROUPED];
  8792. return firstGroup || null;
  8793. };
  8794. /**
  8795. * Create or delete the group holding all ungrouped items. This group is used when
  8796. * there are no groups specified.
  8797. * @protected
  8798. */
  8799. ItemSet.prototype._updateUngrouped = function() {
  8800. var ungrouped = this.groups[UNGROUPED];
  8801. if (this.groupsData) {
  8802. // remove the group holding all ungrouped items
  8803. if (ungrouped) {
  8804. ungrouped.hide();
  8805. delete this.groups[UNGROUPED];
  8806. }
  8807. }
  8808. else {
  8809. // create a group holding all (unfiltered) items
  8810. if (!ungrouped) {
  8811. var id = null;
  8812. var data = null;
  8813. ungrouped = new Group(id, data, this);
  8814. this.groups[UNGROUPED] = ungrouped;
  8815. for (var itemId in this.items) {
  8816. if (this.items.hasOwnProperty(itemId)) {
  8817. ungrouped.add(this.items[itemId]);
  8818. }
  8819. }
  8820. ungrouped.show();
  8821. }
  8822. }
  8823. };
  8824. /**
  8825. * Get the element for the labelset
  8826. * @return {HTMLElement} labelSet
  8827. */
  8828. ItemSet.prototype.getLabelSet = function() {
  8829. return this.dom.labelSet;
  8830. };
  8831. /**
  8832. * Set items
  8833. * @param {vis.DataSet | null} items
  8834. */
  8835. ItemSet.prototype.setItems = function(items) {
  8836. var me = this,
  8837. ids,
  8838. oldItemsData = this.itemsData;
  8839. // replace the dataset
  8840. if (!items) {
  8841. this.itemsData = null;
  8842. }
  8843. else if (items instanceof DataSet || items instanceof DataView) {
  8844. this.itemsData = items;
  8845. }
  8846. else {
  8847. throw new TypeError('Data must be an instance of DataSet or DataView');
  8848. }
  8849. if (oldItemsData) {
  8850. // unsubscribe from old dataset
  8851. util.forEach(this.itemListeners, function (callback, event) {
  8852. oldItemsData.off(event, callback);
  8853. });
  8854. // remove all drawn items
  8855. ids = oldItemsData.getIds();
  8856. this._onRemove(ids);
  8857. }
  8858. if (this.itemsData) {
  8859. // subscribe to new dataset
  8860. var id = this.id;
  8861. util.forEach(this.itemListeners, function (callback, event) {
  8862. me.itemsData.on(event, callback, id);
  8863. });
  8864. // add all new items
  8865. ids = this.itemsData.getIds();
  8866. this._onAdd(ids);
  8867. // update the group holding all ungrouped items
  8868. this._updateUngrouped();
  8869. }
  8870. };
  8871. /**
  8872. * Get the current items
  8873. * @returns {vis.DataSet | null}
  8874. */
  8875. ItemSet.prototype.getItems = function() {
  8876. return this.itemsData;
  8877. };
  8878. /**
  8879. * Set groups
  8880. * @param {vis.DataSet} groups
  8881. */
  8882. ItemSet.prototype.setGroups = function(groups) {
  8883. var me = this,
  8884. ids;
  8885. // unsubscribe from current dataset
  8886. if (this.groupsData) {
  8887. util.forEach(this.groupListeners, function (callback, event) {
  8888. me.groupsData.unsubscribe(event, callback);
  8889. });
  8890. // remove all drawn groups
  8891. ids = this.groupsData.getIds();
  8892. this.groupsData = null;
  8893. this._onRemoveGroups(ids); // note: this will cause a redraw
  8894. }
  8895. // replace the dataset
  8896. if (!groups) {
  8897. this.groupsData = null;
  8898. }
  8899. else if (groups instanceof DataSet || groups instanceof DataView) {
  8900. this.groupsData = groups;
  8901. }
  8902. else {
  8903. throw new TypeError('Data must be an instance of DataSet or DataView');
  8904. }
  8905. if (this.groupsData) {
  8906. // subscribe to new dataset
  8907. var id = this.id;
  8908. util.forEach(this.groupListeners, function (callback, event) {
  8909. me.groupsData.on(event, callback, id);
  8910. });
  8911. // draw all ms
  8912. ids = this.groupsData.getIds();
  8913. this._onAddGroups(ids);
  8914. }
  8915. // update the group holding all ungrouped items
  8916. this._updateUngrouped();
  8917. // update the order of all items in each group
  8918. this._order();
  8919. this.body.emitter.emit('change');
  8920. };
  8921. /**
  8922. * Get the current groups
  8923. * @returns {vis.DataSet | null} groups
  8924. */
  8925. ItemSet.prototype.getGroups = function() {
  8926. return this.groupsData;
  8927. };
  8928. /**
  8929. * Remove an item by its id
  8930. * @param {String | Number} id
  8931. */
  8932. ItemSet.prototype.removeItem = function(id) {
  8933. var item = this.itemsData.get(id),
  8934. dataset = this.itemsData.getDataSet();
  8935. if (item) {
  8936. // confirm deletion
  8937. this.options.onRemove(item, function (item) {
  8938. if (item) {
  8939. // remove by id here, it is possible that an item has no id defined
  8940. // itself, so better not delete by the item itself
  8941. dataset.remove(id);
  8942. }
  8943. });
  8944. }
  8945. };
  8946. /**
  8947. * Handle updated items
  8948. * @param {Number[]} ids
  8949. * @protected
  8950. */
  8951. ItemSet.prototype._onUpdate = function(ids) {
  8952. var me = this;
  8953. ids.forEach(function (id) {
  8954. var itemData = me.itemsData.get(id, me.itemOptions),
  8955. item = me.items[id],
  8956. type = itemData.type || me.options.type || (itemData.end ? 'range' : 'box');
  8957. var constructor = ItemSet.types[type];
  8958. if (item) {
  8959. // update item
  8960. if (!constructor || !(item instanceof constructor)) {
  8961. // item type has changed, delete the item and recreate it
  8962. me._removeItem(item);
  8963. item = null;
  8964. }
  8965. else {
  8966. me._updateItem(item, itemData);
  8967. }
  8968. }
  8969. if (!item) {
  8970. // create item
  8971. if (constructor) {
  8972. item = new constructor(itemData, me.conversion, me.options);
  8973. item.id = id; // TODO: not so nice setting id afterwards
  8974. me._addItem(item);
  8975. }
  8976. else if (type == 'rangeoverflow') {
  8977. // TODO: deprecated since version 2.1.0 (or 3.0.0?). cleanup some day
  8978. throw new TypeError('Item type "rangeoverflow" is deprecated. Use css styling instead: ' +
  8979. '.vis.timeline .item.range .content {overflow: visible;}');
  8980. }
  8981. else {
  8982. throw new TypeError('Unknown item type "' + type + '"');
  8983. }
  8984. }
  8985. });
  8986. this._order();
  8987. this.stackDirty = true; // force re-stacking of all items next redraw
  8988. this.body.emitter.emit('change');
  8989. };
  8990. /**
  8991. * Handle added items
  8992. * @param {Number[]} ids
  8993. * @protected
  8994. */
  8995. ItemSet.prototype._onAdd = ItemSet.prototype._onUpdate;
  8996. /**
  8997. * Handle removed items
  8998. * @param {Number[]} ids
  8999. * @protected
  9000. */
  9001. ItemSet.prototype._onRemove = function(ids) {
  9002. var count = 0;
  9003. var me = this;
  9004. ids.forEach(function (id) {
  9005. var item = me.items[id];
  9006. if (item) {
  9007. count++;
  9008. me._removeItem(item);
  9009. }
  9010. });
  9011. if (count) {
  9012. // update order
  9013. this._order();
  9014. this.stackDirty = true; // force re-stacking of all items next redraw
  9015. this.body.emitter.emit('change');
  9016. }
  9017. };
  9018. /**
  9019. * Update the order of item in all groups
  9020. * @private
  9021. */
  9022. ItemSet.prototype._order = function() {
  9023. // reorder the items in all groups
  9024. // TODO: optimization: only reorder groups affected by the changed items
  9025. util.forEach(this.groups, function (group) {
  9026. group.order();
  9027. });
  9028. };
  9029. /**
  9030. * Handle updated groups
  9031. * @param {Number[]} ids
  9032. * @private
  9033. */
  9034. ItemSet.prototype._onUpdateGroups = function(ids) {
  9035. this._onAddGroups(ids);
  9036. };
  9037. /**
  9038. * Handle changed groups
  9039. * @param {Number[]} ids
  9040. * @private
  9041. */
  9042. ItemSet.prototype._onAddGroups = function(ids) {
  9043. var me = this;
  9044. ids.forEach(function (id) {
  9045. var groupData = me.groupsData.get(id);
  9046. var group = me.groups[id];
  9047. if (!group) {
  9048. // check for reserved ids
  9049. if (id == UNGROUPED) {
  9050. throw new Error('Illegal group id. ' + id + ' is a reserved id.');
  9051. }
  9052. var groupOptions = Object.create(me.options);
  9053. util.extend(groupOptions, {
  9054. height: null
  9055. });
  9056. group = new Group(id, groupData, me);
  9057. me.groups[id] = group;
  9058. // add items with this groupId to the new group
  9059. for (var itemId in me.items) {
  9060. if (me.items.hasOwnProperty(itemId)) {
  9061. var item = me.items[itemId];
  9062. if (item.data.group == id) {
  9063. group.add(item);
  9064. }
  9065. }
  9066. }
  9067. group.order();
  9068. group.show();
  9069. }
  9070. else {
  9071. // update group
  9072. group.setData(groupData);
  9073. }
  9074. });
  9075. this.body.emitter.emit('change');
  9076. };
  9077. /**
  9078. * Handle removed groups
  9079. * @param {Number[]} ids
  9080. * @private
  9081. */
  9082. ItemSet.prototype._onRemoveGroups = function(ids) {
  9083. var groups = this.groups;
  9084. ids.forEach(function (id) {
  9085. var group = groups[id];
  9086. if (group) {
  9087. group.hide();
  9088. delete groups[id];
  9089. }
  9090. });
  9091. this.markDirty();
  9092. this.body.emitter.emit('change');
  9093. };
  9094. /**
  9095. * Reorder the groups if needed
  9096. * @return {boolean} changed
  9097. * @private
  9098. */
  9099. ItemSet.prototype._orderGroups = function () {
  9100. if (this.groupsData) {
  9101. // reorder the groups
  9102. var groupIds = this.groupsData.getIds({
  9103. order: this.options.groupOrder
  9104. });
  9105. var changed = !util.equalArray(groupIds, this.groupIds);
  9106. if (changed) {
  9107. // hide all groups, removes them from the DOM
  9108. var groups = this.groups;
  9109. groupIds.forEach(function (groupId) {
  9110. groups[groupId].hide();
  9111. });
  9112. // show the groups again, attach them to the DOM in correct order
  9113. groupIds.forEach(function (groupId) {
  9114. groups[groupId].show();
  9115. });
  9116. this.groupIds = groupIds;
  9117. }
  9118. return changed;
  9119. }
  9120. else {
  9121. return false;
  9122. }
  9123. };
  9124. /**
  9125. * Add a new item
  9126. * @param {Item} item
  9127. * @private
  9128. */
  9129. ItemSet.prototype._addItem = function(item) {
  9130. this.items[item.id] = item;
  9131. // add to group
  9132. var groupId = this.groupsData ? item.data.group : UNGROUPED;
  9133. var group = this.groups[groupId];
  9134. if (group) group.add(item);
  9135. };
  9136. /**
  9137. * Update an existing item
  9138. * @param {Item} item
  9139. * @param {Object} itemData
  9140. * @private
  9141. */
  9142. ItemSet.prototype._updateItem = function(item, itemData) {
  9143. var oldGroupId = item.data.group;
  9144. // update the items data (will redraw the item when displayed)
  9145. item.setData(itemData);
  9146. // update group
  9147. if (oldGroupId != item.data.group) {
  9148. var oldGroup = this.groups[oldGroupId];
  9149. if (oldGroup) oldGroup.remove(item);
  9150. var groupId = this.groupsData ? item.data.group : UNGROUPED;
  9151. var group = this.groups[groupId];
  9152. if (group) group.add(item);
  9153. }
  9154. };
  9155. /**
  9156. * Delete an item from the ItemSet: remove it from the DOM, from the map
  9157. * with items, and from the map with visible items, and from the selection
  9158. * @param {Item} item
  9159. * @private
  9160. */
  9161. ItemSet.prototype._removeItem = function(item) {
  9162. // remove from DOM
  9163. item.hide();
  9164. // remove from items
  9165. delete this.items[item.id];
  9166. // remove from selection
  9167. var index = this.selection.indexOf(item.id);
  9168. if (index != -1) this.selection.splice(index, 1);
  9169. // remove from group
  9170. var groupId = this.groupsData ? item.data.group : UNGROUPED;
  9171. var group = this.groups[groupId];
  9172. if (group) group.remove(item);
  9173. };
  9174. /**
  9175. * Create an array containing all items being a range (having an end date)
  9176. * @param array
  9177. * @returns {Array}
  9178. * @private
  9179. */
  9180. ItemSet.prototype._constructByEndArray = function(array) {
  9181. var endArray = [];
  9182. for (var i = 0; i < array.length; i++) {
  9183. if (array[i] instanceof RangeItem) {
  9184. endArray.push(array[i]);
  9185. }
  9186. }
  9187. return endArray;
  9188. };
  9189. /**
  9190. * Register the clicked item on touch, before dragStart is initiated.
  9191. *
  9192. * dragStart is initiated from a mousemove event, which can have left the item
  9193. * already resulting in an item == null
  9194. *
  9195. * @param {Event} event
  9196. * @private
  9197. */
  9198. ItemSet.prototype._onTouch = function (event) {
  9199. // store the touched item, used in _onDragStart
  9200. this.touchParams.item = ItemSet.itemFromTarget(event);
  9201. };
  9202. /**
  9203. * Start dragging the selected events
  9204. * @param {Event} event
  9205. * @private
  9206. */
  9207. ItemSet.prototype._onDragStart = function (event) {
  9208. if (!this.options.editable.updateTime && !this.options.editable.updateGroup) {
  9209. return;
  9210. }
  9211. var item = this.touchParams.item || null,
  9212. me = this,
  9213. props;
  9214. if (item && item.selected) {
  9215. var dragLeftItem = event.target.dragLeftItem;
  9216. var dragRightItem = event.target.dragRightItem;
  9217. if (dragLeftItem) {
  9218. props = {
  9219. item: dragLeftItem
  9220. };
  9221. if (me.options.editable.updateTime) {
  9222. props.start = item.data.start.valueOf();
  9223. }
  9224. if (me.options.editable.updateGroup) {
  9225. if ('group' in item.data) props.group = item.data.group;
  9226. }
  9227. this.touchParams.itemProps = [props];
  9228. }
  9229. else if (dragRightItem) {
  9230. props = {
  9231. item: dragRightItem
  9232. };
  9233. if (me.options.editable.updateTime) {
  9234. props.end = item.data.end.valueOf();
  9235. }
  9236. if (me.options.editable.updateGroup) {
  9237. if ('group' in item.data) props.group = item.data.group;
  9238. }
  9239. this.touchParams.itemProps = [props];
  9240. }
  9241. else {
  9242. this.touchParams.itemProps = this.getSelection().map(function (id) {
  9243. var item = me.items[id];
  9244. var props = {
  9245. item: item
  9246. };
  9247. if (me.options.editable.updateTime) {
  9248. if ('start' in item.data) props.start = item.data.start.valueOf();
  9249. if ('end' in item.data) props.end = item.data.end.valueOf();
  9250. }
  9251. if (me.options.editable.updateGroup) {
  9252. if ('group' in item.data) props.group = item.data.group;
  9253. }
  9254. return props;
  9255. });
  9256. }
  9257. event.stopPropagation();
  9258. }
  9259. };
  9260. /**
  9261. * Drag selected items
  9262. * @param {Event} event
  9263. * @private
  9264. */
  9265. ItemSet.prototype._onDrag = function (event) {
  9266. if (this.touchParams.itemProps) {
  9267. var me = this;
  9268. var range = this.body.range;
  9269. var snap = this.body.util.snap || null;
  9270. var deltaX = event.gesture.deltaX;
  9271. var scale = (this.props.width / (range.end - range.start));
  9272. var offset = deltaX / scale;
  9273. // move
  9274. this.touchParams.itemProps.forEach(function (props) {
  9275. var newProps = {};
  9276. if ('start' in props) {
  9277. var start = new Date(props.start + offset);
  9278. newProps.start = snap ? snap(start) : start;
  9279. }
  9280. if ('end' in props) {
  9281. var end = new Date(props.end + offset);
  9282. newProps.end = snap ? snap(end) : end;
  9283. }
  9284. if ('group' in props) {
  9285. // drag from one group to another
  9286. var group = ItemSet.groupFromTarget(event);
  9287. newProps.group = group && group.groupId;
  9288. }
  9289. // confirm moving the item
  9290. var itemData = util.extend({}, props.item.data, newProps);
  9291. me.options.onMoving(itemData, function (itemData) {
  9292. if (itemData) {
  9293. me._updateItemProps(props.item, itemData);
  9294. }
  9295. });
  9296. });
  9297. this.stackDirty = true; // force re-stacking of all items next redraw
  9298. this.body.emitter.emit('change');
  9299. event.stopPropagation();
  9300. }
  9301. };
  9302. /**
  9303. * Update an items properties
  9304. * @param {Item} item
  9305. * @param {Object} props Can contain properties start, end, and group.
  9306. * @private
  9307. */
  9308. ItemSet.prototype._updateItemProps = function(item, props) {
  9309. // TODO: copy all properties from props to item? (also new ones)
  9310. if ('start' in props) item.data.start = props.start;
  9311. if ('end' in props) item.data.end = props.end;
  9312. if ('group' in props && item.data.group != props.group) {
  9313. this._moveToGroup(item, props.group)
  9314. }
  9315. };
  9316. /**
  9317. * Move an item to another group
  9318. * @param {Item} item
  9319. * @param {String | Number} groupId
  9320. * @private
  9321. */
  9322. ItemSet.prototype._moveToGroup = function(item, groupId) {
  9323. var group = this.groups[groupId];
  9324. if (group && group.groupId != item.data.group) {
  9325. var oldGroup = item.parent;
  9326. oldGroup.remove(item);
  9327. oldGroup.order();
  9328. group.add(item);
  9329. group.order();
  9330. item.data.group = group.groupId;
  9331. }
  9332. };
  9333. /**
  9334. * End of dragging selected items
  9335. * @param {Event} event
  9336. * @private
  9337. */
  9338. ItemSet.prototype._onDragEnd = function (event) {
  9339. if (this.touchParams.itemProps) {
  9340. // prepare a change set for the changed items
  9341. var changes = [],
  9342. me = this,
  9343. dataset = this.itemsData.getDataSet();
  9344. var itemProps = this.touchParams.itemProps ;
  9345. this.touchParams.itemProps = null;
  9346. itemProps.forEach(function (props) {
  9347. var id = props.item.id,
  9348. itemData = me.itemsData.get(id, me.itemOptions);
  9349. var changed = false;
  9350. if ('start' in props.item.data) {
  9351. changed = (props.start != props.item.data.start.valueOf());
  9352. itemData.start = util.convert(props.item.data.start,
  9353. dataset._options.type && dataset._options.type.start || 'Date');
  9354. }
  9355. if ('end' in props.item.data) {
  9356. changed = changed || (props.end != props.item.data.end.valueOf());
  9357. itemData.end = util.convert(props.item.data.end,
  9358. dataset._options.type && dataset._options.type.end || 'Date');
  9359. }
  9360. if ('group' in props.item.data) {
  9361. changed = changed || (props.group != props.item.data.group);
  9362. itemData.group = props.item.data.group;
  9363. }
  9364. // only apply changes when start or end is actually changed
  9365. if (changed) {
  9366. me.options.onMove(itemData, function (itemData) {
  9367. if (itemData) {
  9368. // apply changes
  9369. itemData[dataset._fieldId] = id; // ensure the item contains its id (can be undefined)
  9370. changes.push(itemData);
  9371. }
  9372. else {
  9373. // restore original values
  9374. me._updateItemProps(props.item, props);
  9375. me.stackDirty = true; // force re-stacking of all items next redraw
  9376. me.body.emitter.emit('change');
  9377. }
  9378. });
  9379. }
  9380. });
  9381. // apply the changes to the data (if there are changes)
  9382. if (changes.length) {
  9383. dataset.update(changes);
  9384. }
  9385. event.stopPropagation();
  9386. }
  9387. };
  9388. /**
  9389. * Handle selecting/deselecting an item when tapping it
  9390. * @param {Event} event
  9391. * @private
  9392. */
  9393. ItemSet.prototype._onSelectItem = function (event) {
  9394. if (!this.options.selectable) return;
  9395. var ctrlKey = event.gesture.srcEvent && event.gesture.srcEvent.ctrlKey;
  9396. var shiftKey = event.gesture.srcEvent && event.gesture.srcEvent.shiftKey;
  9397. if (ctrlKey || shiftKey) {
  9398. this._onMultiSelectItem(event);
  9399. return;
  9400. }
  9401. var oldSelection = this.getSelection();
  9402. var item = ItemSet.itemFromTarget(event);
  9403. var selection = item ? [item.id] : [];
  9404. this.setSelection(selection);
  9405. var newSelection = this.getSelection();
  9406. // emit a select event,
  9407. // except when old selection is empty and new selection is still empty
  9408. if (newSelection.length > 0 || oldSelection.length > 0) {
  9409. this.body.emitter.emit('select', {
  9410. items: this.getSelection()
  9411. });
  9412. }
  9413. event.stopPropagation();
  9414. };
  9415. /**
  9416. * Handle creation and updates of an item on double tap
  9417. * @param event
  9418. * @private
  9419. */
  9420. ItemSet.prototype._onAddItem = function (event) {
  9421. if (!this.options.selectable) return;
  9422. if (!this.options.editable.add) return;
  9423. var me = this,
  9424. snap = this.body.util.snap || null,
  9425. item = ItemSet.itemFromTarget(event);
  9426. if (item) {
  9427. // update item
  9428. // execute async handler to update the item (or cancel it)
  9429. var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset
  9430. this.options.onUpdate(itemData, function (itemData) {
  9431. if (itemData) {
  9432. me.itemsData.update(itemData);
  9433. }
  9434. });
  9435. }
  9436. else {
  9437. // add item
  9438. var xAbs = util.getAbsoluteLeft(this.dom.frame);
  9439. var x = event.gesture.center.pageX - xAbs;
  9440. var start = this.body.util.toTime(x);
  9441. var newItem = {
  9442. start: snap ? snap(start) : start,
  9443. content: 'new item'
  9444. };
  9445. // when default type is a range, add a default end date to the new item
  9446. if (this.options.type === 'range') {
  9447. var end = this.body.util.toTime(x + this.props.width / 5);
  9448. newItem.end = snap ? snap(end) : end;
  9449. }
  9450. newItem[this.itemsData._fieldId] = util.randomUUID();
  9451. var group = ItemSet.groupFromTarget(event);
  9452. if (group) {
  9453. newItem.group = group.groupId;
  9454. }
  9455. // execute async handler to customize (or cancel) adding an item
  9456. this.options.onAdd(newItem, function (item) {
  9457. if (item) {
  9458. me.itemsData.add(item);
  9459. // TODO: need to trigger a redraw?
  9460. }
  9461. });
  9462. }
  9463. };
  9464. /**
  9465. * Handle selecting/deselecting multiple items when holding an item
  9466. * @param {Event} event
  9467. * @private
  9468. */
  9469. ItemSet.prototype._onMultiSelectItem = function (event) {
  9470. if (!this.options.selectable) return;
  9471. var selection,
  9472. item = ItemSet.itemFromTarget(event);
  9473. if (item) {
  9474. // multi select items
  9475. selection = this.getSelection(); // current selection
  9476. var index = selection.indexOf(item.id);
  9477. if (index == -1) {
  9478. // item is not yet selected -> select it
  9479. selection.push(item.id);
  9480. }
  9481. else {
  9482. // item is already selected -> deselect it
  9483. selection.splice(index, 1);
  9484. }
  9485. this.setSelection(selection);
  9486. this.body.emitter.emit('select', {
  9487. items: this.getSelection()
  9488. });
  9489. event.stopPropagation();
  9490. }
  9491. };
  9492. /**
  9493. * Find an item from an event target:
  9494. * searches for the attribute 'timeline-item' in the event target's element tree
  9495. * @param {Event} event
  9496. * @return {Item | null} item
  9497. */
  9498. ItemSet.itemFromTarget = function(event) {
  9499. var target = event.target;
  9500. while (target) {
  9501. if (target.hasOwnProperty('timeline-item')) {
  9502. return target['timeline-item'];
  9503. }
  9504. target = target.parentNode;
  9505. }
  9506. return null;
  9507. };
  9508. /**
  9509. * Find the Group from an event target:
  9510. * searches for the attribute 'timeline-group' in the event target's element tree
  9511. * @param {Event} event
  9512. * @return {Group | null} group
  9513. */
  9514. ItemSet.groupFromTarget = function(event) {
  9515. var target = event.target;
  9516. while (target) {
  9517. if (target.hasOwnProperty('timeline-group')) {
  9518. return target['timeline-group'];
  9519. }
  9520. target = target.parentNode;
  9521. }
  9522. return null;
  9523. };
  9524. /**
  9525. * Find the ItemSet from an event target:
  9526. * searches for the attribute 'timeline-itemset' in the event target's element tree
  9527. * @param {Event} event
  9528. * @return {ItemSet | null} item
  9529. */
  9530. ItemSet.itemSetFromTarget = function(event) {
  9531. var target = event.target;
  9532. while (target) {
  9533. if (target.hasOwnProperty('timeline-itemset')) {
  9534. return target['timeline-itemset'];
  9535. }
  9536. target = target.parentNode;
  9537. }
  9538. return null;
  9539. };
  9540. module.exports = ItemSet;
  9541. /***/ },
  9542. /* 25 */
  9543. /***/ function(module, exports, __webpack_require__) {
  9544. var util = __webpack_require__(1);
  9545. var DOMutil = __webpack_require__(2);
  9546. var Component = __webpack_require__(18);
  9547. /**
  9548. * Legend for Graph2d
  9549. */
  9550. function Legend(body, options, side, linegraphOptions) {
  9551. this.body = body;
  9552. this.defaultOptions = {
  9553. enabled: true,
  9554. icons: true,
  9555. iconSize: 20,
  9556. iconSpacing: 6,
  9557. left: {
  9558. visible: true,
  9559. position: 'top-left' // top/bottom - left,center,right
  9560. },
  9561. right: {
  9562. visible: true,
  9563. position: 'top-left' // top/bottom - left,center,right
  9564. }
  9565. }
  9566. this.side = side;
  9567. this.options = util.extend({},this.defaultOptions);
  9568. this.linegraphOptions = linegraphOptions;
  9569. this.svgElements = {};
  9570. this.dom = {};
  9571. this.groups = {};
  9572. this.amountOfGroups = 0;
  9573. this._create();
  9574. this.setOptions(options);
  9575. }
  9576. Legend.prototype = new Component();
  9577. Legend.prototype.addGroup = function(label, graphOptions) {
  9578. if (!this.groups.hasOwnProperty(label)) {
  9579. this.groups[label] = graphOptions;
  9580. }
  9581. this.amountOfGroups += 1;
  9582. };
  9583. Legend.prototype.updateGroup = function(label, graphOptions) {
  9584. this.groups[label] = graphOptions;
  9585. };
  9586. Legend.prototype.removeGroup = function(label) {
  9587. if (this.groups.hasOwnProperty(label)) {
  9588. delete this.groups[label];
  9589. this.amountOfGroups -= 1;
  9590. }
  9591. };
  9592. Legend.prototype._create = function() {
  9593. this.dom.frame = document.createElement('div');
  9594. this.dom.frame.className = 'legend';
  9595. this.dom.frame.style.position = "absolute";
  9596. this.dom.frame.style.top = "10px";
  9597. this.dom.frame.style.display = "block";
  9598. this.dom.textArea = document.createElement('div');
  9599. this.dom.textArea.className = 'legendText';
  9600. this.dom.textArea.style.position = "relative";
  9601. this.dom.textArea.style.top = "0px";
  9602. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  9603. this.svg.style.position = 'absolute';
  9604. this.svg.style.top = 0 +'px';
  9605. this.svg.style.width = this.options.iconSize + 5 + 'px';
  9606. this.dom.frame.appendChild(this.svg);
  9607. this.dom.frame.appendChild(this.dom.textArea);
  9608. };
  9609. /**
  9610. * Hide the component from the DOM
  9611. */
  9612. Legend.prototype.hide = function() {
  9613. // remove the frame containing the items
  9614. if (this.dom.frame.parentNode) {
  9615. this.dom.frame.parentNode.removeChild(this.dom.frame);
  9616. }
  9617. };
  9618. /**
  9619. * Show the component in the DOM (when not already visible).
  9620. * @return {Boolean} changed
  9621. */
  9622. Legend.prototype.show = function() {
  9623. // show frame containing the items
  9624. if (!this.dom.frame.parentNode) {
  9625. this.body.dom.center.appendChild(this.dom.frame);
  9626. }
  9627. };
  9628. Legend.prototype.setOptions = function(options) {
  9629. var fields = ['enabled','orientation','icons','left','right'];
  9630. util.selectiveDeepExtend(fields, this.options, options);
  9631. };
  9632. Legend.prototype.redraw = function() {
  9633. var activeGroups = 0;
  9634. for (var groupId in this.groups) {
  9635. if (this.groups.hasOwnProperty(groupId)) {
  9636. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  9637. activeGroups++;
  9638. }
  9639. }
  9640. }
  9641. if (this.options[this.side].visible == false || this.amountOfGroups == 0 || this.options.enabled == false || activeGroups == 0) {
  9642. this.hide();
  9643. }
  9644. else {
  9645. this.show();
  9646. if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'bottom-left') {
  9647. this.dom.frame.style.left = '4px';
  9648. this.dom.frame.style.textAlign = "left";
  9649. this.dom.textArea.style.textAlign = "left";
  9650. this.dom.textArea.style.left = (this.options.iconSize + 15) + 'px';
  9651. this.dom.textArea.style.right = '';
  9652. this.svg.style.left = 0 +'px';
  9653. this.svg.style.right = '';
  9654. }
  9655. else {
  9656. this.dom.frame.style.right = '4px';
  9657. this.dom.frame.style.textAlign = "right";
  9658. this.dom.textArea.style.textAlign = "right";
  9659. this.dom.textArea.style.right = (this.options.iconSize + 15) + 'px';
  9660. this.dom.textArea.style.left = '';
  9661. this.svg.style.right = 0 +'px';
  9662. this.svg.style.left = '';
  9663. }
  9664. if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'top-right') {
  9665. this.dom.frame.style.top = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
  9666. this.dom.frame.style.bottom = '';
  9667. }
  9668. else {
  9669. this.dom.frame.style.bottom = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
  9670. this.dom.frame.style.top = '';
  9671. }
  9672. if (this.options.icons == false) {
  9673. this.dom.frame.style.width = this.dom.textArea.offsetWidth + 10 + 'px';
  9674. this.dom.textArea.style.right = '';
  9675. this.dom.textArea.style.left = '';
  9676. this.svg.style.width = '0px';
  9677. }
  9678. else {
  9679. this.dom.frame.style.width = this.options.iconSize + 15 + this.dom.textArea.offsetWidth + 10 + 'px'
  9680. this.drawLegendIcons();
  9681. }
  9682. var content = '';
  9683. for (var groupId in this.groups) {
  9684. if (this.groups.hasOwnProperty(groupId)) {
  9685. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  9686. content += this.groups[groupId].content + '<br />';
  9687. }
  9688. }
  9689. }
  9690. this.dom.textArea.innerHTML = content;
  9691. this.dom.textArea.style.lineHeight = ((0.75 * this.options.iconSize) + this.options.iconSpacing) + 'px';
  9692. }
  9693. };
  9694. Legend.prototype.drawLegendIcons = function() {
  9695. if (this.dom.frame.parentNode) {
  9696. DOMutil.prepareElements(this.svgElements);
  9697. var padding = window.getComputedStyle(this.dom.frame).paddingTop;
  9698. var iconOffset = Number(padding.replace('px',''));
  9699. var x = iconOffset;
  9700. var iconWidth = this.options.iconSize;
  9701. var iconHeight = 0.75 * this.options.iconSize;
  9702. var y = iconOffset + 0.5 * iconHeight + 3;
  9703. this.svg.style.width = iconWidth + 5 + iconOffset + 'px';
  9704. for (var groupId in this.groups) {
  9705. if (this.groups.hasOwnProperty(groupId)) {
  9706. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  9707. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  9708. y += iconHeight + this.options.iconSpacing;
  9709. }
  9710. }
  9711. }
  9712. DOMutil.cleanupElements(this.svgElements);
  9713. }
  9714. };
  9715. module.exports = Legend;
  9716. /***/ },
  9717. /* 26 */
  9718. /***/ function(module, exports, __webpack_require__) {
  9719. var util = __webpack_require__(1);
  9720. var DOMutil = __webpack_require__(2);
  9721. var DataSet = __webpack_require__(3);
  9722. var DataView = __webpack_require__(4);
  9723. var Component = __webpack_require__(18);
  9724. var DataAxis = __webpack_require__(21);
  9725. var GraphGroup = __webpack_require__(22);
  9726. var Legend = __webpack_require__(25);
  9727. var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
  9728. /**
  9729. * This is the constructor of the LineGraph. It requires a Timeline body and options.
  9730. *
  9731. * @param body
  9732. * @param options
  9733. * @constructor
  9734. */
  9735. function LineGraph(body, options) {
  9736. this.id = util.randomUUID();
  9737. this.body = body;
  9738. this.defaultOptions = {
  9739. yAxisOrientation: 'left',
  9740. defaultGroup: 'default',
  9741. sort: true,
  9742. sampling: true,
  9743. graphHeight: '400px',
  9744. shaded: {
  9745. enabled: false,
  9746. orientation: 'bottom' // top, bottom
  9747. },
  9748. style: 'line', // line, bar
  9749. barChart: {
  9750. width: 50,
  9751. handleOverlap: 'overlap',
  9752. align: 'center' // left, center, right
  9753. },
  9754. catmullRom: {
  9755. enabled: true,
  9756. parametrization: 'centripetal', // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5)
  9757. alpha: 0.5
  9758. },
  9759. drawPoints: {
  9760. enabled: true,
  9761. size: 6,
  9762. style: 'square' // square, circle
  9763. },
  9764. dataAxis: {
  9765. showMinorLabels: true,
  9766. showMajorLabels: true,
  9767. icons: false,
  9768. width: '40px',
  9769. visible: true,
  9770. customRange: {
  9771. left: {min:undefined, max:undefined},
  9772. right: {min:undefined, max:undefined}
  9773. }
  9774. },
  9775. legend: {
  9776. enabled: false,
  9777. icons: true,
  9778. left: {
  9779. visible: true,
  9780. position: 'top-left' // top/bottom - left,right
  9781. },
  9782. right: {
  9783. visible: true,
  9784. position: 'top-right' // top/bottom - left,right
  9785. }
  9786. },
  9787. groups: {
  9788. visibility: {}
  9789. }
  9790. };
  9791. // options is shared by this ItemSet and all its items
  9792. this.options = util.extend({}, this.defaultOptions);
  9793. this.dom = {};
  9794. this.props = {};
  9795. this.hammer = null;
  9796. this.groups = {};
  9797. this.abortedGraphUpdate = false;
  9798. var me = this;
  9799. this.itemsData = null; // DataSet
  9800. this.groupsData = null; // DataSet
  9801. // listeners for the DataSet of the items
  9802. this.itemListeners = {
  9803. 'add': function (event, params, senderId) {
  9804. me._onAdd(params.items);
  9805. },
  9806. 'update': function (event, params, senderId) {
  9807. me._onUpdate(params.items);
  9808. },
  9809. 'remove': function (event, params, senderId) {
  9810. me._onRemove(params.items);
  9811. }
  9812. };
  9813. // listeners for the DataSet of the groups
  9814. this.groupListeners = {
  9815. 'add': function (event, params, senderId) {
  9816. me._onAddGroups(params.items);
  9817. },
  9818. 'update': function (event, params, senderId) {
  9819. me._onUpdateGroups(params.items);
  9820. },
  9821. 'remove': function (event, params, senderId) {
  9822. me._onRemoveGroups(params.items);
  9823. }
  9824. };
  9825. this.items = {}; // object with an Item for every data item
  9826. this.selection = []; // list with the ids of all selected nodes
  9827. this.lastStart = this.body.range.start;
  9828. this.touchParams = {}; // stores properties while dragging
  9829. this.svgElements = {};
  9830. this.setOptions(options);
  9831. this.groupsUsingDefaultStyles = [0];
  9832. this.body.emitter.on("rangechanged", function() {
  9833. me.lastStart = me.body.range.start;
  9834. me.svg.style.left = util.option.asSize(-me.width);
  9835. me._updateGraph.apply(me);
  9836. });
  9837. // create the HTML DOM
  9838. this._create();
  9839. this.body.emitter.emit("change");
  9840. }
  9841. LineGraph.prototype = new Component();
  9842. /**
  9843. * Create the HTML DOM for the ItemSet
  9844. */
  9845. LineGraph.prototype._create = function(){
  9846. var frame = document.createElement('div');
  9847. frame.className = 'LineGraph';
  9848. this.dom.frame = frame;
  9849. // create svg element for graph drawing.
  9850. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  9851. this.svg.style.position = "relative";
  9852. this.svg.style.height = ('' + this.options.graphHeight).replace("px",'') + 'px';
  9853. this.svg.style.display = "block";
  9854. frame.appendChild(this.svg);
  9855. // data axis
  9856. this.options.dataAxis.orientation = 'left';
  9857. this.yAxisLeft = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
  9858. this.options.dataAxis.orientation = 'right';
  9859. this.yAxisRight = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
  9860. delete this.options.dataAxis.orientation;
  9861. // legends
  9862. this.legendLeft = new Legend(this.body, this.options.legend, 'left', this.options.groups);
  9863. this.legendRight = new Legend(this.body, this.options.legend, 'right', this.options.groups);
  9864. this.show();
  9865. };
  9866. /**
  9867. * set the options of the LineGraph. the mergeOptions is used for subObjects that have an enabled element.
  9868. * @param options
  9869. */
  9870. LineGraph.prototype.setOptions = function(options) {
  9871. if (options) {
  9872. var fields = ['sampling','defaultGroup','graphHeight','yAxisOrientation','style','barChart','dataAxis','sort','groups'];
  9873. util.selectiveDeepExtend(fields, this.options, options);
  9874. util.mergeOptions(this.options, options,'catmullRom');
  9875. util.mergeOptions(this.options, options,'drawPoints');
  9876. util.mergeOptions(this.options, options,'shaded');
  9877. util.mergeOptions(this.options, options,'legend');
  9878. if (options.catmullRom) {
  9879. if (typeof options.catmullRom == 'object') {
  9880. if (options.catmullRom.parametrization) {
  9881. if (options.catmullRom.parametrization == 'uniform') {
  9882. this.options.catmullRom.alpha = 0;
  9883. }
  9884. else if (options.catmullRom.parametrization == 'chordal') {
  9885. this.options.catmullRom.alpha = 1.0;
  9886. }
  9887. else {
  9888. this.options.catmullRom.parametrization = 'centripetal';
  9889. this.options.catmullRom.alpha = 0.5;
  9890. }
  9891. }
  9892. }
  9893. }
  9894. if (this.yAxisLeft) {
  9895. if (options.dataAxis !== undefined) {
  9896. this.yAxisLeft.setOptions(this.options.dataAxis);
  9897. this.yAxisRight.setOptions(this.options.dataAxis);
  9898. }
  9899. }
  9900. if (this.legendLeft) {
  9901. if (options.legend !== undefined) {
  9902. this.legendLeft.setOptions(this.options.legend);
  9903. this.legendRight.setOptions(this.options.legend);
  9904. }
  9905. }
  9906. if (this.groups.hasOwnProperty(UNGROUPED)) {
  9907. this.groups[UNGROUPED].setOptions(options);
  9908. }
  9909. }
  9910. if (this.dom.frame) {
  9911. this._updateGraph();
  9912. }
  9913. };
  9914. /**
  9915. * Hide the component from the DOM
  9916. */
  9917. LineGraph.prototype.hide = function() {
  9918. // remove the frame containing the items
  9919. if (this.dom.frame.parentNode) {
  9920. this.dom.frame.parentNode.removeChild(this.dom.frame);
  9921. }
  9922. };
  9923. /**
  9924. * Show the component in the DOM (when not already visible).
  9925. * @return {Boolean} changed
  9926. */
  9927. LineGraph.prototype.show = function() {
  9928. // show frame containing the items
  9929. if (!this.dom.frame.parentNode) {
  9930. this.body.dom.center.appendChild(this.dom.frame);
  9931. }
  9932. };
  9933. /**
  9934. * Set items
  9935. * @param {vis.DataSet | null} items
  9936. */
  9937. LineGraph.prototype.setItems = function(items) {
  9938. var me = this,
  9939. ids,
  9940. oldItemsData = this.itemsData;
  9941. // replace the dataset
  9942. if (!items) {
  9943. this.itemsData = null;
  9944. }
  9945. else if (items instanceof DataSet || items instanceof DataView) {
  9946. this.itemsData = items;
  9947. }
  9948. else {
  9949. throw new TypeError('Data must be an instance of DataSet or DataView');
  9950. }
  9951. if (oldItemsData) {
  9952. // unsubscribe from old dataset
  9953. util.forEach(this.itemListeners, function (callback, event) {
  9954. oldItemsData.off(event, callback);
  9955. });
  9956. // remove all drawn items
  9957. ids = oldItemsData.getIds();
  9958. this._onRemove(ids);
  9959. }
  9960. if (this.itemsData) {
  9961. // subscribe to new dataset
  9962. var id = this.id;
  9963. util.forEach(this.itemListeners, function (callback, event) {
  9964. me.itemsData.on(event, callback, id);
  9965. });
  9966. // add all new items
  9967. ids = this.itemsData.getIds();
  9968. this._onAdd(ids);
  9969. }
  9970. this._updateUngrouped();
  9971. this._updateGraph();
  9972. this.redraw();
  9973. };
  9974. /**
  9975. * Set groups
  9976. * @param {vis.DataSet} groups
  9977. */
  9978. LineGraph.prototype.setGroups = function(groups) {
  9979. var me = this,
  9980. ids;
  9981. // unsubscribe from current dataset
  9982. if (this.groupsData) {
  9983. util.forEach(this.groupListeners, function (callback, event) {
  9984. me.groupsData.unsubscribe(event, callback);
  9985. });
  9986. // remove all drawn groups
  9987. ids = this.groupsData.getIds();
  9988. this.groupsData = null;
  9989. this._onRemoveGroups(ids); // note: this will cause a redraw
  9990. }
  9991. // replace the dataset
  9992. if (!groups) {
  9993. this.groupsData = null;
  9994. }
  9995. else if (groups instanceof DataSet || groups instanceof DataView) {
  9996. this.groupsData = groups;
  9997. }
  9998. else {
  9999. throw new TypeError('Data must be an instance of DataSet or DataView');
  10000. }
  10001. if (this.groupsData) {
  10002. // subscribe to new dataset
  10003. var id = this.id;
  10004. util.forEach(this.groupListeners, function (callback, event) {
  10005. me.groupsData.on(event, callback, id);
  10006. });
  10007. // draw all ms
  10008. ids = this.groupsData.getIds();
  10009. this._onAddGroups(ids);
  10010. }
  10011. this._onUpdate();
  10012. };
  10013. /**
  10014. * Update the datapoints
  10015. * @param [ids]
  10016. * @private
  10017. */
  10018. LineGraph.prototype._onUpdate = function(ids) {
  10019. this._updateUngrouped();
  10020. this._updateAllGroupData();
  10021. this._updateGraph();
  10022. this.redraw();
  10023. };
  10024. LineGraph.prototype._onAdd = function (ids) {this._onUpdate(ids);};
  10025. LineGraph.prototype._onRemove = function (ids) {this._onUpdate(ids);};
  10026. LineGraph.prototype._onUpdateGroups = function (groupIds) {
  10027. for (var i = 0; i < groupIds.length; i++) {
  10028. var group = this.groupsData.get(groupIds[i]);
  10029. this._updateGroup(group, groupIds[i]);
  10030. }
  10031. this._updateGraph();
  10032. this.redraw();
  10033. };
  10034. LineGraph.prototype._onAddGroups = function (groupIds) {this._onUpdateGroups(groupIds);};
  10035. LineGraph.prototype._onRemoveGroups = function (groupIds) {
  10036. for (var i = 0; i < groupIds.length; i++) {
  10037. if (!this.groups.hasOwnProperty(groupIds[i])) {
  10038. if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') {
  10039. this.yAxisRight.removeGroup(groupIds[i]);
  10040. this.legendRight.removeGroup(groupIds[i]);
  10041. this.legendRight.redraw();
  10042. }
  10043. else {
  10044. this.yAxisLeft.removeGroup(groupIds[i]);
  10045. this.legendLeft.removeGroup(groupIds[i]);
  10046. this.legendLeft.redraw();
  10047. }
  10048. delete this.groups[groupIds[i]];
  10049. }
  10050. }
  10051. this._updateUngrouped();
  10052. this._updateGraph();
  10053. this.redraw();
  10054. };
  10055. /**
  10056. * update a group object
  10057. *
  10058. * @param group
  10059. * @param groupId
  10060. * @private
  10061. */
  10062. LineGraph.prototype._updateGroup = function (group, groupId) {
  10063. if (!this.groups.hasOwnProperty(groupId)) {
  10064. this.groups[groupId] = new GraphGroup(group, groupId, this.options, this.groupsUsingDefaultStyles);
  10065. if (this.groups[groupId].options.yAxisOrientation == 'right') {
  10066. this.yAxisRight.addGroup(groupId, this.groups[groupId]);
  10067. this.legendRight.addGroup(groupId, this.groups[groupId]);
  10068. }
  10069. else {
  10070. this.yAxisLeft.addGroup(groupId, this.groups[groupId]);
  10071. this.legendLeft.addGroup(groupId, this.groups[groupId]);
  10072. }
  10073. }
  10074. else {
  10075. this.groups[groupId].update(group);
  10076. if (this.groups[groupId].options.yAxisOrientation == 'right') {
  10077. this.yAxisRight.updateGroup(groupId, this.groups[groupId]);
  10078. this.legendRight.updateGroup(groupId, this.groups[groupId]);
  10079. }
  10080. else {
  10081. this.yAxisLeft.updateGroup(groupId, this.groups[groupId]);
  10082. this.legendLeft.updateGroup(groupId, this.groups[groupId]);
  10083. }
  10084. }
  10085. this.legendLeft.redraw();
  10086. this.legendRight.redraw();
  10087. };
  10088. LineGraph.prototype._updateAllGroupData = function () {
  10089. if (this.itemsData != null) {
  10090. var groupsContent = {};
  10091. var groupId;
  10092. for (groupId in this.groups) {
  10093. if (this.groups.hasOwnProperty(groupId)) {
  10094. groupsContent[groupId] = [];
  10095. }
  10096. }
  10097. for (var itemId in this.itemsData._data) {
  10098. if (this.itemsData._data.hasOwnProperty(itemId)) {
  10099. var item = this.itemsData._data[itemId];
  10100. item.x = util.convert(item.x,"Date");
  10101. groupsContent[item.group].push(item);
  10102. }
  10103. }
  10104. for (groupId in this.groups) {
  10105. if (this.groups.hasOwnProperty(groupId)) {
  10106. this.groups[groupId].setItems(groupsContent[groupId]);
  10107. }
  10108. }
  10109. }
  10110. };
  10111. /**
  10112. * Create or delete the group holding all ungrouped items. This group is used when
  10113. * there are no groups specified. This anonymous group is called 'graph'.
  10114. * @protected
  10115. */
  10116. LineGraph.prototype._updateUngrouped = function() {
  10117. if (this.itemsData != null) {
  10118. // var t0 = new Date();
  10119. var group = {id: UNGROUPED, content: this.options.defaultGroup};
  10120. this._updateGroup(group, UNGROUPED);
  10121. var ungroupedCounter = 0;
  10122. if (this.itemsData) {
  10123. for (var itemId in this.itemsData._data) {
  10124. if (this.itemsData._data.hasOwnProperty(itemId)) {
  10125. var item = this.itemsData._data[itemId];
  10126. if (item != undefined) {
  10127. if (item.hasOwnProperty('group')) {
  10128. if (item.group === undefined) {
  10129. item.group = UNGROUPED;
  10130. }
  10131. }
  10132. else {
  10133. item.group = UNGROUPED;
  10134. }
  10135. ungroupedCounter = item.group == UNGROUPED ? ungroupedCounter + 1 : ungroupedCounter;
  10136. }
  10137. }
  10138. }
  10139. }
  10140. if (ungroupedCounter == 0) {
  10141. delete this.groups[UNGROUPED];
  10142. this.legendLeft.removeGroup(UNGROUPED);
  10143. this.legendRight.removeGroup(UNGROUPED);
  10144. this.yAxisLeft.removeGroup(UNGROUPED);
  10145. this.yAxisRight.removeGroup(UNGROUPED);
  10146. }
  10147. }
  10148. else {
  10149. delete this.groups[UNGROUPED];
  10150. this.legendLeft.removeGroup(UNGROUPED);
  10151. this.legendRight.removeGroup(UNGROUPED);
  10152. this.yAxisLeft.removeGroup(UNGROUPED);
  10153. this.yAxisRight.removeGroup(UNGROUPED);
  10154. }
  10155. this.legendLeft.redraw();
  10156. this.legendRight.redraw();
  10157. };
  10158. /**
  10159. * Redraw the component, mandatory function
  10160. * @return {boolean} Returns true if the component is resized
  10161. */
  10162. LineGraph.prototype.redraw = function() {
  10163. var resized = false;
  10164. this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px';
  10165. if (this.lastWidth === undefined && this.width || this.lastWidth != this.width) {
  10166. resized = true;
  10167. }
  10168. // check if this component is resized
  10169. resized = this._isResized() || resized;
  10170. // check whether zoomed (in that case we need to re-stack everything)
  10171. var visibleInterval = this.body.range.end - this.body.range.start;
  10172. var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.width != this.lastWidth);
  10173. this.lastVisibleInterval = visibleInterval;
  10174. this.lastWidth = this.width;
  10175. // calculate actual size and position
  10176. this.width = this.dom.frame.offsetWidth;
  10177. // the svg element is three times as big as the width, this allows for fully dragging left and right
  10178. // without reloading the graph. the controls for this are bound to events in the constructor
  10179. if (resized == true) {
  10180. this.svg.style.width = util.option.asSize(3*this.width);
  10181. this.svg.style.left = util.option.asSize(-this.width);
  10182. }
  10183. if (zoomed == true || this.abortedGraphUpdate == true) {
  10184. this._updateGraph();
  10185. }
  10186. else {
  10187. // move the whole svg while dragging
  10188. if (this.lastStart != 0) {
  10189. var offset = this.body.range.start - this.lastStart;
  10190. var range = this.body.range.end - this.body.range.start;
  10191. if (this.width != 0) {
  10192. var rangePerPixelInv = this.width/range;
  10193. var xOffset = offset * rangePerPixelInv;
  10194. this.svg.style.left = (-this.width - xOffset) + "px";
  10195. }
  10196. }
  10197. }
  10198. this.legendLeft.redraw();
  10199. this.legendRight.redraw();
  10200. return resized;
  10201. };
  10202. /**
  10203. * Update and redraw the graph.
  10204. *
  10205. */
  10206. LineGraph.prototype._updateGraph = function () {
  10207. // reset the svg elements
  10208. DOMutil.prepareElements(this.svgElements);
  10209. if (this.width != 0 && this.itemsData != null) {
  10210. var group, i;
  10211. var preprocessedGroupData = {};
  10212. var processedGroupData = {};
  10213. var groupRanges = {};
  10214. var changeCalled = false;
  10215. // getting group Ids
  10216. var groupIds = [];
  10217. for (var groupId in this.groups) {
  10218. if (this.groups.hasOwnProperty(groupId)) {
  10219. group = this.groups[groupId];
  10220. if (group.visible == true && (this.options.groups.visibility[groupId] === undefined || this.options.groups.visibility[groupId] == true)) {
  10221. groupIds.push(groupId);
  10222. }
  10223. }
  10224. }
  10225. if (groupIds.length > 0) {
  10226. // this is the range of the SVG canvas
  10227. var minDate = this.body.util.toGlobalTime(- this.body.domProps.root.width);
  10228. var maxDate = this.body.util.toGlobalTime(2 * this.body.domProps.root.width);
  10229. var groupsData = {};
  10230. // fill groups data
  10231. this._getRelevantData(groupIds, groupsData, minDate, maxDate);
  10232. // we transform the X coordinates to detect collisions
  10233. for (i = 0; i < groupIds.length; i++) {
  10234. preprocessedGroupData[groupIds[i]] = this._convertXcoordinates(groupsData[groupIds[i]]);
  10235. }
  10236. // now all needed data has been collected we start the processing.
  10237. this._getYRanges(groupIds, preprocessedGroupData, groupRanges);
  10238. // update the Y axis first, we use this data to draw at the correct Y points
  10239. // changeCalled is required to clean the SVG on a change emit.
  10240. changeCalled = this._updateYAxis(groupIds, groupRanges);
  10241. if (changeCalled == true) {
  10242. DOMutil.cleanupElements(this.svgElements);
  10243. this.abortedGraphUpdate = true;
  10244. this.body.emitter.emit("change");
  10245. return;
  10246. }
  10247. this.abortedGraphUpdate = false;
  10248. // With the yAxis scaled correctly, use this to get the Y values of the points.
  10249. for (i = 0; i < groupIds.length; i++) {
  10250. group = this.groups[groupIds[i]];
  10251. processedGroupData[groupIds[i]] = this._convertYcoordinates(groupsData[groupIds[i]], group);
  10252. }
  10253. // draw the groups
  10254. for (i = 0; i < groupIds.length; i++) {
  10255. group = this.groups[groupIds[i]];
  10256. if (group.options.style == 'line') {
  10257. this._drawLineGraph(processedGroupData[groupIds[i]], group);
  10258. }
  10259. }
  10260. this._drawBarGraphs(groupIds, processedGroupData);
  10261. }
  10262. }
  10263. // cleanup unused svg elements
  10264. DOMutil.cleanupElements(this.svgElements);
  10265. };
  10266. LineGraph.prototype._getRelevantData = function (groupIds, groupsData, minDate, maxDate) {
  10267. // first select and preprocess the data from the datasets.
  10268. // the groups have their preselection of data, we now loop over this data to see
  10269. // what data we need to draw. Sorted data is much faster.
  10270. // more optimization is possible by doing the sampling before and using the binary search
  10271. // to find the end date to determine the increment.
  10272. var group, i, j, item;
  10273. if (groupIds.length > 0) {
  10274. for (i = 0; i < groupIds.length; i++) {
  10275. group = this.groups[groupIds[i]];
  10276. groupsData[groupIds[i]] = [];
  10277. var dataContainer = groupsData[groupIds[i]];
  10278. // optimization for sorted data
  10279. if (group.options.sort == true) {
  10280. var guess = Math.max(0, util.binarySearchGeneric(group.itemsData, minDate, 'x', 'before'));
  10281. for (j = guess; j < group.itemsData.length; j++) {
  10282. item = group.itemsData[j];
  10283. if (item !== undefined) {
  10284. if (item.x > maxDate) {
  10285. dataContainer.push(item);
  10286. break;
  10287. }
  10288. else {
  10289. dataContainer.push(item);
  10290. }
  10291. }
  10292. }
  10293. }
  10294. else {
  10295. for (j = 0; j < group.itemsData.length; j++) {
  10296. item = group.itemsData[j];
  10297. if (item !== undefined) {
  10298. if (item.x > minDate && item.x < maxDate) {
  10299. dataContainer.push(item);
  10300. }
  10301. }
  10302. }
  10303. }
  10304. }
  10305. }
  10306. this._applySampling(groupIds, groupsData);
  10307. };
  10308. LineGraph.prototype._applySampling = function (groupIds, groupsData) {
  10309. var group;
  10310. if (groupIds.length > 0) {
  10311. for (var i = 0; i < groupIds.length; i++) {
  10312. group = this.groups[groupIds[i]];
  10313. if (group.options.sampling == true) {
  10314. var dataContainer = groupsData[groupIds[i]];
  10315. if (dataContainer.length > 0) {
  10316. var increment = 1;
  10317. var amountOfPoints = dataContainer.length;
  10318. // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop
  10319. // of width changing of the yAxis.
  10320. var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x);
  10321. var pointsPerPixel = amountOfPoints / xDistance;
  10322. increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel)));
  10323. var sampledData = [];
  10324. for (var j = 0; j < amountOfPoints; j += increment) {
  10325. sampledData.push(dataContainer[j]);
  10326. }
  10327. groupsData[groupIds[i]] = sampledData;
  10328. }
  10329. }
  10330. }
  10331. }
  10332. };
  10333. LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) {
  10334. var groupData, group, i,j;
  10335. var barCombinedDataLeft = [];
  10336. var barCombinedDataRight = [];
  10337. var barCombinedData;
  10338. if (groupIds.length > 0) {
  10339. for (i = 0; i < groupIds.length; i++) {
  10340. groupData = groupsData[groupIds[i]];
  10341. if (groupData.length > 0) {
  10342. group = this.groups[groupIds[i]];
  10343. if (group.options.style == 'line' || group.options.barChart.handleOverlap != "stack") {
  10344. var yMin = groupData[0].y;
  10345. var yMax = groupData[0].y;
  10346. for (j = 0; j < groupData.length; j++) {
  10347. yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
  10348. yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
  10349. }
  10350. groupRanges[groupIds[i]] = {min: yMin, max: yMax, yAxisOrientation: group.options.yAxisOrientation};
  10351. }
  10352. else if (group.options.style == 'bar') {
  10353. if (group.options.yAxisOrientation == 'left') {
  10354. barCombinedData = barCombinedDataLeft;
  10355. }
  10356. else {
  10357. barCombinedData = barCombinedDataRight;
  10358. }
  10359. groupRanges[groupIds[i]] = {min: 0, max: 0, yAxisOrientation: group.options.yAxisOrientation, ignore: true};
  10360. // combine data
  10361. for (j = 0; j < groupData.length; j++) {
  10362. barCombinedData.push({
  10363. x: groupData[j].x,
  10364. y: groupData[j].y,
  10365. groupId: groupIds[i]
  10366. });
  10367. }
  10368. }
  10369. }
  10370. }
  10371. var intersections;
  10372. if (barCombinedDataLeft.length > 0) {
  10373. // sort by time and by group
  10374. barCombinedDataLeft.sort(function (a, b) {
  10375. if (a.x == b.x) {
  10376. return a.groupId - b.groupId;
  10377. } else {
  10378. return a.x - b.x;
  10379. }
  10380. });
  10381. intersections = {};
  10382. this._getDataIntersections(intersections, barCombinedDataLeft);
  10383. groupRanges["__barchartLeft"] = this._getStackedBarYRange(intersections, barCombinedDataLeft);
  10384. groupRanges["__barchartLeft"].yAxisOrientation = "left";
  10385. groupIds.push("__barchartLeft");
  10386. }
  10387. if (barCombinedDataRight.length > 0) {
  10388. // sort by time and by group
  10389. barCombinedDataRight.sort(function (a, b) {
  10390. if (a.x == b.x) {
  10391. return a.groupId - b.groupId;
  10392. } else {
  10393. return a.x - b.x;
  10394. }
  10395. });
  10396. intersections = {};
  10397. this._getDataIntersections(intersections, barCombinedDataRight);
  10398. groupRanges["__barchartRight"] = this._getStackedBarYRange(intersections, barCombinedDataRight);
  10399. groupRanges["__barchartRight"].yAxisOrientation = "right";
  10400. groupIds.push("__barchartRight");
  10401. }
  10402. }
  10403. };
  10404. LineGraph.prototype._getStackedBarYRange = function (intersections, combinedData) {
  10405. var key;
  10406. var yMin = combinedData[0].y;
  10407. var yMax = combinedData[0].y;
  10408. for (var i = 0; i < combinedData.length; i++) {
  10409. key = combinedData[i].x;
  10410. if (intersections[key] === undefined) {
  10411. yMin = yMin > combinedData[i].y ? combinedData[i].y : yMin;
  10412. yMax = yMax < combinedData[i].y ? combinedData[i].y : yMax;
  10413. }
  10414. else {
  10415. intersections[key].accumulated += combinedData[i].y;
  10416. }
  10417. }
  10418. for (var xpos in intersections) {
  10419. if (intersections.hasOwnProperty(xpos)) {
  10420. yMin = yMin > intersections[xpos].accumulated ? intersections[xpos].accumulated : yMin;
  10421. yMax = yMax < intersections[xpos].accumulated ? intersections[xpos].accumulated : yMax;
  10422. }
  10423. }
  10424. return {min: yMin, max: yMax};
  10425. };
  10426. /**
  10427. * this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden.
  10428. * @param {Array} groupIds
  10429. * @param {Object} groupRanges
  10430. * @private
  10431. */
  10432. LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
  10433. var changeCalled = false;
  10434. var yAxisLeftUsed = false;
  10435. var yAxisRightUsed = false;
  10436. var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal;
  10437. // if groups are present
  10438. if (groupIds.length > 0) {
  10439. for (var i = 0; i < groupIds.length; i++) {
  10440. if (groupRanges.hasOwnProperty(groupIds[i])) {
  10441. if (groupRanges[groupIds[i]].ignore !== true) {
  10442. minVal = groupRanges[groupIds[i]].min;
  10443. maxVal = groupRanges[groupIds[i]].max;
  10444. if (groupRanges[groupIds[i]].yAxisOrientation == 'left') {
  10445. yAxisLeftUsed = true;
  10446. minLeft = minLeft > minVal ? minVal : minLeft;
  10447. maxLeft = maxLeft < maxVal ? maxVal : maxLeft;
  10448. }
  10449. else {
  10450. yAxisRightUsed = true;
  10451. minRight = minRight > minVal ? minVal : minRight;
  10452. maxRight = maxRight < maxVal ? maxVal : maxRight;
  10453. }
  10454. }
  10455. }
  10456. }
  10457. if (yAxisLeftUsed == true) {
  10458. this.yAxisLeft.setRange(minLeft, maxLeft);
  10459. }
  10460. if (yAxisRightUsed == true) {
  10461. this.yAxisRight.setRange(minRight, maxRight);
  10462. }
  10463. }
  10464. changeCalled = this._toggleAxisVisiblity(yAxisLeftUsed , this.yAxisLeft) || changeCalled;
  10465. changeCalled = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || changeCalled;
  10466. if (yAxisRightUsed == true && yAxisLeftUsed == true) {
  10467. this.yAxisLeft.drawIcons = true;
  10468. this.yAxisRight.drawIcons = true;
  10469. }
  10470. else {
  10471. this.yAxisLeft.drawIcons = false;
  10472. this.yAxisRight.drawIcons = false;
  10473. }
  10474. this.yAxisRight.master = !yAxisLeftUsed;
  10475. if (this.yAxisRight.master == false) {
  10476. if (yAxisRightUsed == true) {this.yAxisLeft.lineOffset = this.yAxisRight.width;}
  10477. else {this.yAxisLeft.lineOffset = 0;}
  10478. changeCalled = this.yAxisLeft.redraw() || changeCalled;
  10479. this.yAxisRight.stepPixelsForced = this.yAxisLeft.stepPixels;
  10480. changeCalled = this.yAxisRight.redraw() || changeCalled;
  10481. }
  10482. else {
  10483. changeCalled = this.yAxisRight.redraw() || changeCalled;
  10484. }
  10485. // clean the accumulated lists
  10486. if (groupIds.indexOf("__barchartLeft") != -1) {
  10487. groupIds.splice(groupIds.indexOf("__barchartLeft"),1);
  10488. }
  10489. if (groupIds.indexOf("__barchartRight") != -1) {
  10490. groupIds.splice(groupIds.indexOf("__barchartRight"),1);
  10491. }
  10492. return changeCalled;
  10493. };
  10494. /**
  10495. * This shows or hides the Y axis if needed. If there is a change, the changed event is emitted by the updateYAxis function
  10496. *
  10497. * @param {boolean} axisUsed
  10498. * @returns {boolean}
  10499. * @private
  10500. * @param axis
  10501. */
  10502. LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) {
  10503. var changed = false;
  10504. if (axisUsed == false) {
  10505. if (axis.dom.frame.parentNode) {
  10506. axis.hide();
  10507. changed = true;
  10508. }
  10509. }
  10510. else {
  10511. if (!axis.dom.frame.parentNode) {
  10512. axis.show();
  10513. changed = true;
  10514. }
  10515. }
  10516. return changed;
  10517. };
  10518. /**
  10519. * draw a bar graph
  10520. *
  10521. * @param groupIds
  10522. * @param processedGroupData
  10523. */
  10524. LineGraph.prototype._drawBarGraphs = function (groupIds, processedGroupData) {
  10525. var combinedData = [];
  10526. var intersections = {};
  10527. var coreDistance;
  10528. var key, drawData;
  10529. var group;
  10530. var i,j;
  10531. var barPoints = 0;
  10532. // combine all barchart data
  10533. for (i = 0; i < groupIds.length; i++) {
  10534. group = this.groups[groupIds[i]];
  10535. if (group.options.style == 'bar') {
  10536. if (group.visible == true && (this.options.groups.visibility[groupIds[i]] === undefined || this.options.groups.visibility[groupIds[i]] == true)) {
  10537. for (j = 0; j < processedGroupData[groupIds[i]].length; j++) {
  10538. combinedData.push({
  10539. x: processedGroupData[groupIds[i]][j].x,
  10540. y: processedGroupData[groupIds[i]][j].y,
  10541. groupId: groupIds[i]
  10542. });
  10543. barPoints += 1;
  10544. }
  10545. }
  10546. }
  10547. }
  10548. if (barPoints == 0) {return;}
  10549. // sort by time and by group
  10550. combinedData.sort(function (a, b) {
  10551. if (a.x == b.x) {
  10552. return a.groupId - b.groupId;
  10553. } else {
  10554. return a.x - b.x;
  10555. }
  10556. });
  10557. // get intersections
  10558. this._getDataIntersections(intersections, combinedData);
  10559. // plot barchart
  10560. for (i = 0; i < combinedData.length; i++) {
  10561. group = this.groups[combinedData[i].groupId];
  10562. var minWidth = 0.1 * group.options.barChart.width;
  10563. key = combinedData[i].x;
  10564. var heightOffset = 0;
  10565. if (intersections[key] === undefined) {
  10566. if (i+1 < combinedData.length) {coreDistance = Math.abs(combinedData[i+1].x - key);}
  10567. if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[i-1].x - key));}
  10568. drawData = this._getSafeDrawData(coreDistance, group, minWidth);
  10569. }
  10570. else {
  10571. var nextKey = i + (intersections[key].amount - intersections[key].resolved);
  10572. var prevKey = i - (intersections[key].resolved + 1);
  10573. if (nextKey < combinedData.length) {coreDistance = Math.abs(combinedData[nextKey].x - key);}
  10574. if (prevKey > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[prevKey].x - key));}
  10575. drawData = this._getSafeDrawData(coreDistance, group, minWidth);
  10576. intersections[key].resolved += 1;
  10577. if (group.options.barChart.handleOverlap == 'stack') {
  10578. heightOffset = intersections[key].accumulated;
  10579. intersections[key].accumulated += group.zeroPosition - combinedData[i].y;
  10580. }
  10581. else if (group.options.barChart.handleOverlap == 'sideBySide') {
  10582. drawData.width = drawData.width / intersections[key].amount;
  10583. drawData.offset += (intersections[key].resolved) * drawData.width - (0.5*drawData.width * (intersections[key].amount+1));
  10584. if (group.options.barChart.align == 'left') {drawData.offset -= 0.5*drawData.width;}
  10585. else if (group.options.barChart.align == 'right') {drawData.offset += 0.5*drawData.width;}
  10586. }
  10587. }
  10588. DOMutil.drawBar(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, drawData.width, group.zeroPosition - combinedData[i].y, group.className + ' bar', this.svgElements, this.svg);
  10589. // draw points
  10590. if (group.options.drawPoints.enabled == true) {
  10591. DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, group, this.svgElements, this.svg);
  10592. }
  10593. }
  10594. };
  10595. /**
  10596. * Fill the intersections object with counters of how many datapoints share the same x coordinates
  10597. * @param intersections
  10598. * @param combinedData
  10599. * @private
  10600. */
  10601. LineGraph.prototype._getDataIntersections = function (intersections, combinedData) {
  10602. // get intersections
  10603. var coreDistance;
  10604. for (var i = 0; i < combinedData.length; i++) {
  10605. if (i + 1 < combinedData.length) {
  10606. coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x);
  10607. }
  10608. if (i > 0) {
  10609. coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x));
  10610. }
  10611. if (coreDistance == 0) {
  10612. if (intersections[combinedData[i].x] === undefined) {
  10613. intersections[combinedData[i].x] = {amount: 0, resolved: 0, accumulated: 0};
  10614. }
  10615. intersections[combinedData[i].x].amount += 1;
  10616. }
  10617. }
  10618. };
  10619. /**
  10620. * Get the width and offset for bargraphs based on the coredistance between datapoints
  10621. *
  10622. * @param coreDistance
  10623. * @param group
  10624. * @param minWidth
  10625. * @returns {{width: Number, offset: Number}}
  10626. * @private
  10627. */
  10628. LineGraph.prototype._getSafeDrawData = function (coreDistance, group, minWidth) {
  10629. var width, offset;
  10630. if (coreDistance < group.options.barChart.width && coreDistance > 0) {
  10631. width = coreDistance < minWidth ? minWidth : coreDistance;
  10632. offset = 0; // recalculate offset with the new width;
  10633. if (group.options.barChart.align == 'left') {
  10634. offset -= 0.5 * coreDistance;
  10635. }
  10636. else if (group.options.barChart.align == 'right') {
  10637. offset += 0.5 * coreDistance;
  10638. }
  10639. }
  10640. else {
  10641. // default settings
  10642. width = group.options.barChart.width;
  10643. offset = 0;
  10644. if (group.options.barChart.align == 'left') {
  10645. offset -= 0.5 * group.options.barChart.width;
  10646. }
  10647. else if (group.options.barChart.align == 'right') {
  10648. offset += 0.5 * group.options.barChart.width;
  10649. }
  10650. }
  10651. return {width: width, offset: offset};
  10652. };
  10653. /**
  10654. * draw a line graph
  10655. *
  10656. * @param dataset
  10657. * @param group
  10658. */
  10659. LineGraph.prototype._drawLineGraph = function (dataset, group) {
  10660. if (dataset != null) {
  10661. if (dataset.length > 0) {
  10662. var path, d;
  10663. var svgHeight = Number(this.svg.style.height.replace("px",""));
  10664. path = DOMutil.getSVGElement('path', this.svgElements, this.svg);
  10665. path.setAttributeNS(null, "class", group.className);
  10666. // construct path from dataset
  10667. if (group.options.catmullRom.enabled == true) {
  10668. d = this._catmullRom(dataset, group);
  10669. }
  10670. else {
  10671. d = this._linear(dataset);
  10672. }
  10673. // append with points for fill and finalize the path
  10674. if (group.options.shaded.enabled == true) {
  10675. var fillPath = DOMutil.getSVGElement('path',this.svgElements, this.svg);
  10676. var dFill;
  10677. if (group.options.shaded.orientation == 'top') {
  10678. dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0;
  10679. }
  10680. else {
  10681. dFill = "M" + dataset[0].x + "," + svgHeight + " " + d + "L" + dataset[dataset.length - 1].x + "," + svgHeight;
  10682. }
  10683. fillPath.setAttributeNS(null, "class", group.className + " fill");
  10684. fillPath.setAttributeNS(null, "d", dFill);
  10685. }
  10686. // copy properties to path for drawing.
  10687. path.setAttributeNS(null, "d", "M" + d);
  10688. // draw points
  10689. if (group.options.drawPoints.enabled == true) {
  10690. this._drawPoints(dataset, group, this.svgElements, this.svg);
  10691. }
  10692. }
  10693. }
  10694. };
  10695. /**
  10696. * draw the data points
  10697. *
  10698. * @param {Array} dataset
  10699. * @param {Object} JSONcontainer
  10700. * @param {Object} svg | SVG DOM element
  10701. * @param {GraphGroup} group
  10702. * @param {Number} [offset]
  10703. */
  10704. LineGraph.prototype._drawPoints = function (dataset, group, JSONcontainer, svg, offset) {
  10705. if (offset === undefined) {offset = 0;}
  10706. for (var i = 0; i < dataset.length; i++) {
  10707. DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, group, JSONcontainer, svg);
  10708. }
  10709. };
  10710. /**
  10711. * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
  10712. * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
  10713. * the yAxis.
  10714. *
  10715. * @param datapoints
  10716. * @returns {Array}
  10717. * @private
  10718. */
  10719. LineGraph.prototype._convertXcoordinates = function (datapoints) {
  10720. var extractedData = [];
  10721. var xValue, yValue;
  10722. var toScreen = this.body.util.toScreen;
  10723. for (var i = 0; i < datapoints.length; i++) {
  10724. xValue = toScreen(datapoints[i].x) + this.width;
  10725. yValue = datapoints[i].y;
  10726. extractedData.push({x: xValue, y: yValue});
  10727. }
  10728. return extractedData;
  10729. };
  10730. /**
  10731. * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
  10732. * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
  10733. * the yAxis.
  10734. *
  10735. * @param datapoints
  10736. * @returns {Array}
  10737. * @private
  10738. */
  10739. LineGraph.prototype._convertYcoordinates = function (datapoints, group) {
  10740. var extractedData = [];
  10741. var xValue, yValue;
  10742. var toScreen = this.body.util.toScreen;
  10743. var axis = this.yAxisLeft;
  10744. var svgHeight = Number(this.svg.style.height.replace("px",""));
  10745. if (group.options.yAxisOrientation == 'right') {
  10746. axis = this.yAxisRight;
  10747. }
  10748. for (var i = 0; i < datapoints.length; i++) {
  10749. xValue = toScreen(datapoints[i].x) + this.width;
  10750. yValue = Math.round(axis.convertValue(datapoints[i].y));
  10751. extractedData.push({x: xValue, y: yValue});
  10752. }
  10753. group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0)));
  10754. return extractedData;
  10755. };
  10756. /**
  10757. * This uses an uniform parametrization of the CatmullRom algorithm:
  10758. * "On the Parameterization of Catmull-Rom Curves" by Cem Yuksel et al.
  10759. * @param data
  10760. * @returns {string}
  10761. * @private
  10762. */
  10763. LineGraph.prototype._catmullRomUniform = function(data) {
  10764. // catmull rom
  10765. var p0, p1, p2, p3, bp1, bp2;
  10766. var d = Math.round(data[0].x) + "," + Math.round(data[0].y) + " ";
  10767. var normalization = 1/6;
  10768. var length = data.length;
  10769. for (var i = 0; i < length - 1; i++) {
  10770. p0 = (i == 0) ? data[0] : data[i-1];
  10771. p1 = data[i];
  10772. p2 = data[i+1];
  10773. p3 = (i + 2 < length) ? data[i+2] : p2;
  10774. // Catmull-Rom to Cubic Bezier conversion matrix
  10775. // 0 1 0 0
  10776. // -1/6 1 1/6 0
  10777. // 0 1/6 1 -1/6
  10778. // 0 0 1 0
  10779. // bp0 = { x: p1.x, y: p1.y };
  10780. bp1 = { x: ((-p0.x + 6*p1.x + p2.x) *normalization), y: ((-p0.y + 6*p1.y + p2.y) *normalization)};
  10781. bp2 = { x: (( p1.x + 6*p2.x - p3.x) *normalization), y: (( p1.y + 6*p2.y - p3.y) *normalization)};
  10782. // bp0 = { x: p2.x, y: p2.y };
  10783. d += "C" +
  10784. bp1.x + "," +
  10785. bp1.y + " " +
  10786. bp2.x + "," +
  10787. bp2.y + " " +
  10788. p2.x + "," +
  10789. p2.y + " ";
  10790. }
  10791. return d;
  10792. };
  10793. /**
  10794. * This uses either the chordal or centripetal parameterization of the catmull-rom algorithm.
  10795. * By default, the centripetal parameterization is used because this gives the nicest results.
  10796. * These parameterizations are relatively heavy because the distance between 4 points have to be calculated.
  10797. *
  10798. * One optimization can be used to reuse distances since this is a sliding window approach.
  10799. * @param data
  10800. * @returns {string}
  10801. * @private
  10802. */
  10803. LineGraph.prototype._catmullRom = function(data, group) {
  10804. var alpha = group.options.catmullRom.alpha;
  10805. if (alpha == 0 || alpha === undefined) {
  10806. return this._catmullRomUniform(data);
  10807. }
  10808. else {
  10809. var p0, p1, p2, p3, bp1, bp2, d1,d2,d3, A, B, N, M;
  10810. var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA;
  10811. var d = Math.round(data[0].x) + "," + Math.round(data[0].y) + " ";
  10812. var length = data.length;
  10813. for (var i = 0; i < length - 1; i++) {
  10814. p0 = (i == 0) ? data[0] : data[i-1];
  10815. p1 = data[i];
  10816. p2 = data[i+1];
  10817. p3 = (i + 2 < length) ? data[i+2] : p2;
  10818. d1 = Math.sqrt(Math.pow(p0.x - p1.x,2) + Math.pow(p0.y - p1.y,2));
  10819. d2 = Math.sqrt(Math.pow(p1.x - p2.x,2) + Math.pow(p1.y - p2.y,2));
  10820. d3 = Math.sqrt(Math.pow(p2.x - p3.x,2) + Math.pow(p2.y - p3.y,2));
  10821. // Catmull-Rom to Cubic Bezier conversion matrix
  10822. //
  10823. // A = 2d1^2a + 3d1^a * d2^a + d3^2a
  10824. // B = 2d3^2a + 3d3^a * d2^a + d2^2a
  10825. //
  10826. // [ 0 1 0 0 ]
  10827. // [ -d2^2a/N A/N d1^2a/N 0 ]
  10828. // [ 0 d3^2a/M B/M -d2^2a/M ]
  10829. // [ 0 0 1 0 ]
  10830. // [ 0 1 0 0 ]
  10831. // [ -d2pow2a/N A/N d1pow2a/N 0 ]
  10832. // [ 0 d3pow2a/M B/M -d2pow2a/M ]
  10833. // [ 0 0 1 0 ]
  10834. d3powA = Math.pow(d3, alpha);
  10835. d3pow2A = Math.pow(d3,2*alpha);
  10836. d2powA = Math.pow(d2, alpha);
  10837. d2pow2A = Math.pow(d2,2*alpha);
  10838. d1powA = Math.pow(d1, alpha);
  10839. d1pow2A = Math.pow(d1,2*alpha);
  10840. A = 2*d1pow2A + 3*d1powA * d2powA + d2pow2A;
  10841. B = 2*d3pow2A + 3*d3powA * d2powA + d2pow2A;
  10842. N = 3*d1powA * (d1powA + d2powA);
  10843. if (N > 0) {N = 1 / N;}
  10844. M = 3*d3powA * (d3powA + d2powA);
  10845. if (M > 0) {M = 1 / M;}
  10846. bp1 = { x: ((-d2pow2A * p0.x + A*p1.x + d1pow2A * p2.x) * N),
  10847. y: ((-d2pow2A * p0.y + A*p1.y + d1pow2A * p2.y) * N)};
  10848. bp2 = { x: (( d3pow2A * p1.x + B*p2.x - d2pow2A * p3.x) * M),
  10849. y: (( d3pow2A * p1.y + B*p2.y - d2pow2A * p3.y) * M)};
  10850. if (bp1.x == 0 && bp1.y == 0) {bp1 = p1;}
  10851. if (bp2.x == 0 && bp2.y == 0) {bp2 = p2;}
  10852. d += "C" +
  10853. bp1.x + "," +
  10854. bp1.y + " " +
  10855. bp2.x + "," +
  10856. bp2.y + " " +
  10857. p2.x + "," +
  10858. p2.y + " ";
  10859. }
  10860. return d;
  10861. }
  10862. };
  10863. /**
  10864. * this generates the SVG path for a linear drawing between datapoints.
  10865. * @param data
  10866. * @returns {string}
  10867. * @private
  10868. */
  10869. LineGraph.prototype._linear = function(data) {
  10870. // linear
  10871. var d = "";
  10872. for (var i = 0; i < data.length; i++) {
  10873. if (i == 0) {
  10874. d += data[i].x + "," + data[i].y;
  10875. }
  10876. else {
  10877. d += " " + data[i].x + "," + data[i].y;
  10878. }
  10879. }
  10880. return d;
  10881. };
  10882. module.exports = LineGraph;
  10883. /***/ },
  10884. /* 27 */
  10885. /***/ function(module, exports, __webpack_require__) {
  10886. var util = __webpack_require__(1);
  10887. var Component = __webpack_require__(18);
  10888. var TimeStep = __webpack_require__(17);
  10889. var moment = __webpack_require__(41);
  10890. /**
  10891. * A horizontal time axis
  10892. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
  10893. * @param {Object} [options] See TimeAxis.setOptions for the available
  10894. * options.
  10895. * @constructor TimeAxis
  10896. * @extends Component
  10897. */
  10898. function TimeAxis (body, options) {
  10899. this.dom = {
  10900. foreground: null,
  10901. majorLines: [],
  10902. majorTexts: [],
  10903. minorLines: [],
  10904. minorTexts: [],
  10905. redundant: {
  10906. majorLines: [],
  10907. majorTexts: [],
  10908. minorLines: [],
  10909. minorTexts: []
  10910. }
  10911. };
  10912. this.props = {
  10913. range: {
  10914. start: 0,
  10915. end: 0,
  10916. minimumStep: 0
  10917. },
  10918. lineTop: 0
  10919. };
  10920. this.defaultOptions = {
  10921. orientation: 'bottom', // supported: 'top', 'bottom'
  10922. // TODO: implement timeaxis orientations 'left' and 'right'
  10923. showMinorLabels: true,
  10924. showMajorLabels: true
  10925. };
  10926. this.options = util.extend({}, this.defaultOptions);
  10927. this.body = body;
  10928. // create the HTML DOM
  10929. this._create();
  10930. this.setOptions(options);
  10931. }
  10932. TimeAxis.prototype = new Component();
  10933. /**
  10934. * Set options for the TimeAxis.
  10935. * Parameters will be merged in current options.
  10936. * @param {Object} options Available options:
  10937. * {string} [orientation]
  10938. * {boolean} [showMinorLabels]
  10939. * {boolean} [showMajorLabels]
  10940. */
  10941. TimeAxis.prototype.setOptions = function(options) {
  10942. if (options) {
  10943. // copy all options that we know
  10944. util.selectiveExtend(['orientation', 'showMinorLabels', 'showMajorLabels'], this.options, options);
  10945. // apply locale to moment.js
  10946. // TODO: not so nice, this is applied globally to moment.js
  10947. if ('locale' in options) {
  10948. if (typeof moment.locale === 'function') {
  10949. // moment.js 2.8.1+
  10950. moment.locale(options.locale);
  10951. }
  10952. else {
  10953. moment.lang(options.locale);
  10954. }
  10955. }
  10956. }
  10957. };
  10958. /**
  10959. * Create the HTML DOM for the TimeAxis
  10960. */
  10961. TimeAxis.prototype._create = function() {
  10962. this.dom.foreground = document.createElement('div');
  10963. this.dom.background = document.createElement('div');
  10964. this.dom.foreground.className = 'timeaxis foreground';
  10965. this.dom.background.className = 'timeaxis background';
  10966. };
  10967. /**
  10968. * Destroy the TimeAxis
  10969. */
  10970. TimeAxis.prototype.destroy = function() {
  10971. // remove from DOM
  10972. if (this.dom.foreground.parentNode) {
  10973. this.dom.foreground.parentNode.removeChild(this.dom.foreground);
  10974. }
  10975. if (this.dom.background.parentNode) {
  10976. this.dom.background.parentNode.removeChild(this.dom.background);
  10977. }
  10978. this.body = null;
  10979. };
  10980. /**
  10981. * Repaint the component
  10982. * @return {boolean} Returns true if the component is resized
  10983. */
  10984. TimeAxis.prototype.redraw = function () {
  10985. var options = this.options,
  10986. props = this.props,
  10987. foreground = this.dom.foreground,
  10988. background = this.dom.background;
  10989. // determine the correct parent DOM element (depending on option orientation)
  10990. var parent = (options.orientation == 'top') ? this.body.dom.top : this.body.dom.bottom;
  10991. var parentChanged = (foreground.parentNode !== parent);
  10992. // calculate character width and height
  10993. this._calculateCharSize();
  10994. // TODO: recalculate sizes only needed when parent is resized or options is changed
  10995. var orientation = this.options.orientation,
  10996. showMinorLabels = this.options.showMinorLabels,
  10997. showMajorLabels = this.options.showMajorLabels;
  10998. // determine the width and height of the elemens for the axis
  10999. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  11000. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  11001. props.height = props.minorLabelHeight + props.majorLabelHeight;
  11002. props.width = foreground.offsetWidth;
  11003. props.minorLineHeight = this.body.domProps.root.height - props.majorLabelHeight -
  11004. (options.orientation == 'top' ? this.body.domProps.bottom.height : this.body.domProps.top.height);
  11005. props.minorLineWidth = 1; // TODO: really calculate width
  11006. props.majorLineHeight = props.minorLineHeight + props.majorLabelHeight;
  11007. props.majorLineWidth = 1; // TODO: really calculate width
  11008. // take foreground and background offline while updating (is almost twice as fast)
  11009. var foregroundNextSibling = foreground.nextSibling;
  11010. var backgroundNextSibling = background.nextSibling;
  11011. foreground.parentNode && foreground.parentNode.removeChild(foreground);
  11012. background.parentNode && background.parentNode.removeChild(background);
  11013. foreground.style.height = this.props.height + 'px';
  11014. this._repaintLabels();
  11015. // put DOM online again (at the same place)
  11016. if (foregroundNextSibling) {
  11017. parent.insertBefore(foreground, foregroundNextSibling);
  11018. }
  11019. else {
  11020. parent.appendChild(foreground)
  11021. }
  11022. if (backgroundNextSibling) {
  11023. this.body.dom.backgroundVertical.insertBefore(background, backgroundNextSibling);
  11024. }
  11025. else {
  11026. this.body.dom.backgroundVertical.appendChild(background)
  11027. }
  11028. return this._isResized() || parentChanged;
  11029. };
  11030. /**
  11031. * Repaint major and minor text labels and vertical grid lines
  11032. * @private
  11033. */
  11034. TimeAxis.prototype._repaintLabels = function () {
  11035. var orientation = this.options.orientation;
  11036. // calculate range and step (step such that we have space for 7 characters per label)
  11037. var start = util.convert(this.body.range.start, 'Number'),
  11038. end = util.convert(this.body.range.end, 'Number'),
  11039. minimumStep = this.body.util.toTime((this.props.minorCharWidth || 10) * 7).valueOf()
  11040. -this.body.util.toTime(0).valueOf();
  11041. var step = new TimeStep(new Date(start), new Date(end), minimumStep);
  11042. this.step = step;
  11043. // Move all DOM elements to a "redundant" list, where they
  11044. // can be picked for re-use, and clear the lists with lines and texts.
  11045. // At the end of the function _repaintLabels, left over elements will be cleaned up
  11046. var dom = this.dom;
  11047. dom.redundant.majorLines = dom.majorLines;
  11048. dom.redundant.majorTexts = dom.majorTexts;
  11049. dom.redundant.minorLines = dom.minorLines;
  11050. dom.redundant.minorTexts = dom.minorTexts;
  11051. dom.majorLines = [];
  11052. dom.majorTexts = [];
  11053. dom.minorLines = [];
  11054. dom.minorTexts = [];
  11055. step.first();
  11056. var xFirstMajorLabel = undefined;
  11057. var max = 0;
  11058. while (step.hasNext() && max < 1000) {
  11059. max++;
  11060. var cur = step.getCurrent(),
  11061. x = this.body.util.toScreen(cur),
  11062. isMajor = step.isMajor();
  11063. // TODO: lines must have a width, such that we can create css backgrounds
  11064. if (this.options.showMinorLabels) {
  11065. this._repaintMinorText(x, step.getLabelMinor(), orientation);
  11066. }
  11067. if (isMajor && this.options.showMajorLabels) {
  11068. if (x > 0) {
  11069. if (xFirstMajorLabel == undefined) {
  11070. xFirstMajorLabel = x;
  11071. }
  11072. this._repaintMajorText(x, step.getLabelMajor(), orientation);
  11073. }
  11074. this._repaintMajorLine(x, orientation);
  11075. }
  11076. else {
  11077. this._repaintMinorLine(x, orientation);
  11078. }
  11079. step.next();
  11080. }
  11081. // create a major label on the left when needed
  11082. if (this.options.showMajorLabels) {
  11083. var leftTime = this.body.util.toTime(0),
  11084. leftText = step.getLabelMajor(leftTime),
  11085. widthText = leftText.length * (this.props.majorCharWidth || 10) + 10; // upper bound estimation
  11086. if (xFirstMajorLabel == undefined || widthText < xFirstMajorLabel) {
  11087. this._repaintMajorText(0, leftText, orientation);
  11088. }
  11089. }
  11090. // Cleanup leftover DOM elements from the redundant list
  11091. util.forEach(this.dom.redundant, function (arr) {
  11092. while (arr.length) {
  11093. var elem = arr.pop();
  11094. if (elem && elem.parentNode) {
  11095. elem.parentNode.removeChild(elem);
  11096. }
  11097. }
  11098. });
  11099. };
  11100. /**
  11101. * Create a minor label for the axis at position x
  11102. * @param {Number} x
  11103. * @param {String} text
  11104. * @param {String} orientation "top" or "bottom" (default)
  11105. * @private
  11106. */
  11107. TimeAxis.prototype._repaintMinorText = function (x, text, orientation) {
  11108. // reuse redundant label
  11109. var label = this.dom.redundant.minorTexts.shift();
  11110. if (!label) {
  11111. // create new label
  11112. var content = document.createTextNode('');
  11113. label = document.createElement('div');
  11114. label.appendChild(content);
  11115. label.className = 'text minor';
  11116. this.dom.foreground.appendChild(label);
  11117. }
  11118. this.dom.minorTexts.push(label);
  11119. label.childNodes[0].nodeValue = text;
  11120. label.style.top = (orientation == 'top') ? (this.props.majorLabelHeight + 'px') : '0';
  11121. label.style.left = x + 'px';
  11122. //label.title = title; // TODO: this is a heavy operation
  11123. };
  11124. /**
  11125. * Create a Major label for the axis at position x
  11126. * @param {Number} x
  11127. * @param {String} text
  11128. * @param {String} orientation "top" or "bottom" (default)
  11129. * @private
  11130. */
  11131. TimeAxis.prototype._repaintMajorText = function (x, text, orientation) {
  11132. // reuse redundant label
  11133. var label = this.dom.redundant.majorTexts.shift();
  11134. if (!label) {
  11135. // create label
  11136. var content = document.createTextNode(text);
  11137. label = document.createElement('div');
  11138. label.className = 'text major';
  11139. label.appendChild(content);
  11140. this.dom.foreground.appendChild(label);
  11141. }
  11142. this.dom.majorTexts.push(label);
  11143. label.childNodes[0].nodeValue = text;
  11144. //label.title = title; // TODO: this is a heavy operation
  11145. label.style.top = (orientation == 'top') ? '0' : (this.props.minorLabelHeight + 'px');
  11146. label.style.left = x + 'px';
  11147. };
  11148. /**
  11149. * Create a minor line for the axis at position x
  11150. * @param {Number} x
  11151. * @param {String} orientation "top" or "bottom" (default)
  11152. * @private
  11153. */
  11154. TimeAxis.prototype._repaintMinorLine = function (x, orientation) {
  11155. // reuse redundant line
  11156. var line = this.dom.redundant.minorLines.shift();
  11157. if (!line) {
  11158. // create vertical line
  11159. line = document.createElement('div');
  11160. line.className = 'grid vertical minor';
  11161. this.dom.background.appendChild(line);
  11162. }
  11163. this.dom.minorLines.push(line);
  11164. var props = this.props;
  11165. if (orientation == 'top') {
  11166. line.style.top = props.majorLabelHeight + 'px';
  11167. }
  11168. else {
  11169. line.style.top = this.body.domProps.top.height + 'px';
  11170. }
  11171. line.style.height = props.minorLineHeight + 'px';
  11172. line.style.left = (x - props.minorLineWidth / 2) + 'px';
  11173. };
  11174. /**
  11175. * Create a Major line for the axis at position x
  11176. * @param {Number} x
  11177. * @param {String} orientation "top" or "bottom" (default)
  11178. * @private
  11179. */
  11180. TimeAxis.prototype._repaintMajorLine = function (x, orientation) {
  11181. // reuse redundant line
  11182. var line = this.dom.redundant.majorLines.shift();
  11183. if (!line) {
  11184. // create vertical line
  11185. line = document.createElement('DIV');
  11186. line.className = 'grid vertical major';
  11187. this.dom.background.appendChild(line);
  11188. }
  11189. this.dom.majorLines.push(line);
  11190. var props = this.props;
  11191. if (orientation == 'top') {
  11192. line.style.top = '0';
  11193. }
  11194. else {
  11195. line.style.top = this.body.domProps.top.height + 'px';
  11196. }
  11197. line.style.left = (x - props.majorLineWidth / 2) + 'px';
  11198. line.style.height = props.majorLineHeight + 'px';
  11199. };
  11200. /**
  11201. * Determine the size of text on the axis (both major and minor axis).
  11202. * The size is calculated only once and then cached in this.props.
  11203. * @private
  11204. */
  11205. TimeAxis.prototype._calculateCharSize = function () {
  11206. // Note: We calculate char size with every redraw. Size may change, for
  11207. // example when any of the timelines parents had display:none for example.
  11208. // determine the char width and height on the minor axis
  11209. if (!this.dom.measureCharMinor) {
  11210. this.dom.measureCharMinor = document.createElement('DIV');
  11211. this.dom.measureCharMinor.className = 'text minor measure';
  11212. this.dom.measureCharMinor.style.position = 'absolute';
  11213. this.dom.measureCharMinor.appendChild(document.createTextNode('0'));
  11214. this.dom.foreground.appendChild(this.dom.measureCharMinor);
  11215. }
  11216. this.props.minorCharHeight = this.dom.measureCharMinor.clientHeight;
  11217. this.props.minorCharWidth = this.dom.measureCharMinor.clientWidth;
  11218. // determine the char width and height on the major axis
  11219. if (!this.dom.measureCharMajor) {
  11220. this.dom.measureCharMajor = document.createElement('DIV');
  11221. this.dom.measureCharMajor.className = 'text minor measure';
  11222. this.dom.measureCharMajor.style.position = 'absolute';
  11223. this.dom.measureCharMajor.appendChild(document.createTextNode('0'));
  11224. this.dom.foreground.appendChild(this.dom.measureCharMajor);
  11225. }
  11226. this.props.majorCharHeight = this.dom.measureCharMajor.clientHeight;
  11227. this.props.majorCharWidth = this.dom.measureCharMajor.clientWidth;
  11228. };
  11229. /**
  11230. * Snap a date to a rounded value.
  11231. * The snap intervals are dependent on the current scale and step.
  11232. * @param {Date} date the date to be snapped.
  11233. * @return {Date} snappedDate
  11234. */
  11235. TimeAxis.prototype.snap = function(date) {
  11236. return this.step.snap(date);
  11237. };
  11238. module.exports = TimeAxis;
  11239. /***/ },
  11240. /* 28 */
  11241. /***/ function(module, exports, __webpack_require__) {
  11242. var Hammer = __webpack_require__(42);
  11243. /**
  11244. * @constructor Item
  11245. * @param {Object} data Object containing (optional) parameters type,
  11246. * start, end, content, group, className.
  11247. * @param {{toScreen: function, toTime: function}} conversion
  11248. * Conversion functions from time to screen and vice versa
  11249. * @param {Object} options Configuration options
  11250. * // TODO: describe available options
  11251. */
  11252. function Item (data, conversion, options) {
  11253. this.id = null;
  11254. this.parent = null;
  11255. this.data = data;
  11256. this.dom = null;
  11257. this.conversion = conversion || {};
  11258. this.options = options || {};
  11259. this.selected = false;
  11260. this.displayed = false;
  11261. this.dirty = true;
  11262. this.top = null;
  11263. this.left = null;
  11264. this.width = null;
  11265. this.height = null;
  11266. }
  11267. /**
  11268. * Select current item
  11269. */
  11270. Item.prototype.select = function() {
  11271. this.selected = true;
  11272. this.dirty = true;
  11273. if (this.displayed) this.redraw();
  11274. };
  11275. /**
  11276. * Unselect current item
  11277. */
  11278. Item.prototype.unselect = function() {
  11279. this.selected = false;
  11280. this.dirty = true;
  11281. if (this.displayed) this.redraw();
  11282. };
  11283. /**
  11284. * Set data for the item. Existing data will be updated. The id should not
  11285. * be changed. When the item is displayed, it will be redrawn immediately.
  11286. * @param {Object} data
  11287. */
  11288. Item.prototype.setData = function(data) {
  11289. this.data = data;
  11290. this.dirty = true;
  11291. if (this.displayed) this.redraw();
  11292. };
  11293. /**
  11294. * Set a parent for the item
  11295. * @param {ItemSet | Group} parent
  11296. */
  11297. Item.prototype.setParent = function(parent) {
  11298. if (this.displayed) {
  11299. this.hide();
  11300. this.parent = parent;
  11301. if (this.parent) {
  11302. this.show();
  11303. }
  11304. }
  11305. else {
  11306. this.parent = parent;
  11307. }
  11308. };
  11309. /**
  11310. * Check whether this item is visible inside given range
  11311. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  11312. * @returns {boolean} True if visible
  11313. */
  11314. Item.prototype.isVisible = function(range) {
  11315. // Should be implemented by Item implementations
  11316. return false;
  11317. };
  11318. /**
  11319. * Show the Item in the DOM (when not already visible)
  11320. * @return {Boolean} changed
  11321. */
  11322. Item.prototype.show = function() {
  11323. return false;
  11324. };
  11325. /**
  11326. * Hide the Item from the DOM (when visible)
  11327. * @return {Boolean} changed
  11328. */
  11329. Item.prototype.hide = function() {
  11330. return false;
  11331. };
  11332. /**
  11333. * Repaint the item
  11334. */
  11335. Item.prototype.redraw = function() {
  11336. // should be implemented by the item
  11337. };
  11338. /**
  11339. * Reposition the Item horizontally
  11340. */
  11341. Item.prototype.repositionX = function() {
  11342. // should be implemented by the item
  11343. };
  11344. /**
  11345. * Reposition the Item vertically
  11346. */
  11347. Item.prototype.repositionY = function() {
  11348. // should be implemented by the item
  11349. };
  11350. /**
  11351. * Repaint a delete button on the top right of the item when the item is selected
  11352. * @param {HTMLElement} anchor
  11353. * @protected
  11354. */
  11355. Item.prototype._repaintDeleteButton = function (anchor) {
  11356. if (this.selected && this.options.editable.remove && !this.dom.deleteButton) {
  11357. // create and show button
  11358. var me = this;
  11359. var deleteButton = document.createElement('div');
  11360. deleteButton.className = 'delete';
  11361. deleteButton.title = 'Delete this item';
  11362. Hammer(deleteButton, {
  11363. preventDefault: true
  11364. }).on('tap', function (event) {
  11365. me.parent.removeFromDataSet(me);
  11366. event.stopPropagation();
  11367. });
  11368. anchor.appendChild(deleteButton);
  11369. this.dom.deleteButton = deleteButton;
  11370. }
  11371. else if (!this.selected && this.dom.deleteButton) {
  11372. // remove button
  11373. if (this.dom.deleteButton.parentNode) {
  11374. this.dom.deleteButton.parentNode.removeChild(this.dom.deleteButton);
  11375. }
  11376. this.dom.deleteButton = null;
  11377. }
  11378. };
  11379. /**
  11380. * Set HTML contents for the item
  11381. * @param {Element} element HTML element to fill with the contents
  11382. * @private
  11383. */
  11384. Item.prototype._updateContents = function (element) {
  11385. var content;
  11386. if (this.options.template) {
  11387. var itemData = this.parent.itemSet.itemsData.get(this.id); // get a clone of the data from the dataset
  11388. content = this.options.template(itemData);
  11389. }
  11390. else {
  11391. content = this.data.content;
  11392. }
  11393. if (content instanceof Element) {
  11394. element.innerHTML = '';
  11395. element.appendChild(content);
  11396. }
  11397. else if (content != undefined) {
  11398. element.innerHTML = content;
  11399. }
  11400. else {
  11401. throw new Error('Property "content" missing in item ' + this.data.id);
  11402. }
  11403. };
  11404. /**
  11405. * Set HTML contents for the item
  11406. * @param {Element} element HTML element to fill with the contents
  11407. * @private
  11408. */
  11409. Item.prototype._updateTitle = function (element) {
  11410. if (this.data.title != null) {
  11411. element.title = this.data.title || '';
  11412. }
  11413. else {
  11414. element.removeAttribute('title');
  11415. }
  11416. };
  11417. /**
  11418. * Process dataAttributes timeline option and set as data- attributes on dom.content
  11419. * @param {Element} element HTML element to which the attributes will be attached
  11420. * @private
  11421. */
  11422. Item.prototype._updateDataAttributes = function(element) {
  11423. if (this.options.dataAttributes && this.options.dataAttributes.length > 0) {
  11424. for (var i = 0; i < this.options.dataAttributes.length; i++) {
  11425. var name = this.options.dataAttributes[i];
  11426. var value = this.data[name];
  11427. if (value != null) {
  11428. element.setAttribute('data-' + name, value);
  11429. }
  11430. else {
  11431. element.removeAttribute('data-' + name);
  11432. }
  11433. }
  11434. }
  11435. };
  11436. module.exports = Item;
  11437. /***/ },
  11438. /* 29 */
  11439. /***/ function(module, exports, __webpack_require__) {
  11440. var Hammer = __webpack_require__(42);
  11441. var Item = __webpack_require__(28);
  11442. var RangeItem = __webpack_require__(32);
  11443. /**
  11444. * @constructor BackgroundItem
  11445. * @extends Item
  11446. * @param {Object} data Object containing parameters start, end
  11447. * content, className.
  11448. * @param {{toScreen: function, toTime: function}} conversion
  11449. * Conversion functions from time to screen and vice versa
  11450. * @param {Object} [options] Configuration options
  11451. * // TODO: describe options
  11452. */
  11453. // TODO: implement support for the BackgroundItem just having a start, then being displayed as a sort of an annotation
  11454. function BackgroundItem (data, conversion, options) {
  11455. this.props = {
  11456. content: {
  11457. width: 0
  11458. }
  11459. };
  11460. this.overflow = false; // if contents can overflow (css styling), this flag is set to true
  11461. // validate data
  11462. if (data) {
  11463. if (data.start == undefined) {
  11464. throw new Error('Property "start" missing in item ' + data.id);
  11465. }
  11466. if (data.end == undefined) {
  11467. throw new Error('Property "end" missing in item ' + data.id);
  11468. }
  11469. }
  11470. Item.call(this, data, conversion, options);
  11471. }
  11472. BackgroundItem.prototype = new Item (null, null, null);
  11473. BackgroundItem.prototype.baseClassName = 'item background';
  11474. /**
  11475. * Check whether this item is visible inside given range
  11476. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  11477. * @returns {boolean} True if visible
  11478. */
  11479. BackgroundItem.prototype.isVisible = function(range) {
  11480. // determine visibility
  11481. return (this.data.start < range.end) && (this.data.end > range.start);
  11482. };
  11483. /**
  11484. * Repaint the item
  11485. */
  11486. BackgroundItem.prototype.redraw = function() {
  11487. var dom = this.dom;
  11488. if (!dom) {
  11489. // create DOM
  11490. this.dom = {};
  11491. dom = this.dom;
  11492. // background box
  11493. dom.box = document.createElement('div');
  11494. // className is updated in redraw()
  11495. // contents box
  11496. dom.content = document.createElement('div');
  11497. dom.content.className = 'content';
  11498. dom.box.appendChild(dom.content);
  11499. // attach this item as attribute
  11500. dom.box['timeline-item'] = this;
  11501. this.dirty = true;
  11502. }
  11503. // append DOM to parent DOM
  11504. if (!this.parent) {
  11505. throw new Error('Cannot redraw item: no parent attached');
  11506. }
  11507. if (!dom.box.parentNode) {
  11508. var background = this.parent.dom.background;
  11509. if (!background) {
  11510. throw new Error('Cannot redraw time axis: parent has no background container element');
  11511. }
  11512. background.appendChild(dom.box);
  11513. }
  11514. this.displayed = true;
  11515. // Update DOM when item is marked dirty. An item is marked dirty when:
  11516. // - the item is not yet rendered
  11517. // - the item's data is changed
  11518. // - the item is selected/deselected
  11519. if (this.dirty) {
  11520. this._updateContents(this.dom.content);
  11521. this._updateTitle(this.dom.content);
  11522. this._updateDataAttributes(this.dom.content);
  11523. // update class
  11524. var className = (this.data.className ? (' ' + this.data.className) : '') +
  11525. (this.selected ? ' selected' : '');
  11526. dom.box.className = this.baseClassName + className;
  11527. // determine from css whether this box has overflow
  11528. this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden';
  11529. // recalculate size
  11530. this.props.content.width = this.dom.content.offsetWidth;
  11531. this.height = 0; // set height zero, so this item will be ignored when stacking items
  11532. this.dirty = false;
  11533. }
  11534. };
  11535. /**
  11536. * Show the item in the DOM (when not already visible). The items DOM will
  11537. * be created when needed.
  11538. */
  11539. BackgroundItem.prototype.show = RangeItem.prototype.show;
  11540. /**
  11541. * Hide the item from the DOM (when visible)
  11542. * @return {Boolean} changed
  11543. */
  11544. BackgroundItem.prototype.hide = RangeItem.prototype.hide;
  11545. /**
  11546. * Reposition the item horizontally
  11547. * @Override
  11548. */
  11549. BackgroundItem.prototype.repositionX = RangeItem.prototype.repositionX;
  11550. /**
  11551. * Reposition the item vertically
  11552. * @Override
  11553. */
  11554. BackgroundItem.prototype.repositionY = function() {
  11555. var onTop = this.options.orientation === 'top';
  11556. this.dom.content.style.top = onTop ? '' : '0';
  11557. this.dom.content.style.bottom = onTop ? '0' : '';
  11558. };
  11559. module.exports = BackgroundItem;
  11560. /***/ },
  11561. /* 30 */
  11562. /***/ function(module, exports, __webpack_require__) {
  11563. var Item = __webpack_require__(28);
  11564. /**
  11565. * @constructor BoxItem
  11566. * @extends Item
  11567. * @param {Object} data Object containing parameters start
  11568. * content, className.
  11569. * @param {{toScreen: function, toTime: function}} conversion
  11570. * Conversion functions from time to screen and vice versa
  11571. * @param {Object} [options] Configuration options
  11572. * // TODO: describe available options
  11573. */
  11574. function BoxItem (data, conversion, options) {
  11575. this.props = {
  11576. dot: {
  11577. width: 0,
  11578. height: 0
  11579. },
  11580. line: {
  11581. width: 0,
  11582. height: 0
  11583. }
  11584. };
  11585. // validate data
  11586. if (data) {
  11587. if (data.start == undefined) {
  11588. throw new Error('Property "start" missing in item ' + data);
  11589. }
  11590. }
  11591. Item.call(this, data, conversion, options);
  11592. }
  11593. BoxItem.prototype = new Item (null, null, null);
  11594. /**
  11595. * Check whether this item is visible inside given range
  11596. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  11597. * @returns {boolean} True if visible
  11598. */
  11599. BoxItem.prototype.isVisible = function(range) {
  11600. // determine visibility
  11601. // TODO: account for the real width of the item. Right now we just add 1/4 to the window
  11602. var interval = (range.end - range.start) / 4;
  11603. return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
  11604. };
  11605. /**
  11606. * Repaint the item
  11607. */
  11608. BoxItem.prototype.redraw = function() {
  11609. var dom = this.dom;
  11610. if (!dom) {
  11611. // create DOM
  11612. this.dom = {};
  11613. dom = this.dom;
  11614. // create main box
  11615. dom.box = document.createElement('DIV');
  11616. // contents box (inside the background box). used for making margins
  11617. dom.content = document.createElement('DIV');
  11618. dom.content.className = 'content';
  11619. dom.box.appendChild(dom.content);
  11620. // line to axis
  11621. dom.line = document.createElement('DIV');
  11622. dom.line.className = 'line';
  11623. // dot on axis
  11624. dom.dot = document.createElement('DIV');
  11625. dom.dot.className = 'dot';
  11626. // attach this item as attribute
  11627. dom.box['timeline-item'] = this;
  11628. this.dirty = true;
  11629. }
  11630. // append DOM to parent DOM
  11631. if (!this.parent) {
  11632. throw new Error('Cannot redraw item: no parent attached');
  11633. }
  11634. if (!dom.box.parentNode) {
  11635. var foreground = this.parent.dom.foreground;
  11636. if (!foreground) throw new Error('Cannot redraw time axis: parent has no foreground container element');
  11637. foreground.appendChild(dom.box);
  11638. }
  11639. if (!dom.line.parentNode) {
  11640. var background = this.parent.dom.background;
  11641. if (!background) throw new Error('Cannot redraw time axis: parent has no background container element');
  11642. background.appendChild(dom.line);
  11643. }
  11644. if (!dom.dot.parentNode) {
  11645. var axis = this.parent.dom.axis;
  11646. if (!background) throw new Error('Cannot redraw time axis: parent has no axis container element');
  11647. axis.appendChild(dom.dot);
  11648. }
  11649. this.displayed = true;
  11650. // Update DOM when item is marked dirty. An item is marked dirty when:
  11651. // - the item is not yet rendered
  11652. // - the item's data is changed
  11653. // - the item is selected/deselected
  11654. if (this.dirty) {
  11655. this._updateContents(this.dom.content);
  11656. this._updateTitle(this.dom.box);
  11657. this._updateDataAttributes(this.dom.box);
  11658. // update class
  11659. var className = (this.data.className? ' ' + this.data.className : '') +
  11660. (this.selected ? ' selected' : '');
  11661. dom.box.className = 'item box' + className;
  11662. dom.line.className = 'item line' + className;
  11663. dom.dot.className = 'item dot' + className;
  11664. // recalculate size
  11665. this.props.dot.height = dom.dot.offsetHeight;
  11666. this.props.dot.width = dom.dot.offsetWidth;
  11667. this.props.line.width = dom.line.offsetWidth;
  11668. this.width = dom.box.offsetWidth;
  11669. this.height = dom.box.offsetHeight;
  11670. this.dirty = false;
  11671. }
  11672. this._repaintDeleteButton(dom.box);
  11673. };
  11674. /**
  11675. * Show the item in the DOM (when not already displayed). The items DOM will
  11676. * be created when needed.
  11677. */
  11678. BoxItem.prototype.show = function() {
  11679. if (!this.displayed) {
  11680. this.redraw();
  11681. }
  11682. };
  11683. /**
  11684. * Hide the item from the DOM (when visible)
  11685. */
  11686. BoxItem.prototype.hide = function() {
  11687. if (this.displayed) {
  11688. var dom = this.dom;
  11689. if (dom.box.parentNode) dom.box.parentNode.removeChild(dom.box);
  11690. if (dom.line.parentNode) dom.line.parentNode.removeChild(dom.line);
  11691. if (dom.dot.parentNode) dom.dot.parentNode.removeChild(dom.dot);
  11692. this.top = null;
  11693. this.left = null;
  11694. this.displayed = false;
  11695. }
  11696. };
  11697. /**
  11698. * Reposition the item horizontally
  11699. * @Override
  11700. */
  11701. BoxItem.prototype.repositionX = function() {
  11702. var start = this.conversion.toScreen(this.data.start);
  11703. var align = this.options.align;
  11704. var left;
  11705. var box = this.dom.box;
  11706. var line = this.dom.line;
  11707. var dot = this.dom.dot;
  11708. // calculate left position of the box
  11709. if (align == 'right') {
  11710. this.left = start - this.width;
  11711. }
  11712. else if (align == 'left') {
  11713. this.left = start;
  11714. }
  11715. else {
  11716. // default or 'center'
  11717. this.left = start - this.width / 2;
  11718. }
  11719. // reposition box
  11720. box.style.left = this.left + 'px';
  11721. // reposition line
  11722. line.style.left = (start - this.props.line.width / 2) + 'px';
  11723. // reposition dot
  11724. dot.style.left = (start - this.props.dot.width / 2) + 'px';
  11725. };
  11726. /**
  11727. * Reposition the item vertically
  11728. * @Override
  11729. */
  11730. BoxItem.prototype.repositionY = function() {
  11731. var orientation = this.options.orientation;
  11732. var box = this.dom.box;
  11733. var line = this.dom.line;
  11734. var dot = this.dom.dot;
  11735. if (orientation == 'top') {
  11736. box.style.top = (this.top || 0) + 'px';
  11737. line.style.top = '0';
  11738. line.style.height = (this.parent.top + this.top + 1) + 'px';
  11739. line.style.bottom = '';
  11740. }
  11741. else { // orientation 'bottom'
  11742. var itemSetHeight = this.parent.itemSet.props.height; // TODO: this is nasty
  11743. var lineHeight = itemSetHeight - this.parent.top - this.parent.height + this.top;
  11744. box.style.top = (this.parent.height - this.top - this.height || 0) + 'px';
  11745. line.style.top = (itemSetHeight - lineHeight) + 'px';
  11746. line.style.bottom = '0';
  11747. }
  11748. dot.style.top = (-this.props.dot.height / 2) + 'px';
  11749. };
  11750. module.exports = BoxItem;
  11751. /***/ },
  11752. /* 31 */
  11753. /***/ function(module, exports, __webpack_require__) {
  11754. var Item = __webpack_require__(28);
  11755. /**
  11756. * @constructor PointItem
  11757. * @extends Item
  11758. * @param {Object} data Object containing parameters start
  11759. * content, className.
  11760. * @param {{toScreen: function, toTime: function}} conversion
  11761. * Conversion functions from time to screen and vice versa
  11762. * @param {Object} [options] Configuration options
  11763. * // TODO: describe available options
  11764. */
  11765. function PointItem (data, conversion, options) {
  11766. this.props = {
  11767. dot: {
  11768. top: 0,
  11769. width: 0,
  11770. height: 0
  11771. },
  11772. content: {
  11773. height: 0,
  11774. marginLeft: 0
  11775. }
  11776. };
  11777. // validate data
  11778. if (data) {
  11779. if (data.start == undefined) {
  11780. throw new Error('Property "start" missing in item ' + data);
  11781. }
  11782. }
  11783. Item.call(this, data, conversion, options);
  11784. }
  11785. PointItem.prototype = new Item (null, null, null);
  11786. /**
  11787. * Check whether this item is visible inside given range
  11788. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  11789. * @returns {boolean} True if visible
  11790. */
  11791. PointItem.prototype.isVisible = function(range) {
  11792. // determine visibility
  11793. // TODO: account for the real width of the item. Right now we just add 1/4 to the window
  11794. var interval = (range.end - range.start) / 4;
  11795. return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
  11796. };
  11797. /**
  11798. * Repaint the item
  11799. */
  11800. PointItem.prototype.redraw = function() {
  11801. var dom = this.dom;
  11802. if (!dom) {
  11803. // create DOM
  11804. this.dom = {};
  11805. dom = this.dom;
  11806. // background box
  11807. dom.point = document.createElement('div');
  11808. // className is updated in redraw()
  11809. // contents box, right from the dot
  11810. dom.content = document.createElement('div');
  11811. dom.content.className = 'content';
  11812. dom.point.appendChild(dom.content);
  11813. // dot at start
  11814. dom.dot = document.createElement('div');
  11815. dom.point.appendChild(dom.dot);
  11816. // attach this item as attribute
  11817. dom.point['timeline-item'] = this;
  11818. this.dirty = true;
  11819. }
  11820. // append DOM to parent DOM
  11821. if (!this.parent) {
  11822. throw new Error('Cannot redraw item: no parent attached');
  11823. }
  11824. if (!dom.point.parentNode) {
  11825. var foreground = this.parent.dom.foreground;
  11826. if (!foreground) {
  11827. throw new Error('Cannot redraw time axis: parent has no foreground container element');
  11828. }
  11829. foreground.appendChild(dom.point);
  11830. }
  11831. this.displayed = true;
  11832. // Update DOM when item is marked dirty. An item is marked dirty when:
  11833. // - the item is not yet rendered
  11834. // - the item's data is changed
  11835. // - the item is selected/deselected
  11836. if (this.dirty) {
  11837. this._updateContents(this.dom.content);
  11838. this._updateTitle(this.dom.point);
  11839. this._updateDataAttributes(this.dom.point);
  11840. // update class
  11841. var className = (this.data.className? ' ' + this.data.className : '') +
  11842. (this.selected ? ' selected' : '');
  11843. dom.point.className = 'item point' + className;
  11844. dom.dot.className = 'item dot' + className;
  11845. // recalculate size
  11846. this.width = dom.point.offsetWidth;
  11847. this.height = dom.point.offsetHeight;
  11848. this.props.dot.width = dom.dot.offsetWidth;
  11849. this.props.dot.height = dom.dot.offsetHeight;
  11850. this.props.content.height = dom.content.offsetHeight;
  11851. // resize contents
  11852. dom.content.style.marginLeft = 2 * this.props.dot.width + 'px';
  11853. //dom.content.style.marginRight = ... + 'px'; // TODO: margin right
  11854. dom.dot.style.top = ((this.height - this.props.dot.height) / 2) + 'px';
  11855. dom.dot.style.left = (this.props.dot.width / 2) + 'px';
  11856. this.dirty = false;
  11857. }
  11858. this._repaintDeleteButton(dom.point);
  11859. };
  11860. /**
  11861. * Show the item in the DOM (when not already visible). The items DOM will
  11862. * be created when needed.
  11863. */
  11864. PointItem.prototype.show = function() {
  11865. if (!this.displayed) {
  11866. this.redraw();
  11867. }
  11868. };
  11869. /**
  11870. * Hide the item from the DOM (when visible)
  11871. */
  11872. PointItem.prototype.hide = function() {
  11873. if (this.displayed) {
  11874. if (this.dom.point.parentNode) {
  11875. this.dom.point.parentNode.removeChild(this.dom.point);
  11876. }
  11877. this.top = null;
  11878. this.left = null;
  11879. this.displayed = false;
  11880. }
  11881. };
  11882. /**
  11883. * Reposition the item horizontally
  11884. * @Override
  11885. */
  11886. PointItem.prototype.repositionX = function() {
  11887. var start = this.conversion.toScreen(this.data.start);
  11888. this.left = start - this.props.dot.width;
  11889. // reposition point
  11890. this.dom.point.style.left = this.left + 'px';
  11891. };
  11892. /**
  11893. * Reposition the item vertically
  11894. * @Override
  11895. */
  11896. PointItem.prototype.repositionY = function() {
  11897. var orientation = this.options.orientation,
  11898. point = this.dom.point;
  11899. if (orientation == 'top') {
  11900. point.style.top = this.top + 'px';
  11901. }
  11902. else {
  11903. point.style.top = (this.parent.height - this.top - this.height) + 'px';
  11904. }
  11905. };
  11906. module.exports = PointItem;
  11907. /***/ },
  11908. /* 32 */
  11909. /***/ function(module, exports, __webpack_require__) {
  11910. var Hammer = __webpack_require__(42);
  11911. var Item = __webpack_require__(28);
  11912. /**
  11913. * @constructor RangeItem
  11914. * @extends Item
  11915. * @param {Object} data Object containing parameters start, end
  11916. * content, className.
  11917. * @param {{toScreen: function, toTime: function}} conversion
  11918. * Conversion functions from time to screen and vice versa
  11919. * @param {Object} [options] Configuration options
  11920. * // TODO: describe options
  11921. */
  11922. function RangeItem (data, conversion, options) {
  11923. this.props = {
  11924. content: {
  11925. width: 0
  11926. }
  11927. };
  11928. this.overflow = false; // if contents can overflow (css styling), this flag is set to true
  11929. // validate data
  11930. if (data) {
  11931. if (data.start == undefined) {
  11932. throw new Error('Property "start" missing in item ' + data.id);
  11933. }
  11934. if (data.end == undefined) {
  11935. throw new Error('Property "end" missing in item ' + data.id);
  11936. }
  11937. }
  11938. Item.call(this, data, conversion, options);
  11939. }
  11940. RangeItem.prototype = new Item (null, null, null);
  11941. RangeItem.prototype.baseClassName = 'item range';
  11942. /**
  11943. * Check whether this item is visible inside given range
  11944. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  11945. * @returns {boolean} True if visible
  11946. */
  11947. RangeItem.prototype.isVisible = function(range) {
  11948. // determine visibility
  11949. return (this.data.start < range.end) && (this.data.end > range.start);
  11950. };
  11951. /**
  11952. * Repaint the item
  11953. */
  11954. RangeItem.prototype.redraw = function() {
  11955. var dom = this.dom;
  11956. if (!dom) {
  11957. // create DOM
  11958. this.dom = {};
  11959. dom = this.dom;
  11960. // background box
  11961. dom.box = document.createElement('div');
  11962. // className is updated in redraw()
  11963. // contents box
  11964. dom.content = document.createElement('div');
  11965. dom.content.className = 'content';
  11966. dom.box.appendChild(dom.content);
  11967. // attach this item as attribute
  11968. dom.box['timeline-item'] = this;
  11969. this.dirty = true;
  11970. }
  11971. // append DOM to parent DOM
  11972. if (!this.parent) {
  11973. throw new Error('Cannot redraw item: no parent attached');
  11974. }
  11975. if (!dom.box.parentNode) {
  11976. var foreground = this.parent.dom.foreground;
  11977. if (!foreground) {
  11978. throw new Error('Cannot redraw time axis: parent has no foreground container element');
  11979. }
  11980. foreground.appendChild(dom.box);
  11981. }
  11982. this.displayed = true;
  11983. // Update DOM when item is marked dirty. An item is marked dirty when:
  11984. // - the item is not yet rendered
  11985. // - the item's data is changed
  11986. // - the item is selected/deselected
  11987. if (this.dirty) {
  11988. this._updateContents(this.dom.content);
  11989. this._updateTitle(this.dom.box);
  11990. this._updateDataAttributes(this.dom.box);
  11991. // update class
  11992. var className = (this.data.className ? (' ' + this.data.className) : '') +
  11993. (this.selected ? ' selected' : '');
  11994. dom.box.className = this.baseClassName + className;
  11995. // determine from css whether this box has overflow
  11996. this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden';
  11997. // recalculate size
  11998. this.props.content.width = this.dom.content.offsetWidth;
  11999. this.height = this.dom.box.offsetHeight;
  12000. this.dirty = false;
  12001. }
  12002. this._repaintDeleteButton(dom.box);
  12003. this._repaintDragLeft();
  12004. this._repaintDragRight();
  12005. };
  12006. /**
  12007. * Show the item in the DOM (when not already visible). The items DOM will
  12008. * be created when needed.
  12009. */
  12010. RangeItem.prototype.show = function() {
  12011. if (!this.displayed) {
  12012. this.redraw();
  12013. }
  12014. };
  12015. /**
  12016. * Hide the item from the DOM (when visible)
  12017. * @return {Boolean} changed
  12018. */
  12019. RangeItem.prototype.hide = function() {
  12020. if (this.displayed) {
  12021. var box = this.dom.box;
  12022. if (box.parentNode) {
  12023. box.parentNode.removeChild(box);
  12024. }
  12025. this.top = null;
  12026. this.left = null;
  12027. this.displayed = false;
  12028. }
  12029. };
  12030. /**
  12031. * Reposition the item horizontally
  12032. * @Override
  12033. */
  12034. RangeItem.prototype.repositionX = function() {
  12035. var parentWidth = this.parent.width;
  12036. var start = this.conversion.toScreen(this.data.start);
  12037. var end = this.conversion.toScreen(this.data.end);
  12038. var contentLeft;
  12039. var contentWidth;
  12040. // limit the width of the this, as browsers cannot draw very wide divs
  12041. if (start < -parentWidth) {
  12042. start = -parentWidth;
  12043. }
  12044. if (end > 2 * parentWidth) {
  12045. end = 2 * parentWidth;
  12046. }
  12047. var boxWidth = Math.max(end - start, 1);
  12048. if (this.overflow) {
  12049. this.left = start;
  12050. this.width = boxWidth + this.props.content.width;
  12051. contentWidth = this.props.content.width;
  12052. // Note: The calculation of width is an optimistic calculation, giving
  12053. // a width which will not change when moving the Timeline
  12054. // So no re-stacking needed, which is nicer for the eye;
  12055. }
  12056. else {
  12057. this.left = start;
  12058. this.width = boxWidth;
  12059. contentWidth = Math.min(end - start, this.props.content.width);
  12060. }
  12061. this.dom.box.style.left = this.left + 'px';
  12062. this.dom.box.style.width = boxWidth + 'px';
  12063. switch (this.options.align) {
  12064. case 'left':
  12065. this.dom.content.style.left = '0';
  12066. break;
  12067. case 'right':
  12068. this.dom.content.style.left = Math.max((boxWidth - contentWidth - 2 * this.options.padding), 0) + 'px';
  12069. break;
  12070. case 'center':
  12071. this.dom.content.style.left = Math.max((boxWidth - contentWidth - 2 * this.options.padding) / 2, 0) + 'px';
  12072. break;
  12073. default: // 'auto'
  12074. if (this.overflow) {
  12075. // when range exceeds left of the window, position the contents at the left of the visible area
  12076. contentLeft = Math.max(-start, 0);
  12077. }
  12078. else {
  12079. // when range exceeds left of the window, position the contents at the left of the visible area
  12080. if (start < 0) {
  12081. contentLeft = Math.min(-start,
  12082. (end - start - this.props.content.width - 2 * this.options.padding));
  12083. // TODO: remove the need for options.padding. it's terrible.
  12084. }
  12085. else {
  12086. contentLeft = 0;
  12087. }
  12088. }
  12089. this.dom.content.style.left = contentLeft + 'px';
  12090. }
  12091. };
  12092. /**
  12093. * Reposition the item vertically
  12094. * @Override
  12095. */
  12096. RangeItem.prototype.repositionY = function() {
  12097. var orientation = this.options.orientation,
  12098. box = this.dom.box;
  12099. if (orientation == 'top') {
  12100. box.style.top = this.top + 'px';
  12101. }
  12102. else {
  12103. box.style.top = (this.parent.height - this.top - this.height) + 'px';
  12104. }
  12105. };
  12106. /**
  12107. * Repaint a drag area on the left side of the range when the range is selected
  12108. * @protected
  12109. */
  12110. RangeItem.prototype._repaintDragLeft = function () {
  12111. if (this.selected && this.options.editable.updateTime && !this.dom.dragLeft) {
  12112. // create and show drag area
  12113. var dragLeft = document.createElement('div');
  12114. dragLeft.className = 'drag-left';
  12115. dragLeft.dragLeftItem = this;
  12116. // TODO: this should be redundant?
  12117. Hammer(dragLeft, {
  12118. preventDefault: true
  12119. }).on('drag', function () {
  12120. //console.log('drag left')
  12121. });
  12122. this.dom.box.appendChild(dragLeft);
  12123. this.dom.dragLeft = dragLeft;
  12124. }
  12125. else if (!this.selected && this.dom.dragLeft) {
  12126. // delete drag area
  12127. if (this.dom.dragLeft.parentNode) {
  12128. this.dom.dragLeft.parentNode.removeChild(this.dom.dragLeft);
  12129. }
  12130. this.dom.dragLeft = null;
  12131. }
  12132. };
  12133. /**
  12134. * Repaint a drag area on the right side of the range when the range is selected
  12135. * @protected
  12136. */
  12137. RangeItem.prototype._repaintDragRight = function () {
  12138. if (this.selected && this.options.editable.updateTime && !this.dom.dragRight) {
  12139. // create and show drag area
  12140. var dragRight = document.createElement('div');
  12141. dragRight.className = 'drag-right';
  12142. dragRight.dragRightItem = this;
  12143. // TODO: this should be redundant?
  12144. Hammer(dragRight, {
  12145. preventDefault: true
  12146. }).on('drag', function () {
  12147. //console.log('drag right')
  12148. });
  12149. this.dom.box.appendChild(dragRight);
  12150. this.dom.dragRight = dragRight;
  12151. }
  12152. else if (!this.selected && this.dom.dragRight) {
  12153. // delete drag area
  12154. if (this.dom.dragRight.parentNode) {
  12155. this.dom.dragRight.parentNode.removeChild(this.dom.dragRight);
  12156. }
  12157. this.dom.dragRight = null;
  12158. }
  12159. };
  12160. module.exports = RangeItem;
  12161. /***/ },
  12162. /* 33 */
  12163. /***/ function(module, exports, __webpack_require__) {
  12164. var Emitter = __webpack_require__(50);
  12165. var Hammer = __webpack_require__(42);
  12166. var mousetrap = __webpack_require__(51);
  12167. var util = __webpack_require__(1);
  12168. var hammerUtil = __webpack_require__(44);
  12169. var DataSet = __webpack_require__(3);
  12170. var DataView = __webpack_require__(4);
  12171. var dotparser = __webpack_require__(39);
  12172. var gephiParser = __webpack_require__(40);
  12173. var Groups = __webpack_require__(35);
  12174. var Images = __webpack_require__(36);
  12175. var Node = __webpack_require__(37);
  12176. var Edge = __webpack_require__(34);
  12177. var Popup = __webpack_require__(38);
  12178. var MixinLoader = __webpack_require__(48);
  12179. var Activator = __webpack_require__(49);
  12180. var locales = __webpack_require__(46);
  12181. // Load custom shapes into CanvasRenderingContext2D
  12182. __webpack_require__(47);
  12183. /**
  12184. * @constructor Network
  12185. * Create a network visualization, displaying nodes and edges.
  12186. *
  12187. * @param {Element} container The DOM element in which the Network will
  12188. * be created. Normally a div element.
  12189. * @param {Object} data An object containing parameters
  12190. * {Array} nodes
  12191. * {Array} edges
  12192. * @param {Object} options Options
  12193. */
  12194. function Network (container, data, options) {
  12195. if (!(this instanceof Network)) {
  12196. throw new SyntaxError('Constructor must be called with the new operator');
  12197. }
  12198. this._initializeMixinLoaders();
  12199. // create variables and set default values
  12200. this.containerElement = container;
  12201. // render and calculation settings
  12202. this.renderRefreshRate = 60; // hz (fps)
  12203. this.renderTimestep = 1000 / this.renderRefreshRate; // ms -- saves calculation later on
  12204. this.renderTime = 0.5 * this.renderTimestep; // measured time it takes to render a frame
  12205. this.maxPhysicsTicksPerRender = 3; // max amount of physics ticks per render step.
  12206. this.physicsDiscreteStepsize = 0.50; // discrete stepsize of the simulation
  12207. this.initializing = true;
  12208. this.triggerFunctions = {add:null,edit:null,editEdge:null,connect:null,del:null};
  12209. // set constant values
  12210. this.defaultOptions = {
  12211. nodes: {
  12212. mass: 1,
  12213. radiusMin: 10,
  12214. radiusMax: 30,
  12215. radius: 10,
  12216. shape: 'ellipse',
  12217. image: undefined,
  12218. widthMin: 16, // px
  12219. widthMax: 64, // px
  12220. fontColor: 'black',
  12221. fontSize: 14, // px
  12222. fontFace: 'verdana',
  12223. fontFill: undefined,
  12224. level: -1,
  12225. color: {
  12226. border: '#2B7CE9',
  12227. background: '#97C2FC',
  12228. highlight: {
  12229. border: '#2B7CE9',
  12230. background: '#D2E5FF'
  12231. },
  12232. hover: {
  12233. border: '#2B7CE9',
  12234. background: '#D2E5FF'
  12235. }
  12236. },
  12237. borderColor: '#2B7CE9',
  12238. backgroundColor: '#97C2FC',
  12239. highlightColor: '#D2E5FF',
  12240. group: undefined,
  12241. borderWidth: 1,
  12242. borderWidthSelected: undefined
  12243. },
  12244. edges: {
  12245. widthMin: 1, //
  12246. widthMax: 15,//
  12247. width: 1,
  12248. widthSelectionMultiplier: 2,
  12249. hoverWidth: 1.5,
  12250. style: 'line',
  12251. color: {
  12252. color:'#848484',
  12253. highlight:'#848484',
  12254. hover: '#848484'
  12255. },
  12256. fontColor: '#343434',
  12257. fontSize: 14, // px
  12258. fontFace: 'arial',
  12259. fontFill: 'white',
  12260. arrowScaleFactor: 1,
  12261. dash: {
  12262. length: 10,
  12263. gap: 5,
  12264. altLength: undefined
  12265. },
  12266. inheritColor: "from" // to, from, false, true (== from)
  12267. },
  12268. configurePhysics:false,
  12269. physics: {
  12270. barnesHut: {
  12271. enabled: true,
  12272. theta: 1 / 0.6, // inverted to save time during calculation
  12273. gravitationalConstant: -2000,
  12274. centralGravity: 0.3,
  12275. springLength: 95,
  12276. springConstant: 0.04,
  12277. damping: 0.09
  12278. },
  12279. repulsion: {
  12280. centralGravity: 0.0,
  12281. springLength: 200,
  12282. springConstant: 0.05,
  12283. nodeDistance: 100,
  12284. damping: 0.09
  12285. },
  12286. hierarchicalRepulsion: {
  12287. enabled: false,
  12288. centralGravity: 0.0,
  12289. springLength: 100,
  12290. springConstant: 0.01,
  12291. nodeDistance: 150,
  12292. damping: 0.09
  12293. },
  12294. damping: null,
  12295. centralGravity: null,
  12296. springLength: null,
  12297. springConstant: null
  12298. },
  12299. clustering: { // Per Node in Cluster = PNiC
  12300. enabled: false, // (Boolean) | global on/off switch for clustering.
  12301. initialMaxNodes: 100, // (# nodes) | if the initial amount of nodes is larger than this, we cluster until the total number is less than this threshold.
  12302. 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
  12303. 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
  12304. chainThreshold: 0.4, // (% of all drawn nodes)| maximum percentage of allowed chainnodes (long strings of connected nodes) within all nodes. (lower means less chains).
  12305. clusterEdgeThreshold: 20, // (px) | edge length threshold. if smaller, this node is clustered.
  12306. sectorThreshold: 100, // (# nodes in cluster) | cluster size threshold. If larger, expanding in own sector.
  12307. 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.
  12308. fontSizeMultiplier: 4.0, // (px PNiC) | how much the cluster font size grows per node in cluster (in px).
  12309. maxFontSize: 1000,
  12310. forceAmplification: 0.1, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster).
  12311. distanceAmplification: 0.1, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster).
  12312. edgeGrowth: 20, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
  12313. nodeScaling: {width: 1, // (px PNiC) | growth of the width per node in cluster.
  12314. height: 1, // (px PNiC) | growth of the height per node in cluster.
  12315. radius: 1}, // (px PNiC) | growth of the radius per node in cluster.
  12316. maxNodeSizeIncrements: 600, // (# increments) | max growth of the width per node in cluster.
  12317. activeAreaBoxSize: 80, // (px) | box area around the curser where clusters are popped open.
  12318. clusterLevelDifference: 2
  12319. },
  12320. navigation: {
  12321. enabled: false
  12322. },
  12323. keyboard: {
  12324. enabled: false,
  12325. speed: {x: 10, y: 10, zoom: 0.02}
  12326. },
  12327. dataManipulation: {
  12328. enabled: false,
  12329. initiallyVisible: false
  12330. },
  12331. hierarchicalLayout: {
  12332. enabled:false,
  12333. levelSeparation: 150,
  12334. nodeSpacing: 100,
  12335. direction: "UD", // UD, DU, LR, RL
  12336. layout: "hubsize" // hubsize, directed
  12337. },
  12338. freezeForStabilization: false,
  12339. smoothCurves: {
  12340. enabled: true,
  12341. dynamic: true,
  12342. type: "continuous",
  12343. roundness: 0.5
  12344. },
  12345. dynamicSmoothCurves: true,
  12346. maxVelocity: 30,
  12347. minVelocity: 0.1, // px/s
  12348. stabilize: true, // stabilize before displaying the network
  12349. stabilizationIterations: 1000, // maximum number of iteration to stabilize
  12350. locale: 'en',
  12351. locales: locales,
  12352. tooltip: {
  12353. delay: 300,
  12354. fontColor: 'black',
  12355. fontSize: 14, // px
  12356. fontFace: 'verdana',
  12357. color: {
  12358. border: '#666',
  12359. background: '#FFFFC6'
  12360. }
  12361. },
  12362. dragNetwork: true,
  12363. dragNodes: true,
  12364. zoomable: true,
  12365. hover: false,
  12366. hideEdgesOnDrag: false,
  12367. hideNodesOnDrag: false,
  12368. width : '100%',
  12369. height : '100%',
  12370. selectable: true
  12371. };
  12372. this.constants = util.extend({}, this.defaultOptions);
  12373. this.hoverObj = {nodes:{},edges:{}};
  12374. this.controlNodesActive = false;
  12375. // animation properties
  12376. this.animationSpeed = 1/this.renderRefreshRate;
  12377. this.animationEasingFunction = "easeInOutQuint";
  12378. this.easingTime = 0;
  12379. this.sourceScale = 0;
  12380. this.targetScale = 0;
  12381. this.sourceTranslation = 0;
  12382. this.targetTranslation = 0;
  12383. // Node variables
  12384. var network = this;
  12385. this.groups = new Groups(); // object with groups
  12386. this.images = new Images(); // object with images
  12387. this.images.setOnloadCallback(function () {
  12388. network._redraw();
  12389. });
  12390. // keyboard navigation variables
  12391. this.xIncrement = 0;
  12392. this.yIncrement = 0;
  12393. this.zoomIncrement = 0;
  12394. // loading all the mixins:
  12395. // load the force calculation functions, grouped under the physics system.
  12396. this._loadPhysicsSystem();
  12397. // create a frame and canvas
  12398. this._create();
  12399. // load the sector system. (mandatory, fully integrated with Network)
  12400. this._loadSectorSystem();
  12401. // load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it)
  12402. this._loadClusterSystem();
  12403. // load the selection system. (mandatory, required by Network)
  12404. this._loadSelectionSystem();
  12405. // load the selection system. (mandatory, required by Network)
  12406. this._loadHierarchySystem();
  12407. /*
  12408. * Easing Functions - inspired from http://gizma.com/easing/
  12409. * only considering the t value for the range [0, 1] => [0, 1]
  12410. * https://gist.github.com/gre/1650294
  12411. */
  12412. this.easingFunctions = {
  12413. // no easing, no acceleration
  12414. linear: function (t) {
  12415. return t
  12416. },
  12417. // accelerating from zero velocity
  12418. easeInQuad: function (t) {
  12419. return t * t
  12420. },
  12421. // decelerating to zero velocity
  12422. easeOutQuad: function (t) {
  12423. return t * (2 - t)
  12424. },
  12425. // acceleration until halfway, then deceleration
  12426. easeInOutQuad: function (t) {
  12427. return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t
  12428. },
  12429. // accelerating from zero velocity
  12430. easeInCubic: function (t) {
  12431. return t * t * t
  12432. },
  12433. // decelerating to zero velocity
  12434. easeOutCubic: function (t) {
  12435. return (--t) * t * t + 1
  12436. },
  12437. // acceleration until halfway, then deceleration
  12438. easeInOutCubic: function (t) {
  12439. return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
  12440. },
  12441. // accelerating from zero velocity
  12442. easeInQuart: function (t) {
  12443. return t * t * t * t
  12444. },
  12445. // decelerating to zero velocity
  12446. easeOutQuart: function (t) {
  12447. return 1 - (--t) * t * t * t
  12448. },
  12449. // acceleration until halfway, then deceleration
  12450. easeInOutQuart: function (t) {
  12451. return t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t
  12452. },
  12453. // accelerating from zero velocity
  12454. easeInQuint: function (t) {
  12455. return t * t * t * t * t
  12456. },
  12457. // decelerating to zero velocity
  12458. easeOutQuint: function (t) {
  12459. return 1 + (--t) * t * t * t * t
  12460. },
  12461. // acceleration until halfway, then deceleration
  12462. easeInOutQuint: function (t) {
  12463. return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
  12464. }
  12465. };
  12466. // apply options
  12467. this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
  12468. this._setScale(1);
  12469. this.setOptions(options);
  12470. // other vars
  12471. this.freezeSimulation = false;// freeze the simulation
  12472. this.cachedFunctions = {};
  12473. this.stabilized = false;
  12474. this.stabilizationIterations = null;
  12475. // containers for nodes and edges
  12476. this.calculationNodes = {};
  12477. this.calculationNodeIndices = [];
  12478. this.nodeIndices = []; // array with all the indices of the nodes. Used to speed up forces calculation
  12479. this.nodes = {}; // object with Node objects
  12480. this.edges = {}; // object with Edge objects
  12481. // position and scale variables and objects
  12482. this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw.
  12483. this.canvasBottomRight = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  12484. this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  12485. this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action
  12486. this.scale = 1; // defining the global scale variable in the constructor
  12487. this.previousScale = this.scale; // this is used to check if the zoom operation is zooming in or out
  12488. // datasets or dataviews
  12489. this.nodesData = null; // A DataSet or DataView
  12490. this.edgesData = null; // A DataSet or DataView
  12491. // create event listeners used to subscribe on the DataSets of the nodes and edges
  12492. this.nodesListeners = {
  12493. 'add': function (event, params) {
  12494. network._addNodes(params.items);
  12495. network.start();
  12496. },
  12497. 'update': function (event, params) {
  12498. network._updateNodes(params.items);
  12499. network.start();
  12500. },
  12501. 'remove': function (event, params) {
  12502. network._removeNodes(params.items);
  12503. network.start();
  12504. }
  12505. };
  12506. this.edgesListeners = {
  12507. 'add': function (event, params) {
  12508. network._addEdges(params.items);
  12509. network.start();
  12510. },
  12511. 'update': function (event, params) {
  12512. network._updateEdges(params.items);
  12513. network.start();
  12514. },
  12515. 'remove': function (event, params) {
  12516. network._removeEdges(params.items);
  12517. network.start();
  12518. }
  12519. };
  12520. // properties for the animation
  12521. this.moving = true;
  12522. this.timer = undefined; // Scheduling function. Is definded in this.start();
  12523. // load data (the disable start variable will be the same as the enabled clustering)
  12524. this.setData(data,this.constants.clustering.enabled || this.constants.hierarchicalLayout.enabled);
  12525. // hierarchical layout
  12526. this.initializing = false;
  12527. if (this.constants.hierarchicalLayout.enabled == true) {
  12528. this._setupHierarchicalLayout();
  12529. }
  12530. else {
  12531. // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
  12532. if (this.constants.stabilize == false) {
  12533. this.zoomExtent(undefined, true,this.constants.clustering.enabled);
  12534. }
  12535. }
  12536. // if clustering is disabled, the simulation will have started in the setData function
  12537. if (this.constants.clustering.enabled) {
  12538. this.startWithClustering();
  12539. }
  12540. }
  12541. // Extend Network with an Emitter mixin
  12542. Emitter(Network.prototype);
  12543. /**
  12544. * Get the script path where the vis.js library is located
  12545. *
  12546. * @returns {string | null} path Path or null when not found. Path does not
  12547. * end with a slash.
  12548. * @private
  12549. */
  12550. Network.prototype._getScriptPath = function() {
  12551. var scripts = document.getElementsByTagName( 'script' );
  12552. // find script named vis.js or vis.min.js
  12553. for (var i = 0; i < scripts.length; i++) {
  12554. var src = scripts[i].src;
  12555. var match = src && /\/?vis(.min)?\.js$/.exec(src);
  12556. if (match) {
  12557. // return path without the script name
  12558. return src.substring(0, src.length - match[0].length);
  12559. }
  12560. }
  12561. return null;
  12562. };
  12563. /**
  12564. * Find the center position of the network
  12565. * @private
  12566. */
  12567. Network.prototype._getRange = function() {
  12568. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  12569. for (var nodeId in this.nodes) {
  12570. if (this.nodes.hasOwnProperty(nodeId)) {
  12571. node = this.nodes[nodeId];
  12572. if (minX > (node.x)) {minX = node.x;}
  12573. if (maxX < (node.x)) {maxX = node.x;}
  12574. if (minY > (node.y)) {minY = node.y;}
  12575. if (maxY < (node.y)) {maxY = node.y;}
  12576. }
  12577. }
  12578. if (minX == 1e9 && maxX == -1e9 && minY == 1e9 && maxY == -1e9) {
  12579. minY = 0, maxY = 0, minX = 0, maxX = 0;
  12580. }
  12581. return {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  12582. };
  12583. /**
  12584. * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  12585. * @returns {{x: number, y: number}}
  12586. * @private
  12587. */
  12588. Network.prototype._findCenter = function(range) {
  12589. return {x: (0.5 * (range.maxX + range.minX)),
  12590. y: (0.5 * (range.maxY + range.minY))};
  12591. };
  12592. /**
  12593. * This function zooms out to fit all data on screen based on amount of nodes
  12594. *
  12595. * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
  12596. * @param {Boolean} [disableStart] | If true, start is not called.
  12597. */
  12598. Network.prototype.zoomExtent = function(animationOptions, initialZoom, disableStart) {
  12599. if (initialZoom === undefined) {
  12600. initialZoom = false;
  12601. }
  12602. if (disableStart === undefined) {
  12603. disableStart = false;
  12604. }
  12605. if (animationOptions === undefined) {
  12606. animationOptions = false;
  12607. }
  12608. var range = this._getRange();
  12609. var scale = this._getScale();
  12610. var zoomLevel;
  12611. if (initialZoom == true) {
  12612. var numberOfNodes = this.nodeIndices.length;
  12613. if (this.constants.smoothCurves == true) {
  12614. if (this.constants.clustering.enabled == true &&
  12615. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  12616. 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.
  12617. }
  12618. else {
  12619. zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  12620. }
  12621. }
  12622. else {
  12623. if (this.constants.clustering.enabled == true &&
  12624. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  12625. 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.
  12626. }
  12627. else {
  12628. zoomLevel = 30.5062972 / (numberOfNodes + 19.93597763) + 0.08413486; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  12629. }
  12630. }
  12631. // correct for larger canvasses.
  12632. var factor = Math.min(this.frame.canvas.clientWidth / 600, this.frame.canvas.clientHeight / 600);
  12633. zoomLevel *= factor;
  12634. }
  12635. else {
  12636. var xDistance = (Math.abs(range.minX) + Math.abs(range.maxX)) * 1.1;
  12637. var yDistance = (Math.abs(range.minY) + Math.abs(range.maxY)) * 1.1;
  12638. var xZoomLevel = this.frame.canvas.clientWidth / xDistance;
  12639. var yZoomLevel = this.frame.canvas.clientHeight / yDistance;
  12640. zoomLevel = (xZoomLevel <= yZoomLevel) ? xZoomLevel : yZoomLevel;
  12641. }
  12642. if (zoomLevel > 1.0) {
  12643. zoomLevel = 1.0;
  12644. }
  12645. var center = this._findCenter(range);
  12646. if (disableStart == false) {
  12647. var options = {position: center, scale: zoomLevel, animation: animationOptions};
  12648. this.moveTo(options);
  12649. this.moving = true;
  12650. this.start();
  12651. }
  12652. else {
  12653. center.x *= zoomLevel;
  12654. center.y *= zoomLevel;
  12655. center.x -= 0.5 * this.frame.canvas.clientWidth;
  12656. center.y -= 0.5 * this.frame.canvas.clientHeight;
  12657. this._setScale(zoomLevel);
  12658. this._setTranslation(-center.x,-center.y);
  12659. }
  12660. };
  12661. /**
  12662. * Update the this.nodeIndices with the most recent node index list
  12663. * @private
  12664. */
  12665. Network.prototype._updateNodeIndexList = function() {
  12666. this._clearNodeIndexList();
  12667. for (var idx in this.nodes) {
  12668. if (this.nodes.hasOwnProperty(idx)) {
  12669. this.nodeIndices.push(idx);
  12670. }
  12671. }
  12672. };
  12673. /**
  12674. * Set nodes and edges, and optionally options as well.
  12675. *
  12676. * @param {Object} data Object containing parameters:
  12677. * {Array | DataSet | DataView} [nodes] Array with nodes
  12678. * {Array | DataSet | DataView} [edges] Array with edges
  12679. * {String} [dot] String containing data in DOT format
  12680. * {String} [gephi] String containing data in gephi JSON format
  12681. * {Options} [options] Object with options
  12682. * @param {Boolean} [disableStart] | optional: disable the calling of the start function.
  12683. */
  12684. Network.prototype.setData = function(data, disableStart) {
  12685. if (disableStart === undefined) {
  12686. disableStart = false;
  12687. }
  12688. // we set initializing to true to ensure that the hierarchical layout is not performed until both nodes and edges are added.
  12689. this.initializing = true;
  12690. if (data && data.dot && (data.nodes || data.edges)) {
  12691. throw new SyntaxError('Data must contain either parameter "dot" or ' +
  12692. ' parameter pair "nodes" and "edges", but not both.');
  12693. }
  12694. // set options
  12695. this.setOptions(data && data.options);
  12696. // set all data
  12697. if (data && data.dot) {
  12698. // parse DOT file
  12699. if(data && data.dot) {
  12700. var dotData = dotparser.DOTToGraph(data.dot);
  12701. this.setData(dotData);
  12702. return;
  12703. }
  12704. }
  12705. else if (data && data.gephi) {
  12706. // parse DOT file
  12707. if(data && data.gephi) {
  12708. var gephiData = gephiParser.parseGephi(data.gephi);
  12709. this.setData(gephiData);
  12710. return;
  12711. }
  12712. }
  12713. else {
  12714. this._setNodes(data && data.nodes);
  12715. this._setEdges(data && data.edges);
  12716. }
  12717. this._putDataInSector();
  12718. if (disableStart == false) {
  12719. if (this.constants.hierarchicalLayout.enabled == true) {
  12720. this._resetLevels();
  12721. this._setupHierarchicalLayout();
  12722. }
  12723. else {
  12724. // find a stable position or start animating to a stable position
  12725. if (this.constants.stabilize) {
  12726. this._stabilize();
  12727. }
  12728. }
  12729. this.start();
  12730. }
  12731. this.initializing = false;
  12732. };
  12733. /**
  12734. * Set options
  12735. * @param {Object} options
  12736. */
  12737. Network.prototype.setOptions = function (options) {
  12738. if (options) {
  12739. var prop;
  12740. var fields = ['nodes','edges','smoothCurves','hierarchicalLayout','clustering','navigation','keyboard','dataManipulation',
  12741. 'onAdd','onEdit','onEditEdge','onConnect','onDelete','clickToUse'
  12742. ];
  12743. util.selectiveNotDeepExtend(fields,this.constants, options);
  12744. util.selectiveNotDeepExtend(['color'],this.constants.nodes, options.nodes);
  12745. util.selectiveNotDeepExtend(['color','length'],this.constants.edges, options.edges);
  12746. if (options.physics) {
  12747. util.mergeOptions(this.constants.physics, options.physics,'barnesHut');
  12748. util.mergeOptions(this.constants.physics, options.physics,'repulsion');
  12749. if (options.physics.hierarchicalRepulsion) {
  12750. this.constants.hierarchicalLayout.enabled = true;
  12751. this.constants.physics.hierarchicalRepulsion.enabled = true;
  12752. this.constants.physics.barnesHut.enabled = false;
  12753. for (prop in options.physics.hierarchicalRepulsion) {
  12754. if (options.physics.hierarchicalRepulsion.hasOwnProperty(prop)) {
  12755. this.constants.physics.hierarchicalRepulsion[prop] = options.physics.hierarchicalRepulsion[prop];
  12756. }
  12757. }
  12758. }
  12759. }
  12760. if (options.onAdd) {this.triggerFunctions.add = options.onAdd;}
  12761. if (options.onEdit) {this.triggerFunctions.edit = options.onEdit;}
  12762. if (options.onEditEdge) {this.triggerFunctions.editEdge = options.onEditEdge;}
  12763. if (options.onConnect) {this.triggerFunctions.connect = options.onConnect;}
  12764. if (options.onDelete) {this.triggerFunctions.del = options.onDelete;}
  12765. util.mergeOptions(this.constants, options,'smoothCurves');
  12766. util.mergeOptions(this.constants, options,'hierarchicalLayout');
  12767. util.mergeOptions(this.constants, options,'clustering');
  12768. util.mergeOptions(this.constants, options,'navigation');
  12769. util.mergeOptions(this.constants, options,'keyboard');
  12770. util.mergeOptions(this.constants, options,'dataManipulation');
  12771. if (options.dataManipulation) {
  12772. this.editMode = this.constants.dataManipulation.initiallyVisible;
  12773. }
  12774. // TODO: work out these options and document them
  12775. if (options.edges) {
  12776. if (options.edges.color !== undefined) {
  12777. if (util.isString(options.edges.color)) {
  12778. this.constants.edges.color = {};
  12779. this.constants.edges.color.color = options.edges.color;
  12780. this.constants.edges.color.highlight = options.edges.color;
  12781. this.constants.edges.color.hover = options.edges.color;
  12782. }
  12783. else {
  12784. if (options.edges.color.color !== undefined) {this.constants.edges.color.color = options.edges.color.color;}
  12785. if (options.edges.color.highlight !== undefined) {this.constants.edges.color.highlight = options.edges.color.highlight;}
  12786. if (options.edges.color.hover !== undefined) {this.constants.edges.color.hover = options.edges.color.hover;}
  12787. }
  12788. }
  12789. if (!options.edges.fontColor) {
  12790. if (options.edges.color !== undefined) {
  12791. if (util.isString(options.edges.color)) {this.constants.edges.fontColor = options.edges.color;}
  12792. else if (options.edges.color.color !== undefined) {this.constants.edges.fontColor = options.edges.color.color;}
  12793. }
  12794. }
  12795. }
  12796. if (options.nodes) {
  12797. if (options.nodes.color) {
  12798. var newColorObj = util.parseColor(options.nodes.color);
  12799. this.constants.nodes.color.background = newColorObj.background;
  12800. this.constants.nodes.color.border = newColorObj.border;
  12801. this.constants.nodes.color.highlight.background = newColorObj.highlight.background;
  12802. this.constants.nodes.color.highlight.border = newColorObj.highlight.border;
  12803. this.constants.nodes.color.hover.background = newColorObj.hover.background;
  12804. this.constants.nodes.color.hover.border = newColorObj.hover.border;
  12805. }
  12806. }
  12807. if (options.groups) {
  12808. for (var groupname in options.groups) {
  12809. if (options.groups.hasOwnProperty(groupname)) {
  12810. var group = options.groups[groupname];
  12811. this.groups.add(groupname, group);
  12812. }
  12813. }
  12814. }
  12815. if (options.tooltip) {
  12816. for (prop in options.tooltip) {
  12817. if (options.tooltip.hasOwnProperty(prop)) {
  12818. this.constants.tooltip[prop] = options.tooltip[prop];
  12819. }
  12820. }
  12821. if (options.tooltip.color) {
  12822. this.constants.tooltip.color = util.parseColor(options.tooltip.color);
  12823. }
  12824. }
  12825. if ('clickToUse' in options) {
  12826. if (options.clickToUse) {
  12827. this.activator = new Activator(this.frame);
  12828. this.activator.on('change', this._createKeyBinds.bind(this));
  12829. }
  12830. else {
  12831. if (this.activator) {
  12832. this.activator.destroy();
  12833. delete this.activator;
  12834. }
  12835. }
  12836. }
  12837. if (options.labels) {
  12838. throw new Error('Option "labels" is deprecated. Use options "locale" and "locales" instead.');
  12839. }
  12840. }
  12841. // (Re)loading the mixins that can be enabled or disabled in the options.
  12842. // load the force calculation functions, grouped under the physics system.
  12843. this._loadPhysicsSystem();
  12844. // load the navigation system.
  12845. this._loadNavigationControls();
  12846. // load the data manipulation system
  12847. this._loadManipulationSystem();
  12848. // configure the smooth curves
  12849. this._configureSmoothCurves();
  12850. // bind keys. If disabled, this will not do anything;
  12851. this._createKeyBinds();
  12852. this.setSize(this.constants.width, this.constants.height);
  12853. this.moving = true;
  12854. this.start();
  12855. };
  12856. /**
  12857. * Create the main frame for the Network.
  12858. * This function is executed once when a Network object is created. The frame
  12859. * contains a canvas, and this canvas contains all objects like the axis and
  12860. * nodes.
  12861. * @private
  12862. */
  12863. Network.prototype._create = function () {
  12864. // remove all elements from the container element.
  12865. while (this.containerElement.hasChildNodes()) {
  12866. this.containerElement.removeChild(this.containerElement.firstChild);
  12867. }
  12868. this.frame = document.createElement('div');
  12869. this.frame.className = 'vis network-frame';
  12870. this.frame.style.position = 'relative';
  12871. this.frame.style.overflow = 'hidden';
  12872. // create the network canvas (HTML canvas element)
  12873. this.frame.canvas = document.createElement( 'canvas' );
  12874. this.frame.canvas.style.position = 'relative';
  12875. this.frame.appendChild(this.frame.canvas);
  12876. if (!this.frame.canvas.getContext) {
  12877. var noCanvas = document.createElement( 'DIV' );
  12878. noCanvas.style.color = 'red';
  12879. noCanvas.style.fontWeight = 'bold' ;
  12880. noCanvas.style.padding = '10px';
  12881. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  12882. this.frame.canvas.appendChild(noCanvas);
  12883. }
  12884. var me = this;
  12885. this.drag = {};
  12886. this.pinch = {};
  12887. this.hammer = Hammer(this.frame.canvas, {
  12888. prevent_default: true
  12889. });
  12890. this.hammer.on('tap', me._onTap.bind(me) );
  12891. this.hammer.on('doubletap', me._onDoubleTap.bind(me) );
  12892. this.hammer.on('hold', me._onHold.bind(me) );
  12893. this.hammer.on('pinch', me._onPinch.bind(me) );
  12894. this.hammer.on('touch', me._onTouch.bind(me) );
  12895. this.hammer.on('dragstart', me._onDragStart.bind(me) );
  12896. this.hammer.on('drag', me._onDrag.bind(me) );
  12897. this.hammer.on('dragend', me._onDragEnd.bind(me) );
  12898. this.hammer.on('release', me._onRelease.bind(me) );
  12899. this.hammer.on('mousewheel',me._onMouseWheel.bind(me) );
  12900. this.hammer.on('DOMMouseScroll',me._onMouseWheel.bind(me) ); // for FF
  12901. this.hammer.on('mousemove', me._onMouseMoveTitle.bind(me) );
  12902. // add the frame to the container element
  12903. this.containerElement.appendChild(this.frame);
  12904. };
  12905. /**
  12906. * Binding the keys for keyboard navigation. These functions are defined in the NavigationMixin
  12907. * @private
  12908. */
  12909. Network.prototype._createKeyBinds = function() {
  12910. var me = this;
  12911. this.mousetrap = mousetrap;
  12912. this.mousetrap.reset();
  12913. if (this.constants.keyboard.enabled && this.isActive()) {
  12914. this.mousetrap.bind("up", this._moveUp.bind(me) , "keydown");
  12915. this.mousetrap.bind("up", this._yStopMoving.bind(me), "keyup");
  12916. this.mousetrap.bind("down", this._moveDown.bind(me) , "keydown");
  12917. this.mousetrap.bind("down", this._yStopMoving.bind(me), "keyup");
  12918. this.mousetrap.bind("left", this._moveLeft.bind(me) , "keydown");
  12919. this.mousetrap.bind("left", this._xStopMoving.bind(me), "keyup");
  12920. this.mousetrap.bind("right",this._moveRight.bind(me), "keydown");
  12921. this.mousetrap.bind("right",this._xStopMoving.bind(me), "keyup");
  12922. this.mousetrap.bind("=", this._zoomIn.bind(me), "keydown");
  12923. this.mousetrap.bind("=", this._stopZoom.bind(me), "keyup");
  12924. this.mousetrap.bind("-", this._zoomOut.bind(me), "keydown");
  12925. this.mousetrap.bind("-", this._stopZoom.bind(me), "keyup");
  12926. this.mousetrap.bind("[", this._zoomIn.bind(me), "keydown");
  12927. this.mousetrap.bind("[", this._stopZoom.bind(me), "keyup");
  12928. this.mousetrap.bind("]", this._zoomOut.bind(me), "keydown");
  12929. this.mousetrap.bind("]", this._stopZoom.bind(me), "keyup");
  12930. this.mousetrap.bind("pageup",this._zoomIn.bind(me), "keydown");
  12931. this.mousetrap.bind("pageup",this._stopZoom.bind(me), "keyup");
  12932. this.mousetrap.bind("pagedown",this._zoomOut.bind(me),"keydown");
  12933. this.mousetrap.bind("pagedown",this._stopZoom.bind(me), "keyup");
  12934. }
  12935. if (this.constants.dataManipulation.enabled == true) {
  12936. this.mousetrap.bind("escape",this._createManipulatorBar.bind(me));
  12937. this.mousetrap.bind("del",this._deleteSelected.bind(me));
  12938. }
  12939. };
  12940. /**
  12941. * Get the pointer location from a touch location
  12942. * @param {{pageX: Number, pageY: Number}} touch
  12943. * @return {{x: Number, y: Number}} pointer
  12944. * @private
  12945. */
  12946. Network.prototype._getPointer = function (touch) {
  12947. return {
  12948. x: touch.pageX - util.getAbsoluteLeft(this.frame.canvas),
  12949. y: touch.pageY - util.getAbsoluteTop(this.frame.canvas)
  12950. };
  12951. };
  12952. /**
  12953. * On start of a touch gesture, store the pointer
  12954. * @param event
  12955. * @private
  12956. */
  12957. Network.prototype._onTouch = function (event) {
  12958. this.drag.pointer = this._getPointer(event.gesture.center);
  12959. this.drag.pinched = false;
  12960. this.pinch.scale = this._getScale();
  12961. this._handleTouch(this.drag.pointer);
  12962. };
  12963. /**
  12964. * handle drag start event
  12965. * @private
  12966. */
  12967. Network.prototype._onDragStart = function () {
  12968. this._handleDragStart();
  12969. };
  12970. /**
  12971. * This function is called by _onDragStart.
  12972. * It is separated out because we can then overload it for the datamanipulation system.
  12973. *
  12974. * @private
  12975. */
  12976. Network.prototype._handleDragStart = function() {
  12977. var drag = this.drag;
  12978. var node = this._getNodeAt(drag.pointer);
  12979. // note: drag.pointer is set in _onTouch to get the initial touch location
  12980. drag.dragging = true;
  12981. drag.selection = [];
  12982. drag.translation = this._getTranslation();
  12983. drag.nodeId = null;
  12984. if (node != null) {
  12985. drag.nodeId = node.id;
  12986. // select the clicked node if not yet selected
  12987. if (!node.isSelected()) {
  12988. this._selectObject(node,false);
  12989. }
  12990. // create an array with the selected nodes and their original location and status
  12991. for (var objectId in this.selectionObj.nodes) {
  12992. if (this.selectionObj.nodes.hasOwnProperty(objectId)) {
  12993. var object = this.selectionObj.nodes[objectId];
  12994. var s = {
  12995. id: object.id,
  12996. node: object,
  12997. // store original x, y, xFixed and yFixed, make the node temporarily Fixed
  12998. x: object.x,
  12999. y: object.y,
  13000. xFixed: object.xFixed,
  13001. yFixed: object.yFixed
  13002. };
  13003. object.xFixed = true;
  13004. object.yFixed = true;
  13005. drag.selection.push(s);
  13006. }
  13007. }
  13008. }
  13009. };
  13010. /**
  13011. * handle drag event
  13012. * @private
  13013. */
  13014. Network.prototype._onDrag = function (event) {
  13015. this._handleOnDrag(event)
  13016. };
  13017. /**
  13018. * This function is called by _onDrag.
  13019. * It is separated out because we can then overload it for the datamanipulation system.
  13020. *
  13021. * @private
  13022. */
  13023. Network.prototype._handleOnDrag = function(event) {
  13024. if (this.drag.pinched) {
  13025. return;
  13026. }
  13027. var pointer = this._getPointer(event.gesture.center);
  13028. var me = this;
  13029. var drag = this.drag;
  13030. var selection = drag.selection;
  13031. if (selection && selection.length && this.constants.dragNodes == true) {
  13032. // calculate delta's and new location
  13033. var deltaX = pointer.x - drag.pointer.x;
  13034. var deltaY = pointer.y - drag.pointer.y;
  13035. // update position of all selected nodes
  13036. selection.forEach(function (s) {
  13037. var node = s.node;
  13038. if (!s.xFixed) {
  13039. node.x = me._XconvertDOMtoCanvas(me._XconvertCanvasToDOM(s.x) + deltaX);
  13040. }
  13041. if (!s.yFixed) {
  13042. node.y = me._YconvertDOMtoCanvas(me._YconvertCanvasToDOM(s.y) + deltaY);
  13043. }
  13044. });
  13045. // start _animationStep if not yet running
  13046. if (!this.moving) {
  13047. this.moving = true;
  13048. this.start();
  13049. }
  13050. }
  13051. else {
  13052. if (this.constants.dragNetwork == true) {
  13053. // move the network
  13054. var diffX = pointer.x - this.drag.pointer.x;
  13055. var diffY = pointer.y - this.drag.pointer.y;
  13056. this._setTranslation(
  13057. this.drag.translation.x + diffX,
  13058. this.drag.translation.y + diffY
  13059. );
  13060. this._redraw();
  13061. // this.moving = true;
  13062. // this.start();
  13063. }
  13064. }
  13065. };
  13066. /**
  13067. * handle drag start event
  13068. * @private
  13069. */
  13070. Network.prototype._onDragEnd = function () {
  13071. this.drag.dragging = false;
  13072. var selection = this.drag.selection;
  13073. if (selection && selection.length) {
  13074. selection.forEach(function (s) {
  13075. // restore original xFixed and yFixed
  13076. s.node.xFixed = s.xFixed;
  13077. s.node.yFixed = s.yFixed;
  13078. });
  13079. this.moving = true;
  13080. this.start();
  13081. }
  13082. else {
  13083. this._redraw();
  13084. }
  13085. };
  13086. /**
  13087. * handle tap/click event: select/unselect a node
  13088. * @private
  13089. */
  13090. Network.prototype._onTap = function (event) {
  13091. var pointer = this._getPointer(event.gesture.center);
  13092. this.pointerPosition = pointer;
  13093. this._handleTap(pointer);
  13094. };
  13095. /**
  13096. * handle doubletap event
  13097. * @private
  13098. */
  13099. Network.prototype._onDoubleTap = function (event) {
  13100. var pointer = this._getPointer(event.gesture.center);
  13101. this._handleDoubleTap(pointer);
  13102. };
  13103. /**
  13104. * handle long tap event: multi select nodes
  13105. * @private
  13106. */
  13107. Network.prototype._onHold = function (event) {
  13108. var pointer = this._getPointer(event.gesture.center);
  13109. this.pointerPosition = pointer;
  13110. this._handleOnHold(pointer);
  13111. };
  13112. /**
  13113. * handle the release of the screen
  13114. *
  13115. * @private
  13116. */
  13117. Network.prototype._onRelease = function (event) {
  13118. var pointer = this._getPointer(event.gesture.center);
  13119. this._handleOnRelease(pointer);
  13120. };
  13121. /**
  13122. * Handle pinch event
  13123. * @param event
  13124. * @private
  13125. */
  13126. Network.prototype._onPinch = function (event) {
  13127. var pointer = this._getPointer(event.gesture.center);
  13128. this.drag.pinched = true;
  13129. if (!('scale' in this.pinch)) {
  13130. this.pinch.scale = 1;
  13131. }
  13132. // TODO: enabled moving while pinching?
  13133. var scale = this.pinch.scale * event.gesture.scale;
  13134. this._zoom(scale, pointer)
  13135. };
  13136. /**
  13137. * Zoom the network in or out
  13138. * @param {Number} scale a number around 1, and between 0.01 and 10
  13139. * @param {{x: Number, y: Number}} pointer Position on screen
  13140. * @return {Number} appliedScale scale is limited within the boundaries
  13141. * @private
  13142. */
  13143. Network.prototype._zoom = function(scale, pointer) {
  13144. if (this.constants.zoomable == true) {
  13145. var scaleOld = this._getScale();
  13146. if (scale < 0.00001) {
  13147. scale = 0.00001;
  13148. }
  13149. if (scale > 10) {
  13150. scale = 10;
  13151. }
  13152. var preScaleDragPointer = null;
  13153. if (this.drag !== undefined) {
  13154. if (this.drag.dragging == true) {
  13155. preScaleDragPointer = this.DOMtoCanvas(this.drag.pointer);
  13156. }
  13157. }
  13158. // + this.frame.canvas.clientHeight / 2
  13159. var translation = this._getTranslation();
  13160. var scaleFrac = scale / scaleOld;
  13161. var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
  13162. var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
  13163. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  13164. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  13165. this._setScale(scale);
  13166. this._setTranslation(tx, ty);
  13167. this.updateClustersDefault();
  13168. if (preScaleDragPointer != null) {
  13169. var postScaleDragPointer = this.canvasToDOM(preScaleDragPointer);
  13170. this.drag.pointer.x = postScaleDragPointer.x;
  13171. this.drag.pointer.y = postScaleDragPointer.y;
  13172. }
  13173. this._redraw();
  13174. if (scaleOld < scale) {
  13175. this.emit("zoom", {direction:"+"});
  13176. }
  13177. else {
  13178. this.emit("zoom", {direction:"-"});
  13179. }
  13180. return scale;
  13181. }
  13182. };
  13183. /**
  13184. * Event handler for mouse wheel event, used to zoom the timeline
  13185. * See http://adomas.org/javascript-mouse-wheel/
  13186. * https://github.com/EightMedia/hammer.js/issues/256
  13187. * @param {MouseEvent} event
  13188. * @private
  13189. */
  13190. Network.prototype._onMouseWheel = function(event) {
  13191. // retrieve delta
  13192. var delta = 0;
  13193. if (event.wheelDelta) { /* IE/Opera. */
  13194. delta = event.wheelDelta/120;
  13195. } else if (event.detail) { /* Mozilla case. */
  13196. // In Mozilla, sign of delta is different than in IE.
  13197. // Also, delta is multiple of 3.
  13198. delta = -event.detail/3;
  13199. }
  13200. // If delta is nonzero, handle it.
  13201. // Basically, delta is now positive if wheel was scrolled up,
  13202. // and negative, if wheel was scrolled down.
  13203. if (delta) {
  13204. // calculate the new scale
  13205. var scale = this._getScale();
  13206. var zoom = delta / 10;
  13207. if (delta < 0) {
  13208. zoom = zoom / (1 - zoom);
  13209. }
  13210. scale *= (1 + zoom);
  13211. // calculate the pointer location
  13212. var gesture = hammerUtil.fakeGesture(this, event);
  13213. var pointer = this._getPointer(gesture.center);
  13214. // apply the new scale
  13215. this._zoom(scale, pointer);
  13216. }
  13217. // Prevent default actions caused by mouse wheel.
  13218. event.preventDefault();
  13219. };
  13220. /**
  13221. * Mouse move handler for checking whether the title moves over a node with a title.
  13222. * @param {Event} event
  13223. * @private
  13224. */
  13225. Network.prototype._onMouseMoveTitle = function (event) {
  13226. var gesture = hammerUtil.fakeGesture(this, event);
  13227. var pointer = this._getPointer(gesture.center);
  13228. // check if the previously selected node is still selected
  13229. if (this.popupObj) {
  13230. this._checkHidePopup(pointer);
  13231. }
  13232. // start a timeout that will check if the mouse is positioned above
  13233. // an element
  13234. var me = this;
  13235. var checkShow = function() {
  13236. me._checkShowPopup(pointer);
  13237. };
  13238. if (this.popupTimer) {
  13239. clearInterval(this.popupTimer); // stop any running calculationTimer
  13240. }
  13241. if (!this.drag.dragging) {
  13242. this.popupTimer = setTimeout(checkShow, this.constants.tooltip.delay);
  13243. }
  13244. /**
  13245. * Adding hover highlights
  13246. */
  13247. if (this.constants.hover == true) {
  13248. // removing all hover highlights
  13249. for (var edgeId in this.hoverObj.edges) {
  13250. if (this.hoverObj.edges.hasOwnProperty(edgeId)) {
  13251. this.hoverObj.edges[edgeId].hover = false;
  13252. delete this.hoverObj.edges[edgeId];
  13253. }
  13254. }
  13255. // adding hover highlights
  13256. var obj = this._getNodeAt(pointer);
  13257. if (obj == null) {
  13258. obj = this._getEdgeAt(pointer);
  13259. }
  13260. if (obj != null) {
  13261. this._hoverObject(obj);
  13262. }
  13263. // removing all node hover highlights except for the selected one.
  13264. for (var nodeId in this.hoverObj.nodes) {
  13265. if (this.hoverObj.nodes.hasOwnProperty(nodeId)) {
  13266. if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj == null) {
  13267. this._blurObject(this.hoverObj.nodes[nodeId]);
  13268. delete this.hoverObj.nodes[nodeId];
  13269. }
  13270. }
  13271. }
  13272. this.redraw();
  13273. }
  13274. };
  13275. /**
  13276. * Check if there is an element on the given position in the network
  13277. * (a node or edge). If so, and if this element has a title,
  13278. * show a popup window with its title.
  13279. *
  13280. * @param {{x:Number, y:Number}} pointer
  13281. * @private
  13282. */
  13283. Network.prototype._checkShowPopup = function (pointer) {
  13284. var obj = {
  13285. left: this._XconvertDOMtoCanvas(pointer.x),
  13286. top: this._YconvertDOMtoCanvas(pointer.y),
  13287. right: this._XconvertDOMtoCanvas(pointer.x),
  13288. bottom: this._YconvertDOMtoCanvas(pointer.y)
  13289. };
  13290. var id;
  13291. var lastPopupNode = this.popupObj;
  13292. if (this.popupObj == undefined) {
  13293. // search the nodes for overlap, select the top one in case of multiple nodes
  13294. var nodes = this.nodes;
  13295. for (id in nodes) {
  13296. if (nodes.hasOwnProperty(id)) {
  13297. var node = nodes[id];
  13298. if (node.getTitle() !== undefined && node.isOverlappingWith(obj)) {
  13299. this.popupObj = node;
  13300. break;
  13301. }
  13302. }
  13303. }
  13304. }
  13305. if (this.popupObj === undefined) {
  13306. // search the edges for overlap
  13307. var edges = this.edges;
  13308. for (id in edges) {
  13309. if (edges.hasOwnProperty(id)) {
  13310. var edge = edges[id];
  13311. if (edge.connected && (edge.getTitle() !== undefined) &&
  13312. edge.isOverlappingWith(obj)) {
  13313. this.popupObj = edge;
  13314. break;
  13315. }
  13316. }
  13317. }
  13318. }
  13319. if (this.popupObj) {
  13320. // show popup message window
  13321. if (this.popupObj != lastPopupNode) {
  13322. var me = this;
  13323. if (!me.popup) {
  13324. me.popup = new Popup(me.frame, me.constants.tooltip);
  13325. }
  13326. // adjust a small offset such that the mouse cursor is located in the
  13327. // bottom left location of the popup, and you can easily move over the
  13328. // popup area
  13329. me.popup.setPosition(pointer.x - 3, pointer.y - 3);
  13330. me.popup.setText(me.popupObj.getTitle());
  13331. me.popup.show();
  13332. }
  13333. }
  13334. else {
  13335. if (this.popup) {
  13336. this.popup.hide();
  13337. }
  13338. }
  13339. };
  13340. /**
  13341. * Check if the popup must be hided, which is the case when the mouse is no
  13342. * longer hovering on the object
  13343. * @param {{x:Number, y:Number}} pointer
  13344. * @private
  13345. */
  13346. Network.prototype._checkHidePopup = function (pointer) {
  13347. if (!this.popupObj || !this._getNodeAt(pointer) ) {
  13348. this.popupObj = undefined;
  13349. if (this.popup) {
  13350. this.popup.hide();
  13351. }
  13352. }
  13353. };
  13354. /**
  13355. * Set a new size for the network
  13356. * @param {string} width Width in pixels or percentage (for example '800px'
  13357. * or '50%')
  13358. * @param {string} height Height in pixels or percentage (for example '400px'
  13359. * or '30%')
  13360. */
  13361. Network.prototype.setSize = function(width, height) {
  13362. var emitEvent = false;
  13363. if (width != this.constants.width || height != this.constants.height || this.frame.style.width != width || this.frame.style.height != height) {
  13364. this.frame.style.width = width;
  13365. this.frame.style.height = height;
  13366. this.frame.canvas.style.width = '100%';
  13367. this.frame.canvas.style.height = '100%';
  13368. this.frame.canvas.width = this.frame.canvas.clientWidth;
  13369. this.frame.canvas.height = this.frame.canvas.clientHeight;
  13370. this.constants.width = width;
  13371. this.constants.height = height;
  13372. emitEvent = true;
  13373. }
  13374. else {
  13375. // this would adapt the width of the canvas to the width from 100% if and only if
  13376. // there is a change.
  13377. if (this.frame.canvas.width != this.frame.canvas.clientWidth) {
  13378. this.frame.canvas.width = this.frame.canvas.clientWidth;
  13379. emitEvent = true;
  13380. }
  13381. if (this.frame.canvas.height != this.frame.canvas.clientHeight) {
  13382. this.frame.canvas.height = this.frame.canvas.clientHeight;
  13383. emitEvent = true;
  13384. }
  13385. }
  13386. if (emitEvent == true) {
  13387. this.emit('resize', {width:this.frame.canvas.width,height:this.frame.canvas.height});
  13388. }
  13389. };
  13390. /**
  13391. * Set a data set with nodes for the network
  13392. * @param {Array | DataSet | DataView} nodes The data containing the nodes.
  13393. * @private
  13394. */
  13395. Network.prototype._setNodes = function(nodes) {
  13396. var oldNodesData = this.nodesData;
  13397. if (nodes instanceof DataSet || nodes instanceof DataView) {
  13398. this.nodesData = nodes;
  13399. }
  13400. else if (nodes instanceof Array) {
  13401. this.nodesData = new DataSet();
  13402. this.nodesData.add(nodes);
  13403. }
  13404. else if (!nodes) {
  13405. this.nodesData = new DataSet();
  13406. }
  13407. else {
  13408. throw new TypeError('Array or DataSet expected');
  13409. }
  13410. if (oldNodesData) {
  13411. // unsubscribe from old dataset
  13412. util.forEach(this.nodesListeners, function (callback, event) {
  13413. oldNodesData.off(event, callback);
  13414. });
  13415. }
  13416. // remove drawn nodes
  13417. this.nodes = {};
  13418. if (this.nodesData) {
  13419. // subscribe to new dataset
  13420. var me = this;
  13421. util.forEach(this.nodesListeners, function (callback, event) {
  13422. me.nodesData.on(event, callback);
  13423. });
  13424. // draw all new nodes
  13425. var ids = this.nodesData.getIds();
  13426. this._addNodes(ids);
  13427. }
  13428. this._updateSelection();
  13429. };
  13430. /**
  13431. * Add nodes
  13432. * @param {Number[] | String[]} ids
  13433. * @private
  13434. */
  13435. Network.prototype._addNodes = function(ids) {
  13436. var id;
  13437. for (var i = 0, len = ids.length; i < len; i++) {
  13438. id = ids[i];
  13439. var data = this.nodesData.get(id);
  13440. var node = new Node(data, this.images, this.groups, this.constants);
  13441. this.nodes[id] = node; // note: this may replace an existing node
  13442. if ((node.xFixed == false || node.yFixed == false) && (node.x === null || node.y === null)) {
  13443. var radius = 10 * 0.1*ids.length + 10;
  13444. var angle = 2 * Math.PI * Math.random();
  13445. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  13446. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  13447. }
  13448. this.moving = true;
  13449. }
  13450. this._updateNodeIndexList();
  13451. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  13452. this._resetLevels();
  13453. this._setupHierarchicalLayout();
  13454. }
  13455. this._updateCalculationNodes();
  13456. this._reconnectEdges();
  13457. this._updateValueRange(this.nodes);
  13458. this.updateLabels();
  13459. };
  13460. /**
  13461. * Update existing nodes, or create them when not yet existing
  13462. * @param {Number[] | String[]} ids
  13463. * @private
  13464. */
  13465. Network.prototype._updateNodes = function(ids) {
  13466. var nodes = this.nodes,
  13467. nodesData = this.nodesData;
  13468. for (var i = 0, len = ids.length; i < len; i++) {
  13469. var id = ids[i];
  13470. var node = nodes[id];
  13471. var data = nodesData.get(id);
  13472. if (node) {
  13473. // update node
  13474. node.setProperties(data, this.constants);
  13475. }
  13476. else {
  13477. // create node
  13478. node = new Node(properties, this.images, this.groups, this.constants);
  13479. nodes[id] = node;
  13480. }
  13481. }
  13482. this.moving = true;
  13483. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  13484. this._resetLevels();
  13485. this._setupHierarchicalLayout();
  13486. }
  13487. this._updateNodeIndexList();
  13488. this._reconnectEdges();
  13489. this._updateValueRange(nodes);
  13490. };
  13491. /**
  13492. * Remove existing nodes. If nodes do not exist, the method will just ignore it.
  13493. * @param {Number[] | String[]} ids
  13494. * @private
  13495. */
  13496. Network.prototype._removeNodes = function(ids) {
  13497. var nodes = this.nodes;
  13498. for (var i = 0, len = ids.length; i < len; i++) {
  13499. var id = ids[i];
  13500. delete nodes[id];
  13501. }
  13502. this._updateNodeIndexList();
  13503. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  13504. this._resetLevels();
  13505. this._setupHierarchicalLayout();
  13506. }
  13507. this._updateCalculationNodes();
  13508. this._reconnectEdges();
  13509. this._updateSelection();
  13510. this._updateValueRange(nodes);
  13511. };
  13512. /**
  13513. * Load edges by reading the data table
  13514. * @param {Array | DataSet | DataView} edges The data containing the edges.
  13515. * @private
  13516. * @private
  13517. */
  13518. Network.prototype._setEdges = function(edges) {
  13519. var oldEdgesData = this.edgesData;
  13520. if (edges instanceof DataSet || edges instanceof DataView) {
  13521. this.edgesData = edges;
  13522. }
  13523. else if (edges instanceof Array) {
  13524. this.edgesData = new DataSet();
  13525. this.edgesData.add(edges);
  13526. }
  13527. else if (!edges) {
  13528. this.edgesData = new DataSet();
  13529. }
  13530. else {
  13531. throw new TypeError('Array or DataSet expected');
  13532. }
  13533. if (oldEdgesData) {
  13534. // unsubscribe from old dataset
  13535. util.forEach(this.edgesListeners, function (callback, event) {
  13536. oldEdgesData.off(event, callback);
  13537. });
  13538. }
  13539. // remove drawn edges
  13540. this.edges = {};
  13541. if (this.edgesData) {
  13542. // subscribe to new dataset
  13543. var me = this;
  13544. util.forEach(this.edgesListeners, function (callback, event) {
  13545. me.edgesData.on(event, callback);
  13546. });
  13547. // draw all new nodes
  13548. var ids = this.edgesData.getIds();
  13549. this._addEdges(ids);
  13550. }
  13551. this._reconnectEdges();
  13552. };
  13553. /**
  13554. * Add edges
  13555. * @param {Number[] | String[]} ids
  13556. * @private
  13557. */
  13558. Network.prototype._addEdges = function (ids) {
  13559. var edges = this.edges,
  13560. edgesData = this.edgesData;
  13561. for (var i = 0, len = ids.length; i < len; i++) {
  13562. var id = ids[i];
  13563. var oldEdge = edges[id];
  13564. if (oldEdge) {
  13565. oldEdge.disconnect();
  13566. }
  13567. var data = edgesData.get(id, {"showInternalIds" : true});
  13568. edges[id] = new Edge(data, this, this.constants);
  13569. }
  13570. this.moving = true;
  13571. this._updateValueRange(edges);
  13572. this._createBezierNodes();
  13573. this._updateCalculationNodes();
  13574. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  13575. this._resetLevels();
  13576. this._setupHierarchicalLayout();
  13577. }
  13578. };
  13579. /**
  13580. * Update existing edges, or create them when not yet existing
  13581. * @param {Number[] | String[]} ids
  13582. * @private
  13583. */
  13584. Network.prototype._updateEdges = function (ids) {
  13585. var edges = this.edges,
  13586. edgesData = this.edgesData;
  13587. for (var i = 0, len = ids.length; i < len; i++) {
  13588. var id = ids[i];
  13589. var data = edgesData.get(id);
  13590. var edge = edges[id];
  13591. if (edge) {
  13592. // update edge
  13593. edge.disconnect();
  13594. edge.setProperties(data, this.constants);
  13595. edge.connect();
  13596. }
  13597. else {
  13598. // create edge
  13599. edge = new Edge(data, this, this.constants);
  13600. this.edges[id] = edge;
  13601. }
  13602. }
  13603. this._createBezierNodes();
  13604. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  13605. this._resetLevels();
  13606. this._setupHierarchicalLayout();
  13607. }
  13608. this.moving = true;
  13609. this._updateValueRange(edges);
  13610. };
  13611. /**
  13612. * Remove existing edges. Non existing ids will be ignored
  13613. * @param {Number[] | String[]} ids
  13614. * @private
  13615. */
  13616. Network.prototype._removeEdges = function (ids) {
  13617. var edges = this.edges;
  13618. for (var i = 0, len = ids.length; i < len; i++) {
  13619. var id = ids[i];
  13620. var edge = edges[id];
  13621. if (edge) {
  13622. if (edge.via != null) {
  13623. delete this.sectors['support']['nodes'][edge.via.id];
  13624. }
  13625. edge.disconnect();
  13626. delete edges[id];
  13627. }
  13628. }
  13629. this.moving = true;
  13630. this._updateValueRange(edges);
  13631. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  13632. this._resetLevels();
  13633. this._setupHierarchicalLayout();
  13634. }
  13635. this._updateCalculationNodes();
  13636. };
  13637. /**
  13638. * Reconnect all edges
  13639. * @private
  13640. */
  13641. Network.prototype._reconnectEdges = function() {
  13642. var id,
  13643. nodes = this.nodes,
  13644. edges = this.edges;
  13645. for (id in nodes) {
  13646. if (nodes.hasOwnProperty(id)) {
  13647. nodes[id].edges = [];
  13648. }
  13649. }
  13650. for (id in edges) {
  13651. if (edges.hasOwnProperty(id)) {
  13652. var edge = edges[id];
  13653. edge.from = null;
  13654. edge.to = null;
  13655. edge.connect();
  13656. }
  13657. }
  13658. };
  13659. /**
  13660. * Update the values of all object in the given array according to the current
  13661. * value range of the objects in the array.
  13662. * @param {Object} obj An object containing a set of Edges or Nodes
  13663. * The objects must have a method getValue() and
  13664. * setValueRange(min, max).
  13665. * @private
  13666. */
  13667. Network.prototype._updateValueRange = function(obj) {
  13668. var id;
  13669. // determine the range of the objects
  13670. var valueMin = undefined;
  13671. var valueMax = undefined;
  13672. for (id in obj) {
  13673. if (obj.hasOwnProperty(id)) {
  13674. var value = obj[id].getValue();
  13675. if (value !== undefined) {
  13676. valueMin = (valueMin === undefined) ? value : Math.min(value, valueMin);
  13677. valueMax = (valueMax === undefined) ? value : Math.max(value, valueMax);
  13678. }
  13679. }
  13680. }
  13681. // adjust the range of all objects
  13682. if (valueMin !== undefined && valueMax !== undefined) {
  13683. for (id in obj) {
  13684. if (obj.hasOwnProperty(id)) {
  13685. obj[id].setValueRange(valueMin, valueMax);
  13686. }
  13687. }
  13688. }
  13689. };
  13690. /**
  13691. * Redraw the network with the current data
  13692. * chart will be resized too.
  13693. */
  13694. Network.prototype.redraw = function() {
  13695. this.setSize(this.constants.width, this.constants.height);
  13696. this._redraw();
  13697. };
  13698. /**
  13699. * Redraw the network with the current data
  13700. * @private
  13701. */
  13702. Network.prototype._redraw = function() {
  13703. var ctx = this.frame.canvas.getContext('2d');
  13704. // clear the canvas
  13705. var w = this.frame.canvas.width;
  13706. var h = this.frame.canvas.height;
  13707. ctx.clearRect(0, 0, w, h);
  13708. // set scaling and translation
  13709. ctx.save();
  13710. ctx.translate(this.translation.x, this.translation.y);
  13711. ctx.scale(this.scale, this.scale);
  13712. this.canvasTopLeft = {
  13713. "x": this._XconvertDOMtoCanvas(0),
  13714. "y": this._YconvertDOMtoCanvas(0)
  13715. };
  13716. this.canvasBottomRight = {
  13717. "x": this._XconvertDOMtoCanvas(this.frame.canvas.clientWidth),
  13718. "y": this._YconvertDOMtoCanvas(this.frame.canvas.clientHeight)
  13719. };
  13720. this._doInAllSectors("_drawAllSectorNodes",ctx);
  13721. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideEdgesOnDrag == false) {
  13722. this._doInAllSectors("_drawEdges",ctx);
  13723. }
  13724. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideNodesOnDrag == false) {
  13725. this._doInAllSectors("_drawNodes",ctx,false);
  13726. }
  13727. if (this.controlNodesActive == true) {
  13728. this._doInAllSectors("_drawControlNodes",ctx);
  13729. }
  13730. // this._doInSupportSector("_drawNodes",ctx,true);
  13731. // this._drawTree(ctx,"#F00F0F");
  13732. // restore original scaling and translation
  13733. ctx.restore();
  13734. };
  13735. /**
  13736. * Set the translation of the network
  13737. * @param {Number} offsetX Horizontal offset
  13738. * @param {Number} offsetY Vertical offset
  13739. * @private
  13740. */
  13741. Network.prototype._setTranslation = function(offsetX, offsetY) {
  13742. if (this.translation === undefined) {
  13743. this.translation = {
  13744. x: 0,
  13745. y: 0
  13746. };
  13747. }
  13748. if (offsetX !== undefined) {
  13749. this.translation.x = offsetX;
  13750. }
  13751. if (offsetY !== undefined) {
  13752. this.translation.y = offsetY;
  13753. }
  13754. this.emit('viewChanged');
  13755. };
  13756. /**
  13757. * Get the translation of the network
  13758. * @return {Object} translation An object with parameters x and y, both a number
  13759. * @private
  13760. */
  13761. Network.prototype._getTranslation = function() {
  13762. return {
  13763. x: this.translation.x,
  13764. y: this.translation.y
  13765. };
  13766. };
  13767. /**
  13768. * Scale the network
  13769. * @param {Number} scale Scaling factor 1.0 is unscaled
  13770. * @private
  13771. */
  13772. Network.prototype._setScale = function(scale) {
  13773. this.scale = scale;
  13774. };
  13775. /**
  13776. * Get the current scale of the network
  13777. * @return {Number} scale Scaling factor 1.0 is unscaled
  13778. * @private
  13779. */
  13780. Network.prototype._getScale = function() {
  13781. return this.scale;
  13782. };
  13783. /**
  13784. * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to
  13785. * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  13786. * @param {number} x
  13787. * @returns {number}
  13788. * @private
  13789. */
  13790. Network.prototype._XconvertDOMtoCanvas = function(x) {
  13791. return (x - this.translation.x) / this.scale;
  13792. };
  13793. /**
  13794. * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  13795. * the X coordinate in DOM-space (coordinate point in browser relative to the container div)
  13796. * @param {number} x
  13797. * @returns {number}
  13798. * @private
  13799. */
  13800. Network.prototype._XconvertCanvasToDOM = function(x) {
  13801. return x * this.scale + this.translation.x;
  13802. };
  13803. /**
  13804. * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to
  13805. * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  13806. * @param {number} y
  13807. * @returns {number}
  13808. * @private
  13809. */
  13810. Network.prototype._YconvertDOMtoCanvas = function(y) {
  13811. return (y - this.translation.y) / this.scale;
  13812. };
  13813. /**
  13814. * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  13815. * the Y coordinate in DOM-space (coordinate point in browser relative to the container div)
  13816. * @param {number} y
  13817. * @returns {number}
  13818. * @private
  13819. */
  13820. Network.prototype._YconvertCanvasToDOM = function(y) {
  13821. return y * this.scale + this.translation.y ;
  13822. };
  13823. /**
  13824. *
  13825. * @param {object} pos = {x: number, y: number}
  13826. * @returns {{x: number, y: number}}
  13827. * @constructor
  13828. */
  13829. Network.prototype.canvasToDOM = function (pos) {
  13830. return {x: this._XconvertCanvasToDOM(pos.x), y: this._YconvertCanvasToDOM(pos.y)};
  13831. };
  13832. /**
  13833. *
  13834. * @param {object} pos = {x: number, y: number}
  13835. * @returns {{x: number, y: number}}
  13836. * @constructor
  13837. */
  13838. Network.prototype.DOMtoCanvas = function (pos) {
  13839. return {x: this._XconvertDOMtoCanvas(pos.x), y: this._YconvertDOMtoCanvas(pos.y)};
  13840. };
  13841. /**
  13842. * Redraw all nodes
  13843. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  13844. * @param {CanvasRenderingContext2D} ctx
  13845. * @param {Boolean} [alwaysShow]
  13846. * @private
  13847. */
  13848. Network.prototype._drawNodes = function(ctx,alwaysShow) {
  13849. if (alwaysShow === undefined) {
  13850. alwaysShow = false;
  13851. }
  13852. // first draw the unselected nodes
  13853. var nodes = this.nodes;
  13854. var selected = [];
  13855. for (var id in nodes) {
  13856. if (nodes.hasOwnProperty(id)) {
  13857. nodes[id].setScaleAndPos(this.scale,this.canvasTopLeft,this.canvasBottomRight);
  13858. if (nodes[id].isSelected()) {
  13859. selected.push(id);
  13860. }
  13861. else {
  13862. if (nodes[id].inArea() || alwaysShow) {
  13863. nodes[id].draw(ctx);
  13864. }
  13865. }
  13866. }
  13867. }
  13868. // draw the selected nodes on top
  13869. for (var s = 0, sMax = selected.length; s < sMax; s++) {
  13870. if (nodes[selected[s]].inArea() || alwaysShow) {
  13871. nodes[selected[s]].draw(ctx);
  13872. }
  13873. }
  13874. };
  13875. /**
  13876. * Redraw all edges
  13877. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  13878. * @param {CanvasRenderingContext2D} ctx
  13879. * @private
  13880. */
  13881. Network.prototype._drawEdges = function(ctx) {
  13882. var edges = this.edges;
  13883. for (var id in edges) {
  13884. if (edges.hasOwnProperty(id)) {
  13885. var edge = edges[id];
  13886. edge.setScale(this.scale);
  13887. if (edge.connected) {
  13888. edges[id].draw(ctx);
  13889. }
  13890. }
  13891. }
  13892. };
  13893. /**
  13894. * Redraw all edges
  13895. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  13896. * @param {CanvasRenderingContext2D} ctx
  13897. * @private
  13898. */
  13899. Network.prototype._drawControlNodes = function(ctx) {
  13900. var edges = this.edges;
  13901. for (var id in edges) {
  13902. if (edges.hasOwnProperty(id)) {
  13903. edges[id]._drawControlNodes(ctx);
  13904. }
  13905. }
  13906. };
  13907. /**
  13908. * Find a stable position for all nodes
  13909. * @private
  13910. */
  13911. Network.prototype._stabilize = function() {
  13912. if (this.constants.freezeForStabilization == true) {
  13913. this._freezeDefinedNodes();
  13914. }
  13915. // find stable position
  13916. var count = 0;
  13917. while (this.moving && count < this.constants.stabilizationIterations) {
  13918. this._physicsTick();
  13919. count++;
  13920. }
  13921. this.zoomExtent(undefined,false,true);
  13922. if (this.constants.freezeForStabilization == true) {
  13923. this._restoreFrozenNodes();
  13924. }
  13925. };
  13926. /**
  13927. * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization
  13928. * because only the supportnodes for the smoothCurves have to settle.
  13929. *
  13930. * @private
  13931. */
  13932. Network.prototype._freezeDefinedNodes = function() {
  13933. var nodes = this.nodes;
  13934. for (var id in nodes) {
  13935. if (nodes.hasOwnProperty(id)) {
  13936. if (nodes[id].x != null && nodes[id].y != null) {
  13937. nodes[id].fixedData.x = nodes[id].xFixed;
  13938. nodes[id].fixedData.y = nodes[id].yFixed;
  13939. nodes[id].xFixed = true;
  13940. nodes[id].yFixed = true;
  13941. }
  13942. }
  13943. }
  13944. };
  13945. /**
  13946. * Unfreezes the nodes that have been frozen by _freezeDefinedNodes.
  13947. *
  13948. * @private
  13949. */
  13950. Network.prototype._restoreFrozenNodes = function() {
  13951. var nodes = this.nodes;
  13952. for (var id in nodes) {
  13953. if (nodes.hasOwnProperty(id)) {
  13954. if (nodes[id].fixedData.x != null) {
  13955. nodes[id].xFixed = nodes[id].fixedData.x;
  13956. nodes[id].yFixed = nodes[id].fixedData.y;
  13957. }
  13958. }
  13959. }
  13960. };
  13961. /**
  13962. * Check if any of the nodes is still moving
  13963. * @param {number} vmin the minimum velocity considered as 'moving'
  13964. * @return {boolean} true if moving, false if non of the nodes is moving
  13965. * @private
  13966. */
  13967. Network.prototype._isMoving = function(vmin) {
  13968. var nodes = this.nodes;
  13969. for (var id in nodes) {
  13970. if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vmin)) {
  13971. return true;
  13972. }
  13973. }
  13974. return false;
  13975. };
  13976. /**
  13977. * /**
  13978. * Perform one discrete step for all nodes
  13979. *
  13980. * @private
  13981. */
  13982. Network.prototype._discreteStepNodes = function() {
  13983. var interval = this.physicsDiscreteStepsize;
  13984. var nodes = this.nodes;
  13985. var nodeId;
  13986. var nodesPresent = false;
  13987. if (this.constants.maxVelocity > 0) {
  13988. for (nodeId in nodes) {
  13989. if (nodes.hasOwnProperty(nodeId)) {
  13990. nodes[nodeId].discreteStepLimited(interval, this.constants.maxVelocity);
  13991. nodesPresent = true;
  13992. }
  13993. }
  13994. }
  13995. else {
  13996. for (nodeId in nodes) {
  13997. if (nodes.hasOwnProperty(nodeId)) {
  13998. nodes[nodeId].discreteStep(interval);
  13999. nodesPresent = true;
  14000. }
  14001. }
  14002. }
  14003. if (nodesPresent == true) {
  14004. var vminCorrected = this.constants.minVelocity / Math.max(this.scale,0.05);
  14005. if (vminCorrected > 0.5*this.constants.maxVelocity) {
  14006. return true;
  14007. }
  14008. else {
  14009. return this._isMoving(vminCorrected);
  14010. }
  14011. }
  14012. return false;
  14013. };
  14014. /**
  14015. * A single simulation step (or "tick") in the physics simulation
  14016. *
  14017. * @private
  14018. */
  14019. Network.prototype._physicsTick = function() {
  14020. if (!this.freezeSimulation) {
  14021. if (this.moving == true) {
  14022. var mainMovingStatus = false;
  14023. var supportMovingStatus = false;
  14024. this._doInAllActiveSectors("_initializeForceCalculation");
  14025. var mainMoving = this._doInAllActiveSectors("_discreteStepNodes");
  14026. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  14027. supportMovingStatus = this._doInSupportSector("_discreteStepNodes");
  14028. }
  14029. // gather movement data from all sectors, if one moves, we are NOT stabilzied
  14030. for (var i = 0; i < mainMoving.length; i++) {mainMovingStatus = mainMoving[0] || mainMovingStatus;}
  14031. // determine if the network has stabilzied
  14032. this.moving = mainMovingStatus || supportMovingStatus;
  14033. this.stabilizationIterations++;
  14034. }
  14035. }
  14036. };
  14037. /**
  14038. * This function runs one step of the animation. It calls an x amount of physics ticks and one render tick.
  14039. * It reschedules itself at the beginning of the function
  14040. *
  14041. * @private
  14042. */
  14043. Network.prototype._animationStep = function() {
  14044. // reset the timer so a new scheduled animation step can be set
  14045. this.timer = undefined;
  14046. // handle the keyboad movement
  14047. this._handleNavigation();
  14048. // this schedules a new animation step
  14049. this.start();
  14050. // start the physics simulation
  14051. var calculationTime = Date.now();
  14052. var maxSteps = 1;
  14053. this._physicsTick();
  14054. var timeRequired = Date.now() - calculationTime;
  14055. while (timeRequired < 0.9*(this.renderTimestep - this.renderTime) && maxSteps < this.maxPhysicsTicksPerRender) {
  14056. this._physicsTick();
  14057. timeRequired = Date.now() - calculationTime;
  14058. maxSteps++;
  14059. }
  14060. // start the rendering process
  14061. var renderTime = Date.now();
  14062. this._redraw();
  14063. this.renderTime = Date.now() - renderTime;
  14064. };
  14065. if (typeof window !== 'undefined') {
  14066. window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
  14067. window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  14068. }
  14069. /**
  14070. * Schedule a animation step with the refreshrate interval.
  14071. */
  14072. Network.prototype.start = function() {
  14073. if (this.moving == true || this.xIncrement != 0 || this.yIncrement != 0 || this.zoomIncrement != 0) {
  14074. if (!this.timer) {
  14075. var ua = navigator.userAgent.toLowerCase();
  14076. var requiresTimeout = false;
  14077. if (ua.indexOf('msie 9.0') != -1) { // IE 9
  14078. requiresTimeout = true;
  14079. }
  14080. else if (ua.indexOf('safari') != -1) { // safari
  14081. if (ua.indexOf('chrome') <= -1) {
  14082. requiresTimeout = true;
  14083. }
  14084. }
  14085. if (requiresTimeout == true) {
  14086. this.timer = window.setTimeout(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
  14087. }
  14088. else{
  14089. this.timer = window.requestAnimationFrame(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
  14090. }
  14091. }
  14092. }
  14093. else {
  14094. this._redraw();
  14095. if (this.stabilizationIterations > 0) {
  14096. // trigger the "stabilized" event.
  14097. // The event is triggered on the next tick, to prevent the case that
  14098. // it is fired while initializing the Network, in which case you would not
  14099. // be able to catch it
  14100. var me = this;
  14101. var params = {
  14102. iterations: me.stabilizationIterations
  14103. };
  14104. me.stabilizationIterations = 0;
  14105. setTimeout(function () {
  14106. me.emit("stabilized", params);
  14107. }, 0);
  14108. }
  14109. }
  14110. };
  14111. /**
  14112. * Move the network according to the keyboard presses.
  14113. *
  14114. * @private
  14115. */
  14116. Network.prototype._handleNavigation = function() {
  14117. if (this.xIncrement != 0 || this.yIncrement != 0) {
  14118. var translation = this._getTranslation();
  14119. this._setTranslation(translation.x+this.xIncrement, translation.y+this.yIncrement);
  14120. }
  14121. if (this.zoomIncrement != 0) {
  14122. var center = {
  14123. x: this.frame.canvas.clientWidth / 2,
  14124. y: this.frame.canvas.clientHeight / 2
  14125. };
  14126. this._zoom(this.scale*(1 + this.zoomIncrement), center);
  14127. }
  14128. };
  14129. /**
  14130. * Freeze the _animationStep
  14131. */
  14132. Network.prototype.toggleFreeze = function() {
  14133. if (this.freezeSimulation == false) {
  14134. this.freezeSimulation = true;
  14135. }
  14136. else {
  14137. this.freezeSimulation = false;
  14138. this.start();
  14139. }
  14140. };
  14141. /**
  14142. * This function cleans the support nodes if they are not needed and adds them when they are.
  14143. *
  14144. * @param {boolean} [disableStart]
  14145. * @private
  14146. */
  14147. Network.prototype._configureSmoothCurves = function(disableStart) {
  14148. if (disableStart === undefined) {
  14149. disableStart = true;
  14150. }
  14151. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  14152. this._createBezierNodes();
  14153. // cleanup unused support nodes
  14154. for (var nodeId in this.sectors['support']['nodes']) {
  14155. if (this.sectors['support']['nodes'].hasOwnProperty(nodeId)) {
  14156. if (this.edges[this.sectors['support']['nodes'][nodeId].parentEdgeId] === undefined) {
  14157. delete this.sectors['support']['nodes'][nodeId];
  14158. }
  14159. }
  14160. }
  14161. }
  14162. else {
  14163. // delete the support nodes
  14164. this.sectors['support']['nodes'] = {};
  14165. for (var edgeId in this.edges) {
  14166. if (this.edges.hasOwnProperty(edgeId)) {
  14167. this.edges[edgeId].via = null;
  14168. }
  14169. }
  14170. }
  14171. this._updateCalculationNodes();
  14172. if (!disableStart) {
  14173. this.moving = true;
  14174. this.start();
  14175. }
  14176. };
  14177. /**
  14178. * Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
  14179. * are used for the force calculation.
  14180. *
  14181. * @private
  14182. */
  14183. Network.prototype._createBezierNodes = function() {
  14184. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  14185. for (var edgeId in this.edges) {
  14186. if (this.edges.hasOwnProperty(edgeId)) {
  14187. var edge = this.edges[edgeId];
  14188. if (edge.via == null) {
  14189. var nodeId = "edgeId:".concat(edge.id);
  14190. this.sectors['support']['nodes'][nodeId] = new Node(
  14191. {id:nodeId,
  14192. mass:1,
  14193. shape:'circle',
  14194. image:"",
  14195. internalMultiplier:1
  14196. },{},{},this.constants);
  14197. edge.via = this.sectors['support']['nodes'][nodeId];
  14198. edge.via.parentEdgeId = edge.id;
  14199. edge.positionBezierNode();
  14200. }
  14201. }
  14202. }
  14203. }
  14204. };
  14205. /**
  14206. * load the functions that load the mixins into the prototype.
  14207. *
  14208. * @private
  14209. */
  14210. Network.prototype._initializeMixinLoaders = function () {
  14211. for (var mixin in MixinLoader) {
  14212. if (MixinLoader.hasOwnProperty(mixin)) {
  14213. Network.prototype[mixin] = MixinLoader[mixin];
  14214. }
  14215. }
  14216. };
  14217. /**
  14218. * Load the XY positions of the nodes into the dataset.
  14219. */
  14220. Network.prototype.storePosition = function() {
  14221. var dataArray = [];
  14222. for (var nodeId in this.nodes) {
  14223. if (this.nodes.hasOwnProperty(nodeId)) {
  14224. var node = this.nodes[nodeId];
  14225. var allowedToMoveX = !this.nodes.xFixed;
  14226. var allowedToMoveY = !this.nodes.yFixed;
  14227. if (this.nodesData._data[nodeId].x != Math.round(node.x) || this.nodesData._data[nodeId].y != Math.round(node.y)) {
  14228. dataArray.push({id:nodeId,x:Math.round(node.x),y:Math.round(node.y),allowedToMoveX:allowedToMoveX,allowedToMoveY:allowedToMoveY});
  14229. }
  14230. }
  14231. }
  14232. this.nodesData.update(dataArray);
  14233. };
  14234. /**
  14235. * Center a node in view.
  14236. *
  14237. * @param {Number} nodeId
  14238. * @param {Number} [options]
  14239. */
  14240. Network.prototype.focusOnNode = function (nodeId, options) {
  14241. if (this.nodes.hasOwnProperty(nodeId)) {
  14242. if (options === undefined) {
  14243. options = {};
  14244. }
  14245. var nodePosition = {x: this.nodes[nodeId].x, y: this.nodes[nodeId].y};
  14246. options.position = nodePosition;
  14247. this.moveTo(options)
  14248. }
  14249. else {
  14250. console.log("This nodeId cannot be found.");
  14251. }
  14252. };
  14253. /**
  14254. *
  14255. * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels
  14256. * | options.scale = Number // scale to move to
  14257. * | options.position = {x:Number, y:Number} // position to move to
  14258. * | options.animation = {duration:Number, easingFunction:String} || Boolean // position to move to
  14259. */
  14260. Network.prototype.moveTo = function (options) {
  14261. if (options === undefined) {
  14262. options = {};
  14263. return;
  14264. }
  14265. if (options.offset === undefined) {options.offset = {x: 0, y: 0}; }
  14266. if (options.offset.x === undefined) {options.offset.x = 0; }
  14267. if (options.offset.y === undefined) {options.offset.y = 0; }
  14268. if (options.scale === undefined) {options.scale = this._getScale(); }
  14269. if (options.position === undefined) {options.position = this._getTranslation();}
  14270. if (options.animation === undefined) {options.animation = {duration:0}; }
  14271. if (options.animation === false ) {options.animation = {duration:0}; }
  14272. if (options.animation === true ) {options.animation = {}; }
  14273. if (options.animation.duration === undefined) {options.animation.duration = 1000; } // default duration
  14274. if (options.animation.easingFunction === undefined) {options.animation.easingFunction = "easeInOutQuad"; } // default easing function
  14275. this.animateView(options);
  14276. };
  14277. /**
  14278. *
  14279. * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels
  14280. * | options.time = Number // animation time in milliseconds
  14281. * | options.scale = Number // scale to animate to
  14282. * | options.position = {x:Number, y:Number} // position to animate to
  14283. * | options.easingFunction = String // linear, easeInQuad, easeOutQuad, easeInOutQuad,
  14284. * // easeInCubic, easeOutCubic, easeInOutCubic,
  14285. * // easeInQuart, easeOutQuart, easeInOutQuart,
  14286. * // easeInQuint, easeOutQuint, easeInOutQuint
  14287. */
  14288. Network.prototype.animateView = function (options) {
  14289. if (options === undefined) {
  14290. options = {};
  14291. return;
  14292. }
  14293. // forcefully complete the old animation if it was still running
  14294. if (this.easingTime != 0) {
  14295. this._transitionRedraw(1); // by setting easingtime to 1, we finish the animation.
  14296. }
  14297. this.sourceScale = this._getScale();
  14298. this.sourceTranslation = this._getTranslation();
  14299. this.targetScale = options.scale;
  14300. // set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw
  14301. // but at least then we'll have the target transition
  14302. this._setScale(this.targetScale);
  14303. var viewCenter = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  14304. var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
  14305. x: viewCenter.x - options.position.x,
  14306. y: viewCenter.y - options.position.y
  14307. };
  14308. this.targetTranslation = {
  14309. x: this.sourceTranslation.x + distanceFromCenter.x * this.targetScale + options.offset.x,
  14310. y: this.sourceTranslation.y + distanceFromCenter.y * this.targetScale + options.offset.y
  14311. };
  14312. // if the time is set to 0, don't do an animation
  14313. if (options.animation.duration == 0) {
  14314. this._setScale(this.targetScale);
  14315. this._setTranslation(this.targetTranslation.x, this.targetTranslation.y);
  14316. this._redraw();
  14317. }
  14318. else {
  14319. this.animationSpeed = 1 / (this.renderRefreshRate * options.animation.duration * 0.001) || 1 / this.renderRefreshRate;
  14320. this.animationEasingFunction = options.animation.easingFunction;
  14321. this._classicRedraw = this._redraw;
  14322. this._redraw = this._transitionRedraw;
  14323. this.moving = true;
  14324. this.start();
  14325. }
  14326. };
  14327. /**
  14328. *
  14329. * @param easingTime
  14330. * @private
  14331. */
  14332. Network.prototype._transitionRedraw = function (easingTime) {
  14333. this.easingTime = easingTime || this.easingTime + this.animationSpeed;
  14334. this.easingTime += this.animationSpeed;
  14335. var progress = this.easingFunctions[this.animationEasingFunction](this.easingTime);
  14336. this._setScale(this.sourceScale + (this.targetScale - this.sourceScale) * progress);
  14337. this._setTranslation(
  14338. this.sourceTranslation.x + (this.targetTranslation.x - this.sourceTranslation.x) * progress,
  14339. this.sourceTranslation.y + (this.targetTranslation.y - this.sourceTranslation.y) * progress
  14340. );
  14341. this._classicRedraw();
  14342. this.moving = true;
  14343. // cleanup
  14344. if (this.easingTime >= 1.0) {
  14345. this.easingTime = 0;
  14346. this._redraw = this._classicRedraw;
  14347. this.emit("animationFinished");
  14348. }
  14349. };
  14350. Network.prototype._classicRedraw = function () {
  14351. // placeholder function to be overloaded by animations;
  14352. };
  14353. /**
  14354. * Returns true when the Network is active.
  14355. * @returns {boolean}
  14356. */
  14357. Network.prototype.isActive = function () {
  14358. return !this.activator || this.activator.active;
  14359. };
  14360. /**
  14361. * Sets the scale
  14362. * @returns {Number}
  14363. */
  14364. Network.prototype.setScale = function () {
  14365. return this._setScale();
  14366. };
  14367. /**
  14368. * Returns the scale
  14369. * @returns {Number}
  14370. */
  14371. Network.prototype.getScale = function () {
  14372. return this._getScale();
  14373. };
  14374. module.exports = Network;
  14375. /***/ },
  14376. /* 34 */
  14377. /***/ function(module, exports, __webpack_require__) {
  14378. var util = __webpack_require__(1);
  14379. var Node = __webpack_require__(37);
  14380. /**
  14381. * @class Edge
  14382. *
  14383. * A edge connects two nodes
  14384. * @param {Object} properties Object with properties. Must contain
  14385. * At least properties from and to.
  14386. * Available properties: from (number),
  14387. * to (number), label (string, color (string),
  14388. * width (number), style (string),
  14389. * length (number), title (string)
  14390. * @param {Network} network A Network object, used to find and edge to
  14391. * nodes.
  14392. * @param {Object} constants An object with default values for
  14393. * example for the color
  14394. */
  14395. function Edge (properties, network, networkConstants) {
  14396. if (!network) {
  14397. throw "No network provided";
  14398. }
  14399. var fields = ['edges','physics'];
  14400. var constants = util.selectiveBridgeObject(fields,networkConstants);
  14401. this.options = constants.edges;
  14402. this.physics = constants.physics;
  14403. this.options['smoothCurves'] = networkConstants['smoothCurves'];
  14404. this.network = network;
  14405. // initialize variables
  14406. this.id = undefined;
  14407. this.fromId = undefined;
  14408. this.toId = undefined;
  14409. this.title = undefined;
  14410. this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
  14411. this.value = undefined;
  14412. this.selected = false;
  14413. this.hover = false;
  14414. this.from = null; // a node
  14415. this.to = null; // a node
  14416. this.via = null; // a temp node
  14417. // we use this to be able to reconnect the edge to a cluster if its node is put into a cluster
  14418. // by storing the original information we can revert to the original connection when the cluser is opened.
  14419. this.originalFromId = [];
  14420. this.originalToId = [];
  14421. this.connected = false;
  14422. this.widthFixed = false;
  14423. this.lengthFixed = false;
  14424. this.setProperties(properties);
  14425. this.controlNodesEnabled = false;
  14426. this.controlNodes = {from:null, to:null, positions:{}};
  14427. this.connectedNode = null;
  14428. }
  14429. /**
  14430. * Set or overwrite properties for the edge
  14431. * @param {Object} properties an object with properties
  14432. * @param {Object} constants and object with default, global properties
  14433. */
  14434. Edge.prototype.setProperties = function(properties) {
  14435. if (!properties) {
  14436. return;
  14437. }
  14438. var fields = ['style','fontSize','fontFace','fontColor','fontFill','width',
  14439. 'widthSelectionMultiplier','hoverWidth','arrowScaleFactor','dash','inheritColor'
  14440. ];
  14441. util.selectiveDeepExtend(fields, this.options, properties);
  14442. if (properties.from !== undefined) {this.fromId = properties.from;}
  14443. if (properties.to !== undefined) {this.toId = properties.to;}
  14444. if (properties.id !== undefined) {this.id = properties.id;}
  14445. if (properties.label !== undefined) {this.label = properties.label;}
  14446. if (properties.title !== undefined) {this.title = properties.title;}
  14447. if (properties.value !== undefined) {this.value = properties.value;}
  14448. if (properties.length !== undefined) {this.physics.springLength = properties.length;}
  14449. if (properties.color !== undefined) {
  14450. this.options.inheritColor = false;
  14451. if (util.isString(properties.color)) {
  14452. this.options.color.color = properties.color;
  14453. this.options.color.highlight = properties.color;
  14454. }
  14455. else {
  14456. if (properties.color.color !== undefined) {this.options.color.color = properties.color.color;}
  14457. if (properties.color.highlight !== undefined) {this.options.color.highlight = properties.color.highlight;}
  14458. if (properties.color.hover !== undefined) {this.options.color.hover = properties.color.hover;}
  14459. }
  14460. }
  14461. // A node is connected when it has a from and to node.
  14462. this.connect();
  14463. this.widthFixed = this.widthFixed || (properties.width !== undefined);
  14464. this.lengthFixed = this.lengthFixed || (properties.length !== undefined);
  14465. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  14466. // set draw method based on style
  14467. switch (this.options.style) {
  14468. case 'line': this.draw = this._drawLine; break;
  14469. case 'arrow': this.draw = this._drawArrow; break;
  14470. case 'arrow-center': this.draw = this._drawArrowCenter; break;
  14471. case 'dash-line': this.draw = this._drawDashLine; break;
  14472. default: this.draw = this._drawLine; break;
  14473. }
  14474. };
  14475. /**
  14476. * Connect an edge to its nodes
  14477. */
  14478. Edge.prototype.connect = function () {
  14479. this.disconnect();
  14480. this.from = this.network.nodes[this.fromId] || null;
  14481. this.to = this.network.nodes[this.toId] || null;
  14482. this.connected = (this.from && this.to);
  14483. if (this.connected) {
  14484. this.from.attachEdge(this);
  14485. this.to.attachEdge(this);
  14486. }
  14487. else {
  14488. if (this.from) {
  14489. this.from.detachEdge(this);
  14490. }
  14491. if (this.to) {
  14492. this.to.detachEdge(this);
  14493. }
  14494. }
  14495. };
  14496. /**
  14497. * Disconnect an edge from its nodes
  14498. */
  14499. Edge.prototype.disconnect = function () {
  14500. if (this.from) {
  14501. this.from.detachEdge(this);
  14502. this.from = null;
  14503. }
  14504. if (this.to) {
  14505. this.to.detachEdge(this);
  14506. this.to = null;
  14507. }
  14508. this.connected = false;
  14509. };
  14510. /**
  14511. * get the title of this edge.
  14512. * @return {string} title The title of the edge, or undefined when no title
  14513. * has been set.
  14514. */
  14515. Edge.prototype.getTitle = function() {
  14516. return typeof this.title === "function" ? this.title() : this.title;
  14517. };
  14518. /**
  14519. * Retrieve the value of the edge. Can be undefined
  14520. * @return {Number} value
  14521. */
  14522. Edge.prototype.getValue = function() {
  14523. return this.value;
  14524. };
  14525. /**
  14526. * Adjust the value range of the edge. The edge will adjust it's width
  14527. * based on its value.
  14528. * @param {Number} min
  14529. * @param {Number} max
  14530. */
  14531. Edge.prototype.setValueRange = function(min, max) {
  14532. if (!this.widthFixed && this.value !== undefined) {
  14533. var scale = (this.options.widthMax - this.options.widthMin) / (max - min);
  14534. this.options.width= (this.value - min) * scale + this.options.widthMin;
  14535. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  14536. }
  14537. };
  14538. /**
  14539. * Redraw a edge
  14540. * Draw this edge in the given canvas
  14541. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  14542. * @param {CanvasRenderingContext2D} ctx
  14543. */
  14544. Edge.prototype.draw = function(ctx) {
  14545. throw "Method draw not initialized in edge";
  14546. };
  14547. /**
  14548. * Check if this object is overlapping with the provided object
  14549. * @param {Object} obj an object with parameters left, top
  14550. * @return {boolean} True if location is located on the edge
  14551. */
  14552. Edge.prototype.isOverlappingWith = function(obj) {
  14553. if (this.connected) {
  14554. var distMax = 10;
  14555. var xFrom = this.from.x;
  14556. var yFrom = this.from.y;
  14557. var xTo = this.to.x;
  14558. var yTo = this.to.y;
  14559. var xObj = obj.left;
  14560. var yObj = obj.top;
  14561. var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  14562. return (dist < distMax);
  14563. }
  14564. else {
  14565. return false
  14566. }
  14567. };
  14568. Edge.prototype._getColor = function() {
  14569. var colorObj = this.options.color;
  14570. if (this.options.inheritColor == "to") {
  14571. colorObj = {
  14572. highlight: this.to.options.color.highlight.border,
  14573. hover: this.to.options.color.hover.border,
  14574. color: this.to.options.color.border
  14575. };
  14576. }
  14577. else if (this.options.inheritColor == "from" || this.options.inheritColor == true) {
  14578. colorObj = {
  14579. highlight: this.from.options.color.highlight.border,
  14580. hover: this.from.options.color.hover.border,
  14581. color: this.from.options.color.border
  14582. };
  14583. }
  14584. if (this.selected == true) {return colorObj.highlight;}
  14585. else if (this.hover == true) {return colorObj.hover;}
  14586. else {return colorObj.color;}
  14587. }
  14588. /**
  14589. * Redraw a edge as a line
  14590. * Draw this edge in the given canvas
  14591. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  14592. * @param {CanvasRenderingContext2D} ctx
  14593. * @private
  14594. */
  14595. Edge.prototype._drawLine = function(ctx) {
  14596. // set style
  14597. ctx.strokeStyle = this._getColor();
  14598. ctx.lineWidth = this._getLineWidth();
  14599. if (this.from != this.to) {
  14600. // draw line
  14601. var via = this._line(ctx);
  14602. // draw label
  14603. var point;
  14604. if (this.label) {
  14605. if (this.options.smoothCurves.enabled == true && via != null) {
  14606. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  14607. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  14608. point = {x:midpointX, y:midpointY};
  14609. }
  14610. else {
  14611. point = this._pointOnLine(0.5);
  14612. }
  14613. this._label(ctx, this.label, point.x, point.y);
  14614. }
  14615. }
  14616. else {
  14617. var x, y;
  14618. var radius = this.physics.springLength / 4;
  14619. var node = this.from;
  14620. if (!node.width) {
  14621. node.resize(ctx);
  14622. }
  14623. if (node.width > node.height) {
  14624. x = node.x + node.width / 2;
  14625. y = node.y - radius;
  14626. }
  14627. else {
  14628. x = node.x + radius;
  14629. y = node.y - node.height / 2;
  14630. }
  14631. this._circle(ctx, x, y, radius);
  14632. point = this._pointOnCircle(x, y, radius, 0.5);
  14633. this._label(ctx, this.label, point.x, point.y);
  14634. }
  14635. };
  14636. /**
  14637. * Get the line width of the edge. Depends on width and whether one of the
  14638. * connected nodes is selected.
  14639. * @return {Number} width
  14640. * @private
  14641. */
  14642. Edge.prototype._getLineWidth = function() {
  14643. if (this.selected == true) {
  14644. return Math.max(Math.min(this.widthSelected, this.options.widthMax), 0.3*this.networkScaleInv);
  14645. }
  14646. else {
  14647. if (this.hover == true) {
  14648. return Math.max(Math.min(this.options.hoverWidth, this.options.widthMax), 0.3*this.networkScaleInv);
  14649. }
  14650. else {
  14651. return Math.max(this.options.width, 0.3*this.networkScaleInv);
  14652. }
  14653. }
  14654. };
  14655. Edge.prototype._getViaCoordinates = function () {
  14656. var xVia = null;
  14657. var yVia = null;
  14658. var factor = this.options.smoothCurves.roundness;
  14659. var type = this.options.smoothCurves.type;
  14660. var dx = Math.abs(this.from.x - this.to.x);
  14661. var dy = Math.abs(this.from.y - this.to.y);
  14662. if (type == 'discrete' || type == 'diagonalCross') {
  14663. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  14664. if (this.from.y > this.to.y) {
  14665. if (this.from.x < this.to.x) {
  14666. xVia = this.from.x + factor * dy;
  14667. yVia = this.from.y - factor * dy;
  14668. }
  14669. else if (this.from.x > this.to.x) {
  14670. xVia = this.from.x - factor * dy;
  14671. yVia = this.from.y - factor * dy;
  14672. }
  14673. }
  14674. else if (this.from.y < this.to.y) {
  14675. if (this.from.x < this.to.x) {
  14676. xVia = this.from.x + factor * dy;
  14677. yVia = this.from.y + factor * dy;
  14678. }
  14679. else if (this.from.x > this.to.x) {
  14680. xVia = this.from.x - factor * dy;
  14681. yVia = this.from.y + factor * dy;
  14682. }
  14683. }
  14684. if (type == "discrete") {
  14685. xVia = dx < factor * dy ? this.from.x : xVia;
  14686. }
  14687. }
  14688. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  14689. if (this.from.y > this.to.y) {
  14690. if (this.from.x < this.to.x) {
  14691. xVia = this.from.x + factor * dx;
  14692. yVia = this.from.y - factor * dx;
  14693. }
  14694. else if (this.from.x > this.to.x) {
  14695. xVia = this.from.x - factor * dx;
  14696. yVia = this.from.y - factor * dx;
  14697. }
  14698. }
  14699. else if (this.from.y < this.to.y) {
  14700. if (this.from.x < this.to.x) {
  14701. xVia = this.from.x + factor * dx;
  14702. yVia = this.from.y + factor * dx;
  14703. }
  14704. else if (this.from.x > this.to.x) {
  14705. xVia = this.from.x - factor * dx;
  14706. yVia = this.from.y + factor * dx;
  14707. }
  14708. }
  14709. if (type == "discrete") {
  14710. yVia = dy < factor * dx ? this.from.y : yVia;
  14711. }
  14712. }
  14713. }
  14714. else if (type == "straightCross") {
  14715. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { // up - down
  14716. xVia = this.from.x;
  14717. if (this.from.y < this.to.y) {
  14718. yVia = this.to.y - (1-factor) * dy;
  14719. }
  14720. else {
  14721. yVia = this.to.y + (1-factor) * dy;
  14722. }
  14723. }
  14724. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { // left - right
  14725. if (this.from.x < this.to.x) {
  14726. xVia = this.to.x - (1-factor) * dx;
  14727. }
  14728. else {
  14729. xVia = this.to.x + (1-factor) * dx;
  14730. }
  14731. yVia = this.from.y;
  14732. }
  14733. }
  14734. else if (type == 'horizontal') {
  14735. if (this.from.x < this.to.x) {
  14736. xVia = this.to.x - (1-factor) * dx;
  14737. }
  14738. else {
  14739. xVia = this.to.x + (1-factor) * dx;
  14740. }
  14741. yVia = this.from.y;
  14742. }
  14743. else if (type == 'vertical') {
  14744. xVia = this.from.x;
  14745. if (this.from.y < this.to.y) {
  14746. yVia = this.to.y - (1-factor) * dy;
  14747. }
  14748. else {
  14749. yVia = this.to.y + (1-factor) * dy;
  14750. }
  14751. }
  14752. else { // continuous
  14753. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  14754. if (this.from.y > this.to.y) {
  14755. if (this.from.x < this.to.x) {
  14756. // console.log(1)
  14757. xVia = this.from.x + factor * dy;
  14758. yVia = this.from.y - factor * dy;
  14759. xVia = this.to.x < xVia ? this.to.x : xVia;
  14760. }
  14761. else if (this.from.x > this.to.x) {
  14762. // console.log(2)
  14763. xVia = this.from.x - factor * dy;
  14764. yVia = this.from.y - factor * dy;
  14765. xVia = this.to.x > xVia ? this.to.x :xVia;
  14766. }
  14767. }
  14768. else if (this.from.y < this.to.y) {
  14769. if (this.from.x < this.to.x) {
  14770. // console.log(3)
  14771. xVia = this.from.x + factor * dy;
  14772. yVia = this.from.y + factor * dy;
  14773. xVia = this.to.x < xVia ? this.to.x : xVia;
  14774. }
  14775. else if (this.from.x > this.to.x) {
  14776. // console.log(4, this.from.x, this.to.x)
  14777. xVia = this.from.x - factor * dy;
  14778. yVia = this.from.y + factor * dy;
  14779. xVia = this.to.x > xVia ? this.to.x : xVia;
  14780. }
  14781. }
  14782. }
  14783. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  14784. if (this.from.y > this.to.y) {
  14785. if (this.from.x < this.to.x) {
  14786. // console.log(5)
  14787. xVia = this.from.x + factor * dx;
  14788. yVia = this.from.y - factor * dx;
  14789. yVia = this.to.y > yVia ? this.to.y : yVia;
  14790. }
  14791. else if (this.from.x > this.to.x) {
  14792. // console.log(6)
  14793. xVia = this.from.x - factor * dx;
  14794. yVia = this.from.y - factor * dx;
  14795. yVia = this.to.y > yVia ? this.to.y : yVia;
  14796. }
  14797. }
  14798. else if (this.from.y < this.to.y) {
  14799. if (this.from.x < this.to.x) {
  14800. // console.log(7)
  14801. xVia = this.from.x + factor * dx;
  14802. yVia = this.from.y + factor * dx;
  14803. yVia = this.to.y < yVia ? this.to.y : yVia;
  14804. }
  14805. else if (this.from.x > this.to.x) {
  14806. // console.log(8)
  14807. xVia = this.from.x - factor * dx;
  14808. yVia = this.from.y + factor * dx;
  14809. yVia = this.to.y < yVia ? this.to.y : yVia;
  14810. }
  14811. }
  14812. }
  14813. }
  14814. return {x:xVia, y:yVia};
  14815. }
  14816. /**
  14817. * Draw a line between two nodes
  14818. * @param {CanvasRenderingContext2D} ctx
  14819. * @private
  14820. */
  14821. Edge.prototype._line = function (ctx) {
  14822. // draw a straight line
  14823. ctx.beginPath();
  14824. ctx.moveTo(this.from.x, this.from.y);
  14825. if (this.options.smoothCurves.enabled == true) {
  14826. if (this.options.smoothCurves.dynamic == false) {
  14827. var via = this._getViaCoordinates();
  14828. if (via.x == null) {
  14829. ctx.lineTo(this.to.x, this.to.y);
  14830. ctx.stroke();
  14831. return null;
  14832. }
  14833. else {
  14834. // this.via.x = via.x;
  14835. // this.via.y = via.y;
  14836. ctx.quadraticCurveTo(via.x,via.y,this.to.x, this.to.y);
  14837. ctx.stroke();
  14838. return via;
  14839. }
  14840. }
  14841. else {
  14842. ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
  14843. ctx.stroke();
  14844. return this.via;
  14845. }
  14846. }
  14847. else {
  14848. ctx.lineTo(this.to.x, this.to.y);
  14849. ctx.stroke();
  14850. return null;
  14851. }
  14852. };
  14853. /**
  14854. * Draw a line from a node to itself, a circle
  14855. * @param {CanvasRenderingContext2D} ctx
  14856. * @param {Number} x
  14857. * @param {Number} y
  14858. * @param {Number} radius
  14859. * @private
  14860. */
  14861. Edge.prototype._circle = function (ctx, x, y, radius) {
  14862. // draw a circle
  14863. ctx.beginPath();
  14864. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  14865. ctx.stroke();
  14866. };
  14867. /**
  14868. * Draw label with white background and with the middle at (x, y)
  14869. * @param {CanvasRenderingContext2D} ctx
  14870. * @param {String} text
  14871. * @param {Number} x
  14872. * @param {Number} y
  14873. * @private
  14874. */
  14875. Edge.prototype._label = function (ctx, text, x, y) {
  14876. if (text) {
  14877. // TODO: cache the calculated size
  14878. ctx.font = ((this.from.selected || this.to.selected) ? "bold " : "") +
  14879. this.options.fontSize + "px " + this.options.fontFace;
  14880. ctx.fillStyle = this.options.fontFill;
  14881. var lines = String(text).split('\n');
  14882. var lineCount = lines.length;
  14883. var fontSize = (Number(this.options.fontSize) + 4);
  14884. var yLine = y + (1 - lineCount) / 2 * fontSize;
  14885. if (this.options.fontFill !== undefined && this.options.fontFill !== null && this.options.fontFill !== "none") {
  14886. var width = ctx.measureText(lines[0]).width;
  14887. for (var i = 1; i < lineCount; i++) {
  14888. var lineWidth = ctx.measureText(lines[i]).width;
  14889. width = lineWidth > width ? lineWidth : width;
  14890. }
  14891. var height = this.options.fontSize * lineCount;
  14892. var left = x - width / 2;
  14893. var top = y - height / 2;
  14894. ctx.fillRect(left, top, width, height);
  14895. }
  14896. // draw text
  14897. ctx.fillStyle = this.options.fontColor || "black";
  14898. ctx.textAlign = "center";
  14899. ctx.textBaseline = "middle";
  14900. for (var i = 0; i < lineCount; i++) {
  14901. ctx.fillText(lines[i], x, yLine);
  14902. yLine += fontSize;
  14903. }
  14904. }
  14905. };
  14906. /**
  14907. * Redraw a edge as a dashed line
  14908. * Draw this edge in the given canvas
  14909. * @author David Jordan
  14910. * @date 2012-08-08
  14911. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  14912. * @param {CanvasRenderingContext2D} ctx
  14913. * @private
  14914. */
  14915. Edge.prototype._drawDashLine = function(ctx) {
  14916. // set style
  14917. if (this.selected == true) {ctx.strokeStyle = this.options.color.highlight;}
  14918. else if (this.hover == true) {ctx.strokeStyle = this.options.color.hover;}
  14919. else {ctx.strokeStyle = this.options.color.color;}
  14920. ctx.lineWidth = this._getLineWidth();
  14921. var via = null;
  14922. // only firefox and chrome support this method, else we use the legacy one.
  14923. if (ctx.mozDash !== undefined || ctx.setLineDash !== undefined) {
  14924. // configure the dash pattern
  14925. var pattern = [0];
  14926. if (this.options.dash.length !== undefined && this.options.dash.gap !== undefined) {
  14927. pattern = [this.options.dash.length,this.options.dash.gap];
  14928. }
  14929. else {
  14930. pattern = [5,5];
  14931. }
  14932. // set dash settings for chrome or firefox
  14933. if (typeof ctx.setLineDash !== 'undefined') { //Chrome
  14934. ctx.setLineDash(pattern);
  14935. ctx.lineDashOffset = 0;
  14936. } else { //Firefox
  14937. ctx.mozDash = pattern;
  14938. ctx.mozDashOffset = 0;
  14939. }
  14940. // draw the line
  14941. via = this._line(ctx);
  14942. // restore the dash settings.
  14943. if (typeof ctx.setLineDash !== 'undefined') { //Chrome
  14944. ctx.setLineDash([0]);
  14945. ctx.lineDashOffset = 0;
  14946. } else { //Firefox
  14947. ctx.mozDash = [0];
  14948. ctx.mozDashOffset = 0;
  14949. }
  14950. }
  14951. else { // unsupporting smooth lines
  14952. // draw dashed line
  14953. ctx.beginPath();
  14954. ctx.lineCap = 'round';
  14955. if (this.options.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
  14956. {
  14957. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  14958. [this.options.dash.length,this.options.dash.gap,this.options.dash.altLength,this.options.dash.gap]);
  14959. }
  14960. 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
  14961. {
  14962. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  14963. [this.options.dash.length,this.options.dash.gap]);
  14964. }
  14965. else //If all else fails draw a line
  14966. {
  14967. ctx.moveTo(this.from.x, this.from.y);
  14968. ctx.lineTo(this.to.x, this.to.y);
  14969. }
  14970. ctx.stroke();
  14971. }
  14972. // draw label
  14973. if (this.label) {
  14974. var point;
  14975. if (this.options.smoothCurves.enabled == true && via != null) {
  14976. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  14977. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  14978. point = {x:midpointX, y:midpointY};
  14979. }
  14980. else {
  14981. point = this._pointOnLine(0.5);
  14982. }
  14983. this._label(ctx, this.label, point.x, point.y);
  14984. }
  14985. };
  14986. /**
  14987. * Get a point on a line
  14988. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  14989. * @return {Object} point
  14990. * @private
  14991. */
  14992. Edge.prototype._pointOnLine = function (percentage) {
  14993. return {
  14994. x: (1 - percentage) * this.from.x + percentage * this.to.x,
  14995. y: (1 - percentage) * this.from.y + percentage * this.to.y
  14996. }
  14997. };
  14998. /**
  14999. * Get a point on a circle
  15000. * @param {Number} x
  15001. * @param {Number} y
  15002. * @param {Number} radius
  15003. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  15004. * @return {Object} point
  15005. * @private
  15006. */
  15007. Edge.prototype._pointOnCircle = function (x, y, radius, percentage) {
  15008. var angle = (percentage - 3/8) * 2 * Math.PI;
  15009. return {
  15010. x: x + radius * Math.cos(angle),
  15011. y: y - radius * Math.sin(angle)
  15012. }
  15013. };
  15014. /**
  15015. * Redraw a edge as a line with an arrow halfway the line
  15016. * Draw this edge in the given canvas
  15017. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  15018. * @param {CanvasRenderingContext2D} ctx
  15019. * @private
  15020. */
  15021. Edge.prototype._drawArrowCenter = function(ctx) {
  15022. var point;
  15023. // set style
  15024. if (this.selected == true) {ctx.strokeStyle = this.options.color.highlight; ctx.fillStyle = this.options.color.highlight;}
  15025. else if (this.hover == true) {ctx.strokeStyle = this.options.color.hover; ctx.fillStyle = this.options.color.hover;}
  15026. else {ctx.strokeStyle = this.options.color.color; ctx.fillStyle = this.options.color.color;}
  15027. ctx.lineWidth = this._getLineWidth();
  15028. if (this.from != this.to) {
  15029. // draw line
  15030. var via = this._line(ctx);
  15031. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  15032. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  15033. // draw an arrow halfway the line
  15034. if (this.options.smoothCurves.enabled == true && via != null) {
  15035. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  15036. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  15037. point = {x:midpointX, y:midpointY};
  15038. }
  15039. else {
  15040. point = this._pointOnLine(0.5);
  15041. }
  15042. ctx.arrow(point.x, point.y, angle, length);
  15043. ctx.fill();
  15044. ctx.stroke();
  15045. // draw label
  15046. if (this.label) {
  15047. this._label(ctx, this.label, point.x, point.y);
  15048. }
  15049. }
  15050. else {
  15051. // draw circle
  15052. var x, y;
  15053. var radius = 0.25 * Math.max(100,this.physics.springLength);
  15054. var node = this.from;
  15055. if (!node.width) {
  15056. node.resize(ctx);
  15057. }
  15058. if (node.width > node.height) {
  15059. x = node.x + node.width * 0.5;
  15060. y = node.y - radius;
  15061. }
  15062. else {
  15063. x = node.x + radius;
  15064. y = node.y - node.height * 0.5;
  15065. }
  15066. this._circle(ctx, x, y, radius);
  15067. // draw all arrows
  15068. var angle = 0.2 * Math.PI;
  15069. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  15070. point = this._pointOnCircle(x, y, radius, 0.5);
  15071. ctx.arrow(point.x, point.y, angle, length);
  15072. ctx.fill();
  15073. ctx.stroke();
  15074. // draw label
  15075. if (this.label) {
  15076. point = this._pointOnCircle(x, y, radius, 0.5);
  15077. this._label(ctx, this.label, point.x, point.y);
  15078. }
  15079. }
  15080. };
  15081. /**
  15082. * Redraw a edge as a line with an arrow
  15083. * Draw this edge in the given canvas
  15084. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  15085. * @param {CanvasRenderingContext2D} ctx
  15086. * @private
  15087. */
  15088. Edge.prototype._drawArrow = function(ctx) {
  15089. // set style
  15090. if (this.selected == true) {ctx.strokeStyle = this.options.color.highlight; ctx.fillStyle = this.options.color.highlight;}
  15091. else if (this.hover == true) {ctx.strokeStyle = this.options.color.hover; ctx.fillStyle = this.options.color.hover;}
  15092. else {ctx.strokeStyle = this.options.color.color; ctx.fillStyle = this.options.color.color;}
  15093. ctx.lineWidth = this._getLineWidth();
  15094. var angle, length;
  15095. //draw a line
  15096. if (this.from != this.to) {
  15097. angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  15098. var dx = (this.to.x - this.from.x);
  15099. var dy = (this.to.y - this.from.y);
  15100. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  15101. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  15102. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  15103. var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  15104. var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  15105. var via;
  15106. if (this.options.smoothCurves.dynamic == true && this.options.smoothCurves.enabled == true ) {
  15107. via = this.via;
  15108. }
  15109. else if (this.options.smoothCurves.enabled == true) {
  15110. via = this._getViaCoordinates();
  15111. }
  15112. if (this.options.smoothCurves.enabled == true && via.x != null) {
  15113. angle = Math.atan2((this.to.y - via.y), (this.to.x - via.x));
  15114. dx = (this.to.x - via.x);
  15115. dy = (this.to.y - via.y);
  15116. edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  15117. }
  15118. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  15119. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  15120. var xTo,yTo;
  15121. if (this.options.smoothCurves.enabled == true && via.x != null) {
  15122. xTo = (1 - toBorderPoint) * via.x + toBorderPoint * this.to.x;
  15123. yTo = (1 - toBorderPoint) * via.y + toBorderPoint * this.to.y;
  15124. }
  15125. else {
  15126. xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  15127. yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  15128. }
  15129. ctx.beginPath();
  15130. ctx.moveTo(xFrom,yFrom);
  15131. if (this.options.smoothCurves.enabled == true && via.x != null) {
  15132. ctx.quadraticCurveTo(via.x,via.y,xTo, yTo);
  15133. }
  15134. else {
  15135. ctx.lineTo(xTo, yTo);
  15136. }
  15137. ctx.stroke();
  15138. // draw arrow at the end of the line
  15139. length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  15140. ctx.arrow(xTo, yTo, angle, length);
  15141. ctx.fill();
  15142. ctx.stroke();
  15143. // draw label
  15144. if (this.label) {
  15145. var point;
  15146. if (this.options.smoothCurves.enabled == true && via != null) {
  15147. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  15148. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  15149. point = {x:midpointX, y:midpointY};
  15150. }
  15151. else {
  15152. point = this._pointOnLine(0.5);
  15153. }
  15154. this._label(ctx, this.label, point.x, point.y);
  15155. }
  15156. }
  15157. else {
  15158. // draw circle
  15159. var node = this.from;
  15160. var x, y, arrow;
  15161. var radius = 0.25 * Math.max(100,this.physics.springLength);
  15162. if (!node.width) {
  15163. node.resize(ctx);
  15164. }
  15165. if (node.width > node.height) {
  15166. x = node.x + node.width * 0.5;
  15167. y = node.y - radius;
  15168. arrow = {
  15169. x: x,
  15170. y: node.y,
  15171. angle: 0.9 * Math.PI
  15172. };
  15173. }
  15174. else {
  15175. x = node.x + radius;
  15176. y = node.y - node.height * 0.5;
  15177. arrow = {
  15178. x: node.x,
  15179. y: y,
  15180. angle: 0.6 * Math.PI
  15181. };
  15182. }
  15183. ctx.beginPath();
  15184. // TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
  15185. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  15186. ctx.stroke();
  15187. // draw all arrows
  15188. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  15189. ctx.arrow(arrow.x, arrow.y, arrow.angle, length);
  15190. ctx.fill();
  15191. ctx.stroke();
  15192. // draw label
  15193. if (this.label) {
  15194. point = this._pointOnCircle(x, y, radius, 0.5);
  15195. this._label(ctx, this.label, point.x, point.y);
  15196. }
  15197. }
  15198. };
  15199. /**
  15200. * Calculate the distance between a point (x3,y3) and a line segment from
  15201. * (x1,y1) to (x2,y2).
  15202. * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
  15203. * @param {number} x1
  15204. * @param {number} y1
  15205. * @param {number} x2
  15206. * @param {number} y2
  15207. * @param {number} x3
  15208. * @param {number} y3
  15209. * @private
  15210. */
  15211. Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
  15212. if (this.from != this.to) {
  15213. if (this.options.smoothCurves.enabled == true) {
  15214. var xVia, yVia;
  15215. if (this.options.smoothCurves.enabled == true && this.options.smoothCurves.dynamic == true) {
  15216. xVia = this.via.x;
  15217. yVia = this.via.y;
  15218. }
  15219. else {
  15220. var via = this._getViaCoordinates();
  15221. xVia = via.x;
  15222. yVia = via.y;
  15223. }
  15224. var minDistance = 1e9;
  15225. var distance;
  15226. var i,t,x,y, lastX, lastY;
  15227. for (i = 0; i < 10; i++) {
  15228. t = 0.1*i;
  15229. x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*xVia + Math.pow(t,2)*x2;
  15230. y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*yVia + Math.pow(t,2)*y2;
  15231. if (i > 0) {
  15232. distance = this._getDistanceToLine(lastX,lastY,x,y, x3,y3);
  15233. minDistance = distance < minDistance ? distance : minDistance;
  15234. }
  15235. lastX = x; lastY = y;
  15236. }
  15237. return minDistance
  15238. }
  15239. else {
  15240. return this._getDistanceToLine(x1,y1,x2,y2,x3,y3);
  15241. }
  15242. }
  15243. else {
  15244. var x, y, dx, dy;
  15245. var radius = 0.25 * this.physics.springLength;
  15246. var node = this.from;
  15247. if (node.width > node.height) {
  15248. x = node.x + 0.5 * node.width;
  15249. y = node.y - radius;
  15250. }
  15251. else {
  15252. x = node.x + radius;
  15253. y = node.y - 0.5 * node.height;
  15254. }
  15255. dx = x - x3;
  15256. dy = y - y3;
  15257. return Math.abs(Math.sqrt(dx*dx + dy*dy) - radius);
  15258. }
  15259. };
  15260. Edge.prototype._getDistanceToLine = function(x1,y1,x2,y2,x3,y3) {
  15261. var px = x2-x1,
  15262. py = y2-y1,
  15263. something = px*px + py*py,
  15264. u = ((x3 - x1) * px + (y3 - y1) * py) / something;
  15265. if (u > 1) {
  15266. u = 1;
  15267. }
  15268. else if (u < 0) {
  15269. u = 0;
  15270. }
  15271. var x = x1 + u * px,
  15272. y = y1 + u * py,
  15273. dx = x - x3,
  15274. dy = y - y3;
  15275. //# Note: If the actual distance does not matter,
  15276. //# if you only want to compare what this function
  15277. //# returns to other results of this function, you
  15278. //# can just return the squared distance instead
  15279. //# (i.e. remove the sqrt) to gain a little performance
  15280. return Math.sqrt(dx*dx + dy*dy);
  15281. }
  15282. /**
  15283. * This allows the zoom level of the network to influence the rendering
  15284. *
  15285. * @param scale
  15286. */
  15287. Edge.prototype.setScale = function(scale) {
  15288. this.networkScaleInv = 1.0/scale;
  15289. };
  15290. Edge.prototype.select = function() {
  15291. this.selected = true;
  15292. };
  15293. Edge.prototype.unselect = function() {
  15294. this.selected = false;
  15295. };
  15296. Edge.prototype.positionBezierNode = function() {
  15297. if (this.via !== null && this.from !== null && this.to !== null) {
  15298. this.via.x = 0.5 * (this.from.x + this.to.x);
  15299. this.via.y = 0.5 * (this.from.y + this.to.y);
  15300. }
  15301. };
  15302. /**
  15303. * This function draws the control nodes for the manipulator. In order to enable this, only set the this.controlNodesEnabled to true.
  15304. * @param ctx
  15305. */
  15306. Edge.prototype._drawControlNodes = function(ctx) {
  15307. if (this.controlNodesEnabled == true) {
  15308. if (this.controlNodes.from === null && this.controlNodes.to === null) {
  15309. var nodeIdFrom = "edgeIdFrom:".concat(this.id);
  15310. var nodeIdTo = "edgeIdTo:".concat(this.id);
  15311. var constants = {
  15312. nodes:{group:'', radius:8},
  15313. physics:{damping:0},
  15314. clustering: {maxNodeSizeIncrements: 0 ,nodeScaling: {width:0, height: 0, radius:0}}
  15315. };
  15316. this.controlNodes.from = new Node(
  15317. {id:nodeIdFrom,
  15318. shape:'dot',
  15319. color:{background:'#ff4e00', border:'#3c3c3c', highlight: {background:'#07f968'}}
  15320. },{},{},constants);
  15321. this.controlNodes.to = new Node(
  15322. {id:nodeIdTo,
  15323. shape:'dot',
  15324. color:{background:'#ff4e00', border:'#3c3c3c', highlight: {background:'#07f968'}}
  15325. },{},{},constants);
  15326. }
  15327. if (this.controlNodes.from.selected == false && this.controlNodes.to.selected == false) {
  15328. this.controlNodes.positions = this.getControlNodePositions(ctx);
  15329. this.controlNodes.from.x = this.controlNodes.positions.from.x;
  15330. this.controlNodes.from.y = this.controlNodes.positions.from.y;
  15331. this.controlNodes.to.x = this.controlNodes.positions.to.x;
  15332. this.controlNodes.to.y = this.controlNodes.positions.to.y;
  15333. }
  15334. this.controlNodes.from.draw(ctx);
  15335. this.controlNodes.to.draw(ctx);
  15336. }
  15337. else {
  15338. this.controlNodes = {from:null, to:null, positions:{}};
  15339. }
  15340. };
  15341. /**
  15342. * Enable control nodes.
  15343. * @private
  15344. */
  15345. Edge.prototype._enableControlNodes = function() {
  15346. this.controlNodesEnabled = true;
  15347. };
  15348. /**
  15349. * disable control nodes
  15350. * @private
  15351. */
  15352. Edge.prototype._disableControlNodes = function() {
  15353. this.controlNodesEnabled = false;
  15354. };
  15355. /**
  15356. * This checks if one of the control nodes is selected and if so, returns the control node object. Else it returns null.
  15357. * @param x
  15358. * @param y
  15359. * @returns {null}
  15360. * @private
  15361. */
  15362. Edge.prototype._getSelectedControlNode = function(x,y) {
  15363. var positions = this.controlNodes.positions;
  15364. var fromDistance = Math.sqrt(Math.pow(x - positions.from.x,2) + Math.pow(y - positions.from.y,2));
  15365. var toDistance = Math.sqrt(Math.pow(x - positions.to.x ,2) + Math.pow(y - positions.to.y ,2));
  15366. if (fromDistance < 15) {
  15367. this.connectedNode = this.from;
  15368. this.from = this.controlNodes.from;
  15369. return this.controlNodes.from;
  15370. }
  15371. else if (toDistance < 15) {
  15372. this.connectedNode = this.to;
  15373. this.to = this.controlNodes.to;
  15374. return this.controlNodes.to;
  15375. }
  15376. else {
  15377. return null;
  15378. }
  15379. };
  15380. /**
  15381. * this resets the control nodes to their original position.
  15382. * @private
  15383. */
  15384. Edge.prototype._restoreControlNodes = function() {
  15385. if (this.controlNodes.from.selected == true) {
  15386. this.from = this.connectedNode;
  15387. this.connectedNode = null;
  15388. this.controlNodes.from.unselect();
  15389. }
  15390. if (this.controlNodes.to.selected == true) {
  15391. this.to = this.connectedNode;
  15392. this.connectedNode = null;
  15393. this.controlNodes.to.unselect();
  15394. }
  15395. };
  15396. /**
  15397. * this calculates the position of the control nodes on the edges of the parent nodes.
  15398. *
  15399. * @param ctx
  15400. * @returns {{from: {x: number, y: number}, to: {x: *, y: *}}}
  15401. */
  15402. Edge.prototype.getControlNodePositions = function(ctx) {
  15403. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  15404. var dx = (this.to.x - this.from.x);
  15405. var dy = (this.to.y - this.from.y);
  15406. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  15407. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  15408. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  15409. var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  15410. var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  15411. var via;
  15412. if (this.options.smoothCurves.dynamic == true && this.options.smoothCurves.enabled == true) {
  15413. via = this.via;
  15414. }
  15415. else if (this.options.smoothCurves.enabled == true) {
  15416. via = this._getViaCoordinates();
  15417. }
  15418. if (this.options.smoothCurves.enabled == true && via.x != null) {
  15419. angle = Math.atan2((this.to.y - via.y), (this.to.x - via.x));
  15420. dx = (this.to.x - via.x);
  15421. dy = (this.to.y - via.y);
  15422. edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  15423. }
  15424. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  15425. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  15426. var xTo,yTo;
  15427. if (this.options.smoothCurves.enabled == true && via.x != null) {
  15428. xTo = (1 - toBorderPoint) * via.x + toBorderPoint * this.to.x;
  15429. yTo = (1 - toBorderPoint) * via.y + toBorderPoint * this.to.y;
  15430. }
  15431. else {
  15432. xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  15433. yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  15434. }
  15435. return {from:{x:xFrom,y:yFrom},to:{x:xTo,y:yTo}};
  15436. };
  15437. module.exports = Edge;
  15438. /***/ },
  15439. /* 35 */
  15440. /***/ function(module, exports, __webpack_require__) {
  15441. var util = __webpack_require__(1);
  15442. /**
  15443. * @class Groups
  15444. * This class can store groups and properties specific for groups.
  15445. */
  15446. function Groups() {
  15447. this.clear();
  15448. this.defaultIndex = 0;
  15449. }
  15450. /**
  15451. * default constants for group colors
  15452. */
  15453. Groups.DEFAULT = [
  15454. {border: "#2B7CE9", background: "#97C2FC", highlight: {border: "#2B7CE9", background: "#D2E5FF"}, hover: {border: "#2B7CE9", background: "#D2E5FF"}}, // blue
  15455. {border: "#FFA500", background: "#FFFF00", highlight: {border: "#FFA500", background: "#FFFFA3"}, hover: {border: "#FFA500", background: "#FFFFA3"}}, // yellow
  15456. {border: "#FA0A10", background: "#FB7E81", highlight: {border: "#FA0A10", background: "#FFAFB1"}, hover: {border: "#FA0A10", background: "#FFAFB1"}}, // red
  15457. {border: "#41A906", background: "#7BE141", highlight: {border: "#41A906", background: "#A1EC76"}, hover: {border: "#41A906", background: "#A1EC76"}}, // green
  15458. {border: "#E129F0", background: "#EB7DF4", highlight: {border: "#E129F0", background: "#F0B3F5"}, hover: {border: "#E129F0", background: "#F0B3F5"}}, // magenta
  15459. {border: "#7C29F0", background: "#AD85E4", highlight: {border: "#7C29F0", background: "#D3BDF0"}, hover: {border: "#7C29F0", background: "#D3BDF0"}}, // purple
  15460. {border: "#C37F00", background: "#FFA807", highlight: {border: "#C37F00", background: "#FFCA66"}, hover: {border: "#C37F00", background: "#FFCA66"}}, // orange
  15461. {border: "#4220FB", background: "#6E6EFD", highlight: {border: "#4220FB", background: "#9B9BFD"}, hover: {border: "#4220FB", background: "#9B9BFD"}}, // darkblue
  15462. {border: "#FD5A77", background: "#FFC0CB", highlight: {border: "#FD5A77", background: "#FFD1D9"}, hover: {border: "#FD5A77", background: "#FFD1D9"}}, // pink
  15463. {border: "#4AD63A", background: "#C2FABC", highlight: {border: "#4AD63A", background: "#E6FFE3"}, hover: {border: "#4AD63A", background: "#E6FFE3"}} // mint
  15464. ];
  15465. /**
  15466. * Clear all groups
  15467. */
  15468. Groups.prototype.clear = function () {
  15469. this.groups = {};
  15470. this.groups.length = function()
  15471. {
  15472. var i = 0;
  15473. for ( var p in this ) {
  15474. if (this.hasOwnProperty(p)) {
  15475. i++;
  15476. }
  15477. }
  15478. return i;
  15479. }
  15480. };
  15481. /**
  15482. * get group properties of a groupname. If groupname is not found, a new group
  15483. * is added.
  15484. * @param {*} groupname Can be a number, string, Date, etc.
  15485. * @return {Object} group The created group, containing all group properties
  15486. */
  15487. Groups.prototype.get = function (groupname) {
  15488. var group = this.groups[groupname];
  15489. if (group == undefined) {
  15490. // create new group
  15491. var index = this.defaultIndex % Groups.DEFAULT.length;
  15492. this.defaultIndex++;
  15493. group = {};
  15494. group.color = Groups.DEFAULT[index];
  15495. this.groups[groupname] = group;
  15496. }
  15497. return group;
  15498. };
  15499. /**
  15500. * Add a custom group style
  15501. * @param {String} groupname
  15502. * @param {Object} style An object containing borderColor,
  15503. * backgroundColor, etc.
  15504. * @return {Object} group The created group object
  15505. */
  15506. Groups.prototype.add = function (groupname, style) {
  15507. this.groups[groupname] = style;
  15508. if (style.color) {
  15509. style.color = util.parseColor(style.color);
  15510. }
  15511. return style;
  15512. };
  15513. module.exports = Groups;
  15514. /***/ },
  15515. /* 36 */
  15516. /***/ function(module, exports, __webpack_require__) {
  15517. /**
  15518. * @class Images
  15519. * This class loads images and keeps them stored.
  15520. */
  15521. function Images() {
  15522. this.images = {};
  15523. this.callback = undefined;
  15524. }
  15525. /**
  15526. * Set an onload callback function. This will be called each time an image
  15527. * is loaded
  15528. * @param {function} callback
  15529. */
  15530. Images.prototype.setOnloadCallback = function(callback) {
  15531. this.callback = callback;
  15532. };
  15533. /**
  15534. *
  15535. * @param {string} url Url of the image
  15536. * @param {string} url Url of an image to use if the url image is not found
  15537. * @return {Image} img The image object
  15538. */
  15539. Images.prototype.load = function(url, brokenUrl) {
  15540. var img = this.images[url];
  15541. if (img == undefined) {
  15542. // create the image
  15543. var images = this;
  15544. img = new Image();
  15545. this.images[url] = img;
  15546. img.onload = function() {
  15547. if (images.callback) {
  15548. images.callback(this);
  15549. }
  15550. };
  15551. img.onerror = function () {
  15552. this.src = brokenUrl;
  15553. if (images.callback) {
  15554. images.callback(this);
  15555. }
  15556. };
  15557. img.src = url;
  15558. }
  15559. return img;
  15560. };
  15561. module.exports = Images;
  15562. /***/ },
  15563. /* 37 */
  15564. /***/ function(module, exports, __webpack_require__) {
  15565. var util = __webpack_require__(1);
  15566. /**
  15567. * @class Node
  15568. * A node. A node can be connected to other nodes via one or multiple edges.
  15569. * @param {object} properties An object containing properties for the node. All
  15570. * properties are optional, except for the id.
  15571. * {number} id Id of the node. Required
  15572. * {string} label Text label for the node
  15573. * {number} x Horizontal position of the node
  15574. * {number} y Vertical position of the node
  15575. * {string} shape Node shape, available:
  15576. * "database", "circle", "ellipse",
  15577. * "box", "image", "text", "dot",
  15578. * "star", "triangle", "triangleDown",
  15579. * "square"
  15580. * {string} image An image url
  15581. * {string} title An title text, can be HTML
  15582. * {anytype} group A group name or number
  15583. * @param {Network.Images} imagelist A list with images. Only needed
  15584. * when the node has an image
  15585. * @param {Network.Groups} grouplist A list with groups. Needed for
  15586. * retrieving group properties
  15587. * @param {Object} constants An object with default values for
  15588. * example for the color
  15589. *
  15590. */
  15591. function Node(properties, imagelist, grouplist, networkConstants) {
  15592. var constants = util.selectiveBridgeObject(['nodes'],networkConstants);
  15593. this.options = constants.nodes;
  15594. this.selected = false;
  15595. this.hover = false;
  15596. this.edges = []; // all edges connected to this node
  15597. this.dynamicEdges = [];
  15598. this.reroutedEdges = {};
  15599. this.fontDrawThreshold = 3;
  15600. // set defaults for the properties
  15601. this.id = undefined;
  15602. this.x = null;
  15603. this.y = null;
  15604. this.xFixed = false;
  15605. this.yFixed = false;
  15606. this.horizontalAlignLeft = true; // these are for the navigation controls
  15607. this.verticalAlignTop = true; // these are for the navigation controls
  15608. this.baseRadiusValue = networkConstants.nodes.radius;
  15609. this.radiusFixed = false;
  15610. this.level = -1;
  15611. this.preassignedLevel = false;
  15612. this.hierarchyEnumerated = false;
  15613. this.imagelist = imagelist;
  15614. this.grouplist = grouplist;
  15615. // physics properties
  15616. this.fx = 0.0; // external force x
  15617. this.fy = 0.0; // external force y
  15618. this.vx = 0.0; // velocity x
  15619. this.vy = 0.0; // velocity y
  15620. this.damping = networkConstants.physics.damping; // written every time gravity is calculated
  15621. this.fixedData = {x:null,y:null};
  15622. this.setProperties(properties, constants);
  15623. // creating the variables for clustering
  15624. this.resetCluster();
  15625. this.dynamicEdgesLength = 0;
  15626. this.clusterSession = 0;
  15627. this.clusterSizeWidthFactor = networkConstants.clustering.nodeScaling.width;
  15628. this.clusterSizeHeightFactor = networkConstants.clustering.nodeScaling.height;
  15629. this.clusterSizeRadiusFactor = networkConstants.clustering.nodeScaling.radius;
  15630. this.maxNodeSizeIncrements = networkConstants.clustering.maxNodeSizeIncrements;
  15631. this.growthIndicator = 0;
  15632. // variables to tell the node about the network.
  15633. this.networkScaleInv = 1;
  15634. this.networkScale = 1;
  15635. this.canvasTopLeft = {"x": -300, "y": -300};
  15636. this.canvasBottomRight = {"x": 300, "y": 300};
  15637. this.parentEdgeId = null;
  15638. }
  15639. /**
  15640. * (re)setting the clustering variables and objects
  15641. */
  15642. Node.prototype.resetCluster = function() {
  15643. // clustering variables
  15644. this.formationScale = undefined; // this is used to determine when to open the cluster
  15645. this.clusterSize = 1; // this signifies the total amount of nodes in this cluster
  15646. this.containedNodes = {};
  15647. this.containedEdges = {};
  15648. this.clusterSessions = [];
  15649. };
  15650. /**
  15651. * Attach a edge to the node
  15652. * @param {Edge} edge
  15653. */
  15654. Node.prototype.attachEdge = function(edge) {
  15655. if (this.edges.indexOf(edge) == -1) {
  15656. this.edges.push(edge);
  15657. }
  15658. if (this.dynamicEdges.indexOf(edge) == -1) {
  15659. this.dynamicEdges.push(edge);
  15660. }
  15661. this.dynamicEdgesLength = this.dynamicEdges.length;
  15662. };
  15663. /**
  15664. * Detach a edge from the node
  15665. * @param {Edge} edge
  15666. */
  15667. Node.prototype.detachEdge = function(edge) {
  15668. var index = this.edges.indexOf(edge);
  15669. if (index != -1) {
  15670. this.edges.splice(index, 1);
  15671. this.dynamicEdges.splice(index, 1);
  15672. }
  15673. this.dynamicEdgesLength = this.dynamicEdges.length;
  15674. };
  15675. /**
  15676. * Set or overwrite properties for the node
  15677. * @param {Object} properties an object with properties
  15678. * @param {Object} constants and object with default, global properties
  15679. */
  15680. Node.prototype.setProperties = function(properties, constants) {
  15681. if (!properties) {
  15682. return;
  15683. }
  15684. var fields = ['borderWidth','borderWidthSelected','shape','image','brokenImage','radius','fontColor',
  15685. 'fontSize','fontFace','fontFill','group','mass'
  15686. ];
  15687. util.selectiveDeepExtend(fields, this.options, properties);
  15688. this.originalLabel = undefined;
  15689. // basic properties
  15690. if (properties.id !== undefined) {this.id = properties.id;}
  15691. if (properties.label !== undefined) {this.label = properties.label; this.originalLabel = properties.label;}
  15692. if (properties.title !== undefined) {this.title = properties.title;}
  15693. if (properties.x !== undefined) {this.x = properties.x;}
  15694. if (properties.y !== undefined) {this.y = properties.y;}
  15695. if (properties.value !== undefined) {this.value = properties.value;}
  15696. if (properties.level !== undefined) {this.level = properties.level; this.preassignedLevel = true;}
  15697. // navigation controls properties
  15698. if (properties.horizontalAlignLeft !== undefined) {this.horizontalAlignLeft = properties.horizontalAlignLeft;}
  15699. if (properties.verticalAlignTop !== undefined) {this.verticalAlignTop = properties.verticalAlignTop;}
  15700. if (properties.triggerFunction !== undefined) {this.triggerFunction = properties.triggerFunction;}
  15701. if (this.id === undefined) {
  15702. throw "Node must have an id";
  15703. }
  15704. // copy group properties
  15705. if (typeof this.options.group === 'number' || (typeof this.options.group === 'string' && this.options.group != '')) {
  15706. var groupObj = this.grouplist.get(this.options.group);
  15707. for (var prop in groupObj) {
  15708. if (groupObj.hasOwnProperty(prop)) {
  15709. this.options[prop] = groupObj[prop];
  15710. }
  15711. }
  15712. }
  15713. // individual shape properties
  15714. if (properties.radius !== undefined) {this.baseRadiusValue = this.options.radius;}
  15715. if (properties.color !== undefined) {this.options.color = util.parseColor(properties.color);}
  15716. if (this.options.image!== undefined && this.options.image!= "") {
  15717. if (this.imagelist) {
  15718. this.imageObj = this.imagelist.load(this.options.image, this.options.brokenImage);
  15719. }
  15720. else {
  15721. throw "No imagelist provided";
  15722. }
  15723. }
  15724. this.xFixed = this.xFixed || (properties.x !== undefined && !properties.allowedToMoveX);
  15725. this.yFixed = this.yFixed || (properties.y !== undefined && !properties.allowedToMoveY);
  15726. this.radiusFixed = this.radiusFixed || (properties.radius !== undefined);
  15727. if (this.options.shape == 'image') {
  15728. this.options.radiusMin = constants.nodes.widthMin;
  15729. this.options.radiusMax = constants.nodes.widthMax;
  15730. }
  15731. // choose draw method depending on the shape
  15732. switch (this.options.shape) {
  15733. case 'database': this.draw = this._drawDatabase; this.resize = this._resizeDatabase; break;
  15734. case 'box': this.draw = this._drawBox; this.resize = this._resizeBox; break;
  15735. case 'circle': this.draw = this._drawCircle; this.resize = this._resizeCircle; break;
  15736. case 'ellipse': this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
  15737. // TODO: add diamond shape
  15738. case 'image': this.draw = this._drawImage; this.resize = this._resizeImage; break;
  15739. case 'text': this.draw = this._drawText; this.resize = this._resizeText; break;
  15740. case 'dot': this.draw = this._drawDot; this.resize = this._resizeShape; break;
  15741. case 'square': this.draw = this._drawSquare; this.resize = this._resizeShape; break;
  15742. case 'triangle': this.draw = this._drawTriangle; this.resize = this._resizeShape; break;
  15743. case 'triangleDown': this.draw = this._drawTriangleDown; this.resize = this._resizeShape; break;
  15744. case 'star': this.draw = this._drawStar; this.resize = this._resizeShape; break;
  15745. default: this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
  15746. }
  15747. // reset the size of the node, this can be changed
  15748. this._reset();
  15749. };
  15750. /**
  15751. * select this node
  15752. */
  15753. Node.prototype.select = function() {
  15754. this.selected = true;
  15755. this._reset();
  15756. };
  15757. /**
  15758. * unselect this node
  15759. */
  15760. Node.prototype.unselect = function() {
  15761. this.selected = false;
  15762. this._reset();
  15763. };
  15764. /**
  15765. * Reset the calculated size of the node, forces it to recalculate its size
  15766. */
  15767. Node.prototype.clearSizeCache = function() {
  15768. this._reset();
  15769. };
  15770. /**
  15771. * Reset the calculated size of the node, forces it to recalculate its size
  15772. * @private
  15773. */
  15774. Node.prototype._reset = function() {
  15775. this.width = undefined;
  15776. this.height = undefined;
  15777. };
  15778. /**
  15779. * get the title of this node.
  15780. * @return {string} title The title of the node, or undefined when no title
  15781. * has been set.
  15782. */
  15783. Node.prototype.getTitle = function() {
  15784. return typeof this.title === "function" ? this.title() : this.title;
  15785. };
  15786. /**
  15787. * Calculate the distance to the border of the Node
  15788. * @param {CanvasRenderingContext2D} ctx
  15789. * @param {Number} angle Angle in radians
  15790. * @returns {number} distance Distance to the border in pixels
  15791. */
  15792. Node.prototype.distanceToBorder = function (ctx, angle) {
  15793. var borderWidth = 1;
  15794. if (!this.width) {
  15795. this.resize(ctx);
  15796. }
  15797. switch (this.options.shape) {
  15798. case 'circle':
  15799. case 'dot':
  15800. return this.options.radius+ borderWidth;
  15801. case 'ellipse':
  15802. var a = this.width / 2;
  15803. var b = this.height / 2;
  15804. var w = (Math.sin(angle) * a);
  15805. var h = (Math.cos(angle) * b);
  15806. return a * b / Math.sqrt(w * w + h * h);
  15807. // TODO: implement distanceToBorder for database
  15808. // TODO: implement distanceToBorder for triangle
  15809. // TODO: implement distanceToBorder for triangleDown
  15810. case 'box':
  15811. case 'image':
  15812. case 'text':
  15813. default:
  15814. if (this.width) {
  15815. return Math.min(
  15816. Math.abs(this.width / 2 / Math.cos(angle)),
  15817. Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth;
  15818. // TODO: reckon with border radius too in case of box
  15819. }
  15820. else {
  15821. return 0;
  15822. }
  15823. }
  15824. // TODO: implement calculation of distance to border for all shapes
  15825. };
  15826. /**
  15827. * Set forces acting on the node
  15828. * @param {number} fx Force in horizontal direction
  15829. * @param {number} fy Force in vertical direction
  15830. */
  15831. Node.prototype._setForce = function(fx, fy) {
  15832. this.fx = fx;
  15833. this.fy = fy;
  15834. };
  15835. /**
  15836. * Add forces acting on the node
  15837. * @param {number} fx Force in horizontal direction
  15838. * @param {number} fy Force in vertical direction
  15839. * @private
  15840. */
  15841. Node.prototype._addForce = function(fx, fy) {
  15842. this.fx += fx;
  15843. this.fy += fy;
  15844. };
  15845. /**
  15846. * Perform one discrete step for the node
  15847. * @param {number} interval Time interval in seconds
  15848. */
  15849. Node.prototype.discreteStep = function(interval) {
  15850. if (!this.xFixed) {
  15851. var dx = this.damping * this.vx; // damping force
  15852. var ax = (this.fx - dx) / this.options.mass; // acceleration
  15853. this.vx += ax * interval; // velocity
  15854. this.x += this.vx * interval; // position
  15855. }
  15856. if (!this.yFixed) {
  15857. var dy = this.damping * this.vy; // damping force
  15858. var ay = (this.fy - dy) / this.options.mass; // acceleration
  15859. this.vy += ay * interval; // velocity
  15860. this.y += this.vy * interval; // position
  15861. }
  15862. };
  15863. /**
  15864. * Perform one discrete step for the node
  15865. * @param {number} interval Time interval in seconds
  15866. * @param {number} maxVelocity The speed limit imposed on the velocity
  15867. */
  15868. Node.prototype.discreteStepLimited = function(interval, maxVelocity) {
  15869. if (!this.xFixed) {
  15870. var dx = this.damping * this.vx; // damping force
  15871. var ax = (this.fx - dx) / this.options.mass; // acceleration
  15872. this.vx += ax * interval; // velocity
  15873. this.vx = (Math.abs(this.vx) > maxVelocity) ? ((this.vx > 0) ? maxVelocity : -maxVelocity) : this.vx;
  15874. this.x += this.vx * interval; // position
  15875. }
  15876. else {
  15877. this.fx = 0;
  15878. }
  15879. if (!this.yFixed) {
  15880. var dy = this.damping * this.vy; // damping force
  15881. var ay = (this.fy - dy) / this.options.mass; // acceleration
  15882. this.vy += ay * interval; // velocity
  15883. this.vy = (Math.abs(this.vy) > maxVelocity) ? ((this.vy > 0) ? maxVelocity : -maxVelocity) : this.vy;
  15884. this.y += this.vy * interval; // position
  15885. }
  15886. else {
  15887. this.fy = 0;
  15888. }
  15889. };
  15890. /**
  15891. * Check if this node has a fixed x and y position
  15892. * @return {boolean} true if fixed, false if not
  15893. */
  15894. Node.prototype.isFixed = function() {
  15895. return (this.xFixed && this.yFixed);
  15896. };
  15897. /**
  15898. * Check if this node is moving
  15899. * @param {number} vmin the minimum velocity considered as "moving"
  15900. * @return {boolean} true if moving, false if it has no velocity
  15901. */
  15902. Node.prototype.isMoving = function(vmin) {
  15903. var velocity = Math.sqrt(Math.pow(this.vx,2) + Math.pow(this.vy,2));
  15904. // this.velocity = Math.sqrt(Math.pow(this.vx,2) + Math.pow(this.vy,2))
  15905. return (velocity > vmin);
  15906. };
  15907. /**
  15908. * check if this node is selecte
  15909. * @return {boolean} selected True if node is selected, else false
  15910. */
  15911. Node.prototype.isSelected = function() {
  15912. return this.selected;
  15913. };
  15914. /**
  15915. * Retrieve the value of the node. Can be undefined
  15916. * @return {Number} value
  15917. */
  15918. Node.prototype.getValue = function() {
  15919. return this.value;
  15920. };
  15921. /**
  15922. * Calculate the distance from the nodes location to the given location (x,y)
  15923. * @param {Number} x
  15924. * @param {Number} y
  15925. * @return {Number} value
  15926. */
  15927. Node.prototype.getDistance = function(x, y) {
  15928. var dx = this.x - x,
  15929. dy = this.y - y;
  15930. return Math.sqrt(dx * dx + dy * dy);
  15931. };
  15932. /**
  15933. * Adjust the value range of the node. The node will adjust it's radius
  15934. * based on its value.
  15935. * @param {Number} min
  15936. * @param {Number} max
  15937. */
  15938. Node.prototype.setValueRange = function(min, max) {
  15939. if (!this.radiusFixed && this.value !== undefined) {
  15940. if (max == min) {
  15941. this.options.radius= (this.options.radiusMin + this.options.radiusMax) / 2;
  15942. }
  15943. else {
  15944. var scale = (this.options.radiusMax - this.options.radiusMin) / (max - min);
  15945. this.options.radius= (this.value - min) * scale + this.options.radiusMin;
  15946. }
  15947. }
  15948. this.baseRadiusValue = this.options.radius;
  15949. };
  15950. /**
  15951. * Draw this node in the given canvas
  15952. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  15953. * @param {CanvasRenderingContext2D} ctx
  15954. */
  15955. Node.prototype.draw = function(ctx) {
  15956. throw "Draw method not initialized for node";
  15957. };
  15958. /**
  15959. * Recalculate the size of this node in the given canvas
  15960. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  15961. * @param {CanvasRenderingContext2D} ctx
  15962. */
  15963. Node.prototype.resize = function(ctx) {
  15964. throw "Resize method not initialized for node";
  15965. };
  15966. /**
  15967. * Check if this object is overlapping with the provided object
  15968. * @param {Object} obj an object with parameters left, top, right, bottom
  15969. * @return {boolean} True if location is located on node
  15970. */
  15971. Node.prototype.isOverlappingWith = function(obj) {
  15972. return (this.left < obj.right &&
  15973. this.left + this.width > obj.left &&
  15974. this.top < obj.bottom &&
  15975. this.top + this.height > obj.top);
  15976. };
  15977. Node.prototype._resizeImage = function (ctx) {
  15978. // TODO: pre calculate the image size
  15979. if (!this.width || !this.height) { // undefined or 0
  15980. var width, height;
  15981. if (this.value) {
  15982. this.options.radius= this.baseRadiusValue;
  15983. var scale = this.imageObj.height / this.imageObj.width;
  15984. if (scale !== undefined) {
  15985. width = this.options.radius|| this.imageObj.width;
  15986. height = this.options.radius* scale || this.imageObj.height;
  15987. }
  15988. else {
  15989. width = 0;
  15990. height = 0;
  15991. }
  15992. }
  15993. else {
  15994. width = this.imageObj.width;
  15995. height = this.imageObj.height;
  15996. }
  15997. this.width = width;
  15998. this.height = height;
  15999. this.growthIndicator = 0;
  16000. if (this.width > 0 && this.height > 0) {
  16001. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  16002. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  16003. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  16004. this.growthIndicator = this.width - width;
  16005. }
  16006. }
  16007. };
  16008. Node.prototype._drawImage = function (ctx) {
  16009. this._resizeImage(ctx);
  16010. this.left = this.x - this.width / 2;
  16011. this.top = this.y - this.height / 2;
  16012. var yLabel;
  16013. if (this.imageObj.width != 0 ) {
  16014. // draw the shade
  16015. if (this.clusterSize > 1) {
  16016. var lineWidth = ((this.clusterSize > 1) ? 10 : 0.0);
  16017. lineWidth *= this.networkScaleInv;
  16018. lineWidth = Math.min(0.2 * this.width,lineWidth);
  16019. ctx.globalAlpha = 0.5;
  16020. ctx.drawImage(this.imageObj, this.left - lineWidth, this.top - lineWidth, this.width + 2*lineWidth, this.height + 2*lineWidth);
  16021. }
  16022. // draw the image
  16023. ctx.globalAlpha = 1.0;
  16024. ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height);
  16025. yLabel = this.y + this.height / 2;
  16026. }
  16027. else {
  16028. // image still loading... just draw the label for now
  16029. yLabel = this.y;
  16030. }
  16031. this._label(ctx, this.label, this.x, yLabel, undefined, "top");
  16032. };
  16033. Node.prototype._resizeBox = function (ctx) {
  16034. if (!this.width) {
  16035. var margin = 5;
  16036. var textSize = this.getTextSize(ctx);
  16037. this.width = textSize.width + 2 * margin;
  16038. this.height = textSize.height + 2 * margin;
  16039. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  16040. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  16041. this.growthIndicator = this.width - (textSize.width + 2 * margin);
  16042. // this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  16043. }
  16044. };
  16045. Node.prototype._drawBox = function (ctx) {
  16046. this._resizeBox(ctx);
  16047. this.left = this.x - this.width / 2;
  16048. this.top = this.y - this.height / 2;
  16049. var clusterLineWidth = 2.5;
  16050. var borderWidth = this.options.borderWidth;
  16051. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  16052. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  16053. // draw the outer border
  16054. if (this.clusterSize > 1) {
  16055. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16056. ctx.lineWidth *= this.networkScaleInv;
  16057. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16058. 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);
  16059. ctx.stroke();
  16060. }
  16061. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16062. ctx.lineWidth *= this.networkScaleInv;
  16063. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16064. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.options.color.background;
  16065. ctx.roundRect(this.left, this.top, this.width, this.height, this.options.radius);
  16066. ctx.fill();
  16067. ctx.stroke();
  16068. this._label(ctx, this.label, this.x, this.y);
  16069. };
  16070. Node.prototype._resizeDatabase = function (ctx) {
  16071. if (!this.width) {
  16072. var margin = 5;
  16073. var textSize = this.getTextSize(ctx);
  16074. var size = textSize.width + 2 * margin;
  16075. this.width = size;
  16076. this.height = size;
  16077. // scaling used for clustering
  16078. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  16079. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  16080. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  16081. this.growthIndicator = this.width - size;
  16082. }
  16083. };
  16084. Node.prototype._drawDatabase = function (ctx) {
  16085. this._resizeDatabase(ctx);
  16086. this.left = this.x - this.width / 2;
  16087. this.top = this.y - this.height / 2;
  16088. var clusterLineWidth = 2.5;
  16089. var borderWidth = this.options.borderWidth;
  16090. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  16091. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  16092. // draw the outer border
  16093. if (this.clusterSize > 1) {
  16094. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16095. ctx.lineWidth *= this.networkScaleInv;
  16096. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16097. 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);
  16098. ctx.stroke();
  16099. }
  16100. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16101. ctx.lineWidth *= this.networkScaleInv;
  16102. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16103. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  16104. ctx.database(this.x - this.width/2, this.y - this.height*0.5, this.width, this.height);
  16105. ctx.fill();
  16106. ctx.stroke();
  16107. this._label(ctx, this.label, this.x, this.y);
  16108. };
  16109. Node.prototype._resizeCircle = function (ctx) {
  16110. if (!this.width) {
  16111. var margin = 5;
  16112. var textSize = this.getTextSize(ctx);
  16113. var diameter = Math.max(textSize.width, textSize.height) + 2 * margin;
  16114. this.options.radius = diameter / 2;
  16115. this.width = diameter;
  16116. this.height = diameter;
  16117. // scaling used for clustering
  16118. // this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  16119. // this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  16120. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  16121. this.growthIndicator = this.options.radius- 0.5*diameter;
  16122. }
  16123. };
  16124. Node.prototype._drawCircle = function (ctx) {
  16125. this._resizeCircle(ctx);
  16126. this.left = this.x - this.width / 2;
  16127. this.top = this.y - this.height / 2;
  16128. var clusterLineWidth = 2.5;
  16129. var borderWidth = this.options.borderWidth;
  16130. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  16131. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  16132. // draw the outer border
  16133. if (this.clusterSize > 1) {
  16134. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16135. ctx.lineWidth *= this.networkScaleInv;
  16136. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16137. ctx.circle(this.x, this.y, this.options.radius+2*ctx.lineWidth);
  16138. ctx.stroke();
  16139. }
  16140. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16141. ctx.lineWidth *= this.networkScaleInv;
  16142. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16143. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  16144. ctx.circle(this.x, this.y, this.options.radius);
  16145. ctx.fill();
  16146. ctx.stroke();
  16147. this._label(ctx, this.label, this.x, this.y);
  16148. };
  16149. Node.prototype._resizeEllipse = function (ctx) {
  16150. if (!this.width) {
  16151. var textSize = this.getTextSize(ctx);
  16152. this.width = textSize.width * 1.5;
  16153. this.height = textSize.height * 2;
  16154. if (this.width < this.height) {
  16155. this.width = this.height;
  16156. }
  16157. var defaultSize = this.width;
  16158. // scaling used for clustering
  16159. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  16160. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  16161. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  16162. this.growthIndicator = this.width - defaultSize;
  16163. }
  16164. };
  16165. Node.prototype._drawEllipse = function (ctx) {
  16166. this._resizeEllipse(ctx);
  16167. this.left = this.x - this.width / 2;
  16168. this.top = this.y - this.height / 2;
  16169. var clusterLineWidth = 2.5;
  16170. var borderWidth = this.options.borderWidth;
  16171. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  16172. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  16173. // draw the outer border
  16174. if (this.clusterSize > 1) {
  16175. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16176. ctx.lineWidth *= this.networkScaleInv;
  16177. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16178. ctx.ellipse(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth);
  16179. ctx.stroke();
  16180. }
  16181. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16182. ctx.lineWidth *= this.networkScaleInv;
  16183. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16184. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  16185. ctx.ellipse(this.left, this.top, this.width, this.height);
  16186. ctx.fill();
  16187. ctx.stroke();
  16188. this._label(ctx, this.label, this.x, this.y);
  16189. };
  16190. Node.prototype._drawDot = function (ctx) {
  16191. this._drawShape(ctx, 'circle');
  16192. };
  16193. Node.prototype._drawTriangle = function (ctx) {
  16194. this._drawShape(ctx, 'triangle');
  16195. };
  16196. Node.prototype._drawTriangleDown = function (ctx) {
  16197. this._drawShape(ctx, 'triangleDown');
  16198. };
  16199. Node.prototype._drawSquare = function (ctx) {
  16200. this._drawShape(ctx, 'square');
  16201. };
  16202. Node.prototype._drawStar = function (ctx) {
  16203. this._drawShape(ctx, 'star');
  16204. };
  16205. Node.prototype._resizeShape = function (ctx) {
  16206. if (!this.width) {
  16207. this.options.radius= this.baseRadiusValue;
  16208. var size = 2 * this.options.radius;
  16209. this.width = size;
  16210. this.height = size;
  16211. // scaling used for clustering
  16212. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  16213. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  16214. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  16215. this.growthIndicator = this.width - size;
  16216. }
  16217. };
  16218. Node.prototype._drawShape = function (ctx, shape) {
  16219. this._resizeShape(ctx);
  16220. this.left = this.x - this.width / 2;
  16221. this.top = this.y - this.height / 2;
  16222. var clusterLineWidth = 2.5;
  16223. var borderWidth = this.options.borderWidth;
  16224. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  16225. var radiusMultiplier = 2;
  16226. // choose draw method depending on the shape
  16227. switch (shape) {
  16228. case 'dot': radiusMultiplier = 2; break;
  16229. case 'square': radiusMultiplier = 2; break;
  16230. case 'triangle': radiusMultiplier = 3; break;
  16231. case 'triangleDown': radiusMultiplier = 3; break;
  16232. case 'star': radiusMultiplier = 4; break;
  16233. }
  16234. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  16235. // draw the outer border
  16236. if (this.clusterSize > 1) {
  16237. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16238. ctx.lineWidth *= this.networkScaleInv;
  16239. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16240. ctx[shape](this.x, this.y, this.options.radius+ radiusMultiplier * ctx.lineWidth);
  16241. ctx.stroke();
  16242. }
  16243. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16244. ctx.lineWidth *= this.networkScaleInv;
  16245. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16246. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  16247. ctx[shape](this.x, this.y, this.options.radius);
  16248. ctx.fill();
  16249. ctx.stroke();
  16250. if (this.label) {
  16251. this._label(ctx, this.label, this.x, this.y + this.height / 2, undefined, 'top',true);
  16252. }
  16253. };
  16254. Node.prototype._resizeText = function (ctx) {
  16255. if (!this.width) {
  16256. var margin = 5;
  16257. var textSize = this.getTextSize(ctx);
  16258. this.width = textSize.width + 2 * margin;
  16259. this.height = textSize.height + 2 * margin;
  16260. // scaling used for clustering
  16261. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  16262. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  16263. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  16264. this.growthIndicator = this.width - (textSize.width + 2 * margin);
  16265. }
  16266. };
  16267. Node.prototype._drawText = function (ctx) {
  16268. this._resizeText(ctx);
  16269. this.left = this.x - this.width / 2;
  16270. this.top = this.y - this.height / 2;
  16271. this._label(ctx, this.label, this.x, this.y);
  16272. };
  16273. Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNode) {
  16274. if (text && Number(this.options.fontSize) * this.networkScale > this.fontDrawThreshold) {
  16275. ctx.font = (this.selected ? "bold " : "") + this.options.fontSize + "px " + this.options.fontFace;
  16276. ctx.textAlign = align || "center";
  16277. ctx.textBaseline = baseline || "middle";
  16278. var lines = text.split('\n');
  16279. var lineCount = lines.length;
  16280. var fontSize = (Number(this.options.fontSize) + 4);
  16281. var yLine = y + (1 - lineCount) / 2 * fontSize;
  16282. if (labelUnderNode == true) {
  16283. yLine = y + (1 - lineCount) / (2 * fontSize);
  16284. }
  16285. // font fill from edges now for nodes!
  16286. if (this.options.fontFill !== undefined && this.options.fontFill !== null && this.options.fontFill !== "none") {
  16287. var width = ctx.measureText(lines[0]).width;
  16288. for (var i = 1; i < lineCount; i++) {
  16289. var lineWidth = ctx.measureText(lines[i]).width;
  16290. width = lineWidth > width ? lineWidth : width;
  16291. }
  16292. var height = this.options.fontSize * lineCount;
  16293. var left = x - width / 2;
  16294. var top = y - height / 2;
  16295. ctx.fillStyle = this.options.fontFill;
  16296. ctx.fillRect(left, top, width, height);
  16297. }
  16298. // draw text
  16299. ctx.fillStyle = this.options.fontColor || "black";
  16300. for (var i = 0; i < lineCount; i++) {
  16301. ctx.fillText(lines[i], x, yLine);
  16302. yLine += fontSize;
  16303. }
  16304. }
  16305. };
  16306. Node.prototype.getTextSize = function(ctx) {
  16307. if (this.label !== undefined) {
  16308. ctx.font = (this.selected ? "bold " : "") + this.options.fontSize + "px " + this.options.fontFace;
  16309. var lines = this.label.split('\n'),
  16310. height = (Number(this.options.fontSize) + 4) * lines.length,
  16311. width = 0;
  16312. for (var i = 0, iMax = lines.length; i < iMax; i++) {
  16313. width = Math.max(width, ctx.measureText(lines[i]).width);
  16314. }
  16315. return {"width": width, "height": height};
  16316. }
  16317. else {
  16318. return {"width": 0, "height": 0};
  16319. }
  16320. };
  16321. /**
  16322. * this is used to determine if a node is visible at all. this is used to determine when it needs to be drawn.
  16323. * there is a safety margin of 0.3 * width;
  16324. *
  16325. * @returns {boolean}
  16326. */
  16327. Node.prototype.inArea = function() {
  16328. if (this.width !== undefined) {
  16329. return (this.x + this.width *this.networkScaleInv >= this.canvasTopLeft.x &&
  16330. this.x - this.width *this.networkScaleInv < this.canvasBottomRight.x &&
  16331. this.y + this.height*this.networkScaleInv >= this.canvasTopLeft.y &&
  16332. this.y - this.height*this.networkScaleInv < this.canvasBottomRight.y);
  16333. }
  16334. else {
  16335. return true;
  16336. }
  16337. };
  16338. /**
  16339. * checks if the core of the node is in the display area, this is used for opening clusters around zoom
  16340. * @returns {boolean}
  16341. */
  16342. Node.prototype.inView = function() {
  16343. return (this.x >= this.canvasTopLeft.x &&
  16344. this.x < this.canvasBottomRight.x &&
  16345. this.y >= this.canvasTopLeft.y &&
  16346. this.y < this.canvasBottomRight.y);
  16347. };
  16348. /**
  16349. * This allows the zoom level of the network to influence the rendering
  16350. * We store the inverted scale and the coordinates of the top left, and bottom right points of the canvas
  16351. *
  16352. * @param scale
  16353. * @param canvasTopLeft
  16354. * @param canvasBottomRight
  16355. */
  16356. Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight) {
  16357. this.networkScaleInv = 1.0/scale;
  16358. this.networkScale = scale;
  16359. this.canvasTopLeft = canvasTopLeft;
  16360. this.canvasBottomRight = canvasBottomRight;
  16361. };
  16362. /**
  16363. * This allows the zoom level of the network to influence the rendering
  16364. *
  16365. * @param scale
  16366. */
  16367. Node.prototype.setScale = function(scale) {
  16368. this.networkScaleInv = 1.0/scale;
  16369. this.networkScale = scale;
  16370. };
  16371. /**
  16372. * set the velocity at 0. Is called when this node is contained in another during clustering
  16373. */
  16374. Node.prototype.clearVelocity = function() {
  16375. this.vx = 0;
  16376. this.vy = 0;
  16377. };
  16378. /**
  16379. * Basic preservation of (kinectic) energy
  16380. *
  16381. * @param massBeforeClustering
  16382. */
  16383. Node.prototype.updateVelocity = function(massBeforeClustering) {
  16384. var energyBefore = this.vx * this.vx * massBeforeClustering;
  16385. //this.vx = (this.vx < 0) ? -Math.sqrt(energyBefore/this.options.mass) : Math.sqrt(energyBefore/this.options.mass);
  16386. this.vx = Math.sqrt(energyBefore/this.options.mass);
  16387. energyBefore = this.vy * this.vy * massBeforeClustering;
  16388. //this.vy = (this.vy < 0) ? -Math.sqrt(energyBefore/this.options.mass) : Math.sqrt(energyBefore/this.options.mass);
  16389. this.vy = Math.sqrt(energyBefore/this.options.mass);
  16390. };
  16391. module.exports = Node;
  16392. /***/ },
  16393. /* 38 */
  16394. /***/ function(module, exports, __webpack_require__) {
  16395. /**
  16396. * Popup is a class to create a popup window with some text
  16397. * @param {Element} container The container object.
  16398. * @param {Number} [x]
  16399. * @param {Number} [y]
  16400. * @param {String} [text]
  16401. * @param {Object} [style] An object containing borderColor,
  16402. * backgroundColor, etc.
  16403. */
  16404. function Popup(container, x, y, text, style) {
  16405. if (container) {
  16406. this.container = container;
  16407. }
  16408. else {
  16409. this.container = document.body;
  16410. }
  16411. // x, y and text are optional, see if a style object was passed in their place
  16412. if (style === undefined) {
  16413. if (typeof x === "object") {
  16414. style = x;
  16415. x = undefined;
  16416. } else if (typeof text === "object") {
  16417. style = text;
  16418. text = undefined;
  16419. } else {
  16420. // for backwards compatibility, in case clients other than Network are creating Popup directly
  16421. style = {
  16422. fontColor: 'black',
  16423. fontSize: 14, // px
  16424. fontFace: 'verdana',
  16425. color: {
  16426. border: '#666',
  16427. background: '#FFFFC6'
  16428. }
  16429. }
  16430. }
  16431. }
  16432. this.x = 0;
  16433. this.y = 0;
  16434. this.padding = 5;
  16435. if (x !== undefined && y !== undefined ) {
  16436. this.setPosition(x, y);
  16437. }
  16438. if (text !== undefined) {
  16439. this.setText(text);
  16440. }
  16441. // create the frame
  16442. this.frame = document.createElement("div");
  16443. var styleAttr = this.frame.style;
  16444. styleAttr.position = "absolute";
  16445. styleAttr.visibility = "hidden";
  16446. styleAttr.border = "1px solid " + style.color.border;
  16447. styleAttr.color = style.fontColor;
  16448. styleAttr.fontSize = style.fontSize + "px";
  16449. styleAttr.fontFamily = style.fontFace;
  16450. styleAttr.padding = this.padding + "px";
  16451. styleAttr.backgroundColor = style.color.background;
  16452. styleAttr.borderRadius = "3px";
  16453. styleAttr.MozBorderRadius = "3px";
  16454. styleAttr.WebkitBorderRadius = "3px";
  16455. styleAttr.boxShadow = "3px 3px 10px rgba(128, 128, 128, 0.5)";
  16456. styleAttr.whiteSpace = "nowrap";
  16457. this.container.appendChild(this.frame);
  16458. }
  16459. /**
  16460. * @param {number} x Horizontal position of the popup window
  16461. * @param {number} y Vertical position of the popup window
  16462. */
  16463. Popup.prototype.setPosition = function(x, y) {
  16464. this.x = parseInt(x);
  16465. this.y = parseInt(y);
  16466. };
  16467. /**
  16468. * Set the text for the popup window. This can be HTML code
  16469. * @param {string} text
  16470. */
  16471. Popup.prototype.setText = function(text) {
  16472. this.frame.innerHTML = text;
  16473. };
  16474. /**
  16475. * Show the popup window
  16476. * @param {boolean} show Optional. Show or hide the window
  16477. */
  16478. Popup.prototype.show = function (show) {
  16479. if (show === undefined) {
  16480. show = true;
  16481. }
  16482. if (show) {
  16483. var height = this.frame.clientHeight;
  16484. var width = this.frame.clientWidth;
  16485. var maxHeight = this.frame.parentNode.clientHeight;
  16486. var maxWidth = this.frame.parentNode.clientWidth;
  16487. var top = (this.y - height);
  16488. if (top + height + this.padding > maxHeight) {
  16489. top = maxHeight - height - this.padding;
  16490. }
  16491. if (top < this.padding) {
  16492. top = this.padding;
  16493. }
  16494. var left = this.x;
  16495. if (left + width + this.padding > maxWidth) {
  16496. left = maxWidth - width - this.padding;
  16497. }
  16498. if (left < this.padding) {
  16499. left = this.padding;
  16500. }
  16501. this.frame.style.left = left + "px";
  16502. this.frame.style.top = top + "px";
  16503. this.frame.style.visibility = "visible";
  16504. }
  16505. else {
  16506. this.hide();
  16507. }
  16508. };
  16509. /**
  16510. * Hide the popup window
  16511. */
  16512. Popup.prototype.hide = function () {
  16513. this.frame.style.visibility = "hidden";
  16514. };
  16515. module.exports = Popup;
  16516. /***/ },
  16517. /* 39 */
  16518. /***/ function(module, exports, __webpack_require__) {
  16519. /**
  16520. * Parse a text source containing data in DOT language into a JSON object.
  16521. * The object contains two lists: one with nodes and one with edges.
  16522. *
  16523. * DOT language reference: http://www.graphviz.org/doc/info/lang.html
  16524. *
  16525. * @param {String} data Text containing a graph in DOT-notation
  16526. * @return {Object} graph An object containing two parameters:
  16527. * {Object[]} nodes
  16528. * {Object[]} edges
  16529. */
  16530. function parseDOT (data) {
  16531. dot = data;
  16532. return parseGraph();
  16533. }
  16534. // token types enumeration
  16535. var TOKENTYPE = {
  16536. NULL : 0,
  16537. DELIMITER : 1,
  16538. IDENTIFIER: 2,
  16539. UNKNOWN : 3
  16540. };
  16541. // map with all delimiters
  16542. var DELIMITERS = {
  16543. '{': true,
  16544. '}': true,
  16545. '[': true,
  16546. ']': true,
  16547. ';': true,
  16548. '=': true,
  16549. ',': true,
  16550. '->': true,
  16551. '--': true
  16552. };
  16553. var dot = ''; // current dot file
  16554. var index = 0; // current index in dot file
  16555. var c = ''; // current token character in expr
  16556. var token = ''; // current token
  16557. var tokenType = TOKENTYPE.NULL; // type of the token
  16558. /**
  16559. * Get the first character from the dot file.
  16560. * The character is stored into the char c. If the end of the dot file is
  16561. * reached, the function puts an empty string in c.
  16562. */
  16563. function first() {
  16564. index = 0;
  16565. c = dot.charAt(0);
  16566. }
  16567. /**
  16568. * Get the next character from the dot file.
  16569. * The character is stored into the char c. If the end of the dot file is
  16570. * reached, the function puts an empty string in c.
  16571. */
  16572. function next() {
  16573. index++;
  16574. c = dot.charAt(index);
  16575. }
  16576. /**
  16577. * Preview the next character from the dot file.
  16578. * @return {String} cNext
  16579. */
  16580. function nextPreview() {
  16581. return dot.charAt(index + 1);
  16582. }
  16583. /**
  16584. * Test whether given character is alphabetic or numeric
  16585. * @param {String} c
  16586. * @return {Boolean} isAlphaNumeric
  16587. */
  16588. var regexAlphaNumeric = /[a-zA-Z_0-9.:#]/;
  16589. function isAlphaNumeric(c) {
  16590. return regexAlphaNumeric.test(c);
  16591. }
  16592. /**
  16593. * Merge all properties of object b into object b
  16594. * @param {Object} a
  16595. * @param {Object} b
  16596. * @return {Object} a
  16597. */
  16598. function merge (a, b) {
  16599. if (!a) {
  16600. a = {};
  16601. }
  16602. if (b) {
  16603. for (var name in b) {
  16604. if (b.hasOwnProperty(name)) {
  16605. a[name] = b[name];
  16606. }
  16607. }
  16608. }
  16609. return a;
  16610. }
  16611. /**
  16612. * Set a value in an object, where the provided parameter name can be a
  16613. * path with nested parameters. For example:
  16614. *
  16615. * var obj = {a: 2};
  16616. * setValue(obj, 'b.c', 3); // obj = {a: 2, b: {c: 3}}
  16617. *
  16618. * @param {Object} obj
  16619. * @param {String} path A parameter name or dot-separated parameter path,
  16620. * like "color.highlight.border".
  16621. * @param {*} value
  16622. */
  16623. function setValue(obj, path, value) {
  16624. var keys = path.split('.');
  16625. var o = obj;
  16626. while (keys.length) {
  16627. var key = keys.shift();
  16628. if (keys.length) {
  16629. // this isn't the end point
  16630. if (!o[key]) {
  16631. o[key] = {};
  16632. }
  16633. o = o[key];
  16634. }
  16635. else {
  16636. // this is the end point
  16637. o[key] = value;
  16638. }
  16639. }
  16640. }
  16641. /**
  16642. * Add a node to a graph object. If there is already a node with
  16643. * the same id, their attributes will be merged.
  16644. * @param {Object} graph
  16645. * @param {Object} node
  16646. */
  16647. function addNode(graph, node) {
  16648. var i, len;
  16649. var current = null;
  16650. // find root graph (in case of subgraph)
  16651. var graphs = [graph]; // list with all graphs from current graph to root graph
  16652. var root = graph;
  16653. while (root.parent) {
  16654. graphs.push(root.parent);
  16655. root = root.parent;
  16656. }
  16657. // find existing node (at root level) by its id
  16658. if (root.nodes) {
  16659. for (i = 0, len = root.nodes.length; i < len; i++) {
  16660. if (node.id === root.nodes[i].id) {
  16661. current = root.nodes[i];
  16662. break;
  16663. }
  16664. }
  16665. }
  16666. if (!current) {
  16667. // this is a new node
  16668. current = {
  16669. id: node.id
  16670. };
  16671. if (graph.node) {
  16672. // clone default attributes
  16673. current.attr = merge(current.attr, graph.node);
  16674. }
  16675. }
  16676. // add node to this (sub)graph and all its parent graphs
  16677. for (i = graphs.length - 1; i >= 0; i--) {
  16678. var g = graphs[i];
  16679. if (!g.nodes) {
  16680. g.nodes = [];
  16681. }
  16682. if (g.nodes.indexOf(current) == -1) {
  16683. g.nodes.push(current);
  16684. }
  16685. }
  16686. // merge attributes
  16687. if (node.attr) {
  16688. current.attr = merge(current.attr, node.attr);
  16689. }
  16690. }
  16691. /**
  16692. * Add an edge to a graph object
  16693. * @param {Object} graph
  16694. * @param {Object} edge
  16695. */
  16696. function addEdge(graph, edge) {
  16697. if (!graph.edges) {
  16698. graph.edges = [];
  16699. }
  16700. graph.edges.push(edge);
  16701. if (graph.edge) {
  16702. var attr = merge({}, graph.edge); // clone default attributes
  16703. edge.attr = merge(attr, edge.attr); // merge attributes
  16704. }
  16705. }
  16706. /**
  16707. * Create an edge to a graph object
  16708. * @param {Object} graph
  16709. * @param {String | Number | Object} from
  16710. * @param {String | Number | Object} to
  16711. * @param {String} type
  16712. * @param {Object | null} attr
  16713. * @return {Object} edge
  16714. */
  16715. function createEdge(graph, from, to, type, attr) {
  16716. var edge = {
  16717. from: from,
  16718. to: to,
  16719. type: type
  16720. };
  16721. if (graph.edge) {
  16722. edge.attr = merge({}, graph.edge); // clone default attributes
  16723. }
  16724. edge.attr = merge(edge.attr || {}, attr); // merge attributes
  16725. return edge;
  16726. }
  16727. /**
  16728. * Get next token in the current dot file.
  16729. * The token and token type are available as token and tokenType
  16730. */
  16731. function getToken() {
  16732. tokenType = TOKENTYPE.NULL;
  16733. token = '';
  16734. // skip over whitespaces
  16735. while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter
  16736. next();
  16737. }
  16738. do {
  16739. var isComment = false;
  16740. // skip comment
  16741. if (c == '#') {
  16742. // find the previous non-space character
  16743. var i = index - 1;
  16744. while (dot.charAt(i) == ' ' || dot.charAt(i) == '\t') {
  16745. i--;
  16746. }
  16747. if (dot.charAt(i) == '\n' || dot.charAt(i) == '') {
  16748. // the # is at the start of a line, this is indeed a line comment
  16749. while (c != '' && c != '\n') {
  16750. next();
  16751. }
  16752. isComment = true;
  16753. }
  16754. }
  16755. if (c == '/' && nextPreview() == '/') {
  16756. // skip line comment
  16757. while (c != '' && c != '\n') {
  16758. next();
  16759. }
  16760. isComment = true;
  16761. }
  16762. if (c == '/' && nextPreview() == '*') {
  16763. // skip block comment
  16764. while (c != '') {
  16765. if (c == '*' && nextPreview() == '/') {
  16766. // end of block comment found. skip these last two characters
  16767. next();
  16768. next();
  16769. break;
  16770. }
  16771. else {
  16772. next();
  16773. }
  16774. }
  16775. isComment = true;
  16776. }
  16777. // skip over whitespaces
  16778. while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter
  16779. next();
  16780. }
  16781. }
  16782. while (isComment);
  16783. // check for end of dot file
  16784. if (c == '') {
  16785. // token is still empty
  16786. tokenType = TOKENTYPE.DELIMITER;
  16787. return;
  16788. }
  16789. // check for delimiters consisting of 2 characters
  16790. var c2 = c + nextPreview();
  16791. if (DELIMITERS[c2]) {
  16792. tokenType = TOKENTYPE.DELIMITER;
  16793. token = c2;
  16794. next();
  16795. next();
  16796. return;
  16797. }
  16798. // check for delimiters consisting of 1 character
  16799. if (DELIMITERS[c]) {
  16800. tokenType = TOKENTYPE.DELIMITER;
  16801. token = c;
  16802. next();
  16803. return;
  16804. }
  16805. // check for an identifier (number or string)
  16806. // TODO: more precise parsing of numbers/strings (and the port separator ':')
  16807. if (isAlphaNumeric(c) || c == '-') {
  16808. token += c;
  16809. next();
  16810. while (isAlphaNumeric(c)) {
  16811. token += c;
  16812. next();
  16813. }
  16814. if (token == 'false') {
  16815. token = false; // convert to boolean
  16816. }
  16817. else if (token == 'true') {
  16818. token = true; // convert to boolean
  16819. }
  16820. else if (!isNaN(Number(token))) {
  16821. token = Number(token); // convert to number
  16822. }
  16823. tokenType = TOKENTYPE.IDENTIFIER;
  16824. return;
  16825. }
  16826. // check for a string enclosed by double quotes
  16827. if (c == '"') {
  16828. next();
  16829. while (c != '' && (c != '"' || (c == '"' && nextPreview() == '"'))) {
  16830. token += c;
  16831. if (c == '"') { // skip the escape character
  16832. next();
  16833. }
  16834. next();
  16835. }
  16836. if (c != '"') {
  16837. throw newSyntaxError('End of string " expected');
  16838. }
  16839. next();
  16840. tokenType = TOKENTYPE.IDENTIFIER;
  16841. return;
  16842. }
  16843. // something unknown is found, wrong characters, a syntax error
  16844. tokenType = TOKENTYPE.UNKNOWN;
  16845. while (c != '') {
  16846. token += c;
  16847. next();
  16848. }
  16849. throw new SyntaxError('Syntax error in part "' + chop(token, 30) + '"');
  16850. }
  16851. /**
  16852. * Parse a graph.
  16853. * @returns {Object} graph
  16854. */
  16855. function parseGraph() {
  16856. var graph = {};
  16857. first();
  16858. getToken();
  16859. // optional strict keyword
  16860. if (token == 'strict') {
  16861. graph.strict = true;
  16862. getToken();
  16863. }
  16864. // graph or digraph keyword
  16865. if (token == 'graph' || token == 'digraph') {
  16866. graph.type = token;
  16867. getToken();
  16868. }
  16869. // optional graph id
  16870. if (tokenType == TOKENTYPE.IDENTIFIER) {
  16871. graph.id = token;
  16872. getToken();
  16873. }
  16874. // open angle bracket
  16875. if (token != '{') {
  16876. throw newSyntaxError('Angle bracket { expected');
  16877. }
  16878. getToken();
  16879. // statements
  16880. parseStatements(graph);
  16881. // close angle bracket
  16882. if (token != '}') {
  16883. throw newSyntaxError('Angle bracket } expected');
  16884. }
  16885. getToken();
  16886. // end of file
  16887. if (token !== '') {
  16888. throw newSyntaxError('End of file expected');
  16889. }
  16890. getToken();
  16891. // remove temporary default properties
  16892. delete graph.node;
  16893. delete graph.edge;
  16894. delete graph.graph;
  16895. return graph;
  16896. }
  16897. /**
  16898. * Parse a list with statements.
  16899. * @param {Object} graph
  16900. */
  16901. function parseStatements (graph) {
  16902. while (token !== '' && token != '}') {
  16903. parseStatement(graph);
  16904. if (token == ';') {
  16905. getToken();
  16906. }
  16907. }
  16908. }
  16909. /**
  16910. * Parse a single statement. Can be a an attribute statement, node
  16911. * statement, a series of node statements and edge statements, or a
  16912. * parameter.
  16913. * @param {Object} graph
  16914. */
  16915. function parseStatement(graph) {
  16916. // parse subgraph
  16917. var subgraph = parseSubgraph(graph);
  16918. if (subgraph) {
  16919. // edge statements
  16920. parseEdge(graph, subgraph);
  16921. return;
  16922. }
  16923. // parse an attribute statement
  16924. var attr = parseAttributeStatement(graph);
  16925. if (attr) {
  16926. return;
  16927. }
  16928. // parse node
  16929. if (tokenType != TOKENTYPE.IDENTIFIER) {
  16930. throw newSyntaxError('Identifier expected');
  16931. }
  16932. var id = token; // id can be a string or a number
  16933. getToken();
  16934. if (token == '=') {
  16935. // id statement
  16936. getToken();
  16937. if (tokenType != TOKENTYPE.IDENTIFIER) {
  16938. throw newSyntaxError('Identifier expected');
  16939. }
  16940. graph[id] = token;
  16941. getToken();
  16942. // TODO: implement comma separated list with "a_list: ID=ID [','] [a_list] "
  16943. }
  16944. else {
  16945. parseNodeStatement(graph, id);
  16946. }
  16947. }
  16948. /**
  16949. * Parse a subgraph
  16950. * @param {Object} graph parent graph object
  16951. * @return {Object | null} subgraph
  16952. */
  16953. function parseSubgraph (graph) {
  16954. var subgraph = null;
  16955. // optional subgraph keyword
  16956. if (token == 'subgraph') {
  16957. subgraph = {};
  16958. subgraph.type = 'subgraph';
  16959. getToken();
  16960. // optional graph id
  16961. if (tokenType == TOKENTYPE.IDENTIFIER) {
  16962. subgraph.id = token;
  16963. getToken();
  16964. }
  16965. }
  16966. // open angle bracket
  16967. if (token == '{') {
  16968. getToken();
  16969. if (!subgraph) {
  16970. subgraph = {};
  16971. }
  16972. subgraph.parent = graph;
  16973. subgraph.node = graph.node;
  16974. subgraph.edge = graph.edge;
  16975. subgraph.graph = graph.graph;
  16976. // statements
  16977. parseStatements(subgraph);
  16978. // close angle bracket
  16979. if (token != '}') {
  16980. throw newSyntaxError('Angle bracket } expected');
  16981. }
  16982. getToken();
  16983. // remove temporary default properties
  16984. delete subgraph.node;
  16985. delete subgraph.edge;
  16986. delete subgraph.graph;
  16987. delete subgraph.parent;
  16988. // register at the parent graph
  16989. if (!graph.subgraphs) {
  16990. graph.subgraphs = [];
  16991. }
  16992. graph.subgraphs.push(subgraph);
  16993. }
  16994. return subgraph;
  16995. }
  16996. /**
  16997. * parse an attribute statement like "node [shape=circle fontSize=16]".
  16998. * Available keywords are 'node', 'edge', 'graph'.
  16999. * The previous list with default attributes will be replaced
  17000. * @param {Object} graph
  17001. * @returns {String | null} keyword Returns the name of the parsed attribute
  17002. * (node, edge, graph), or null if nothing
  17003. * is parsed.
  17004. */
  17005. function parseAttributeStatement (graph) {
  17006. // attribute statements
  17007. if (token == 'node') {
  17008. getToken();
  17009. // node attributes
  17010. graph.node = parseAttributeList();
  17011. return 'node';
  17012. }
  17013. else if (token == 'edge') {
  17014. getToken();
  17015. // edge attributes
  17016. graph.edge = parseAttributeList();
  17017. return 'edge';
  17018. }
  17019. else if (token == 'graph') {
  17020. getToken();
  17021. // graph attributes
  17022. graph.graph = parseAttributeList();
  17023. return 'graph';
  17024. }
  17025. return null;
  17026. }
  17027. /**
  17028. * parse a node statement
  17029. * @param {Object} graph
  17030. * @param {String | Number} id
  17031. */
  17032. function parseNodeStatement(graph, id) {
  17033. // node statement
  17034. var node = {
  17035. id: id
  17036. };
  17037. var attr = parseAttributeList();
  17038. if (attr) {
  17039. node.attr = attr;
  17040. }
  17041. addNode(graph, node);
  17042. // edge statements
  17043. parseEdge(graph, id);
  17044. }
  17045. /**
  17046. * Parse an edge or a series of edges
  17047. * @param {Object} graph
  17048. * @param {String | Number} from Id of the from node
  17049. */
  17050. function parseEdge(graph, from) {
  17051. while (token == '->' || token == '--') {
  17052. var to;
  17053. var type = token;
  17054. getToken();
  17055. var subgraph = parseSubgraph(graph);
  17056. if (subgraph) {
  17057. to = subgraph;
  17058. }
  17059. else {
  17060. if (tokenType != TOKENTYPE.IDENTIFIER) {
  17061. throw newSyntaxError('Identifier or subgraph expected');
  17062. }
  17063. to = token;
  17064. addNode(graph, {
  17065. id: to
  17066. });
  17067. getToken();
  17068. }
  17069. // parse edge attributes
  17070. var attr = parseAttributeList();
  17071. // create edge
  17072. var edge = createEdge(graph, from, to, type, attr);
  17073. addEdge(graph, edge);
  17074. from = to;
  17075. }
  17076. }
  17077. /**
  17078. * Parse a set with attributes,
  17079. * for example [label="1.000", shape=solid]
  17080. * @return {Object | null} attr
  17081. */
  17082. function parseAttributeList() {
  17083. var attr = null;
  17084. while (token == '[') {
  17085. getToken();
  17086. attr = {};
  17087. while (token !== '' && token != ']') {
  17088. if (tokenType != TOKENTYPE.IDENTIFIER) {
  17089. throw newSyntaxError('Attribute name expected');
  17090. }
  17091. var name = token;
  17092. getToken();
  17093. if (token != '=') {
  17094. throw newSyntaxError('Equal sign = expected');
  17095. }
  17096. getToken();
  17097. if (tokenType != TOKENTYPE.IDENTIFIER) {
  17098. throw newSyntaxError('Attribute value expected');
  17099. }
  17100. var value = token;
  17101. setValue(attr, name, value); // name can be a path
  17102. getToken();
  17103. if (token ==',') {
  17104. getToken();
  17105. }
  17106. }
  17107. if (token != ']') {
  17108. throw newSyntaxError('Bracket ] expected');
  17109. }
  17110. getToken();
  17111. }
  17112. return attr;
  17113. }
  17114. /**
  17115. * Create a syntax error with extra information on current token and index.
  17116. * @param {String} message
  17117. * @returns {SyntaxError} err
  17118. */
  17119. function newSyntaxError(message) {
  17120. return new SyntaxError(message + ', got "' + chop(token, 30) + '" (char ' + index + ')');
  17121. }
  17122. /**
  17123. * Chop off text after a maximum length
  17124. * @param {String} text
  17125. * @param {Number} maxLength
  17126. * @returns {String}
  17127. */
  17128. function chop (text, maxLength) {
  17129. return (text.length <= maxLength) ? text : (text.substr(0, 27) + '...');
  17130. }
  17131. /**
  17132. * Execute a function fn for each pair of elements in two arrays
  17133. * @param {Array | *} array1
  17134. * @param {Array | *} array2
  17135. * @param {function} fn
  17136. */
  17137. function forEach2(array1, array2, fn) {
  17138. if (array1 instanceof Array) {
  17139. array1.forEach(function (elem1) {
  17140. if (array2 instanceof Array) {
  17141. array2.forEach(function (elem2) {
  17142. fn(elem1, elem2);
  17143. });
  17144. }
  17145. else {
  17146. fn(elem1, array2);
  17147. }
  17148. });
  17149. }
  17150. else {
  17151. if (array2 instanceof Array) {
  17152. array2.forEach(function (elem2) {
  17153. fn(array1, elem2);
  17154. });
  17155. }
  17156. else {
  17157. fn(array1, array2);
  17158. }
  17159. }
  17160. }
  17161. /**
  17162. * Convert a string containing a graph in DOT language into a map containing
  17163. * with nodes and edges in the format of graph.
  17164. * @param {String} data Text containing a graph in DOT-notation
  17165. * @return {Object} graphData
  17166. */
  17167. function DOTToGraph (data) {
  17168. // parse the DOT file
  17169. var dotData = parseDOT(data);
  17170. var graphData = {
  17171. nodes: [],
  17172. edges: [],
  17173. options: {}
  17174. };
  17175. // copy the nodes
  17176. if (dotData.nodes) {
  17177. dotData.nodes.forEach(function (dotNode) {
  17178. var graphNode = {
  17179. id: dotNode.id,
  17180. label: String(dotNode.label || dotNode.id)
  17181. };
  17182. merge(graphNode, dotNode.attr);
  17183. if (graphNode.image) {
  17184. graphNode.shape = 'image';
  17185. }
  17186. graphData.nodes.push(graphNode);
  17187. });
  17188. }
  17189. // copy the edges
  17190. if (dotData.edges) {
  17191. /**
  17192. * Convert an edge in DOT format to an edge with VisGraph format
  17193. * @param {Object} dotEdge
  17194. * @returns {Object} graphEdge
  17195. */
  17196. function convertEdge(dotEdge) {
  17197. var graphEdge = {
  17198. from: dotEdge.from,
  17199. to: dotEdge.to
  17200. };
  17201. merge(graphEdge, dotEdge.attr);
  17202. graphEdge.style = (dotEdge.type == '->') ? 'arrow' : 'line';
  17203. return graphEdge;
  17204. }
  17205. dotData.edges.forEach(function (dotEdge) {
  17206. var from, to;
  17207. if (dotEdge.from instanceof Object) {
  17208. from = dotEdge.from.nodes;
  17209. }
  17210. else {
  17211. from = {
  17212. id: dotEdge.from
  17213. }
  17214. }
  17215. if (dotEdge.to instanceof Object) {
  17216. to = dotEdge.to.nodes;
  17217. }
  17218. else {
  17219. to = {
  17220. id: dotEdge.to
  17221. }
  17222. }
  17223. if (dotEdge.from instanceof Object && dotEdge.from.edges) {
  17224. dotEdge.from.edges.forEach(function (subEdge) {
  17225. var graphEdge = convertEdge(subEdge);
  17226. graphData.edges.push(graphEdge);
  17227. });
  17228. }
  17229. forEach2(from, to, function (from, to) {
  17230. var subEdge = createEdge(graphData, from.id, to.id, dotEdge.type, dotEdge.attr);
  17231. var graphEdge = convertEdge(subEdge);
  17232. graphData.edges.push(graphEdge);
  17233. });
  17234. if (dotEdge.to instanceof Object && dotEdge.to.edges) {
  17235. dotEdge.to.edges.forEach(function (subEdge) {
  17236. var graphEdge = convertEdge(subEdge);
  17237. graphData.edges.push(graphEdge);
  17238. });
  17239. }
  17240. });
  17241. }
  17242. // copy the options
  17243. if (dotData.attr) {
  17244. graphData.options = dotData.attr;
  17245. }
  17246. return graphData;
  17247. }
  17248. // exports
  17249. exports.parseDOT = parseDOT;
  17250. exports.DOTToGraph = DOTToGraph;
  17251. /***/ },
  17252. /* 40 */
  17253. /***/ function(module, exports, __webpack_require__) {
  17254. function parseGephi(gephiJSON, options) {
  17255. var edges = [];
  17256. var nodes = [];
  17257. this.options = {
  17258. edges: {
  17259. inheritColor: true
  17260. },
  17261. nodes: {
  17262. allowedToMove: false,
  17263. parseColor: false
  17264. }
  17265. };
  17266. if (options !== undefined) {
  17267. this.options.nodes['allowedToMove'] = options.allowedToMove | false;
  17268. this.options.nodes['parseColor'] = options.parseColor | false;
  17269. this.options.edges['inheritColor'] = options.inheritColor | true;
  17270. }
  17271. var gEdges = gephiJSON.edges;
  17272. var gNodes = gephiJSON.nodes;
  17273. for (var i = 0; i < gEdges.length; i++) {
  17274. var edge = {};
  17275. var gEdge = gEdges[i];
  17276. edge['id'] = gEdge.id;
  17277. edge['from'] = gEdge.source;
  17278. edge['to'] = gEdge.target;
  17279. edge['attributes'] = gEdge.attributes;
  17280. // edge['value'] = gEdge.attributes !== undefined ? gEdge.attributes.Weight : undefined;
  17281. // edge['width'] = edge['value'] !== undefined ? undefined : edgegEdge.size;
  17282. edge['color'] = gEdge.color;
  17283. edge['inheritColor'] = edge['color'] !== undefined ? false : this.options.inheritColor;
  17284. edges.push(edge);
  17285. }
  17286. for (var i = 0; i < gNodes.length; i++) {
  17287. var node = {};
  17288. var gNode = gNodes[i];
  17289. node['id'] = gNode.id;
  17290. node['attributes'] = gNode.attributes;
  17291. node['x'] = gNode.x;
  17292. node['y'] = gNode.y;
  17293. node['label'] = gNode.label;
  17294. if (this.options.nodes.parseColor == true) {
  17295. node['color'] = gNode.color;
  17296. }
  17297. else {
  17298. node['color'] = gNode.color !== undefined ? {background:gNode.color, border:gNode.color} : undefined;
  17299. }
  17300. node['radius'] = gNode.size;
  17301. node['allowedToMoveX'] = this.options.nodes.allowedToMove;
  17302. node['allowedToMoveY'] = this.options.nodes.allowedToMove;
  17303. nodes.push(node);
  17304. }
  17305. return {nodes:nodes, edges:edges};
  17306. }
  17307. exports.parseGephi = parseGephi;
  17308. /***/ },
  17309. /* 41 */
  17310. /***/ function(module, exports, __webpack_require__) {
  17311. // first check if moment.js is already loaded in the browser window, if so,
  17312. // use this instance. Else, load via commonjs.
  17313. module.exports = (typeof window !== 'undefined') && window['moment'] || __webpack_require__(52);
  17314. /***/ },
  17315. /* 42 */
  17316. /***/ function(module, exports, __webpack_require__) {
  17317. // Only load hammer.js when in a browser environment
  17318. // (loading hammer.js in a node.js environment gives errors)
  17319. if (typeof window !== 'undefined') {
  17320. module.exports = window['Hammer'] || __webpack_require__(53);
  17321. }
  17322. else {
  17323. module.exports = function () {
  17324. throw Error('hammer.js is only available in a browser, not in node.js.');
  17325. }
  17326. }
  17327. /***/ },
  17328. /* 43 */
  17329. /***/ function(module, exports, __webpack_require__) {
  17330. var Emitter = __webpack_require__(50);
  17331. var Hammer = __webpack_require__(42);
  17332. var util = __webpack_require__(1);
  17333. var DataSet = __webpack_require__(3);
  17334. var DataView = __webpack_require__(4);
  17335. var Range = __webpack_require__(15);
  17336. var TimeAxis = __webpack_require__(27);
  17337. var CurrentTime = __webpack_require__(19);
  17338. var CustomTime = __webpack_require__(20);
  17339. var ItemSet = __webpack_require__(24);
  17340. var Activator = __webpack_require__(49);
  17341. /**
  17342. * Create a timeline visualization
  17343. * @param {HTMLElement} container
  17344. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  17345. * @param {Object} [options] See Core.setOptions for the available options.
  17346. * @constructor
  17347. */
  17348. function Core () {}
  17349. // turn Core into an event emitter
  17350. Emitter(Core.prototype);
  17351. /**
  17352. * Create the main DOM for the Core: a root panel containing left, right,
  17353. * top, bottom, content, and background panel.
  17354. * @param {Element} container The container element where the Core will
  17355. * be attached.
  17356. * @private
  17357. */
  17358. Core.prototype._create = function (container) {
  17359. this.dom = {};
  17360. this.dom.root = document.createElement('div');
  17361. this.dom.background = document.createElement('div');
  17362. this.dom.backgroundVertical = document.createElement('div');
  17363. this.dom.backgroundHorizontal = document.createElement('div');
  17364. this.dom.centerContainer = document.createElement('div');
  17365. this.dom.leftContainer = document.createElement('div');
  17366. this.dom.rightContainer = document.createElement('div');
  17367. this.dom.center = document.createElement('div');
  17368. this.dom.left = document.createElement('div');
  17369. this.dom.right = document.createElement('div');
  17370. this.dom.top = document.createElement('div');
  17371. this.dom.bottom = document.createElement('div');
  17372. this.dom.shadowTop = document.createElement('div');
  17373. this.dom.shadowBottom = document.createElement('div');
  17374. this.dom.shadowTopLeft = document.createElement('div');
  17375. this.dom.shadowBottomLeft = document.createElement('div');
  17376. this.dom.shadowTopRight = document.createElement('div');
  17377. this.dom.shadowBottomRight = document.createElement('div');
  17378. this.dom.root.className = 'vis timeline root';
  17379. this.dom.background.className = 'vispanel background';
  17380. this.dom.backgroundVertical.className = 'vispanel background vertical';
  17381. this.dom.backgroundHorizontal.className = 'vispanel background horizontal';
  17382. this.dom.centerContainer.className = 'vispanel center';
  17383. this.dom.leftContainer.className = 'vispanel left';
  17384. this.dom.rightContainer.className = 'vispanel right';
  17385. this.dom.top.className = 'vispanel top';
  17386. this.dom.bottom.className = 'vispanel bottom';
  17387. this.dom.left.className = 'content';
  17388. this.dom.center.className = 'content';
  17389. this.dom.right.className = 'content';
  17390. this.dom.shadowTop.className = 'shadow top';
  17391. this.dom.shadowBottom.className = 'shadow bottom';
  17392. this.dom.shadowTopLeft.className = 'shadow top';
  17393. this.dom.shadowBottomLeft.className = 'shadow bottom';
  17394. this.dom.shadowTopRight.className = 'shadow top';
  17395. this.dom.shadowBottomRight.className = 'shadow bottom';
  17396. this.dom.root.appendChild(this.dom.background);
  17397. this.dom.root.appendChild(this.dom.backgroundVertical);
  17398. this.dom.root.appendChild(this.dom.backgroundHorizontal);
  17399. this.dom.root.appendChild(this.dom.centerContainer);
  17400. this.dom.root.appendChild(this.dom.leftContainer);
  17401. this.dom.root.appendChild(this.dom.rightContainer);
  17402. this.dom.root.appendChild(this.dom.top);
  17403. this.dom.root.appendChild(this.dom.bottom);
  17404. this.dom.centerContainer.appendChild(this.dom.center);
  17405. this.dom.leftContainer.appendChild(this.dom.left);
  17406. this.dom.rightContainer.appendChild(this.dom.right);
  17407. this.dom.centerContainer.appendChild(this.dom.shadowTop);
  17408. this.dom.centerContainer.appendChild(this.dom.shadowBottom);
  17409. this.dom.leftContainer.appendChild(this.dom.shadowTopLeft);
  17410. this.dom.leftContainer.appendChild(this.dom.shadowBottomLeft);
  17411. this.dom.rightContainer.appendChild(this.dom.shadowTopRight);
  17412. this.dom.rightContainer.appendChild(this.dom.shadowBottomRight);
  17413. this.on('rangechange', this.redraw.bind(this));
  17414. this.on('change', this.redraw.bind(this));
  17415. this.on('touch', this._onTouch.bind(this));
  17416. this.on('pinch', this._onPinch.bind(this));
  17417. this.on('dragstart', this._onDragStart.bind(this));
  17418. this.on('drag', this._onDrag.bind(this));
  17419. // create event listeners for all interesting events, these events will be
  17420. // emitted via emitter
  17421. this.hammer = Hammer(this.dom.root, {
  17422. preventDefault: true
  17423. });
  17424. this.listeners = {};
  17425. var me = this;
  17426. var events = [
  17427. 'touch', 'pinch',
  17428. 'tap', 'doubletap', 'hold',
  17429. 'dragstart', 'drag', 'dragend',
  17430. 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
  17431. ];
  17432. events.forEach(function (event) {
  17433. var listener = function () {
  17434. var args = [event].concat(Array.prototype.slice.call(arguments, 0));
  17435. if (me.isActive()) {
  17436. me.emit.apply(me, args);
  17437. }
  17438. };
  17439. me.hammer.on(event, listener);
  17440. me.listeners[event] = listener;
  17441. });
  17442. // size properties of each of the panels
  17443. this.props = {
  17444. root: {},
  17445. background: {},
  17446. centerContainer: {},
  17447. leftContainer: {},
  17448. rightContainer: {},
  17449. center: {},
  17450. left: {},
  17451. right: {},
  17452. top: {},
  17453. bottom: {},
  17454. border: {},
  17455. scrollTop: 0,
  17456. scrollTopMin: 0
  17457. };
  17458. this.touch = {}; // store state information needed for touch events
  17459. // attach the root panel to the provided container
  17460. if (!container) throw new Error('No container provided');
  17461. container.appendChild(this.dom.root);
  17462. };
  17463. /**
  17464. * Set options. Options will be passed to all components loaded in the Timeline.
  17465. * @param {Object} [options]
  17466. * {String} orientation
  17467. * Vertical orientation for the Timeline,
  17468. * can be 'bottom' (default) or 'top'.
  17469. * {String | Number} width
  17470. * Width for the timeline, a number in pixels or
  17471. * a css string like '1000px' or '75%'. '100%' by default.
  17472. * {String | Number} height
  17473. * Fixed height for the Timeline, a number in pixels or
  17474. * a css string like '400px' or '75%'. If undefined,
  17475. * The Timeline will automatically size such that
  17476. * its contents fit.
  17477. * {String | Number} minHeight
  17478. * Minimum height for the Timeline, a number in pixels or
  17479. * a css string like '400px' or '75%'.
  17480. * {String | Number} maxHeight
  17481. * Maximum height for the Timeline, a number in pixels or
  17482. * a css string like '400px' or '75%'.
  17483. * {Number | Date | String} start
  17484. * Start date for the visible window
  17485. * {Number | Date | String} end
  17486. * End date for the visible window
  17487. */
  17488. Core.prototype.setOptions = function (options) {
  17489. if (options) {
  17490. // copy the known options
  17491. var fields = ['width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'start', 'end', 'orientation', 'clickToUse', 'dataAttributes'];
  17492. util.selectiveExtend(fields, this.options, options);
  17493. if ('clickToUse' in options) {
  17494. if (options.clickToUse) {
  17495. this.activator = new Activator(this.dom.root);
  17496. }
  17497. else {
  17498. if (this.activator) {
  17499. this.activator.destroy();
  17500. delete this.activator;
  17501. }
  17502. }
  17503. }
  17504. // enable/disable autoResize
  17505. this._initAutoResize();
  17506. }
  17507. // propagate options to all components
  17508. this.components.forEach(function (component) {
  17509. component.setOptions(options);
  17510. });
  17511. // TODO: remove deprecation error one day (deprecated since version 0.8.0)
  17512. if (options && options.order) {
  17513. throw new Error('Option order is deprecated. There is no replacement for this feature.');
  17514. }
  17515. // redraw everything
  17516. this.redraw();
  17517. };
  17518. /**
  17519. * Returns true when the Timeline is active.
  17520. * @returns {boolean}
  17521. */
  17522. Core.prototype.isActive = function () {
  17523. return !this.activator || this.activator.active;
  17524. };
  17525. /**
  17526. * Destroy the Core, clean up all DOM elements and event listeners.
  17527. */
  17528. Core.prototype.destroy = function () {
  17529. // unbind datasets
  17530. this.clear();
  17531. // remove all event listeners
  17532. this.off();
  17533. // stop checking for changed size
  17534. this._stopAutoResize();
  17535. // remove from DOM
  17536. if (this.dom.root.parentNode) {
  17537. this.dom.root.parentNode.removeChild(this.dom.root);
  17538. }
  17539. this.dom = null;
  17540. // remove Activator
  17541. if (this.activator) {
  17542. this.activator.destroy();
  17543. delete this.activator;
  17544. }
  17545. // cleanup hammer touch events
  17546. for (var event in this.listeners) {
  17547. if (this.listeners.hasOwnProperty(event)) {
  17548. delete this.listeners[event];
  17549. }
  17550. }
  17551. this.listeners = null;
  17552. this.hammer = null;
  17553. // give all components the opportunity to cleanup
  17554. this.components.forEach(function (component) {
  17555. component.destroy();
  17556. });
  17557. this.body = null;
  17558. };
  17559. /**
  17560. * Set a custom time bar
  17561. * @param {Date} time
  17562. */
  17563. Core.prototype.setCustomTime = function (time) {
  17564. if (!this.customTime) {
  17565. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  17566. }
  17567. this.customTime.setCustomTime(time);
  17568. };
  17569. /**
  17570. * Retrieve the current custom time.
  17571. * @return {Date} customTime
  17572. */
  17573. Core.prototype.getCustomTime = function() {
  17574. if (!this.customTime) {
  17575. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  17576. }
  17577. return this.customTime.getCustomTime();
  17578. };
  17579. /**
  17580. * Get the id's of the currently visible items.
  17581. * @returns {Array} The ids of the visible items
  17582. */
  17583. Core.prototype.getVisibleItems = function() {
  17584. return this.itemSet && this.itemSet.getVisibleItems() || [];
  17585. };
  17586. /**
  17587. * Clear the Core. By Default, items, groups and options are cleared.
  17588. * Example usage:
  17589. *
  17590. * timeline.clear(); // clear items, groups, and options
  17591. * timeline.clear({options: true}); // clear options only
  17592. *
  17593. * @param {Object} [what] Optionally specify what to clear. By default:
  17594. * {items: true, groups: true, options: true}
  17595. */
  17596. Core.prototype.clear = function(what) {
  17597. // clear items
  17598. if (!what || what.items) {
  17599. this.setItems(null);
  17600. }
  17601. // clear groups
  17602. if (!what || what.groups) {
  17603. this.setGroups(null);
  17604. }
  17605. // clear options of timeline and of each of the components
  17606. if (!what || what.options) {
  17607. this.components.forEach(function (component) {
  17608. component.setOptions(component.defaultOptions);
  17609. });
  17610. this.setOptions(this.defaultOptions); // this will also do a redraw
  17611. }
  17612. };
  17613. /**
  17614. * Set Core window such that it fits all items
  17615. * @param {Object} [options] Available options:
  17616. * `animate: boolean | number`
  17617. * If true (default), the range is animated
  17618. * smoothly to the new window.
  17619. * If a number, the number is taken as duration
  17620. * for the animation. Default duration is 500 ms.
  17621. */
  17622. Core.prototype.fit = function(options) {
  17623. // apply the data range as range
  17624. var dataRange = this.getItemRange();
  17625. // add 5% space on both sides
  17626. var start = dataRange.min;
  17627. var end = dataRange.max;
  17628. if (start != null && end != null) {
  17629. var interval = (end.valueOf() - start.valueOf());
  17630. if (interval <= 0) {
  17631. // prevent an empty interval
  17632. interval = 24 * 60 * 60 * 1000; // 1 day
  17633. }
  17634. start = new Date(start.valueOf() - interval * 0.05);
  17635. end = new Date(end.valueOf() + interval * 0.05);
  17636. }
  17637. // skip range set if there is no start and end date
  17638. if (start === null && end === null) {
  17639. return;
  17640. }
  17641. var animate = (options && options.animate !== undefined) ? options.animate : true;
  17642. this.range.setRange(start, end, animate);
  17643. };
  17644. /**
  17645. * Set the visible window. Both parameters are optional, you can change only
  17646. * start or only end. Syntax:
  17647. *
  17648. * TimeLine.setWindow(start, end)
  17649. * TimeLine.setWindow(range)
  17650. *
  17651. * Where start and end can be a Date, number, or string, and range is an
  17652. * object with properties start and end.
  17653. *
  17654. * @param {Date | Number | String | Object} [start] Start date of visible window
  17655. * @param {Date | Number | String} [end] End date of visible window
  17656. * @param {Object} [options] Available options:
  17657. * `animate: boolean | number`
  17658. * If true (default), the range is animated
  17659. * smoothly to the new window.
  17660. * If a number, the number is taken as duration
  17661. * for the animation. Default duration is 500 ms.
  17662. */
  17663. Core.prototype.setWindow = function(start, end, options) {
  17664. var animate = (options && options.animate !== undefined) ? options.animate : true;
  17665. if (arguments.length == 1) {
  17666. var range = arguments[0];
  17667. this.range.setRange(range.start, range.end, animate);
  17668. }
  17669. else {
  17670. this.range.setRange(start, end, animate);
  17671. }
  17672. };
  17673. /**
  17674. * Move the window such that given time is centered on screen.
  17675. * @param {Date | Number | String} time
  17676. * @param {Object} [options] Available options:
  17677. * `animate: boolean | number`
  17678. * If true (default), the range is animated
  17679. * smoothly to the new window.
  17680. * If a number, the number is taken as duration
  17681. * for the animation. Default duration is 500 ms.
  17682. */
  17683. Core.prototype.moveTo = function(time, options) {
  17684. var interval = this.range.end - this.range.start;
  17685. var t = util.convert(time, 'Date').valueOf();
  17686. var start = t - interval / 2;
  17687. var end = t + interval / 2;
  17688. var animate = (options && options.animate !== undefined) ? options.animate : true;
  17689. this.range.setRange(start, end, animate);
  17690. };
  17691. /**
  17692. * Get the visible window
  17693. * @return {{start: Date, end: Date}} Visible range
  17694. */
  17695. Core.prototype.getWindow = function() {
  17696. var range = this.range.getRange();
  17697. return {
  17698. start: new Date(range.start),
  17699. end: new Date(range.end)
  17700. };
  17701. };
  17702. /**
  17703. * Force a redraw of the Core. Can be useful to manually redraw when
  17704. * option autoResize=false
  17705. */
  17706. Core.prototype.redraw = function() {
  17707. var resized = false,
  17708. options = this.options,
  17709. props = this.props,
  17710. dom = this.dom;
  17711. if (!dom) return; // when destroyed
  17712. // update class names
  17713. if (options.orientation == 'top') {
  17714. util.addClassName(dom.root, 'top');
  17715. util.removeClassName(dom.root, 'bottom');
  17716. }
  17717. else {
  17718. util.removeClassName(dom.root, 'top');
  17719. util.addClassName(dom.root, 'bottom');
  17720. }
  17721. // update root width and height options
  17722. dom.root.style.maxHeight = util.option.asSize(options.maxHeight, '');
  17723. dom.root.style.minHeight = util.option.asSize(options.minHeight, '');
  17724. dom.root.style.width = util.option.asSize(options.width, '');
  17725. // calculate border widths
  17726. props.border.left = (dom.centerContainer.offsetWidth - dom.centerContainer.clientWidth) / 2;
  17727. props.border.right = props.border.left;
  17728. props.border.top = (dom.centerContainer.offsetHeight - dom.centerContainer.clientHeight) / 2;
  17729. props.border.bottom = props.border.top;
  17730. var borderRootHeight= dom.root.offsetHeight - dom.root.clientHeight;
  17731. var borderRootWidth = dom.root.offsetWidth - dom.root.clientWidth;
  17732. // calculate the heights. If any of the side panels is empty, we set the height to
  17733. // minus the border width, such that the border will be invisible
  17734. props.center.height = dom.center.offsetHeight;
  17735. props.left.height = dom.left.offsetHeight;
  17736. props.right.height = dom.right.offsetHeight;
  17737. props.top.height = dom.top.clientHeight || -props.border.top;
  17738. props.bottom.height = dom.bottom.clientHeight || -props.border.bottom;
  17739. // TODO: compensate borders when any of the panels is empty.
  17740. // apply auto height
  17741. // TODO: only calculate autoHeight when needed (else we cause an extra reflow/repaint of the DOM)
  17742. var contentHeight = Math.max(props.left.height, props.center.height, props.right.height);
  17743. var autoHeight = props.top.height + contentHeight + props.bottom.height +
  17744. borderRootHeight + props.border.top + props.border.bottom;
  17745. dom.root.style.height = util.option.asSize(options.height, autoHeight + 'px');
  17746. // calculate heights of the content panels
  17747. props.root.height = dom.root.offsetHeight;
  17748. props.background.height = props.root.height - borderRootHeight;
  17749. var containerHeight = props.root.height - props.top.height - props.bottom.height -
  17750. borderRootHeight;
  17751. props.centerContainer.height = containerHeight;
  17752. props.leftContainer.height = containerHeight;
  17753. props.rightContainer.height = props.leftContainer.height;
  17754. // calculate the widths of the panels
  17755. props.root.width = dom.root.offsetWidth;
  17756. props.background.width = props.root.width - borderRootWidth;
  17757. props.left.width = dom.leftContainer.clientWidth || -props.border.left;
  17758. props.leftContainer.width = props.left.width;
  17759. props.right.width = dom.rightContainer.clientWidth || -props.border.right;
  17760. props.rightContainer.width = props.right.width;
  17761. var centerWidth = props.root.width - props.left.width - props.right.width - borderRootWidth;
  17762. props.center.width = centerWidth;
  17763. props.centerContainer.width = centerWidth;
  17764. props.top.width = centerWidth;
  17765. props.bottom.width = centerWidth;
  17766. // resize the panels
  17767. dom.background.style.height = props.background.height + 'px';
  17768. dom.backgroundVertical.style.height = props.background.height + 'px';
  17769. dom.backgroundHorizontal.style.height = props.centerContainer.height + 'px';
  17770. dom.centerContainer.style.height = props.centerContainer.height + 'px';
  17771. dom.leftContainer.style.height = props.leftContainer.height + 'px';
  17772. dom.rightContainer.style.height = props.rightContainer.height + 'px';
  17773. dom.background.style.width = props.background.width + 'px';
  17774. dom.backgroundVertical.style.width = props.centerContainer.width + 'px';
  17775. dom.backgroundHorizontal.style.width = props.background.width + 'px';
  17776. dom.centerContainer.style.width = props.center.width + 'px';
  17777. dom.top.style.width = props.top.width + 'px';
  17778. dom.bottom.style.width = props.bottom.width + 'px';
  17779. // reposition the panels
  17780. dom.background.style.left = '0';
  17781. dom.background.style.top = '0';
  17782. dom.backgroundVertical.style.left = (props.left.width + props.border.left) + 'px';
  17783. dom.backgroundVertical.style.top = '0';
  17784. dom.backgroundHorizontal.style.left = '0';
  17785. dom.backgroundHorizontal.style.top = props.top.height + 'px';
  17786. dom.centerContainer.style.left = props.left.width + 'px';
  17787. dom.centerContainer.style.top = props.top.height + 'px';
  17788. dom.leftContainer.style.left = '0';
  17789. dom.leftContainer.style.top = props.top.height + 'px';
  17790. dom.rightContainer.style.left = (props.left.width + props.center.width) + 'px';
  17791. dom.rightContainer.style.top = props.top.height + 'px';
  17792. dom.top.style.left = props.left.width + 'px';
  17793. dom.top.style.top = '0';
  17794. dom.bottom.style.left = props.left.width + 'px';
  17795. dom.bottom.style.top = (props.top.height + props.centerContainer.height) + 'px';
  17796. // update the scrollTop, feasible range for the offset can be changed
  17797. // when the height of the Core or of the contents of the center changed
  17798. this._updateScrollTop();
  17799. // reposition the scrollable contents
  17800. var offset = this.props.scrollTop;
  17801. if (options.orientation == 'bottom') {
  17802. offset += Math.max(this.props.centerContainer.height - this.props.center.height -
  17803. this.props.border.top - this.props.border.bottom, 0);
  17804. }
  17805. dom.center.style.left = '0';
  17806. dom.center.style.top = offset + 'px';
  17807. dom.left.style.left = '0';
  17808. dom.left.style.top = offset + 'px';
  17809. dom.right.style.left = '0';
  17810. dom.right.style.top = offset + 'px';
  17811. // show shadows when vertical scrolling is available
  17812. var visibilityTop = this.props.scrollTop == 0 ? 'hidden' : '';
  17813. var visibilityBottom = this.props.scrollTop == this.props.scrollTopMin ? 'hidden' : '';
  17814. dom.shadowTop.style.visibility = visibilityTop;
  17815. dom.shadowBottom.style.visibility = visibilityBottom;
  17816. dom.shadowTopLeft.style.visibility = visibilityTop;
  17817. dom.shadowBottomLeft.style.visibility = visibilityBottom;
  17818. dom.shadowTopRight.style.visibility = visibilityTop;
  17819. dom.shadowBottomRight.style.visibility = visibilityBottom;
  17820. // redraw all components
  17821. this.components.forEach(function (component) {
  17822. resized = component.redraw() || resized;
  17823. });
  17824. if (resized) {
  17825. // keep repainting until all sizes are settled
  17826. this.redraw();
  17827. }
  17828. };
  17829. // TODO: deprecated since version 1.1.0, remove some day
  17830. Core.prototype.repaint = function () {
  17831. throw new Error('Function repaint is deprecated. Use redraw instead.');
  17832. };
  17833. /**
  17834. * Set a current time. This can be used for example to ensure that a client's
  17835. * time is synchronized with a shared server time.
  17836. * Only applicable when option `showCurrentTime` is true.
  17837. * @param {Date | String | Number} time A Date, unix timestamp, or
  17838. * ISO date string.
  17839. */
  17840. Core.prototype.setCurrentTime = function(time) {
  17841. if (!this.currentTime) {
  17842. throw new Error('Option showCurrentTime must be true');
  17843. }
  17844. this.currentTime.setCurrentTime(time);
  17845. };
  17846. /**
  17847. * Get the current time.
  17848. * Only applicable when option `showCurrentTime` is true.
  17849. * @return {Date} Returns the current time.
  17850. */
  17851. Core.prototype.getCurrentTime = function() {
  17852. if (!this.currentTime) {
  17853. throw new Error('Option showCurrentTime must be true');
  17854. }
  17855. return this.currentTime.getCurrentTime();
  17856. };
  17857. /**
  17858. * Convert a position on screen (pixels) to a datetime
  17859. * @param {int} x Position on the screen in pixels
  17860. * @return {Date} time The datetime the corresponds with given position x
  17861. * @private
  17862. */
  17863. // TODO: move this function to Range
  17864. Core.prototype._toTime = function(x) {
  17865. var conversion = this.range.conversion(this.props.center.width);
  17866. return new Date(x / conversion.scale + conversion.offset);
  17867. };
  17868. /**
  17869. * Convert a position on the global screen (pixels) to a datetime
  17870. * @param {int} x Position on the screen in pixels
  17871. * @return {Date} time The datetime the corresponds with given position x
  17872. * @private
  17873. */
  17874. // TODO: move this function to Range
  17875. Core.prototype._toGlobalTime = function(x) {
  17876. var conversion = this.range.conversion(this.props.root.width);
  17877. return new Date(x / conversion.scale + conversion.offset);
  17878. };
  17879. /**
  17880. * Convert a datetime (Date object) into a position on the screen
  17881. * @param {Date} time A date
  17882. * @return {int} x The position on the screen in pixels which corresponds
  17883. * with the given date.
  17884. * @private
  17885. */
  17886. // TODO: move this function to Range
  17887. Core.prototype._toScreen = function(time) {
  17888. var conversion = this.range.conversion(this.props.center.width);
  17889. return (time.valueOf() - conversion.offset) * conversion.scale;
  17890. };
  17891. /**
  17892. * Convert a datetime (Date object) into a position on the root
  17893. * This is used to get the pixel density estimate for the screen, not the center panel
  17894. * @param {Date} time A date
  17895. * @return {int} x The position on root in pixels which corresponds
  17896. * with the given date.
  17897. * @private
  17898. */
  17899. // TODO: move this function to Range
  17900. Core.prototype._toGlobalScreen = function(time) {
  17901. var conversion = this.range.conversion(this.props.root.width);
  17902. return (time.valueOf() - conversion.offset) * conversion.scale;
  17903. };
  17904. /**
  17905. * Initialize watching when option autoResize is true
  17906. * @private
  17907. */
  17908. Core.prototype._initAutoResize = function () {
  17909. if (this.options.autoResize == true) {
  17910. this._startAutoResize();
  17911. }
  17912. else {
  17913. this._stopAutoResize();
  17914. }
  17915. };
  17916. /**
  17917. * Watch for changes in the size of the container. On resize, the Panel will
  17918. * automatically redraw itself.
  17919. * @private
  17920. */
  17921. Core.prototype._startAutoResize = function () {
  17922. var me = this;
  17923. this._stopAutoResize();
  17924. this._onResize = function() {
  17925. if (me.options.autoResize != true) {
  17926. // stop watching when the option autoResize is changed to false
  17927. me._stopAutoResize();
  17928. return;
  17929. }
  17930. if (me.dom.root) {
  17931. // check whether the frame is resized
  17932. // Note: we compare offsetWidth here, not clientWidth. For some reason,
  17933. // IE does not restore the clientWidth from 0 to the actual width after
  17934. // changing the timeline's container display style from none to visible
  17935. if ((me.dom.root.offsetWidth != me.props.lastWidth) ||
  17936. (me.dom.root.offsetHeight != me.props.lastHeight)) {
  17937. me.props.lastWidth = me.dom.root.offsetWidth;
  17938. me.props.lastHeight = me.dom.root.offsetHeight;
  17939. me.emit('change');
  17940. }
  17941. }
  17942. };
  17943. // add event listener to window resize
  17944. util.addEventListener(window, 'resize', this._onResize);
  17945. this.watchTimer = setInterval(this._onResize, 1000);
  17946. };
  17947. /**
  17948. * Stop watching for a resize of the frame.
  17949. * @private
  17950. */
  17951. Core.prototype._stopAutoResize = function () {
  17952. if (this.watchTimer) {
  17953. clearInterval(this.watchTimer);
  17954. this.watchTimer = undefined;
  17955. }
  17956. // remove event listener on window.resize
  17957. util.removeEventListener(window, 'resize', this._onResize);
  17958. this._onResize = null;
  17959. };
  17960. /**
  17961. * Start moving the timeline vertically
  17962. * @param {Event} event
  17963. * @private
  17964. */
  17965. Core.prototype._onTouch = function (event) {
  17966. this.touch.allowDragging = true;
  17967. };
  17968. /**
  17969. * Start moving the timeline vertically
  17970. * @param {Event} event
  17971. * @private
  17972. */
  17973. Core.prototype._onPinch = function (event) {
  17974. this.touch.allowDragging = false;
  17975. };
  17976. /**
  17977. * Start moving the timeline vertically
  17978. * @param {Event} event
  17979. * @private
  17980. */
  17981. Core.prototype._onDragStart = function (event) {
  17982. this.touch.initialScrollTop = this.props.scrollTop;
  17983. };
  17984. /**
  17985. * Move the timeline vertically
  17986. * @param {Event} event
  17987. * @private
  17988. */
  17989. Core.prototype._onDrag = function (event) {
  17990. // refuse to drag when we where pinching to prevent the timeline make a jump
  17991. // when releasing the fingers in opposite order from the touch screen
  17992. if (!this.touch.allowDragging) return;
  17993. var delta = event.gesture.deltaY;
  17994. var oldScrollTop = this._getScrollTop();
  17995. var newScrollTop = this._setScrollTop(this.touch.initialScrollTop + delta);
  17996. if (newScrollTop != oldScrollTop) {
  17997. this.redraw(); // TODO: this causes two redraws when dragging, the other is triggered by rangechange already
  17998. }
  17999. };
  18000. /**
  18001. * Apply a scrollTop
  18002. * @param {Number} scrollTop
  18003. * @returns {Number} scrollTop Returns the applied scrollTop
  18004. * @private
  18005. */
  18006. Core.prototype._setScrollTop = function (scrollTop) {
  18007. this.props.scrollTop = scrollTop;
  18008. this._updateScrollTop();
  18009. return this.props.scrollTop;
  18010. };
  18011. /**
  18012. * Update the current scrollTop when the height of the containers has been changed
  18013. * @returns {Number} scrollTop Returns the applied scrollTop
  18014. * @private
  18015. */
  18016. Core.prototype._updateScrollTop = function () {
  18017. // recalculate the scrollTopMin
  18018. var scrollTopMin = Math.min(this.props.centerContainer.height - this.props.center.height, 0); // is negative or zero
  18019. if (scrollTopMin != this.props.scrollTopMin) {
  18020. // in case of bottom orientation, change the scrollTop such that the contents
  18021. // do not move relative to the time axis at the bottom
  18022. if (this.options.orientation == 'bottom') {
  18023. this.props.scrollTop += (scrollTopMin - this.props.scrollTopMin);
  18024. }
  18025. this.props.scrollTopMin = scrollTopMin;
  18026. }
  18027. // limit the scrollTop to the feasible scroll range
  18028. if (this.props.scrollTop > 0) this.props.scrollTop = 0;
  18029. if (this.props.scrollTop < scrollTopMin) this.props.scrollTop = scrollTopMin;
  18030. return this.props.scrollTop;
  18031. };
  18032. /**
  18033. * Get the current scrollTop
  18034. * @returns {number} scrollTop
  18035. * @private
  18036. */
  18037. Core.prototype._getScrollTop = function () {
  18038. return this.props.scrollTop;
  18039. };
  18040. module.exports = Core;
  18041. /***/ },
  18042. /* 44 */
  18043. /***/ function(module, exports, __webpack_require__) {
  18044. var Hammer = __webpack_require__(42);
  18045. /**
  18046. * Fake a hammer.js gesture. Event can be a ScrollEvent or MouseMoveEvent
  18047. * @param {Element} element
  18048. * @param {Event} event
  18049. */
  18050. exports.fakeGesture = function(element, event) {
  18051. var eventType = null;
  18052. // for hammer.js 1.0.5
  18053. // var gesture = Hammer.event.collectEventData(this, eventType, event);
  18054. // for hammer.js 1.0.6+
  18055. var touches = Hammer.event.getTouchList(event, eventType);
  18056. var gesture = Hammer.event.collectEventData(this, eventType, touches, event);
  18057. // on IE in standards mode, no touches are recognized by hammer.js,
  18058. // resulting in NaN values for center.pageX and center.pageY
  18059. if (isNaN(gesture.center.pageX)) {
  18060. gesture.center.pageX = event.pageX;
  18061. }
  18062. if (isNaN(gesture.center.pageY)) {
  18063. gesture.center.pageY = event.pageY;
  18064. }
  18065. return gesture;
  18066. };
  18067. /***/ },
  18068. /* 45 */
  18069. /***/ function(module, exports, __webpack_require__) {
  18070. // English
  18071. exports['en'] = {
  18072. current: 'current',
  18073. time: 'time'
  18074. };
  18075. exports['en_EN'] = exports['en'];
  18076. exports['en_US'] = exports['en'];
  18077. // Dutch
  18078. exports['nl'] = {
  18079. custom: 'aangepaste',
  18080. time: 'tijd'
  18081. };
  18082. exports['nl_NL'] = exports['nl'];
  18083. exports['nl_BE'] = exports['nl'];
  18084. /***/ },
  18085. /* 46 */
  18086. /***/ function(module, exports, __webpack_require__) {
  18087. // English
  18088. exports['en'] = {
  18089. edit: 'Edit',
  18090. del: 'Delete selected',
  18091. back: 'Back',
  18092. addNode: 'Add Node',
  18093. addEdge: 'Add Edge',
  18094. editNode: 'Edit Node',
  18095. editEdge: 'Edit Edge',
  18096. addDescription: 'Click in an empty space to place a new node.',
  18097. edgeDescription: 'Click on a node and drag the edge to another node to connect them.',
  18098. editEdgeDescription: 'Click on the control points and drag them to a node to connect to it.',
  18099. createEdgeError: 'Cannot link edges to a cluster.',
  18100. deleteClusterError: 'Clusters cannot be deleted.'
  18101. };
  18102. exports['en_EN'] = exports['en'];
  18103. exports['en_US'] = exports['en'];
  18104. // Dutch
  18105. exports['nl'] = {
  18106. edit: 'Wijzigen',
  18107. del: 'Selectie verwijderen',
  18108. back: 'Terug',
  18109. addNode: 'Node toevoegen',
  18110. addEdge: 'Link toevoegen',
  18111. editNode: 'Node wijzigen',
  18112. editEdge: 'Link wijzigen',
  18113. addDescription: 'Klik op een leeg gebied om een nieuwe node te maken.',
  18114. edgeDescription: 'Klik op een node en sleep de link naar een andere node om ze te verbinden.',
  18115. editEdgeDescription: 'Klik op de verbindingspunten en sleep ze naar een node om daarmee te verbinden.',
  18116. createEdgeError: 'Kan geen link maken naar een cluster.',
  18117. deleteClusterError: 'Clusters kunnen niet worden verwijderd.'
  18118. };
  18119. exports['nl_NL'] = exports['nl'];
  18120. exports['nl_BE'] = exports['nl'];
  18121. /***/ },
  18122. /* 47 */
  18123. /***/ function(module, exports, __webpack_require__) {
  18124. /**
  18125. * Canvas shapes used by Network
  18126. */
  18127. if (typeof CanvasRenderingContext2D !== 'undefined') {
  18128. /**
  18129. * Draw a circle shape
  18130. */
  18131. CanvasRenderingContext2D.prototype.circle = function(x, y, r) {
  18132. this.beginPath();
  18133. this.arc(x, y, r, 0, 2*Math.PI, false);
  18134. };
  18135. /**
  18136. * Draw a square shape
  18137. * @param {Number} x horizontal center
  18138. * @param {Number} y vertical center
  18139. * @param {Number} r size, width and height of the square
  18140. */
  18141. CanvasRenderingContext2D.prototype.square = function(x, y, r) {
  18142. this.beginPath();
  18143. this.rect(x - r, y - r, r * 2, r * 2);
  18144. };
  18145. /**
  18146. * Draw a triangle shape
  18147. * @param {Number} x horizontal center
  18148. * @param {Number} y vertical center
  18149. * @param {Number} r radius, half the length of the sides of the triangle
  18150. */
  18151. CanvasRenderingContext2D.prototype.triangle = function(x, y, r) {
  18152. // http://en.wikipedia.org/wiki/Equilateral_triangle
  18153. this.beginPath();
  18154. var s = r * 2;
  18155. var s2 = s / 2;
  18156. var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
  18157. var h = Math.sqrt(s * s - s2 * s2); // height
  18158. this.moveTo(x, y - (h - ir));
  18159. this.lineTo(x + s2, y + ir);
  18160. this.lineTo(x - s2, y + ir);
  18161. this.lineTo(x, y - (h - ir));
  18162. this.closePath();
  18163. };
  18164. /**
  18165. * Draw a triangle shape in downward orientation
  18166. * @param {Number} x horizontal center
  18167. * @param {Number} y vertical center
  18168. * @param {Number} r radius
  18169. */
  18170. CanvasRenderingContext2D.prototype.triangleDown = function(x, y, r) {
  18171. // http://en.wikipedia.org/wiki/Equilateral_triangle
  18172. this.beginPath();
  18173. var s = r * 2;
  18174. var s2 = s / 2;
  18175. var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
  18176. var h = Math.sqrt(s * s - s2 * s2); // height
  18177. this.moveTo(x, y + (h - ir));
  18178. this.lineTo(x + s2, y - ir);
  18179. this.lineTo(x - s2, y - ir);
  18180. this.lineTo(x, y + (h - ir));
  18181. this.closePath();
  18182. };
  18183. /**
  18184. * Draw a star shape, a star with 5 points
  18185. * @param {Number} x horizontal center
  18186. * @param {Number} y vertical center
  18187. * @param {Number} r radius, half the length of the sides of the triangle
  18188. */
  18189. CanvasRenderingContext2D.prototype.star = function(x, y, r) {
  18190. // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/
  18191. this.beginPath();
  18192. for (var n = 0; n < 10; n++) {
  18193. var radius = (n % 2 === 0) ? r * 1.3 : r * 0.5;
  18194. this.lineTo(
  18195. x + radius * Math.sin(n * 2 * Math.PI / 10),
  18196. y - radius * Math.cos(n * 2 * Math.PI / 10)
  18197. );
  18198. }
  18199. this.closePath();
  18200. };
  18201. /**
  18202. * http://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas
  18203. */
  18204. CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
  18205. var r2d = Math.PI/180;
  18206. if( w - ( 2 * r ) < 0 ) { r = ( w / 2 ); } //ensure that the radius isn't too large for x
  18207. if( h - ( 2 * r ) < 0 ) { r = ( h / 2 ); } //ensure that the radius isn't too large for y
  18208. this.beginPath();
  18209. this.moveTo(x+r,y);
  18210. this.lineTo(x+w-r,y);
  18211. this.arc(x+w-r,y+r,r,r2d*270,r2d*360,false);
  18212. this.lineTo(x+w,y+h-r);
  18213. this.arc(x+w-r,y+h-r,r,0,r2d*90,false);
  18214. this.lineTo(x+r,y+h);
  18215. this.arc(x+r,y+h-r,r,r2d*90,r2d*180,false);
  18216. this.lineTo(x,y+r);
  18217. this.arc(x+r,y+r,r,r2d*180,r2d*270,false);
  18218. };
  18219. /**
  18220. * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  18221. */
  18222. CanvasRenderingContext2D.prototype.ellipse = function(x, y, w, h) {
  18223. var kappa = .5522848,
  18224. ox = (w / 2) * kappa, // control point offset horizontal
  18225. oy = (h / 2) * kappa, // control point offset vertical
  18226. xe = x + w, // x-end
  18227. ye = y + h, // y-end
  18228. xm = x + w / 2, // x-middle
  18229. ym = y + h / 2; // y-middle
  18230. this.beginPath();
  18231. this.moveTo(x, ym);
  18232. this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  18233. this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  18234. this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  18235. this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  18236. };
  18237. /**
  18238. * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  18239. */
  18240. CanvasRenderingContext2D.prototype.database = function(x, y, w, h) {
  18241. var f = 1/3;
  18242. var wEllipse = w;
  18243. var hEllipse = h * f;
  18244. var kappa = .5522848,
  18245. ox = (wEllipse / 2) * kappa, // control point offset horizontal
  18246. oy = (hEllipse / 2) * kappa, // control point offset vertical
  18247. xe = x + wEllipse, // x-end
  18248. ye = y + hEllipse, // y-end
  18249. xm = x + wEllipse / 2, // x-middle
  18250. ym = y + hEllipse / 2, // y-middle
  18251. ymb = y + (h - hEllipse/2), // y-midlle, bottom ellipse
  18252. yeb = y + h; // y-end, bottom ellipse
  18253. this.beginPath();
  18254. this.moveTo(xe, ym);
  18255. this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  18256. this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  18257. this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  18258. this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  18259. this.lineTo(xe, ymb);
  18260. this.bezierCurveTo(xe, ymb + oy, xm + ox, yeb, xm, yeb);
  18261. this.bezierCurveTo(xm - ox, yeb, x, ymb + oy, x, ymb);
  18262. this.lineTo(x, ym);
  18263. };
  18264. /**
  18265. * Draw an arrow point (no line)
  18266. */
  18267. CanvasRenderingContext2D.prototype.arrow = function(x, y, angle, length) {
  18268. // tail
  18269. var xt = x - length * Math.cos(angle);
  18270. var yt = y - length * Math.sin(angle);
  18271. // inner tail
  18272. // TODO: allow to customize different shapes
  18273. var xi = x - length * 0.9 * Math.cos(angle);
  18274. var yi = y - length * 0.9 * Math.sin(angle);
  18275. // left
  18276. var xl = xt + length / 3 * Math.cos(angle + 0.5 * Math.PI);
  18277. var yl = yt + length / 3 * Math.sin(angle + 0.5 * Math.PI);
  18278. // right
  18279. var xr = xt + length / 3 * Math.cos(angle - 0.5 * Math.PI);
  18280. var yr = yt + length / 3 * Math.sin(angle - 0.5 * Math.PI);
  18281. this.beginPath();
  18282. this.moveTo(x, y);
  18283. this.lineTo(xl, yl);
  18284. this.lineTo(xi, yi);
  18285. this.lineTo(xr, yr);
  18286. this.closePath();
  18287. };
  18288. /**
  18289. * Sets up the dashedLine functionality for drawing
  18290. * Original code came from http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
  18291. * @author David Jordan
  18292. * @date 2012-08-08
  18293. */
  18294. CanvasRenderingContext2D.prototype.dashedLine = function(x,y,x2,y2,dashArray){
  18295. if (!dashArray) dashArray=[10,5];
  18296. if (dashLength==0) dashLength = 0.001; // Hack for Safari
  18297. var dashCount = dashArray.length;
  18298. this.moveTo(x, y);
  18299. var dx = (x2-x), dy = (y2-y);
  18300. var slope = dy/dx;
  18301. var distRemaining = Math.sqrt( dx*dx + dy*dy );
  18302. var dashIndex=0, draw=true;
  18303. while (distRemaining>=0.1){
  18304. var dashLength = dashArray[dashIndex++%dashCount];
  18305. if (dashLength > distRemaining) dashLength = distRemaining;
  18306. var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) );
  18307. if (dx<0) xStep = -xStep;
  18308. x += xStep;
  18309. y += slope*xStep;
  18310. this[draw ? 'lineTo' : 'moveTo'](x,y);
  18311. distRemaining -= dashLength;
  18312. draw = !draw;
  18313. }
  18314. };
  18315. // TODO: add diamond shape
  18316. }
  18317. /***/ },
  18318. /* 48 */
  18319. /***/ function(module, exports, __webpack_require__) {
  18320. var PhysicsMixin = __webpack_require__(60);
  18321. var ClusterMixin = __webpack_require__(54);
  18322. var SectorsMixin = __webpack_require__(55);
  18323. var SelectionMixin = __webpack_require__(56);
  18324. var ManipulationMixin = __webpack_require__(57);
  18325. var NavigationMixin = __webpack_require__(58);
  18326. var HierarchicalLayoutMixin = __webpack_require__(59);
  18327. /**
  18328. * Load a mixin into the network object
  18329. *
  18330. * @param {Object} sourceVariable | this object has to contain functions.
  18331. * @private
  18332. */
  18333. exports._loadMixin = function (sourceVariable) {
  18334. for (var mixinFunction in sourceVariable) {
  18335. if (sourceVariable.hasOwnProperty(mixinFunction)) {
  18336. this[mixinFunction] = sourceVariable[mixinFunction];
  18337. }
  18338. }
  18339. };
  18340. /**
  18341. * removes a mixin from the network object.
  18342. *
  18343. * @param {Object} sourceVariable | this object has to contain functions.
  18344. * @private
  18345. */
  18346. exports._clearMixin = function (sourceVariable) {
  18347. for (var mixinFunction in sourceVariable) {
  18348. if (sourceVariable.hasOwnProperty(mixinFunction)) {
  18349. this[mixinFunction] = undefined;
  18350. }
  18351. }
  18352. };
  18353. /**
  18354. * Mixin the physics system and initialize the parameters required.
  18355. *
  18356. * @private
  18357. */
  18358. exports._loadPhysicsSystem = function () {
  18359. this._loadMixin(PhysicsMixin);
  18360. this._loadSelectedForceSolver();
  18361. if (this.constants.configurePhysics == true) {
  18362. this._loadPhysicsConfiguration();
  18363. }
  18364. };
  18365. /**
  18366. * Mixin the cluster system and initialize the parameters required.
  18367. *
  18368. * @private
  18369. */
  18370. exports._loadClusterSystem = function () {
  18371. this.clusterSession = 0;
  18372. this.hubThreshold = 5;
  18373. this._loadMixin(ClusterMixin);
  18374. };
  18375. /**
  18376. * Mixin the sector system and initialize the parameters required
  18377. *
  18378. * @private
  18379. */
  18380. exports._loadSectorSystem = function () {
  18381. this.sectors = {};
  18382. this.activeSector = ["default"];
  18383. this.sectors["active"] = {};
  18384. this.sectors["active"]["default"] = {"nodes": {},
  18385. "edges": {},
  18386. "nodeIndices": [],
  18387. "formationScale": 1.0,
  18388. "drawingNode": undefined };
  18389. this.sectors["frozen"] = {};
  18390. this.sectors["support"] = {"nodes": {},
  18391. "edges": {},
  18392. "nodeIndices": [],
  18393. "formationScale": 1.0,
  18394. "drawingNode": undefined };
  18395. this.nodeIndices = this.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields
  18396. this._loadMixin(SectorsMixin);
  18397. };
  18398. /**
  18399. * Mixin the selection system and initialize the parameters required
  18400. *
  18401. * @private
  18402. */
  18403. exports._loadSelectionSystem = function () {
  18404. this.selectionObj = {nodes: {}, edges: {}};
  18405. this._loadMixin(SelectionMixin);
  18406. };
  18407. /**
  18408. * Mixin the navigationUI (User Interface) system and initialize the parameters required
  18409. *
  18410. * @private
  18411. */
  18412. exports._loadManipulationSystem = function () {
  18413. // reset global variables -- these are used by the selection of nodes and edges.
  18414. this.blockConnectingEdgeSelection = false;
  18415. this.forceAppendSelection = false;
  18416. if (this.constants.dataManipulation.enabled == true) {
  18417. // load the manipulator HTML elements. All styling done in css.
  18418. if (this.manipulationDiv === undefined) {
  18419. this.manipulationDiv = document.createElement('div');
  18420. this.manipulationDiv.className = 'network-manipulationDiv';
  18421. this.manipulationDiv.id = 'network-manipulationDiv';
  18422. if (this.editMode == true) {
  18423. this.manipulationDiv.style.display = "block";
  18424. }
  18425. else {
  18426. this.manipulationDiv.style.display = "none";
  18427. }
  18428. this.frame.appendChild(this.manipulationDiv);
  18429. }
  18430. if (this.editModeDiv === undefined) {
  18431. this.editModeDiv = document.createElement('div');
  18432. this.editModeDiv.className = 'network-manipulation-editMode';
  18433. this.editModeDiv.id = 'network-manipulation-editMode';
  18434. if (this.editMode == true) {
  18435. this.editModeDiv.style.display = "none";
  18436. }
  18437. else {
  18438. this.editModeDiv.style.display = "block";
  18439. }
  18440. this.frame.appendChild(this.editModeDiv);
  18441. }
  18442. if (this.closeDiv === undefined) {
  18443. this.closeDiv = document.createElement('div');
  18444. this.closeDiv.className = 'network-manipulation-closeDiv';
  18445. this.closeDiv.id = 'network-manipulation-closeDiv';
  18446. this.closeDiv.style.display = this.manipulationDiv.style.display;
  18447. this.frame.appendChild(this.closeDiv);
  18448. }
  18449. // load the manipulation functions
  18450. this._loadMixin(ManipulationMixin);
  18451. // create the manipulator toolbar
  18452. this._createManipulatorBar();
  18453. }
  18454. else {
  18455. if (this.manipulationDiv !== undefined) {
  18456. // removes all the bindings and overloads
  18457. this._createManipulatorBar();
  18458. // remove the manipulation divs
  18459. this.containerElement.removeChild(this.manipulationDiv);
  18460. this.containerElement.removeChild(this.editModeDiv);
  18461. this.containerElement.removeChild(this.closeDiv);
  18462. this.manipulationDiv = undefined;
  18463. this.editModeDiv = undefined;
  18464. this.closeDiv = undefined;
  18465. // remove the mixin functions
  18466. this._clearMixin(ManipulationMixin);
  18467. }
  18468. }
  18469. };
  18470. /**
  18471. * Mixin the navigation (User Interface) system and initialize the parameters required
  18472. *
  18473. * @private
  18474. */
  18475. exports._loadNavigationControls = function () {
  18476. this._loadMixin(NavigationMixin);
  18477. // the clean function removes the button divs, this is done to remove the bindings.
  18478. this._cleanNavigation();
  18479. if (this.constants.navigation.enabled == true) {
  18480. this._loadNavigationElements();
  18481. }
  18482. };
  18483. /**
  18484. * Mixin the hierarchical layout system.
  18485. *
  18486. * @private
  18487. */
  18488. exports._loadHierarchySystem = function () {
  18489. this._loadMixin(HierarchicalLayoutMixin);
  18490. };
  18491. /***/ },
  18492. /* 49 */
  18493. /***/ function(module, exports, __webpack_require__) {
  18494. var mousetrap = __webpack_require__(51);
  18495. var Emitter = __webpack_require__(50);
  18496. var Hammer = __webpack_require__(42);
  18497. var util = __webpack_require__(1);
  18498. /**
  18499. * Turn an element into an clickToUse element.
  18500. * When not active, the element has a transparent overlay. When the overlay is
  18501. * clicked, the mode is changed to active.
  18502. * When active, the element is displayed with a blue border around it, and
  18503. * the interactive contents of the element can be used. When clicked outside
  18504. * the element, the elements mode is changed to inactive.
  18505. * @param {Element} container
  18506. * @constructor
  18507. */
  18508. function Activator(container) {
  18509. this.active = false;
  18510. this.dom = {
  18511. container: container
  18512. };
  18513. this.dom.overlay = document.createElement('div');
  18514. this.dom.overlay.className = 'overlay';
  18515. this.dom.container.appendChild(this.dom.overlay);
  18516. this.hammer = Hammer(this.dom.overlay, {prevent_default: false});
  18517. this.hammer.on('tap', this._onTapOverlay.bind(this));
  18518. // block all touch events (except tap)
  18519. var me = this;
  18520. var events = [
  18521. 'touch', 'pinch',
  18522. 'doubletap', 'hold',
  18523. 'dragstart', 'drag', 'dragend',
  18524. 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
  18525. ];
  18526. events.forEach(function (event) {
  18527. me.hammer.on(event, function (event) {
  18528. event.stopPropagation();
  18529. });
  18530. });
  18531. // attach a tap event to the window, in order to deactivate when clicking outside the timeline
  18532. this.windowHammer = Hammer(window, {prevent_default: false});
  18533. this.windowHammer.on('tap', function (event) {
  18534. // deactivate when clicked outside the container
  18535. if (!_hasParent(event.target, container)) {
  18536. me.deactivate();
  18537. }
  18538. });
  18539. // mousetrap listener only bounded when active)
  18540. this.escListener = this.deactivate.bind(this);
  18541. }
  18542. // turn into an event emitter
  18543. Emitter(Activator.prototype);
  18544. // The currently active activator
  18545. Activator.current = null;
  18546. /**
  18547. * Destroy the activator. Cleans up all created DOM and event listeners
  18548. */
  18549. Activator.prototype.destroy = function () {
  18550. this.deactivate();
  18551. // remove dom
  18552. this.dom.overlay.parentNode.removeChild(this.dom.overlay);
  18553. // cleanup hammer instances
  18554. this.hammer = null;
  18555. this.windowHammer = null;
  18556. // FIXME: cleaning up hammer instances doesn't work (Timeline not removed from memory)
  18557. };
  18558. /**
  18559. * Activate the element
  18560. * Overlay is hidden, element is decorated with a blue shadow border
  18561. */
  18562. Activator.prototype.activate = function () {
  18563. // we allow only one active activator at a time
  18564. if (Activator.current) {
  18565. Activator.current.deactivate();
  18566. }
  18567. Activator.current = this;
  18568. this.active = true;
  18569. this.dom.overlay.style.display = 'none';
  18570. util.addClassName(this.dom.container, 'vis-active');
  18571. this.emit('change');
  18572. this.emit('activate');
  18573. // ugly hack: bind ESC after emitting the events, as the Network rebinds all
  18574. // keyboard events on a 'change' event
  18575. mousetrap.bind('esc', this.escListener);
  18576. };
  18577. /**
  18578. * Deactivate the element
  18579. * Overlay is displayed on top of the element
  18580. */
  18581. Activator.prototype.deactivate = function () {
  18582. this.active = false;
  18583. this.dom.overlay.style.display = '';
  18584. util.removeClassName(this.dom.container, 'vis-active');
  18585. mousetrap.unbind('esc', this.escListener);
  18586. this.emit('change');
  18587. this.emit('deactivate');
  18588. };
  18589. /**
  18590. * Handle a tap event: activate the container
  18591. * @param event
  18592. * @private
  18593. */
  18594. Activator.prototype._onTapOverlay = function (event) {
  18595. // activate the container
  18596. this.activate();
  18597. event.stopPropagation();
  18598. };
  18599. /**
  18600. * Test whether the element has the requested parent element somewhere in
  18601. * its chain of parent nodes.
  18602. * @param {HTMLElement} element
  18603. * @param {HTMLElement} parent
  18604. * @returns {boolean} Returns true when the parent is found somewhere in the
  18605. * chain of parent nodes.
  18606. * @private
  18607. */
  18608. function _hasParent(element, parent) {
  18609. while (element) {
  18610. if (element === parent) {
  18611. return true
  18612. }
  18613. element = element.parentNode;
  18614. }
  18615. return false;
  18616. }
  18617. module.exports = Activator;
  18618. /***/ },
  18619. /* 50 */
  18620. /***/ function(module, exports, __webpack_require__) {
  18621. /**
  18622. * Expose `Emitter`.
  18623. */
  18624. module.exports = Emitter;
  18625. /**
  18626. * Initialize a new `Emitter`.
  18627. *
  18628. * @api public
  18629. */
  18630. function Emitter(obj) {
  18631. if (obj) return mixin(obj);
  18632. };
  18633. /**
  18634. * Mixin the emitter properties.
  18635. *
  18636. * @param {Object} obj
  18637. * @return {Object}
  18638. * @api private
  18639. */
  18640. function mixin(obj) {
  18641. for (var key in Emitter.prototype) {
  18642. obj[key] = Emitter.prototype[key];
  18643. }
  18644. return obj;
  18645. }
  18646. /**
  18647. * Listen on the given `event` with `fn`.
  18648. *
  18649. * @param {String} event
  18650. * @param {Function} fn
  18651. * @return {Emitter}
  18652. * @api public
  18653. */
  18654. Emitter.prototype.on =
  18655. Emitter.prototype.addEventListener = function(event, fn){
  18656. this._callbacks = this._callbacks || {};
  18657. (this._callbacks[event] = this._callbacks[event] || [])
  18658. .push(fn);
  18659. return this;
  18660. };
  18661. /**
  18662. * Adds an `event` listener that will be invoked a single
  18663. * time then automatically removed.
  18664. *
  18665. * @param {String} event
  18666. * @param {Function} fn
  18667. * @return {Emitter}
  18668. * @api public
  18669. */
  18670. Emitter.prototype.once = function(event, fn){
  18671. var self = this;
  18672. this._callbacks = this._callbacks || {};
  18673. function on() {
  18674. self.off(event, on);
  18675. fn.apply(this, arguments);
  18676. }
  18677. on.fn = fn;
  18678. this.on(event, on);
  18679. return this;
  18680. };
  18681. /**
  18682. * Remove the given callback for `event` or all
  18683. * registered callbacks.
  18684. *
  18685. * @param {String} event
  18686. * @param {Function} fn
  18687. * @return {Emitter}
  18688. * @api public
  18689. */
  18690. Emitter.prototype.off =
  18691. Emitter.prototype.removeListener =
  18692. Emitter.prototype.removeAllListeners =
  18693. Emitter.prototype.removeEventListener = function(event, fn){
  18694. this._callbacks = this._callbacks || {};
  18695. // all
  18696. if (0 == arguments.length) {
  18697. this._callbacks = {};
  18698. return this;
  18699. }
  18700. // specific event
  18701. var callbacks = this._callbacks[event];
  18702. if (!callbacks) return this;
  18703. // remove all handlers
  18704. if (1 == arguments.length) {
  18705. delete this._callbacks[event];
  18706. return this;
  18707. }
  18708. // remove specific handler
  18709. var cb;
  18710. for (var i = 0; i < callbacks.length; i++) {
  18711. cb = callbacks[i];
  18712. if (cb === fn || cb.fn === fn) {
  18713. callbacks.splice(i, 1);
  18714. break;
  18715. }
  18716. }
  18717. return this;
  18718. };
  18719. /**
  18720. * Emit `event` with the given args.
  18721. *
  18722. * @param {String} event
  18723. * @param {Mixed} ...
  18724. * @return {Emitter}
  18725. */
  18726. Emitter.prototype.emit = function(event){
  18727. this._callbacks = this._callbacks || {};
  18728. var args = [].slice.call(arguments, 1)
  18729. , callbacks = this._callbacks[event];
  18730. if (callbacks) {
  18731. callbacks = callbacks.slice(0);
  18732. for (var i = 0, len = callbacks.length; i < len; ++i) {
  18733. callbacks[i].apply(this, args);
  18734. }
  18735. }
  18736. return this;
  18737. };
  18738. /**
  18739. * Return array of callbacks for `event`.
  18740. *
  18741. * @param {String} event
  18742. * @return {Array}
  18743. * @api public
  18744. */
  18745. Emitter.prototype.listeners = function(event){
  18746. this._callbacks = this._callbacks || {};
  18747. return this._callbacks[event] || [];
  18748. };
  18749. /**
  18750. * Check if this emitter has `event` handlers.
  18751. *
  18752. * @param {String} event
  18753. * @return {Boolean}
  18754. * @api public
  18755. */
  18756. Emitter.prototype.hasListeners = function(event){
  18757. return !! this.listeners(event).length;
  18758. };
  18759. /***/ },
  18760. /* 51 */
  18761. /***/ function(module, exports, __webpack_require__) {
  18762. /**
  18763. * Copyright 2012 Craig Campbell
  18764. *
  18765. * Licensed under the Apache License, Version 2.0 (the "License");
  18766. * you may not use this file except in compliance with the License.
  18767. * You may obtain a copy of the License at
  18768. *
  18769. * http://www.apache.org/licenses/LICENSE-2.0
  18770. *
  18771. * Unless required by applicable law or agreed to in writing, software
  18772. * distributed under the License is distributed on an "AS IS" BASIS,
  18773. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18774. * See the License for the specific language governing permissions and
  18775. * limitations under the License.
  18776. *
  18777. * Mousetrap is a simple keyboard shortcut library for Javascript with
  18778. * no external dependencies
  18779. *
  18780. * @version 1.1.2
  18781. * @url craig.is/killing/mice
  18782. */
  18783. /**
  18784. * mapping of special keycodes to their corresponding keys
  18785. *
  18786. * everything in this dictionary cannot use keypress events
  18787. * so it has to be here to map to the correct keycodes for
  18788. * keyup/keydown events
  18789. *
  18790. * @type {Object}
  18791. */
  18792. var _MAP = {
  18793. 8: 'backspace',
  18794. 9: 'tab',
  18795. 13: 'enter',
  18796. 16: 'shift',
  18797. 17: 'ctrl',
  18798. 18: 'alt',
  18799. 20: 'capslock',
  18800. 27: 'esc',
  18801. 32: 'space',
  18802. 33: 'pageup',
  18803. 34: 'pagedown',
  18804. 35: 'end',
  18805. 36: 'home',
  18806. 37: 'left',
  18807. 38: 'up',
  18808. 39: 'right',
  18809. 40: 'down',
  18810. 45: 'ins',
  18811. 46: 'del',
  18812. 91: 'meta',
  18813. 93: 'meta',
  18814. 224: 'meta'
  18815. },
  18816. /**
  18817. * mapping for special characters so they can support
  18818. *
  18819. * this dictionary is only used incase you want to bind a
  18820. * keyup or keydown event to one of these keys
  18821. *
  18822. * @type {Object}
  18823. */
  18824. _KEYCODE_MAP = {
  18825. 106: '*',
  18826. 107: '+',
  18827. 109: '-',
  18828. 110: '.',
  18829. 111 : '/',
  18830. 186: ';',
  18831. 187: '=',
  18832. 188: ',',
  18833. 189: '-',
  18834. 190: '.',
  18835. 191: '/',
  18836. 192: '`',
  18837. 219: '[',
  18838. 220: '\\',
  18839. 221: ']',
  18840. 222: '\''
  18841. },
  18842. /**
  18843. * this is a mapping of keys that require shift on a US keypad
  18844. * back to the non shift equivelents
  18845. *
  18846. * this is so you can use keyup events with these keys
  18847. *
  18848. * note that this will only work reliably on US keyboards
  18849. *
  18850. * @type {Object}
  18851. */
  18852. _SHIFT_MAP = {
  18853. '~': '`',
  18854. '!': '1',
  18855. '@': '2',
  18856. '#': '3',
  18857. '$': '4',
  18858. '%': '5',
  18859. '^': '6',
  18860. '&': '7',
  18861. '*': '8',
  18862. '(': '9',
  18863. ')': '0',
  18864. '_': '-',
  18865. '+': '=',
  18866. ':': ';',
  18867. '\"': '\'',
  18868. '<': ',',
  18869. '>': '.',
  18870. '?': '/',
  18871. '|': '\\'
  18872. },
  18873. /**
  18874. * this is a list of special strings you can use to map
  18875. * to modifier keys when you specify your keyboard shortcuts
  18876. *
  18877. * @type {Object}
  18878. */
  18879. _SPECIAL_ALIASES = {
  18880. 'option': 'alt',
  18881. 'command': 'meta',
  18882. 'return': 'enter',
  18883. 'escape': 'esc'
  18884. },
  18885. /**
  18886. * variable to store the flipped version of _MAP from above
  18887. * needed to check if we should use keypress or not when no action
  18888. * is specified
  18889. *
  18890. * @type {Object|undefined}
  18891. */
  18892. _REVERSE_MAP,
  18893. /**
  18894. * a list of all the callbacks setup via Mousetrap.bind()
  18895. *
  18896. * @type {Object}
  18897. */
  18898. _callbacks = {},
  18899. /**
  18900. * direct map of string combinations to callbacks used for trigger()
  18901. *
  18902. * @type {Object}
  18903. */
  18904. _direct_map = {},
  18905. /**
  18906. * keeps track of what level each sequence is at since multiple
  18907. * sequences can start out with the same sequence
  18908. *
  18909. * @type {Object}
  18910. */
  18911. _sequence_levels = {},
  18912. /**
  18913. * variable to store the setTimeout call
  18914. *
  18915. * @type {null|number}
  18916. */
  18917. _reset_timer,
  18918. /**
  18919. * temporary state where we will ignore the next keyup
  18920. *
  18921. * @type {boolean|string}
  18922. */
  18923. _ignore_next_keyup = false,
  18924. /**
  18925. * are we currently inside of a sequence?
  18926. * type of action ("keyup" or "keydown" or "keypress") or false
  18927. *
  18928. * @type {boolean|string}
  18929. */
  18930. _inside_sequence = false;
  18931. /**
  18932. * loop through the f keys, f1 to f19 and add them to the map
  18933. * programatically
  18934. */
  18935. for (var i = 1; i < 20; ++i) {
  18936. _MAP[111 + i] = 'f' + i;
  18937. }
  18938. /**
  18939. * loop through to map numbers on the numeric keypad
  18940. */
  18941. for (i = 0; i <= 9; ++i) {
  18942. _MAP[i + 96] = i;
  18943. }
  18944. /**
  18945. * cross browser add event method
  18946. *
  18947. * @param {Element|HTMLDocument} object
  18948. * @param {string} type
  18949. * @param {Function} callback
  18950. * @returns void
  18951. */
  18952. function _addEvent(object, type, callback) {
  18953. if (object.addEventListener) {
  18954. return object.addEventListener(type, callback, false);
  18955. }
  18956. object.attachEvent('on' + type, callback);
  18957. }
  18958. /**
  18959. * takes the event and returns the key character
  18960. *
  18961. * @param {Event} e
  18962. * @return {string}
  18963. */
  18964. function _characterFromEvent(e) {
  18965. // for keypress events we should return the character as is
  18966. if (e.type == 'keypress') {
  18967. return String.fromCharCode(e.which);
  18968. }
  18969. // for non keypress events the special maps are needed
  18970. if (_MAP[e.which]) {
  18971. return _MAP[e.which];
  18972. }
  18973. if (_KEYCODE_MAP[e.which]) {
  18974. return _KEYCODE_MAP[e.which];
  18975. }
  18976. // if it is not in the special map
  18977. return String.fromCharCode(e.which).toLowerCase();
  18978. }
  18979. /**
  18980. * should we stop this event before firing off callbacks
  18981. *
  18982. * @param {Event} e
  18983. * @return {boolean}
  18984. */
  18985. function _stop(e) {
  18986. var element = e.target || e.srcElement,
  18987. tag_name = element.tagName;
  18988. // if the element has the class "mousetrap" then no need to stop
  18989. if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
  18990. return false;
  18991. }
  18992. // stop for input, select, and textarea
  18993. return tag_name == 'INPUT' || tag_name == 'SELECT' || tag_name == 'TEXTAREA' || (element.contentEditable && element.contentEditable == 'true');
  18994. }
  18995. /**
  18996. * checks if two arrays are equal
  18997. *
  18998. * @param {Array} modifiers1
  18999. * @param {Array} modifiers2
  19000. * @returns {boolean}
  19001. */
  19002. function _modifiersMatch(modifiers1, modifiers2) {
  19003. return modifiers1.sort().join(',') === modifiers2.sort().join(',');
  19004. }
  19005. /**
  19006. * resets all sequence counters except for the ones passed in
  19007. *
  19008. * @param {Object} do_not_reset
  19009. * @returns void
  19010. */
  19011. function _resetSequences(do_not_reset) {
  19012. do_not_reset = do_not_reset || {};
  19013. var active_sequences = false,
  19014. key;
  19015. for (key in _sequence_levels) {
  19016. if (do_not_reset[key]) {
  19017. active_sequences = true;
  19018. continue;
  19019. }
  19020. _sequence_levels[key] = 0;
  19021. }
  19022. if (!active_sequences) {
  19023. _inside_sequence = false;
  19024. }
  19025. }
  19026. /**
  19027. * finds all callbacks that match based on the keycode, modifiers,
  19028. * and action
  19029. *
  19030. * @param {string} character
  19031. * @param {Array} modifiers
  19032. * @param {string} action
  19033. * @param {boolean=} remove - should we remove any matches
  19034. * @param {string=} combination
  19035. * @returns {Array}
  19036. */
  19037. function _getMatches(character, modifiers, action, remove, combination) {
  19038. var i,
  19039. callback,
  19040. matches = [];
  19041. // if there are no events related to this keycode
  19042. if (!_callbacks[character]) {
  19043. return [];
  19044. }
  19045. // if a modifier key is coming up on its own we should allow it
  19046. if (action == 'keyup' && _isModifier(character)) {
  19047. modifiers = [character];
  19048. }
  19049. // loop through all callbacks for the key that was pressed
  19050. // and see if any of them match
  19051. for (i = 0; i < _callbacks[character].length; ++i) {
  19052. callback = _callbacks[character][i];
  19053. // if this is a sequence but it is not at the right level
  19054. // then move onto the next match
  19055. if (callback.seq && _sequence_levels[callback.seq] != callback.level) {
  19056. continue;
  19057. }
  19058. // if the action we are looking for doesn't match the action we got
  19059. // then we should keep going
  19060. if (action != callback.action) {
  19061. continue;
  19062. }
  19063. // if this is a keypress event that means that we need to only
  19064. // look at the character, otherwise check the modifiers as
  19065. // well
  19066. if (action == 'keypress' || _modifiersMatch(modifiers, callback.modifiers)) {
  19067. // remove is used so if you change your mind and call bind a
  19068. // second time with a new function the first one is overwritten
  19069. if (remove && callback.combo == combination) {
  19070. _callbacks[character].splice(i, 1);
  19071. }
  19072. matches.push(callback);
  19073. }
  19074. }
  19075. return matches;
  19076. }
  19077. /**
  19078. * takes a key event and figures out what the modifiers are
  19079. *
  19080. * @param {Event} e
  19081. * @returns {Array}
  19082. */
  19083. function _eventModifiers(e) {
  19084. var modifiers = [];
  19085. if (e.shiftKey) {
  19086. modifiers.push('shift');
  19087. }
  19088. if (e.altKey) {
  19089. modifiers.push('alt');
  19090. }
  19091. if (e.ctrlKey) {
  19092. modifiers.push('ctrl');
  19093. }
  19094. if (e.metaKey) {
  19095. modifiers.push('meta');
  19096. }
  19097. return modifiers;
  19098. }
  19099. /**
  19100. * actually calls the callback function
  19101. *
  19102. * if your callback function returns false this will use the jquery
  19103. * convention - prevent default and stop propogation on the event
  19104. *
  19105. * @param {Function} callback
  19106. * @param {Event} e
  19107. * @returns void
  19108. */
  19109. function _fireCallback(callback, e) {
  19110. if (callback(e) === false) {
  19111. if (e.preventDefault) {
  19112. e.preventDefault();
  19113. }
  19114. if (e.stopPropagation) {
  19115. e.stopPropagation();
  19116. }
  19117. e.returnValue = false;
  19118. e.cancelBubble = true;
  19119. }
  19120. }
  19121. /**
  19122. * handles a character key event
  19123. *
  19124. * @param {string} character
  19125. * @param {Event} e
  19126. * @returns void
  19127. */
  19128. function _handleCharacter(character, e) {
  19129. // if this event should not happen stop here
  19130. if (_stop(e)) {
  19131. return;
  19132. }
  19133. var callbacks = _getMatches(character, _eventModifiers(e), e.type),
  19134. i,
  19135. do_not_reset = {},
  19136. processed_sequence_callback = false;
  19137. // loop through matching callbacks for this key event
  19138. for (i = 0; i < callbacks.length; ++i) {
  19139. // fire for all sequence callbacks
  19140. // this is because if for example you have multiple sequences
  19141. // bound such as "g i" and "g t" they both need to fire the
  19142. // callback for matching g cause otherwise you can only ever
  19143. // match the first one
  19144. if (callbacks[i].seq) {
  19145. processed_sequence_callback = true;
  19146. // keep a list of which sequences were matches for later
  19147. do_not_reset[callbacks[i].seq] = 1;
  19148. _fireCallback(callbacks[i].callback, e);
  19149. continue;
  19150. }
  19151. // if there were no sequence matches but we are still here
  19152. // that means this is a regular match so we should fire that
  19153. if (!processed_sequence_callback && !_inside_sequence) {
  19154. _fireCallback(callbacks[i].callback, e);
  19155. }
  19156. }
  19157. // if you are inside of a sequence and the key you are pressing
  19158. // is not a modifier key then we should reset all sequences
  19159. // that were not matched by this key event
  19160. if (e.type == _inside_sequence && !_isModifier(character)) {
  19161. _resetSequences(do_not_reset);
  19162. }
  19163. }
  19164. /**
  19165. * handles a keydown event
  19166. *
  19167. * @param {Event} e
  19168. * @returns void
  19169. */
  19170. function _handleKey(e) {
  19171. // normalize e.which for key events
  19172. // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
  19173. e.which = typeof e.which == "number" ? e.which : e.keyCode;
  19174. var character = _characterFromEvent(e);
  19175. // no character found then stop
  19176. if (!character) {
  19177. return;
  19178. }
  19179. if (e.type == 'keyup' && _ignore_next_keyup == character) {
  19180. _ignore_next_keyup = false;
  19181. return;
  19182. }
  19183. _handleCharacter(character, e);
  19184. }
  19185. /**
  19186. * determines if the keycode specified is a modifier key or not
  19187. *
  19188. * @param {string} key
  19189. * @returns {boolean}
  19190. */
  19191. function _isModifier(key) {
  19192. return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta';
  19193. }
  19194. /**
  19195. * called to set a 1 second timeout on the specified sequence
  19196. *
  19197. * this is so after each key press in the sequence you have 1 second
  19198. * to press the next key before you have to start over
  19199. *
  19200. * @returns void
  19201. */
  19202. function _resetSequenceTimer() {
  19203. clearTimeout(_reset_timer);
  19204. _reset_timer = setTimeout(_resetSequences, 1000);
  19205. }
  19206. /**
  19207. * reverses the map lookup so that we can look for specific keys
  19208. * to see what can and can't use keypress
  19209. *
  19210. * @return {Object}
  19211. */
  19212. function _getReverseMap() {
  19213. if (!_REVERSE_MAP) {
  19214. _REVERSE_MAP = {};
  19215. for (var key in _MAP) {
  19216. // pull out the numeric keypad from here cause keypress should
  19217. // be able to detect the keys from the character
  19218. if (key > 95 && key < 112) {
  19219. continue;
  19220. }
  19221. if (_MAP.hasOwnProperty(key)) {
  19222. _REVERSE_MAP[_MAP[key]] = key;
  19223. }
  19224. }
  19225. }
  19226. return _REVERSE_MAP;
  19227. }
  19228. /**
  19229. * picks the best action based on the key combination
  19230. *
  19231. * @param {string} key - character for key
  19232. * @param {Array} modifiers
  19233. * @param {string=} action passed in
  19234. */
  19235. function _pickBestAction(key, modifiers, action) {
  19236. // if no action was picked in we should try to pick the one
  19237. // that we think would work best for this key
  19238. if (!action) {
  19239. action = _getReverseMap()[key] ? 'keydown' : 'keypress';
  19240. }
  19241. // modifier keys don't work as expected with keypress,
  19242. // switch to keydown
  19243. if (action == 'keypress' && modifiers.length) {
  19244. action = 'keydown';
  19245. }
  19246. return action;
  19247. }
  19248. /**
  19249. * binds a key sequence to an event
  19250. *
  19251. * @param {string} combo - combo specified in bind call
  19252. * @param {Array} keys
  19253. * @param {Function} callback
  19254. * @param {string=} action
  19255. * @returns void
  19256. */
  19257. function _bindSequence(combo, keys, callback, action) {
  19258. // start off by adding a sequence level record for this combination
  19259. // and setting the level to 0
  19260. _sequence_levels[combo] = 0;
  19261. // if there is no action pick the best one for the first key
  19262. // in the sequence
  19263. if (!action) {
  19264. action = _pickBestAction(keys[0], []);
  19265. }
  19266. /**
  19267. * callback to increase the sequence level for this sequence and reset
  19268. * all other sequences that were active
  19269. *
  19270. * @param {Event} e
  19271. * @returns void
  19272. */
  19273. var _increaseSequence = function(e) {
  19274. _inside_sequence = action;
  19275. ++_sequence_levels[combo];
  19276. _resetSequenceTimer();
  19277. },
  19278. /**
  19279. * wraps the specified callback inside of another function in order
  19280. * to reset all sequence counters as soon as this sequence is done
  19281. *
  19282. * @param {Event} e
  19283. * @returns void
  19284. */
  19285. _callbackAndReset = function(e) {
  19286. _fireCallback(callback, e);
  19287. // we should ignore the next key up if the action is key down
  19288. // or keypress. this is so if you finish a sequence and
  19289. // release the key the final key will not trigger a keyup
  19290. if (action !== 'keyup') {
  19291. _ignore_next_keyup = _characterFromEvent(e);
  19292. }
  19293. // weird race condition if a sequence ends with the key
  19294. // another sequence begins with
  19295. setTimeout(_resetSequences, 10);
  19296. },
  19297. i;
  19298. // loop through keys one at a time and bind the appropriate callback
  19299. // function. for any key leading up to the final one it should
  19300. // increase the sequence. after the final, it should reset all sequences
  19301. for (i = 0; i < keys.length; ++i) {
  19302. _bindSingle(keys[i], i < keys.length - 1 ? _increaseSequence : _callbackAndReset, action, combo, i);
  19303. }
  19304. }
  19305. /**
  19306. * binds a single keyboard combination
  19307. *
  19308. * @param {string} combination
  19309. * @param {Function} callback
  19310. * @param {string=} action
  19311. * @param {string=} sequence_name - name of sequence if part of sequence
  19312. * @param {number=} level - what part of the sequence the command is
  19313. * @returns void
  19314. */
  19315. function _bindSingle(combination, callback, action, sequence_name, level) {
  19316. // make sure multiple spaces in a row become a single space
  19317. combination = combination.replace(/\s+/g, ' ');
  19318. var sequence = combination.split(' '),
  19319. i,
  19320. key,
  19321. keys,
  19322. modifiers = [];
  19323. // if this pattern is a sequence of keys then run through this method
  19324. // to reprocess each pattern one key at a time
  19325. if (sequence.length > 1) {
  19326. return _bindSequence(combination, sequence, callback, action);
  19327. }
  19328. // take the keys from this pattern and figure out what the actual
  19329. // pattern is all about
  19330. keys = combination === '+' ? ['+'] : combination.split('+');
  19331. for (i = 0; i < keys.length; ++i) {
  19332. key = keys[i];
  19333. // normalize key names
  19334. if (_SPECIAL_ALIASES[key]) {
  19335. key = _SPECIAL_ALIASES[key];
  19336. }
  19337. // if this is not a keypress event then we should
  19338. // be smart about using shift keys
  19339. // this will only work for US keyboards however
  19340. if (action && action != 'keypress' && _SHIFT_MAP[key]) {
  19341. key = _SHIFT_MAP[key];
  19342. modifiers.push('shift');
  19343. }
  19344. // if this key is a modifier then add it to the list of modifiers
  19345. if (_isModifier(key)) {
  19346. modifiers.push(key);
  19347. }
  19348. }
  19349. // depending on what the key combination is
  19350. // we will try to pick the best event for it
  19351. action = _pickBestAction(key, modifiers, action);
  19352. // make sure to initialize array if this is the first time
  19353. // a callback is added for this key
  19354. if (!_callbacks[key]) {
  19355. _callbacks[key] = [];
  19356. }
  19357. // remove an existing match if there is one
  19358. _getMatches(key, modifiers, action, !sequence_name, combination);
  19359. // add this call back to the array
  19360. // if it is a sequence put it at the beginning
  19361. // if not put it at the end
  19362. //
  19363. // this is important because the way these are processed expects
  19364. // the sequence ones to come first
  19365. _callbacks[key][sequence_name ? 'unshift' : 'push']({
  19366. callback: callback,
  19367. modifiers: modifiers,
  19368. action: action,
  19369. seq: sequence_name,
  19370. level: level,
  19371. combo: combination
  19372. });
  19373. }
  19374. /**
  19375. * binds multiple combinations to the same callback
  19376. *
  19377. * @param {Array} combinations
  19378. * @param {Function} callback
  19379. * @param {string|undefined} action
  19380. * @returns void
  19381. */
  19382. function _bindMultiple(combinations, callback, action) {
  19383. for (var i = 0; i < combinations.length; ++i) {
  19384. _bindSingle(combinations[i], callback, action);
  19385. }
  19386. }
  19387. // start!
  19388. _addEvent(document, 'keypress', _handleKey);
  19389. _addEvent(document, 'keydown', _handleKey);
  19390. _addEvent(document, 'keyup', _handleKey);
  19391. var mousetrap = {
  19392. /**
  19393. * binds an event to mousetrap
  19394. *
  19395. * can be a single key, a combination of keys separated with +,
  19396. * a comma separated list of keys, an array of keys, or
  19397. * a sequence of keys separated by spaces
  19398. *
  19399. * be sure to list the modifier keys first to make sure that the
  19400. * correct key ends up getting bound (the last key in the pattern)
  19401. *
  19402. * @param {string|Array} keys
  19403. * @param {Function} callback
  19404. * @param {string=} action - 'keypress', 'keydown', or 'keyup'
  19405. * @returns void
  19406. */
  19407. bind: function(keys, callback, action) {
  19408. _bindMultiple(keys instanceof Array ? keys : [keys], callback, action);
  19409. _direct_map[keys + ':' + action] = callback;
  19410. return this;
  19411. },
  19412. /**
  19413. * unbinds an event to mousetrap
  19414. *
  19415. * the unbinding sets the callback function of the specified key combo
  19416. * to an empty function and deletes the corresponding key in the
  19417. * _direct_map dict.
  19418. *
  19419. * the keycombo+action has to be exactly the same as
  19420. * it was defined in the bind method
  19421. *
  19422. * TODO: actually remove this from the _callbacks dictionary instead
  19423. * of binding an empty function
  19424. *
  19425. * @param {string|Array} keys
  19426. * @param {string} action
  19427. * @returns void
  19428. */
  19429. unbind: function(keys, action) {
  19430. if (_direct_map[keys + ':' + action]) {
  19431. delete _direct_map[keys + ':' + action];
  19432. this.bind(keys, function() {}, action);
  19433. }
  19434. return this;
  19435. },
  19436. /**
  19437. * triggers an event that has already been bound
  19438. *
  19439. * @param {string} keys
  19440. * @param {string=} action
  19441. * @returns void
  19442. */
  19443. trigger: function(keys, action) {
  19444. _direct_map[keys + ':' + action]();
  19445. return this;
  19446. },
  19447. /**
  19448. * resets the library back to its initial state. this is useful
  19449. * if you want to clear out the current keyboard shortcuts and bind
  19450. * new ones - for example if you switch to another page
  19451. *
  19452. * @returns void
  19453. */
  19454. reset: function() {
  19455. _callbacks = {};
  19456. _direct_map = {};
  19457. return this;
  19458. }
  19459. };
  19460. module.exports = mousetrap;
  19461. /***/ },
  19462. /* 52 */
  19463. /***/ function(module, exports, __webpack_require__) {
  19464. var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(global, module) {//! moment.js
  19465. //! version : 2.8.3
  19466. //! authors : Tim Wood, Iskren Chernev, Moment.js contributors
  19467. //! license : MIT
  19468. //! momentjs.com
  19469. (function (undefined) {
  19470. /************************************
  19471. Constants
  19472. ************************************/
  19473. var moment,
  19474. VERSION = '2.8.3',
  19475. // the global-scope this is NOT the global object in Node.js
  19476. globalScope = typeof global !== 'undefined' ? global : this,
  19477. oldGlobalMoment,
  19478. round = Math.round,
  19479. hasOwnProperty = Object.prototype.hasOwnProperty,
  19480. i,
  19481. YEAR = 0,
  19482. MONTH = 1,
  19483. DATE = 2,
  19484. HOUR = 3,
  19485. MINUTE = 4,
  19486. SECOND = 5,
  19487. MILLISECOND = 6,
  19488. // internal storage for locale config files
  19489. locales = {},
  19490. // extra moment internal properties (plugins register props here)
  19491. momentProperties = [],
  19492. // check for nodeJS
  19493. hasModule = (typeof module !== 'undefined' && module.exports),
  19494. // ASP.NET json date format regex
  19495. aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
  19496. aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,
  19497. // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
  19498. // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
  19499. isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,
  19500. // format tokens
  19501. formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,
  19502. localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,
  19503. // parsing token regexes
  19504. parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
  19505. parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
  19506. parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999
  19507. parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
  19508. parseTokenDigits = /\d+/, // nonzero number of digits
  19509. 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.
  19510. parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
  19511. parseTokenT = /T/i, // T (ISO separator)
  19512. parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
  19513. parseTokenOrdinal = /\d{1,2}/,
  19514. //strict parsing regexes
  19515. parseTokenOneDigit = /\d/, // 0 - 9
  19516. parseTokenTwoDigits = /\d\d/, // 00 - 99
  19517. parseTokenThreeDigits = /\d{3}/, // 000 - 999
  19518. parseTokenFourDigits = /\d{4}/, // 0000 - 9999
  19519. parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999
  19520. parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf
  19521. // iso 8601 regex
  19522. // 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)
  19523. 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)?)?$/,
  19524. isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
  19525. isoDates = [
  19526. ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/],
  19527. ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/],
  19528. ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/],
  19529. ['GGGG-[W]WW', /\d{4}-W\d{2}/],
  19530. ['YYYY-DDD', /\d{4}-\d{3}/]
  19531. ],
  19532. // iso time formats and regexes
  19533. isoTimes = [
  19534. ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/],
  19535. ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
  19536. ['HH:mm', /(T| )\d\d:\d\d/],
  19537. ['HH', /(T| )\d\d/]
  19538. ],
  19539. // timezone chunker '+10:00' > ['10', '00'] or '-1530' > ['-15', '30']
  19540. parseTimezoneChunker = /([\+\-]|\d\d)/gi,
  19541. // getter and setter names
  19542. proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
  19543. unitMillisecondFactors = {
  19544. 'Milliseconds' : 1,
  19545. 'Seconds' : 1e3,
  19546. 'Minutes' : 6e4,
  19547. 'Hours' : 36e5,
  19548. 'Days' : 864e5,
  19549. 'Months' : 2592e6,
  19550. 'Years' : 31536e6
  19551. },
  19552. unitAliases = {
  19553. ms : 'millisecond',
  19554. s : 'second',
  19555. m : 'minute',
  19556. h : 'hour',
  19557. d : 'day',
  19558. D : 'date',
  19559. w : 'week',
  19560. W : 'isoWeek',
  19561. M : 'month',
  19562. Q : 'quarter',
  19563. y : 'year',
  19564. DDD : 'dayOfYear',
  19565. e : 'weekday',
  19566. E : 'isoWeekday',
  19567. gg: 'weekYear',
  19568. GG: 'isoWeekYear'
  19569. },
  19570. camelFunctions = {
  19571. dayofyear : 'dayOfYear',
  19572. isoweekday : 'isoWeekday',
  19573. isoweek : 'isoWeek',
  19574. weekyear : 'weekYear',
  19575. isoweekyear : 'isoWeekYear'
  19576. },
  19577. // format function strings
  19578. formatFunctions = {},
  19579. // default relative time thresholds
  19580. relativeTimeThresholds = {
  19581. s: 45, // seconds to minute
  19582. m: 45, // minutes to hour
  19583. h: 22, // hours to day
  19584. d: 26, // days to month
  19585. M: 11 // months to year
  19586. },
  19587. // tokens to ordinalize and pad
  19588. ordinalizeTokens = 'DDD w W M D d'.split(' '),
  19589. paddedTokens = 'M D H h m s w W'.split(' '),
  19590. formatTokenFunctions = {
  19591. M : function () {
  19592. return this.month() + 1;
  19593. },
  19594. MMM : function (format) {
  19595. return this.localeData().monthsShort(this, format);
  19596. },
  19597. MMMM : function (format) {
  19598. return this.localeData().months(this, format);
  19599. },
  19600. D : function () {
  19601. return this.date();
  19602. },
  19603. DDD : function () {
  19604. return this.dayOfYear();
  19605. },
  19606. d : function () {
  19607. return this.day();
  19608. },
  19609. dd : function (format) {
  19610. return this.localeData().weekdaysMin(this, format);
  19611. },
  19612. ddd : function (format) {
  19613. return this.localeData().weekdaysShort(this, format);
  19614. },
  19615. dddd : function (format) {
  19616. return this.localeData().weekdays(this, format);
  19617. },
  19618. w : function () {
  19619. return this.week();
  19620. },
  19621. W : function () {
  19622. return this.isoWeek();
  19623. },
  19624. YY : function () {
  19625. return leftZeroFill(this.year() % 100, 2);
  19626. },
  19627. YYYY : function () {
  19628. return leftZeroFill(this.year(), 4);
  19629. },
  19630. YYYYY : function () {
  19631. return leftZeroFill(this.year(), 5);
  19632. },
  19633. YYYYYY : function () {
  19634. var y = this.year(), sign = y >= 0 ? '+' : '-';
  19635. return sign + leftZeroFill(Math.abs(y), 6);
  19636. },
  19637. gg : function () {
  19638. return leftZeroFill(this.weekYear() % 100, 2);
  19639. },
  19640. gggg : function () {
  19641. return leftZeroFill(this.weekYear(), 4);
  19642. },
  19643. ggggg : function () {
  19644. return leftZeroFill(this.weekYear(), 5);
  19645. },
  19646. GG : function () {
  19647. return leftZeroFill(this.isoWeekYear() % 100, 2);
  19648. },
  19649. GGGG : function () {
  19650. return leftZeroFill(this.isoWeekYear(), 4);
  19651. },
  19652. GGGGG : function () {
  19653. return leftZeroFill(this.isoWeekYear(), 5);
  19654. },
  19655. e : function () {
  19656. return this.weekday();
  19657. },
  19658. E : function () {
  19659. return this.isoWeekday();
  19660. },
  19661. a : function () {
  19662. return this.localeData().meridiem(this.hours(), this.minutes(), true);
  19663. },
  19664. A : function () {
  19665. return this.localeData().meridiem(this.hours(), this.minutes(), false);
  19666. },
  19667. H : function () {
  19668. return this.hours();
  19669. },
  19670. h : function () {
  19671. return this.hours() % 12 || 12;
  19672. },
  19673. m : function () {
  19674. return this.minutes();
  19675. },
  19676. s : function () {
  19677. return this.seconds();
  19678. },
  19679. S : function () {
  19680. return toInt(this.milliseconds() / 100);
  19681. },
  19682. SS : function () {
  19683. return leftZeroFill(toInt(this.milliseconds() / 10), 2);
  19684. },
  19685. SSS : function () {
  19686. return leftZeroFill(this.milliseconds(), 3);
  19687. },
  19688. SSSS : function () {
  19689. return leftZeroFill(this.milliseconds(), 3);
  19690. },
  19691. Z : function () {
  19692. var a = -this.zone(),
  19693. b = '+';
  19694. if (a < 0) {
  19695. a = -a;
  19696. b = '-';
  19697. }
  19698. return b + leftZeroFill(toInt(a / 60), 2) + ':' + leftZeroFill(toInt(a) % 60, 2);
  19699. },
  19700. ZZ : function () {
  19701. var a = -this.zone(),
  19702. b = '+';
  19703. if (a < 0) {
  19704. a = -a;
  19705. b = '-';
  19706. }
  19707. return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2);
  19708. },
  19709. z : function () {
  19710. return this.zoneAbbr();
  19711. },
  19712. zz : function () {
  19713. return this.zoneName();
  19714. },
  19715. X : function () {
  19716. return this.unix();
  19717. },
  19718. Q : function () {
  19719. return this.quarter();
  19720. }
  19721. },
  19722. deprecations = {},
  19723. lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'];
  19724. // Pick the first defined of two or three arguments. dfl comes from
  19725. // default.
  19726. function dfl(a, b, c) {
  19727. switch (arguments.length) {
  19728. case 2: return a != null ? a : b;
  19729. case 3: return a != null ? a : b != null ? b : c;
  19730. default: throw new Error('Implement me');
  19731. }
  19732. }
  19733. function hasOwnProp(a, b) {
  19734. return hasOwnProperty.call(a, b);
  19735. }
  19736. function defaultParsingFlags() {
  19737. // We need to deep clone this object, and es5 standard is not very
  19738. // helpful.
  19739. return {
  19740. empty : false,
  19741. unusedTokens : [],
  19742. unusedInput : [],
  19743. overflow : -2,
  19744. charsLeftOver : 0,
  19745. nullInput : false,
  19746. invalidMonth : null,
  19747. invalidFormat : false,
  19748. userInvalidated : false,
  19749. iso: false
  19750. };
  19751. }
  19752. function printMsg(msg) {
  19753. if (moment.suppressDeprecationWarnings === false &&
  19754. typeof console !== 'undefined' && console.warn) {
  19755. console.warn('Deprecation warning: ' + msg);
  19756. }
  19757. }
  19758. function deprecate(msg, fn) {
  19759. var firstTime = true;
  19760. return extend(function () {
  19761. if (firstTime) {
  19762. printMsg(msg);
  19763. firstTime = false;
  19764. }
  19765. return fn.apply(this, arguments);
  19766. }, fn);
  19767. }
  19768. function deprecateSimple(name, msg) {
  19769. if (!deprecations[name]) {
  19770. printMsg(msg);
  19771. deprecations[name] = true;
  19772. }
  19773. }
  19774. function padToken(func, count) {
  19775. return function (a) {
  19776. return leftZeroFill(func.call(this, a), count);
  19777. };
  19778. }
  19779. function ordinalizeToken(func, period) {
  19780. return function (a) {
  19781. return this.localeData().ordinal(func.call(this, a), period);
  19782. };
  19783. }
  19784. while (ordinalizeTokens.length) {
  19785. i = ordinalizeTokens.pop();
  19786. formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i);
  19787. }
  19788. while (paddedTokens.length) {
  19789. i = paddedTokens.pop();
  19790. formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2);
  19791. }
  19792. formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3);
  19793. /************************************
  19794. Constructors
  19795. ************************************/
  19796. function Locale() {
  19797. }
  19798. // Moment prototype object
  19799. function Moment(config, skipOverflow) {
  19800. if (skipOverflow !== false) {
  19801. checkOverflow(config);
  19802. }
  19803. copyConfig(this, config);
  19804. this._d = new Date(+config._d);
  19805. }
  19806. // Duration Constructor
  19807. function Duration(duration) {
  19808. var normalizedInput = normalizeObjectUnits(duration),
  19809. years = normalizedInput.year || 0,
  19810. quarters = normalizedInput.quarter || 0,
  19811. months = normalizedInput.month || 0,
  19812. weeks = normalizedInput.week || 0,
  19813. days = normalizedInput.day || 0,
  19814. hours = normalizedInput.hour || 0,
  19815. minutes = normalizedInput.minute || 0,
  19816. seconds = normalizedInput.second || 0,
  19817. milliseconds = normalizedInput.millisecond || 0;
  19818. // representation for dateAddRemove
  19819. this._milliseconds = +milliseconds +
  19820. seconds * 1e3 + // 1000
  19821. minutes * 6e4 + // 1000 * 60
  19822. hours * 36e5; // 1000 * 60 * 60
  19823. // Because of dateAddRemove treats 24 hours as different from a
  19824. // day when working around DST, we need to store them separately
  19825. this._days = +days +
  19826. weeks * 7;
  19827. // It is impossible translate months into days without knowing
  19828. // which months you are are talking about, so we have to store
  19829. // it separately.
  19830. this._months = +months +
  19831. quarters * 3 +
  19832. years * 12;
  19833. this._data = {};
  19834. this._locale = moment.localeData();
  19835. this._bubble();
  19836. }
  19837. /************************************
  19838. Helpers
  19839. ************************************/
  19840. function extend(a, b) {
  19841. for (var i in b) {
  19842. if (hasOwnProp(b, i)) {
  19843. a[i] = b[i];
  19844. }
  19845. }
  19846. if (hasOwnProp(b, 'toString')) {
  19847. a.toString = b.toString;
  19848. }
  19849. if (hasOwnProp(b, 'valueOf')) {
  19850. a.valueOf = b.valueOf;
  19851. }
  19852. return a;
  19853. }
  19854. function copyConfig(to, from) {
  19855. var i, prop, val;
  19856. if (typeof from._isAMomentObject !== 'undefined') {
  19857. to._isAMomentObject = from._isAMomentObject;
  19858. }
  19859. if (typeof from._i !== 'undefined') {
  19860. to._i = from._i;
  19861. }
  19862. if (typeof from._f !== 'undefined') {
  19863. to._f = from._f;
  19864. }
  19865. if (typeof from._l !== 'undefined') {
  19866. to._l = from._l;
  19867. }
  19868. if (typeof from._strict !== 'undefined') {
  19869. to._strict = from._strict;
  19870. }
  19871. if (typeof from._tzm !== 'undefined') {
  19872. to._tzm = from._tzm;
  19873. }
  19874. if (typeof from._isUTC !== 'undefined') {
  19875. to._isUTC = from._isUTC;
  19876. }
  19877. if (typeof from._offset !== 'undefined') {
  19878. to._offset = from._offset;
  19879. }
  19880. if (typeof from._pf !== 'undefined') {
  19881. to._pf = from._pf;
  19882. }
  19883. if (typeof from._locale !== 'undefined') {
  19884. to._locale = from._locale;
  19885. }
  19886. if (momentProperties.length > 0) {
  19887. for (i in momentProperties) {
  19888. prop = momentProperties[i];
  19889. val = from[prop];
  19890. if (typeof val !== 'undefined') {
  19891. to[prop] = val;
  19892. }
  19893. }
  19894. }
  19895. return to;
  19896. }
  19897. function absRound(number) {
  19898. if (number < 0) {
  19899. return Math.ceil(number);
  19900. } else {
  19901. return Math.floor(number);
  19902. }
  19903. }
  19904. // left zero fill a number
  19905. // see http://jsperf.com/left-zero-filling for performance comparison
  19906. function leftZeroFill(number, targetLength, forceSign) {
  19907. var output = '' + Math.abs(number),
  19908. sign = number >= 0;
  19909. while (output.length < targetLength) {
  19910. output = '0' + output;
  19911. }
  19912. return (sign ? (forceSign ? '+' : '') : '-') + output;
  19913. }
  19914. function positiveMomentsDifference(base, other) {
  19915. var res = {milliseconds: 0, months: 0};
  19916. res.months = other.month() - base.month() +
  19917. (other.year() - base.year()) * 12;
  19918. if (base.clone().add(res.months, 'M').isAfter(other)) {
  19919. --res.months;
  19920. }
  19921. res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
  19922. return res;
  19923. }
  19924. function momentsDifference(base, other) {
  19925. var res;
  19926. other = makeAs(other, base);
  19927. if (base.isBefore(other)) {
  19928. res = positiveMomentsDifference(base, other);
  19929. } else {
  19930. res = positiveMomentsDifference(other, base);
  19931. res.milliseconds = -res.milliseconds;
  19932. res.months = -res.months;
  19933. }
  19934. return res;
  19935. }
  19936. // TODO: remove 'name' arg after deprecation is removed
  19937. function createAdder(direction, name) {
  19938. return function (val, period) {
  19939. var dur, tmp;
  19940. //invert the arguments, but complain about it
  19941. if (period !== null && !isNaN(+period)) {
  19942. deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).');
  19943. tmp = val; val = period; period = tmp;
  19944. }
  19945. val = typeof val === 'string' ? +val : val;
  19946. dur = moment.duration(val, period);
  19947. addOrSubtractDurationFromMoment(this, dur, direction);
  19948. return this;
  19949. };
  19950. }
  19951. function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) {
  19952. var milliseconds = duration._milliseconds,
  19953. days = duration._days,
  19954. months = duration._months;
  19955. updateOffset = updateOffset == null ? true : updateOffset;
  19956. if (milliseconds) {
  19957. mom._d.setTime(+mom._d + milliseconds * isAdding);
  19958. }
  19959. if (days) {
  19960. rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding);
  19961. }
  19962. if (months) {
  19963. rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding);
  19964. }
  19965. if (updateOffset) {
  19966. moment.updateOffset(mom, days || months);
  19967. }
  19968. }
  19969. // check if is an array
  19970. function isArray(input) {
  19971. return Object.prototype.toString.call(input) === '[object Array]';
  19972. }
  19973. function isDate(input) {
  19974. return Object.prototype.toString.call(input) === '[object Date]' ||
  19975. input instanceof Date;
  19976. }
  19977. // compare two arrays, return the number of differences
  19978. function compareArrays(array1, array2, dontConvert) {
  19979. var len = Math.min(array1.length, array2.length),
  19980. lengthDiff = Math.abs(array1.length - array2.length),
  19981. diffs = 0,
  19982. i;
  19983. for (i = 0; i < len; i++) {
  19984. if ((dontConvert && array1[i] !== array2[i]) ||
  19985. (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
  19986. diffs++;
  19987. }
  19988. }
  19989. return diffs + lengthDiff;
  19990. }
  19991. function normalizeUnits(units) {
  19992. if (units) {
  19993. var lowered = units.toLowerCase().replace(/(.)s$/, '$1');
  19994. units = unitAliases[units] || camelFunctions[lowered] || lowered;
  19995. }
  19996. return units;
  19997. }
  19998. function normalizeObjectUnits(inputObject) {
  19999. var normalizedInput = {},
  20000. normalizedProp,
  20001. prop;
  20002. for (prop in inputObject) {
  20003. if (hasOwnProp(inputObject, prop)) {
  20004. normalizedProp = normalizeUnits(prop);
  20005. if (normalizedProp) {
  20006. normalizedInput[normalizedProp] = inputObject[prop];
  20007. }
  20008. }
  20009. }
  20010. return normalizedInput;
  20011. }
  20012. function makeList(field) {
  20013. var count, setter;
  20014. if (field.indexOf('week') === 0) {
  20015. count = 7;
  20016. setter = 'day';
  20017. }
  20018. else if (field.indexOf('month') === 0) {
  20019. count = 12;
  20020. setter = 'month';
  20021. }
  20022. else {
  20023. return;
  20024. }
  20025. moment[field] = function (format, index) {
  20026. var i, getter,
  20027. method = moment._locale[field],
  20028. results = [];
  20029. if (typeof format === 'number') {
  20030. index = format;
  20031. format = undefined;
  20032. }
  20033. getter = function (i) {
  20034. var m = moment().utc().set(setter, i);
  20035. return method.call(moment._locale, m, format || '');
  20036. };
  20037. if (index != null) {
  20038. return getter(index);
  20039. }
  20040. else {
  20041. for (i = 0; i < count; i++) {
  20042. results.push(getter(i));
  20043. }
  20044. return results;
  20045. }
  20046. };
  20047. }
  20048. function toInt(argumentForCoercion) {
  20049. var coercedNumber = +argumentForCoercion,
  20050. value = 0;
  20051. if (coercedNumber !== 0 && isFinite(coercedNumber)) {
  20052. if (coercedNumber >= 0) {
  20053. value = Math.floor(coercedNumber);
  20054. } else {
  20055. value = Math.ceil(coercedNumber);
  20056. }
  20057. }
  20058. return value;
  20059. }
  20060. function daysInMonth(year, month) {
  20061. return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
  20062. }
  20063. function weeksInYear(year, dow, doy) {
  20064. return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week;
  20065. }
  20066. function daysInYear(year) {
  20067. return isLeapYear(year) ? 366 : 365;
  20068. }
  20069. function isLeapYear(year) {
  20070. return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
  20071. }
  20072. function checkOverflow(m) {
  20073. var overflow;
  20074. if (m._a && m._pf.overflow === -2) {
  20075. overflow =
  20076. m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH :
  20077. m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE :
  20078. m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR :
  20079. m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE :
  20080. m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND :
  20081. m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND :
  20082. -1;
  20083. if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
  20084. overflow = DATE;
  20085. }
  20086. m._pf.overflow = overflow;
  20087. }
  20088. }
  20089. function isValid(m) {
  20090. if (m._isValid == null) {
  20091. m._isValid = !isNaN(m._d.getTime()) &&
  20092. m._pf.overflow < 0 &&
  20093. !m._pf.empty &&
  20094. !m._pf.invalidMonth &&
  20095. !m._pf.nullInput &&
  20096. !m._pf.invalidFormat &&
  20097. !m._pf.userInvalidated;
  20098. if (m._strict) {
  20099. m._isValid = m._isValid &&
  20100. m._pf.charsLeftOver === 0 &&
  20101. m._pf.unusedTokens.length === 0;
  20102. }
  20103. }
  20104. return m._isValid;
  20105. }
  20106. function normalizeLocale(key) {
  20107. return key ? key.toLowerCase().replace('_', '-') : key;
  20108. }
  20109. // pick the locale from the array
  20110. // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
  20111. // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
  20112. function chooseLocale(names) {
  20113. var i = 0, j, next, locale, split;
  20114. while (i < names.length) {
  20115. split = normalizeLocale(names[i]).split('-');
  20116. j = split.length;
  20117. next = normalizeLocale(names[i + 1]);
  20118. next = next ? next.split('-') : null;
  20119. while (j > 0) {
  20120. locale = loadLocale(split.slice(0, j).join('-'));
  20121. if (locale) {
  20122. return locale;
  20123. }
  20124. if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
  20125. //the next array item is better than a shallower substring of this one
  20126. break;
  20127. }
  20128. j--;
  20129. }
  20130. i++;
  20131. }
  20132. return null;
  20133. }
  20134. function loadLocale(name) {
  20135. var oldLocale = null;
  20136. if (!locales[name] && hasModule) {
  20137. try {
  20138. oldLocale = moment.locale();
  20139. !(function webpackMissingModule() { var e = new Error("Cannot find module \"./locale\""); e.code = 'MODULE_NOT_FOUND'; throw e; }());
  20140. // because defineLocale currently also sets the global locale, we want to undo that for lazy loaded locales
  20141. moment.locale(oldLocale);
  20142. } catch (e) { }
  20143. }
  20144. return locales[name];
  20145. }
  20146. // Return a moment from input, that is local/utc/zone equivalent to model.
  20147. function makeAs(input, model) {
  20148. return model._isUTC ? moment(input).zone(model._offset || 0) :
  20149. moment(input).local();
  20150. }
  20151. /************************************
  20152. Locale
  20153. ************************************/
  20154. extend(Locale.prototype, {
  20155. set : function (config) {
  20156. var prop, i;
  20157. for (i in config) {
  20158. prop = config[i];
  20159. if (typeof prop === 'function') {
  20160. this[i] = prop;
  20161. } else {
  20162. this['_' + i] = prop;
  20163. }
  20164. }
  20165. },
  20166. _months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
  20167. months : function (m) {
  20168. return this._months[m.month()];
  20169. },
  20170. _monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
  20171. monthsShort : function (m) {
  20172. return this._monthsShort[m.month()];
  20173. },
  20174. monthsParse : function (monthName) {
  20175. var i, mom, regex;
  20176. if (!this._monthsParse) {
  20177. this._monthsParse = [];
  20178. }
  20179. for (i = 0; i < 12; i++) {
  20180. // make the regex if we don't have it already
  20181. if (!this._monthsParse[i]) {
  20182. mom = moment.utc([2000, i]);
  20183. regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
  20184. this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
  20185. }
  20186. // test the regex
  20187. if (this._monthsParse[i].test(monthName)) {
  20188. return i;
  20189. }
  20190. }
  20191. },
  20192. _weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
  20193. weekdays : function (m) {
  20194. return this._weekdays[m.day()];
  20195. },
  20196. _weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
  20197. weekdaysShort : function (m) {
  20198. return this._weekdaysShort[m.day()];
  20199. },
  20200. _weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
  20201. weekdaysMin : function (m) {
  20202. return this._weekdaysMin[m.day()];
  20203. },
  20204. weekdaysParse : function (weekdayName) {
  20205. var i, mom, regex;
  20206. if (!this._weekdaysParse) {
  20207. this._weekdaysParse = [];
  20208. }
  20209. for (i = 0; i < 7; i++) {
  20210. // make the regex if we don't have it already
  20211. if (!this._weekdaysParse[i]) {
  20212. mom = moment([2000, 1]).day(i);
  20213. regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
  20214. this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
  20215. }
  20216. // test the regex
  20217. if (this._weekdaysParse[i].test(weekdayName)) {
  20218. return i;
  20219. }
  20220. }
  20221. },
  20222. _longDateFormat : {
  20223. LT : 'h:mm A',
  20224. L : 'MM/DD/YYYY',
  20225. LL : 'MMMM D, YYYY',
  20226. LLL : 'MMMM D, YYYY LT',
  20227. LLLL : 'dddd, MMMM D, YYYY LT'
  20228. },
  20229. longDateFormat : function (key) {
  20230. var output = this._longDateFormat[key];
  20231. if (!output && this._longDateFormat[key.toUpperCase()]) {
  20232. output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
  20233. return val.slice(1);
  20234. });
  20235. this._longDateFormat[key] = output;
  20236. }
  20237. return output;
  20238. },
  20239. isPM : function (input) {
  20240. // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
  20241. // Using charAt should be more compatible.
  20242. return ((input + '').toLowerCase().charAt(0) === 'p');
  20243. },
  20244. _meridiemParse : /[ap]\.?m?\.?/i,
  20245. meridiem : function (hours, minutes, isLower) {
  20246. if (hours > 11) {
  20247. return isLower ? 'pm' : 'PM';
  20248. } else {
  20249. return isLower ? 'am' : 'AM';
  20250. }
  20251. },
  20252. _calendar : {
  20253. sameDay : '[Today at] LT',
  20254. nextDay : '[Tomorrow at] LT',
  20255. nextWeek : 'dddd [at] LT',
  20256. lastDay : '[Yesterday at] LT',
  20257. lastWeek : '[Last] dddd [at] LT',
  20258. sameElse : 'L'
  20259. },
  20260. calendar : function (key, mom) {
  20261. var output = this._calendar[key];
  20262. return typeof output === 'function' ? output.apply(mom) : output;
  20263. },
  20264. _relativeTime : {
  20265. future : 'in %s',
  20266. past : '%s ago',
  20267. s : 'a few seconds',
  20268. m : 'a minute',
  20269. mm : '%d minutes',
  20270. h : 'an hour',
  20271. hh : '%d hours',
  20272. d : 'a day',
  20273. dd : '%d days',
  20274. M : 'a month',
  20275. MM : '%d months',
  20276. y : 'a year',
  20277. yy : '%d years'
  20278. },
  20279. relativeTime : function (number, withoutSuffix, string, isFuture) {
  20280. var output = this._relativeTime[string];
  20281. return (typeof output === 'function') ?
  20282. output(number, withoutSuffix, string, isFuture) :
  20283. output.replace(/%d/i, number);
  20284. },
  20285. pastFuture : function (diff, output) {
  20286. var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
  20287. return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
  20288. },
  20289. ordinal : function (number) {
  20290. return this._ordinal.replace('%d', number);
  20291. },
  20292. _ordinal : '%d',
  20293. preparse : function (string) {
  20294. return string;
  20295. },
  20296. postformat : function (string) {
  20297. return string;
  20298. },
  20299. week : function (mom) {
  20300. return weekOfYear(mom, this._week.dow, this._week.doy).week;
  20301. },
  20302. _week : {
  20303. dow : 0, // Sunday is the first day of the week.
  20304. doy : 6 // The week that contains Jan 1st is the first week of the year.
  20305. },
  20306. _invalidDate: 'Invalid date',
  20307. invalidDate: function () {
  20308. return this._invalidDate;
  20309. }
  20310. });
  20311. /************************************
  20312. Formatting
  20313. ************************************/
  20314. function removeFormattingTokens(input) {
  20315. if (input.match(/\[[\s\S]/)) {
  20316. return input.replace(/^\[|\]$/g, '');
  20317. }
  20318. return input.replace(/\\/g, '');
  20319. }
  20320. function makeFormatFunction(format) {
  20321. var array = format.match(formattingTokens), i, length;
  20322. for (i = 0, length = array.length; i < length; i++) {
  20323. if (formatTokenFunctions[array[i]]) {
  20324. array[i] = formatTokenFunctions[array[i]];
  20325. } else {
  20326. array[i] = removeFormattingTokens(array[i]);
  20327. }
  20328. }
  20329. return function (mom) {
  20330. var output = '';
  20331. for (i = 0; i < length; i++) {
  20332. output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
  20333. }
  20334. return output;
  20335. };
  20336. }
  20337. // format date using native date object
  20338. function formatMoment(m, format) {
  20339. if (!m.isValid()) {
  20340. return m.localeData().invalidDate();
  20341. }
  20342. format = expandFormat(format, m.localeData());
  20343. if (!formatFunctions[format]) {
  20344. formatFunctions[format] = makeFormatFunction(format);
  20345. }
  20346. return formatFunctions[format](m);
  20347. }
  20348. function expandFormat(format, locale) {
  20349. var i = 5;
  20350. function replaceLongDateFormatTokens(input) {
  20351. return locale.longDateFormat(input) || input;
  20352. }
  20353. localFormattingTokens.lastIndex = 0;
  20354. while (i >= 0 && localFormattingTokens.test(format)) {
  20355. format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
  20356. localFormattingTokens.lastIndex = 0;
  20357. i -= 1;
  20358. }
  20359. return format;
  20360. }
  20361. /************************************
  20362. Parsing
  20363. ************************************/
  20364. // get the regex to find the next token
  20365. function getParseRegexForToken(token, config) {
  20366. var a, strict = config._strict;
  20367. switch (token) {
  20368. case 'Q':
  20369. return parseTokenOneDigit;
  20370. case 'DDDD':
  20371. return parseTokenThreeDigits;
  20372. case 'YYYY':
  20373. case 'GGGG':
  20374. case 'gggg':
  20375. return strict ? parseTokenFourDigits : parseTokenOneToFourDigits;
  20376. case 'Y':
  20377. case 'G':
  20378. case 'g':
  20379. return parseTokenSignedNumber;
  20380. case 'YYYYYY':
  20381. case 'YYYYY':
  20382. case 'GGGGG':
  20383. case 'ggggg':
  20384. return strict ? parseTokenSixDigits : parseTokenOneToSixDigits;
  20385. case 'S':
  20386. if (strict) {
  20387. return parseTokenOneDigit;
  20388. }
  20389. /* falls through */
  20390. case 'SS':
  20391. if (strict) {
  20392. return parseTokenTwoDigits;
  20393. }
  20394. /* falls through */
  20395. case 'SSS':
  20396. if (strict) {
  20397. return parseTokenThreeDigits;
  20398. }
  20399. /* falls through */
  20400. case 'DDD':
  20401. return parseTokenOneToThreeDigits;
  20402. case 'MMM':
  20403. case 'MMMM':
  20404. case 'dd':
  20405. case 'ddd':
  20406. case 'dddd':
  20407. return parseTokenWord;
  20408. case 'a':
  20409. case 'A':
  20410. return config._locale._meridiemParse;
  20411. case 'X':
  20412. return parseTokenTimestampMs;
  20413. case 'Z':
  20414. case 'ZZ':
  20415. return parseTokenTimezone;
  20416. case 'T':
  20417. return parseTokenT;
  20418. case 'SSSS':
  20419. return parseTokenDigits;
  20420. case 'MM':
  20421. case 'DD':
  20422. case 'YY':
  20423. case 'GG':
  20424. case 'gg':
  20425. case 'HH':
  20426. case 'hh':
  20427. case 'mm':
  20428. case 'ss':
  20429. case 'ww':
  20430. case 'WW':
  20431. return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits;
  20432. case 'M':
  20433. case 'D':
  20434. case 'd':
  20435. case 'H':
  20436. case 'h':
  20437. case 'm':
  20438. case 's':
  20439. case 'w':
  20440. case 'W':
  20441. case 'e':
  20442. case 'E':
  20443. return parseTokenOneOrTwoDigits;
  20444. case 'Do':
  20445. return parseTokenOrdinal;
  20446. default :
  20447. a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), 'i'));
  20448. return a;
  20449. }
  20450. }
  20451. function timezoneMinutesFromString(string) {
  20452. string = string || '';
  20453. var possibleTzMatches = (string.match(parseTokenTimezone) || []),
  20454. tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [],
  20455. parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
  20456. minutes = +(parts[1] * 60) + toInt(parts[2]);
  20457. return parts[0] === '+' ? -minutes : minutes;
  20458. }
  20459. // function to convert string input to date
  20460. function addTimeToArrayFromToken(token, input, config) {
  20461. var a, datePartArray = config._a;
  20462. switch (token) {
  20463. // QUARTER
  20464. case 'Q':
  20465. if (input != null) {
  20466. datePartArray[MONTH] = (toInt(input) - 1) * 3;
  20467. }
  20468. break;
  20469. // MONTH
  20470. case 'M' : // fall through to MM
  20471. case 'MM' :
  20472. if (input != null) {
  20473. datePartArray[MONTH] = toInt(input) - 1;
  20474. }
  20475. break;
  20476. case 'MMM' : // fall through to MMMM
  20477. case 'MMMM' :
  20478. a = config._locale.monthsParse(input);
  20479. // if we didn't find a month name, mark the date as invalid.
  20480. if (a != null) {
  20481. datePartArray[MONTH] = a;
  20482. } else {
  20483. config._pf.invalidMonth = input;
  20484. }
  20485. break;
  20486. // DAY OF MONTH
  20487. case 'D' : // fall through to DD
  20488. case 'DD' :
  20489. if (input != null) {
  20490. datePartArray[DATE] = toInt(input);
  20491. }
  20492. break;
  20493. case 'Do' :
  20494. if (input != null) {
  20495. datePartArray[DATE] = toInt(parseInt(input, 10));
  20496. }
  20497. break;
  20498. // DAY OF YEAR
  20499. case 'DDD' : // fall through to DDDD
  20500. case 'DDDD' :
  20501. if (input != null) {
  20502. config._dayOfYear = toInt(input);
  20503. }
  20504. break;
  20505. // YEAR
  20506. case 'YY' :
  20507. datePartArray[YEAR] = moment.parseTwoDigitYear(input);
  20508. break;
  20509. case 'YYYY' :
  20510. case 'YYYYY' :
  20511. case 'YYYYYY' :
  20512. datePartArray[YEAR] = toInt(input);
  20513. break;
  20514. // AM / PM
  20515. case 'a' : // fall through to A
  20516. case 'A' :
  20517. config._isPm = config._locale.isPM(input);
  20518. break;
  20519. // 24 HOUR
  20520. case 'H' : // fall through to hh
  20521. case 'HH' : // fall through to hh
  20522. case 'h' : // fall through to hh
  20523. case 'hh' :
  20524. datePartArray[HOUR] = toInt(input);
  20525. break;
  20526. // MINUTE
  20527. case 'm' : // fall through to mm
  20528. case 'mm' :
  20529. datePartArray[MINUTE] = toInt(input);
  20530. break;
  20531. // SECOND
  20532. case 's' : // fall through to ss
  20533. case 'ss' :
  20534. datePartArray[SECOND] = toInt(input);
  20535. break;
  20536. // MILLISECOND
  20537. case 'S' :
  20538. case 'SS' :
  20539. case 'SSS' :
  20540. case 'SSSS' :
  20541. datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000);
  20542. break;
  20543. // UNIX TIMESTAMP WITH MS
  20544. case 'X':
  20545. config._d = new Date(parseFloat(input) * 1000);
  20546. break;
  20547. // TIMEZONE
  20548. case 'Z' : // fall through to ZZ
  20549. case 'ZZ' :
  20550. config._useUTC = true;
  20551. config._tzm = timezoneMinutesFromString(input);
  20552. break;
  20553. // WEEKDAY - human
  20554. case 'dd':
  20555. case 'ddd':
  20556. case 'dddd':
  20557. a = config._locale.weekdaysParse(input);
  20558. // if we didn't get a weekday name, mark the date as invalid
  20559. if (a != null) {
  20560. config._w = config._w || {};
  20561. config._w['d'] = a;
  20562. } else {
  20563. config._pf.invalidWeekday = input;
  20564. }
  20565. break;
  20566. // WEEK, WEEK DAY - numeric
  20567. case 'w':
  20568. case 'ww':
  20569. case 'W':
  20570. case 'WW':
  20571. case 'd':
  20572. case 'e':
  20573. case 'E':
  20574. token = token.substr(0, 1);
  20575. /* falls through */
  20576. case 'gggg':
  20577. case 'GGGG':
  20578. case 'GGGGG':
  20579. token = token.substr(0, 2);
  20580. if (input) {
  20581. config._w = config._w || {};
  20582. config._w[token] = toInt(input);
  20583. }
  20584. break;
  20585. case 'gg':
  20586. case 'GG':
  20587. config._w = config._w || {};
  20588. config._w[token] = moment.parseTwoDigitYear(input);
  20589. }
  20590. }
  20591. function dayOfYearFromWeekInfo(config) {
  20592. var w, weekYear, week, weekday, dow, doy, temp;
  20593. w = config._w;
  20594. if (w.GG != null || w.W != null || w.E != null) {
  20595. dow = 1;
  20596. doy = 4;
  20597. // TODO: We need to take the current isoWeekYear, but that depends on
  20598. // how we interpret now (local, utc, fixed offset). So create
  20599. // a now version of current config (take local/utc/offset flags, and
  20600. // create now).
  20601. weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year);
  20602. week = dfl(w.W, 1);
  20603. weekday = dfl(w.E, 1);
  20604. } else {
  20605. dow = config._locale._week.dow;
  20606. doy = config._locale._week.doy;
  20607. weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year);
  20608. week = dfl(w.w, 1);
  20609. if (w.d != null) {
  20610. // weekday -- low day numbers are considered next week
  20611. weekday = w.d;
  20612. if (weekday < dow) {
  20613. ++week;
  20614. }
  20615. } else if (w.e != null) {
  20616. // local weekday -- counting starts from begining of week
  20617. weekday = w.e + dow;
  20618. } else {
  20619. // default to begining of week
  20620. weekday = dow;
  20621. }
  20622. }
  20623. temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow);
  20624. config._a[YEAR] = temp.year;
  20625. config._dayOfYear = temp.dayOfYear;
  20626. }
  20627. // convert an array to a date.
  20628. // the array should mirror the parameters below
  20629. // note: all values past the year are optional and will default to the lowest possible value.
  20630. // [year, month, day , hour, minute, second, millisecond]
  20631. function dateFromConfig(config) {
  20632. var i, date, input = [], currentDate, yearToUse;
  20633. if (config._d) {
  20634. return;
  20635. }
  20636. currentDate = currentDateArray(config);
  20637. //compute day of the year from weeks and weekdays
  20638. if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
  20639. dayOfYearFromWeekInfo(config);
  20640. }
  20641. //if the day of the year is set, figure out what it is
  20642. if (config._dayOfYear) {
  20643. yearToUse = dfl(config._a[YEAR], currentDate[YEAR]);
  20644. if (config._dayOfYear > daysInYear(yearToUse)) {
  20645. config._pf._overflowDayOfYear = true;
  20646. }
  20647. date = makeUTCDate(yearToUse, 0, config._dayOfYear);
  20648. config._a[MONTH] = date.getUTCMonth();
  20649. config._a[DATE] = date.getUTCDate();
  20650. }
  20651. // Default to current date.
  20652. // * if no year, month, day of month are given, default to today
  20653. // * if day of month is given, default month and year
  20654. // * if month is given, default only year
  20655. // * if year is given, don't default anything
  20656. for (i = 0; i < 3 && config._a[i] == null; ++i) {
  20657. config._a[i] = input[i] = currentDate[i];
  20658. }
  20659. // Zero out whatever was not defaulted, including time
  20660. for (; i < 7; i++) {
  20661. config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
  20662. }
  20663. config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input);
  20664. // Apply timezone offset from input. The actual zone can be changed
  20665. // with parseZone.
  20666. if (config._tzm != null) {
  20667. config._d.setUTCMinutes(config._d.getUTCMinutes() + config._tzm);
  20668. }
  20669. }
  20670. function dateFromObject(config) {
  20671. var normalizedInput;
  20672. if (config._d) {
  20673. return;
  20674. }
  20675. normalizedInput = normalizeObjectUnits(config._i);
  20676. config._a = [
  20677. normalizedInput.year,
  20678. normalizedInput.month,
  20679. normalizedInput.day,
  20680. normalizedInput.hour,
  20681. normalizedInput.minute,
  20682. normalizedInput.second,
  20683. normalizedInput.millisecond
  20684. ];
  20685. dateFromConfig(config);
  20686. }
  20687. function currentDateArray(config) {
  20688. var now = new Date();
  20689. if (config._useUTC) {
  20690. return [
  20691. now.getUTCFullYear(),
  20692. now.getUTCMonth(),
  20693. now.getUTCDate()
  20694. ];
  20695. } else {
  20696. return [now.getFullYear(), now.getMonth(), now.getDate()];
  20697. }
  20698. }
  20699. // date from string and format string
  20700. function makeDateFromStringAndFormat(config) {
  20701. if (config._f === moment.ISO_8601) {
  20702. parseISO(config);
  20703. return;
  20704. }
  20705. config._a = [];
  20706. config._pf.empty = true;
  20707. // This array is used to make a Date, either with `new Date` or `Date.UTC`
  20708. var string = '' + config._i,
  20709. i, parsedInput, tokens, token, skipped,
  20710. stringLength = string.length,
  20711. totalParsedInputLength = 0;
  20712. tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
  20713. for (i = 0; i < tokens.length; i++) {
  20714. token = tokens[i];
  20715. parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
  20716. if (parsedInput) {
  20717. skipped = string.substr(0, string.indexOf(parsedInput));
  20718. if (skipped.length > 0) {
  20719. config._pf.unusedInput.push(skipped);
  20720. }
  20721. string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
  20722. totalParsedInputLength += parsedInput.length;
  20723. }
  20724. // don't parse if it's not a known token
  20725. if (formatTokenFunctions[token]) {
  20726. if (parsedInput) {
  20727. config._pf.empty = false;
  20728. }
  20729. else {
  20730. config._pf.unusedTokens.push(token);
  20731. }
  20732. addTimeToArrayFromToken(token, parsedInput, config);
  20733. }
  20734. else if (config._strict && !parsedInput) {
  20735. config._pf.unusedTokens.push(token);
  20736. }
  20737. }
  20738. // add remaining unparsed input length to the string
  20739. config._pf.charsLeftOver = stringLength - totalParsedInputLength;
  20740. if (string.length > 0) {
  20741. config._pf.unusedInput.push(string);
  20742. }
  20743. // handle am pm
  20744. if (config._isPm && config._a[HOUR] < 12) {
  20745. config._a[HOUR] += 12;
  20746. }
  20747. // if is 12 am, change hours to 0
  20748. if (config._isPm === false && config._a[HOUR] === 12) {
  20749. config._a[HOUR] = 0;
  20750. }
  20751. dateFromConfig(config);
  20752. checkOverflow(config);
  20753. }
  20754. function unescapeFormat(s) {
  20755. return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
  20756. return p1 || p2 || p3 || p4;
  20757. });
  20758. }
  20759. // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
  20760. function regexpEscape(s) {
  20761. return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
  20762. }
  20763. // date from string and array of format strings
  20764. function makeDateFromStringAndArray(config) {
  20765. var tempConfig,
  20766. bestMoment,
  20767. scoreToBeat,
  20768. i,
  20769. currentScore;
  20770. if (config._f.length === 0) {
  20771. config._pf.invalidFormat = true;
  20772. config._d = new Date(NaN);
  20773. return;
  20774. }
  20775. for (i = 0; i < config._f.length; i++) {
  20776. currentScore = 0;
  20777. tempConfig = copyConfig({}, config);
  20778. if (config._useUTC != null) {
  20779. tempConfig._useUTC = config._useUTC;
  20780. }
  20781. tempConfig._pf = defaultParsingFlags();
  20782. tempConfig._f = config._f[i];
  20783. makeDateFromStringAndFormat(tempConfig);
  20784. if (!isValid(tempConfig)) {
  20785. continue;
  20786. }
  20787. // if there is any input that was not parsed add a penalty for that format
  20788. currentScore += tempConfig._pf.charsLeftOver;
  20789. //or tokens
  20790. currentScore += tempConfig._pf.unusedTokens.length * 10;
  20791. tempConfig._pf.score = currentScore;
  20792. if (scoreToBeat == null || currentScore < scoreToBeat) {
  20793. scoreToBeat = currentScore;
  20794. bestMoment = tempConfig;
  20795. }
  20796. }
  20797. extend(config, bestMoment || tempConfig);
  20798. }
  20799. // date from iso format
  20800. function parseISO(config) {
  20801. var i, l,
  20802. string = config._i,
  20803. match = isoRegex.exec(string);
  20804. if (match) {
  20805. config._pf.iso = true;
  20806. for (i = 0, l = isoDates.length; i < l; i++) {
  20807. if (isoDates[i][1].exec(string)) {
  20808. // match[5] should be 'T' or undefined
  20809. config._f = isoDates[i][0] + (match[6] || ' ');
  20810. break;
  20811. }
  20812. }
  20813. for (i = 0, l = isoTimes.length; i < l; i++) {
  20814. if (isoTimes[i][1].exec(string)) {
  20815. config._f += isoTimes[i][0];
  20816. break;
  20817. }
  20818. }
  20819. if (string.match(parseTokenTimezone)) {
  20820. config._f += 'Z';
  20821. }
  20822. makeDateFromStringAndFormat(config);
  20823. } else {
  20824. config._isValid = false;
  20825. }
  20826. }
  20827. // date from iso format or fallback
  20828. function makeDateFromString(config) {
  20829. parseISO(config);
  20830. if (config._isValid === false) {
  20831. delete config._isValid;
  20832. moment.createFromInputFallback(config);
  20833. }
  20834. }
  20835. function map(arr, fn) {
  20836. var res = [], i;
  20837. for (i = 0; i < arr.length; ++i) {
  20838. res.push(fn(arr[i], i));
  20839. }
  20840. return res;
  20841. }
  20842. function makeDateFromInput(config) {
  20843. var input = config._i, matched;
  20844. if (input === undefined) {
  20845. config._d = new Date();
  20846. } else if (isDate(input)) {
  20847. config._d = new Date(+input);
  20848. } else if ((matched = aspNetJsonRegex.exec(input)) !== null) {
  20849. config._d = new Date(+matched[1]);
  20850. } else if (typeof input === 'string') {
  20851. makeDateFromString(config);
  20852. } else if (isArray(input)) {
  20853. config._a = map(input.slice(0), function (obj) {
  20854. return parseInt(obj, 10);
  20855. });
  20856. dateFromConfig(config);
  20857. } else if (typeof(input) === 'object') {
  20858. dateFromObject(config);
  20859. } else if (typeof(input) === 'number') {
  20860. // from milliseconds
  20861. config._d = new Date(input);
  20862. } else {
  20863. moment.createFromInputFallback(config);
  20864. }
  20865. }
  20866. function makeDate(y, m, d, h, M, s, ms) {
  20867. //can't just apply() to create a date:
  20868. //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
  20869. var date = new Date(y, m, d, h, M, s, ms);
  20870. //the date constructor doesn't accept years < 1970
  20871. if (y < 1970) {
  20872. date.setFullYear(y);
  20873. }
  20874. return date;
  20875. }
  20876. function makeUTCDate(y) {
  20877. var date = new Date(Date.UTC.apply(null, arguments));
  20878. if (y < 1970) {
  20879. date.setUTCFullYear(y);
  20880. }
  20881. return date;
  20882. }
  20883. function parseWeekday(input, locale) {
  20884. if (typeof input === 'string') {
  20885. if (!isNaN(input)) {
  20886. input = parseInt(input, 10);
  20887. }
  20888. else {
  20889. input = locale.weekdaysParse(input);
  20890. if (typeof input !== 'number') {
  20891. return null;
  20892. }
  20893. }
  20894. }
  20895. return input;
  20896. }
  20897. /************************************
  20898. Relative Time
  20899. ************************************/
  20900. // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
  20901. function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
  20902. return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
  20903. }
  20904. function relativeTime(posNegDuration, withoutSuffix, locale) {
  20905. var duration = moment.duration(posNegDuration).abs(),
  20906. seconds = round(duration.as('s')),
  20907. minutes = round(duration.as('m')),
  20908. hours = round(duration.as('h')),
  20909. days = round(duration.as('d')),
  20910. months = round(duration.as('M')),
  20911. years = round(duration.as('y')),
  20912. args = seconds < relativeTimeThresholds.s && ['s', seconds] ||
  20913. minutes === 1 && ['m'] ||
  20914. minutes < relativeTimeThresholds.m && ['mm', minutes] ||
  20915. hours === 1 && ['h'] ||
  20916. hours < relativeTimeThresholds.h && ['hh', hours] ||
  20917. days === 1 && ['d'] ||
  20918. days < relativeTimeThresholds.d && ['dd', days] ||
  20919. months === 1 && ['M'] ||
  20920. months < relativeTimeThresholds.M && ['MM', months] ||
  20921. years === 1 && ['y'] || ['yy', years];
  20922. args[2] = withoutSuffix;
  20923. args[3] = +posNegDuration > 0;
  20924. args[4] = locale;
  20925. return substituteTimeAgo.apply({}, args);
  20926. }
  20927. /************************************
  20928. Week of Year
  20929. ************************************/
  20930. // firstDayOfWeek 0 = sun, 6 = sat
  20931. // the day of the week that starts the week
  20932. // (usually sunday or monday)
  20933. // firstDayOfWeekOfYear 0 = sun, 6 = sat
  20934. // the first week is the week that contains the first
  20935. // of this day of the week
  20936. // (eg. ISO weeks use thursday (4))
  20937. function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
  20938. var end = firstDayOfWeekOfYear - firstDayOfWeek,
  20939. daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(),
  20940. adjustedMoment;
  20941. if (daysToDayOfWeek > end) {
  20942. daysToDayOfWeek -= 7;
  20943. }
  20944. if (daysToDayOfWeek < end - 7) {
  20945. daysToDayOfWeek += 7;
  20946. }
  20947. adjustedMoment = moment(mom).add(daysToDayOfWeek, 'd');
  20948. return {
  20949. week: Math.ceil(adjustedMoment.dayOfYear() / 7),
  20950. year: adjustedMoment.year()
  20951. };
  20952. }
  20953. //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
  20954. function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) {
  20955. var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear;
  20956. d = d === 0 ? 7 : d;
  20957. weekday = weekday != null ? weekday : firstDayOfWeek;
  20958. daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0);
  20959. dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1;
  20960. return {
  20961. year: dayOfYear > 0 ? year : year - 1,
  20962. dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear
  20963. };
  20964. }
  20965. /************************************
  20966. Top Level Functions
  20967. ************************************/
  20968. function makeMoment(config) {
  20969. var input = config._i,
  20970. format = config._f;
  20971. config._locale = config._locale || moment.localeData(config._l);
  20972. if (input === null || (format === undefined && input === '')) {
  20973. return moment.invalid({nullInput: true});
  20974. }
  20975. if (typeof input === 'string') {
  20976. config._i = input = config._locale.preparse(input);
  20977. }
  20978. if (moment.isMoment(input)) {
  20979. return new Moment(input, true);
  20980. } else if (format) {
  20981. if (isArray(format)) {
  20982. makeDateFromStringAndArray(config);
  20983. } else {
  20984. makeDateFromStringAndFormat(config);
  20985. }
  20986. } else {
  20987. makeDateFromInput(config);
  20988. }
  20989. return new Moment(config);
  20990. }
  20991. moment = function (input, format, locale, strict) {
  20992. var c;
  20993. if (typeof(locale) === 'boolean') {
  20994. strict = locale;
  20995. locale = undefined;
  20996. }
  20997. // object construction must be done this way.
  20998. // https://github.com/moment/moment/issues/1423
  20999. c = {};
  21000. c._isAMomentObject = true;
  21001. c._i = input;
  21002. c._f = format;
  21003. c._l = locale;
  21004. c._strict = strict;
  21005. c._isUTC = false;
  21006. c._pf = defaultParsingFlags();
  21007. return makeMoment(c);
  21008. };
  21009. moment.suppressDeprecationWarnings = false;
  21010. moment.createFromInputFallback = deprecate(
  21011. 'moment construction falls back to js Date. This is ' +
  21012. 'discouraged and will be removed in upcoming major ' +
  21013. 'release. Please refer to ' +
  21014. 'https://github.com/moment/moment/issues/1407 for more info.',
  21015. function (config) {
  21016. config._d = new Date(config._i);
  21017. }
  21018. );
  21019. // Pick a moment m from moments so that m[fn](other) is true for all
  21020. // other. This relies on the function fn to be transitive.
  21021. //
  21022. // moments should either be an array of moment objects or an array, whose
  21023. // first element is an array of moment objects.
  21024. function pickBy(fn, moments) {
  21025. var res, i;
  21026. if (moments.length === 1 && isArray(moments[0])) {
  21027. moments = moments[0];
  21028. }
  21029. if (!moments.length) {
  21030. return moment();
  21031. }
  21032. res = moments[0];
  21033. for (i = 1; i < moments.length; ++i) {
  21034. if (moments[i][fn](res)) {
  21035. res = moments[i];
  21036. }
  21037. }
  21038. return res;
  21039. }
  21040. moment.min = function () {
  21041. var args = [].slice.call(arguments, 0);
  21042. return pickBy('isBefore', args);
  21043. };
  21044. moment.max = function () {
  21045. var args = [].slice.call(arguments, 0);
  21046. return pickBy('isAfter', args);
  21047. };
  21048. // creating with utc
  21049. moment.utc = function (input, format, locale, strict) {
  21050. var c;
  21051. if (typeof(locale) === 'boolean') {
  21052. strict = locale;
  21053. locale = undefined;
  21054. }
  21055. // object construction must be done this way.
  21056. // https://github.com/moment/moment/issues/1423
  21057. c = {};
  21058. c._isAMomentObject = true;
  21059. c._useUTC = true;
  21060. c._isUTC = true;
  21061. c._l = locale;
  21062. c._i = input;
  21063. c._f = format;
  21064. c._strict = strict;
  21065. c._pf = defaultParsingFlags();
  21066. return makeMoment(c).utc();
  21067. };
  21068. // creating with unix timestamp (in seconds)
  21069. moment.unix = function (input) {
  21070. return moment(input * 1000);
  21071. };
  21072. // duration
  21073. moment.duration = function (input, key) {
  21074. var duration = input,
  21075. // matching against regexp is expensive, do it on demand
  21076. match = null,
  21077. sign,
  21078. ret,
  21079. parseIso,
  21080. diffRes;
  21081. if (moment.isDuration(input)) {
  21082. duration = {
  21083. ms: input._milliseconds,
  21084. d: input._days,
  21085. M: input._months
  21086. };
  21087. } else if (typeof input === 'number') {
  21088. duration = {};
  21089. if (key) {
  21090. duration[key] = input;
  21091. } else {
  21092. duration.milliseconds = input;
  21093. }
  21094. } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) {
  21095. sign = (match[1] === '-') ? -1 : 1;
  21096. duration = {
  21097. y: 0,
  21098. d: toInt(match[DATE]) * sign,
  21099. h: toInt(match[HOUR]) * sign,
  21100. m: toInt(match[MINUTE]) * sign,
  21101. s: toInt(match[SECOND]) * sign,
  21102. ms: toInt(match[MILLISECOND]) * sign
  21103. };
  21104. } else if (!!(match = isoDurationRegex.exec(input))) {
  21105. sign = (match[1] === '-') ? -1 : 1;
  21106. parseIso = function (inp) {
  21107. // We'd normally use ~~inp for this, but unfortunately it also
  21108. // converts floats to ints.
  21109. // inp may be undefined, so careful calling replace on it.
  21110. var res = inp && parseFloat(inp.replace(',', '.'));
  21111. // apply sign while we're at it
  21112. return (isNaN(res) ? 0 : res) * sign;
  21113. };
  21114. duration = {
  21115. y: parseIso(match[2]),
  21116. M: parseIso(match[3]),
  21117. d: parseIso(match[4]),
  21118. h: parseIso(match[5]),
  21119. m: parseIso(match[6]),
  21120. s: parseIso(match[7]),
  21121. w: parseIso(match[8])
  21122. };
  21123. } else if (typeof duration === 'object' &&
  21124. ('from' in duration || 'to' in duration)) {
  21125. diffRes = momentsDifference(moment(duration.from), moment(duration.to));
  21126. duration = {};
  21127. duration.ms = diffRes.milliseconds;
  21128. duration.M = diffRes.months;
  21129. }
  21130. ret = new Duration(duration);
  21131. if (moment.isDuration(input) && hasOwnProp(input, '_locale')) {
  21132. ret._locale = input._locale;
  21133. }
  21134. return ret;
  21135. };
  21136. // version number
  21137. moment.version = VERSION;
  21138. // default format
  21139. moment.defaultFormat = isoFormat;
  21140. // constant that refers to the ISO standard
  21141. moment.ISO_8601 = function () {};
  21142. // Plugins that add properties should also add the key here (null value),
  21143. // so we can properly clone ourselves.
  21144. moment.momentProperties = momentProperties;
  21145. // This function will be called whenever a moment is mutated.
  21146. // It is intended to keep the offset in sync with the timezone.
  21147. moment.updateOffset = function () {};
  21148. // This function allows you to set a threshold for relative time strings
  21149. moment.relativeTimeThreshold = function (threshold, limit) {
  21150. if (relativeTimeThresholds[threshold] === undefined) {
  21151. return false;
  21152. }
  21153. if (limit === undefined) {
  21154. return relativeTimeThresholds[threshold];
  21155. }
  21156. relativeTimeThresholds[threshold] = limit;
  21157. return true;
  21158. };
  21159. moment.lang = deprecate(
  21160. 'moment.lang is deprecated. Use moment.locale instead.',
  21161. function (key, value) {
  21162. return moment.locale(key, value);
  21163. }
  21164. );
  21165. // This function will load locale and then set the global locale. If
  21166. // no arguments are passed in, it will simply return the current global
  21167. // locale key.
  21168. moment.locale = function (key, values) {
  21169. var data;
  21170. if (key) {
  21171. if (typeof(values) !== 'undefined') {
  21172. data = moment.defineLocale(key, values);
  21173. }
  21174. else {
  21175. data = moment.localeData(key);
  21176. }
  21177. if (data) {
  21178. moment.duration._locale = moment._locale = data;
  21179. }
  21180. }
  21181. return moment._locale._abbr;
  21182. };
  21183. moment.defineLocale = function (name, values) {
  21184. if (values !== null) {
  21185. values.abbr = name;
  21186. if (!locales[name]) {
  21187. locales[name] = new Locale();
  21188. }
  21189. locales[name].set(values);
  21190. // backwards compat for now: also set the locale
  21191. moment.locale(name);
  21192. return locales[name];
  21193. } else {
  21194. // useful for testing
  21195. delete locales[name];
  21196. return null;
  21197. }
  21198. };
  21199. moment.langData = deprecate(
  21200. 'moment.langData is deprecated. Use moment.localeData instead.',
  21201. function (key) {
  21202. return moment.localeData(key);
  21203. }
  21204. );
  21205. // returns locale data
  21206. moment.localeData = function (key) {
  21207. var locale;
  21208. if (key && key._locale && key._locale._abbr) {
  21209. key = key._locale._abbr;
  21210. }
  21211. if (!key) {
  21212. return moment._locale;
  21213. }
  21214. if (!isArray(key)) {
  21215. //short-circuit everything else
  21216. locale = loadLocale(key);
  21217. if (locale) {
  21218. return locale;
  21219. }
  21220. key = [key];
  21221. }
  21222. return chooseLocale(key);
  21223. };
  21224. // compare moment object
  21225. moment.isMoment = function (obj) {
  21226. return obj instanceof Moment ||
  21227. (obj != null && hasOwnProp(obj, '_isAMomentObject'));
  21228. };
  21229. // for typechecking Duration objects
  21230. moment.isDuration = function (obj) {
  21231. return obj instanceof Duration;
  21232. };
  21233. for (i = lists.length - 1; i >= 0; --i) {
  21234. makeList(lists[i]);
  21235. }
  21236. moment.normalizeUnits = function (units) {
  21237. return normalizeUnits(units);
  21238. };
  21239. moment.invalid = function (flags) {
  21240. var m = moment.utc(NaN);
  21241. if (flags != null) {
  21242. extend(m._pf, flags);
  21243. }
  21244. else {
  21245. m._pf.userInvalidated = true;
  21246. }
  21247. return m;
  21248. };
  21249. moment.parseZone = function () {
  21250. return moment.apply(null, arguments).parseZone();
  21251. };
  21252. moment.parseTwoDigitYear = function (input) {
  21253. return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
  21254. };
  21255. /************************************
  21256. Moment Prototype
  21257. ************************************/
  21258. extend(moment.fn = Moment.prototype, {
  21259. clone : function () {
  21260. return moment(this);
  21261. },
  21262. valueOf : function () {
  21263. return +this._d + ((this._offset || 0) * 60000);
  21264. },
  21265. unix : function () {
  21266. return Math.floor(+this / 1000);
  21267. },
  21268. toString : function () {
  21269. return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
  21270. },
  21271. toDate : function () {
  21272. return this._offset ? new Date(+this) : this._d;
  21273. },
  21274. toISOString : function () {
  21275. var m = moment(this).utc();
  21276. if (0 < m.year() && m.year() <= 9999) {
  21277. return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  21278. } else {
  21279. return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  21280. }
  21281. },
  21282. toArray : function () {
  21283. var m = this;
  21284. return [
  21285. m.year(),
  21286. m.month(),
  21287. m.date(),
  21288. m.hours(),
  21289. m.minutes(),
  21290. m.seconds(),
  21291. m.milliseconds()
  21292. ];
  21293. },
  21294. isValid : function () {
  21295. return isValid(this);
  21296. },
  21297. isDSTShifted : function () {
  21298. if (this._a) {
  21299. return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0;
  21300. }
  21301. return false;
  21302. },
  21303. parsingFlags : function () {
  21304. return extend({}, this._pf);
  21305. },
  21306. invalidAt: function () {
  21307. return this._pf.overflow;
  21308. },
  21309. utc : function (keepLocalTime) {
  21310. return this.zone(0, keepLocalTime);
  21311. },
  21312. local : function (keepLocalTime) {
  21313. if (this._isUTC) {
  21314. this.zone(0, keepLocalTime);
  21315. this._isUTC = false;
  21316. if (keepLocalTime) {
  21317. this.add(this._dateTzOffset(), 'm');
  21318. }
  21319. }
  21320. return this;
  21321. },
  21322. format : function (inputString) {
  21323. var output = formatMoment(this, inputString || moment.defaultFormat);
  21324. return this.localeData().postformat(output);
  21325. },
  21326. add : createAdder(1, 'add'),
  21327. subtract : createAdder(-1, 'subtract'),
  21328. diff : function (input, units, asFloat) {
  21329. var that = makeAs(input, this),
  21330. zoneDiff = (this.zone() - that.zone()) * 6e4,
  21331. diff, output, daysAdjust;
  21332. units = normalizeUnits(units);
  21333. if (units === 'year' || units === 'month') {
  21334. // average number of days in the months in the given dates
  21335. diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2
  21336. // difference in months
  21337. output = ((this.year() - that.year()) * 12) + (this.month() - that.month());
  21338. // adjust by taking difference in days, average number of days
  21339. // and dst in the given months.
  21340. daysAdjust = (this - moment(this).startOf('month')) -
  21341. (that - moment(that).startOf('month'));
  21342. // same as above but with zones, to negate all dst
  21343. daysAdjust -= ((this.zone() - moment(this).startOf('month').zone()) -
  21344. (that.zone() - moment(that).startOf('month').zone())) * 6e4;
  21345. output += daysAdjust / diff;
  21346. if (units === 'year') {
  21347. output = output / 12;
  21348. }
  21349. } else {
  21350. diff = (this - that);
  21351. output = units === 'second' ? diff / 1e3 : // 1000
  21352. units === 'minute' ? diff / 6e4 : // 1000 * 60
  21353. units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
  21354. units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
  21355. units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
  21356. diff;
  21357. }
  21358. return asFloat ? output : absRound(output);
  21359. },
  21360. from : function (time, withoutSuffix) {
  21361. return moment.duration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
  21362. },
  21363. fromNow : function (withoutSuffix) {
  21364. return this.from(moment(), withoutSuffix);
  21365. },
  21366. calendar : function (time) {
  21367. // We want to compare the start of today, vs this.
  21368. // Getting start-of-today depends on whether we're zone'd or not.
  21369. var now = time || moment(),
  21370. sod = makeAs(now, this).startOf('day'),
  21371. diff = this.diff(sod, 'days', true),
  21372. format = diff < -6 ? 'sameElse' :
  21373. diff < -1 ? 'lastWeek' :
  21374. diff < 0 ? 'lastDay' :
  21375. diff < 1 ? 'sameDay' :
  21376. diff < 2 ? 'nextDay' :
  21377. diff < 7 ? 'nextWeek' : 'sameElse';
  21378. return this.format(this.localeData().calendar(format, this));
  21379. },
  21380. isLeapYear : function () {
  21381. return isLeapYear(this.year());
  21382. },
  21383. isDST : function () {
  21384. return (this.zone() < this.clone().month(0).zone() ||
  21385. this.zone() < this.clone().month(5).zone());
  21386. },
  21387. day : function (input) {
  21388. var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
  21389. if (input != null) {
  21390. input = parseWeekday(input, this.localeData());
  21391. return this.add(input - day, 'd');
  21392. } else {
  21393. return day;
  21394. }
  21395. },
  21396. month : makeAccessor('Month', true),
  21397. startOf : function (units) {
  21398. units = normalizeUnits(units);
  21399. // the following switch intentionally omits break keywords
  21400. // to utilize falling through the cases.
  21401. switch (units) {
  21402. case 'year':
  21403. this.month(0);
  21404. /* falls through */
  21405. case 'quarter':
  21406. case 'month':
  21407. this.date(1);
  21408. /* falls through */
  21409. case 'week':
  21410. case 'isoWeek':
  21411. case 'day':
  21412. this.hours(0);
  21413. /* falls through */
  21414. case 'hour':
  21415. this.minutes(0);
  21416. /* falls through */
  21417. case 'minute':
  21418. this.seconds(0);
  21419. /* falls through */
  21420. case 'second':
  21421. this.milliseconds(0);
  21422. /* falls through */
  21423. }
  21424. // weeks are a special case
  21425. if (units === 'week') {
  21426. this.weekday(0);
  21427. } else if (units === 'isoWeek') {
  21428. this.isoWeekday(1);
  21429. }
  21430. // quarters are also special
  21431. if (units === 'quarter') {
  21432. this.month(Math.floor(this.month() / 3) * 3);
  21433. }
  21434. return this;
  21435. },
  21436. endOf: function (units) {
  21437. units = normalizeUnits(units);
  21438. return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
  21439. },
  21440. isAfter: function (input, units) {
  21441. units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond');
  21442. if (units === 'millisecond') {
  21443. input = moment.isMoment(input) ? input : moment(input);
  21444. return +this > +input;
  21445. } else {
  21446. return +this.clone().startOf(units) > +moment(input).startOf(units);
  21447. }
  21448. },
  21449. isBefore: function (input, units) {
  21450. units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond');
  21451. if (units === 'millisecond') {
  21452. input = moment.isMoment(input) ? input : moment(input);
  21453. return +this < +input;
  21454. } else {
  21455. return +this.clone().startOf(units) < +moment(input).startOf(units);
  21456. }
  21457. },
  21458. isSame: function (input, units) {
  21459. units = normalizeUnits(units || 'millisecond');
  21460. if (units === 'millisecond') {
  21461. input = moment.isMoment(input) ? input : moment(input);
  21462. return +this === +input;
  21463. } else {
  21464. return +this.clone().startOf(units) === +makeAs(input, this).startOf(units);
  21465. }
  21466. },
  21467. min: deprecate(
  21468. 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548',
  21469. function (other) {
  21470. other = moment.apply(null, arguments);
  21471. return other < this ? this : other;
  21472. }
  21473. ),
  21474. max: deprecate(
  21475. 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548',
  21476. function (other) {
  21477. other = moment.apply(null, arguments);
  21478. return other > this ? this : other;
  21479. }
  21480. ),
  21481. // keepLocalTime = true means only change the timezone, without
  21482. // affecting the local hour. So 5:31:26 +0300 --[zone(2, true)]-->
  21483. // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist int zone
  21484. // +0200, so we adjust the time as needed, to be valid.
  21485. //
  21486. // Keeping the time actually adds/subtracts (one hour)
  21487. // from the actual represented time. That is why we call updateOffset
  21488. // a second time. In case it wants us to change the offset again
  21489. // _changeInProgress == true case, then we have to adjust, because
  21490. // there is no such time in the given timezone.
  21491. zone : function (input, keepLocalTime) {
  21492. var offset = this._offset || 0,
  21493. localAdjust;
  21494. if (input != null) {
  21495. if (typeof input === 'string') {
  21496. input = timezoneMinutesFromString(input);
  21497. }
  21498. if (Math.abs(input) < 16) {
  21499. input = input * 60;
  21500. }
  21501. if (!this._isUTC && keepLocalTime) {
  21502. localAdjust = this._dateTzOffset();
  21503. }
  21504. this._offset = input;
  21505. this._isUTC = true;
  21506. if (localAdjust != null) {
  21507. this.subtract(localAdjust, 'm');
  21508. }
  21509. if (offset !== input) {
  21510. if (!keepLocalTime || this._changeInProgress) {
  21511. addOrSubtractDurationFromMoment(this,
  21512. moment.duration(offset - input, 'm'), 1, false);
  21513. } else if (!this._changeInProgress) {
  21514. this._changeInProgress = true;
  21515. moment.updateOffset(this, true);
  21516. this._changeInProgress = null;
  21517. }
  21518. }
  21519. } else {
  21520. return this._isUTC ? offset : this._dateTzOffset();
  21521. }
  21522. return this;
  21523. },
  21524. zoneAbbr : function () {
  21525. return this._isUTC ? 'UTC' : '';
  21526. },
  21527. zoneName : function () {
  21528. return this._isUTC ? 'Coordinated Universal Time' : '';
  21529. },
  21530. parseZone : function () {
  21531. if (this._tzm) {
  21532. this.zone(this._tzm);
  21533. } else if (typeof this._i === 'string') {
  21534. this.zone(this._i);
  21535. }
  21536. return this;
  21537. },
  21538. hasAlignedHourOffset : function (input) {
  21539. if (!input) {
  21540. input = 0;
  21541. }
  21542. else {
  21543. input = moment(input).zone();
  21544. }
  21545. return (this.zone() - input) % 60 === 0;
  21546. },
  21547. daysInMonth : function () {
  21548. return daysInMonth(this.year(), this.month());
  21549. },
  21550. dayOfYear : function (input) {
  21551. var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1;
  21552. return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
  21553. },
  21554. quarter : function (input) {
  21555. return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
  21556. },
  21557. weekYear : function (input) {
  21558. var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year;
  21559. return input == null ? year : this.add((input - year), 'y');
  21560. },
  21561. isoWeekYear : function (input) {
  21562. var year = weekOfYear(this, 1, 4).year;
  21563. return input == null ? year : this.add((input - year), 'y');
  21564. },
  21565. week : function (input) {
  21566. var week = this.localeData().week(this);
  21567. return input == null ? week : this.add((input - week) * 7, 'd');
  21568. },
  21569. isoWeek : function (input) {
  21570. var week = weekOfYear(this, 1, 4).week;
  21571. return input == null ? week : this.add((input - week) * 7, 'd');
  21572. },
  21573. weekday : function (input) {
  21574. var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
  21575. return input == null ? weekday : this.add(input - weekday, 'd');
  21576. },
  21577. isoWeekday : function (input) {
  21578. // behaves the same as moment#day except
  21579. // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
  21580. // as a setter, sunday should belong to the previous week.
  21581. return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
  21582. },
  21583. isoWeeksInYear : function () {
  21584. return weeksInYear(this.year(), 1, 4);
  21585. },
  21586. weeksInYear : function () {
  21587. var weekInfo = this.localeData()._week;
  21588. return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
  21589. },
  21590. get : function (units) {
  21591. units = normalizeUnits(units);
  21592. return this[units]();
  21593. },
  21594. set : function (units, value) {
  21595. units = normalizeUnits(units);
  21596. if (typeof this[units] === 'function') {
  21597. this[units](value);
  21598. }
  21599. return this;
  21600. },
  21601. // If passed a locale key, it will set the locale for this
  21602. // instance. Otherwise, it will return the locale configuration
  21603. // variables for this instance.
  21604. locale : function (key) {
  21605. var newLocaleData;
  21606. if (key === undefined) {
  21607. return this._locale._abbr;
  21608. } else {
  21609. newLocaleData = moment.localeData(key);
  21610. if (newLocaleData != null) {
  21611. this._locale = newLocaleData;
  21612. }
  21613. return this;
  21614. }
  21615. },
  21616. lang : deprecate(
  21617. 'moment().lang() is deprecated. Use moment().localeData() instead.',
  21618. function (key) {
  21619. if (key === undefined) {
  21620. return this.localeData();
  21621. } else {
  21622. return this.locale(key);
  21623. }
  21624. }
  21625. ),
  21626. localeData : function () {
  21627. return this._locale;
  21628. },
  21629. _dateTzOffset : function () {
  21630. // On Firefox.24 Date#getTimezoneOffset returns a floating point.
  21631. // https://github.com/moment/moment/pull/1871
  21632. return Math.round(this._d.getTimezoneOffset() / 15) * 15;
  21633. }
  21634. });
  21635. function rawMonthSetter(mom, value) {
  21636. var dayOfMonth;
  21637. // TODO: Move this out of here!
  21638. if (typeof value === 'string') {
  21639. value = mom.localeData().monthsParse(value);
  21640. // TODO: Another silent failure?
  21641. if (typeof value !== 'number') {
  21642. return mom;
  21643. }
  21644. }
  21645. dayOfMonth = Math.min(mom.date(),
  21646. daysInMonth(mom.year(), value));
  21647. mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
  21648. return mom;
  21649. }
  21650. function rawGetter(mom, unit) {
  21651. return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]();
  21652. }
  21653. function rawSetter(mom, unit, value) {
  21654. if (unit === 'Month') {
  21655. return rawMonthSetter(mom, value);
  21656. } else {
  21657. return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
  21658. }
  21659. }
  21660. function makeAccessor(unit, keepTime) {
  21661. return function (value) {
  21662. if (value != null) {
  21663. rawSetter(this, unit, value);
  21664. moment.updateOffset(this, keepTime);
  21665. return this;
  21666. } else {
  21667. return rawGetter(this, unit);
  21668. }
  21669. };
  21670. }
  21671. moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false);
  21672. moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false);
  21673. moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false);
  21674. // Setting the hour should keep the time, because the user explicitly
  21675. // specified which hour he wants. So trying to maintain the same hour (in
  21676. // a new timezone) makes sense. Adding/subtracting hours does not follow
  21677. // this rule.
  21678. moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true);
  21679. // moment.fn.month is defined separately
  21680. moment.fn.date = makeAccessor('Date', true);
  21681. moment.fn.dates = deprecate('dates accessor is deprecated. Use date instead.', makeAccessor('Date', true));
  21682. moment.fn.year = makeAccessor('FullYear', true);
  21683. moment.fn.years = deprecate('years accessor is deprecated. Use year instead.', makeAccessor('FullYear', true));
  21684. // add plural methods
  21685. moment.fn.days = moment.fn.day;
  21686. moment.fn.months = moment.fn.month;
  21687. moment.fn.weeks = moment.fn.week;
  21688. moment.fn.isoWeeks = moment.fn.isoWeek;
  21689. moment.fn.quarters = moment.fn.quarter;
  21690. // add aliased format methods
  21691. moment.fn.toJSON = moment.fn.toISOString;
  21692. /************************************
  21693. Duration Prototype
  21694. ************************************/
  21695. function daysToYears (days) {
  21696. // 400 years have 146097 days (taking into account leap year rules)
  21697. return days * 400 / 146097;
  21698. }
  21699. function yearsToDays (years) {
  21700. // years * 365 + absRound(years / 4) -
  21701. // absRound(years / 100) + absRound(years / 400);
  21702. return years * 146097 / 400;
  21703. }
  21704. extend(moment.duration.fn = Duration.prototype, {
  21705. _bubble : function () {
  21706. var milliseconds = this._milliseconds,
  21707. days = this._days,
  21708. months = this._months,
  21709. data = this._data,
  21710. seconds, minutes, hours, years = 0;
  21711. // The following code bubbles up values, see the tests for
  21712. // examples of what that means.
  21713. data.milliseconds = milliseconds % 1000;
  21714. seconds = absRound(milliseconds / 1000);
  21715. data.seconds = seconds % 60;
  21716. minutes = absRound(seconds / 60);
  21717. data.minutes = minutes % 60;
  21718. hours = absRound(minutes / 60);
  21719. data.hours = hours % 24;
  21720. days += absRound(hours / 24);
  21721. // Accurately convert days to years, assume start from year 0.
  21722. years = absRound(daysToYears(days));
  21723. days -= absRound(yearsToDays(years));
  21724. // 30 days to a month
  21725. // TODO (iskren): Use anchor date (like 1st Jan) to compute this.
  21726. months += absRound(days / 30);
  21727. days %= 30;
  21728. // 12 months -> 1 year
  21729. years += absRound(months / 12);
  21730. months %= 12;
  21731. data.days = days;
  21732. data.months = months;
  21733. data.years = years;
  21734. },
  21735. abs : function () {
  21736. this._milliseconds = Math.abs(this._milliseconds);
  21737. this._days = Math.abs(this._days);
  21738. this._months = Math.abs(this._months);
  21739. this._data.milliseconds = Math.abs(this._data.milliseconds);
  21740. this._data.seconds = Math.abs(this._data.seconds);
  21741. this._data.minutes = Math.abs(this._data.minutes);
  21742. this._data.hours = Math.abs(this._data.hours);
  21743. this._data.months = Math.abs(this._data.months);
  21744. this._data.years = Math.abs(this._data.years);
  21745. return this;
  21746. },
  21747. weeks : function () {
  21748. return absRound(this.days() / 7);
  21749. },
  21750. valueOf : function () {
  21751. return this._milliseconds +
  21752. this._days * 864e5 +
  21753. (this._months % 12) * 2592e6 +
  21754. toInt(this._months / 12) * 31536e6;
  21755. },
  21756. humanize : function (withSuffix) {
  21757. var output = relativeTime(this, !withSuffix, this.localeData());
  21758. if (withSuffix) {
  21759. output = this.localeData().pastFuture(+this, output);
  21760. }
  21761. return this.localeData().postformat(output);
  21762. },
  21763. add : function (input, val) {
  21764. // supports only 2.0-style add(1, 's') or add(moment)
  21765. var dur = moment.duration(input, val);
  21766. this._milliseconds += dur._milliseconds;
  21767. this._days += dur._days;
  21768. this._months += dur._months;
  21769. this._bubble();
  21770. return this;
  21771. },
  21772. subtract : function (input, val) {
  21773. var dur = moment.duration(input, val);
  21774. this._milliseconds -= dur._milliseconds;
  21775. this._days -= dur._days;
  21776. this._months -= dur._months;
  21777. this._bubble();
  21778. return this;
  21779. },
  21780. get : function (units) {
  21781. units = normalizeUnits(units);
  21782. return this[units.toLowerCase() + 's']();
  21783. },
  21784. as : function (units) {
  21785. var days, months;
  21786. units = normalizeUnits(units);
  21787. if (units === 'month' || units === 'year') {
  21788. days = this._days + this._milliseconds / 864e5;
  21789. months = this._months + daysToYears(days) * 12;
  21790. return units === 'month' ? months : months / 12;
  21791. } else {
  21792. // handle milliseconds separately because of floating point math errors (issue #1867)
  21793. days = this._days + yearsToDays(this._months / 12);
  21794. switch (units) {
  21795. case 'week': return days / 7 + this._milliseconds / 6048e5;
  21796. case 'day': return days + this._milliseconds / 864e5;
  21797. case 'hour': return days * 24 + this._milliseconds / 36e5;
  21798. case 'minute': return days * 24 * 60 + this._milliseconds / 6e4;
  21799. case 'second': return days * 24 * 60 * 60 + this._milliseconds / 1000;
  21800. // Math.floor prevents floating point math errors here
  21801. case 'millisecond': return Math.floor(days * 24 * 60 * 60 * 1000) + this._milliseconds;
  21802. default: throw new Error('Unknown unit ' + units);
  21803. }
  21804. }
  21805. },
  21806. lang : moment.fn.lang,
  21807. locale : moment.fn.locale,
  21808. toIsoString : deprecate(
  21809. 'toIsoString() is deprecated. Please use toISOString() instead ' +
  21810. '(notice the capitals)',
  21811. function () {
  21812. return this.toISOString();
  21813. }
  21814. ),
  21815. toISOString : function () {
  21816. // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
  21817. var years = Math.abs(this.years()),
  21818. months = Math.abs(this.months()),
  21819. days = Math.abs(this.days()),
  21820. hours = Math.abs(this.hours()),
  21821. minutes = Math.abs(this.minutes()),
  21822. seconds = Math.abs(this.seconds() + this.milliseconds() / 1000);
  21823. if (!this.asSeconds()) {
  21824. // this is the same as C#'s (Noda) and python (isodate)...
  21825. // but not other JS (goog.date)
  21826. return 'P0D';
  21827. }
  21828. return (this.asSeconds() < 0 ? '-' : '') +
  21829. 'P' +
  21830. (years ? years + 'Y' : '') +
  21831. (months ? months + 'M' : '') +
  21832. (days ? days + 'D' : '') +
  21833. ((hours || minutes || seconds) ? 'T' : '') +
  21834. (hours ? hours + 'H' : '') +
  21835. (minutes ? minutes + 'M' : '') +
  21836. (seconds ? seconds + 'S' : '');
  21837. },
  21838. localeData : function () {
  21839. return this._locale;
  21840. }
  21841. });
  21842. moment.duration.fn.toString = moment.duration.fn.toISOString;
  21843. function makeDurationGetter(name) {
  21844. moment.duration.fn[name] = function () {
  21845. return this._data[name];
  21846. };
  21847. }
  21848. for (i in unitMillisecondFactors) {
  21849. if (hasOwnProp(unitMillisecondFactors, i)) {
  21850. makeDurationGetter(i.toLowerCase());
  21851. }
  21852. }
  21853. moment.duration.fn.asMilliseconds = function () {
  21854. return this.as('ms');
  21855. };
  21856. moment.duration.fn.asSeconds = function () {
  21857. return this.as('s');
  21858. };
  21859. moment.duration.fn.asMinutes = function () {
  21860. return this.as('m');
  21861. };
  21862. moment.duration.fn.asHours = function () {
  21863. return this.as('h');
  21864. };
  21865. moment.duration.fn.asDays = function () {
  21866. return this.as('d');
  21867. };
  21868. moment.duration.fn.asWeeks = function () {
  21869. return this.as('weeks');
  21870. };
  21871. moment.duration.fn.asMonths = function () {
  21872. return this.as('M');
  21873. };
  21874. moment.duration.fn.asYears = function () {
  21875. return this.as('y');
  21876. };
  21877. /************************************
  21878. Default Locale
  21879. ************************************/
  21880. // Set default locale, other locale will inherit from English.
  21881. moment.locale('en', {
  21882. ordinal : function (number) {
  21883. var b = number % 10,
  21884. output = (toInt(number % 100 / 10) === 1) ? 'th' :
  21885. (b === 1) ? 'st' :
  21886. (b === 2) ? 'nd' :
  21887. (b === 3) ? 'rd' : 'th';
  21888. return number + output;
  21889. }
  21890. });
  21891. /* EMBED_LOCALES */
  21892. /************************************
  21893. Exposing Moment
  21894. ************************************/
  21895. function makeGlobal(shouldDeprecate) {
  21896. /*global ender:false */
  21897. if (typeof ender !== 'undefined') {
  21898. return;
  21899. }
  21900. oldGlobalMoment = globalScope.moment;
  21901. if (shouldDeprecate) {
  21902. globalScope.moment = deprecate(
  21903. 'Accessing Moment through the global scope is ' +
  21904. 'deprecated, and will be removed in an upcoming ' +
  21905. 'release.',
  21906. moment);
  21907. } else {
  21908. globalScope.moment = moment;
  21909. }
  21910. }
  21911. // CommonJS module is defined
  21912. if (hasModule) {
  21913. module.exports = moment;
  21914. } else if (true) {
  21915. !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
  21916. if (module.config && module.config() && module.config().noGlobal === true) {
  21917. // release the global variable
  21918. globalScope.moment = oldGlobalMoment;
  21919. }
  21920. return moment;
  21921. }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  21922. makeGlobal(true);
  21923. } else {
  21924. makeGlobal();
  21925. }
  21926. }).call(this);
  21927. /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()), __webpack_require__(65)(module)))
  21928. /***/ },
  21929. /* 53 */
  21930. /***/ function(module, exports, __webpack_require__) {
  21931. var __WEBPACK_AMD_DEFINE_RESULT__;/*! Hammer.JS - v1.1.3 - 2014-05-20
  21932. * http://eightmedia.github.io/hammer.js
  21933. *
  21934. * Copyright (c) 2014 Jorik Tangelder <j.tangelder@gmail.com>;
  21935. * Licensed under the MIT license */
  21936. (function(window, undefined) {
  21937. 'use strict';
  21938. /**
  21939. * @main
  21940. * @module hammer
  21941. *
  21942. * @class Hammer
  21943. * @static
  21944. */
  21945. /**
  21946. * Hammer, use this to create instances
  21947. * ````
  21948. * var hammertime = new Hammer(myElement);
  21949. * ````
  21950. *
  21951. * @method Hammer
  21952. * @param {HTMLElement} element
  21953. * @param {Object} [options={}]
  21954. * @return {Hammer.Instance}
  21955. */
  21956. var Hammer = function Hammer(element, options) {
  21957. return new Hammer.Instance(element, options || {});
  21958. };
  21959. /**
  21960. * version, as defined in package.json
  21961. * the value will be set at each build
  21962. * @property VERSION
  21963. * @final
  21964. * @type {String}
  21965. */
  21966. Hammer.VERSION = '1.1.3';
  21967. /**
  21968. * default settings.
  21969. * more settings are defined per gesture at `/gestures`. Each gesture can be disabled/enabled
  21970. * by setting it's name (like `swipe`) to false.
  21971. * You can set the defaults for all instances by changing this object before creating an instance.
  21972. * @example
  21973. * ````
  21974. * Hammer.defaults.drag = false;
  21975. * Hammer.defaults.behavior.touchAction = 'pan-y';
  21976. * delete Hammer.defaults.behavior.userSelect;
  21977. * ````
  21978. * @property defaults
  21979. * @type {Object}
  21980. */
  21981. Hammer.defaults = {
  21982. /**
  21983. * this setting object adds styles and attributes to the element to prevent the browser from doing
  21984. * its native behavior. The css properties are auto prefixed for the browsers when needed.
  21985. * @property defaults.behavior
  21986. * @type {Object}
  21987. */
  21988. behavior: {
  21989. /**
  21990. * Disables text selection to improve the dragging gesture. When the value is `none` it also sets
  21991. * `onselectstart=false` for IE on the element. Mainly for desktop browsers.
  21992. * @property defaults.behavior.userSelect
  21993. * @type {String}
  21994. * @default 'none'
  21995. */
  21996. userSelect: 'none',
  21997. /**
  21998. * Specifies whether and how a given region can be manipulated by the user (for instance, by panning or zooming).
  21999. * Used by Chrome 35> and IE10>. By default this makes the element blocking any touch event.
  22000. * @property defaults.behavior.touchAction
  22001. * @type {String}
  22002. * @default: 'pan-y'
  22003. */
  22004. touchAction: 'pan-y',
  22005. /**
  22006. * Disables the default callout shown when you touch and hold a touch target.
  22007. * On iOS, when you touch and hold a touch target such as a link, Safari displays
  22008. * a callout containing information about the link. This property allows you to disable that callout.
  22009. * @property defaults.behavior.touchCallout
  22010. * @type {String}
  22011. * @default 'none'
  22012. */
  22013. touchCallout: 'none',
  22014. /**
  22015. * Specifies whether zooming is enabled. Used by IE10>
  22016. * @property defaults.behavior.contentZooming
  22017. * @type {String}
  22018. * @default 'none'
  22019. */
  22020. contentZooming: 'none',
  22021. /**
  22022. * Specifies that an entire element should be draggable instead of its contents.
  22023. * Mainly for desktop browsers.
  22024. * @property defaults.behavior.userDrag
  22025. * @type {String}
  22026. * @default 'none'
  22027. */
  22028. userDrag: 'none',
  22029. /**
  22030. * Overrides the highlight color shown when the user taps a link or a JavaScript
  22031. * clickable element in Safari on iPhone. This property obeys the alpha value, if specified.
  22032. *
  22033. * If you don't specify an alpha value, Safari on iPhone applies a default alpha value
  22034. * to the color. To disable tap highlighting, set the alpha value to 0 (invisible).
  22035. * If you set the alpha value to 1.0 (opaque), the element is not visible when tapped.
  22036. * @property defaults.behavior.tapHighlightColor
  22037. * @type {String}
  22038. * @default 'rgba(0,0,0,0)'
  22039. */
  22040. tapHighlightColor: 'rgba(0,0,0,0)'
  22041. }
  22042. };
  22043. /**
  22044. * hammer document where the base events are added at
  22045. * @property DOCUMENT
  22046. * @type {HTMLElement}
  22047. * @default window.document
  22048. */
  22049. Hammer.DOCUMENT = document;
  22050. /**
  22051. * detect support for pointer events
  22052. * @property HAS_POINTEREVENTS
  22053. * @type {Boolean}
  22054. */
  22055. Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;
  22056. /**
  22057. * detect support for touch events
  22058. * @property HAS_TOUCHEVENTS
  22059. * @type {Boolean}
  22060. */
  22061. Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);
  22062. /**
  22063. * detect mobile browsers
  22064. * @property IS_MOBILE
  22065. * @type {Boolean}
  22066. */
  22067. Hammer.IS_MOBILE = /mobile|tablet|ip(ad|hone|od)|android|silk/i.test(navigator.userAgent);
  22068. /**
  22069. * detect if we want to support mouseevents at all
  22070. * @property NO_MOUSEEVENTS
  22071. * @type {Boolean}
  22072. */
  22073. Hammer.NO_MOUSEEVENTS = (Hammer.HAS_TOUCHEVENTS && Hammer.IS_MOBILE) || Hammer.HAS_POINTEREVENTS;
  22074. /**
  22075. * interval in which Hammer recalculates current velocity/direction/angle in ms
  22076. * @property CALCULATE_INTERVAL
  22077. * @type {Number}
  22078. * @default 25
  22079. */
  22080. Hammer.CALCULATE_INTERVAL = 25;
  22081. /**
  22082. * eventtypes per touchevent (start, move, end) are filled by `Event.determineEventTypes` on `setup`
  22083. * the object contains the DOM event names per type (`EVENT_START`, `EVENT_MOVE`, `EVENT_END`)
  22084. * @property EVENT_TYPES
  22085. * @private
  22086. * @writeOnce
  22087. * @type {Object}
  22088. */
  22089. var EVENT_TYPES = {};
  22090. /**
  22091. * direction strings, for safe comparisons
  22092. * @property DIRECTION_DOWN|LEFT|UP|RIGHT
  22093. * @final
  22094. * @type {String}
  22095. * @default 'down' 'left' 'up' 'right'
  22096. */
  22097. var DIRECTION_DOWN = Hammer.DIRECTION_DOWN = 'down';
  22098. var DIRECTION_LEFT = Hammer.DIRECTION_LEFT = 'left';
  22099. var DIRECTION_UP = Hammer.DIRECTION_UP = 'up';
  22100. var DIRECTION_RIGHT = Hammer.DIRECTION_RIGHT = 'right';
  22101. /**
  22102. * pointertype strings, for safe comparisons
  22103. * @property POINTER_MOUSE|TOUCH|PEN
  22104. * @final
  22105. * @type {String}
  22106. * @default 'mouse' 'touch' 'pen'
  22107. */
  22108. var POINTER_MOUSE = Hammer.POINTER_MOUSE = 'mouse';
  22109. var POINTER_TOUCH = Hammer.POINTER_TOUCH = 'touch';
  22110. var POINTER_PEN = Hammer.POINTER_PEN = 'pen';
  22111. /**
  22112. * eventtypes
  22113. * @property EVENT_START|MOVE|END|RELEASE|TOUCH
  22114. * @final
  22115. * @type {String}
  22116. * @default 'start' 'change' 'move' 'end' 'release' 'touch'
  22117. */
  22118. var EVENT_START = Hammer.EVENT_START = 'start';
  22119. var EVENT_MOVE = Hammer.EVENT_MOVE = 'move';
  22120. var EVENT_END = Hammer.EVENT_END = 'end';
  22121. var EVENT_RELEASE = Hammer.EVENT_RELEASE = 'release';
  22122. var EVENT_TOUCH = Hammer.EVENT_TOUCH = 'touch';
  22123. /**
  22124. * if the window events are set...
  22125. * @property READY
  22126. * @writeOnce
  22127. * @type {Boolean}
  22128. * @default false
  22129. */
  22130. Hammer.READY = false;
  22131. /**
  22132. * plugins namespace
  22133. * @property plugins
  22134. * @type {Object}
  22135. */
  22136. Hammer.plugins = Hammer.plugins || {};
  22137. /**
  22138. * gestures namespace
  22139. * see `/gestures` for the definitions
  22140. * @property gestures
  22141. * @type {Object}
  22142. */
  22143. Hammer.gestures = Hammer.gestures || {};
  22144. /**
  22145. * setup events to detect gestures on the document
  22146. * this function is called when creating an new instance
  22147. * @private
  22148. */
  22149. function setup() {
  22150. if(Hammer.READY) {
  22151. return;
  22152. }
  22153. // find what eventtypes we add listeners to
  22154. Event.determineEventTypes();
  22155. // Register all gestures inside Hammer.gestures
  22156. Utils.each(Hammer.gestures, function(gesture) {
  22157. Detection.register(gesture);
  22158. });
  22159. // Add touch events on the document
  22160. Event.onTouch(Hammer.DOCUMENT, EVENT_MOVE, Detection.detect);
  22161. Event.onTouch(Hammer.DOCUMENT, EVENT_END, Detection.detect);
  22162. // Hammer is ready...!
  22163. Hammer.READY = true;
  22164. }
  22165. /**
  22166. * @module hammer
  22167. *
  22168. * @class Utils
  22169. * @static
  22170. */
  22171. var Utils = Hammer.utils = {
  22172. /**
  22173. * extend method, could also be used for cloning when `dest` is an empty object.
  22174. * changes the dest object
  22175. * @method extend
  22176. * @param {Object} dest
  22177. * @param {Object} src
  22178. * @param {Boolean} [merge=false] do a merge
  22179. * @return {Object} dest
  22180. */
  22181. extend: function extend(dest, src, merge) {
  22182. for(var key in src) {
  22183. if(!src.hasOwnProperty(key) || (dest[key] !== undefined && merge)) {
  22184. continue;
  22185. }
  22186. dest[key] = src[key];
  22187. }
  22188. return dest;
  22189. },
  22190. /**
  22191. * simple addEventListener wrapper
  22192. * @method on
  22193. * @param {HTMLElement} element
  22194. * @param {String} type
  22195. * @param {Function} handler
  22196. */
  22197. on: function on(element, type, handler) {
  22198. element.addEventListener(type, handler, false);
  22199. },
  22200. /**
  22201. * simple removeEventListener wrapper
  22202. * @method off
  22203. * @param {HTMLElement} element
  22204. * @param {String} type
  22205. * @param {Function} handler
  22206. */
  22207. off: function off(element, type, handler) {
  22208. element.removeEventListener(type, handler, false);
  22209. },
  22210. /**
  22211. * forEach over arrays and objects
  22212. * @method each
  22213. * @param {Object|Array} obj
  22214. * @param {Function} iterator
  22215. * @param {any} iterator.item
  22216. * @param {Number} iterator.index
  22217. * @param {Object|Array} iterator.obj the source object
  22218. * @param {Object} context value to use as `this` in the iterator
  22219. */
  22220. each: function each(obj, iterator, context) {
  22221. var i, len;
  22222. // native forEach on arrays
  22223. if('forEach' in obj) {
  22224. obj.forEach(iterator, context);
  22225. // arrays
  22226. } else if(obj.length !== undefined) {
  22227. for(i = 0, len = obj.length; i < len; i++) {
  22228. if(iterator.call(context, obj[i], i, obj) === false) {
  22229. return;
  22230. }
  22231. }
  22232. // objects
  22233. } else {
  22234. for(i in obj) {
  22235. if(obj.hasOwnProperty(i) &&
  22236. iterator.call(context, obj[i], i, obj) === false) {
  22237. return;
  22238. }
  22239. }
  22240. }
  22241. },
  22242. /**
  22243. * find if a string contains the string using indexOf
  22244. * @method inStr
  22245. * @param {String} src
  22246. * @param {String} find
  22247. * @return {Boolean} found
  22248. */
  22249. inStr: function inStr(src, find) {
  22250. return src.indexOf(find) > -1;
  22251. },
  22252. /**
  22253. * find if a array contains the object using indexOf or a simple polyfill
  22254. * @method inArray
  22255. * @param {String} src
  22256. * @param {String} find
  22257. * @return {Boolean|Number} false when not found, or the index
  22258. */
  22259. inArray: function inArray(src, find) {
  22260. if(src.indexOf) {
  22261. var index = src.indexOf(find);
  22262. return (index === -1) ? false : index;
  22263. } else {
  22264. for(var i = 0, len = src.length; i < len; i++) {
  22265. if(src[i] === find) {
  22266. return i;
  22267. }
  22268. }
  22269. return false;
  22270. }
  22271. },
  22272. /**
  22273. * convert an array-like object (`arguments`, `touchlist`) to an array
  22274. * @method toArray
  22275. * @param {Object} obj
  22276. * @return {Array}
  22277. */
  22278. toArray: function toArray(obj) {
  22279. return Array.prototype.slice.call(obj, 0);
  22280. },
  22281. /**
  22282. * find if a node is in the given parent
  22283. * @method hasParent
  22284. * @param {HTMLElement} node
  22285. * @param {HTMLElement} parent
  22286. * @return {Boolean} found
  22287. */
  22288. hasParent: function hasParent(node, parent) {
  22289. while(node) {
  22290. if(node == parent) {
  22291. return true;
  22292. }
  22293. node = node.parentNode;
  22294. }
  22295. return false;
  22296. },
  22297. /**
  22298. * get the center of all the touches
  22299. * @method getCenter
  22300. * @param {Array} touches
  22301. * @return {Object} center contains `pageX`, `pageY`, `clientX` and `clientY` properties
  22302. */
  22303. getCenter: function getCenter(touches) {
  22304. var pageX = [],
  22305. pageY = [],
  22306. clientX = [],
  22307. clientY = [],
  22308. min = Math.min,
  22309. max = Math.max;
  22310. // no need to loop when only one touch
  22311. if(touches.length === 1) {
  22312. return {
  22313. pageX: touches[0].pageX,
  22314. pageY: touches[0].pageY,
  22315. clientX: touches[0].clientX,
  22316. clientY: touches[0].clientY
  22317. };
  22318. }
  22319. Utils.each(touches, function(touch) {
  22320. pageX.push(touch.pageX);
  22321. pageY.push(touch.pageY);
  22322. clientX.push(touch.clientX);
  22323. clientY.push(touch.clientY);
  22324. });
  22325. return {
  22326. pageX: (min.apply(Math, pageX) + max.apply(Math, pageX)) / 2,
  22327. pageY: (min.apply(Math, pageY) + max.apply(Math, pageY)) / 2,
  22328. clientX: (min.apply(Math, clientX) + max.apply(Math, clientX)) / 2,
  22329. clientY: (min.apply(Math, clientY) + max.apply(Math, clientY)) / 2
  22330. };
  22331. },
  22332. /**
  22333. * calculate the velocity between two points. unit is in px per ms.
  22334. * @method getVelocity
  22335. * @param {Number} deltaTime
  22336. * @param {Number} deltaX
  22337. * @param {Number} deltaY
  22338. * @return {Object} velocity `x` and `y`
  22339. */
  22340. getVelocity: function getVelocity(deltaTime, deltaX, deltaY) {
  22341. return {
  22342. x: Math.abs(deltaX / deltaTime) || 0,
  22343. y: Math.abs(deltaY / deltaTime) || 0
  22344. };
  22345. },
  22346. /**
  22347. * calculate the angle between two coordinates
  22348. * @method getAngle
  22349. * @param {Touch} touch1
  22350. * @param {Touch} touch2
  22351. * @return {Number} angle
  22352. */
  22353. getAngle: function getAngle(touch1, touch2) {
  22354. var x = touch2.clientX - touch1.clientX,
  22355. y = touch2.clientY - touch1.clientY;
  22356. return Math.atan2(y, x) * 180 / Math.PI;
  22357. },
  22358. /**
  22359. * do a small comparision to get the direction between two touches.
  22360. * @method getDirection
  22361. * @param {Touch} touch1
  22362. * @param {Touch} touch2
  22363. * @return {String} direction matches `DIRECTION_LEFT|RIGHT|UP|DOWN`
  22364. */
  22365. getDirection: function getDirection(touch1, touch2) {
  22366. var x = Math.abs(touch1.clientX - touch2.clientX),
  22367. y = Math.abs(touch1.clientY - touch2.clientY);
  22368. if(x >= y) {
  22369. return touch1.clientX - touch2.clientX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
  22370. }
  22371. return touch1.clientY - touch2.clientY > 0 ? DIRECTION_UP : DIRECTION_DOWN;
  22372. },
  22373. /**
  22374. * calculate the distance between two touches
  22375. * @method getDistance
  22376. * @param {Touch}touch1
  22377. * @param {Touch} touch2
  22378. * @return {Number} distance
  22379. */
  22380. getDistance: function getDistance(touch1, touch2) {
  22381. var x = touch2.clientX - touch1.clientX,
  22382. y = touch2.clientY - touch1.clientY;
  22383. return Math.sqrt((x * x) + (y * y));
  22384. },
  22385. /**
  22386. * calculate the scale factor between two touchLists
  22387. * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
  22388. * @method getScale
  22389. * @param {Array} start array of touches
  22390. * @param {Array} end array of touches
  22391. * @return {Number} scale
  22392. */
  22393. getScale: function getScale(start, end) {
  22394. // need two fingers...
  22395. if(start.length >= 2 && end.length >= 2) {
  22396. return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]);
  22397. }
  22398. return 1;
  22399. },
  22400. /**
  22401. * calculate the rotation degrees between two touchLists
  22402. * @method getRotation
  22403. * @param {Array} start array of touches
  22404. * @param {Array} end array of touches
  22405. * @return {Number} rotation
  22406. */
  22407. getRotation: function getRotation(start, end) {
  22408. // need two fingers
  22409. if(start.length >= 2 && end.length >= 2) {
  22410. return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]);
  22411. }
  22412. return 0;
  22413. },
  22414. /**
  22415. * find out if the direction is vertical *
  22416. * @method isVertical
  22417. * @param {String} direction matches `DIRECTION_UP|DOWN`
  22418. * @return {Boolean} is_vertical
  22419. */
  22420. isVertical: function isVertical(direction) {
  22421. return direction == DIRECTION_UP || direction == DIRECTION_DOWN;
  22422. },
  22423. /**
  22424. * set css properties with their prefixes
  22425. * @param {HTMLElement} element
  22426. * @param {String} prop
  22427. * @param {String} value
  22428. * @param {Boolean} [toggle=true]
  22429. * @return {Boolean}
  22430. */
  22431. setPrefixedCss: function setPrefixedCss(element, prop, value, toggle) {
  22432. var prefixes = ['', 'Webkit', 'Moz', 'O', 'ms'];
  22433. prop = Utils.toCamelCase(prop);
  22434. for(var i = 0; i < prefixes.length; i++) {
  22435. var p = prop;
  22436. // prefixes
  22437. if(prefixes[i]) {
  22438. p = prefixes[i] + p.slice(0, 1).toUpperCase() + p.slice(1);
  22439. }
  22440. // test the style
  22441. if(p in element.style) {
  22442. element.style[p] = (toggle == null || toggle) && value || '';
  22443. break;
  22444. }
  22445. }
  22446. },
  22447. /**
  22448. * toggle browser default behavior by setting css properties.
  22449. * `userSelect='none'` also sets `element.onselectstart` to false
  22450. * `userDrag='none'` also sets `element.ondragstart` to false
  22451. *
  22452. * @method toggleBehavior
  22453. * @param {HtmlElement} element
  22454. * @param {Object} props
  22455. * @param {Boolean} [toggle=true]
  22456. */
  22457. toggleBehavior: function toggleBehavior(element, props, toggle) {
  22458. if(!props || !element || !element.style) {
  22459. return;
  22460. }
  22461. // set the css properties
  22462. Utils.each(props, function(value, prop) {
  22463. Utils.setPrefixedCss(element, prop, value, toggle);
  22464. });
  22465. var falseFn = toggle && function() {
  22466. return false;
  22467. };
  22468. // also the disable onselectstart
  22469. if(props.userSelect == 'none') {
  22470. element.onselectstart = falseFn;
  22471. }
  22472. // and disable ondragstart
  22473. if(props.userDrag == 'none') {
  22474. element.ondragstart = falseFn;
  22475. }
  22476. },
  22477. /**
  22478. * convert a string with underscores to camelCase
  22479. * so prevent_default becomes preventDefault
  22480. * @param {String} str
  22481. * @return {String} camelCaseStr
  22482. */
  22483. toCamelCase: function toCamelCase(str) {
  22484. return str.replace(/[_-]([a-z])/g, function(s) {
  22485. return s[1].toUpperCase();
  22486. });
  22487. }
  22488. };
  22489. /**
  22490. * @module hammer
  22491. */
  22492. /**
  22493. * @class Event
  22494. * @static
  22495. */
  22496. var Event = Hammer.event = {
  22497. /**
  22498. * when touch events have been fired, this is true
  22499. * this is used to stop mouse events
  22500. * @property prevent_mouseevents
  22501. * @private
  22502. * @type {Boolean}
  22503. */
  22504. preventMouseEvents: false,
  22505. /**
  22506. * if EVENT_START has been fired
  22507. * @property started
  22508. * @private
  22509. * @type {Boolean}
  22510. */
  22511. started: false,
  22512. /**
  22513. * when the mouse is hold down, this is true
  22514. * @property should_detect
  22515. * @private
  22516. * @type {Boolean}
  22517. */
  22518. shouldDetect: false,
  22519. /**
  22520. * simple event binder with a hook and support for multiple types
  22521. * @method on
  22522. * @param {HTMLElement} element
  22523. * @param {String} type
  22524. * @param {Function} handler
  22525. * @param {Function} [hook]
  22526. * @param {Object} hook.type
  22527. */
  22528. on: function on(element, type, handler, hook) {
  22529. var types = type.split(' ');
  22530. Utils.each(types, function(type) {
  22531. Utils.on(element, type, handler);
  22532. hook && hook(type);
  22533. });
  22534. },
  22535. /**
  22536. * simple event unbinder with a hook and support for multiple types
  22537. * @method off
  22538. * @param {HTMLElement} element
  22539. * @param {String} type
  22540. * @param {Function} handler
  22541. * @param {Function} [hook]
  22542. * @param {Object} hook.type
  22543. */
  22544. off: function off(element, type, handler, hook) {
  22545. var types = type.split(' ');
  22546. Utils.each(types, function(type) {
  22547. Utils.off(element, type, handler);
  22548. hook && hook(type);
  22549. });
  22550. },
  22551. /**
  22552. * the core touch event handler.
  22553. * this finds out if we should to detect gestures
  22554. * @method onTouch
  22555. * @param {HTMLElement} element
  22556. * @param {String} eventType matches `EVENT_START|MOVE|END`
  22557. * @param {Function} handler
  22558. * @return onTouchHandler {Function} the core event handler
  22559. */
  22560. onTouch: function onTouch(element, eventType, handler) {
  22561. var self = this;
  22562. var onTouchHandler = function onTouchHandler(ev) {
  22563. var srcType = ev.type.toLowerCase(),
  22564. isPointer = Hammer.HAS_POINTEREVENTS,
  22565. isMouse = Utils.inStr(srcType, 'mouse'),
  22566. triggerType;
  22567. // if we are in a mouseevent, but there has been a touchevent triggered in this session
  22568. // we want to do nothing. simply break out of the event.
  22569. if(isMouse && self.preventMouseEvents) {
  22570. return;
  22571. // mousebutton must be down
  22572. } else if(isMouse && eventType == EVENT_START && ev.button === 0) {
  22573. self.preventMouseEvents = false;
  22574. self.shouldDetect = true;
  22575. } else if(isPointer && eventType == EVENT_START) {
  22576. self.shouldDetect = (ev.buttons === 1 || PointerEvent.matchType(POINTER_TOUCH, ev));
  22577. // just a valid start event, but no mouse
  22578. } else if(!isMouse && eventType == EVENT_START) {
  22579. self.preventMouseEvents = true;
  22580. self.shouldDetect = true;
  22581. }
  22582. // update the pointer event before entering the detection
  22583. if(isPointer && eventType != EVENT_END) {
  22584. PointerEvent.updatePointer(eventType, ev);
  22585. }
  22586. // we are in a touch/down state, so allowed detection of gestures
  22587. if(self.shouldDetect) {
  22588. triggerType = self.doDetect.call(self, ev, eventType, element, handler);
  22589. }
  22590. // ...and we are done with the detection
  22591. // so reset everything to start each detection totally fresh
  22592. if(triggerType == EVENT_END) {
  22593. self.preventMouseEvents = false;
  22594. self.shouldDetect = false;
  22595. PointerEvent.reset();
  22596. // update the pointerevent object after the detection
  22597. }
  22598. if(isPointer && eventType == EVENT_END) {
  22599. PointerEvent.updatePointer(eventType, ev);
  22600. }
  22601. };
  22602. this.on(element, EVENT_TYPES[eventType], onTouchHandler);
  22603. return onTouchHandler;
  22604. },
  22605. /**
  22606. * the core detection method
  22607. * this finds out what hammer-touch-events to trigger
  22608. * @method doDetect
  22609. * @param {Object} ev
  22610. * @param {String} eventType matches `EVENT_START|MOVE|END`
  22611. * @param {HTMLElement} element
  22612. * @param {Function} handler
  22613. * @return {String} triggerType matches `EVENT_START|MOVE|END`
  22614. */
  22615. doDetect: function doDetect(ev, eventType, element, handler) {
  22616. var touchList = this.getTouchList(ev, eventType);
  22617. var touchListLength = touchList.length;
  22618. var triggerType = eventType;
  22619. var triggerChange = touchList.trigger; // used by fakeMultitouch plugin
  22620. var changedLength = touchListLength;
  22621. // at each touchstart-like event we want also want to trigger a TOUCH event...
  22622. if(eventType == EVENT_START) {
  22623. triggerChange = EVENT_TOUCH;
  22624. // ...the same for a touchend-like event
  22625. } else if(eventType == EVENT_END) {
  22626. triggerChange = EVENT_RELEASE;
  22627. // keep track of how many touches have been removed
  22628. changedLength = touchList.length - ((ev.changedTouches) ? ev.changedTouches.length : 1);
  22629. }
  22630. // after there are still touches on the screen,
  22631. // we just want to trigger a MOVE event. so change the START or END to a MOVE
  22632. // but only after detection has been started, the first time we actualy want a START
  22633. if(changedLength > 0 && this.started) {
  22634. triggerType = EVENT_MOVE;
  22635. }
  22636. // detection has been started, we keep track of this, see above
  22637. this.started = true;
  22638. // generate some event data, some basic information
  22639. var evData = this.collectEventData(element, triggerType, touchList, ev);
  22640. // trigger the triggerType event before the change (TOUCH, RELEASE) events
  22641. // but the END event should be at last
  22642. if(eventType != EVENT_END) {
  22643. handler.call(Detection, evData);
  22644. }
  22645. // trigger a change (TOUCH, RELEASE) event, this means the length of the touches changed
  22646. if(triggerChange) {
  22647. evData.changedLength = changedLength;
  22648. evData.eventType = triggerChange;
  22649. handler.call(Detection, evData);
  22650. evData.eventType = triggerType;
  22651. delete evData.changedLength;
  22652. }
  22653. // trigger the END event
  22654. if(triggerType == EVENT_END) {
  22655. handler.call(Detection, evData);
  22656. // ...and we are done with the detection
  22657. // so reset everything to start each detection totally fresh
  22658. this.started = false;
  22659. }
  22660. return triggerType;
  22661. },
  22662. /**
  22663. * we have different events for each device/browser
  22664. * determine what we need and set them in the EVENT_TYPES constant
  22665. * the `onTouch` method is bind to these properties.
  22666. * @method determineEventTypes
  22667. * @return {Object} events
  22668. */
  22669. determineEventTypes: function determineEventTypes() {
  22670. var types;
  22671. if(Hammer.HAS_POINTEREVENTS) {
  22672. if(window.PointerEvent) {
  22673. types = [
  22674. 'pointerdown',
  22675. 'pointermove',
  22676. 'pointerup pointercancel lostpointercapture'
  22677. ];
  22678. } else {
  22679. types = [
  22680. 'MSPointerDown',
  22681. 'MSPointerMove',
  22682. 'MSPointerUp MSPointerCancel MSLostPointerCapture'
  22683. ];
  22684. }
  22685. } else if(Hammer.NO_MOUSEEVENTS) {
  22686. types = [
  22687. 'touchstart',
  22688. 'touchmove',
  22689. 'touchend touchcancel'
  22690. ];
  22691. } else {
  22692. types = [
  22693. 'touchstart mousedown',
  22694. 'touchmove mousemove',
  22695. 'touchend touchcancel mouseup'
  22696. ];
  22697. }
  22698. EVENT_TYPES[EVENT_START] = types[0];
  22699. EVENT_TYPES[EVENT_MOVE] = types[1];
  22700. EVENT_TYPES[EVENT_END] = types[2];
  22701. return EVENT_TYPES;
  22702. },
  22703. /**
  22704. * create touchList depending on the event
  22705. * @method getTouchList
  22706. * @param {Object} ev
  22707. * @param {String} eventType
  22708. * @return {Array} touches
  22709. */
  22710. getTouchList: function getTouchList(ev, eventType) {
  22711. // get the fake pointerEvent touchlist
  22712. if(Hammer.HAS_POINTEREVENTS) {
  22713. return PointerEvent.getTouchList();
  22714. }
  22715. // get the touchlist
  22716. if(ev.touches) {
  22717. if(eventType == EVENT_MOVE) {
  22718. return ev.touches;
  22719. }
  22720. var identifiers = [];
  22721. var concat = [].concat(Utils.toArray(ev.touches), Utils.toArray(ev.changedTouches));
  22722. var touchList = [];
  22723. Utils.each(concat, function(touch) {
  22724. if(Utils.inArray(identifiers, touch.identifier) === false) {
  22725. touchList.push(touch);
  22726. }
  22727. identifiers.push(touch.identifier);
  22728. });
  22729. return touchList;
  22730. }
  22731. // make fake touchList from mouse position
  22732. ev.identifier = 1;
  22733. return [ev];
  22734. },
  22735. /**
  22736. * collect basic event data
  22737. * @method collectEventData
  22738. * @param {HTMLElement} element
  22739. * @param {String} eventType matches `EVENT_START|MOVE|END`
  22740. * @param {Array} touches
  22741. * @param {Object} ev
  22742. * @return {Object} ev
  22743. */
  22744. collectEventData: function collectEventData(element, eventType, touches, ev) {
  22745. // find out pointerType
  22746. var pointerType = POINTER_TOUCH;
  22747. if(Utils.inStr(ev.type, 'mouse') || PointerEvent.matchType(POINTER_MOUSE, ev)) {
  22748. pointerType = POINTER_MOUSE;
  22749. } else if(PointerEvent.matchType(POINTER_PEN, ev)) {
  22750. pointerType = POINTER_PEN;
  22751. }
  22752. return {
  22753. center: Utils.getCenter(touches),
  22754. timeStamp: Date.now(),
  22755. target: ev.target,
  22756. touches: touches,
  22757. eventType: eventType,
  22758. pointerType: pointerType,
  22759. srcEvent: ev,
  22760. /**
  22761. * prevent the browser default actions
  22762. * mostly used to disable scrolling of the browser
  22763. */
  22764. preventDefault: function() {
  22765. var srcEvent = this.srcEvent;
  22766. srcEvent.preventManipulation && srcEvent.preventManipulation();
  22767. srcEvent.preventDefault && srcEvent.preventDefault();
  22768. },
  22769. /**
  22770. * stop bubbling the event up to its parents
  22771. */
  22772. stopPropagation: function() {
  22773. this.srcEvent.stopPropagation();
  22774. },
  22775. /**
  22776. * immediately stop gesture detection
  22777. * might be useful after a swipe was detected
  22778. * @return {*}
  22779. */
  22780. stopDetect: function() {
  22781. return Detection.stopDetect();
  22782. }
  22783. };
  22784. }
  22785. };
  22786. /**
  22787. * @module hammer
  22788. *
  22789. * @class PointerEvent
  22790. * @static
  22791. */
  22792. var PointerEvent = Hammer.PointerEvent = {
  22793. /**
  22794. * holds all pointers, by `identifier`
  22795. * @property pointers
  22796. * @type {Object}
  22797. */
  22798. pointers: {},
  22799. /**
  22800. * get the pointers as an array
  22801. * @method getTouchList
  22802. * @return {Array} touchlist
  22803. */
  22804. getTouchList: function getTouchList() {
  22805. var touchlist = [];
  22806. // we can use forEach since pointerEvents only is in IE10
  22807. Utils.each(this.pointers, function(pointer) {
  22808. touchlist.push(pointer);
  22809. });
  22810. return touchlist;
  22811. },
  22812. /**
  22813. * update the position of a pointer
  22814. * @method updatePointer
  22815. * @param {String} eventType matches `EVENT_START|MOVE|END`
  22816. * @param {Object} pointerEvent
  22817. */
  22818. updatePointer: function updatePointer(eventType, pointerEvent) {
  22819. if(eventType == EVENT_END || (eventType != EVENT_END && pointerEvent.buttons !== 1)) {
  22820. delete this.pointers[pointerEvent.pointerId];
  22821. } else {
  22822. pointerEvent.identifier = pointerEvent.pointerId;
  22823. this.pointers[pointerEvent.pointerId] = pointerEvent;
  22824. }
  22825. },
  22826. /**
  22827. * check if ev matches pointertype
  22828. * @method matchType
  22829. * @param {String} pointerType matches `POINTER_MOUSE|TOUCH|PEN`
  22830. * @param {PointerEvent} ev
  22831. */
  22832. matchType: function matchType(pointerType, ev) {
  22833. if(!ev.pointerType) {
  22834. return false;
  22835. }
  22836. var pt = ev.pointerType,
  22837. types = {};
  22838. types[POINTER_MOUSE] = (pt === (ev.MSPOINTER_TYPE_MOUSE || POINTER_MOUSE));
  22839. types[POINTER_TOUCH] = (pt === (ev.MSPOINTER_TYPE_TOUCH || POINTER_TOUCH));
  22840. types[POINTER_PEN] = (pt === (ev.MSPOINTER_TYPE_PEN || POINTER_PEN));
  22841. return types[pointerType];
  22842. },
  22843. /**
  22844. * reset the stored pointers
  22845. * @method reset
  22846. */
  22847. reset: function resetList() {
  22848. this.pointers = {};
  22849. }
  22850. };
  22851. /**
  22852. * @module hammer
  22853. *
  22854. * @class Detection
  22855. * @static
  22856. */
  22857. var Detection = Hammer.detection = {
  22858. // contains all registred Hammer.gestures in the correct order
  22859. gestures: [],
  22860. // data of the current Hammer.gesture detection session
  22861. current: null,
  22862. // the previous Hammer.gesture session data
  22863. // is a full clone of the previous gesture.current object
  22864. previous: null,
  22865. // when this becomes true, no gestures are fired
  22866. stopped: false,
  22867. /**
  22868. * start Hammer.gesture detection
  22869. * @method startDetect
  22870. * @param {Hammer.Instance} inst
  22871. * @param {Object} eventData
  22872. */
  22873. startDetect: function startDetect(inst, eventData) {
  22874. // already busy with a Hammer.gesture detection on an element
  22875. if(this.current) {
  22876. return;
  22877. }
  22878. this.stopped = false;
  22879. // holds current session
  22880. this.current = {
  22881. inst: inst, // reference to HammerInstance we're working for
  22882. startEvent: Utils.extend({}, eventData), // start eventData for distances, timing etc
  22883. lastEvent: false, // last eventData
  22884. lastCalcEvent: false, // last eventData for calculations.
  22885. futureCalcEvent: false, // last eventData for calculations.
  22886. lastCalcData: {}, // last lastCalcData
  22887. name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
  22888. };
  22889. this.detect(eventData);
  22890. },
  22891. /**
  22892. * Hammer.gesture detection
  22893. * @method detect
  22894. * @param {Object} eventData
  22895. * @return {any}
  22896. */
  22897. detect: function detect(eventData) {
  22898. if(!this.current || this.stopped) {
  22899. return;
  22900. }
  22901. // extend event data with calculations about scale, distance etc
  22902. eventData = this.extendEventData(eventData);
  22903. // hammer instance and instance options
  22904. var inst = this.current.inst,
  22905. instOptions = inst.options;
  22906. // call Hammer.gesture handlers
  22907. Utils.each(this.gestures, function triggerGesture(gesture) {
  22908. // only when the instance options have enabled this gesture
  22909. if(!this.stopped && inst.enabled && instOptions[gesture.name]) {
  22910. gesture.handler.call(gesture, eventData, inst);
  22911. }
  22912. }, this);
  22913. // store as previous event event
  22914. if(this.current) {
  22915. this.current.lastEvent = eventData;
  22916. }
  22917. if(eventData.eventType == EVENT_END) {
  22918. this.stopDetect();
  22919. }
  22920. return eventData;
  22921. },
  22922. /**
  22923. * clear the Hammer.gesture vars
  22924. * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
  22925. * to stop other Hammer.gestures from being fired
  22926. * @method stopDetect
  22927. */
  22928. stopDetect: function stopDetect() {
  22929. // clone current data to the store as the previous gesture
  22930. // used for the double tap gesture, since this is an other gesture detect session
  22931. this.previous = Utils.extend({}, this.current);
  22932. // reset the current
  22933. this.current = null;
  22934. this.stopped = true;
  22935. },
  22936. /**
  22937. * calculate velocity, angle and direction
  22938. * @method getVelocityData
  22939. * @param {Object} ev
  22940. * @param {Object} center
  22941. * @param {Number} deltaTime
  22942. * @param {Number} deltaX
  22943. * @param {Number} deltaY
  22944. */
  22945. getCalculatedData: function getCalculatedData(ev, center, deltaTime, deltaX, deltaY) {
  22946. var cur = this.current,
  22947. recalc = false,
  22948. calcEv = cur.lastCalcEvent,
  22949. calcData = cur.lastCalcData;
  22950. if(calcEv && ev.timeStamp - calcEv.timeStamp > Hammer.CALCULATE_INTERVAL) {
  22951. center = calcEv.center;
  22952. deltaTime = ev.timeStamp - calcEv.timeStamp;
  22953. deltaX = ev.center.clientX - calcEv.center.clientX;
  22954. deltaY = ev.center.clientY - calcEv.center.clientY;
  22955. recalc = true;
  22956. }
  22957. if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
  22958. cur.futureCalcEvent = ev;
  22959. }
  22960. if(!cur.lastCalcEvent || recalc) {
  22961. calcData.velocity = Utils.getVelocity(deltaTime, deltaX, deltaY);
  22962. calcData.angle = Utils.getAngle(center, ev.center);
  22963. calcData.direction = Utils.getDirection(center, ev.center);
  22964. cur.lastCalcEvent = cur.futureCalcEvent || ev;
  22965. cur.futureCalcEvent = ev;
  22966. }
  22967. ev.velocityX = calcData.velocity.x;
  22968. ev.velocityY = calcData.velocity.y;
  22969. ev.interimAngle = calcData.angle;
  22970. ev.interimDirection = calcData.direction;
  22971. },
  22972. /**
  22973. * extend eventData for Hammer.gestures
  22974. * @method extendEventData
  22975. * @param {Object} ev
  22976. * @return {Object} ev
  22977. */
  22978. extendEventData: function extendEventData(ev) {
  22979. var cur = this.current,
  22980. startEv = cur.startEvent,
  22981. lastEv = cur.lastEvent || startEv;
  22982. // update the start touchlist to calculate the scale/rotation
  22983. if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
  22984. startEv.touches = [];
  22985. Utils.each(ev.touches, function(touch) {
  22986. startEv.touches.push({
  22987. clientX: touch.clientX,
  22988. clientY: touch.clientY
  22989. });
  22990. });
  22991. }
  22992. var deltaTime = ev.timeStamp - startEv.timeStamp,
  22993. deltaX = ev.center.clientX - startEv.center.clientX,
  22994. deltaY = ev.center.clientY - startEv.center.clientY;
  22995. this.getCalculatedData(ev, lastEv.center, deltaTime, deltaX, deltaY);
  22996. Utils.extend(ev, {
  22997. startEvent: startEv,
  22998. deltaTime: deltaTime,
  22999. deltaX: deltaX,
  23000. deltaY: deltaY,
  23001. distance: Utils.getDistance(startEv.center, ev.center),
  23002. angle: Utils.getAngle(startEv.center, ev.center),
  23003. direction: Utils.getDirection(startEv.center, ev.center),
  23004. scale: Utils.getScale(startEv.touches, ev.touches),
  23005. rotation: Utils.getRotation(startEv.touches, ev.touches)
  23006. });
  23007. return ev;
  23008. },
  23009. /**
  23010. * register new gesture
  23011. * @method register
  23012. * @param {Object} gesture object, see `gestures/` for documentation
  23013. * @return {Array} gestures
  23014. */
  23015. register: function register(gesture) {
  23016. // add an enable gesture options if there is no given
  23017. var options = gesture.defaults || {};
  23018. if(options[gesture.name] === undefined) {
  23019. options[gesture.name] = true;
  23020. }
  23021. // extend Hammer default options with the Hammer.gesture options
  23022. Utils.extend(Hammer.defaults, options, true);
  23023. // set its index
  23024. gesture.index = gesture.index || 1000;
  23025. // add Hammer.gesture to the list
  23026. this.gestures.push(gesture);
  23027. // sort the list by index
  23028. this.gestures.sort(function(a, b) {
  23029. if(a.index < b.index) {
  23030. return -1;
  23031. }
  23032. if(a.index > b.index) {
  23033. return 1;
  23034. }
  23035. return 0;
  23036. });
  23037. return this.gestures;
  23038. }
  23039. };
  23040. /**
  23041. * @module hammer
  23042. */
  23043. /**
  23044. * create new hammer instance
  23045. * all methods should return the instance itself, so it is chainable.
  23046. *
  23047. * @class Instance
  23048. * @constructor
  23049. * @param {HTMLElement} element
  23050. * @param {Object} [options={}] options are merged with `Hammer.defaults`
  23051. * @return {Hammer.Instance}
  23052. */
  23053. Hammer.Instance = function(element, options) {
  23054. var self = this;
  23055. // setup HammerJS window events and register all gestures
  23056. // this also sets up the default options
  23057. setup();
  23058. /**
  23059. * @property element
  23060. * @type {HTMLElement}
  23061. */
  23062. this.element = element;
  23063. /**
  23064. * @property enabled
  23065. * @type {Boolean}
  23066. * @protected
  23067. */
  23068. this.enabled = true;
  23069. /**
  23070. * options, merged with the defaults
  23071. * options with an _ are converted to camelCase
  23072. * @property options
  23073. * @type {Object}
  23074. */
  23075. Utils.each(options, function(value, name) {
  23076. delete options[name];
  23077. options[Utils.toCamelCase(name)] = value;
  23078. });
  23079. this.options = Utils.extend(Utils.extend({}, Hammer.defaults), options || {});
  23080. // add some css to the element to prevent the browser from doing its native behavoir
  23081. if(this.options.behavior) {
  23082. Utils.toggleBehavior(this.element, this.options.behavior, true);
  23083. }
  23084. /**
  23085. * event start handler on the element to start the detection
  23086. * @property eventStartHandler
  23087. * @type {Object}
  23088. */
  23089. this.eventStartHandler = Event.onTouch(element, EVENT_START, function(ev) {
  23090. if(self.enabled && ev.eventType == EVENT_START) {
  23091. Detection.startDetect(self, ev);
  23092. } else if(ev.eventType == EVENT_TOUCH) {
  23093. Detection.detect(ev);
  23094. }
  23095. });
  23096. /**
  23097. * keep a list of user event handlers which needs to be removed when calling 'dispose'
  23098. * @property eventHandlers
  23099. * @type {Array}
  23100. */
  23101. this.eventHandlers = [];
  23102. };
  23103. Hammer.Instance.prototype = {
  23104. /**
  23105. * bind events to the instance
  23106. * @method on
  23107. * @chainable
  23108. * @param {String} gestures multiple gestures by splitting with a space
  23109. * @param {Function} handler
  23110. * @param {Object} handler.ev event object
  23111. */
  23112. on: function onEvent(gestures, handler) {
  23113. var self = this;
  23114. Event.on(self.element, gestures, handler, function(type) {
  23115. self.eventHandlers.push({ gesture: type, handler: handler });
  23116. });
  23117. return self;
  23118. },
  23119. /**
  23120. * unbind events to the instance
  23121. * @method off
  23122. * @chainable
  23123. * @param {String} gestures
  23124. * @param {Function} handler
  23125. */
  23126. off: function offEvent(gestures, handler) {
  23127. var self = this;
  23128. Event.off(self.element, gestures, handler, function(type) {
  23129. var index = Utils.inArray({ gesture: type, handler: handler });
  23130. if(index !== false) {
  23131. self.eventHandlers.splice(index, 1);
  23132. }
  23133. });
  23134. return self;
  23135. },
  23136. /**
  23137. * trigger gesture event
  23138. * @method trigger
  23139. * @chainable
  23140. * @param {String} gesture
  23141. * @param {Object} [eventData]
  23142. */
  23143. trigger: function triggerEvent(gesture, eventData) {
  23144. // optional
  23145. if(!eventData) {
  23146. eventData = {};
  23147. }
  23148. // create DOM event
  23149. var event = Hammer.DOCUMENT.createEvent('Event');
  23150. event.initEvent(gesture, true, true);
  23151. event.gesture = eventData;
  23152. // trigger on the target if it is in the instance element,
  23153. // this is for event delegation tricks
  23154. var element = this.element;
  23155. if(Utils.hasParent(eventData.target, element)) {
  23156. element = eventData.target;
  23157. }
  23158. element.dispatchEvent(event);
  23159. return this;
  23160. },
  23161. /**
  23162. * enable of disable hammer.js detection
  23163. * @method enable
  23164. * @chainable
  23165. * @param {Boolean} state
  23166. */
  23167. enable: function enable(state) {
  23168. this.enabled = state;
  23169. return this;
  23170. },
  23171. /**
  23172. * dispose this hammer instance
  23173. * @method dispose
  23174. * @return {Null}
  23175. */
  23176. dispose: function dispose() {
  23177. var i, eh;
  23178. // undo all changes made by stop_browser_behavior
  23179. Utils.toggleBehavior(this.element, this.options.behavior, false);
  23180. // unbind all custom event handlers
  23181. for(i = -1; (eh = this.eventHandlers[++i]);) {
  23182. Utils.off(this.element, eh.gesture, eh.handler);
  23183. }
  23184. this.eventHandlers = [];
  23185. // unbind the start event listener
  23186. Event.off(this.element, EVENT_TYPES[EVENT_START], this.eventStartHandler);
  23187. return null;
  23188. }
  23189. };
  23190. /**
  23191. * @module gestures
  23192. */
  23193. /**
  23194. * Move with x fingers (default 1) around on the page.
  23195. * Preventing the default browser behavior is a good way to improve feel and working.
  23196. * ````
  23197. * hammertime.on("drag", function(ev) {
  23198. * console.log(ev);
  23199. * ev.gesture.preventDefault();
  23200. * });
  23201. * ````
  23202. *
  23203. * @class Drag
  23204. * @static
  23205. */
  23206. /**
  23207. * @event drag
  23208. * @param {Object} ev
  23209. */
  23210. /**
  23211. * @event dragstart
  23212. * @param {Object} ev
  23213. */
  23214. /**
  23215. * @event dragend
  23216. * @param {Object} ev
  23217. */
  23218. /**
  23219. * @event drapleft
  23220. * @param {Object} ev
  23221. */
  23222. /**
  23223. * @event dragright
  23224. * @param {Object} ev
  23225. */
  23226. /**
  23227. * @event dragup
  23228. * @param {Object} ev
  23229. */
  23230. /**
  23231. * @event dragdown
  23232. * @param {Object} ev
  23233. */
  23234. /**
  23235. * @param {String} name
  23236. */
  23237. (function(name) {
  23238. var triggered = false;
  23239. function dragGesture(ev, inst) {
  23240. var cur = Detection.current;
  23241. // max touches
  23242. if(inst.options.dragMaxTouches > 0 &&
  23243. ev.touches.length > inst.options.dragMaxTouches) {
  23244. return;
  23245. }
  23246. switch(ev.eventType) {
  23247. case EVENT_START:
  23248. triggered = false;
  23249. break;
  23250. case EVENT_MOVE:
  23251. // when the distance we moved is too small we skip this gesture
  23252. // or we can be already in dragging
  23253. if(ev.distance < inst.options.dragMinDistance &&
  23254. cur.name != name) {
  23255. return;
  23256. }
  23257. var startCenter = cur.startEvent.center;
  23258. // we are dragging!
  23259. if(cur.name != name) {
  23260. cur.name = name;
  23261. if(inst.options.dragDistanceCorrection && ev.distance > 0) {
  23262. // When a drag is triggered, set the event center to dragMinDistance pixels from the original event center.
  23263. // Without this correction, the dragged distance would jumpstart at dragMinDistance pixels instead of at 0.
  23264. // It might be useful to save the original start point somewhere
  23265. var factor = Math.abs(inst.options.dragMinDistance / ev.distance);
  23266. startCenter.pageX += ev.deltaX * factor;
  23267. startCenter.pageY += ev.deltaY * factor;
  23268. startCenter.clientX += ev.deltaX * factor;
  23269. startCenter.clientY += ev.deltaY * factor;
  23270. // recalculate event data using new start point
  23271. ev = Detection.extendEventData(ev);
  23272. }
  23273. }
  23274. // lock drag to axis?
  23275. if(cur.lastEvent.dragLockToAxis ||
  23276. ( inst.options.dragLockToAxis &&
  23277. inst.options.dragLockMinDistance <= ev.distance
  23278. )) {
  23279. ev.dragLockToAxis = true;
  23280. }
  23281. // keep direction on the axis that the drag gesture started on
  23282. var lastDirection = cur.lastEvent.direction;
  23283. if(ev.dragLockToAxis && lastDirection !== ev.direction) {
  23284. if(Utils.isVertical(lastDirection)) {
  23285. ev.direction = (ev.deltaY < 0) ? DIRECTION_UP : DIRECTION_DOWN;
  23286. } else {
  23287. ev.direction = (ev.deltaX < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
  23288. }
  23289. }
  23290. // first time, trigger dragstart event
  23291. if(!triggered) {
  23292. inst.trigger(name + 'start', ev);
  23293. triggered = true;
  23294. }
  23295. // trigger events
  23296. inst.trigger(name, ev);
  23297. inst.trigger(name + ev.direction, ev);
  23298. var isVertical = Utils.isVertical(ev.direction);
  23299. // block the browser events
  23300. if((inst.options.dragBlockVertical && isVertical) ||
  23301. (inst.options.dragBlockHorizontal && !isVertical)) {
  23302. ev.preventDefault();
  23303. }
  23304. break;
  23305. case EVENT_RELEASE:
  23306. if(triggered && ev.changedLength <= inst.options.dragMaxTouches) {
  23307. inst.trigger(name + 'end', ev);
  23308. triggered = false;
  23309. }
  23310. break;
  23311. case EVENT_END:
  23312. triggered = false;
  23313. break;
  23314. }
  23315. }
  23316. Hammer.gestures.Drag = {
  23317. name: name,
  23318. index: 50,
  23319. handler: dragGesture,
  23320. defaults: {
  23321. /**
  23322. * minimal movement that have to be made before the drag event gets triggered
  23323. * @property dragMinDistance
  23324. * @type {Number}
  23325. * @default 10
  23326. */
  23327. dragMinDistance: 10,
  23328. /**
  23329. * Set dragDistanceCorrection to true to make the starting point of the drag
  23330. * be calculated from where the drag was triggered, not from where the touch started.
  23331. * Useful to avoid a jerk-starting drag, which can make fine-adjustments
  23332. * through dragging difficult, and be visually unappealing.
  23333. * @property dragDistanceCorrection
  23334. * @type {Boolean}
  23335. * @default true
  23336. */
  23337. dragDistanceCorrection: true,
  23338. /**
  23339. * set 0 for unlimited, but this can conflict with transform
  23340. * @property dragMaxTouches
  23341. * @type {Number}
  23342. * @default 1
  23343. */
  23344. dragMaxTouches: 1,
  23345. /**
  23346. * prevent default browser behavior when dragging occurs
  23347. * be careful with it, it makes the element a blocking element
  23348. * when you are using the drag gesture, it is a good practice to set this true
  23349. * @property dragBlockHorizontal
  23350. * @type {Boolean}
  23351. * @default false
  23352. */
  23353. dragBlockHorizontal: false,
  23354. /**
  23355. * same as `dragBlockHorizontal`, but for vertical movement
  23356. * @property dragBlockVertical
  23357. * @type {Boolean}
  23358. * @default false
  23359. */
  23360. dragBlockVertical: false,
  23361. /**
  23362. * dragLockToAxis keeps the drag gesture on the axis that it started on,
  23363. * It disallows vertical directions if the initial direction was horizontal, and vice versa.
  23364. * @property dragLockToAxis
  23365. * @type {Boolean}
  23366. * @default false
  23367. */
  23368. dragLockToAxis: false,
  23369. /**
  23370. * drag lock only kicks in when distance > dragLockMinDistance
  23371. * This way, locking occurs only when the distance has become large enough to reliably determine the direction
  23372. * @property dragLockMinDistance
  23373. * @type {Number}
  23374. * @default 25
  23375. */
  23376. dragLockMinDistance: 25
  23377. }
  23378. };
  23379. })('drag');
  23380. /**
  23381. * @module gestures
  23382. */
  23383. /**
  23384. * trigger a simple gesture event, so you can do anything in your handler.
  23385. * only usable if you know what your doing...
  23386. *
  23387. * @class Gesture
  23388. * @static
  23389. */
  23390. /**
  23391. * @event gesture
  23392. * @param {Object} ev
  23393. */
  23394. Hammer.gestures.Gesture = {
  23395. name: 'gesture',
  23396. index: 1337,
  23397. handler: function releaseGesture(ev, inst) {
  23398. inst.trigger(this.name, ev);
  23399. }
  23400. };
  23401. /**
  23402. * @module gestures
  23403. */
  23404. /**
  23405. * Touch stays at the same place for x time
  23406. *
  23407. * @class Hold
  23408. * @static
  23409. */
  23410. /**
  23411. * @event hold
  23412. * @param {Object} ev
  23413. */
  23414. /**
  23415. * @param {String} name
  23416. */
  23417. (function(name) {
  23418. var timer;
  23419. function holdGesture(ev, inst) {
  23420. var options = inst.options,
  23421. current = Detection.current;
  23422. switch(ev.eventType) {
  23423. case EVENT_START:
  23424. clearTimeout(timer);
  23425. // set the gesture so we can check in the timeout if it still is
  23426. current.name = name;
  23427. // set timer and if after the timeout it still is hold,
  23428. // we trigger the hold event
  23429. timer = setTimeout(function() {
  23430. if(current && current.name == name) {
  23431. inst.trigger(name, ev);
  23432. }
  23433. }, options.holdTimeout);
  23434. break;
  23435. case EVENT_MOVE:
  23436. if(ev.distance > options.holdThreshold) {
  23437. clearTimeout(timer);
  23438. }
  23439. break;
  23440. case EVENT_RELEASE:
  23441. clearTimeout(timer);
  23442. break;
  23443. }
  23444. }
  23445. Hammer.gestures.Hold = {
  23446. name: name,
  23447. index: 10,
  23448. defaults: {
  23449. /**
  23450. * @property holdTimeout
  23451. * @type {Number}
  23452. * @default 500
  23453. */
  23454. holdTimeout: 500,
  23455. /**
  23456. * movement allowed while holding
  23457. * @property holdThreshold
  23458. * @type {Number}
  23459. * @default 2
  23460. */
  23461. holdThreshold: 2
  23462. },
  23463. handler: holdGesture
  23464. };
  23465. })('hold');
  23466. /**
  23467. * @module gestures
  23468. */
  23469. /**
  23470. * when a touch is being released from the page
  23471. *
  23472. * @class Release
  23473. * @static
  23474. */
  23475. /**
  23476. * @event release
  23477. * @param {Object} ev
  23478. */
  23479. Hammer.gestures.Release = {
  23480. name: 'release',
  23481. index: Infinity,
  23482. handler: function releaseGesture(ev, inst) {
  23483. if(ev.eventType == EVENT_RELEASE) {
  23484. inst.trigger(this.name, ev);
  23485. }
  23486. }
  23487. };
  23488. /**
  23489. * @module gestures
  23490. */
  23491. /**
  23492. * triggers swipe events when the end velocity is above the threshold
  23493. * for best usage, set `preventDefault` (on the drag gesture) to `true`
  23494. * ````
  23495. * hammertime.on("dragleft swipeleft", function(ev) {
  23496. * console.log(ev);
  23497. * ev.gesture.preventDefault();
  23498. * });
  23499. * ````
  23500. *
  23501. * @class Swipe
  23502. * @static
  23503. */
  23504. /**
  23505. * @event swipe
  23506. * @param {Object} ev
  23507. */
  23508. /**
  23509. * @event swipeleft
  23510. * @param {Object} ev
  23511. */
  23512. /**
  23513. * @event swiperight
  23514. * @param {Object} ev
  23515. */
  23516. /**
  23517. * @event swipeup
  23518. * @param {Object} ev
  23519. */
  23520. /**
  23521. * @event swipedown
  23522. * @param {Object} ev
  23523. */
  23524. Hammer.gestures.Swipe = {
  23525. name: 'swipe',
  23526. index: 40,
  23527. defaults: {
  23528. /**
  23529. * @property swipeMinTouches
  23530. * @type {Number}
  23531. * @default 1
  23532. */
  23533. swipeMinTouches: 1,
  23534. /**
  23535. * @property swipeMaxTouches
  23536. * @type {Number}
  23537. * @default 1
  23538. */
  23539. swipeMaxTouches: 1,
  23540. /**
  23541. * horizontal swipe velocity
  23542. * @property swipeVelocityX
  23543. * @type {Number}
  23544. * @default 0.6
  23545. */
  23546. swipeVelocityX: 0.6,
  23547. /**
  23548. * vertical swipe velocity
  23549. * @property swipeVelocityY
  23550. * @type {Number}
  23551. * @default 0.6
  23552. */
  23553. swipeVelocityY: 0.6
  23554. },
  23555. handler: function swipeGesture(ev, inst) {
  23556. if(ev.eventType == EVENT_RELEASE) {
  23557. var touches = ev.touches.length,
  23558. options = inst.options;
  23559. // max touches
  23560. if(touches < options.swipeMinTouches ||
  23561. touches > options.swipeMaxTouches) {
  23562. return;
  23563. }
  23564. // when the distance we moved is too small we skip this gesture
  23565. // or we can be already in dragging
  23566. if(ev.velocityX > options.swipeVelocityX ||
  23567. ev.velocityY > options.swipeVelocityY) {
  23568. // trigger swipe events
  23569. inst.trigger(this.name, ev);
  23570. inst.trigger(this.name + ev.direction, ev);
  23571. }
  23572. }
  23573. }
  23574. };
  23575. /**
  23576. * @module gestures
  23577. */
  23578. /**
  23579. * Single tap and a double tap on a place
  23580. *
  23581. * @class Tap
  23582. * @static
  23583. */
  23584. /**
  23585. * @event tap
  23586. * @param {Object} ev
  23587. */
  23588. /**
  23589. * @event doubletap
  23590. * @param {Object} ev
  23591. */
  23592. /**
  23593. * @param {String} name
  23594. */
  23595. (function(name) {
  23596. var hasMoved = false;
  23597. function tapGesture(ev, inst) {
  23598. var options = inst.options,
  23599. current = Detection.current,
  23600. prev = Detection.previous,
  23601. sincePrev,
  23602. didDoubleTap;
  23603. switch(ev.eventType) {
  23604. case EVENT_START:
  23605. hasMoved = false;
  23606. break;
  23607. case EVENT_MOVE:
  23608. hasMoved = hasMoved || (ev.distance > options.tapMaxDistance);
  23609. break;
  23610. case EVENT_END:
  23611. if(!Utils.inStr(ev.srcEvent.type, 'cancel') && ev.deltaTime < options.tapMaxTime && !hasMoved) {
  23612. // previous gesture, for the double tap since these are two different gesture detections
  23613. sincePrev = prev && prev.lastEvent && ev.timeStamp - prev.lastEvent.timeStamp;
  23614. didDoubleTap = false;
  23615. // check if double tap
  23616. if(prev && prev.name == name &&
  23617. (sincePrev && sincePrev < options.doubleTapInterval) &&
  23618. ev.distance < options.doubleTapDistance) {
  23619. inst.trigger('doubletap', ev);
  23620. didDoubleTap = true;
  23621. }
  23622. // do a single tap
  23623. if(!didDoubleTap || options.tapAlways) {
  23624. current.name = name;
  23625. inst.trigger(current.name, ev);
  23626. }
  23627. }
  23628. break;
  23629. }
  23630. }
  23631. Hammer.gestures.Tap = {
  23632. name: name,
  23633. index: 100,
  23634. handler: tapGesture,
  23635. defaults: {
  23636. /**
  23637. * max time of a tap, this is for the slow tappers
  23638. * @property tapMaxTime
  23639. * @type {Number}
  23640. * @default 250
  23641. */
  23642. tapMaxTime: 250,
  23643. /**
  23644. * max distance of movement of a tap, this is for the slow tappers
  23645. * @property tapMaxDistance
  23646. * @type {Number}
  23647. * @default 10
  23648. */
  23649. tapMaxDistance: 10,
  23650. /**
  23651. * always trigger the `tap` event, even while double-tapping
  23652. * @property tapAlways
  23653. * @type {Boolean}
  23654. * @default true
  23655. */
  23656. tapAlways: true,
  23657. /**
  23658. * max distance between two taps
  23659. * @property doubleTapDistance
  23660. * @type {Number}
  23661. * @default 20
  23662. */
  23663. doubleTapDistance: 20,
  23664. /**
  23665. * max time between two taps
  23666. * @property doubleTapInterval
  23667. * @type {Number}
  23668. * @default 300
  23669. */
  23670. doubleTapInterval: 300
  23671. }
  23672. };
  23673. })('tap');
  23674. /**
  23675. * @module gestures
  23676. */
  23677. /**
  23678. * when a touch is being touched at the page
  23679. *
  23680. * @class Touch
  23681. * @static
  23682. */
  23683. /**
  23684. * @event touch
  23685. * @param {Object} ev
  23686. */
  23687. Hammer.gestures.Touch = {
  23688. name: 'touch',
  23689. index: -Infinity,
  23690. defaults: {
  23691. /**
  23692. * call preventDefault at touchstart, and makes the element blocking by disabling the scrolling of the page,
  23693. * but it improves gestures like transforming and dragging.
  23694. * be careful with using this, it can be very annoying for users to be stuck on the page
  23695. * @property preventDefault
  23696. * @type {Boolean}
  23697. * @default false
  23698. */
  23699. preventDefault: false,
  23700. /**
  23701. * disable mouse events, so only touch (or pen!) input triggers events
  23702. * @property preventMouse
  23703. * @type {Boolean}
  23704. * @default false
  23705. */
  23706. preventMouse: false
  23707. },
  23708. handler: function touchGesture(ev, inst) {
  23709. if(inst.options.preventMouse && ev.pointerType == POINTER_MOUSE) {
  23710. ev.stopDetect();
  23711. return;
  23712. }
  23713. if(inst.options.preventDefault) {
  23714. ev.preventDefault();
  23715. }
  23716. if(ev.eventType == EVENT_TOUCH) {
  23717. inst.trigger('touch', ev);
  23718. }
  23719. }
  23720. };
  23721. /**
  23722. * @module gestures
  23723. */
  23724. /**
  23725. * User want to scale or rotate with 2 fingers
  23726. * Preventing the default browser behavior is a good way to improve feel and working. This can be done with the
  23727. * `preventDefault` option.
  23728. *
  23729. * @class Transform
  23730. * @static
  23731. */
  23732. /**
  23733. * @event transform
  23734. * @param {Object} ev
  23735. */
  23736. /**
  23737. * @event transformstart
  23738. * @param {Object} ev
  23739. */
  23740. /**
  23741. * @event transformend
  23742. * @param {Object} ev
  23743. */
  23744. /**
  23745. * @event pinchin
  23746. * @param {Object} ev
  23747. */
  23748. /**
  23749. * @event pinchout
  23750. * @param {Object} ev
  23751. */
  23752. /**
  23753. * @event rotate
  23754. * @param {Object} ev
  23755. */
  23756. /**
  23757. * @param {String} name
  23758. */
  23759. (function(name) {
  23760. var triggered = false;
  23761. function transformGesture(ev, inst) {
  23762. switch(ev.eventType) {
  23763. case EVENT_START:
  23764. triggered = false;
  23765. break;
  23766. case EVENT_MOVE:
  23767. // at least multitouch
  23768. if(ev.touches.length < 2) {
  23769. return;
  23770. }
  23771. var scaleThreshold = Math.abs(1 - ev.scale);
  23772. var rotationThreshold = Math.abs(ev.rotation);
  23773. // when the distance we moved is too small we skip this gesture
  23774. // or we can be already in dragging
  23775. if(scaleThreshold < inst.options.transformMinScale &&
  23776. rotationThreshold < inst.options.transformMinRotation) {
  23777. return;
  23778. }
  23779. // we are transforming!
  23780. Detection.current.name = name;
  23781. // first time, trigger dragstart event
  23782. if(!triggered) {
  23783. inst.trigger(name + 'start', ev);
  23784. triggered = true;
  23785. }
  23786. inst.trigger(name, ev); // basic transform event
  23787. // trigger rotate event
  23788. if(rotationThreshold > inst.options.transformMinRotation) {
  23789. inst.trigger('rotate', ev);
  23790. }
  23791. // trigger pinch event
  23792. if(scaleThreshold > inst.options.transformMinScale) {
  23793. inst.trigger('pinch', ev);
  23794. inst.trigger('pinch' + (ev.scale < 1 ? 'in' : 'out'), ev);
  23795. }
  23796. break;
  23797. case EVENT_RELEASE:
  23798. if(triggered && ev.changedLength < 2) {
  23799. inst.trigger(name + 'end', ev);
  23800. triggered = false;
  23801. }
  23802. break;
  23803. }
  23804. }
  23805. Hammer.gestures.Transform = {
  23806. name: name,
  23807. index: 45,
  23808. defaults: {
  23809. /**
  23810. * minimal scale factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
  23811. * @property transformMinScale
  23812. * @type {Number}
  23813. * @default 0.01
  23814. */
  23815. transformMinScale: 0.01,
  23816. /**
  23817. * rotation in degrees
  23818. * @property transformMinRotation
  23819. * @type {Number}
  23820. * @default 1
  23821. */
  23822. transformMinRotation: 1
  23823. },
  23824. handler: transformGesture
  23825. };
  23826. })('transform');
  23827. /**
  23828. * @module hammer
  23829. */
  23830. // AMD export
  23831. if(true) {
  23832. !(__WEBPACK_AMD_DEFINE_RESULT__ = function() {
  23833. return Hammer;
  23834. }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  23835. // commonjs export
  23836. } else if(typeof module !== 'undefined' && module.exports) {
  23837. module.exports = Hammer;
  23838. // browser export
  23839. } else {
  23840. window.Hammer = Hammer;
  23841. }
  23842. })(window);
  23843. /***/ },
  23844. /* 54 */
  23845. /***/ function(module, exports, __webpack_require__) {
  23846. /**
  23847. * Creation of the ClusterMixin var.
  23848. *
  23849. * This contains all the functions the Network object can use to employ clustering
  23850. */
  23851. /**
  23852. * This is only called in the constructor of the network object
  23853. *
  23854. */
  23855. exports.startWithClustering = function() {
  23856. // cluster if the data set is big
  23857. this.clusterToFit(this.constants.clustering.initialMaxNodes, true);
  23858. // updates the lables after clustering
  23859. this.updateLabels();
  23860. // this is called here because if clusterin is disabled, the start and stabilize are called in
  23861. // the setData function.
  23862. if (this.stabilize) {
  23863. this._stabilize();
  23864. }
  23865. this.start();
  23866. };
  23867. /**
  23868. * This function clusters until the initialMaxNodes has been reached
  23869. *
  23870. * @param {Number} maxNumberOfNodes
  23871. * @param {Boolean} reposition
  23872. */
  23873. exports.clusterToFit = function(maxNumberOfNodes, reposition) {
  23874. var numberOfNodes = this.nodeIndices.length;
  23875. var maxLevels = 50;
  23876. var level = 0;
  23877. // we first cluster the hubs, then we pull in the outliers, repeat
  23878. while (numberOfNodes > maxNumberOfNodes && level < maxLevels) {
  23879. if (level % 3 == 0) {
  23880. this.forceAggregateHubs(true);
  23881. this.normalizeClusterLevels();
  23882. }
  23883. else {
  23884. this.increaseClusterLevel(); // this also includes a cluster normalization
  23885. }
  23886. numberOfNodes = this.nodeIndices.length;
  23887. level += 1;
  23888. }
  23889. // after the clustering we reposition the nodes to reduce the initial chaos
  23890. if (level > 0 && reposition == true) {
  23891. this.repositionNodes();
  23892. }
  23893. this._updateCalculationNodes();
  23894. };
  23895. /**
  23896. * This function can be called to open up a specific cluster. It is only called by
  23897. * It will unpack the cluster back one level.
  23898. *
  23899. * @param node | Node object: cluster to open.
  23900. */
  23901. exports.openCluster = function(node) {
  23902. var isMovingBeforeClustering = this.moving;
  23903. if (node.clusterSize > this.constants.clustering.sectorThreshold && this._nodeInActiveArea(node) &&
  23904. !(this._sector() == "default" && this.nodeIndices.length == 1)) {
  23905. // this loads a new sector, loads the nodes and edges and nodeIndices of it.
  23906. this._addSector(node);
  23907. var level = 0;
  23908. // we decluster until we reach a decent number of nodes
  23909. while ((this.nodeIndices.length < this.constants.clustering.initialMaxNodes) && (level < 10)) {
  23910. this.decreaseClusterLevel();
  23911. level += 1;
  23912. }
  23913. }
  23914. else {
  23915. this._expandClusterNode(node,false,true);
  23916. // update the index list, dynamic edges and labels
  23917. this._updateNodeIndexList();
  23918. this._updateDynamicEdges();
  23919. this._updateCalculationNodes();
  23920. this.updateLabels();
  23921. }
  23922. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  23923. if (this.moving != isMovingBeforeClustering) {
  23924. this.start();
  23925. }
  23926. };
  23927. /**
  23928. * This calls the updateClustes with default arguments
  23929. */
  23930. exports.updateClustersDefault = function() {
  23931. if (this.constants.clustering.enabled == true) {
  23932. this.updateClusters(0,false,false);
  23933. }
  23934. };
  23935. /**
  23936. * This function can be called to increase the cluster level. This means that the nodes with only one edge connection will
  23937. * be clustered with their connected node. This can be repeated as many times as needed.
  23938. * This can be called externally (by a keybind for instance) to reduce the complexity of big datasets.
  23939. */
  23940. exports.increaseClusterLevel = function() {
  23941. this.updateClusters(-1,false,true);
  23942. };
  23943. /**
  23944. * This function can be called to decrease the cluster level. This means that the nodes with only one edge connection will
  23945. * be unpacked if they are a cluster. This can be repeated as many times as needed.
  23946. * This can be called externally (by a key-bind for instance) to look into clusters without zooming.
  23947. */
  23948. exports.decreaseClusterLevel = function() {
  23949. this.updateClusters(1,false,true);
  23950. };
  23951. /**
  23952. * This is the main clustering function. It clusters and declusters on zoom or forced
  23953. * This function clusters on zoom, it can be called with a predefined zoom direction
  23954. * If out, check if we can form clusters, if in, check if we can open clusters.
  23955. * This function is only called from _zoom()
  23956. *
  23957. * @param {Number} zoomDirection | -1 / 0 / +1 for zoomOut / determineByZoom / zoomIn
  23958. * @param {Boolean} recursive | enabled or disable recursive calling of the opening of clusters
  23959. * @param {Boolean} force | enabled or disable forcing
  23960. * @param {Boolean} doNotStart | if true do not call start
  23961. *
  23962. */
  23963. exports.updateClusters = function(zoomDirection,recursive,force,doNotStart) {
  23964. var isMovingBeforeClustering = this.moving;
  23965. var amountOfNodes = this.nodeIndices.length;
  23966. // on zoom out collapse the sector if the scale is at the level the sector was made
  23967. if (this.previousScale > this.scale && zoomDirection == 0) {
  23968. this._collapseSector();
  23969. }
  23970. // check if we zoom in or out
  23971. if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
  23972. // forming clusters when forced pulls outliers in. When not forced, the edge length of the
  23973. // outer nodes determines if it is being clustered
  23974. this._formClusters(force);
  23975. }
  23976. else if (this.previousScale < this.scale || zoomDirection == 1) { // zoom in
  23977. if (force == true) {
  23978. // _openClusters checks for each node if the formationScale of the cluster is smaller than
  23979. // the current scale and if so, declusters. When forced, all clusters are reduced by one step
  23980. this._openClusters(recursive,force);
  23981. }
  23982. else {
  23983. // if a cluster takes up a set percentage of the active window
  23984. this._openClustersBySize();
  23985. }
  23986. }
  23987. this._updateNodeIndexList();
  23988. // if a cluster was NOT formed and the user zoomed out, we try clustering by hubs
  23989. if (this.nodeIndices.length == amountOfNodes && (this.previousScale > this.scale || zoomDirection == -1)) {
  23990. this._aggregateHubs(force);
  23991. this._updateNodeIndexList();
  23992. }
  23993. // we now reduce chains.
  23994. if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
  23995. this.handleChains();
  23996. this._updateNodeIndexList();
  23997. }
  23998. this.previousScale = this.scale;
  23999. // rest of the update the index list, dynamic edges and labels
  24000. this._updateDynamicEdges();
  24001. this.updateLabels();
  24002. // if a cluster was formed, we increase the clusterSession
  24003. if (this.nodeIndices.length < amountOfNodes) { // this means a clustering operation has taken place
  24004. this.clusterSession += 1;
  24005. // if clusters have been made, we normalize the cluster level
  24006. this.normalizeClusterLevels();
  24007. }
  24008. if (doNotStart == false || doNotStart === undefined) {
  24009. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  24010. if (this.moving != isMovingBeforeClustering) {
  24011. this.start();
  24012. }
  24013. }
  24014. this._updateCalculationNodes();
  24015. };
  24016. /**
  24017. * This function handles the chains. It is called on every updateClusters().
  24018. */
  24019. exports.handleChains = function() {
  24020. // after clustering we check how many chains there are
  24021. var chainPercentage = this._getChainFraction();
  24022. if (chainPercentage > this.constants.clustering.chainThreshold) {
  24023. this._reduceAmountOfChains(1 - this.constants.clustering.chainThreshold / chainPercentage)
  24024. }
  24025. };
  24026. /**
  24027. * this functions starts clustering by hubs
  24028. * The minimum hub threshold is set globally
  24029. *
  24030. * @private
  24031. */
  24032. exports._aggregateHubs = function(force) {
  24033. this._getHubSize();
  24034. this._formClustersByHub(force,false);
  24035. };
  24036. /**
  24037. * This function is fired by keypress. It forces hubs to form.
  24038. *
  24039. */
  24040. exports.forceAggregateHubs = function(doNotStart) {
  24041. var isMovingBeforeClustering = this.moving;
  24042. var amountOfNodes = this.nodeIndices.length;
  24043. this._aggregateHubs(true);
  24044. // update the index list, dynamic edges and labels
  24045. this._updateNodeIndexList();
  24046. this._updateDynamicEdges();
  24047. this.updateLabels();
  24048. // if a cluster was formed, we increase the clusterSession
  24049. if (this.nodeIndices.length != amountOfNodes) {
  24050. this.clusterSession += 1;
  24051. }
  24052. if (doNotStart == false || doNotStart === undefined) {
  24053. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  24054. if (this.moving != isMovingBeforeClustering) {
  24055. this.start();
  24056. }
  24057. }
  24058. };
  24059. /**
  24060. * If a cluster takes up more than a set percentage of the screen, open the cluster
  24061. *
  24062. * @private
  24063. */
  24064. exports._openClustersBySize = function() {
  24065. for (var nodeId in this.nodes) {
  24066. if (this.nodes.hasOwnProperty(nodeId)) {
  24067. var node = this.nodes[nodeId];
  24068. if (node.inView() == true) {
  24069. if ((node.width*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  24070. (node.height*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  24071. this.openCluster(node);
  24072. }
  24073. }
  24074. }
  24075. }
  24076. };
  24077. /**
  24078. * This function loops over all nodes in the nodeIndices list. For each node it checks if it is a cluster and if it
  24079. * has to be opened based on the current zoom level.
  24080. *
  24081. * @private
  24082. */
  24083. exports._openClusters = function(recursive,force) {
  24084. for (var i = 0; i < this.nodeIndices.length; i++) {
  24085. var node = this.nodes[this.nodeIndices[i]];
  24086. this._expandClusterNode(node,recursive,force);
  24087. this._updateCalculationNodes();
  24088. }
  24089. };
  24090. /**
  24091. * This function checks if a node has to be opened. This is done by checking the zoom level.
  24092. * If the node contains child nodes, this function is recursively called on the child nodes as well.
  24093. * This recursive behaviour is optional and can be set by the recursive argument.
  24094. *
  24095. * @param {Node} parentNode | to check for cluster and expand
  24096. * @param {Boolean} recursive | enabled or disable recursive calling
  24097. * @param {Boolean} force | enabled or disable forcing
  24098. * @param {Boolean} [openAll] | This will recursively force all nodes in the parent to be released
  24099. * @private
  24100. */
  24101. exports._expandClusterNode = function(parentNode, recursive, force, openAll) {
  24102. // first check if node is a cluster
  24103. if (parentNode.clusterSize > 1) {
  24104. // this means that on a double tap event or a zoom event, the cluster fully unpacks if it is smaller than 20
  24105. if (parentNode.clusterSize < this.constants.clustering.sectorThreshold) {
  24106. openAll = true;
  24107. }
  24108. recursive = openAll ? true : recursive;
  24109. // if the last child has been added on a smaller scale than current scale decluster
  24110. if (parentNode.formationScale < this.scale || force == true) {
  24111. // we will check if any of the contained child nodes should be removed from the cluster
  24112. for (var containedNodeId in parentNode.containedNodes) {
  24113. if (parentNode.containedNodes.hasOwnProperty(containedNodeId)) {
  24114. var childNode = parentNode.containedNodes[containedNodeId];
  24115. // force expand will expand the largest cluster size clusters. Since we cluster from outside in, we assume that
  24116. // the largest cluster is the one that comes from outside
  24117. if (force == true) {
  24118. if (childNode.clusterSession == parentNode.clusterSessions[parentNode.clusterSessions.length-1]
  24119. || openAll) {
  24120. this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
  24121. }
  24122. }
  24123. else {
  24124. if (this._nodeInActiveArea(parentNode)) {
  24125. this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
  24126. }
  24127. }
  24128. }
  24129. }
  24130. }
  24131. }
  24132. };
  24133. /**
  24134. * ONLY CALLED FROM _expandClusterNode
  24135. *
  24136. * This function will expel a child_node from a parent_node. This is to de-cluster the node. This function will remove
  24137. * the child node from the parent contained_node object and put it back into the global nodes object.
  24138. * The same holds for the edge that was connected to the child node. It is moved back into the global edges object.
  24139. *
  24140. * @param {Node} parentNode | the parent node
  24141. * @param {String} containedNodeId | child_node id as it is contained in the containedNodes object of the parent node
  24142. * @param {Boolean} recursive | This will also check if the child needs to be expanded.
  24143. * With force and recursive both true, the entire cluster is unpacked
  24144. * @param {Boolean} force | This will disregard the zoom level and will expel this child from the parent
  24145. * @param {Boolean} openAll | This will recursively force all nodes in the parent to be released
  24146. * @private
  24147. */
  24148. exports._expelChildFromParent = function(parentNode, containedNodeId, recursive, force, openAll) {
  24149. var childNode = parentNode.containedNodes[containedNodeId];
  24150. // if child node has been added on smaller scale than current, kick out
  24151. if (childNode.formationScale < this.scale || force == true) {
  24152. // unselect all selected items
  24153. this._unselectAll();
  24154. // put the child node back in the global nodes object
  24155. this.nodes[containedNodeId] = childNode;
  24156. // release the contained edges from this childNode back into the global edges
  24157. this._releaseContainedEdges(parentNode,childNode);
  24158. // reconnect rerouted edges to the childNode
  24159. this._connectEdgeBackToChild(parentNode,childNode);
  24160. // validate all edges in dynamicEdges
  24161. this._validateEdges(parentNode);
  24162. // undo the changes from the clustering operation on the parent node
  24163. parentNode.options.mass -= childNode.options.mass;
  24164. parentNode.clusterSize -= childNode.clusterSize;
  24165. parentNode.options.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
  24166. parentNode.dynamicEdgesLength = parentNode.dynamicEdges.length;
  24167. // place the child node near the parent, not at the exact same location to avoid chaos in the system
  24168. childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random());
  24169. childNode.y = parentNode.y + parentNode.growthIndicator * (0.5 - Math.random());
  24170. // remove node from the list
  24171. delete parentNode.containedNodes[containedNodeId];
  24172. // check if there are other childs with this clusterSession in the parent.
  24173. var othersPresent = false;
  24174. for (var childNodeId in parentNode.containedNodes) {
  24175. if (parentNode.containedNodes.hasOwnProperty(childNodeId)) {
  24176. if (parentNode.containedNodes[childNodeId].clusterSession == childNode.clusterSession) {
  24177. othersPresent = true;
  24178. break;
  24179. }
  24180. }
  24181. }
  24182. // if there are no others, remove the cluster session from the list
  24183. if (othersPresent == false) {
  24184. parentNode.clusterSessions.pop();
  24185. }
  24186. this._repositionBezierNodes(childNode);
  24187. // this._repositionBezierNodes(parentNode);
  24188. // remove the clusterSession from the child node
  24189. childNode.clusterSession = 0;
  24190. // recalculate the size of the node on the next time the node is rendered
  24191. parentNode.clearSizeCache();
  24192. // restart the simulation to reorganise all nodes
  24193. this.moving = true;
  24194. }
  24195. // check if a further expansion step is possible if recursivity is enabled
  24196. if (recursive == true) {
  24197. this._expandClusterNode(childNode,recursive,force,openAll);
  24198. }
  24199. };
  24200. /**
  24201. * position the bezier nodes at the center of the edges
  24202. *
  24203. * @param node
  24204. * @private
  24205. */
  24206. exports._repositionBezierNodes = function(node) {
  24207. for (var i = 0; i < node.dynamicEdges.length; i++) {
  24208. node.dynamicEdges[i].positionBezierNode();
  24209. }
  24210. };
  24211. /**
  24212. * This function checks if any nodes at the end of their trees have edges below a threshold length
  24213. * This function is called only from updateClusters()
  24214. * forceLevelCollapse ignores the length of the edge and collapses one level
  24215. * This means that a node with only one edge will be clustered with its connected node
  24216. *
  24217. * @private
  24218. * @param {Boolean} force
  24219. */
  24220. exports._formClusters = function(force) {
  24221. if (force == false) {
  24222. this._formClustersByZoom();
  24223. }
  24224. else {
  24225. this._forceClustersByZoom();
  24226. }
  24227. };
  24228. /**
  24229. * This function handles the clustering by zooming out, this is based on a minimum edge distance
  24230. *
  24231. * @private
  24232. */
  24233. exports._formClustersByZoom = function() {
  24234. var dx,dy,length,
  24235. minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
  24236. // check if any edges are shorter than minLength and start the clustering
  24237. // the clustering favours the node with the larger mass
  24238. for (var edgeId in this.edges) {
  24239. if (this.edges.hasOwnProperty(edgeId)) {
  24240. var edge = this.edges[edgeId];
  24241. if (edge.connected) {
  24242. if (edge.toId != edge.fromId) {
  24243. dx = (edge.to.x - edge.from.x);
  24244. dy = (edge.to.y - edge.from.y);
  24245. length = Math.sqrt(dx * dx + dy * dy);
  24246. if (length < minLength) {
  24247. // first check which node is larger
  24248. var parentNode = edge.from;
  24249. var childNode = edge.to;
  24250. if (edge.to.options.mass > edge.from.options.mass) {
  24251. parentNode = edge.to;
  24252. childNode = edge.from;
  24253. }
  24254. if (childNode.dynamicEdgesLength == 1) {
  24255. this._addToCluster(parentNode,childNode,false);
  24256. }
  24257. else if (parentNode.dynamicEdgesLength == 1) {
  24258. this._addToCluster(childNode,parentNode,false);
  24259. }
  24260. }
  24261. }
  24262. }
  24263. }
  24264. }
  24265. };
  24266. /**
  24267. * This function forces the network to cluster all nodes with only one connecting edge to their
  24268. * connected node.
  24269. *
  24270. * @private
  24271. */
  24272. exports._forceClustersByZoom = function() {
  24273. for (var nodeId in this.nodes) {
  24274. // another node could have absorbed this child.
  24275. if (this.nodes.hasOwnProperty(nodeId)) {
  24276. var childNode = this.nodes[nodeId];
  24277. // the edges can be swallowed by another decrease
  24278. if (childNode.dynamicEdgesLength == 1 && childNode.dynamicEdges.length != 0) {
  24279. var edge = childNode.dynamicEdges[0];
  24280. var parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId];
  24281. // group to the largest node
  24282. if (childNode.id != parentNode.id) {
  24283. if (parentNode.options.mass > childNode.options.mass) {
  24284. this._addToCluster(parentNode,childNode,true);
  24285. }
  24286. else {
  24287. this._addToCluster(childNode,parentNode,true);
  24288. }
  24289. }
  24290. }
  24291. }
  24292. }
  24293. };
  24294. /**
  24295. * To keep the nodes of roughly equal size we normalize the cluster levels.
  24296. * This function clusters a node to its smallest connected neighbour.
  24297. *
  24298. * @param node
  24299. * @private
  24300. */
  24301. exports._clusterToSmallestNeighbour = function(node) {
  24302. var smallestNeighbour = -1;
  24303. var smallestNeighbourNode = null;
  24304. for (var i = 0; i < node.dynamicEdges.length; i++) {
  24305. if (node.dynamicEdges[i] !== undefined) {
  24306. var neighbour = null;
  24307. if (node.dynamicEdges[i].fromId != node.id) {
  24308. neighbour = node.dynamicEdges[i].from;
  24309. }
  24310. else if (node.dynamicEdges[i].toId != node.id) {
  24311. neighbour = node.dynamicEdges[i].to;
  24312. }
  24313. if (neighbour != null && smallestNeighbour > neighbour.clusterSessions.length) {
  24314. smallestNeighbour = neighbour.clusterSessions.length;
  24315. smallestNeighbourNode = neighbour;
  24316. }
  24317. }
  24318. }
  24319. if (neighbour != null && this.nodes[neighbour.id] !== undefined) {
  24320. this._addToCluster(neighbour, node, true);
  24321. }
  24322. };
  24323. /**
  24324. * This function forms clusters from hubs, it loops over all nodes
  24325. *
  24326. * @param {Boolean} force | Disregard zoom level
  24327. * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
  24328. * @private
  24329. */
  24330. exports._formClustersByHub = function(force, onlyEqual) {
  24331. // we loop over all nodes in the list
  24332. for (var nodeId in this.nodes) {
  24333. // we check if it is still available since it can be used by the clustering in this loop
  24334. if (this.nodes.hasOwnProperty(nodeId)) {
  24335. this._formClusterFromHub(this.nodes[nodeId],force,onlyEqual);
  24336. }
  24337. }
  24338. };
  24339. /**
  24340. * This function forms a cluster from a specific preselected hub node
  24341. *
  24342. * @param {Node} hubNode | the node we will cluster as a hub
  24343. * @param {Boolean} force | Disregard zoom level
  24344. * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
  24345. * @param {Number} [absorptionSizeOffset] |
  24346. * @private
  24347. */
  24348. exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSizeOffset) {
  24349. if (absorptionSizeOffset === undefined) {
  24350. absorptionSizeOffset = 0;
  24351. }
  24352. // we decide if the node is a hub
  24353. if ((hubNode.dynamicEdgesLength >= this.hubThreshold && onlyEqual == false) ||
  24354. (hubNode.dynamicEdgesLength == this.hubThreshold && onlyEqual == true)) {
  24355. // initialize variables
  24356. var dx,dy,length;
  24357. var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
  24358. var allowCluster = false;
  24359. // we create a list of edges because the dynamicEdges change over the course of this loop
  24360. var edgesIdarray = [];
  24361. var amountOfInitialEdges = hubNode.dynamicEdges.length;
  24362. for (var j = 0; j < amountOfInitialEdges; j++) {
  24363. edgesIdarray.push(hubNode.dynamicEdges[j].id);
  24364. }
  24365. // if the hub clustering is not forces, we check if one of the edges connected
  24366. // to a cluster is small enough based on the constants.clustering.clusterEdgeThreshold
  24367. if (force == false) {
  24368. allowCluster = false;
  24369. for (j = 0; j < amountOfInitialEdges; j++) {
  24370. var edge = this.edges[edgesIdarray[j]];
  24371. if (edge !== undefined) {
  24372. if (edge.connected) {
  24373. if (edge.toId != edge.fromId) {
  24374. dx = (edge.to.x - edge.from.x);
  24375. dy = (edge.to.y - edge.from.y);
  24376. length = Math.sqrt(dx * dx + dy * dy);
  24377. if (length < minLength) {
  24378. allowCluster = true;
  24379. break;
  24380. }
  24381. }
  24382. }
  24383. }
  24384. }
  24385. }
  24386. // start the clustering if allowed
  24387. if ((!force && allowCluster) || force) {
  24388. // we loop over all edges INITIALLY connected to this hub
  24389. for (j = 0; j < amountOfInitialEdges; j++) {
  24390. edge = this.edges[edgesIdarray[j]];
  24391. // the edge can be clustered by this function in a previous loop
  24392. if (edge !== undefined) {
  24393. var childNode = this.nodes[(edge.fromId == hubNode.id) ? edge.toId : edge.fromId];
  24394. // we do not want hubs to merge with other hubs nor do we want to cluster itself.
  24395. if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) &&
  24396. (childNode.id != hubNode.id)) {
  24397. this._addToCluster(hubNode,childNode,force);
  24398. }
  24399. }
  24400. }
  24401. }
  24402. }
  24403. };
  24404. /**
  24405. * This function adds the child node to the parent node, creating a cluster if it is not already.
  24406. *
  24407. * @param {Node} parentNode | this is the node that will house the child node
  24408. * @param {Node} childNode | this node will be deleted from the global this.nodes and stored in the parent node
  24409. * @param {Boolean} force | true will only update the remainingEdges at the very end of the clustering, ensuring single level collapse
  24410. * @private
  24411. */
  24412. exports._addToCluster = function(parentNode, childNode, force) {
  24413. // join child node in the parent node
  24414. parentNode.containedNodes[childNode.id] = childNode;
  24415. // manage all the edges connected to the child and parent nodes
  24416. for (var i = 0; i < childNode.dynamicEdges.length; i++) {
  24417. var edge = childNode.dynamicEdges[i];
  24418. if (edge.toId == parentNode.id || edge.fromId == parentNode.id) { // edge connected to parentNode
  24419. this._addToContainedEdges(parentNode,childNode,edge);
  24420. }
  24421. else {
  24422. this._connectEdgeToCluster(parentNode,childNode,edge);
  24423. }
  24424. }
  24425. // a contained node has no dynamic edges.
  24426. childNode.dynamicEdges = [];
  24427. // remove circular edges from clusters
  24428. this._containCircularEdgesFromNode(parentNode,childNode);
  24429. // remove the childNode from the global nodes object
  24430. delete this.nodes[childNode.id];
  24431. // update the properties of the child and parent
  24432. var massBefore = parentNode.options.mass;
  24433. childNode.clusterSession = this.clusterSession;
  24434. parentNode.options.mass += childNode.options.mass;
  24435. parentNode.clusterSize += childNode.clusterSize;
  24436. parentNode.options.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
  24437. // keep track of the clustersessions so we can open the cluster up as it has been formed.
  24438. if (parentNode.clusterSessions[parentNode.clusterSessions.length - 1] != this.clusterSession) {
  24439. parentNode.clusterSessions.push(this.clusterSession);
  24440. }
  24441. // forced clusters only open from screen size and double tap
  24442. if (force == true) {
  24443. // parentNode.formationScale = Math.pow(1 - (1.0/11.0),this.clusterSession+3);
  24444. parentNode.formationScale = 0;
  24445. }
  24446. else {
  24447. parentNode.formationScale = this.scale; // The latest child has been added on this scale
  24448. }
  24449. // recalculate the size of the node on the next time the node is rendered
  24450. parentNode.clearSizeCache();
  24451. // set the pop-out scale for the childnode
  24452. parentNode.containedNodes[childNode.id].formationScale = parentNode.formationScale;
  24453. // nullify the movement velocity of the child, this is to avoid hectic behaviour
  24454. childNode.clearVelocity();
  24455. // the mass has altered, preservation of energy dictates the velocity to be updated
  24456. parentNode.updateVelocity(massBefore);
  24457. // restart the simulation to reorganise all nodes
  24458. this.moving = true;
  24459. };
  24460. /**
  24461. * This function will apply the changes made to the remainingEdges during the formation of the clusters.
  24462. * This is a seperate function to allow for level-wise collapsing of the node barnesHutTree.
  24463. * It has to be called if a level is collapsed. It is called by _formClusters().
  24464. * @private
  24465. */
  24466. exports._updateDynamicEdges = function() {
  24467. for (var i = 0; i < this.nodeIndices.length; i++) {
  24468. var node = this.nodes[this.nodeIndices[i]];
  24469. node.dynamicEdgesLength = node.dynamicEdges.length;
  24470. // this corrects for multiple edges pointing at the same other node
  24471. var correction = 0;
  24472. if (node.dynamicEdgesLength > 1) {
  24473. for (var j = 0; j < node.dynamicEdgesLength - 1; j++) {
  24474. var edgeToId = node.dynamicEdges[j].toId;
  24475. var edgeFromId = node.dynamicEdges[j].fromId;
  24476. for (var k = j+1; k < node.dynamicEdgesLength; k++) {
  24477. if ((node.dynamicEdges[k].toId == edgeToId && node.dynamicEdges[k].fromId == edgeFromId) ||
  24478. (node.dynamicEdges[k].fromId == edgeToId && node.dynamicEdges[k].toId == edgeFromId)) {
  24479. correction += 1;
  24480. }
  24481. }
  24482. }
  24483. }
  24484. node.dynamicEdgesLength -= correction;
  24485. }
  24486. };
  24487. /**
  24488. * This adds an edge from the childNode to the contained edges of the parent node
  24489. *
  24490. * @param parentNode | Node object
  24491. * @param childNode | Node object
  24492. * @param edge | Edge object
  24493. * @private
  24494. */
  24495. exports._addToContainedEdges = function(parentNode, childNode, edge) {
  24496. // create an array object if it does not yet exist for this childNode
  24497. if (!(parentNode.containedEdges.hasOwnProperty(childNode.id))) {
  24498. parentNode.containedEdges[childNode.id] = []
  24499. }
  24500. // add this edge to the list
  24501. parentNode.containedEdges[childNode.id].push(edge);
  24502. // remove the edge from the global edges object
  24503. delete this.edges[edge.id];
  24504. // remove the edge from the parent object
  24505. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  24506. if (parentNode.dynamicEdges[i].id == edge.id) {
  24507. parentNode.dynamicEdges.splice(i,1);
  24508. break;
  24509. }
  24510. }
  24511. };
  24512. /**
  24513. * This function connects an edge that was connected to a child node to the parent node.
  24514. * It keeps track of which nodes it has been connected to with the originalId array.
  24515. *
  24516. * @param {Node} parentNode | Node object
  24517. * @param {Node} childNode | Node object
  24518. * @param {Edge} edge | Edge object
  24519. * @private
  24520. */
  24521. exports._connectEdgeToCluster = function(parentNode, childNode, edge) {
  24522. // handle circular edges
  24523. if (edge.toId == edge.fromId) {
  24524. this._addToContainedEdges(parentNode, childNode, edge);
  24525. }
  24526. else {
  24527. if (edge.toId == childNode.id) { // edge connected to other node on the "to" side
  24528. edge.originalToId.push(childNode.id);
  24529. edge.to = parentNode;
  24530. edge.toId = parentNode.id;
  24531. }
  24532. else { // edge connected to other node with the "from" side
  24533. edge.originalFromId.push(childNode.id);
  24534. edge.from = parentNode;
  24535. edge.fromId = parentNode.id;
  24536. }
  24537. this._addToReroutedEdges(parentNode,childNode,edge);
  24538. }
  24539. };
  24540. /**
  24541. * If a node is connected to itself, a circular edge is drawn. When clustering we want to contain
  24542. * these edges inside of the cluster.
  24543. *
  24544. * @param parentNode
  24545. * @param childNode
  24546. * @private
  24547. */
  24548. exports._containCircularEdgesFromNode = function(parentNode, childNode) {
  24549. // manage all the edges connected to the child and parent nodes
  24550. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  24551. var edge = parentNode.dynamicEdges[i];
  24552. // handle circular edges
  24553. if (edge.toId == edge.fromId) {
  24554. this._addToContainedEdges(parentNode, childNode, edge);
  24555. }
  24556. }
  24557. };
  24558. /**
  24559. * This adds an edge from the childNode to the rerouted edges of the parent node
  24560. *
  24561. * @param parentNode | Node object
  24562. * @param childNode | Node object
  24563. * @param edge | Edge object
  24564. * @private
  24565. */
  24566. exports._addToReroutedEdges = function(parentNode, childNode, edge) {
  24567. // create an array object if it does not yet exist for this childNode
  24568. // we store the edge in the rerouted edges so we can restore it when the cluster pops open
  24569. if (!(parentNode.reroutedEdges.hasOwnProperty(childNode.id))) {
  24570. parentNode.reroutedEdges[childNode.id] = [];
  24571. }
  24572. parentNode.reroutedEdges[childNode.id].push(edge);
  24573. // this edge becomes part of the dynamicEdges of the cluster node
  24574. parentNode.dynamicEdges.push(edge);
  24575. };
  24576. /**
  24577. * This function connects an edge that was connected to a cluster node back to the child node.
  24578. *
  24579. * @param parentNode | Node object
  24580. * @param childNode | Node object
  24581. * @private
  24582. */
  24583. exports._connectEdgeBackToChild = function(parentNode, childNode) {
  24584. if (parentNode.reroutedEdges.hasOwnProperty(childNode.id)) {
  24585. for (var i = 0; i < parentNode.reroutedEdges[childNode.id].length; i++) {
  24586. var edge = parentNode.reroutedEdges[childNode.id][i];
  24587. if (edge.originalFromId[edge.originalFromId.length-1] == childNode.id) {
  24588. edge.originalFromId.pop();
  24589. edge.fromId = childNode.id;
  24590. edge.from = childNode;
  24591. }
  24592. else {
  24593. edge.originalToId.pop();
  24594. edge.toId = childNode.id;
  24595. edge.to = childNode;
  24596. }
  24597. // append this edge to the list of edges connecting to the childnode
  24598. childNode.dynamicEdges.push(edge);
  24599. // remove the edge from the parent object
  24600. for (var j = 0; j < parentNode.dynamicEdges.length; j++) {
  24601. if (parentNode.dynamicEdges[j].id == edge.id) {
  24602. parentNode.dynamicEdges.splice(j,1);
  24603. break;
  24604. }
  24605. }
  24606. }
  24607. // remove the entry from the rerouted edges
  24608. delete parentNode.reroutedEdges[childNode.id];
  24609. }
  24610. };
  24611. /**
  24612. * When loops are clustered, an edge can be both in the rerouted array and the contained array.
  24613. * This function is called last to verify that all edges in dynamicEdges are in fact connected to the
  24614. * parentNode
  24615. *
  24616. * @param parentNode | Node object
  24617. * @private
  24618. */
  24619. exports._validateEdges = function(parentNode) {
  24620. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  24621. var edge = parentNode.dynamicEdges[i];
  24622. if (parentNode.id != edge.toId && parentNode.id != edge.fromId) {
  24623. parentNode.dynamicEdges.splice(i,1);
  24624. }
  24625. }
  24626. };
  24627. /**
  24628. * This function released the contained edges back into the global domain and puts them back into the
  24629. * dynamic edges of both parent and child.
  24630. *
  24631. * @param {Node} parentNode |
  24632. * @param {Node} childNode |
  24633. * @private
  24634. */
  24635. exports._releaseContainedEdges = function(parentNode, childNode) {
  24636. for (var i = 0; i < parentNode.containedEdges[childNode.id].length; i++) {
  24637. var edge = parentNode.containedEdges[childNode.id][i];
  24638. // put the edge back in the global edges object
  24639. this.edges[edge.id] = edge;
  24640. // put the edge back in the dynamic edges of the child and parent
  24641. childNode.dynamicEdges.push(edge);
  24642. parentNode.dynamicEdges.push(edge);
  24643. }
  24644. // remove the entry from the contained edges
  24645. delete parentNode.containedEdges[childNode.id];
  24646. };
  24647. // ------------------- UTILITY FUNCTIONS ---------------------------- //
  24648. /**
  24649. * This updates the node labels for all nodes (for debugging purposes)
  24650. */
  24651. exports.updateLabels = function() {
  24652. var nodeId;
  24653. // update node labels
  24654. for (nodeId in this.nodes) {
  24655. if (this.nodes.hasOwnProperty(nodeId)) {
  24656. var node = this.nodes[nodeId];
  24657. if (node.clusterSize > 1) {
  24658. node.label = "[".concat(String(node.clusterSize),"]");
  24659. }
  24660. }
  24661. }
  24662. // update node labels
  24663. for (nodeId in this.nodes) {
  24664. if (this.nodes.hasOwnProperty(nodeId)) {
  24665. node = this.nodes[nodeId];
  24666. if (node.clusterSize == 1) {
  24667. if (node.originalLabel !== undefined) {
  24668. node.label = node.originalLabel;
  24669. }
  24670. else {
  24671. node.label = String(node.id);
  24672. }
  24673. }
  24674. }
  24675. }
  24676. // /* Debug Override */
  24677. // for (nodeId in this.nodes) {
  24678. // if (this.nodes.hasOwnProperty(nodeId)) {
  24679. // node = this.nodes[nodeId];
  24680. // node.label = String(node.level);
  24681. // }
  24682. // }
  24683. };
  24684. /**
  24685. * We want to keep the cluster level distribution rather small. This means we do not want unclustered nodes
  24686. * if the rest of the nodes are already a few cluster levels in.
  24687. * To fix this we use this function. It determines the min and max cluster level and sends nodes that have not
  24688. * clustered enough to the clusterToSmallestNeighbours function.
  24689. */
  24690. exports.normalizeClusterLevels = function() {
  24691. var maxLevel = 0;
  24692. var minLevel = 1e9;
  24693. var clusterLevel = 0;
  24694. var nodeId;
  24695. // we loop over all nodes in the list
  24696. for (nodeId in this.nodes) {
  24697. if (this.nodes.hasOwnProperty(nodeId)) {
  24698. clusterLevel = this.nodes[nodeId].clusterSessions.length;
  24699. if (maxLevel < clusterLevel) {maxLevel = clusterLevel;}
  24700. if (minLevel > clusterLevel) {minLevel = clusterLevel;}
  24701. }
  24702. }
  24703. if (maxLevel - minLevel > this.constants.clustering.clusterLevelDifference) {
  24704. var amountOfNodes = this.nodeIndices.length;
  24705. var targetLevel = maxLevel - this.constants.clustering.clusterLevelDifference;
  24706. // we loop over all nodes in the list
  24707. for (nodeId in this.nodes) {
  24708. if (this.nodes.hasOwnProperty(nodeId)) {
  24709. if (this.nodes[nodeId].clusterSessions.length < targetLevel) {
  24710. this._clusterToSmallestNeighbour(this.nodes[nodeId]);
  24711. }
  24712. }
  24713. }
  24714. this._updateNodeIndexList();
  24715. this._updateDynamicEdges();
  24716. // if a cluster was formed, we increase the clusterSession
  24717. if (this.nodeIndices.length != amountOfNodes) {
  24718. this.clusterSession += 1;
  24719. }
  24720. }
  24721. };
  24722. /**
  24723. * This function determines if the cluster we want to decluster is in the active area
  24724. * this means around the zoom center
  24725. *
  24726. * @param {Node} node
  24727. * @returns {boolean}
  24728. * @private
  24729. */
  24730. exports._nodeInActiveArea = function(node) {
  24731. return (
  24732. Math.abs(node.x - this.areaCenter.x) <= this.constants.clustering.activeAreaBoxSize/this.scale
  24733. &&
  24734. Math.abs(node.y - this.areaCenter.y) <= this.constants.clustering.activeAreaBoxSize/this.scale
  24735. )
  24736. };
  24737. /**
  24738. * This is an adaptation of the original repositioning function. This is called if the system is clustered initially
  24739. * It puts large clusters away from the center and randomizes the order.
  24740. *
  24741. */
  24742. exports.repositionNodes = function() {
  24743. for (var i = 0; i < this.nodeIndices.length; i++) {
  24744. var node = this.nodes[this.nodeIndices[i]];
  24745. if ((node.xFixed == false || node.yFixed == false)) {
  24746. var radius = 10 * 0.1*this.nodeIndices.length * Math.min(100,node.options.mass);
  24747. var angle = 2 * Math.PI * Math.random();
  24748. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  24749. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  24750. this._repositionBezierNodes(node);
  24751. }
  24752. }
  24753. };
  24754. /**
  24755. * We determine how many connections denote an important hub.
  24756. * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
  24757. *
  24758. * @private
  24759. */
  24760. exports._getHubSize = function() {
  24761. var average = 0;
  24762. var averageSquared = 0;
  24763. var hubCounter = 0;
  24764. var largestHub = 0;
  24765. for (var i = 0; i < this.nodeIndices.length; i++) {
  24766. var node = this.nodes[this.nodeIndices[i]];
  24767. if (node.dynamicEdgesLength > largestHub) {
  24768. largestHub = node.dynamicEdgesLength;
  24769. }
  24770. average += node.dynamicEdgesLength;
  24771. averageSquared += Math.pow(node.dynamicEdgesLength,2);
  24772. hubCounter += 1;
  24773. }
  24774. average = average / hubCounter;
  24775. averageSquared = averageSquared / hubCounter;
  24776. var variance = averageSquared - Math.pow(average,2);
  24777. var standardDeviation = Math.sqrt(variance);
  24778. this.hubThreshold = Math.floor(average + 2*standardDeviation);
  24779. // always have at least one to cluster
  24780. if (this.hubThreshold > largestHub) {
  24781. this.hubThreshold = largestHub;
  24782. }
  24783. // console.log("average",average,"averageSQ",averageSquared,"var",variance,"std",standardDeviation);
  24784. // console.log("hubThreshold:",this.hubThreshold);
  24785. };
  24786. /**
  24787. * We reduce the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
  24788. * with this amount we can cluster specifically on these chains.
  24789. *
  24790. * @param {Number} fraction | between 0 and 1, the percentage of chains to reduce
  24791. * @private
  24792. */
  24793. exports._reduceAmountOfChains = function(fraction) {
  24794. this.hubThreshold = 2;
  24795. var reduceAmount = Math.floor(this.nodeIndices.length * fraction);
  24796. for (var nodeId in this.nodes) {
  24797. if (this.nodes.hasOwnProperty(nodeId)) {
  24798. if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) {
  24799. if (reduceAmount > 0) {
  24800. this._formClusterFromHub(this.nodes[nodeId],true,true,1);
  24801. reduceAmount -= 1;
  24802. }
  24803. }
  24804. }
  24805. }
  24806. };
  24807. /**
  24808. * We get the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
  24809. * with this amount we can cluster specifically on these chains.
  24810. *
  24811. * @private
  24812. */
  24813. exports._getChainFraction = function() {
  24814. var chains = 0;
  24815. var total = 0;
  24816. for (var nodeId in this.nodes) {
  24817. if (this.nodes.hasOwnProperty(nodeId)) {
  24818. if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) {
  24819. chains += 1;
  24820. }
  24821. total += 1;
  24822. }
  24823. }
  24824. return chains/total;
  24825. };
  24826. /***/ },
  24827. /* 55 */
  24828. /***/ function(module, exports, __webpack_require__) {
  24829. var util = __webpack_require__(1);
  24830. /**
  24831. * Creation of the SectorMixin var.
  24832. *
  24833. * This contains all the functions the Network object can use to employ the sector system.
  24834. * The sector system is always used by Network, though the benefits only apply to the use of clustering.
  24835. * If clustering is not used, there is no overhead except for a duplicate object with references to nodes and edges.
  24836. */
  24837. /**
  24838. * This function is only called by the setData function of the Network object.
  24839. * This loads the global references into the active sector. This initializes the sector.
  24840. *
  24841. * @private
  24842. */
  24843. exports._putDataInSector = function() {
  24844. this.sectors["active"][this._sector()].nodes = this.nodes;
  24845. this.sectors["active"][this._sector()].edges = this.edges;
  24846. this.sectors["active"][this._sector()].nodeIndices = this.nodeIndices;
  24847. };
  24848. /**
  24849. * /**
  24850. * This function sets the global references to nodes, edges and nodeIndices back to
  24851. * those of the supplied (active) sector. If a type is defined, do the specific type
  24852. *
  24853. * @param {String} sectorId
  24854. * @param {String} [sectorType] | "active" or "frozen"
  24855. * @private
  24856. */
  24857. exports._switchToSector = function(sectorId, sectorType) {
  24858. if (sectorType === undefined || sectorType == "active") {
  24859. this._switchToActiveSector(sectorId);
  24860. }
  24861. else {
  24862. this._switchToFrozenSector(sectorId);
  24863. }
  24864. };
  24865. /**
  24866. * This function sets the global references to nodes, edges and nodeIndices back to
  24867. * those of the supplied active sector.
  24868. *
  24869. * @param sectorId
  24870. * @private
  24871. */
  24872. exports._switchToActiveSector = function(sectorId) {
  24873. this.nodeIndices = this.sectors["active"][sectorId]["nodeIndices"];
  24874. this.nodes = this.sectors["active"][sectorId]["nodes"];
  24875. this.edges = this.sectors["active"][sectorId]["edges"];
  24876. };
  24877. /**
  24878. * This function sets the global references to nodes, edges and nodeIndices back to
  24879. * those of the supplied active sector.
  24880. *
  24881. * @private
  24882. */
  24883. exports._switchToSupportSector = function() {
  24884. this.nodeIndices = this.sectors["support"]["nodeIndices"];
  24885. this.nodes = this.sectors["support"]["nodes"];
  24886. this.edges = this.sectors["support"]["edges"];
  24887. };
  24888. /**
  24889. * This function sets the global references to nodes, edges and nodeIndices back to
  24890. * those of the supplied frozen sector.
  24891. *
  24892. * @param sectorId
  24893. * @private
  24894. */
  24895. exports._switchToFrozenSector = function(sectorId) {
  24896. this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"];
  24897. this.nodes = this.sectors["frozen"][sectorId]["nodes"];
  24898. this.edges = this.sectors["frozen"][sectorId]["edges"];
  24899. };
  24900. /**
  24901. * This function sets the global references to nodes, edges and nodeIndices back to
  24902. * those of the currently active sector.
  24903. *
  24904. * @private
  24905. */
  24906. exports._loadLatestSector = function() {
  24907. this._switchToSector(this._sector());
  24908. };
  24909. /**
  24910. * This function returns the currently active sector Id
  24911. *
  24912. * @returns {String}
  24913. * @private
  24914. */
  24915. exports._sector = function() {
  24916. return this.activeSector[this.activeSector.length-1];
  24917. };
  24918. /**
  24919. * This function returns the previously active sector Id
  24920. *
  24921. * @returns {String}
  24922. * @private
  24923. */
  24924. exports._previousSector = function() {
  24925. if (this.activeSector.length > 1) {
  24926. return this.activeSector[this.activeSector.length-2];
  24927. }
  24928. else {
  24929. throw new TypeError('there are not enough sectors in the this.activeSector array.');
  24930. }
  24931. };
  24932. /**
  24933. * We add the active sector at the end of the this.activeSector array
  24934. * This ensures it is the currently active sector returned by _sector() and it reaches the top
  24935. * of the activeSector stack. When we reverse our steps we move from the end to the beginning of this stack.
  24936. *
  24937. * @param newId
  24938. * @private
  24939. */
  24940. exports._setActiveSector = function(newId) {
  24941. this.activeSector.push(newId);
  24942. };
  24943. /**
  24944. * We remove the currently active sector id from the active sector stack. This happens when
  24945. * we reactivate the previously active sector
  24946. *
  24947. * @private
  24948. */
  24949. exports._forgetLastSector = function() {
  24950. this.activeSector.pop();
  24951. };
  24952. /**
  24953. * This function creates a new active sector with the supplied newId. This newId
  24954. * is the expanding node id.
  24955. *
  24956. * @param {String} newId | Id of the new active sector
  24957. * @private
  24958. */
  24959. exports._createNewSector = function(newId) {
  24960. // create the new sector
  24961. this.sectors["active"][newId] = {"nodes":{},
  24962. "edges":{},
  24963. "nodeIndices":[],
  24964. "formationScale": this.scale,
  24965. "drawingNode": undefined};
  24966. // create the new sector render node. This gives visual feedback that you are in a new sector.
  24967. this.sectors["active"][newId]['drawingNode'] = new Node(
  24968. {id:newId,
  24969. color: {
  24970. background: "#eaefef",
  24971. border: "495c5e"
  24972. }
  24973. },{},{},this.constants);
  24974. this.sectors["active"][newId]['drawingNode'].clusterSize = 2;
  24975. };
  24976. /**
  24977. * This function removes the currently active sector. This is called when we create a new
  24978. * active sector.
  24979. *
  24980. * @param {String} sectorId | Id of the active sector that will be removed
  24981. * @private
  24982. */
  24983. exports._deleteActiveSector = function(sectorId) {
  24984. delete this.sectors["active"][sectorId];
  24985. };
  24986. /**
  24987. * This function removes the currently active sector. This is called when we reactivate
  24988. * the previously active sector.
  24989. *
  24990. * @param {String} sectorId | Id of the active sector that will be removed
  24991. * @private
  24992. */
  24993. exports._deleteFrozenSector = function(sectorId) {
  24994. delete this.sectors["frozen"][sectorId];
  24995. };
  24996. /**
  24997. * Freezing an active sector means moving it from the "active" object to the "frozen" object.
  24998. * We copy the references, then delete the active entree.
  24999. *
  25000. * @param sectorId
  25001. * @private
  25002. */
  25003. exports._freezeSector = function(sectorId) {
  25004. // we move the set references from the active to the frozen stack.
  25005. this.sectors["frozen"][sectorId] = this.sectors["active"][sectorId];
  25006. // we have moved the sector data into the frozen set, we now remove it from the active set
  25007. this._deleteActiveSector(sectorId);
  25008. };
  25009. /**
  25010. * This is the reverse operation of _freezeSector. Activating means moving the sector from the "frozen"
  25011. * object to the "active" object.
  25012. *
  25013. * @param sectorId
  25014. * @private
  25015. */
  25016. exports._activateSector = function(sectorId) {
  25017. // we move the set references from the frozen to the active stack.
  25018. this.sectors["active"][sectorId] = this.sectors["frozen"][sectorId];
  25019. // we have moved the sector data into the active set, we now remove it from the frozen stack
  25020. this._deleteFrozenSector(sectorId);
  25021. };
  25022. /**
  25023. * This function merges the data from the currently active sector with a frozen sector. This is used
  25024. * in the process of reverting back to the previously active sector.
  25025. * The data that is placed in the frozen (the previously active) sector is the node that has been removed from it
  25026. * upon the creation of a new active sector.
  25027. *
  25028. * @param sectorId
  25029. * @private
  25030. */
  25031. exports._mergeThisWithFrozen = function(sectorId) {
  25032. // copy all nodes
  25033. for (var nodeId in this.nodes) {
  25034. if (this.nodes.hasOwnProperty(nodeId)) {
  25035. this.sectors["frozen"][sectorId]["nodes"][nodeId] = this.nodes[nodeId];
  25036. }
  25037. }
  25038. // copy all edges (if not fully clustered, else there are no edges)
  25039. for (var edgeId in this.edges) {
  25040. if (this.edges.hasOwnProperty(edgeId)) {
  25041. this.sectors["frozen"][sectorId]["edges"][edgeId] = this.edges[edgeId];
  25042. }
  25043. }
  25044. // merge the nodeIndices
  25045. for (var i = 0; i < this.nodeIndices.length; i++) {
  25046. this.sectors["frozen"][sectorId]["nodeIndices"].push(this.nodeIndices[i]);
  25047. }
  25048. };
  25049. /**
  25050. * This clusters the sector to one cluster. It was a single cluster before this process started so
  25051. * we revert to that state. The clusterToFit function with a maximum size of 1 node does this.
  25052. *
  25053. * @private
  25054. */
  25055. exports._collapseThisToSingleCluster = function() {
  25056. this.clusterToFit(1,false);
  25057. };
  25058. /**
  25059. * We create a new active sector from the node that we want to open.
  25060. *
  25061. * @param node
  25062. * @private
  25063. */
  25064. exports._addSector = function(node) {
  25065. // this is the currently active sector
  25066. var sector = this._sector();
  25067. // // this should allow me to select nodes from a frozen set.
  25068. // if (this.sectors['active'][sector]["nodes"].hasOwnProperty(node.id)) {
  25069. // console.log("the node is part of the active sector");
  25070. // }
  25071. // else {
  25072. // console.log("I dont know what the fuck happened!!");
  25073. // }
  25074. // when we switch to a new sector, we remove the node that will be expanded from the current nodes list.
  25075. delete this.nodes[node.id];
  25076. var unqiueIdentifier = util.randomUUID();
  25077. // we fully freeze the currently active sector
  25078. this._freezeSector(sector);
  25079. // we create a new active sector. This sector has the Id of the node to ensure uniqueness
  25080. this._createNewSector(unqiueIdentifier);
  25081. // we add the active sector to the sectors array to be able to revert these steps later on
  25082. this._setActiveSector(unqiueIdentifier);
  25083. // we redirect the global references to the new sector's references. this._sector() now returns unqiueIdentifier
  25084. this._switchToSector(this._sector());
  25085. // finally we add the node we removed from our previous active sector to the new active sector
  25086. this.nodes[node.id] = node;
  25087. };
  25088. /**
  25089. * We close the sector that is currently open and revert back to the one before.
  25090. * If the active sector is the "default" sector, nothing happens.
  25091. *
  25092. * @private
  25093. */
  25094. exports._collapseSector = function() {
  25095. // the currently active sector
  25096. var sector = this._sector();
  25097. // we cannot collapse the default sector
  25098. if (sector != "default") {
  25099. if ((this.nodeIndices.length == 1) ||
  25100. (this.sectors["active"][sector]["drawingNode"].width*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  25101. (this.sectors["active"][sector]["drawingNode"].height*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  25102. var previousSector = this._previousSector();
  25103. // we collapse the sector back to a single cluster
  25104. this._collapseThisToSingleCluster();
  25105. // we move the remaining nodes, edges and nodeIndices to the previous sector.
  25106. // This previous sector is the one we will reactivate
  25107. this._mergeThisWithFrozen(previousSector);
  25108. // the previously active (frozen) sector now has all the data from the currently active sector.
  25109. // we can now delete the active sector.
  25110. this._deleteActiveSector(sector);
  25111. // we activate the previously active (and currently frozen) sector.
  25112. this._activateSector(previousSector);
  25113. // we load the references from the newly active sector into the global references
  25114. this._switchToSector(previousSector);
  25115. // we forget the previously active sector because we reverted to the one before
  25116. this._forgetLastSector();
  25117. // finally, we update the node index list.
  25118. this._updateNodeIndexList();
  25119. // we refresh the list with calulation nodes and calculation node indices.
  25120. this._updateCalculationNodes();
  25121. }
  25122. }
  25123. };
  25124. /**
  25125. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  25126. *
  25127. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  25128. * | we dont pass the function itself because then the "this" is the window object
  25129. * | instead of the Network object
  25130. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  25131. * @private
  25132. */
  25133. exports._doInAllActiveSectors = function(runFunction,argument) {
  25134. var returnValues = [];
  25135. if (argument === undefined) {
  25136. for (var sector in this.sectors["active"]) {
  25137. if (this.sectors["active"].hasOwnProperty(sector)) {
  25138. // switch the global references to those of this sector
  25139. this._switchToActiveSector(sector);
  25140. returnValues.push( this[runFunction]() );
  25141. }
  25142. }
  25143. }
  25144. else {
  25145. for (var sector in this.sectors["active"]) {
  25146. if (this.sectors["active"].hasOwnProperty(sector)) {
  25147. // switch the global references to those of this sector
  25148. this._switchToActiveSector(sector);
  25149. var args = Array.prototype.splice.call(arguments, 1);
  25150. if (args.length > 1) {
  25151. returnValues.push( this[runFunction](args[0],args[1]) );
  25152. }
  25153. else {
  25154. returnValues.push( this[runFunction](argument) );
  25155. }
  25156. }
  25157. }
  25158. }
  25159. // we revert the global references back to our active sector
  25160. this._loadLatestSector();
  25161. return returnValues;
  25162. };
  25163. /**
  25164. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  25165. *
  25166. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  25167. * | we dont pass the function itself because then the "this" is the window object
  25168. * | instead of the Network object
  25169. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  25170. * @private
  25171. */
  25172. exports._doInSupportSector = function(runFunction,argument) {
  25173. var returnValues = false;
  25174. if (argument === undefined) {
  25175. this._switchToSupportSector();
  25176. returnValues = this[runFunction]();
  25177. }
  25178. else {
  25179. this._switchToSupportSector();
  25180. var args = Array.prototype.splice.call(arguments, 1);
  25181. if (args.length > 1) {
  25182. returnValues = this[runFunction](args[0],args[1]);
  25183. }
  25184. else {
  25185. returnValues = this[runFunction](argument);
  25186. }
  25187. }
  25188. // we revert the global references back to our active sector
  25189. this._loadLatestSector();
  25190. return returnValues;
  25191. };
  25192. /**
  25193. * This runs a function in all frozen sectors. This is used in the _redraw().
  25194. *
  25195. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  25196. * | we don't pass the function itself because then the "this" is the window object
  25197. * | instead of the Network object
  25198. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  25199. * @private
  25200. */
  25201. exports._doInAllFrozenSectors = function(runFunction,argument) {
  25202. if (argument === undefined) {
  25203. for (var sector in this.sectors["frozen"]) {
  25204. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  25205. // switch the global references to those of this sector
  25206. this._switchToFrozenSector(sector);
  25207. this[runFunction]();
  25208. }
  25209. }
  25210. }
  25211. else {
  25212. for (var sector in this.sectors["frozen"]) {
  25213. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  25214. // switch the global references to those of this sector
  25215. this._switchToFrozenSector(sector);
  25216. var args = Array.prototype.splice.call(arguments, 1);
  25217. if (args.length > 1) {
  25218. this[runFunction](args[0],args[1]);
  25219. }
  25220. else {
  25221. this[runFunction](argument);
  25222. }
  25223. }
  25224. }
  25225. }
  25226. this._loadLatestSector();
  25227. };
  25228. /**
  25229. * This runs a function in all sectors. This is used in the _redraw().
  25230. *
  25231. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  25232. * | we don't pass the function itself because then the "this" is the window object
  25233. * | instead of the Network object
  25234. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  25235. * @private
  25236. */
  25237. exports._doInAllSectors = function(runFunction,argument) {
  25238. var args = Array.prototype.splice.call(arguments, 1);
  25239. if (argument === undefined) {
  25240. this._doInAllActiveSectors(runFunction);
  25241. this._doInAllFrozenSectors(runFunction);
  25242. }
  25243. else {
  25244. if (args.length > 1) {
  25245. this._doInAllActiveSectors(runFunction,args[0],args[1]);
  25246. this._doInAllFrozenSectors(runFunction,args[0],args[1]);
  25247. }
  25248. else {
  25249. this._doInAllActiveSectors(runFunction,argument);
  25250. this._doInAllFrozenSectors(runFunction,argument);
  25251. }
  25252. }
  25253. };
  25254. /**
  25255. * This clears the nodeIndices list. We cannot use this.nodeIndices = [] because we would break the link with the
  25256. * active sector. Thus we clear the nodeIndices in the active sector, then reconnect the this.nodeIndices to it.
  25257. *
  25258. * @private
  25259. */
  25260. exports._clearNodeIndexList = function() {
  25261. var sector = this._sector();
  25262. this.sectors["active"][sector]["nodeIndices"] = [];
  25263. this.nodeIndices = this.sectors["active"][sector]["nodeIndices"];
  25264. };
  25265. /**
  25266. * Draw the encompassing sector node
  25267. *
  25268. * @param ctx
  25269. * @param sectorType
  25270. * @private
  25271. */
  25272. exports._drawSectorNodes = function(ctx,sectorType) {
  25273. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  25274. for (var sector in this.sectors[sectorType]) {
  25275. if (this.sectors[sectorType].hasOwnProperty(sector)) {
  25276. if (this.sectors[sectorType][sector]["drawingNode"] !== undefined) {
  25277. this._switchToSector(sector,sectorType);
  25278. minY = 1e9; maxY = -1e9; minX = 1e9; maxX = -1e9;
  25279. for (var nodeId in this.nodes) {
  25280. if (this.nodes.hasOwnProperty(nodeId)) {
  25281. node = this.nodes[nodeId];
  25282. node.resize(ctx);
  25283. if (minX > node.x - 0.5 * node.width) {minX = node.x - 0.5 * node.width;}
  25284. if (maxX < node.x + 0.5 * node.width) {maxX = node.x + 0.5 * node.width;}
  25285. if (minY > node.y - 0.5 * node.height) {minY = node.y - 0.5 * node.height;}
  25286. if (maxY < node.y + 0.5 * node.height) {maxY = node.y + 0.5 * node.height;}
  25287. }
  25288. }
  25289. node = this.sectors[sectorType][sector]["drawingNode"];
  25290. node.x = 0.5 * (maxX + minX);
  25291. node.y = 0.5 * (maxY + minY);
  25292. node.width = 2 * (node.x - minX);
  25293. node.height = 2 * (node.y - minY);
  25294. node.radius = Math.sqrt(Math.pow(0.5*node.width,2) + Math.pow(0.5*node.height,2));
  25295. node.setScale(this.scale);
  25296. node._drawCircle(ctx);
  25297. }
  25298. }
  25299. }
  25300. };
  25301. exports._drawAllSectorNodes = function(ctx) {
  25302. this._drawSectorNodes(ctx,"frozen");
  25303. this._drawSectorNodes(ctx,"active");
  25304. this._loadLatestSector();
  25305. };
  25306. /***/ },
  25307. /* 56 */
  25308. /***/ function(module, exports, __webpack_require__) {
  25309. var Node = __webpack_require__(37);
  25310. /**
  25311. * This function can be called from the _doInAllSectors function
  25312. *
  25313. * @param object
  25314. * @param overlappingNodes
  25315. * @private
  25316. */
  25317. exports._getNodesOverlappingWith = function(object, overlappingNodes) {
  25318. var nodes = this.nodes;
  25319. for (var nodeId in nodes) {
  25320. if (nodes.hasOwnProperty(nodeId)) {
  25321. if (nodes[nodeId].isOverlappingWith(object)) {
  25322. overlappingNodes.push(nodeId);
  25323. }
  25324. }
  25325. }
  25326. };
  25327. /**
  25328. * retrieve all nodes overlapping with given object
  25329. * @param {Object} object An object with parameters left, top, right, bottom
  25330. * @return {Number[]} An array with id's of the overlapping nodes
  25331. * @private
  25332. */
  25333. exports._getAllNodesOverlappingWith = function (object) {
  25334. var overlappingNodes = [];
  25335. this._doInAllActiveSectors("_getNodesOverlappingWith",object,overlappingNodes);
  25336. return overlappingNodes;
  25337. };
  25338. /**
  25339. * Return a position object in canvasspace from a single point in screenspace
  25340. *
  25341. * @param pointer
  25342. * @returns {{left: number, top: number, right: number, bottom: number}}
  25343. * @private
  25344. */
  25345. exports._pointerToPositionObject = function(pointer) {
  25346. var x = this._XconvertDOMtoCanvas(pointer.x);
  25347. var y = this._YconvertDOMtoCanvas(pointer.y);
  25348. return {
  25349. left: x,
  25350. top: y,
  25351. right: x,
  25352. bottom: y
  25353. };
  25354. };
  25355. /**
  25356. * Get the top node at the a specific point (like a click)
  25357. *
  25358. * @param {{x: Number, y: Number}} pointer
  25359. * @return {Node | null} node
  25360. * @private
  25361. */
  25362. exports._getNodeAt = function (pointer) {
  25363. // we first check if this is an navigation controls element
  25364. var positionObject = this._pointerToPositionObject(pointer);
  25365. var overlappingNodes = this._getAllNodesOverlappingWith(positionObject);
  25366. // if there are overlapping nodes, select the last one, this is the
  25367. // one which is drawn on top of the others
  25368. if (overlappingNodes.length > 0) {
  25369. return this.nodes[overlappingNodes[overlappingNodes.length - 1]];
  25370. }
  25371. else {
  25372. return null;
  25373. }
  25374. };
  25375. /**
  25376. * retrieve all edges overlapping with given object, selector is around center
  25377. * @param {Object} object An object with parameters left, top, right, bottom
  25378. * @return {Number[]} An array with id's of the overlapping nodes
  25379. * @private
  25380. */
  25381. exports._getEdgesOverlappingWith = function (object, overlappingEdges) {
  25382. var edges = this.edges;
  25383. for (var edgeId in edges) {
  25384. if (edges.hasOwnProperty(edgeId)) {
  25385. if (edges[edgeId].isOverlappingWith(object)) {
  25386. overlappingEdges.push(edgeId);
  25387. }
  25388. }
  25389. }
  25390. };
  25391. /**
  25392. * retrieve all nodes overlapping with given object
  25393. * @param {Object} object An object with parameters left, top, right, bottom
  25394. * @return {Number[]} An array with id's of the overlapping nodes
  25395. * @private
  25396. */
  25397. exports._getAllEdgesOverlappingWith = function (object) {
  25398. var overlappingEdges = [];
  25399. this._doInAllActiveSectors("_getEdgesOverlappingWith",object,overlappingEdges);
  25400. return overlappingEdges;
  25401. };
  25402. /**
  25403. * Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call
  25404. * _getNodeAt and _getEdgesAt, then priortize the selection to user preferences.
  25405. *
  25406. * @param pointer
  25407. * @returns {null}
  25408. * @private
  25409. */
  25410. exports._getEdgeAt = function(pointer) {
  25411. var positionObject = this._pointerToPositionObject(pointer);
  25412. var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject);
  25413. if (overlappingEdges.length > 0) {
  25414. return this.edges[overlappingEdges[overlappingEdges.length - 1]];
  25415. }
  25416. else {
  25417. return null;
  25418. }
  25419. };
  25420. /**
  25421. * Add object to the selection array.
  25422. *
  25423. * @param obj
  25424. * @private
  25425. */
  25426. exports._addToSelection = function(obj) {
  25427. if (obj instanceof Node) {
  25428. this.selectionObj.nodes[obj.id] = obj;
  25429. }
  25430. else {
  25431. this.selectionObj.edges[obj.id] = obj;
  25432. }
  25433. };
  25434. /**
  25435. * Add object to the selection array.
  25436. *
  25437. * @param obj
  25438. * @private
  25439. */
  25440. exports._addToHover = function(obj) {
  25441. if (obj instanceof Node) {
  25442. this.hoverObj.nodes[obj.id] = obj;
  25443. }
  25444. else {
  25445. this.hoverObj.edges[obj.id] = obj;
  25446. }
  25447. };
  25448. /**
  25449. * Remove a single option from selection.
  25450. *
  25451. * @param {Object} obj
  25452. * @private
  25453. */
  25454. exports._removeFromSelection = function(obj) {
  25455. if (obj instanceof Node) {
  25456. delete this.selectionObj.nodes[obj.id];
  25457. }
  25458. else {
  25459. delete this.selectionObj.edges[obj.id];
  25460. }
  25461. };
  25462. /**
  25463. * Unselect all. The selectionObj is useful for this.
  25464. *
  25465. * @param {Boolean} [doNotTrigger] | ignore trigger
  25466. * @private
  25467. */
  25468. exports._unselectAll = function(doNotTrigger) {
  25469. if (doNotTrigger === undefined) {
  25470. doNotTrigger = false;
  25471. }
  25472. for(var nodeId in this.selectionObj.nodes) {
  25473. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25474. this.selectionObj.nodes[nodeId].unselect();
  25475. }
  25476. }
  25477. for(var edgeId in this.selectionObj.edges) {
  25478. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25479. this.selectionObj.edges[edgeId].unselect();
  25480. }
  25481. }
  25482. this.selectionObj = {nodes:{},edges:{}};
  25483. if (doNotTrigger == false) {
  25484. this.emit('select', this.getSelection());
  25485. }
  25486. };
  25487. /**
  25488. * Unselect all clusters. The selectionObj is useful for this.
  25489. *
  25490. * @param {Boolean} [doNotTrigger] | ignore trigger
  25491. * @private
  25492. */
  25493. exports._unselectClusters = function(doNotTrigger) {
  25494. if (doNotTrigger === undefined) {
  25495. doNotTrigger = false;
  25496. }
  25497. for (var nodeId in this.selectionObj.nodes) {
  25498. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25499. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  25500. this.selectionObj.nodes[nodeId].unselect();
  25501. this._removeFromSelection(this.selectionObj.nodes[nodeId]);
  25502. }
  25503. }
  25504. }
  25505. if (doNotTrigger == false) {
  25506. this.emit('select', this.getSelection());
  25507. }
  25508. };
  25509. /**
  25510. * return the number of selected nodes
  25511. *
  25512. * @returns {number}
  25513. * @private
  25514. */
  25515. exports._getSelectedNodeCount = function() {
  25516. var count = 0;
  25517. for (var nodeId in this.selectionObj.nodes) {
  25518. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25519. count += 1;
  25520. }
  25521. }
  25522. return count;
  25523. };
  25524. /**
  25525. * return the selected node
  25526. *
  25527. * @returns {number}
  25528. * @private
  25529. */
  25530. exports._getSelectedNode = function() {
  25531. for (var nodeId in this.selectionObj.nodes) {
  25532. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25533. return this.selectionObj.nodes[nodeId];
  25534. }
  25535. }
  25536. return null;
  25537. };
  25538. /**
  25539. * return the selected edge
  25540. *
  25541. * @returns {number}
  25542. * @private
  25543. */
  25544. exports._getSelectedEdge = function() {
  25545. for (var edgeId in this.selectionObj.edges) {
  25546. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25547. return this.selectionObj.edges[edgeId];
  25548. }
  25549. }
  25550. return null;
  25551. };
  25552. /**
  25553. * return the number of selected edges
  25554. *
  25555. * @returns {number}
  25556. * @private
  25557. */
  25558. exports._getSelectedEdgeCount = function() {
  25559. var count = 0;
  25560. for (var edgeId in this.selectionObj.edges) {
  25561. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25562. count += 1;
  25563. }
  25564. }
  25565. return count;
  25566. };
  25567. /**
  25568. * return the number of selected objects.
  25569. *
  25570. * @returns {number}
  25571. * @private
  25572. */
  25573. exports._getSelectedObjectCount = function() {
  25574. var count = 0;
  25575. for(var nodeId in this.selectionObj.nodes) {
  25576. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25577. count += 1;
  25578. }
  25579. }
  25580. for(var edgeId in this.selectionObj.edges) {
  25581. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25582. count += 1;
  25583. }
  25584. }
  25585. return count;
  25586. };
  25587. /**
  25588. * Check if anything is selected
  25589. *
  25590. * @returns {boolean}
  25591. * @private
  25592. */
  25593. exports._selectionIsEmpty = function() {
  25594. for(var nodeId in this.selectionObj.nodes) {
  25595. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25596. return false;
  25597. }
  25598. }
  25599. for(var edgeId in this.selectionObj.edges) {
  25600. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25601. return false;
  25602. }
  25603. }
  25604. return true;
  25605. };
  25606. /**
  25607. * check if one of the selected nodes is a cluster.
  25608. *
  25609. * @returns {boolean}
  25610. * @private
  25611. */
  25612. exports._clusterInSelection = function() {
  25613. for(var nodeId in this.selectionObj.nodes) {
  25614. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25615. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  25616. return true;
  25617. }
  25618. }
  25619. }
  25620. return false;
  25621. };
  25622. /**
  25623. * select the edges connected to the node that is being selected
  25624. *
  25625. * @param {Node} node
  25626. * @private
  25627. */
  25628. exports._selectConnectedEdges = function(node) {
  25629. for (var i = 0; i < node.dynamicEdges.length; i++) {
  25630. var edge = node.dynamicEdges[i];
  25631. edge.select();
  25632. this._addToSelection(edge);
  25633. }
  25634. };
  25635. /**
  25636. * select the edges connected to the node that is being selected
  25637. *
  25638. * @param {Node} node
  25639. * @private
  25640. */
  25641. exports._hoverConnectedEdges = function(node) {
  25642. for (var i = 0; i < node.dynamicEdges.length; i++) {
  25643. var edge = node.dynamicEdges[i];
  25644. edge.hover = true;
  25645. this._addToHover(edge);
  25646. }
  25647. };
  25648. /**
  25649. * unselect the edges connected to the node that is being selected
  25650. *
  25651. * @param {Node} node
  25652. * @private
  25653. */
  25654. exports._unselectConnectedEdges = function(node) {
  25655. for (var i = 0; i < node.dynamicEdges.length; i++) {
  25656. var edge = node.dynamicEdges[i];
  25657. edge.unselect();
  25658. this._removeFromSelection(edge);
  25659. }
  25660. };
  25661. /**
  25662. * This is called when someone clicks on a node. either select or deselect it.
  25663. * If there is an existing selection and we don't want to append to it, clear the existing selection
  25664. *
  25665. * @param {Node || Edge} object
  25666. * @param {Boolean} append
  25667. * @param {Boolean} [doNotTrigger] | ignore trigger
  25668. * @private
  25669. */
  25670. exports._selectObject = function(object, append, doNotTrigger, highlightEdges) {
  25671. if (doNotTrigger === undefined) {
  25672. doNotTrigger = false;
  25673. }
  25674. if (highlightEdges === undefined) {
  25675. highlightEdges = true;
  25676. }
  25677. if (this._selectionIsEmpty() == false && append == false && this.forceAppendSelection == false) {
  25678. this._unselectAll(true);
  25679. }
  25680. if (object.selected == false) {
  25681. object.select();
  25682. this._addToSelection(object);
  25683. if (object instanceof Node && this.blockConnectingEdgeSelection == false && highlightEdges == true) {
  25684. this._selectConnectedEdges(object);
  25685. }
  25686. }
  25687. else {
  25688. object.unselect();
  25689. this._removeFromSelection(object);
  25690. }
  25691. if (doNotTrigger == false) {
  25692. this.emit('select', this.getSelection());
  25693. }
  25694. };
  25695. /**
  25696. * This is called when someone clicks on a node. either select or deselect it.
  25697. * If there is an existing selection and we don't want to append to it, clear the existing selection
  25698. *
  25699. * @param {Node || Edge} object
  25700. * @private
  25701. */
  25702. exports._blurObject = function(object) {
  25703. if (object.hover == true) {
  25704. object.hover = false;
  25705. this.emit("blurNode",{node:object.id});
  25706. }
  25707. };
  25708. /**
  25709. * This is called when someone clicks on a node. either select or deselect it.
  25710. * If there is an existing selection and we don't want to append to it, clear the existing selection
  25711. *
  25712. * @param {Node || Edge} object
  25713. * @private
  25714. */
  25715. exports._hoverObject = function(object) {
  25716. if (object.hover == false) {
  25717. object.hover = true;
  25718. this._addToHover(object);
  25719. if (object instanceof Node) {
  25720. this.emit("hoverNode",{node:object.id});
  25721. }
  25722. }
  25723. if (object instanceof Node) {
  25724. this._hoverConnectedEdges(object);
  25725. }
  25726. };
  25727. /**
  25728. * handles the selection part of the touch, only for navigation controls elements;
  25729. * Touch is triggered before tap, also before hold. Hold triggers after a while.
  25730. * This is the most responsive solution
  25731. *
  25732. * @param {Object} pointer
  25733. * @private
  25734. */
  25735. exports._handleTouch = function(pointer) {
  25736. };
  25737. /**
  25738. * handles the selection part of the tap;
  25739. *
  25740. * @param {Object} pointer
  25741. * @private
  25742. */
  25743. exports._handleTap = function(pointer) {
  25744. var node = this._getNodeAt(pointer);
  25745. if (node != null) {
  25746. this._selectObject(node,false);
  25747. }
  25748. else {
  25749. var edge = this._getEdgeAt(pointer);
  25750. if (edge != null) {
  25751. this._selectObject(edge,false);
  25752. }
  25753. else {
  25754. this._unselectAll();
  25755. }
  25756. }
  25757. this.emit("click", this.getSelection());
  25758. this._redraw();
  25759. };
  25760. /**
  25761. * handles the selection part of the double tap and opens a cluster if needed
  25762. *
  25763. * @param {Object} pointer
  25764. * @private
  25765. */
  25766. exports._handleDoubleTap = function(pointer) {
  25767. var node = this._getNodeAt(pointer);
  25768. if (node != null && node !== undefined) {
  25769. // we reset the areaCenter here so the opening of the node will occur
  25770. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  25771. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  25772. this.openCluster(node);
  25773. }
  25774. this.emit("doubleClick", this.getSelection());
  25775. };
  25776. /**
  25777. * Handle the onHold selection part
  25778. *
  25779. * @param pointer
  25780. * @private
  25781. */
  25782. exports._handleOnHold = function(pointer) {
  25783. var node = this._getNodeAt(pointer);
  25784. if (node != null) {
  25785. this._selectObject(node,true);
  25786. }
  25787. else {
  25788. var edge = this._getEdgeAt(pointer);
  25789. if (edge != null) {
  25790. this._selectObject(edge,true);
  25791. }
  25792. }
  25793. this._redraw();
  25794. };
  25795. /**
  25796. * handle the onRelease event. These functions are here for the navigation controls module.
  25797. *
  25798. * @private
  25799. */
  25800. exports._handleOnRelease = function(pointer) {
  25801. };
  25802. /**
  25803. *
  25804. * retrieve the currently selected objects
  25805. * @return {{nodes: Array.<String>, edges: Array.<String>}} selection
  25806. */
  25807. exports.getSelection = function() {
  25808. var nodeIds = this.getSelectedNodes();
  25809. var edgeIds = this.getSelectedEdges();
  25810. return {nodes:nodeIds, edges:edgeIds};
  25811. };
  25812. /**
  25813. *
  25814. * retrieve the currently selected nodes
  25815. * @return {String[]} selection An array with the ids of the
  25816. * selected nodes.
  25817. */
  25818. exports.getSelectedNodes = function() {
  25819. var idArray = [];
  25820. for(var nodeId in this.selectionObj.nodes) {
  25821. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25822. idArray.push(nodeId);
  25823. }
  25824. }
  25825. return idArray
  25826. };
  25827. /**
  25828. *
  25829. * retrieve the currently selected edges
  25830. * @return {Array} selection An array with the ids of the
  25831. * selected nodes.
  25832. */
  25833. exports.getSelectedEdges = function() {
  25834. var idArray = [];
  25835. for(var edgeId in this.selectionObj.edges) {
  25836. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25837. idArray.push(edgeId);
  25838. }
  25839. }
  25840. return idArray;
  25841. };
  25842. /**
  25843. * select zero or more nodes
  25844. * @param {Number[] | String[]} selection An array with the ids of the
  25845. * selected nodes.
  25846. */
  25847. exports.setSelection = function(selection) {
  25848. var i, iMax, id;
  25849. if (!selection || (selection.length == undefined))
  25850. throw 'Selection must be an array with ids';
  25851. // first unselect any selected node
  25852. this._unselectAll(true);
  25853. for (i = 0, iMax = selection.length; i < iMax; i++) {
  25854. id = selection[i];
  25855. var node = this.nodes[id];
  25856. if (!node) {
  25857. throw new RangeError('Node with id "' + id + '" not found');
  25858. }
  25859. this._selectObject(node,true,true);
  25860. }
  25861. console.log("setSelection is deprecated. Please use selectNodes instead.")
  25862. this.redraw();
  25863. };
  25864. /**
  25865. * select zero or more nodes with the option to highlight edges
  25866. * @param {Number[] | String[]} selection An array with the ids of the
  25867. * selected nodes.
  25868. * @param {boolean} [highlightEdges]
  25869. */
  25870. exports.selectNodes = function(selection, highlightEdges) {
  25871. var i, iMax, id;
  25872. if (!selection || (selection.length == undefined))
  25873. throw 'Selection must be an array with ids';
  25874. // first unselect any selected node
  25875. this._unselectAll(true);
  25876. for (i = 0, iMax = selection.length; i < iMax; i++) {
  25877. id = selection[i];
  25878. var node = this.nodes[id];
  25879. if (!node) {
  25880. throw new RangeError('Node with id "' + id + '" not found');
  25881. }
  25882. this._selectObject(node,true,true,highlightEdges);
  25883. }
  25884. this.redraw();
  25885. };
  25886. /**
  25887. * select zero or more edges
  25888. * @param {Number[] | String[]} selection An array with the ids of the
  25889. * selected nodes.
  25890. */
  25891. exports.selectEdges = function(selection) {
  25892. var i, iMax, id;
  25893. if (!selection || (selection.length == undefined))
  25894. throw 'Selection must be an array with ids';
  25895. // first unselect any selected node
  25896. this._unselectAll(true);
  25897. for (i = 0, iMax = selection.length; i < iMax; i++) {
  25898. id = selection[i];
  25899. var edge = this.edges[id];
  25900. if (!edge) {
  25901. throw new RangeError('Edge with id "' + id + '" not found');
  25902. }
  25903. this._selectObject(edge,true,true,highlightEdges);
  25904. }
  25905. this.redraw();
  25906. };
  25907. /**
  25908. * Validate the selection: remove ids of nodes which no longer exist
  25909. * @private
  25910. */
  25911. exports._updateSelection = function () {
  25912. for(var nodeId in this.selectionObj.nodes) {
  25913. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25914. if (!this.nodes.hasOwnProperty(nodeId)) {
  25915. delete this.selectionObj.nodes[nodeId];
  25916. }
  25917. }
  25918. }
  25919. for(var edgeId in this.selectionObj.edges) {
  25920. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25921. if (!this.edges.hasOwnProperty(edgeId)) {
  25922. delete this.selectionObj.edges[edgeId];
  25923. }
  25924. }
  25925. }
  25926. };
  25927. /***/ },
  25928. /* 57 */
  25929. /***/ function(module, exports, __webpack_require__) {
  25930. var util = __webpack_require__(1);
  25931. var Node = __webpack_require__(37);
  25932. var Edge = __webpack_require__(34);
  25933. /**
  25934. * clears the toolbar div element of children
  25935. *
  25936. * @private
  25937. */
  25938. exports._clearManipulatorBar = function() {
  25939. while (this.manipulationDiv.hasChildNodes()) {
  25940. this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
  25941. }
  25942. };
  25943. /**
  25944. * Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore
  25945. * these functions to their original functionality, we saved them in this.cachedFunctions.
  25946. * This function restores these functions to their original function.
  25947. *
  25948. * @private
  25949. */
  25950. exports._restoreOverloadedFunctions = function() {
  25951. for (var functionName in this.cachedFunctions) {
  25952. if (this.cachedFunctions.hasOwnProperty(functionName)) {
  25953. this[functionName] = this.cachedFunctions[functionName];
  25954. }
  25955. }
  25956. };
  25957. /**
  25958. * Enable or disable edit-mode.
  25959. *
  25960. * @private
  25961. */
  25962. exports._toggleEditMode = function() {
  25963. this.editMode = !this.editMode;
  25964. var toolbar = document.getElementById("network-manipulationDiv");
  25965. var closeDiv = document.getElementById("network-manipulation-closeDiv");
  25966. var editModeDiv = document.getElementById("network-manipulation-editMode");
  25967. if (this.editMode == true) {
  25968. toolbar.style.display="block";
  25969. closeDiv.style.display="block";
  25970. editModeDiv.style.display="none";
  25971. closeDiv.onclick = this._toggleEditMode.bind(this);
  25972. }
  25973. else {
  25974. toolbar.style.display="none";
  25975. closeDiv.style.display="none";
  25976. editModeDiv.style.display="block";
  25977. closeDiv.onclick = null;
  25978. }
  25979. this._createManipulatorBar()
  25980. };
  25981. /**
  25982. * main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
  25983. *
  25984. * @private
  25985. */
  25986. exports._createManipulatorBar = function() {
  25987. // remove bound functions
  25988. if (this.boundFunction) {
  25989. this.off('select', this.boundFunction);
  25990. }
  25991. var locale = this.constants.locales[this.constants.locale];
  25992. if (this.edgeBeingEdited !== undefined) {
  25993. this.edgeBeingEdited._disableControlNodes();
  25994. this.edgeBeingEdited = undefined;
  25995. this.selectedControlNode = null;
  25996. this.controlNodesActive = false;
  25997. }
  25998. // restore overloaded functions
  25999. this._restoreOverloadedFunctions();
  26000. // resume calculation
  26001. this.freezeSimulation = false;
  26002. // reset global variables
  26003. this.blockConnectingEdgeSelection = false;
  26004. this.forceAppendSelection = false;
  26005. if (this.editMode == true) {
  26006. while (this.manipulationDiv.hasChildNodes()) {
  26007. this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
  26008. }
  26009. // add the icons to the manipulator div
  26010. this.manipulationDiv.innerHTML = "" +
  26011. "<span class='network-manipulationUI add' id='network-manipulate-addNode'>" +
  26012. "<span class='network-manipulationLabel'>"+locale['addNode'] +"</span></span>" +
  26013. "<div class='network-seperatorLine'></div>" +
  26014. "<span class='network-manipulationUI connect' id='network-manipulate-connectNode'>" +
  26015. "<span class='network-manipulationLabel'>"+locale['addEdge'] +"</span></span>";
  26016. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  26017. this.manipulationDiv.innerHTML += "" +
  26018. "<div class='network-seperatorLine'></div>" +
  26019. "<span class='network-manipulationUI edit' id='network-manipulate-editNode'>" +
  26020. "<span class='network-manipulationLabel'>"+locale['editNode'] +"</span></span>";
  26021. }
  26022. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  26023. this.manipulationDiv.innerHTML += "" +
  26024. "<div class='network-seperatorLine'></div>" +
  26025. "<span class='network-manipulationUI edit' id='network-manipulate-editEdge'>" +
  26026. "<span class='network-manipulationLabel'>"+locale['editEdge'] +"</span></span>";
  26027. }
  26028. if (this._selectionIsEmpty() == false) {
  26029. this.manipulationDiv.innerHTML += "" +
  26030. "<div class='network-seperatorLine'></div>" +
  26031. "<span class='network-manipulationUI delete' id='network-manipulate-delete'>" +
  26032. "<span class='network-manipulationLabel'>"+locale['del'] +"</span></span>";
  26033. }
  26034. // bind the icons
  26035. var addNodeButton = document.getElementById("network-manipulate-addNode");
  26036. addNodeButton.onclick = this._createAddNodeToolbar.bind(this);
  26037. var addEdgeButton = document.getElementById("network-manipulate-connectNode");
  26038. addEdgeButton.onclick = this._createAddEdgeToolbar.bind(this);
  26039. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  26040. var editButton = document.getElementById("network-manipulate-editNode");
  26041. editButton.onclick = this._editNode.bind(this);
  26042. }
  26043. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  26044. var editButton = document.getElementById("network-manipulate-editEdge");
  26045. editButton.onclick = this._createEditEdgeToolbar.bind(this);
  26046. }
  26047. if (this._selectionIsEmpty() == false) {
  26048. var deleteButton = document.getElementById("network-manipulate-delete");
  26049. deleteButton.onclick = this._deleteSelected.bind(this);
  26050. }
  26051. var closeDiv = document.getElementById("network-manipulation-closeDiv");
  26052. closeDiv.onclick = this._toggleEditMode.bind(this);
  26053. this.boundFunction = this._createManipulatorBar.bind(this);
  26054. this.on('select', this.boundFunction);
  26055. }
  26056. else {
  26057. this.editModeDiv.innerHTML = "" +
  26058. "<span class='network-manipulationUI edit editmode' id='network-manipulate-editModeButton'>" +
  26059. "<span class='network-manipulationLabel'>" + locale['edit'] + "</span></span>";
  26060. var editModeButton = document.getElementById("network-manipulate-editModeButton");
  26061. editModeButton.onclick = this._toggleEditMode.bind(this);
  26062. }
  26063. };
  26064. /**
  26065. * Create the toolbar for adding Nodes
  26066. *
  26067. * @private
  26068. */
  26069. exports._createAddNodeToolbar = function() {
  26070. // clear the toolbar
  26071. this._clearManipulatorBar();
  26072. if (this.boundFunction) {
  26073. this.off('select', this.boundFunction);
  26074. }
  26075. var locale = this.constants.locales[this.constants.locale];
  26076. // create the toolbar contents
  26077. this.manipulationDiv.innerHTML = "" +
  26078. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  26079. "<span class='network-manipulationLabel'>" + locale['back'] + " </span></span>" +
  26080. "<div class='network-seperatorLine'></div>" +
  26081. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  26082. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + locale['addDescription'] + "</span></span>";
  26083. // bind the icon
  26084. var backButton = document.getElementById("network-manipulate-back");
  26085. backButton.onclick = this._createManipulatorBar.bind(this);
  26086. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  26087. this.boundFunction = this._addNode.bind(this);
  26088. this.on('select', this.boundFunction);
  26089. };
  26090. /**
  26091. * create the toolbar to connect nodes
  26092. *
  26093. * @private
  26094. */
  26095. exports._createAddEdgeToolbar = function() {
  26096. // clear the toolbar
  26097. this._clearManipulatorBar();
  26098. this._unselectAll(true);
  26099. this.freezeSimulation = true;
  26100. var locale = this.constants.locales[this.constants.locale];
  26101. if (this.boundFunction) {
  26102. this.off('select', this.boundFunction);
  26103. }
  26104. this._unselectAll();
  26105. this.forceAppendSelection = false;
  26106. this.blockConnectingEdgeSelection = true;
  26107. this.manipulationDiv.innerHTML = "" +
  26108. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  26109. "<span class='network-manipulationLabel'>" + locale['back'] + " </span></span>" +
  26110. "<div class='network-seperatorLine'></div>" +
  26111. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  26112. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + locale['edgeDescription'] + "</span></span>";
  26113. // bind the icon
  26114. var backButton = document.getElementById("network-manipulate-back");
  26115. backButton.onclick = this._createManipulatorBar.bind(this);
  26116. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  26117. this.boundFunction = this._handleConnect.bind(this);
  26118. this.on('select', this.boundFunction);
  26119. // temporarily overload functions
  26120. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  26121. this.cachedFunctions["_handleOnRelease"] = this._handleOnRelease;
  26122. this._handleTouch = this._handleConnect;
  26123. this._handleOnRelease = this._finishConnect;
  26124. // redraw to show the unselect
  26125. this._redraw();
  26126. };
  26127. /**
  26128. * create the toolbar to edit edges
  26129. *
  26130. * @private
  26131. */
  26132. exports._createEditEdgeToolbar = function() {
  26133. // clear the toolbar
  26134. this._clearManipulatorBar();
  26135. this.controlNodesActive = true;
  26136. if (this.boundFunction) {
  26137. this.off('select', this.boundFunction);
  26138. }
  26139. this.edgeBeingEdited = this._getSelectedEdge();
  26140. this.edgeBeingEdited._enableControlNodes();
  26141. var locale = this.constants.locales[this.constants.locale];
  26142. this.manipulationDiv.innerHTML = "" +
  26143. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  26144. "<span class='network-manipulationLabel'>" + locale['back'] + " </span></span>" +
  26145. "<div class='network-seperatorLine'></div>" +
  26146. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  26147. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + locale['editEdgeDescription'] + "</span></span>";
  26148. // bind the icon
  26149. var backButton = document.getElementById("network-manipulate-back");
  26150. backButton.onclick = this._createManipulatorBar.bind(this);
  26151. // temporarily overload functions
  26152. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  26153. this.cachedFunctions["_handleOnRelease"] = this._handleOnRelease;
  26154. this.cachedFunctions["_handleTap"] = this._handleTap;
  26155. this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
  26156. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  26157. this._handleTouch = this._selectControlNode;
  26158. this._handleTap = function () {};
  26159. this._handleOnDrag = this._controlNodeDrag;
  26160. this._handleDragStart = function () {}
  26161. this._handleOnRelease = this._releaseControlNode;
  26162. // redraw to show the unselect
  26163. this._redraw();
  26164. };
  26165. /**
  26166. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  26167. * to walk the user through the process.
  26168. *
  26169. * @private
  26170. */
  26171. exports._selectControlNode = function(pointer) {
  26172. this.edgeBeingEdited.controlNodes.from.unselect();
  26173. this.edgeBeingEdited.controlNodes.to.unselect();
  26174. this.selectedControlNode = this.edgeBeingEdited._getSelectedControlNode(this._XconvertDOMtoCanvas(pointer.x),this._YconvertDOMtoCanvas(pointer.y));
  26175. if (this.selectedControlNode !== null) {
  26176. this.selectedControlNode.select();
  26177. this.freezeSimulation = true;
  26178. }
  26179. this._redraw();
  26180. };
  26181. /**
  26182. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  26183. * to walk the user through the process.
  26184. *
  26185. * @private
  26186. */
  26187. exports._controlNodeDrag = function(event) {
  26188. var pointer = this._getPointer(event.gesture.center);
  26189. if (this.selectedControlNode !== null && this.selectedControlNode !== undefined) {
  26190. this.selectedControlNode.x = this._XconvertDOMtoCanvas(pointer.x);
  26191. this.selectedControlNode.y = this._YconvertDOMtoCanvas(pointer.y);
  26192. }
  26193. this._redraw();
  26194. };
  26195. exports._releaseControlNode = function(pointer) {
  26196. var newNode = this._getNodeAt(pointer);
  26197. if (newNode != null) {
  26198. if (this.edgeBeingEdited.controlNodes.from.selected == true) {
  26199. this._editEdge(newNode.id, this.edgeBeingEdited.to.id);
  26200. this.edgeBeingEdited.controlNodes.from.unselect();
  26201. }
  26202. if (this.edgeBeingEdited.controlNodes.to.selected == true) {
  26203. this._editEdge(this.edgeBeingEdited.from.id, newNode.id);
  26204. this.edgeBeingEdited.controlNodes.to.unselect();
  26205. }
  26206. }
  26207. else {
  26208. this.edgeBeingEdited._restoreControlNodes();
  26209. }
  26210. this.freezeSimulation = false;
  26211. this._redraw();
  26212. };
  26213. /**
  26214. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  26215. * to walk the user through the process.
  26216. *
  26217. * @private
  26218. */
  26219. exports._handleConnect = function(pointer) {
  26220. if (this._getSelectedNodeCount() == 0) {
  26221. var node = this._getNodeAt(pointer);
  26222. var supportNodes, targetNode, targetViaNode, connectionEdge;
  26223. if (node != null) {
  26224. if (node.clusterSize > 1) {
  26225. alert(this.constants.locales[this.constants.locale]['createEdgeError'])
  26226. }
  26227. else {
  26228. this._selectObject(node,false);
  26229. supportNodes = this.sectors['support']['nodes'];
  26230. // create a node the temporary line can look at
  26231. supportNodes['targetNode'] = targetNode = new Node({id:'targetNode'},{},{},this.constants);
  26232. targetNode.x = node.x;
  26233. targetNode.y = node.y;
  26234. supportNodes['targetViaNode'] = targetViaNode = new Node({id:'targetViaNode'},{},{},this.constants);
  26235. targetViaNode.x = node.x;
  26236. targetViaNode.y = node.y;
  26237. targetViaNode.parentEdgeId = "connectionEdge";
  26238. // create a temporary edge
  26239. this.edges['connectionEdge'] = connectionEdge = new Edge({id:"connectionEdge",from:node.id,to:targetNode.id}, this, this.constants);
  26240. connectionEdge.from = node;
  26241. connectionEdge.connected = true;
  26242. connectionEdge.smooth = true;
  26243. connectionEdge.selected = true;
  26244. connectionEdge.to = targetNode;
  26245. connectionEdge.via = targetViaNode;
  26246. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  26247. this._handleOnDrag = function(event) {
  26248. var pointer = this._getPointer(event.gesture.center);
  26249. var supportNodes = this.sectors['support']['nodes'];
  26250. supportNodes['targetNode'].x = this._XconvertDOMtoCanvas(pointer.x);
  26251. supportNodes['targetNode'].y = this._YconvertDOMtoCanvas(pointer.y);
  26252. supportNodes['targetViaNode'].x = 0.5 * (this._XconvertDOMtoCanvas(pointer.x) + this.edges['connectionEdge'].from.x);
  26253. supportNodes['targetViaNode'].y = this._YconvertDOMtoCanvas(pointer.y);
  26254. };
  26255. this.moving = true;
  26256. this.start();
  26257. }
  26258. }
  26259. }
  26260. };
  26261. exports._finishConnect = function(pointer) {
  26262. if (this._getSelectedNodeCount() == 1) {
  26263. // restore the drag function
  26264. this._handleOnDrag = this.cachedFunctions["_handleOnDrag"];
  26265. delete this.cachedFunctions["_handleOnDrag"];
  26266. // remember the edge id
  26267. var connectFromId = this.edges['connectionEdge'].fromId;
  26268. // remove the temporary nodes and edge
  26269. delete this.edges['connectionEdge'];
  26270. delete this.sectors['support']['nodes']['targetNode'];
  26271. delete this.sectors['support']['nodes']['targetViaNode'];
  26272. var node = this._getNodeAt(pointer);
  26273. if (node != null) {
  26274. if (node.clusterSize > 1) {
  26275. alert(this.constants.locales[this.constants.locale]["createEdgeError"])
  26276. }
  26277. else {
  26278. this._createEdge(connectFromId,node.id);
  26279. this._createManipulatorBar();
  26280. }
  26281. }
  26282. this._unselectAll();
  26283. }
  26284. };
  26285. /**
  26286. * Adds a node on the specified location
  26287. */
  26288. exports._addNode = function() {
  26289. if (this._selectionIsEmpty() && this.editMode == true) {
  26290. var positionObject = this._pointerToPositionObject(this.pointerPosition);
  26291. var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",allowedToMoveX:true,allowedToMoveY:true};
  26292. if (this.triggerFunctions.add) {
  26293. if (this.triggerFunctions.add.length == 2) {
  26294. var me = this;
  26295. this.triggerFunctions.add(defaultData, function(finalizedData) {
  26296. me.nodesData.add(finalizedData);
  26297. me._createManipulatorBar();
  26298. me.moving = true;
  26299. me.start();
  26300. });
  26301. }
  26302. else {
  26303. throw new Error('The function for add does not support two arguments (data,callback)');
  26304. this._createManipulatorBar();
  26305. this.moving = true;
  26306. this.start();
  26307. }
  26308. }
  26309. else {
  26310. this.nodesData.add(defaultData);
  26311. this._createManipulatorBar();
  26312. this.moving = true;
  26313. this.start();
  26314. }
  26315. }
  26316. };
  26317. /**
  26318. * connect two nodes with a new edge.
  26319. *
  26320. * @private
  26321. */
  26322. exports._createEdge = function(sourceNodeId,targetNodeId) {
  26323. if (this.editMode == true) {
  26324. var defaultData = {from:sourceNodeId, to:targetNodeId};
  26325. if (this.triggerFunctions.connect) {
  26326. if (this.triggerFunctions.connect.length == 2) {
  26327. var me = this;
  26328. this.triggerFunctions.connect(defaultData, function(finalizedData) {
  26329. me.edgesData.add(finalizedData);
  26330. me.moving = true;
  26331. me.start();
  26332. });
  26333. }
  26334. else {
  26335. throw new Error('The function for connect does not support two arguments (data,callback)');
  26336. this.moving = true;
  26337. this.start();
  26338. }
  26339. }
  26340. else {
  26341. this.edgesData.add(defaultData);
  26342. this.moving = true;
  26343. this.start();
  26344. }
  26345. }
  26346. };
  26347. /**
  26348. * connect two nodes with a new edge.
  26349. *
  26350. * @private
  26351. */
  26352. exports._editEdge = function(sourceNodeId,targetNodeId) {
  26353. if (this.editMode == true) {
  26354. var defaultData = {id: this.edgeBeingEdited.id, from:sourceNodeId, to:targetNodeId};
  26355. if (this.triggerFunctions.editEdge) {
  26356. if (this.triggerFunctions.editEdge.length == 2) {
  26357. var me = this;
  26358. this.triggerFunctions.editEdge(defaultData, function(finalizedData) {
  26359. me.edgesData.update(finalizedData);
  26360. me.moving = true;
  26361. me.start();
  26362. });
  26363. }
  26364. else {
  26365. throw new Error('The function for edit does not support two arguments (data, callback)');
  26366. this.moving = true;
  26367. this.start();
  26368. }
  26369. }
  26370. else {
  26371. this.edgesData.update(defaultData);
  26372. this.moving = true;
  26373. this.start();
  26374. }
  26375. }
  26376. };
  26377. /**
  26378. * Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color.
  26379. *
  26380. * @private
  26381. */
  26382. exports._editNode = function() {
  26383. if (this.triggerFunctions.edit && this.editMode == true) {
  26384. var node = this._getSelectedNode();
  26385. var data = {id:node.id,
  26386. label: node.label,
  26387. group: node.options.group,
  26388. shape: node.options.shape,
  26389. color: {
  26390. background:node.options.color.background,
  26391. border:node.options.color.border,
  26392. highlight: {
  26393. background:node.options.color.highlight.background,
  26394. border:node.options.color.highlight.border
  26395. }
  26396. }};
  26397. if (this.triggerFunctions.edit.length == 2) {
  26398. var me = this;
  26399. this.triggerFunctions.edit(data, function (finalizedData) {
  26400. me.nodesData.update(finalizedData);
  26401. me._createManipulatorBar();
  26402. me.moving = true;
  26403. me.start();
  26404. });
  26405. }
  26406. else {
  26407. throw new Error('The function for edit does not support two arguments (data, callback)');
  26408. }
  26409. }
  26410. else {
  26411. throw new Error('No edit function has been bound to this button');
  26412. }
  26413. };
  26414. /**
  26415. * delete everything in the selection
  26416. *
  26417. * @private
  26418. */
  26419. exports._deleteSelected = function() {
  26420. if (!this._selectionIsEmpty() && this.editMode == true) {
  26421. if (!this._clusterInSelection()) {
  26422. var selectedNodes = this.getSelectedNodes();
  26423. var selectedEdges = this.getSelectedEdges();
  26424. if (this.triggerFunctions.del) {
  26425. var me = this;
  26426. var data = {nodes: selectedNodes, edges: selectedEdges};
  26427. if (this.triggerFunctions.del.length = 2) {
  26428. this.triggerFunctions.del(data, function (finalizedData) {
  26429. me.edgesData.remove(finalizedData.edges);
  26430. me.nodesData.remove(finalizedData.nodes);
  26431. me._unselectAll();
  26432. me.moving = true;
  26433. me.start();
  26434. });
  26435. }
  26436. else {
  26437. throw new Error('The function for delete does not support two arguments (data, callback)')
  26438. }
  26439. }
  26440. else {
  26441. this.edgesData.remove(selectedEdges);
  26442. this.nodesData.remove(selectedNodes);
  26443. this._unselectAll();
  26444. this.moving = true;
  26445. this.start();
  26446. }
  26447. }
  26448. else {
  26449. alert(this.constants.locales[this.constants.locale]["deleteClusterError"]);
  26450. }
  26451. }
  26452. };
  26453. /***/ },
  26454. /* 58 */
  26455. /***/ function(module, exports, __webpack_require__) {
  26456. var util = __webpack_require__(1);
  26457. var Hammer = __webpack_require__(42);
  26458. exports._cleanNavigation = function() {
  26459. // clean up previous navigation items
  26460. var wrapper = document.getElementById('network-navigation_wrapper');
  26461. if (wrapper && wrapper.parentNode) {
  26462. wrapper.parentNode.removeChild(wrapper);
  26463. }
  26464. document.onmouseup = null;
  26465. };
  26466. /**
  26467. * Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation
  26468. * they have a triggerFunction which is called on click. If the position of the navigation controls is dependent
  26469. * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false.
  26470. * This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas.
  26471. *
  26472. * @private
  26473. */
  26474. exports._loadNavigationElements = function() {
  26475. this._cleanNavigation();
  26476. this.navigationDivs = {};
  26477. var navigationDivs = ['up','down','left','right','zoomIn','zoomOut','zoomExtends'];
  26478. var navigationDivActions = ['_moveUp','_moveDown','_moveLeft','_moveRight','_zoomIn','_zoomOut','zoomExtent'];
  26479. this.navigationDivs['wrapper'] = document.createElement('div');
  26480. this.navigationDivs['wrapper'].id = 'network-navigation_wrapper';
  26481. this.frame.appendChild(this.navigationDivs['wrapper']);
  26482. var me = this;
  26483. for (var i = 0; i < navigationDivs.length; i++) {
  26484. this.navigationDivs[navigationDivs[i]] = document.createElement('div');
  26485. this.navigationDivs[navigationDivs[i]].id = 'network-navigation_' + navigationDivs[i];
  26486. this.navigationDivs[navigationDivs[i]].className = 'network-navigation ' + navigationDivs[i];
  26487. this.navigationDivs['wrapper'].appendChild(this.navigationDivs[navigationDivs[i]]);
  26488. var hammer = Hammer(this.navigationDivs[navigationDivs[i]], {prevent_default: true});
  26489. hammer.on('touch', me[navigationDivActions[i]].bind(me));
  26490. }
  26491. var hammer = Hammer(document, {prevent_default: false});
  26492. hammer.on('release', me._stopMovement.bind(me));
  26493. };
  26494. /**
  26495. * this stops all movement induced by the navigation buttons
  26496. *
  26497. * @private
  26498. */
  26499. exports._stopMovement = function() {
  26500. this._xStopMoving();
  26501. this._yStopMoving();
  26502. this._stopZoom();
  26503. };
  26504. /**
  26505. * move the screen up
  26506. * By using the increments, instead of adding a fixed number to the translation, we keep fluent and
  26507. * instant movement. The onKeypress event triggers immediately, then pauses, then triggers frequently
  26508. * To avoid this behaviour, we do the translation in the start loop.
  26509. *
  26510. * @private
  26511. */
  26512. exports._moveUp = function(event) {
  26513. this.yIncrement = this.constants.keyboard.speed.y;
  26514. this.start(); // if there is no node movement, the calculation wont be done
  26515. event.preventDefault();
  26516. };
  26517. /**
  26518. * move the screen down
  26519. * @private
  26520. */
  26521. exports._moveDown = function(event) {
  26522. this.yIncrement = -this.constants.keyboard.speed.y;
  26523. this.start(); // if there is no node movement, the calculation wont be done
  26524. event.preventDefault();
  26525. };
  26526. /**
  26527. * move the screen left
  26528. * @private
  26529. */
  26530. exports._moveLeft = function(event) {
  26531. this.xIncrement = this.constants.keyboard.speed.x;
  26532. this.start(); // if there is no node movement, the calculation wont be done
  26533. event.preventDefault();
  26534. };
  26535. /**
  26536. * move the screen right
  26537. * @private
  26538. */
  26539. exports._moveRight = function(event) {
  26540. this.xIncrement = -this.constants.keyboard.speed.y;
  26541. this.start(); // if there is no node movement, the calculation wont be done
  26542. event.preventDefault();
  26543. };
  26544. /**
  26545. * Zoom in, using the same method as the movement.
  26546. * @private
  26547. */
  26548. exports._zoomIn = function(event) {
  26549. this.zoomIncrement = this.constants.keyboard.speed.zoom;
  26550. this.start(); // if there is no node movement, the calculation wont be done
  26551. event.preventDefault();
  26552. };
  26553. /**
  26554. * Zoom out
  26555. * @private
  26556. */
  26557. exports._zoomOut = function(event) {
  26558. this.zoomIncrement = -this.constants.keyboard.speed.zoom;
  26559. this.start(); // if there is no node movement, the calculation wont be done
  26560. event.preventDefault();
  26561. };
  26562. /**
  26563. * Stop zooming and unhighlight the zoom controls
  26564. * @private
  26565. */
  26566. exports._stopZoom = function(event) {
  26567. this.zoomIncrement = 0;
  26568. event && event.preventDefault();
  26569. };
  26570. /**
  26571. * Stop moving in the Y direction and unHighlight the up and down
  26572. * @private
  26573. */
  26574. exports._yStopMoving = function(event) {
  26575. this.yIncrement = 0;
  26576. event && event.preventDefault();
  26577. };
  26578. /**
  26579. * Stop moving in the X direction and unHighlight left and right.
  26580. * @private
  26581. */
  26582. exports._xStopMoving = function(event) {
  26583. this.xIncrement = 0;
  26584. event && event.preventDefault();
  26585. };
  26586. /***/ },
  26587. /* 59 */
  26588. /***/ function(module, exports, __webpack_require__) {
  26589. exports._resetLevels = function() {
  26590. for (var nodeId in this.nodes) {
  26591. if (this.nodes.hasOwnProperty(nodeId)) {
  26592. var node = this.nodes[nodeId];
  26593. if (node.preassignedLevel == false) {
  26594. node.level = -1;
  26595. node.hierarchyEnumerated = false;
  26596. }
  26597. }
  26598. }
  26599. };
  26600. /**
  26601. * This is the main function to layout the nodes in a hierarchical way.
  26602. * It checks if the node details are supplied correctly
  26603. *
  26604. * @private
  26605. */
  26606. exports._setupHierarchicalLayout = function() {
  26607. if (this.constants.hierarchicalLayout.enabled == true && this.nodeIndices.length > 0) {
  26608. if (this.constants.hierarchicalLayout.direction == "RL" || this.constants.hierarchicalLayout.direction == "DU") {
  26609. this.constants.hierarchicalLayout.levelSeparation *= -1;
  26610. }
  26611. else {
  26612. this.constants.hierarchicalLayout.levelSeparation = Math.abs(this.constants.hierarchicalLayout.levelSeparation);
  26613. }
  26614. if (this.constants.hierarchicalLayout.direction == "RL" || this.constants.hierarchicalLayout.direction == "LR") {
  26615. if (this.constants.smoothCurves.enabled == true) {
  26616. this.constants.smoothCurves.type = "vertical";
  26617. }
  26618. }
  26619. else {
  26620. if (this.constants.smoothCurves.enabled == true) {
  26621. this.constants.smoothCurves.type = "horizontal";
  26622. }
  26623. }
  26624. // get the size of the largest hubs and check if the user has defined a level for a node.
  26625. var hubsize = 0;
  26626. var node, nodeId;
  26627. var definedLevel = false;
  26628. var undefinedLevel = false;
  26629. for (nodeId in this.nodes) {
  26630. if (this.nodes.hasOwnProperty(nodeId)) {
  26631. node = this.nodes[nodeId];
  26632. if (node.level != -1) {
  26633. definedLevel = true;
  26634. }
  26635. else {
  26636. undefinedLevel = true;
  26637. }
  26638. if (hubsize < node.edges.length) {
  26639. hubsize = node.edges.length;
  26640. }
  26641. }
  26642. }
  26643. // if the user defined some levels but not all, alert and run without hierarchical layout
  26644. if (undefinedLevel == true && definedLevel == true) {
  26645. throw new Error("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.");
  26646. this.zoomExtent(undefined,true,this.constants.clustering.enabled);
  26647. if (!this.constants.clustering.enabled) {
  26648. this.start();
  26649. }
  26650. }
  26651. else {
  26652. // setup the system to use hierarchical method.
  26653. this._changeConstants();
  26654. // define levels if undefined by the users. Based on hubsize
  26655. if (undefinedLevel == true) {
  26656. if (this.constants.hierarchicalLayout.layout == "hubsize") {
  26657. this._determineLevels(hubsize);
  26658. }
  26659. else {
  26660. this._determineLevelsDirected();
  26661. }
  26662. }
  26663. // check the distribution of the nodes per level.
  26664. var distribution = this._getDistribution();
  26665. // place the nodes on the canvas. This also stablilizes the system.
  26666. this._placeNodesByHierarchy(distribution);
  26667. // start the simulation.
  26668. this.start();
  26669. }
  26670. }
  26671. };
  26672. /**
  26673. * This function places the nodes on the canvas based on the hierarchial distribution.
  26674. *
  26675. * @param {Object} distribution | obtained by the function this._getDistribution()
  26676. * @private
  26677. */
  26678. exports._placeNodesByHierarchy = function(distribution) {
  26679. var nodeId, node;
  26680. // start placing all the level 0 nodes first. Then recursively position their branches.
  26681. for (var level in distribution) {
  26682. if (distribution.hasOwnProperty(level)) {
  26683. for (nodeId in distribution[level].nodes) {
  26684. if (distribution[level].nodes.hasOwnProperty(nodeId)) {
  26685. node = distribution[level].nodes[nodeId];
  26686. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  26687. if (node.xFixed) {
  26688. node.x = distribution[level].minPos;
  26689. node.xFixed = false;
  26690. distribution[level].minPos += distribution[level].nodeSpacing;
  26691. }
  26692. }
  26693. else {
  26694. if (node.yFixed) {
  26695. node.y = distribution[level].minPos;
  26696. node.yFixed = false;
  26697. distribution[level].minPos += distribution[level].nodeSpacing;
  26698. }
  26699. }
  26700. this._placeBranchNodes(node.edges,node.id,distribution,node.level);
  26701. }
  26702. }
  26703. }
  26704. }
  26705. // stabilize the system after positioning. This function calls zoomExtent.
  26706. this._stabilize();
  26707. };
  26708. /**
  26709. * This function get the distribution of levels based on hubsize
  26710. *
  26711. * @returns {Object}
  26712. * @private
  26713. */
  26714. exports._getDistribution = function() {
  26715. var distribution = {};
  26716. var nodeId, node, level;
  26717. // 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.
  26718. // the fix of X is removed after the x value has been set.
  26719. for (nodeId in this.nodes) {
  26720. if (this.nodes.hasOwnProperty(nodeId)) {
  26721. node = this.nodes[nodeId];
  26722. node.xFixed = true;
  26723. node.yFixed = true;
  26724. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  26725. node.y = this.constants.hierarchicalLayout.levelSeparation*node.level;
  26726. }
  26727. else {
  26728. node.x = this.constants.hierarchicalLayout.levelSeparation*node.level;
  26729. }
  26730. if (distribution[node.level] === undefined) {
  26731. distribution[node.level] = {amount: 0, nodes: {}, minPos:0, nodeSpacing:0};
  26732. }
  26733. distribution[node.level].amount += 1;
  26734. distribution[node.level].nodes[nodeId] = node;
  26735. }
  26736. }
  26737. // determine the largest amount of nodes of all levels
  26738. var maxCount = 0;
  26739. for (level in distribution) {
  26740. if (distribution.hasOwnProperty(level)) {
  26741. if (maxCount < distribution[level].amount) {
  26742. maxCount = distribution[level].amount;
  26743. }
  26744. }
  26745. }
  26746. // set the initial position and spacing of each nodes accordingly
  26747. for (level in distribution) {
  26748. if (distribution.hasOwnProperty(level)) {
  26749. distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing;
  26750. distribution[level].nodeSpacing /= (distribution[level].amount + 1);
  26751. distribution[level].minPos = distribution[level].nodeSpacing - (0.5 * (distribution[level].amount + 1) * distribution[level].nodeSpacing);
  26752. }
  26753. }
  26754. return distribution;
  26755. };
  26756. /**
  26757. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  26758. *
  26759. * @param hubsize
  26760. * @private
  26761. */
  26762. exports._determineLevels = function(hubsize) {
  26763. var nodeId, node;
  26764. // determine hubs
  26765. for (nodeId in this.nodes) {
  26766. if (this.nodes.hasOwnProperty(nodeId)) {
  26767. node = this.nodes[nodeId];
  26768. if (node.edges.length == hubsize) {
  26769. node.level = 0;
  26770. }
  26771. }
  26772. }
  26773. // branch from hubs
  26774. for (nodeId in this.nodes) {
  26775. if (this.nodes.hasOwnProperty(nodeId)) {
  26776. node = this.nodes[nodeId];
  26777. if (node.level == 0) {
  26778. this._setLevel(1,node.edges,node.id);
  26779. }
  26780. }
  26781. }
  26782. };
  26783. /**
  26784. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  26785. *
  26786. * @param hubsize
  26787. * @private
  26788. */
  26789. exports._determineLevelsDirected = function() {
  26790. var nodeId, node;
  26791. // set first node to source
  26792. for (nodeId in this.nodes) {
  26793. if (this.nodes.hasOwnProperty(nodeId)) {
  26794. this.nodes[nodeId].level = 10000;
  26795. break;
  26796. }
  26797. }
  26798. // branch from hubs
  26799. for (nodeId in this.nodes) {
  26800. if (this.nodes.hasOwnProperty(nodeId)) {
  26801. node = this.nodes[nodeId];
  26802. if (node.level == 10000) {
  26803. this._setLevelDirected(10000,node.edges,node.id);
  26804. }
  26805. }
  26806. }
  26807. // branch from hubs
  26808. var minLevel = 10000;
  26809. for (nodeId in this.nodes) {
  26810. if (this.nodes.hasOwnProperty(nodeId)) {
  26811. node = this.nodes[nodeId];
  26812. minLevel = node.level < minLevel ? node.level : minLevel;
  26813. }
  26814. }
  26815. // branch from hubs
  26816. for (nodeId in this.nodes) {
  26817. if (this.nodes.hasOwnProperty(nodeId)) {
  26818. node = this.nodes[nodeId];
  26819. node.level -= minLevel;
  26820. }
  26821. }
  26822. };
  26823. /**
  26824. * Since hierarchical layout does not support:
  26825. * - smooth curves (based on the physics),
  26826. * - clustering (based on dynamic node counts)
  26827. *
  26828. * We disable both features so there will be no problems.
  26829. *
  26830. * @private
  26831. */
  26832. exports._changeConstants = function() {
  26833. this.constants.clustering.enabled = false;
  26834. this.constants.physics.barnesHut.enabled = false;
  26835. this.constants.physics.hierarchicalRepulsion.enabled = true;
  26836. this._loadSelectedForceSolver();
  26837. if (this.constants.smoothCurves.enabled == true) {
  26838. this.constants.smoothCurves.dynamic = false;
  26839. }
  26840. this._configureSmoothCurves();
  26841. };
  26842. /**
  26843. * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
  26844. * on a X position that ensures there will be no overlap.
  26845. *
  26846. * @param edges
  26847. * @param parentId
  26848. * @param distribution
  26849. * @param parentLevel
  26850. * @private
  26851. */
  26852. exports._placeBranchNodes = function(edges, parentId, distribution, parentLevel) {
  26853. for (var i = 0; i < edges.length; i++) {
  26854. var childNode = null;
  26855. if (edges[i].toId == parentId) {
  26856. childNode = edges[i].from;
  26857. }
  26858. else {
  26859. childNode = edges[i].to;
  26860. }
  26861. // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
  26862. var nodeMoved = false;
  26863. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  26864. if (childNode.xFixed && childNode.level > parentLevel) {
  26865. childNode.xFixed = false;
  26866. childNode.x = distribution[childNode.level].minPos;
  26867. nodeMoved = true;
  26868. }
  26869. }
  26870. else {
  26871. if (childNode.yFixed && childNode.level > parentLevel) {
  26872. childNode.yFixed = false;
  26873. childNode.y = distribution[childNode.level].minPos;
  26874. nodeMoved = true;
  26875. }
  26876. }
  26877. if (nodeMoved == true) {
  26878. distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing;
  26879. if (childNode.edges.length > 1) {
  26880. this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level);
  26881. }
  26882. }
  26883. }
  26884. };
  26885. /**
  26886. * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
  26887. *
  26888. * @param level
  26889. * @param edges
  26890. * @param parentId
  26891. * @private
  26892. */
  26893. exports._setLevel = function(level, edges, parentId) {
  26894. for (var i = 0; i < edges.length; i++) {
  26895. var childNode = null;
  26896. if (edges[i].toId == parentId) {
  26897. childNode = edges[i].from;
  26898. }
  26899. else {
  26900. childNode = edges[i].to;
  26901. }
  26902. if (childNode.level == -1 || childNode.level > level) {
  26903. childNode.level = level;
  26904. if (childNode.edges.length > 1) {
  26905. this._setLevel(level+1, childNode.edges, childNode.id);
  26906. }
  26907. }
  26908. }
  26909. };
  26910. /**
  26911. * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
  26912. *
  26913. * @param level
  26914. * @param edges
  26915. * @param parentId
  26916. * @private
  26917. */
  26918. exports._setLevelDirected = function(level, edges, parentId) {
  26919. this.nodes[parentId].hierarchyEnumerated = true;
  26920. for (var i = 0; i < edges.length; i++) {
  26921. var childNode = null;
  26922. var direction = 1;
  26923. if (edges[i].toId == parentId) {
  26924. childNode = edges[i].from;
  26925. direction = -1;
  26926. }
  26927. else {
  26928. childNode = edges[i].to;
  26929. }
  26930. if (childNode.level == -1) {
  26931. childNode.level = level + direction;
  26932. }
  26933. }
  26934. for (var i = 0; i < edges.length; i++) {
  26935. var childNode = null;
  26936. if (edges[i].toId == parentId) {childNode = edges[i].from;}
  26937. else {childNode = edges[i].to;}
  26938. if (childNode.edges.length > 1 && childNode.hierarchyEnumerated === false) {
  26939. this._setLevelDirected(childNode.level, childNode.edges, childNode.id);
  26940. }
  26941. }
  26942. };
  26943. /**
  26944. * Unfix nodes
  26945. *
  26946. * @private
  26947. */
  26948. exports._restoreNodes = function() {
  26949. for (var nodeId in this.nodes) {
  26950. if (this.nodes.hasOwnProperty(nodeId)) {
  26951. this.nodes[nodeId].xFixed = false;
  26952. this.nodes[nodeId].yFixed = false;
  26953. }
  26954. }
  26955. };
  26956. /***/ },
  26957. /* 60 */
  26958. /***/ function(module, exports, __webpack_require__) {
  26959. var util = __webpack_require__(1);
  26960. var RepulsionMixin = __webpack_require__(62);
  26961. var HierarchialRepulsionMixin = __webpack_require__(63);
  26962. var BarnesHutMixin = __webpack_require__(64);
  26963. /**
  26964. * Toggling barnes Hut calculation on and off.
  26965. *
  26966. * @private
  26967. */
  26968. exports._toggleBarnesHut = function () {
  26969. this.constants.physics.barnesHut.enabled = !this.constants.physics.barnesHut.enabled;
  26970. this._loadSelectedForceSolver();
  26971. this.moving = true;
  26972. this.start();
  26973. };
  26974. /**
  26975. * This loads the node force solver based on the barnes hut or repulsion algorithm
  26976. *
  26977. * @private
  26978. */
  26979. exports._loadSelectedForceSolver = function () {
  26980. // this overloads the this._calculateNodeForces
  26981. if (this.constants.physics.barnesHut.enabled == true) {
  26982. this._clearMixin(RepulsionMixin);
  26983. this._clearMixin(HierarchialRepulsionMixin);
  26984. this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity;
  26985. this.constants.physics.springLength = this.constants.physics.barnesHut.springLength;
  26986. this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant;
  26987. this.constants.physics.damping = this.constants.physics.barnesHut.damping;
  26988. this._loadMixin(BarnesHutMixin);
  26989. }
  26990. else if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  26991. this._clearMixin(BarnesHutMixin);
  26992. this._clearMixin(RepulsionMixin);
  26993. this.constants.physics.centralGravity = this.constants.physics.hierarchicalRepulsion.centralGravity;
  26994. this.constants.physics.springLength = this.constants.physics.hierarchicalRepulsion.springLength;
  26995. this.constants.physics.springConstant = this.constants.physics.hierarchicalRepulsion.springConstant;
  26996. this.constants.physics.damping = this.constants.physics.hierarchicalRepulsion.damping;
  26997. this._loadMixin(HierarchialRepulsionMixin);
  26998. }
  26999. else {
  27000. this._clearMixin(BarnesHutMixin);
  27001. this._clearMixin(HierarchialRepulsionMixin);
  27002. this.barnesHutTree = undefined;
  27003. this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity;
  27004. this.constants.physics.springLength = this.constants.physics.repulsion.springLength;
  27005. this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant;
  27006. this.constants.physics.damping = this.constants.physics.repulsion.damping;
  27007. this._loadMixin(RepulsionMixin);
  27008. }
  27009. };
  27010. /**
  27011. * Before calculating the forces, we check if we need to cluster to keep up performance and we check
  27012. * if there is more than one node. If it is just one node, we dont calculate anything.
  27013. *
  27014. * @private
  27015. */
  27016. exports._initializeForceCalculation = function () {
  27017. // stop calculation if there is only one node
  27018. if (this.nodeIndices.length == 1) {
  27019. this.nodes[this.nodeIndices[0]]._setForce(0, 0);
  27020. }
  27021. else {
  27022. // if there are too many nodes on screen, we cluster without repositioning
  27023. if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
  27024. this.clusterToFit(this.constants.clustering.reduceToNodes, false);
  27025. }
  27026. // we now start the force calculation
  27027. this._calculateForces();
  27028. }
  27029. };
  27030. /**
  27031. * Calculate the external forces acting on the nodes
  27032. * Forces are caused by: edges, repulsing forces between nodes, gravity
  27033. * @private
  27034. */
  27035. exports._calculateForces = function () {
  27036. // Gravity is required to keep separated groups from floating off
  27037. // the forces are reset to zero in this loop by using _setForce instead
  27038. // of _addForce
  27039. this._calculateGravitationalForces();
  27040. this._calculateNodeForces();
  27041. if (this.constants.physics.springConstant > 0) {
  27042. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  27043. this._calculateSpringForcesWithSupport();
  27044. }
  27045. else {
  27046. if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  27047. this._calculateHierarchicalSpringForces();
  27048. }
  27049. else {
  27050. this._calculateSpringForces();
  27051. }
  27052. }
  27053. }
  27054. };
  27055. /**
  27056. * Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also
  27057. * handled in the calculateForces function. We then use a quadratic curve with the center node as control.
  27058. * This function joins the datanodes and invisible (called support) nodes into one object.
  27059. * We do this so we do not contaminate this.nodes with the support nodes.
  27060. *
  27061. * @private
  27062. */
  27063. exports._updateCalculationNodes = function () {
  27064. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  27065. this.calculationNodes = {};
  27066. this.calculationNodeIndices = [];
  27067. for (var nodeId in this.nodes) {
  27068. if (this.nodes.hasOwnProperty(nodeId)) {
  27069. this.calculationNodes[nodeId] = this.nodes[nodeId];
  27070. }
  27071. }
  27072. var supportNodes = this.sectors['support']['nodes'];
  27073. for (var supportNodeId in supportNodes) {
  27074. if (supportNodes.hasOwnProperty(supportNodeId)) {
  27075. if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) {
  27076. this.calculationNodes[supportNodeId] = supportNodes[supportNodeId];
  27077. }
  27078. else {
  27079. supportNodes[supportNodeId]._setForce(0, 0);
  27080. }
  27081. }
  27082. }
  27083. for (var idx in this.calculationNodes) {
  27084. if (this.calculationNodes.hasOwnProperty(idx)) {
  27085. this.calculationNodeIndices.push(idx);
  27086. }
  27087. }
  27088. }
  27089. else {
  27090. this.calculationNodes = this.nodes;
  27091. this.calculationNodeIndices = this.nodeIndices;
  27092. }
  27093. };
  27094. /**
  27095. * this function applies the central gravity effect to keep groups from floating off
  27096. *
  27097. * @private
  27098. */
  27099. exports._calculateGravitationalForces = function () {
  27100. var dx, dy, distance, node, i;
  27101. var nodes = this.calculationNodes;
  27102. var gravity = this.constants.physics.centralGravity;
  27103. var gravityForce = 0;
  27104. for (i = 0; i < this.calculationNodeIndices.length; i++) {
  27105. node = nodes[this.calculationNodeIndices[i]];
  27106. node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters.
  27107. // gravity does not apply when we are in a pocket sector
  27108. if (this._sector() == "default" && gravity != 0) {
  27109. dx = -node.x;
  27110. dy = -node.y;
  27111. distance = Math.sqrt(dx * dx + dy * dy);
  27112. gravityForce = (distance == 0) ? 0 : (gravity / distance);
  27113. node.fx = dx * gravityForce;
  27114. node.fy = dy * gravityForce;
  27115. }
  27116. else {
  27117. node.fx = 0;
  27118. node.fy = 0;
  27119. }
  27120. }
  27121. };
  27122. /**
  27123. * this function calculates the effects of the springs in the case of unsmooth curves.
  27124. *
  27125. * @private
  27126. */
  27127. exports._calculateSpringForces = function () {
  27128. var edgeLength, edge, edgeId;
  27129. var dx, dy, fx, fy, springForce, distance;
  27130. var edges = this.edges;
  27131. // forces caused by the edges, modelled as springs
  27132. for (edgeId in edges) {
  27133. if (edges.hasOwnProperty(edgeId)) {
  27134. edge = edges[edgeId];
  27135. if (edge.connected) {
  27136. // only calculate forces if nodes are in the same sector
  27137. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  27138. edgeLength = edge.physics.springLength;
  27139. // this implies that the edges between big clusters are longer
  27140. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  27141. dx = (edge.from.x - edge.to.x);
  27142. dy = (edge.from.y - edge.to.y);
  27143. distance = Math.sqrt(dx * dx + dy * dy);
  27144. if (distance == 0) {
  27145. distance = 0.01;
  27146. }
  27147. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  27148. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  27149. fx = dx * springForce;
  27150. fy = dy * springForce;
  27151. edge.from.fx += fx;
  27152. edge.from.fy += fy;
  27153. edge.to.fx -= fx;
  27154. edge.to.fy -= fy;
  27155. }
  27156. }
  27157. }
  27158. }
  27159. };
  27160. /**
  27161. * This function calculates the springforces on the nodes, accounting for the support nodes.
  27162. *
  27163. * @private
  27164. */
  27165. exports._calculateSpringForcesWithSupport = function () {
  27166. var edgeLength, edge, edgeId, combinedClusterSize;
  27167. var edges = this.edges;
  27168. // forces caused by the edges, modelled as springs
  27169. for (edgeId in edges) {
  27170. if (edges.hasOwnProperty(edgeId)) {
  27171. edge = edges[edgeId];
  27172. if (edge.connected) {
  27173. // only calculate forces if nodes are in the same sector
  27174. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  27175. if (edge.via != null) {
  27176. var node1 = edge.to;
  27177. var node2 = edge.via;
  27178. var node3 = edge.from;
  27179. edgeLength = edge.physics.springLength;
  27180. combinedClusterSize = node1.clusterSize + node3.clusterSize - 2;
  27181. // this implies that the edges between big clusters are longer
  27182. edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth;
  27183. this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
  27184. this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
  27185. }
  27186. }
  27187. }
  27188. }
  27189. }
  27190. };
  27191. /**
  27192. * This is the code actually performing the calculation for the function above. It is split out to avoid repetition.
  27193. *
  27194. * @param node1
  27195. * @param node2
  27196. * @param edgeLength
  27197. * @private
  27198. */
  27199. exports._calculateSpringForce = function (node1, node2, edgeLength) {
  27200. var dx, dy, fx, fy, springForce, distance;
  27201. dx = (node1.x - node2.x);
  27202. dy = (node1.y - node2.y);
  27203. distance = Math.sqrt(dx * dx + dy * dy);
  27204. if (distance == 0) {
  27205. distance = 0.01;
  27206. }
  27207. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  27208. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  27209. fx = dx * springForce;
  27210. fy = dy * springForce;
  27211. node1.fx += fx;
  27212. node1.fy += fy;
  27213. node2.fx -= fx;
  27214. node2.fy -= fy;
  27215. };
  27216. /**
  27217. * Load the HTML for the physics config and bind it
  27218. * @private
  27219. */
  27220. exports._loadPhysicsConfiguration = function () {
  27221. if (this.physicsConfiguration === undefined) {
  27222. this.backupConstants = {};
  27223. util.deepExtend(this.backupConstants,this.constants);
  27224. var hierarchicalLayoutDirections = ["LR", "RL", "UD", "DU"];
  27225. this.physicsConfiguration = document.createElement('div');
  27226. this.physicsConfiguration.className = "PhysicsConfiguration";
  27227. this.physicsConfiguration.innerHTML = '' +
  27228. '<table><tr><td><b>Simulation Mode:</b></td></tr>' +
  27229. '<tr>' +
  27230. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod1" value="BH" checked="checked">Barnes Hut</td>' +
  27231. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod2" value="R">Repulsion</td>' +
  27232. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod3" value="H">Hierarchical</td>' +
  27233. '</tr>' +
  27234. '</table>' +
  27235. '<table id="graph_BH_table" style="display:none">' +
  27236. '<tr><td><b>Barnes Hut</b></td></tr>' +
  27237. '<tr>' +
  27238. '<td width="150px">gravitationalConstant</td><td>0</td><td><input type="range" min="0" max="20000" value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" step="25" style="width:300px" id="graph_BH_gc"></td><td width="50px">-20000</td><td><input value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" id="graph_BH_gc_value" style="width:60px"></td>' +
  27239. '</tr>' +
  27240. '<tr>' +
  27241. '<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3" value="' + this.constants.physics.barnesHut.centralGravity + '" step="0.05" style="width:300px" id="graph_BH_cg"></td><td>3</td><td><input value="' + this.constants.physics.barnesHut.centralGravity + '" id="graph_BH_cg_value" style="width:60px"></td>' +
  27242. '</tr>' +
  27243. '<tr>' +
  27244. '<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>' +
  27245. '</tr>' +
  27246. '<tr>' +
  27247. '<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="0.5" value="' + this.constants.physics.barnesHut.springConstant + '" step="0.001" style="width:300px" id="graph_BH_sc"></td><td>0.5</td><td><input value="' + this.constants.physics.barnesHut.springConstant + '" id="graph_BH_sc_value" style="width:60px"></td>' +
  27248. '</tr>' +
  27249. '<tr>' +
  27250. '<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>' +
  27251. '</tr>' +
  27252. '</table>' +
  27253. '<table id="graph_R_table" style="display:none">' +
  27254. '<tr><td><b>Repulsion</b></td></tr>' +
  27255. '<tr>' +
  27256. '<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>' +
  27257. '</tr>' +
  27258. '<tr>' +
  27259. '<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>' +
  27260. '</tr>' +
  27261. '<tr>' +
  27262. '<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>' +
  27263. '</tr>' +
  27264. '<tr>' +
  27265. '<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>' +
  27266. '</tr>' +
  27267. '<tr>' +
  27268. '<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>' +
  27269. '</tr>' +
  27270. '</table>' +
  27271. '<table id="graph_H_table" style="display:none">' +
  27272. '<tr><td width="150"><b>Hierarchical</b></td></tr>' +
  27273. '<tr>' +
  27274. '<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>' +
  27275. '</tr>' +
  27276. '<tr>' +
  27277. '<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>' +
  27278. '</tr>' +
  27279. '<tr>' +
  27280. '<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>' +
  27281. '</tr>' +
  27282. '<tr>' +
  27283. '<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>' +
  27284. '</tr>' +
  27285. '<tr>' +
  27286. '<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>' +
  27287. '</tr>' +
  27288. '<tr>' +
  27289. '<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>' +
  27290. '</tr>' +
  27291. '<tr>' +
  27292. '<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>' +
  27293. '</tr>' +
  27294. '<tr>' +
  27295. '<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>' +
  27296. '</tr>' +
  27297. '</table>' +
  27298. '<table><tr><td><b>Options:</b></td></tr>' +
  27299. '<tr>' +
  27300. '<td width="180px"><input type="button" id="graph_toggleSmooth" value="Toggle smoothCurves" style="width:150px"></td>' +
  27301. '<td width="180px"><input type="button" id="graph_repositionNodes" value="Reinitialize" style="width:150px"></td>' +
  27302. '<td width="180px"><input type="button" id="graph_generateOptions" value="Generate Options" style="width:150px"></td>' +
  27303. '</tr>' +
  27304. '</table>'
  27305. this.containerElement.parentElement.insertBefore(this.physicsConfiguration, this.containerElement);
  27306. this.optionsDiv = document.createElement("div");
  27307. this.optionsDiv.style.fontSize = "14px";
  27308. this.optionsDiv.style.fontFamily = "verdana";
  27309. this.containerElement.parentElement.insertBefore(this.optionsDiv, this.containerElement);
  27310. var rangeElement;
  27311. rangeElement = document.getElementById('graph_BH_gc');
  27312. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_gc', -1, "physics_barnesHut_gravitationalConstant");
  27313. rangeElement = document.getElementById('graph_BH_cg');
  27314. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_cg', 1, "physics_centralGravity");
  27315. rangeElement = document.getElementById('graph_BH_sc');
  27316. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sc', 1, "physics_springConstant");
  27317. rangeElement = document.getElementById('graph_BH_sl');
  27318. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sl', 1, "physics_springLength");
  27319. rangeElement = document.getElementById('graph_BH_damp');
  27320. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_damp', 1, "physics_damping");
  27321. rangeElement = document.getElementById('graph_R_nd');
  27322. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_nd', 1, "physics_repulsion_nodeDistance");
  27323. rangeElement = document.getElementById('graph_R_cg');
  27324. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_cg', 1, "physics_centralGravity");
  27325. rangeElement = document.getElementById('graph_R_sc');
  27326. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sc', 1, "physics_springConstant");
  27327. rangeElement = document.getElementById('graph_R_sl');
  27328. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sl', 1, "physics_springLength");
  27329. rangeElement = document.getElementById('graph_R_damp');
  27330. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_damp', 1, "physics_damping");
  27331. rangeElement = document.getElementById('graph_H_nd');
  27332. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  27333. rangeElement = document.getElementById('graph_H_cg');
  27334. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_cg', 1, "physics_centralGravity");
  27335. rangeElement = document.getElementById('graph_H_sc');
  27336. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sc', 1, "physics_springConstant");
  27337. rangeElement = document.getElementById('graph_H_sl');
  27338. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sl', 1, "physics_springLength");
  27339. rangeElement = document.getElementById('graph_H_damp');
  27340. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_damp', 1, "physics_damping");
  27341. rangeElement = document.getElementById('graph_H_direction');
  27342. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_direction', hierarchicalLayoutDirections, "hierarchicalLayout_direction");
  27343. rangeElement = document.getElementById('graph_H_levsep');
  27344. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_levsep', 1, "hierarchicalLayout_levelSeparation");
  27345. rangeElement = document.getElementById('graph_H_nspac');
  27346. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nspac', 1, "hierarchicalLayout_nodeSpacing");
  27347. var radioButton1 = document.getElementById("graph_physicsMethod1");
  27348. var radioButton2 = document.getElementById("graph_physicsMethod2");
  27349. var radioButton3 = document.getElementById("graph_physicsMethod3");
  27350. radioButton2.checked = true;
  27351. if (this.constants.physics.barnesHut.enabled) {
  27352. radioButton1.checked = true;
  27353. }
  27354. if (this.constants.hierarchicalLayout.enabled) {
  27355. radioButton3.checked = true;
  27356. }
  27357. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  27358. var graph_repositionNodes = document.getElementById("graph_repositionNodes");
  27359. var graph_generateOptions = document.getElementById("graph_generateOptions");
  27360. graph_toggleSmooth.onclick = graphToggleSmoothCurves.bind(this);
  27361. graph_repositionNodes.onclick = graphRepositionNodes.bind(this);
  27362. graph_generateOptions.onclick = graphGenerateOptions.bind(this);
  27363. if (this.constants.smoothCurves == true && this.constants.dynamicSmoothCurves == false) {
  27364. graph_toggleSmooth.style.background = "#A4FF56";
  27365. }
  27366. else {
  27367. graph_toggleSmooth.style.background = "#FF8532";
  27368. }
  27369. switchConfigurations.apply(this);
  27370. radioButton1.onchange = switchConfigurations.bind(this);
  27371. radioButton2.onchange = switchConfigurations.bind(this);
  27372. radioButton3.onchange = switchConfigurations.bind(this);
  27373. }
  27374. };
  27375. /**
  27376. * This overwrites the this.constants.
  27377. *
  27378. * @param constantsVariableName
  27379. * @param value
  27380. * @private
  27381. */
  27382. exports._overWriteGraphConstants = function (constantsVariableName, value) {
  27383. var nameArray = constantsVariableName.split("_");
  27384. if (nameArray.length == 1) {
  27385. this.constants[nameArray[0]] = value;
  27386. }
  27387. else if (nameArray.length == 2) {
  27388. this.constants[nameArray[0]][nameArray[1]] = value;
  27389. }
  27390. else if (nameArray.length == 3) {
  27391. this.constants[nameArray[0]][nameArray[1]][nameArray[2]] = value;
  27392. }
  27393. };
  27394. /**
  27395. * this function is bound to the toggle smooth curves button. That is also why it is not in the prototype.
  27396. */
  27397. function graphToggleSmoothCurves () {
  27398. this.constants.smoothCurves.enabled = !this.constants.smoothCurves.enabled;
  27399. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  27400. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  27401. else {graph_toggleSmooth.style.background = "#FF8532";}
  27402. this._configureSmoothCurves(false);
  27403. }
  27404. /**
  27405. * this function is used to scramble the nodes
  27406. *
  27407. */
  27408. function graphRepositionNodes () {
  27409. for (var nodeId in this.calculationNodes) {
  27410. if (this.calculationNodes.hasOwnProperty(nodeId)) {
  27411. this.calculationNodes[nodeId].vx = 0; this.calculationNodes[nodeId].vy = 0;
  27412. this.calculationNodes[nodeId].fx = 0; this.calculationNodes[nodeId].fy = 0;
  27413. }
  27414. }
  27415. if (this.constants.hierarchicalLayout.enabled == true) {
  27416. this._setupHierarchicalLayout();
  27417. showValueOfRange.call(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  27418. showValueOfRange.call(this, 'graph_H_cg', 1, "physics_centralGravity");
  27419. showValueOfRange.call(this, 'graph_H_sc', 1, "physics_springConstant");
  27420. showValueOfRange.call(this, 'graph_H_sl', 1, "physics_springLength");
  27421. showValueOfRange.call(this, 'graph_H_damp', 1, "physics_damping");
  27422. }
  27423. else {
  27424. this.repositionNodes();
  27425. }
  27426. this.moving = true;
  27427. this.start();
  27428. }
  27429. /**
  27430. * this is used to generate an options file from the playing with physics system.
  27431. */
  27432. function graphGenerateOptions () {
  27433. var options = "No options are required, default values used.";
  27434. var optionsSpecific = [];
  27435. var radioButton1 = document.getElementById("graph_physicsMethod1");
  27436. var radioButton2 = document.getElementById("graph_physicsMethod2");
  27437. if (radioButton1.checked == true) {
  27438. if (this.constants.physics.barnesHut.gravitationalConstant != this.backupConstants.physics.barnesHut.gravitationalConstant) {optionsSpecific.push("gravitationalConstant: " + this.constants.physics.barnesHut.gravitationalConstant);}
  27439. if (this.constants.physics.centralGravity != this.backupConstants.physics.barnesHut.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  27440. if (this.constants.physics.springLength != this.backupConstants.physics.barnesHut.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  27441. if (this.constants.physics.springConstant != this.backupConstants.physics.barnesHut.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  27442. if (this.constants.physics.damping != this.backupConstants.physics.barnesHut.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  27443. if (optionsSpecific.length != 0) {
  27444. options = "var options = {";
  27445. options += "physics: {barnesHut: {";
  27446. for (var i = 0; i < optionsSpecific.length; i++) {
  27447. options += optionsSpecific[i];
  27448. if (i < optionsSpecific.length - 1) {
  27449. options += ", "
  27450. }
  27451. }
  27452. options += '}}'
  27453. }
  27454. if (this.constants.smoothCurves.enabled != this.backupConstants.smoothCurves.enabled) {
  27455. if (optionsSpecific.length == 0) {options = "var options = {";}
  27456. else {options += ", "}
  27457. options += "smoothCurves: " + this.constants.smoothCurves.enabled;
  27458. }
  27459. if (options != "No options are required, default values used.") {
  27460. options += '};'
  27461. }
  27462. }
  27463. else if (radioButton2.checked == true) {
  27464. options = "var options = {";
  27465. options += "physics: {barnesHut: {enabled: false}";
  27466. if (this.constants.physics.repulsion.nodeDistance != this.backupConstants.physics.repulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.repulsion.nodeDistance);}
  27467. if (this.constants.physics.centralGravity != this.backupConstants.physics.repulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  27468. if (this.constants.physics.springLength != this.backupConstants.physics.repulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  27469. if (this.constants.physics.springConstant != this.backupConstants.physics.repulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  27470. if (this.constants.physics.damping != this.backupConstants.physics.repulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  27471. if (optionsSpecific.length != 0) {
  27472. options += ", repulsion: {";
  27473. for (var i = 0; i < optionsSpecific.length; i++) {
  27474. options += optionsSpecific[i];
  27475. if (i < optionsSpecific.length - 1) {
  27476. options += ", "
  27477. }
  27478. }
  27479. options += '}}'
  27480. }
  27481. if (optionsSpecific.length == 0) {options += "}"}
  27482. if (this.constants.smoothCurves != this.backupConstants.smoothCurves) {
  27483. options += ", smoothCurves: " + this.constants.smoothCurves;
  27484. }
  27485. options += '};'
  27486. }
  27487. else {
  27488. options = "var options = {";
  27489. if (this.constants.physics.hierarchicalRepulsion.nodeDistance != this.backupConstants.physics.hierarchicalRepulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.hierarchicalRepulsion.nodeDistance);}
  27490. if (this.constants.physics.centralGravity != this.backupConstants.physics.hierarchicalRepulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  27491. if (this.constants.physics.springLength != this.backupConstants.physics.hierarchicalRepulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  27492. if (this.constants.physics.springConstant != this.backupConstants.physics.hierarchicalRepulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  27493. if (this.constants.physics.damping != this.backupConstants.physics.hierarchicalRepulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  27494. if (optionsSpecific.length != 0) {
  27495. options += "physics: {hierarchicalRepulsion: {";
  27496. for (var i = 0; i < optionsSpecific.length; i++) {
  27497. options += optionsSpecific[i];
  27498. if (i < optionsSpecific.length - 1) {
  27499. options += ", ";
  27500. }
  27501. }
  27502. options += '}},';
  27503. }
  27504. options += 'hierarchicalLayout: {';
  27505. optionsSpecific = [];
  27506. if (this.constants.hierarchicalLayout.direction != this.backupConstants.hierarchicalLayout.direction) {optionsSpecific.push("direction: " + this.constants.hierarchicalLayout.direction);}
  27507. if (Math.abs(this.constants.hierarchicalLayout.levelSeparation) != this.backupConstants.hierarchicalLayout.levelSeparation) {optionsSpecific.push("levelSeparation: " + this.constants.hierarchicalLayout.levelSeparation);}
  27508. if (this.constants.hierarchicalLayout.nodeSpacing != this.backupConstants.hierarchicalLayout.nodeSpacing) {optionsSpecific.push("nodeSpacing: " + this.constants.hierarchicalLayout.nodeSpacing);}
  27509. if (optionsSpecific.length != 0) {
  27510. for (var i = 0; i < optionsSpecific.length; i++) {
  27511. options += optionsSpecific[i];
  27512. if (i < optionsSpecific.length - 1) {
  27513. options += ", "
  27514. }
  27515. }
  27516. options += '}'
  27517. }
  27518. else {
  27519. options += "enabled:true}";
  27520. }
  27521. options += '};'
  27522. }
  27523. this.optionsDiv.innerHTML = options;
  27524. }
  27525. /**
  27526. * this is used to switch between barnesHut, repulsion and hierarchical.
  27527. *
  27528. */
  27529. function switchConfigurations () {
  27530. var ids = ["graph_BH_table", "graph_R_table", "graph_H_table"];
  27531. var radioButton = document.querySelector('input[name="graph_physicsMethod"]:checked').value;
  27532. var tableId = "graph_" + radioButton + "_table";
  27533. var table = document.getElementById(tableId);
  27534. table.style.display = "block";
  27535. for (var i = 0; i < ids.length; i++) {
  27536. if (ids[i] != tableId) {
  27537. table = document.getElementById(ids[i]);
  27538. table.style.display = "none";
  27539. }
  27540. }
  27541. this._restoreNodes();
  27542. if (radioButton == "R") {
  27543. this.constants.hierarchicalLayout.enabled = false;
  27544. this.constants.physics.hierarchicalRepulsion.enabled = false;
  27545. this.constants.physics.barnesHut.enabled = false;
  27546. }
  27547. else if (radioButton == "H") {
  27548. if (this.constants.hierarchicalLayout.enabled == false) {
  27549. this.constants.hierarchicalLayout.enabled = true;
  27550. this.constants.physics.hierarchicalRepulsion.enabled = true;
  27551. this.constants.physics.barnesHut.enabled = false;
  27552. this.constants.smoothCurves.enabled = false;
  27553. this._setupHierarchicalLayout();
  27554. }
  27555. }
  27556. else {
  27557. this.constants.hierarchicalLayout.enabled = false;
  27558. this.constants.physics.hierarchicalRepulsion.enabled = false;
  27559. this.constants.physics.barnesHut.enabled = true;
  27560. }
  27561. this._loadSelectedForceSolver();
  27562. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  27563. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  27564. else {graph_toggleSmooth.style.background = "#FF8532";}
  27565. this.moving = true;
  27566. this.start();
  27567. }
  27568. /**
  27569. * this generates the ranges depending on the iniital values.
  27570. *
  27571. * @param id
  27572. * @param map
  27573. * @param constantsVariableName
  27574. */
  27575. function showValueOfRange (id,map,constantsVariableName) {
  27576. var valueId = id + "_value";
  27577. var rangeValue = document.getElementById(id).value;
  27578. if (map instanceof Array) {
  27579. document.getElementById(valueId).value = map[parseInt(rangeValue)];
  27580. this._overWriteGraphConstants(constantsVariableName,map[parseInt(rangeValue)]);
  27581. }
  27582. else {
  27583. document.getElementById(valueId).value = parseInt(map) * parseFloat(rangeValue);
  27584. this._overWriteGraphConstants(constantsVariableName, parseInt(map) * parseFloat(rangeValue));
  27585. }
  27586. if (constantsVariableName == "hierarchicalLayout_direction" ||
  27587. constantsVariableName == "hierarchicalLayout_levelSeparation" ||
  27588. constantsVariableName == "hierarchicalLayout_nodeSpacing") {
  27589. this._setupHierarchicalLayout();
  27590. }
  27591. this.moving = true;
  27592. this.start();
  27593. }
  27594. /***/ },
  27595. /* 61 */
  27596. /***/ function(module, exports, __webpack_require__) {
  27597. function webpackContext(req) {
  27598. throw new Error("Cannot find module '" + req + "'.");
  27599. }
  27600. webpackContext.resolve = webpackContext;
  27601. webpackContext.keys = function() { return []; };
  27602. module.exports = webpackContext;
  27603. /***/ },
  27604. /* 62 */
  27605. /***/ function(module, exports, __webpack_require__) {
  27606. /**
  27607. * Calculate the forces the nodes apply on each other based on a repulsion field.
  27608. * This field is linearly approximated.
  27609. *
  27610. * @private
  27611. */
  27612. exports._calculateNodeForces = function () {
  27613. var dx, dy, angle, distance, fx, fy, combinedClusterSize,
  27614. repulsingForce, node1, node2, i, j;
  27615. var nodes = this.calculationNodes;
  27616. var nodeIndices = this.calculationNodeIndices;
  27617. // approximation constants
  27618. var a_base = -2 / 3;
  27619. var b = 4 / 3;
  27620. // repulsing forces between nodes
  27621. var nodeDistance = this.constants.physics.repulsion.nodeDistance;
  27622. var minimumDistance = nodeDistance;
  27623. // we loop from i over all but the last entree in the array
  27624. // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  27625. for (i = 0; i < nodeIndices.length - 1; i++) {
  27626. node1 = nodes[nodeIndices[i]];
  27627. for (j = i + 1; j < nodeIndices.length; j++) {
  27628. node2 = nodes[nodeIndices[j]];
  27629. combinedClusterSize = node1.clusterSize + node2.clusterSize - 2;
  27630. dx = node2.x - node1.x;
  27631. dy = node2.y - node1.y;
  27632. distance = Math.sqrt(dx * dx + dy * dy);
  27633. minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification));
  27634. var a = a_base / minimumDistance;
  27635. if (distance < 2 * minimumDistance) {
  27636. if (distance < 0.5 * minimumDistance) {
  27637. repulsingForce = 1.0;
  27638. }
  27639. else {
  27640. repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness))
  27641. }
  27642. // amplify the repulsion for clusters.
  27643. repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification;
  27644. repulsingForce = repulsingForce / distance;
  27645. fx = dx * repulsingForce;
  27646. fy = dy * repulsingForce;
  27647. node1.fx -= fx;
  27648. node1.fy -= fy;
  27649. node2.fx += fx;
  27650. node2.fy += fy;
  27651. }
  27652. }
  27653. }
  27654. };
  27655. /***/ },
  27656. /* 63 */
  27657. /***/ function(module, exports, __webpack_require__) {
  27658. /**
  27659. * Calculate the forces the nodes apply on eachother based on a repulsion field.
  27660. * This field is linearly approximated.
  27661. *
  27662. * @private
  27663. */
  27664. exports._calculateNodeForces = function () {
  27665. var dx, dy, distance, fx, fy,
  27666. repulsingForce, node1, node2, i, j;
  27667. var nodes = this.calculationNodes;
  27668. var nodeIndices = this.calculationNodeIndices;
  27669. // repulsing forces between nodes
  27670. var nodeDistance = this.constants.physics.hierarchicalRepulsion.nodeDistance;
  27671. // we loop from i over all but the last entree in the array
  27672. // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  27673. for (i = 0; i < nodeIndices.length - 1; i++) {
  27674. node1 = nodes[nodeIndices[i]];
  27675. for (j = i + 1; j < nodeIndices.length; j++) {
  27676. node2 = nodes[nodeIndices[j]];
  27677. // nodes only affect nodes on their level
  27678. if (node1.level == node2.level) {
  27679. dx = node2.x - node1.x;
  27680. dy = node2.y - node1.y;
  27681. distance = Math.sqrt(dx * dx + dy * dy);
  27682. var steepness = 0.05;
  27683. if (distance < nodeDistance) {
  27684. repulsingForce = -Math.pow(steepness*distance,2) + Math.pow(steepness*nodeDistance,2);
  27685. }
  27686. else {
  27687. repulsingForce = 0;
  27688. }
  27689. // normalize force with
  27690. if (distance == 0) {
  27691. distance = 0.01;
  27692. }
  27693. else {
  27694. repulsingForce = repulsingForce / distance;
  27695. }
  27696. fx = dx * repulsingForce;
  27697. fy = dy * repulsingForce;
  27698. node1.fx -= fx;
  27699. node1.fy -= fy;
  27700. node2.fx += fx;
  27701. node2.fy += fy;
  27702. }
  27703. }
  27704. }
  27705. };
  27706. /**
  27707. * this function calculates the effects of the springs in the case of unsmooth curves.
  27708. *
  27709. * @private
  27710. */
  27711. exports._calculateHierarchicalSpringForces = function () {
  27712. var edgeLength, edge, edgeId;
  27713. var dx, dy, fx, fy, springForce, distance;
  27714. var edges = this.edges;
  27715. var nodes = this.calculationNodes;
  27716. var nodeIndices = this.calculationNodeIndices;
  27717. for (var i = 0; i < nodeIndices.length; i++) {
  27718. var node1 = nodes[nodeIndices[i]];
  27719. node1.springFx = 0;
  27720. node1.springFy = 0;
  27721. }
  27722. // forces caused by the edges, modelled as springs
  27723. for (edgeId in edges) {
  27724. if (edges.hasOwnProperty(edgeId)) {
  27725. edge = edges[edgeId];
  27726. if (edge.connected) {
  27727. // only calculate forces if nodes are in the same sector
  27728. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  27729. edgeLength = edge.physics.springLength;
  27730. // this implies that the edges between big clusters are longer
  27731. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  27732. dx = (edge.from.x - edge.to.x);
  27733. dy = (edge.from.y - edge.to.y);
  27734. distance = Math.sqrt(dx * dx + dy * dy);
  27735. if (distance == 0) {
  27736. distance = 0.01;
  27737. }
  27738. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  27739. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  27740. fx = dx * springForce;
  27741. fy = dy * springForce;
  27742. if (edge.to.level != edge.from.level) {
  27743. edge.to.springFx -= fx;
  27744. edge.to.springFy -= fy;
  27745. edge.from.springFx += fx;
  27746. edge.from.springFy += fy;
  27747. }
  27748. else {
  27749. var factor = 0.5;
  27750. edge.to.fx -= factor*fx;
  27751. edge.to.fy -= factor*fy;
  27752. edge.from.fx += factor*fx;
  27753. edge.from.fy += factor*fy;
  27754. }
  27755. }
  27756. }
  27757. }
  27758. }
  27759. // normalize spring forces
  27760. var springForce = 1;
  27761. var springFx, springFy;
  27762. for (i = 0; i < nodeIndices.length; i++) {
  27763. var node = nodes[nodeIndices[i]];
  27764. springFx = Math.min(springForce,Math.max(-springForce,node.springFx));
  27765. springFy = Math.min(springForce,Math.max(-springForce,node.springFy));
  27766. node.fx += springFx;
  27767. node.fy += springFy;
  27768. }
  27769. // retain energy balance
  27770. var totalFx = 0;
  27771. var totalFy = 0;
  27772. for (i = 0; i < nodeIndices.length; i++) {
  27773. var node = nodes[nodeIndices[i]];
  27774. totalFx += node.fx;
  27775. totalFy += node.fy;
  27776. }
  27777. var correctionFx = totalFx / nodeIndices.length;
  27778. var correctionFy = totalFy / nodeIndices.length;
  27779. for (i = 0; i < nodeIndices.length; i++) {
  27780. var node = nodes[nodeIndices[i]];
  27781. node.fx -= correctionFx;
  27782. node.fy -= correctionFy;
  27783. }
  27784. };
  27785. /***/ },
  27786. /* 64 */
  27787. /***/ function(module, exports, __webpack_require__) {
  27788. /**
  27789. * This function calculates the forces the nodes apply on eachother based on a gravitational model.
  27790. * The Barnes Hut method is used to speed up this N-body simulation.
  27791. *
  27792. * @private
  27793. */
  27794. exports._calculateNodeForces = function() {
  27795. if (this.constants.physics.barnesHut.gravitationalConstant != 0) {
  27796. var node;
  27797. var nodes = this.calculationNodes;
  27798. var nodeIndices = this.calculationNodeIndices;
  27799. var nodeCount = nodeIndices.length;
  27800. this._formBarnesHutTree(nodes,nodeIndices);
  27801. var barnesHutTree = this.barnesHutTree;
  27802. // place the nodes one by one recursively
  27803. for (var i = 0; i < nodeCount; i++) {
  27804. node = nodes[nodeIndices[i]];
  27805. if (node.options.mass > 0) {
  27806. // starting with root is irrelevant, it never passes the BarnesHut condition
  27807. this._getForceContribution(barnesHutTree.root.children.NW,node);
  27808. this._getForceContribution(barnesHutTree.root.children.NE,node);
  27809. this._getForceContribution(barnesHutTree.root.children.SW,node);
  27810. this._getForceContribution(barnesHutTree.root.children.SE,node);
  27811. }
  27812. }
  27813. }
  27814. };
  27815. /**
  27816. * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass.
  27817. * If a region contains a single node, we check if it is not itself, then we apply the force.
  27818. *
  27819. * @param parentBranch
  27820. * @param node
  27821. * @private
  27822. */
  27823. exports._getForceContribution = function(parentBranch,node) {
  27824. // we get no force contribution from an empty region
  27825. if (parentBranch.childrenCount > 0) {
  27826. var dx,dy,distance;
  27827. // get the distance from the center of mass to the node.
  27828. dx = parentBranch.centerOfMass.x - node.x;
  27829. dy = parentBranch.centerOfMass.y - node.y;
  27830. distance = Math.sqrt(dx * dx + dy * dy);
  27831. // BarnesHut condition
  27832. // original condition : s/d < theta = passed === d/s > 1/theta = passed
  27833. // calcSize = 1/s --> d * 1/s > 1/theta = passed
  27834. if (distance * parentBranch.calcSize > this.constants.physics.barnesHut.theta) {
  27835. // duplicate code to reduce function calls to speed up program
  27836. if (distance == 0) {
  27837. distance = 0.1*Math.random();
  27838. dx = distance;
  27839. }
  27840. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
  27841. var fx = dx * gravityForce;
  27842. var fy = dy * gravityForce;
  27843. node.fx += fx;
  27844. node.fy += fy;
  27845. }
  27846. else {
  27847. // Did not pass the condition, go into children if available
  27848. if (parentBranch.childrenCount == 4) {
  27849. this._getForceContribution(parentBranch.children.NW,node);
  27850. this._getForceContribution(parentBranch.children.NE,node);
  27851. this._getForceContribution(parentBranch.children.SW,node);
  27852. this._getForceContribution(parentBranch.children.SE,node);
  27853. }
  27854. else { // parentBranch must have only one node, if it was empty we wouldnt be here
  27855. if (parentBranch.children.data.id != node.id) { // if it is not self
  27856. // duplicate code to reduce function calls to speed up program
  27857. if (distance == 0) {
  27858. distance = 0.5*Math.random();
  27859. dx = distance;
  27860. }
  27861. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
  27862. var fx = dx * gravityForce;
  27863. var fy = dy * gravityForce;
  27864. node.fx += fx;
  27865. node.fy += fy;
  27866. }
  27867. }
  27868. }
  27869. }
  27870. };
  27871. /**
  27872. * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes.
  27873. *
  27874. * @param nodes
  27875. * @param nodeIndices
  27876. * @private
  27877. */
  27878. exports._formBarnesHutTree = function(nodes,nodeIndices) {
  27879. var node;
  27880. var nodeCount = nodeIndices.length;
  27881. var minX = Number.MAX_VALUE,
  27882. minY = Number.MAX_VALUE,
  27883. maxX =-Number.MAX_VALUE,
  27884. maxY =-Number.MAX_VALUE;
  27885. // get the range of the nodes
  27886. for (var i = 0; i < nodeCount; i++) {
  27887. var x = nodes[nodeIndices[i]].x;
  27888. var y = nodes[nodeIndices[i]].y;
  27889. if (nodes[nodeIndices[i]].options.mass > 0) {
  27890. if (x < minX) { minX = x; }
  27891. if (x > maxX) { maxX = x; }
  27892. if (y < minY) { minY = y; }
  27893. if (y > maxY) { maxY = y; }
  27894. }
  27895. }
  27896. // make the range a square
  27897. var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
  27898. if (sizeDiff > 0) {minY -= 0.5 * sizeDiff; maxY += 0.5 * sizeDiff;} // xSize > ySize
  27899. else {minX += 0.5 * sizeDiff; maxX -= 0.5 * sizeDiff;} // xSize < ySize
  27900. var minimumTreeSize = 1e-5;
  27901. var rootSize = Math.max(minimumTreeSize,Math.abs(maxX - minX));
  27902. var halfRootSize = 0.5 * rootSize;
  27903. var centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY);
  27904. // construct the barnesHutTree
  27905. var barnesHutTree = {
  27906. root:{
  27907. centerOfMass: {x:0, y:0},
  27908. mass:0,
  27909. range: {
  27910. minX: centerX-halfRootSize,maxX:centerX+halfRootSize,
  27911. minY: centerY-halfRootSize,maxY:centerY+halfRootSize
  27912. },
  27913. size: rootSize,
  27914. calcSize: 1 / rootSize,
  27915. children: { data:null},
  27916. maxWidth: 0,
  27917. level: 0,
  27918. childrenCount: 4
  27919. }
  27920. };
  27921. this._splitBranch(barnesHutTree.root);
  27922. // place the nodes one by one recursively
  27923. for (i = 0; i < nodeCount; i++) {
  27924. node = nodes[nodeIndices[i]];
  27925. if (node.options.mass > 0) {
  27926. this._placeInTree(barnesHutTree.root,node);
  27927. }
  27928. }
  27929. // make global
  27930. this.barnesHutTree = barnesHutTree
  27931. };
  27932. /**
  27933. * this updates the mass of a branch. this is increased by adding a node.
  27934. *
  27935. * @param parentBranch
  27936. * @param node
  27937. * @private
  27938. */
  27939. exports._updateBranchMass = function(parentBranch, node) {
  27940. var totalMass = parentBranch.mass + node.options.mass;
  27941. var totalMassInv = 1/totalMass;
  27942. parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass;
  27943. parentBranch.centerOfMass.x *= totalMassInv;
  27944. parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass;
  27945. parentBranch.centerOfMass.y *= totalMassInv;
  27946. parentBranch.mass = totalMass;
  27947. var biggestSize = Math.max(Math.max(node.height,node.radius),node.width);
  27948. parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth;
  27949. };
  27950. /**
  27951. * determine in which branch the node will be placed.
  27952. *
  27953. * @param parentBranch
  27954. * @param node
  27955. * @param skipMassUpdate
  27956. * @private
  27957. */
  27958. exports._placeInTree = function(parentBranch,node,skipMassUpdate) {
  27959. if (skipMassUpdate != true || skipMassUpdate === undefined) {
  27960. // update the mass of the branch.
  27961. this._updateBranchMass(parentBranch,node);
  27962. }
  27963. if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW
  27964. if (parentBranch.children.NW.range.maxY > node.y) { // in NW
  27965. this._placeInRegion(parentBranch,node,"NW");
  27966. }
  27967. else { // in SW
  27968. this._placeInRegion(parentBranch,node,"SW");
  27969. }
  27970. }
  27971. else { // in NE or SE
  27972. if (parentBranch.children.NW.range.maxY > node.y) { // in NE
  27973. this._placeInRegion(parentBranch,node,"NE");
  27974. }
  27975. else { // in SE
  27976. this._placeInRegion(parentBranch,node,"SE");
  27977. }
  27978. }
  27979. };
  27980. /**
  27981. * actually place the node in a region (or branch)
  27982. *
  27983. * @param parentBranch
  27984. * @param node
  27985. * @param region
  27986. * @private
  27987. */
  27988. exports._placeInRegion = function(parentBranch,node,region) {
  27989. switch (parentBranch.children[region].childrenCount) {
  27990. case 0: // place node here
  27991. parentBranch.children[region].children.data = node;
  27992. parentBranch.children[region].childrenCount = 1;
  27993. this._updateBranchMass(parentBranch.children[region],node);
  27994. break;
  27995. case 1: // convert into children
  27996. // if there are two nodes exactly overlapping (on init, on opening of cluster etc.)
  27997. // we move one node a pixel and we do not put it in the tree.
  27998. if (parentBranch.children[region].children.data.x == node.x &&
  27999. parentBranch.children[region].children.data.y == node.y) {
  28000. node.x += Math.random();
  28001. node.y += Math.random();
  28002. }
  28003. else {
  28004. this._splitBranch(parentBranch.children[region]);
  28005. this._placeInTree(parentBranch.children[region],node);
  28006. }
  28007. break;
  28008. case 4: // place in branch
  28009. this._placeInTree(parentBranch.children[region],node);
  28010. break;
  28011. }
  28012. };
  28013. /**
  28014. * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch
  28015. * after the split is complete.
  28016. *
  28017. * @param parentBranch
  28018. * @private
  28019. */
  28020. exports._splitBranch = function(parentBranch) {
  28021. // if the branch is shaded with a node, replace the node in the new subset.
  28022. var containedNode = null;
  28023. if (parentBranch.childrenCount == 1) {
  28024. containedNode = parentBranch.children.data;
  28025. parentBranch.mass = 0; parentBranch.centerOfMass.x = 0; parentBranch.centerOfMass.y = 0;
  28026. }
  28027. parentBranch.childrenCount = 4;
  28028. parentBranch.children.data = null;
  28029. this._insertRegion(parentBranch,"NW");
  28030. this._insertRegion(parentBranch,"NE");
  28031. this._insertRegion(parentBranch,"SW");
  28032. this._insertRegion(parentBranch,"SE");
  28033. if (containedNode != null) {
  28034. this._placeInTree(parentBranch,containedNode);
  28035. }
  28036. };
  28037. /**
  28038. * This function subdivides the region into four new segments.
  28039. * Specifically, this inserts a single new segment.
  28040. * It fills the children section of the parentBranch
  28041. *
  28042. * @param parentBranch
  28043. * @param region
  28044. * @param parentRange
  28045. * @private
  28046. */
  28047. exports._insertRegion = function(parentBranch, region) {
  28048. var minX,maxX,minY,maxY;
  28049. var childSize = 0.5 * parentBranch.size;
  28050. switch (region) {
  28051. case "NW":
  28052. minX = parentBranch.range.minX;
  28053. maxX = parentBranch.range.minX + childSize;
  28054. minY = parentBranch.range.minY;
  28055. maxY = parentBranch.range.minY + childSize;
  28056. break;
  28057. case "NE":
  28058. minX = parentBranch.range.minX + childSize;
  28059. maxX = parentBranch.range.maxX;
  28060. minY = parentBranch.range.minY;
  28061. maxY = parentBranch.range.minY + childSize;
  28062. break;
  28063. case "SW":
  28064. minX = parentBranch.range.minX;
  28065. maxX = parentBranch.range.minX + childSize;
  28066. minY = parentBranch.range.minY + childSize;
  28067. maxY = parentBranch.range.maxY;
  28068. break;
  28069. case "SE":
  28070. minX = parentBranch.range.minX + childSize;
  28071. maxX = parentBranch.range.maxX;
  28072. minY = parentBranch.range.minY + childSize;
  28073. maxY = parentBranch.range.maxY;
  28074. break;
  28075. }
  28076. parentBranch.children[region] = {
  28077. centerOfMass:{x:0,y:0},
  28078. mass:0,
  28079. range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY},
  28080. size: 0.5 * parentBranch.size,
  28081. calcSize: 2 * parentBranch.calcSize,
  28082. children: {data:null},
  28083. maxWidth: 0,
  28084. level: parentBranch.level+1,
  28085. childrenCount: 0
  28086. };
  28087. };
  28088. /**
  28089. * This function is for debugging purposed, it draws the tree.
  28090. *
  28091. * @param ctx
  28092. * @param color
  28093. * @private
  28094. */
  28095. exports._drawTree = function(ctx,color) {
  28096. if (this.barnesHutTree !== undefined) {
  28097. ctx.lineWidth = 1;
  28098. this._drawBranch(this.barnesHutTree.root,ctx,color);
  28099. }
  28100. };
  28101. /**
  28102. * This function is for debugging purposes. It draws the branches recursively.
  28103. *
  28104. * @param branch
  28105. * @param ctx
  28106. * @param color
  28107. * @private
  28108. */
  28109. exports._drawBranch = function(branch,ctx,color) {
  28110. if (color === undefined) {
  28111. color = "#FF0000";
  28112. }
  28113. if (branch.childrenCount == 4) {
  28114. this._drawBranch(branch.children.NW,ctx);
  28115. this._drawBranch(branch.children.NE,ctx);
  28116. this._drawBranch(branch.children.SE,ctx);
  28117. this._drawBranch(branch.children.SW,ctx);
  28118. }
  28119. ctx.strokeStyle = color;
  28120. ctx.beginPath();
  28121. ctx.moveTo(branch.range.minX,branch.range.minY);
  28122. ctx.lineTo(branch.range.maxX,branch.range.minY);
  28123. ctx.stroke();
  28124. ctx.beginPath();
  28125. ctx.moveTo(branch.range.maxX,branch.range.minY);
  28126. ctx.lineTo(branch.range.maxX,branch.range.maxY);
  28127. ctx.stroke();
  28128. ctx.beginPath();
  28129. ctx.moveTo(branch.range.maxX,branch.range.maxY);
  28130. ctx.lineTo(branch.range.minX,branch.range.maxY);
  28131. ctx.stroke();
  28132. ctx.beginPath();
  28133. ctx.moveTo(branch.range.minX,branch.range.maxY);
  28134. ctx.lineTo(branch.range.minX,branch.range.minY);
  28135. ctx.stroke();
  28136. /*
  28137. if (branch.mass > 0) {
  28138. ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass);
  28139. ctx.stroke();
  28140. }
  28141. */
  28142. };
  28143. /***/ },
  28144. /* 65 */
  28145. /***/ function(module, exports, __webpack_require__) {
  28146. module.exports = function(module) {
  28147. if(!module.webpackPolyfill) {
  28148. module.deprecate = function() {};
  28149. module.paths = [];
  28150. // module.parent = undefined by default
  28151. module.children = [];
  28152. module.webpackPolyfill = 1;
  28153. }
  28154. return module;
  28155. }
  28156. /***/ }
  28157. /******/ ])
  28158. });