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.

31865 lines
993 KiB

  1. /**
  2. * vis.js
  3. * https://github.com/almende/vis
  4. *
  5. * A dynamic, browser-based visualization library.
  6. *
  7. * @version 3.3.0
  8. * @date 2014-08-29
  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. ItemBox: __webpack_require__(29),
  108. ItemPoint: __webpack_require__(30),
  109. ItemRange: __webpack_require__(31)
  110. },
  111. Component: __webpack_require__(18),
  112. CurrentTime: __webpack_require__(19),
  113. CustomTime: __webpack_require__(20),
  114. DataAxis: __webpack_require__(21),
  115. GraphGroup: __webpack_require__(22),
  116. Group: __webpack_require__(23),
  117. ItemSet: __webpack_require__(24),
  118. Legend: __webpack_require__(25),
  119. LineGraph: __webpack_require__(26),
  120. TimeAxis: __webpack_require__(27)
  121. }
  122. };
  123. // Network
  124. exports.Network = __webpack_require__(32);
  125. exports.network = {
  126. Edge: __webpack_require__(33),
  127. Groups: __webpack_require__(34),
  128. Images: __webpack_require__(35),
  129. Node: __webpack_require__(36),
  130. Popup: __webpack_require__(37),
  131. dotparser: __webpack_require__(38),
  132. gephiParser: __webpack_require__(39)
  133. };
  134. // Deprecated since v3.0.0
  135. exports.Graph = function () {
  136. throw new Error('Graph is renamed to Network. Please create a graph as new vis.Network(...)');
  137. };
  138. // bundled external libraries
  139. exports.moment = __webpack_require__(40);
  140. exports.hammer = __webpack_require__(41);
  141. /***/ },
  142. /* 1 */
  143. /***/ function(module, exports, __webpack_require__) {
  144. // utility functions
  145. // first check if moment.js is already loaded in the browser window, if so,
  146. // use this instance. Else, load via commonjs.
  147. var moment = __webpack_require__(40);
  148. /**
  149. * Test whether given object is a number
  150. * @param {*} object
  151. * @return {Boolean} isNumber
  152. */
  153. exports.isNumber = function(object) {
  154. return (object instanceof Number || typeof object == 'number');
  155. };
  156. /**
  157. * Test whether given object is a string
  158. * @param {*} object
  159. * @return {Boolean} isString
  160. */
  161. exports.isString = function(object) {
  162. return (object instanceof String || typeof object == 'string');
  163. };
  164. /**
  165. * Test whether given object is a Date, or a String containing a Date
  166. * @param {Date | String} object
  167. * @return {Boolean} isDate
  168. */
  169. exports.isDate = function(object) {
  170. if (object instanceof Date) {
  171. return true;
  172. }
  173. else if (exports.isString(object)) {
  174. // test whether this string contains a date
  175. var match = ASPDateRegex.exec(object);
  176. if (match) {
  177. return true;
  178. }
  179. else if (!isNaN(Date.parse(object))) {
  180. return true;
  181. }
  182. }
  183. return false;
  184. };
  185. /**
  186. * Test whether given object is an instance of google.visualization.DataTable
  187. * @param {*} object
  188. * @return {Boolean} isDataTable
  189. */
  190. exports.isDataTable = function(object) {
  191. return (typeof (google) !== 'undefined') &&
  192. (google.visualization) &&
  193. (google.visualization.DataTable) &&
  194. (object instanceof google.visualization.DataTable);
  195. };
  196. /**
  197. * Create a semi UUID
  198. * source: http://stackoverflow.com/a/105074/1262753
  199. * @return {String} uuid
  200. */
  201. exports.randomUUID = function() {
  202. var S4 = function () {
  203. return Math.floor(
  204. Math.random() * 0x10000 /* 65536 */
  205. ).toString(16);
  206. };
  207. return (
  208. S4() + S4() + '-' +
  209. S4() + '-' +
  210. S4() + '-' +
  211. S4() + '-' +
  212. S4() + S4() + S4()
  213. );
  214. };
  215. /**
  216. * Extend object a with the properties of object b or a series of objects
  217. * Only properties with defined values are copied
  218. * @param {Object} a
  219. * @param {... Object} b
  220. * @return {Object} a
  221. */
  222. exports.extend = function (a, b) {
  223. for (var i = 1, len = arguments.length; i < len; i++) {
  224. var other = arguments[i];
  225. for (var prop in other) {
  226. if (other.hasOwnProperty(prop)) {
  227. a[prop] = other[prop];
  228. }
  229. }
  230. }
  231. return a;
  232. };
  233. /**
  234. * Extend object a with selected properties of object b or a series of objects
  235. * Only properties with defined values are copied
  236. * @param {Array.<String>} props
  237. * @param {Object} a
  238. * @param {... Object} b
  239. * @return {Object} a
  240. */
  241. exports.selectiveExtend = function (props, a, b) {
  242. if (!Array.isArray(props)) {
  243. throw new Error('Array with property names expected as first argument');
  244. }
  245. for (var i = 2; i < arguments.length; i++) {
  246. var other = arguments[i];
  247. for (var p = 0; p < props.length; p++) {
  248. var prop = props[p];
  249. if (other.hasOwnProperty(prop)) {
  250. a[prop] = other[prop];
  251. }
  252. }
  253. }
  254. return a;
  255. };
  256. /**
  257. * Extend object a with selected properties of object b or a series of objects
  258. * Only properties with defined values are copied
  259. * @param {Array.<String>} props
  260. * @param {Object} a
  261. * @param {... Object} b
  262. * @return {Object} a
  263. */
  264. exports.selectiveDeepExtend = function (props, a, b) {
  265. // TODO: add support for Arrays to deepExtend
  266. if (Array.isArray(b)) {
  267. throw new TypeError('Arrays are not supported by deepExtend');
  268. }
  269. for (var i = 2; i < arguments.length; i++) {
  270. var other = arguments[i];
  271. for (var p = 0; p < props.length; p++) {
  272. var prop = props[p];
  273. if (other.hasOwnProperty(prop)) {
  274. if (b[prop] && b[prop].constructor === Object) {
  275. if (a[prop] === undefined) {
  276. a[prop] = {};
  277. }
  278. if (a[prop].constructor === Object) {
  279. exports.deepExtend(a[prop], b[prop]);
  280. }
  281. else {
  282. a[prop] = b[prop];
  283. }
  284. } else if (Array.isArray(b[prop])) {
  285. throw new TypeError('Arrays are not supported by deepExtend');
  286. } else {
  287. a[prop] = b[prop];
  288. }
  289. }
  290. }
  291. }
  292. return a;
  293. };
  294. /**
  295. * Extend object a with selected properties of object b or a series of objects
  296. * Only properties with defined values are copied
  297. * @param {Array.<String>} props
  298. * @param {Object} a
  299. * @param {... Object} b
  300. * @return {Object} a
  301. */
  302. exports.selectiveNotDeepExtend = function (props, a, b) {
  303. // TODO: add support for Arrays to deepExtend
  304. if (Array.isArray(b)) {
  305. throw new TypeError('Arrays are not supported by deepExtend');
  306. }
  307. for (var prop in b) {
  308. if (b.hasOwnProperty(prop)) {
  309. if (props.indexOf(prop) == -1) {
  310. if (b[prop] && b[prop].constructor === Object) {
  311. if (a[prop] === undefined) {
  312. a[prop] = {};
  313. }
  314. if (a[prop].constructor === Object) {
  315. exports.deepExtend(a[prop], b[prop]);
  316. }
  317. else {
  318. a[prop] = b[prop];
  319. }
  320. } else if (Array.isArray(b[prop])) {
  321. throw new TypeError('Arrays are not supported by deepExtend');
  322. } else {
  323. a[prop] = b[prop];
  324. }
  325. }
  326. }
  327. }
  328. return a;
  329. };
  330. /**
  331. * Deep extend an object a with the properties of object b
  332. * @param {Object} a
  333. * @param {Object} b
  334. * @returns {Object}
  335. */
  336. exports.deepExtend = function(a, b) {
  337. // TODO: add support for Arrays to deepExtend
  338. if (Array.isArray(b)) {
  339. throw new TypeError('Arrays are not supported by deepExtend');
  340. }
  341. for (var prop in b) {
  342. if (b.hasOwnProperty(prop)) {
  343. if (b[prop] && b[prop].constructor === Object) {
  344. if (a[prop] === undefined) {
  345. a[prop] = {};
  346. }
  347. if (a[prop].constructor === Object) {
  348. exports.deepExtend(a[prop], b[prop]);
  349. }
  350. else {
  351. a[prop] = b[prop];
  352. }
  353. } else if (Array.isArray(b[prop])) {
  354. throw new TypeError('Arrays are not supported by deepExtend');
  355. } else {
  356. a[prop] = b[prop];
  357. }
  358. }
  359. }
  360. return a;
  361. };
  362. /**
  363. * Test whether all elements in two arrays are equal.
  364. * @param {Array} a
  365. * @param {Array} b
  366. * @return {boolean} Returns true if both arrays have the same length and same
  367. * elements.
  368. */
  369. exports.equalArray = function (a, b) {
  370. if (a.length != b.length) return false;
  371. for (var i = 0, len = a.length; i < len; i++) {
  372. if (a[i] != b[i]) return false;
  373. }
  374. return true;
  375. };
  376. /**
  377. * Convert an object to another type
  378. * @param {Boolean | Number | String | Date | Moment | Null | undefined} object
  379. * @param {String | undefined} type Name of the type. Available types:
  380. * 'Boolean', 'Number', 'String',
  381. * 'Date', 'Moment', ISODate', 'ASPDate'.
  382. * @return {*} object
  383. * @throws Error
  384. */
  385. exports.convert = function(object, type) {
  386. var match;
  387. if (object === undefined) {
  388. return undefined;
  389. }
  390. if (object === null) {
  391. return null;
  392. }
  393. if (!type) {
  394. return object;
  395. }
  396. if (!(typeof type === 'string') && !(type instanceof String)) {
  397. throw new Error('Type must be a string');
  398. }
  399. //noinspection FallthroughInSwitchStatementJS
  400. switch (type) {
  401. case 'boolean':
  402. case 'Boolean':
  403. return Boolean(object);
  404. case 'number':
  405. case 'Number':
  406. return Number(object.valueOf());
  407. case 'string':
  408. case 'String':
  409. return String(object);
  410. case 'Date':
  411. if (exports.isNumber(object)) {
  412. return new Date(object);
  413. }
  414. if (object instanceof Date) {
  415. return new Date(object.valueOf());
  416. }
  417. else if (moment.isMoment(object)) {
  418. return new Date(object.valueOf());
  419. }
  420. if (exports.isString(object)) {
  421. match = ASPDateRegex.exec(object);
  422. if (match) {
  423. // object is an ASP date
  424. return new Date(Number(match[1])); // parse number
  425. }
  426. else {
  427. return moment(object).toDate(); // parse string
  428. }
  429. }
  430. else {
  431. throw new Error(
  432. 'Cannot convert object of type ' + exports.getType(object) +
  433. ' to type Date');
  434. }
  435. case 'Moment':
  436. if (exports.isNumber(object)) {
  437. return moment(object);
  438. }
  439. if (object instanceof Date) {
  440. return moment(object.valueOf());
  441. }
  442. else if (moment.isMoment(object)) {
  443. return moment(object);
  444. }
  445. if (exports.isString(object)) {
  446. match = ASPDateRegex.exec(object);
  447. if (match) {
  448. // object is an ASP date
  449. return moment(Number(match[1])); // parse number
  450. }
  451. else {
  452. return moment(object); // parse string
  453. }
  454. }
  455. else {
  456. throw new Error(
  457. 'Cannot convert object of type ' + exports.getType(object) +
  458. ' to type Date');
  459. }
  460. case 'ISODate':
  461. if (exports.isNumber(object)) {
  462. return new Date(object);
  463. }
  464. else if (object instanceof Date) {
  465. return object.toISOString();
  466. }
  467. else if (moment.isMoment(object)) {
  468. return object.toDate().toISOString();
  469. }
  470. else if (exports.isString(object)) {
  471. match = ASPDateRegex.exec(object);
  472. if (match) {
  473. // object is an ASP date
  474. return new Date(Number(match[1])).toISOString(); // parse number
  475. }
  476. else {
  477. return new Date(object).toISOString(); // parse string
  478. }
  479. }
  480. else {
  481. throw new Error(
  482. 'Cannot convert object of type ' + exports.getType(object) +
  483. ' to type ISODate');
  484. }
  485. case 'ASPDate':
  486. if (exports.isNumber(object)) {
  487. return '/Date(' + object + ')/';
  488. }
  489. else if (object instanceof Date) {
  490. return '/Date(' + object.valueOf() + ')/';
  491. }
  492. else if (exports.isString(object)) {
  493. match = ASPDateRegex.exec(object);
  494. var value;
  495. if (match) {
  496. // object is an ASP date
  497. value = new Date(Number(match[1])).valueOf(); // parse number
  498. }
  499. else {
  500. value = new Date(object).valueOf(); // parse string
  501. }
  502. return '/Date(' + value + ')/';
  503. }
  504. else {
  505. throw new Error(
  506. 'Cannot convert object of type ' + exports.getType(object) +
  507. ' to type ASPDate');
  508. }
  509. default:
  510. throw new Error('Unknown type "' + type + '"');
  511. }
  512. };
  513. // parse ASP.Net Date pattern,
  514. // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
  515. // code from http://momentjs.com/
  516. var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
  517. /**
  518. * Get the type of an object, for example exports.getType([]) returns 'Array'
  519. * @param {*} object
  520. * @return {String} type
  521. */
  522. exports.getType = function(object) {
  523. var type = typeof object;
  524. if (type == 'object') {
  525. if (object == null) {
  526. return 'null';
  527. }
  528. if (object instanceof Boolean) {
  529. return 'Boolean';
  530. }
  531. if (object instanceof Number) {
  532. return 'Number';
  533. }
  534. if (object instanceof String) {
  535. return 'String';
  536. }
  537. if (object instanceof Array) {
  538. return 'Array';
  539. }
  540. if (object instanceof Date) {
  541. return 'Date';
  542. }
  543. return 'Object';
  544. }
  545. else if (type == 'number') {
  546. return 'Number';
  547. }
  548. else if (type == 'boolean') {
  549. return 'Boolean';
  550. }
  551. else if (type == 'string') {
  552. return 'String';
  553. }
  554. return type;
  555. };
  556. /**
  557. * Retrieve the absolute left value of a DOM element
  558. * @param {Element} elem A dom element, for example a div
  559. * @return {number} left The absolute left position of this element
  560. * in the browser page.
  561. */
  562. exports.getAbsoluteLeft = function(elem) {
  563. return elem.getBoundingClientRect().left + window.pageXOffset;
  564. };
  565. /**
  566. * Retrieve the absolute top value of a DOM element
  567. * @param {Element} elem A dom element, for example a div
  568. * @return {number} top The absolute top position of this element
  569. * in the browser page.
  570. */
  571. exports.getAbsoluteTop = function(elem) {
  572. return elem.getBoundingClientRect().top + window.pageYOffset;
  573. };
  574. /**
  575. * add a className to the given elements style
  576. * @param {Element} elem
  577. * @param {String} className
  578. */
  579. exports.addClassName = function(elem, className) {
  580. var classes = elem.className.split(' ');
  581. if (classes.indexOf(className) == -1) {
  582. classes.push(className); // add the class to the array
  583. elem.className = classes.join(' ');
  584. }
  585. };
  586. /**
  587. * add a className to the given elements style
  588. * @param {Element} elem
  589. * @param {String} className
  590. */
  591. exports.removeClassName = function(elem, className) {
  592. var classes = elem.className.split(' ');
  593. var index = classes.indexOf(className);
  594. if (index != -1) {
  595. classes.splice(index, 1); // remove the class from the array
  596. elem.className = classes.join(' ');
  597. }
  598. };
  599. /**
  600. * For each method for both arrays and objects.
  601. * In case of an array, the built-in Array.forEach() is applied.
  602. * In case of an Object, the method loops over all properties of the object.
  603. * @param {Object | Array} object An Object or Array
  604. * @param {function} callback Callback method, called for each item in
  605. * the object or array with three parameters:
  606. * callback(value, index, object)
  607. */
  608. exports.forEach = function(object, callback) {
  609. var i,
  610. len;
  611. if (object instanceof Array) {
  612. // array
  613. for (i = 0, len = object.length; i < len; i++) {
  614. callback(object[i], i, object);
  615. }
  616. }
  617. else {
  618. // object
  619. for (i in object) {
  620. if (object.hasOwnProperty(i)) {
  621. callback(object[i], i, object);
  622. }
  623. }
  624. }
  625. };
  626. /**
  627. * Convert an object into an array: all objects properties are put into the
  628. * array. The resulting array is unordered.
  629. * @param {Object} object
  630. * @param {Array} array
  631. */
  632. exports.toArray = function(object) {
  633. var array = [];
  634. for (var prop in object) {
  635. if (object.hasOwnProperty(prop)) array.push(object[prop]);
  636. }
  637. return array;
  638. }
  639. /**
  640. * Update a property in an object
  641. * @param {Object} object
  642. * @param {String} key
  643. * @param {*} value
  644. * @return {Boolean} changed
  645. */
  646. exports.updateProperty = function(object, key, value) {
  647. if (object[key] !== value) {
  648. object[key] = value;
  649. return true;
  650. }
  651. else {
  652. return false;
  653. }
  654. };
  655. /**
  656. * Add and event listener. Works for all browsers
  657. * @param {Element} element An html element
  658. * @param {string} action The action, for example "click",
  659. * without the prefix "on"
  660. * @param {function} listener The callback function to be executed
  661. * @param {boolean} [useCapture]
  662. */
  663. exports.addEventListener = function(element, action, listener, useCapture) {
  664. if (element.addEventListener) {
  665. if (useCapture === undefined)
  666. useCapture = false;
  667. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  668. action = "DOMMouseScroll"; // For Firefox
  669. }
  670. element.addEventListener(action, listener, useCapture);
  671. } else {
  672. element.attachEvent("on" + action, listener); // IE browsers
  673. }
  674. };
  675. /**
  676. * Remove an event listener from an element
  677. * @param {Element} element An html dom element
  678. * @param {string} action The name of the event, for example "mousedown"
  679. * @param {function} listener The listener function
  680. * @param {boolean} [useCapture]
  681. */
  682. exports.removeEventListener = function(element, action, listener, useCapture) {
  683. if (element.removeEventListener) {
  684. // non-IE browsers
  685. if (useCapture === undefined)
  686. useCapture = false;
  687. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  688. action = "DOMMouseScroll"; // For Firefox
  689. }
  690. element.removeEventListener(action, listener, useCapture);
  691. } else {
  692. // IE browsers
  693. element.detachEvent("on" + action, listener);
  694. }
  695. };
  696. /**
  697. * Cancels the event if it is cancelable, without stopping further propagation of the event.
  698. */
  699. exports.preventDefault = function (event) {
  700. if (!event)
  701. event = window.event;
  702. if (event.preventDefault) {
  703. event.preventDefault(); // non-IE browsers
  704. }
  705. else {
  706. event.returnValue = false; // IE browsers
  707. }
  708. };
  709. /**
  710. * Get HTML element which is the target of the event
  711. * @param {Event} event
  712. * @return {Element} target element
  713. */
  714. exports.getTarget = function(event) {
  715. // code from http://www.quirksmode.org/js/events_properties.html
  716. if (!event) {
  717. event = window.event;
  718. }
  719. var target;
  720. if (event.target) {
  721. target = event.target;
  722. }
  723. else if (event.srcElement) {
  724. target = event.srcElement;
  725. }
  726. if (target.nodeType != undefined && target.nodeType == 3) {
  727. // defeat Safari bug
  728. target = target.parentNode;
  729. }
  730. return target;
  731. };
  732. exports.option = {};
  733. /**
  734. * Convert a value into a boolean
  735. * @param {Boolean | function | undefined} value
  736. * @param {Boolean} [defaultValue]
  737. * @returns {Boolean} bool
  738. */
  739. exports.option.asBoolean = function (value, defaultValue) {
  740. if (typeof value == 'function') {
  741. value = value();
  742. }
  743. if (value != null) {
  744. return (value != false);
  745. }
  746. return defaultValue || null;
  747. };
  748. /**
  749. * Convert a value into a number
  750. * @param {Boolean | function | undefined} value
  751. * @param {Number} [defaultValue]
  752. * @returns {Number} number
  753. */
  754. exports.option.asNumber = function (value, defaultValue) {
  755. if (typeof value == 'function') {
  756. value = value();
  757. }
  758. if (value != null) {
  759. return Number(value) || defaultValue || null;
  760. }
  761. return defaultValue || null;
  762. };
  763. /**
  764. * Convert a value into a string
  765. * @param {String | function | undefined} value
  766. * @param {String} [defaultValue]
  767. * @returns {String} str
  768. */
  769. exports.option.asString = function (value, defaultValue) {
  770. if (typeof value == 'function') {
  771. value = value();
  772. }
  773. if (value != null) {
  774. return String(value);
  775. }
  776. return defaultValue || null;
  777. };
  778. /**
  779. * Convert a size or location into a string with pixels or a percentage
  780. * @param {String | Number | function | undefined} value
  781. * @param {String} [defaultValue]
  782. * @returns {String} size
  783. */
  784. exports.option.asSize = function (value, defaultValue) {
  785. if (typeof value == 'function') {
  786. value = value();
  787. }
  788. if (exports.isString(value)) {
  789. return value;
  790. }
  791. else if (exports.isNumber(value)) {
  792. return value + 'px';
  793. }
  794. else {
  795. return defaultValue || null;
  796. }
  797. };
  798. /**
  799. * Convert a value into a DOM element
  800. * @param {HTMLElement | function | undefined} value
  801. * @param {HTMLElement} [defaultValue]
  802. * @returns {HTMLElement | null} dom
  803. */
  804. exports.option.asElement = function (value, defaultValue) {
  805. if (typeof value == 'function') {
  806. value = value();
  807. }
  808. return value || defaultValue || null;
  809. };
  810. exports.GiveDec = function(Hex) {
  811. var Value;
  812. if (Hex == "A")
  813. Value = 10;
  814. else if (Hex == "B")
  815. Value = 11;
  816. else if (Hex == "C")
  817. Value = 12;
  818. else if (Hex == "D")
  819. Value = 13;
  820. else if (Hex == "E")
  821. Value = 14;
  822. else if (Hex == "F")
  823. Value = 15;
  824. else
  825. Value = eval(Hex);
  826. return Value;
  827. };
  828. exports.GiveHex = function(Dec) {
  829. var Value;
  830. if(Dec == 10)
  831. Value = "A";
  832. else if (Dec == 11)
  833. Value = "B";
  834. else if (Dec == 12)
  835. Value = "C";
  836. else if (Dec == 13)
  837. Value = "D";
  838. else if (Dec == 14)
  839. Value = "E";
  840. else if (Dec == 15)
  841. Value = "F";
  842. else
  843. Value = "" + Dec;
  844. return Value;
  845. };
  846. /**
  847. * Parse a color property into an object with border, background, and
  848. * highlight colors
  849. * @param {Object | String} color
  850. * @return {Object} colorObject
  851. */
  852. exports.parseColor = function(color) {
  853. var c;
  854. if (exports.isString(color)) {
  855. if (exports.isValidRGB(color)) {
  856. var rgb = color.substr(4).substr(0,color.length-5).split(',');
  857. color = exports.RGBToHex(rgb[0],rgb[1],rgb[2]);
  858. }
  859. if (exports.isValidHex(color)) {
  860. var hsv = exports.hexToHSV(color);
  861. var lighterColorHSV = {h:hsv.h,s:hsv.s * 0.45,v:Math.min(1,hsv.v * 1.05)};
  862. var darkerColorHSV = {h:hsv.h,s:Math.min(1,hsv.v * 1.25),v:hsv.v*0.6};
  863. var darkerColorHex = exports.HSVToHex(darkerColorHSV.h ,darkerColorHSV.h ,darkerColorHSV.v);
  864. var lighterColorHex = exports.HSVToHex(lighterColorHSV.h,lighterColorHSV.s,lighterColorHSV.v);
  865. c = {
  866. background: color,
  867. border:darkerColorHex,
  868. highlight: {
  869. background:lighterColorHex,
  870. border:darkerColorHex
  871. },
  872. hover: {
  873. background:lighterColorHex,
  874. border:darkerColorHex
  875. }
  876. };
  877. }
  878. else {
  879. c = {
  880. background:color,
  881. border:color,
  882. highlight: {
  883. background:color,
  884. border:color
  885. },
  886. hover: {
  887. background:color,
  888. border:color
  889. }
  890. };
  891. }
  892. }
  893. else {
  894. c = {};
  895. c.background = color.background || 'white';
  896. c.border = color.border || c.background;
  897. if (exports.isString(color.highlight)) {
  898. c.highlight = {
  899. border: color.highlight,
  900. background: color.highlight
  901. }
  902. }
  903. else {
  904. c.highlight = {};
  905. c.highlight.background = color.highlight && color.highlight.background || c.background;
  906. c.highlight.border = color.highlight && color.highlight.border || c.border;
  907. }
  908. if (exports.isString(color.hover)) {
  909. c.hover = {
  910. border: color.hover,
  911. background: color.hover
  912. }
  913. }
  914. else {
  915. c.hover = {};
  916. c.hover.background = color.hover && color.hover.background || c.background;
  917. c.hover.border = color.hover && color.hover.border || c.border;
  918. }
  919. }
  920. return c;
  921. };
  922. /**
  923. * http://www.yellowpipe.com/yis/tools/hex-to-rgb/color-converter.php
  924. *
  925. * @param {String} hex
  926. * @returns {{r: *, g: *, b: *}}
  927. */
  928. exports.hexToRGB = function(hex) {
  929. hex = hex.replace("#","").toUpperCase();
  930. var a = exports.GiveDec(hex.substring(0, 1));
  931. var b = exports.GiveDec(hex.substring(1, 2));
  932. var c = exports.GiveDec(hex.substring(2, 3));
  933. var d = exports.GiveDec(hex.substring(3, 4));
  934. var e = exports.GiveDec(hex.substring(4, 5));
  935. var f = exports.GiveDec(hex.substring(5, 6));
  936. var r = (a * 16) + b;
  937. var g = (c * 16) + d;
  938. var b = (e * 16) + f;
  939. return {r:r,g:g,b:b};
  940. };
  941. exports.RGBToHex = function(red,green,blue) {
  942. var a = exports.GiveHex(Math.floor(red / 16));
  943. var b = exports.GiveHex(red % 16);
  944. var c = exports.GiveHex(Math.floor(green / 16));
  945. var d = exports.GiveHex(green % 16);
  946. var e = exports.GiveHex(Math.floor(blue / 16));
  947. var f = exports.GiveHex(blue % 16);
  948. var hex = a + b + c + d + e + f;
  949. return "#" + hex;
  950. };
  951. /**
  952. * http://www.javascripter.net/faq/rgb2hsv.htm
  953. *
  954. * @param red
  955. * @param green
  956. * @param blue
  957. * @returns {*}
  958. * @constructor
  959. */
  960. exports.RGBToHSV = function(red,green,blue) {
  961. red=red/255; green=green/255; blue=blue/255;
  962. var minRGB = Math.min(red,Math.min(green,blue));
  963. var maxRGB = Math.max(red,Math.max(green,blue));
  964. // Black-gray-white
  965. if (minRGB == maxRGB) {
  966. return {h:0,s:0,v:minRGB};
  967. }
  968. // Colors other than black-gray-white:
  969. var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
  970. var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
  971. var hue = 60*(h - d/(maxRGB - minRGB))/360;
  972. var saturation = (maxRGB - minRGB)/maxRGB;
  973. var value = maxRGB;
  974. return {h:hue,s:saturation,v:value};
  975. };
  976. /**
  977. * https://gist.github.com/mjijackson/5311256
  978. * @param h
  979. * @param s
  980. * @param v
  981. * @returns {{r: number, g: number, b: number}}
  982. * @constructor
  983. */
  984. exports.HSVToRGB = function(h, s, v) {
  985. var r, g, b;
  986. var i = Math.floor(h * 6);
  987. var f = h * 6 - i;
  988. var p = v * (1 - s);
  989. var q = v * (1 - f * s);
  990. var t = v * (1 - (1 - f) * s);
  991. switch (i % 6) {
  992. case 0: r = v, g = t, b = p; break;
  993. case 1: r = q, g = v, b = p; break;
  994. case 2: r = p, g = v, b = t; break;
  995. case 3: r = p, g = q, b = v; break;
  996. case 4: r = t, g = p, b = v; break;
  997. case 5: r = v, g = p, b = q; break;
  998. }
  999. return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
  1000. };
  1001. exports.HSVToHex = function(h, s, v) {
  1002. var rgb = exports.HSVToRGB(h, s, v);
  1003. return exports.RGBToHex(rgb.r, rgb.g, rgb.b);
  1004. };
  1005. exports.hexToHSV = function(hex) {
  1006. var rgb = exports.hexToRGB(hex);
  1007. return exports.RGBToHSV(rgb.r, rgb.g, rgb.b);
  1008. };
  1009. exports.isValidHex = function(hex) {
  1010. var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
  1011. return isOk;
  1012. };
  1013. exports.isValidRGB = function(rgb) {
  1014. rgb = rgb.replace(" ","");
  1015. var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb);
  1016. return isOk;
  1017. }
  1018. /**
  1019. * This recursively redirects the prototype of JSON objects to the referenceObject
  1020. * This is used for default options.
  1021. *
  1022. * @param referenceObject
  1023. * @returns {*}
  1024. */
  1025. exports.selectiveBridgeObject = function(fields, referenceObject) {
  1026. if (typeof referenceObject == "object") {
  1027. var objectTo = Object.create(referenceObject);
  1028. for (var i = 0; i < fields.length; i++) {
  1029. if (referenceObject.hasOwnProperty(fields[i])) {
  1030. if (typeof referenceObject[fields[i]] == "object") {
  1031. objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]);
  1032. }
  1033. }
  1034. }
  1035. return objectTo;
  1036. }
  1037. else {
  1038. return null;
  1039. }
  1040. };
  1041. /**
  1042. * This recursively redirects the prototype of JSON objects to the referenceObject
  1043. * This is used for default options.
  1044. *
  1045. * @param referenceObject
  1046. * @returns {*}
  1047. */
  1048. exports.bridgeObject = function(referenceObject) {
  1049. if (typeof referenceObject == "object") {
  1050. var objectTo = Object.create(referenceObject);
  1051. for (var i in referenceObject) {
  1052. if (referenceObject.hasOwnProperty(i)) {
  1053. if (typeof referenceObject[i] == "object") {
  1054. objectTo[i] = exports.bridgeObject(referenceObject[i]);
  1055. }
  1056. }
  1057. }
  1058. return objectTo;
  1059. }
  1060. else {
  1061. return null;
  1062. }
  1063. };
  1064. /**
  1065. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  1066. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  1067. *
  1068. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  1069. * @param [object] options | options
  1070. * @param [String] option | this is the option key in the options argument
  1071. * @private
  1072. */
  1073. exports.mergeOptions = function (mergeTarget, options, option) {
  1074. if (options[option] !== undefined) {
  1075. if (typeof options[option] == 'boolean') {
  1076. mergeTarget[option].enabled = options[option];
  1077. }
  1078. else {
  1079. mergeTarget[option].enabled = true;
  1080. for (prop in options[option]) {
  1081. if (options[option].hasOwnProperty(prop)) {
  1082. mergeTarget[option][prop] = options[option][prop];
  1083. }
  1084. }
  1085. }
  1086. }
  1087. }
  1088. /**
  1089. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  1090. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  1091. *
  1092. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  1093. * @param [object] options | options
  1094. * @param [String] option | this is the option key in the options argument
  1095. * @private
  1096. */
  1097. exports.mergeOptions = function (mergeTarget, options, option) {
  1098. if (options[option] !== undefined) {
  1099. if (typeof options[option] == 'boolean') {
  1100. mergeTarget[option].enabled = options[option];
  1101. }
  1102. else {
  1103. mergeTarget[option].enabled = true;
  1104. for (prop in options[option]) {
  1105. if (options[option].hasOwnProperty(prop)) {
  1106. mergeTarget[option][prop] = options[option][prop];
  1107. }
  1108. }
  1109. }
  1110. }
  1111. }
  1112. /**
  1113. * This function does a binary search for a visible item. The user can select either the this.orderedItems.byStart or .byEnd
  1114. * arrays. This is done by giving a boolean value true if you want to use the byEnd.
  1115. * 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
  1116. * if the time we selected (start or end) is within the current range).
  1117. *
  1118. * The trick is that every interval has to either enter the screen at the initial load or by dragging. The case of the ItemRange that is
  1119. * 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,
  1120. * either the start OR end time has to be in the range.
  1121. *
  1122. * @param {Item[]} orderedItems Items ordered by start
  1123. * @param {{start: number, end: number}} range
  1124. * @param {String} field
  1125. * @param {String} field2
  1126. * @returns {number}
  1127. * @private
  1128. */
  1129. exports.binarySearch = function(orderedItems, range, field, field2) {
  1130. var array = orderedItems;
  1131. var maxIterations = 10000;
  1132. var iteration = 0;
  1133. var found = false;
  1134. var low = 0;
  1135. var high = array.length;
  1136. var newLow = low;
  1137. var newHigh = high;
  1138. var guess = Math.floor(0.5*(high+low));
  1139. var value;
  1140. if (high == 0) {
  1141. guess = -1;
  1142. }
  1143. else if (high == 1) {
  1144. if (array[guess].isVisible(range)) {
  1145. guess = 0;
  1146. }
  1147. else {
  1148. guess = -1;
  1149. }
  1150. }
  1151. else {
  1152. high -= 1;
  1153. while (found == false && iteration < maxIterations) {
  1154. value = field2 === undefined ? array[guess][field] : array[guess][field][field2];
  1155. if (array[guess].isVisible(range)) {
  1156. found = true;
  1157. }
  1158. else {
  1159. if (value < range.start) { // it is too small --> increase low
  1160. newLow = Math.floor(0.5*(high+low));
  1161. }
  1162. else { // it is too big --> decrease high
  1163. newHigh = Math.floor(0.5*(high+low));
  1164. }
  1165. // not in list;
  1166. if (low == newLow && high == newHigh) {
  1167. guess = -1;
  1168. found = true;
  1169. }
  1170. else {
  1171. high = newHigh; low = newLow;
  1172. guess = Math.floor(0.5*(high+low));
  1173. }
  1174. }
  1175. iteration++;
  1176. }
  1177. if (iteration >= maxIterations) {
  1178. console.log("BinarySearch too many iterations. Aborting.");
  1179. }
  1180. }
  1181. return guess;
  1182. };
  1183. /**
  1184. * This function does a binary search for a visible item. The user can select either the this.orderedItems.byStart or .byEnd
  1185. * arrays. This is done by giving a boolean value true if you want to use the byEnd.
  1186. * 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
  1187. * if the time we selected (start or end) is within the current range).
  1188. *
  1189. * The trick is that every interval has to either enter the screen at the initial load or by dragging. The case of the ItemRange that is
  1190. * 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,
  1191. * either the start OR end time has to be in the range.
  1192. *
  1193. * @param {Array} orderedItems
  1194. * @param {{start: number, end: number}} target
  1195. * @param {String} field
  1196. * @param {String} sidePreference 'before' or 'after'
  1197. * @returns {number}
  1198. * @private
  1199. */
  1200. exports.binarySearchGeneric = function(orderedItems, target, field, sidePreference) {
  1201. var maxIterations = 10000;
  1202. var iteration = 0;
  1203. var array = orderedItems;
  1204. var found = false;
  1205. var low = 0;
  1206. var high = array.length;
  1207. var newLow = low;
  1208. var newHigh = high;
  1209. var guess = Math.floor(0.5*(high+low));
  1210. var newGuess;
  1211. var prevValue, value, nextValue;
  1212. if (high == 0) {guess = -1;}
  1213. else if (high == 1) {
  1214. value = array[guess][field];
  1215. if (value == target) {
  1216. guess = 0;
  1217. }
  1218. else {
  1219. guess = -1;
  1220. }
  1221. }
  1222. else {
  1223. high -= 1;
  1224. while (found == false && iteration < maxIterations) {
  1225. prevValue = array[Math.max(0,guess - 1)][field];
  1226. value = array[guess][field];
  1227. nextValue = array[Math.min(array.length-1,guess + 1)][field];
  1228. if (value == target || prevValue < target && value > target || value < target && nextValue > target) {
  1229. found = true;
  1230. if (value != target) {
  1231. if (sidePreference == 'before') {
  1232. if (prevValue < target && value > target) {
  1233. guess = Math.max(0,guess - 1);
  1234. }
  1235. }
  1236. else {
  1237. if (value < target && nextValue > target) {
  1238. guess = Math.min(array.length-1,guess + 1);
  1239. }
  1240. }
  1241. }
  1242. }
  1243. else {
  1244. if (value < target) { // it is too small --> increase low
  1245. newLow = Math.floor(0.5*(high+low));
  1246. }
  1247. else { // it is too big --> decrease high
  1248. newHigh = Math.floor(0.5*(high+low));
  1249. }
  1250. newGuess = Math.floor(0.5*(high+low));
  1251. // not in list;
  1252. if (low == newLow && high == newHigh) {
  1253. guess = -1;
  1254. found = true;
  1255. }
  1256. else {
  1257. high = newHigh; low = newLow;
  1258. guess = Math.floor(0.5*(high+low));
  1259. }
  1260. }
  1261. iteration++;
  1262. }
  1263. if (iteration >= maxIterations) {
  1264. console.log("BinarySearch too many iterations. Aborting.");
  1265. }
  1266. }
  1267. return guess;
  1268. };
  1269. /**
  1270. * Quadratic ease-in-out
  1271. * http://gizma.com/easing/
  1272. * @param {number} t Current time
  1273. * @param {number} start Start value
  1274. * @param {number} end End value
  1275. * @param {number} duration Duration
  1276. * @returns {number} Value corresponding with current time
  1277. */
  1278. exports.easeInOutQuad = function (t, start, end, duration) {
  1279. var change = end - start;
  1280. t /= duration/2;
  1281. if (t < 1) return change/2*t*t + start;
  1282. t--;
  1283. return -change/2 * (t*(t-2) - 1) + start;
  1284. };
  1285. /***/ },
  1286. /* 2 */
  1287. /***/ function(module, exports, __webpack_require__) {
  1288. // DOM utility methods
  1289. /**
  1290. * this prepares the JSON container for allocating SVG elements
  1291. * @param JSONcontainer
  1292. * @private
  1293. */
  1294. exports.prepareElements = function(JSONcontainer) {
  1295. // cleanup the redundant svgElements;
  1296. for (var elementType in JSONcontainer) {
  1297. if (JSONcontainer.hasOwnProperty(elementType)) {
  1298. JSONcontainer[elementType].redundant = JSONcontainer[elementType].used;
  1299. JSONcontainer[elementType].used = [];
  1300. }
  1301. }
  1302. };
  1303. /**
  1304. * this cleans up all the unused SVG elements. By asking for the parentNode, we only need to supply the JSON container from
  1305. * which to remove the redundant elements.
  1306. *
  1307. * @param JSONcontainer
  1308. * @private
  1309. */
  1310. exports.cleanupElements = function(JSONcontainer) {
  1311. // cleanup the redundant svgElements;
  1312. for (var elementType in JSONcontainer) {
  1313. if (JSONcontainer.hasOwnProperty(elementType)) {
  1314. if (JSONcontainer[elementType].redundant) {
  1315. for (var i = 0; i < JSONcontainer[elementType].redundant.length; i++) {
  1316. JSONcontainer[elementType].redundant[i].parentNode.removeChild(JSONcontainer[elementType].redundant[i]);
  1317. }
  1318. JSONcontainer[elementType].redundant = [];
  1319. }
  1320. }
  1321. }
  1322. };
  1323. /**
  1324. * Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer
  1325. * the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this.
  1326. *
  1327. * @param elementType
  1328. * @param JSONcontainer
  1329. * @param svgContainer
  1330. * @returns {*}
  1331. * @private
  1332. */
  1333. exports.getSVGElement = function (elementType, JSONcontainer, svgContainer) {
  1334. var element;
  1335. // allocate SVG element, if it doesnt yet exist, create one.
  1336. if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
  1337. // check if there is an redundant element
  1338. if (JSONcontainer[elementType].redundant.length > 0) {
  1339. element = JSONcontainer[elementType].redundant[0];
  1340. JSONcontainer[elementType].redundant.shift();
  1341. }
  1342. else {
  1343. // create a new element and add it to the SVG
  1344. element = document.createElementNS('http://www.w3.org/2000/svg', elementType);
  1345. svgContainer.appendChild(element);
  1346. }
  1347. }
  1348. else {
  1349. // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it.
  1350. element = document.createElementNS('http://www.w3.org/2000/svg', elementType);
  1351. JSONcontainer[elementType] = {used: [], redundant: []};
  1352. svgContainer.appendChild(element);
  1353. }
  1354. JSONcontainer[elementType].used.push(element);
  1355. return element;
  1356. };
  1357. /**
  1358. * Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer
  1359. * the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this.
  1360. *
  1361. * @param elementType
  1362. * @param JSONcontainer
  1363. * @param DOMContainer
  1364. * @returns {*}
  1365. * @private
  1366. */
  1367. exports.getDOMElement = function (elementType, JSONcontainer, DOMContainer, insertBefore) {
  1368. var element;
  1369. // allocate DOM element, if it doesnt yet exist, create one.
  1370. if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
  1371. // check if there is an redundant element
  1372. if (JSONcontainer[elementType].redundant.length > 0) {
  1373. element = JSONcontainer[elementType].redundant[0];
  1374. JSONcontainer[elementType].redundant.shift();
  1375. }
  1376. else {
  1377. // create a new element and add it to the SVG
  1378. element = document.createElement(elementType);
  1379. if (insertBefore !== undefined) {
  1380. DOMContainer.insertBefore(element, insertBefore);
  1381. }
  1382. else {
  1383. DOMContainer.appendChild(element);
  1384. }
  1385. }
  1386. }
  1387. else {
  1388. // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it.
  1389. element = document.createElement(elementType);
  1390. JSONcontainer[elementType] = {used: [], redundant: []};
  1391. if (insertBefore !== undefined) {
  1392. DOMContainer.insertBefore(element, insertBefore);
  1393. }
  1394. else {
  1395. DOMContainer.appendChild(element);
  1396. }
  1397. }
  1398. JSONcontainer[elementType].used.push(element);
  1399. return element;
  1400. };
  1401. /**
  1402. * draw a point object. this is a seperate function because it can also be called by the legend.
  1403. * The reason the JSONcontainer and the target SVG svgContainer have to be supplied is so the legend can use these functions
  1404. * as well.
  1405. *
  1406. * @param x
  1407. * @param y
  1408. * @param group
  1409. * @param JSONcontainer
  1410. * @param svgContainer
  1411. * @returns {*}
  1412. */
  1413. exports.drawPoint = function(x, y, group, JSONcontainer, svgContainer) {
  1414. var point;
  1415. if (group.options.drawPoints.style == 'circle') {
  1416. point = exports.getSVGElement('circle',JSONcontainer,svgContainer);
  1417. point.setAttributeNS(null, "cx", x);
  1418. point.setAttributeNS(null, "cy", y);
  1419. point.setAttributeNS(null, "r", 0.5 * group.options.drawPoints.size);
  1420. point.setAttributeNS(null, "class", group.className + " point");
  1421. }
  1422. else {
  1423. point = exports.getSVGElement('rect',JSONcontainer,svgContainer);
  1424. point.setAttributeNS(null, "x", x - 0.5*group.options.drawPoints.size);
  1425. point.setAttributeNS(null, "y", y - 0.5*group.options.drawPoints.size);
  1426. point.setAttributeNS(null, "width", group.options.drawPoints.size);
  1427. point.setAttributeNS(null, "height", group.options.drawPoints.size);
  1428. point.setAttributeNS(null, "class", group.className + " point");
  1429. }
  1430. return point;
  1431. };
  1432. /**
  1433. * draw a bar SVG element centered on the X coordinate
  1434. *
  1435. * @param x
  1436. * @param y
  1437. * @param className
  1438. */
  1439. exports.drawBar = function (x, y, width, height, className, JSONcontainer, svgContainer) {
  1440. // if (height != 0) {
  1441. var rect = exports.getSVGElement('rect',JSONcontainer, svgContainer);
  1442. rect.setAttributeNS(null, "x", x - 0.5 * width);
  1443. rect.setAttributeNS(null, "y", y);
  1444. rect.setAttributeNS(null, "width", width);
  1445. rect.setAttributeNS(null, "height", height);
  1446. rect.setAttributeNS(null, "class", className);
  1447. // }
  1448. };
  1449. /***/ },
  1450. /* 3 */
  1451. /***/ function(module, exports, __webpack_require__) {
  1452. var util = __webpack_require__(1);
  1453. /**
  1454. * DataSet
  1455. *
  1456. * Usage:
  1457. * var dataSet = new DataSet({
  1458. * fieldId: '_id',
  1459. * type: {
  1460. * // ...
  1461. * }
  1462. * });
  1463. *
  1464. * dataSet.add(item);
  1465. * dataSet.add(data);
  1466. * dataSet.update(item);
  1467. * dataSet.update(data);
  1468. * dataSet.remove(id);
  1469. * dataSet.remove(ids);
  1470. * var data = dataSet.get();
  1471. * var data = dataSet.get(id);
  1472. * var data = dataSet.get(ids);
  1473. * var data = dataSet.get(ids, options, data);
  1474. * dataSet.clear();
  1475. *
  1476. * A data set can:
  1477. * - add/remove/update data
  1478. * - gives triggers upon changes in the data
  1479. * - can import/export data in various data formats
  1480. *
  1481. * @param {Array | DataTable} [data] Optional array with initial data
  1482. * @param {Object} [options] Available options:
  1483. * {String} fieldId Field name of the id in the
  1484. * items, 'id' by default.
  1485. * {Object.<String, String} type
  1486. * A map with field names as key,
  1487. * and the field type as value.
  1488. * @constructor DataSet
  1489. */
  1490. // TODO: add a DataSet constructor DataSet(data, options)
  1491. function DataSet (data, options) {
  1492. // correctly read optional arguments
  1493. if (data && !Array.isArray(data) && !util.isDataTable(data)) {
  1494. options = data;
  1495. data = null;
  1496. }
  1497. this._options = options || {};
  1498. this._data = {}; // map with data indexed by id
  1499. this._fieldId = this._options.fieldId || 'id'; // name of the field containing id
  1500. this._type = {}; // internal field types (NOTE: this can differ from this._options.type)
  1501. // all variants of a Date are internally stored as Date, so we can convert
  1502. // from everything to everything (also from ISODate to Number for example)
  1503. if (this._options.type) {
  1504. for (var field in this._options.type) {
  1505. if (this._options.type.hasOwnProperty(field)) {
  1506. var value = this._options.type[field];
  1507. if (value == 'Date' || value == 'ISODate' || value == 'ASPDate') {
  1508. this._type[field] = 'Date';
  1509. }
  1510. else {
  1511. this._type[field] = value;
  1512. }
  1513. }
  1514. }
  1515. }
  1516. // TODO: deprecated since version 1.1.1 (or 2.0.0?)
  1517. if (this._options.convert) {
  1518. throw new Error('Option "convert" is deprecated. Use "type" instead.');
  1519. }
  1520. this._subscribers = {}; // event subscribers
  1521. // add initial data when provided
  1522. if (data) {
  1523. this.add(data);
  1524. }
  1525. }
  1526. /**
  1527. * Subscribe to an event, add an event listener
  1528. * @param {String} event Event name. Available events: 'put', 'update',
  1529. * 'remove'
  1530. * @param {function} callback Callback method. Called with three parameters:
  1531. * {String} event
  1532. * {Object | null} params
  1533. * {String | Number} senderId
  1534. */
  1535. DataSet.prototype.on = function(event, callback) {
  1536. var subscribers = this._subscribers[event];
  1537. if (!subscribers) {
  1538. subscribers = [];
  1539. this._subscribers[event] = subscribers;
  1540. }
  1541. subscribers.push({
  1542. callback: callback
  1543. });
  1544. };
  1545. // TODO: make this function deprecated (replaced with `on` since version 0.5)
  1546. DataSet.prototype.subscribe = DataSet.prototype.on;
  1547. /**
  1548. * Unsubscribe from an event, remove an event listener
  1549. * @param {String} event
  1550. * @param {function} callback
  1551. */
  1552. DataSet.prototype.off = function(event, callback) {
  1553. var subscribers = this._subscribers[event];
  1554. if (subscribers) {
  1555. this._subscribers[event] = subscribers.filter(function (listener) {
  1556. return (listener.callback != callback);
  1557. });
  1558. }
  1559. };
  1560. // TODO: make this function deprecated (replaced with `on` since version 0.5)
  1561. DataSet.prototype.unsubscribe = DataSet.prototype.off;
  1562. /**
  1563. * Trigger an event
  1564. * @param {String} event
  1565. * @param {Object | null} params
  1566. * @param {String} [senderId] Optional id of the sender.
  1567. * @private
  1568. */
  1569. DataSet.prototype._trigger = function (event, params, senderId) {
  1570. if (event == '*') {
  1571. throw new Error('Cannot trigger event *');
  1572. }
  1573. var subscribers = [];
  1574. if (event in this._subscribers) {
  1575. subscribers = subscribers.concat(this._subscribers[event]);
  1576. }
  1577. if ('*' in this._subscribers) {
  1578. subscribers = subscribers.concat(this._subscribers['*']);
  1579. }
  1580. for (var i = 0; i < subscribers.length; i++) {
  1581. var subscriber = subscribers[i];
  1582. if (subscriber.callback) {
  1583. subscriber.callback(event, params, senderId || null);
  1584. }
  1585. }
  1586. };
  1587. /**
  1588. * Add data.
  1589. * Adding an item will fail when there already is an item with the same id.
  1590. * @param {Object | Array | DataTable} data
  1591. * @param {String} [senderId] Optional sender id
  1592. * @return {Array} addedIds Array with the ids of the added items
  1593. */
  1594. DataSet.prototype.add = function (data, senderId) {
  1595. var addedIds = [],
  1596. id,
  1597. me = this;
  1598. if (Array.isArray(data)) {
  1599. // Array
  1600. for (var i = 0, len = data.length; i < len; i++) {
  1601. id = me._addItem(data[i]);
  1602. addedIds.push(id);
  1603. }
  1604. }
  1605. else if (util.isDataTable(data)) {
  1606. // Google DataTable
  1607. var columns = this._getColumnNames(data);
  1608. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  1609. var item = {};
  1610. for (var col = 0, cols = columns.length; col < cols; col++) {
  1611. var field = columns[col];
  1612. item[field] = data.getValue(row, col);
  1613. }
  1614. id = me._addItem(item);
  1615. addedIds.push(id);
  1616. }
  1617. }
  1618. else if (data instanceof Object) {
  1619. // Single item
  1620. id = me._addItem(data);
  1621. addedIds.push(id);
  1622. }
  1623. else {
  1624. throw new Error('Unknown dataType');
  1625. }
  1626. if (addedIds.length) {
  1627. this._trigger('add', {items: addedIds}, senderId);
  1628. }
  1629. return addedIds;
  1630. };
  1631. /**
  1632. * Update existing items. When an item does not exist, it will be created
  1633. * @param {Object | Array | DataTable} data
  1634. * @param {String} [senderId] Optional sender id
  1635. * @return {Array} updatedIds The ids of the added or updated items
  1636. */
  1637. DataSet.prototype.update = function (data, senderId) {
  1638. var addedIds = [],
  1639. updatedIds = [],
  1640. me = this,
  1641. fieldId = me._fieldId;
  1642. var addOrUpdate = function (item) {
  1643. var id = item[fieldId];
  1644. if (me._data[id]) {
  1645. // update item
  1646. id = me._updateItem(item);
  1647. updatedIds.push(id);
  1648. }
  1649. else {
  1650. // add new item
  1651. id = me._addItem(item);
  1652. addedIds.push(id);
  1653. }
  1654. };
  1655. if (Array.isArray(data)) {
  1656. // Array
  1657. for (var i = 0, len = data.length; i < len; i++) {
  1658. addOrUpdate(data[i]);
  1659. }
  1660. }
  1661. else if (util.isDataTable(data)) {
  1662. // Google DataTable
  1663. var columns = this._getColumnNames(data);
  1664. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  1665. var item = {};
  1666. for (var col = 0, cols = columns.length; col < cols; col++) {
  1667. var field = columns[col];
  1668. item[field] = data.getValue(row, col);
  1669. }
  1670. addOrUpdate(item);
  1671. }
  1672. }
  1673. else if (data instanceof Object) {
  1674. // Single item
  1675. addOrUpdate(data);
  1676. }
  1677. else {
  1678. throw new Error('Unknown dataType');
  1679. }
  1680. if (addedIds.length) {
  1681. this._trigger('add', {items: addedIds}, senderId);
  1682. }
  1683. if (updatedIds.length) {
  1684. this._trigger('update', {items: updatedIds}, senderId);
  1685. }
  1686. return addedIds.concat(updatedIds);
  1687. };
  1688. /**
  1689. * Get a data item or multiple items.
  1690. *
  1691. * Usage:
  1692. *
  1693. * get()
  1694. * get(options: Object)
  1695. * get(options: Object, data: Array | DataTable)
  1696. *
  1697. * get(id: Number | String)
  1698. * get(id: Number | String, options: Object)
  1699. * get(id: Number | String, options: Object, data: Array | DataTable)
  1700. *
  1701. * get(ids: Number[] | String[])
  1702. * get(ids: Number[] | String[], options: Object)
  1703. * get(ids: Number[] | String[], options: Object, data: Array | DataTable)
  1704. *
  1705. * Where:
  1706. *
  1707. * {Number | String} id The id of an item
  1708. * {Number[] | String{}} ids An array with ids of items
  1709. * {Object} options An Object with options. Available options:
  1710. * {String} [returnType] Type of data to be
  1711. * returned. Can be 'DataTable' or 'Array' (default)
  1712. * {Object.<String, String>} [type]
  1713. * {String[]} [fields] field names to be returned
  1714. * {function} [filter] filter items
  1715. * {String | function} [order] Order the items by
  1716. * a field name or custom sort function.
  1717. * {Array | DataTable} [data] If provided, items will be appended to this
  1718. * array or table. Required in case of Google
  1719. * DataTable.
  1720. *
  1721. * @throws Error
  1722. */
  1723. DataSet.prototype.get = function (args) {
  1724. var me = this;
  1725. // parse the arguments
  1726. var id, ids, options, data;
  1727. var firstType = util.getType(arguments[0]);
  1728. if (firstType == 'String' || firstType == 'Number') {
  1729. // get(id [, options] [, data])
  1730. id = arguments[0];
  1731. options = arguments[1];
  1732. data = arguments[2];
  1733. }
  1734. else if (firstType == 'Array') {
  1735. // get(ids [, options] [, data])
  1736. ids = arguments[0];
  1737. options = arguments[1];
  1738. data = arguments[2];
  1739. }
  1740. else {
  1741. // get([, options] [, data])
  1742. options = arguments[0];
  1743. data = arguments[1];
  1744. }
  1745. // determine the return type
  1746. var returnType;
  1747. if (options && options.returnType) {
  1748. var allowedValues = ["DataTable", "Array", "Object"];
  1749. returnType = allowedValues.indexOf(options.returnType) == -1 ? "Array" : options.returnType;
  1750. if (data && (returnType != util.getType(data))) {
  1751. throw new Error('Type of parameter "data" (' + util.getType(data) + ') ' +
  1752. 'does not correspond with specified options.type (' + options.type + ')');
  1753. }
  1754. if (returnType == 'DataTable' && !util.isDataTable(data)) {
  1755. throw new Error('Parameter "data" must be a DataTable ' +
  1756. 'when options.type is "DataTable"');
  1757. }
  1758. }
  1759. else if (data) {
  1760. returnType = (util.getType(data) == 'DataTable') ? 'DataTable' : 'Array';
  1761. }
  1762. else {
  1763. returnType = 'Array';
  1764. }
  1765. // build options
  1766. var type = options && options.type || this._options.type;
  1767. var filter = options && options.filter;
  1768. var items = [], item, itemId, i, len;
  1769. // convert items
  1770. if (id != undefined) {
  1771. // return a single item
  1772. item = me._getItem(id, type);
  1773. if (filter && !filter(item)) {
  1774. item = null;
  1775. }
  1776. }
  1777. else if (ids != undefined) {
  1778. // return a subset of items
  1779. for (i = 0, len = ids.length; i < len; i++) {
  1780. item = me._getItem(ids[i], type);
  1781. if (!filter || filter(item)) {
  1782. items.push(item);
  1783. }
  1784. }
  1785. }
  1786. else {
  1787. // return all items
  1788. for (itemId in this._data) {
  1789. if (this._data.hasOwnProperty(itemId)) {
  1790. item = me._getItem(itemId, type);
  1791. if (!filter || filter(item)) {
  1792. items.push(item);
  1793. }
  1794. }
  1795. }
  1796. }
  1797. // order the results
  1798. if (options && options.order && id == undefined) {
  1799. this._sort(items, options.order);
  1800. }
  1801. // filter fields of the items
  1802. if (options && options.fields) {
  1803. var fields = options.fields;
  1804. if (id != undefined) {
  1805. item = this._filterFields(item, fields);
  1806. }
  1807. else {
  1808. for (i = 0, len = items.length; i < len; i++) {
  1809. items[i] = this._filterFields(items[i], fields);
  1810. }
  1811. }
  1812. }
  1813. // return the results
  1814. if (returnType == 'DataTable') {
  1815. var columns = this._getColumnNames(data);
  1816. if (id != undefined) {
  1817. // append a single item to the data table
  1818. me._appendRow(data, columns, item);
  1819. }
  1820. else {
  1821. // copy the items to the provided data table
  1822. for (i = 0; i < items.length; i++) {
  1823. me._appendRow(data, columns, items[i]);
  1824. }
  1825. }
  1826. return data;
  1827. }
  1828. else if (returnType == "Object") {
  1829. var result = {};
  1830. for (i = 0; i < items.length; i++) {
  1831. result[items[i].id] = items[i];
  1832. }
  1833. return result;
  1834. }
  1835. else {
  1836. // return an array
  1837. if (id != undefined) {
  1838. // a single item
  1839. return item;
  1840. }
  1841. else {
  1842. // multiple items
  1843. if (data) {
  1844. // copy the items to the provided array
  1845. for (i = 0, len = items.length; i < len; i++) {
  1846. data.push(items[i]);
  1847. }
  1848. return data;
  1849. }
  1850. else {
  1851. // just return our array
  1852. return items;
  1853. }
  1854. }
  1855. }
  1856. };
  1857. /**
  1858. * Get ids of all items or from a filtered set of items.
  1859. * @param {Object} [options] An Object with options. Available options:
  1860. * {function} [filter] filter items
  1861. * {String | function} [order] Order the items by
  1862. * a field name or custom sort function.
  1863. * @return {Array} ids
  1864. */
  1865. DataSet.prototype.getIds = function (options) {
  1866. var data = this._data,
  1867. filter = options && options.filter,
  1868. order = options && options.order,
  1869. type = options && options.type || this._options.type,
  1870. i,
  1871. len,
  1872. id,
  1873. item,
  1874. items,
  1875. ids = [];
  1876. if (filter) {
  1877. // get filtered items
  1878. if (order) {
  1879. // create ordered list
  1880. items = [];
  1881. for (id in data) {
  1882. if (data.hasOwnProperty(id)) {
  1883. item = this._getItem(id, type);
  1884. if (filter(item)) {
  1885. items.push(item);
  1886. }
  1887. }
  1888. }
  1889. this._sort(items, order);
  1890. for (i = 0, len = items.length; i < len; i++) {
  1891. ids[i] = items[i][this._fieldId];
  1892. }
  1893. }
  1894. else {
  1895. // create unordered list
  1896. for (id in data) {
  1897. if (data.hasOwnProperty(id)) {
  1898. item = this._getItem(id, type);
  1899. if (filter(item)) {
  1900. ids.push(item[this._fieldId]);
  1901. }
  1902. }
  1903. }
  1904. }
  1905. }
  1906. else {
  1907. // get all items
  1908. if (order) {
  1909. // create an ordered list
  1910. items = [];
  1911. for (id in data) {
  1912. if (data.hasOwnProperty(id)) {
  1913. items.push(data[id]);
  1914. }
  1915. }
  1916. this._sort(items, order);
  1917. for (i = 0, len = items.length; i < len; i++) {
  1918. ids[i] = items[i][this._fieldId];
  1919. }
  1920. }
  1921. else {
  1922. // create unordered list
  1923. for (id in data) {
  1924. if (data.hasOwnProperty(id)) {
  1925. item = data[id];
  1926. ids.push(item[this._fieldId]);
  1927. }
  1928. }
  1929. }
  1930. }
  1931. return ids;
  1932. };
  1933. /**
  1934. * Returns the DataSet itself. Is overwritten for example by the DataView,
  1935. * which returns the DataSet it is connected to instead.
  1936. */
  1937. DataSet.prototype.getDataSet = function () {
  1938. return this;
  1939. };
  1940. /**
  1941. * Execute a callback function for every item in the dataset.
  1942. * @param {function} callback
  1943. * @param {Object} [options] Available options:
  1944. * {Object.<String, String>} [type]
  1945. * {String[]} [fields] filter fields
  1946. * {function} [filter] filter items
  1947. * {String | function} [order] Order the items by
  1948. * a field name or custom sort function.
  1949. */
  1950. DataSet.prototype.forEach = function (callback, options) {
  1951. var filter = options && options.filter,
  1952. type = options && options.type || this._options.type,
  1953. data = this._data,
  1954. item,
  1955. id;
  1956. if (options && options.order) {
  1957. // execute forEach on ordered list
  1958. var items = this.get(options);
  1959. for (var i = 0, len = items.length; i < len; i++) {
  1960. item = items[i];
  1961. id = item[this._fieldId];
  1962. callback(item, id);
  1963. }
  1964. }
  1965. else {
  1966. // unordered
  1967. for (id in data) {
  1968. if (data.hasOwnProperty(id)) {
  1969. item = this._getItem(id, type);
  1970. if (!filter || filter(item)) {
  1971. callback(item, id);
  1972. }
  1973. }
  1974. }
  1975. }
  1976. };
  1977. /**
  1978. * Map every item in the dataset.
  1979. * @param {function} callback
  1980. * @param {Object} [options] Available options:
  1981. * {Object.<String, String>} [type]
  1982. * {String[]} [fields] filter fields
  1983. * {function} [filter] filter items
  1984. * {String | function} [order] Order the items by
  1985. * a field name or custom sort function.
  1986. * @return {Object[]} mappedItems
  1987. */
  1988. DataSet.prototype.map = function (callback, options) {
  1989. var filter = options && options.filter,
  1990. type = options && options.type || this._options.type,
  1991. mappedItems = [],
  1992. data = this._data,
  1993. item;
  1994. // convert and filter items
  1995. for (var id in data) {
  1996. if (data.hasOwnProperty(id)) {
  1997. item = this._getItem(id, type);
  1998. if (!filter || filter(item)) {
  1999. mappedItems.push(callback(item, id));
  2000. }
  2001. }
  2002. }
  2003. // order items
  2004. if (options && options.order) {
  2005. this._sort(mappedItems, options.order);
  2006. }
  2007. return mappedItems;
  2008. };
  2009. /**
  2010. * Filter the fields of an item
  2011. * @param {Object} item
  2012. * @param {String[]} fields Field names
  2013. * @return {Object} filteredItem
  2014. * @private
  2015. */
  2016. DataSet.prototype._filterFields = function (item, fields) {
  2017. var filteredItem = {};
  2018. for (var field in item) {
  2019. if (item.hasOwnProperty(field) && (fields.indexOf(field) != -1)) {
  2020. filteredItem[field] = item[field];
  2021. }
  2022. }
  2023. return filteredItem;
  2024. };
  2025. /**
  2026. * Sort the provided array with items
  2027. * @param {Object[]} items
  2028. * @param {String | function} order A field name or custom sort function.
  2029. * @private
  2030. */
  2031. DataSet.prototype._sort = function (items, order) {
  2032. if (util.isString(order)) {
  2033. // order by provided field name
  2034. var name = order; // field name
  2035. items.sort(function (a, b) {
  2036. var av = a[name];
  2037. var bv = b[name];
  2038. return (av > bv) ? 1 : ((av < bv) ? -1 : 0);
  2039. });
  2040. }
  2041. else if (typeof order === 'function') {
  2042. // order by sort function
  2043. items.sort(order);
  2044. }
  2045. // TODO: extend order by an Object {field:String, direction:String}
  2046. // where direction can be 'asc' or 'desc'
  2047. else {
  2048. throw new TypeError('Order must be a function or a string');
  2049. }
  2050. };
  2051. /**
  2052. * Remove an object by pointer or by id
  2053. * @param {String | Number | Object | Array} id Object or id, or an array with
  2054. * objects or ids to be removed
  2055. * @param {String} [senderId] Optional sender id
  2056. * @return {Array} removedIds
  2057. */
  2058. DataSet.prototype.remove = function (id, senderId) {
  2059. var removedIds = [],
  2060. i, len, removedId;
  2061. if (Array.isArray(id)) {
  2062. for (i = 0, len = id.length; i < len; i++) {
  2063. removedId = this._remove(id[i]);
  2064. if (removedId != null) {
  2065. removedIds.push(removedId);
  2066. }
  2067. }
  2068. }
  2069. else {
  2070. removedId = this._remove(id);
  2071. if (removedId != null) {
  2072. removedIds.push(removedId);
  2073. }
  2074. }
  2075. if (removedIds.length) {
  2076. this._trigger('remove', {items: removedIds}, senderId);
  2077. }
  2078. return removedIds;
  2079. };
  2080. /**
  2081. * Remove an item by its id
  2082. * @param {Number | String | Object} id id or item
  2083. * @returns {Number | String | null} id
  2084. * @private
  2085. */
  2086. DataSet.prototype._remove = function (id) {
  2087. if (util.isNumber(id) || util.isString(id)) {
  2088. if (this._data[id]) {
  2089. delete this._data[id];
  2090. return id;
  2091. }
  2092. }
  2093. else if (id instanceof Object) {
  2094. var itemId = id[this._fieldId];
  2095. if (itemId && this._data[itemId]) {
  2096. delete this._data[itemId];
  2097. return itemId;
  2098. }
  2099. }
  2100. return null;
  2101. };
  2102. /**
  2103. * Clear the data
  2104. * @param {String} [senderId] Optional sender id
  2105. * @return {Array} removedIds The ids of all removed items
  2106. */
  2107. DataSet.prototype.clear = function (senderId) {
  2108. var ids = Object.keys(this._data);
  2109. this._data = {};
  2110. this._trigger('remove', {items: ids}, senderId);
  2111. return ids;
  2112. };
  2113. /**
  2114. * Find the item with maximum value of a specified field
  2115. * @param {String} field
  2116. * @return {Object | null} item Item containing max value, or null if no items
  2117. */
  2118. DataSet.prototype.max = function (field) {
  2119. var data = this._data,
  2120. max = null,
  2121. maxField = null;
  2122. for (var id in data) {
  2123. if (data.hasOwnProperty(id)) {
  2124. var item = data[id];
  2125. var itemField = item[field];
  2126. if (itemField != null && (!max || itemField > maxField)) {
  2127. max = item;
  2128. maxField = itemField;
  2129. }
  2130. }
  2131. }
  2132. return max;
  2133. };
  2134. /**
  2135. * Find the item with minimum value of a specified field
  2136. * @param {String} field
  2137. * @return {Object | null} item Item containing max value, or null if no items
  2138. */
  2139. DataSet.prototype.min = function (field) {
  2140. var data = this._data,
  2141. min = null,
  2142. minField = null;
  2143. for (var id in data) {
  2144. if (data.hasOwnProperty(id)) {
  2145. var item = data[id];
  2146. var itemField = item[field];
  2147. if (itemField != null && (!min || itemField < minField)) {
  2148. min = item;
  2149. minField = itemField;
  2150. }
  2151. }
  2152. }
  2153. return min;
  2154. };
  2155. /**
  2156. * Find all distinct values of a specified field
  2157. * @param {String} field
  2158. * @return {Array} values Array containing all distinct values. If data items
  2159. * do not contain the specified field are ignored.
  2160. * The returned array is unordered.
  2161. */
  2162. DataSet.prototype.distinct = function (field) {
  2163. var data = this._data;
  2164. var values = [];
  2165. var fieldType = this._options.type && this._options.type[field] || null;
  2166. var count = 0;
  2167. var i;
  2168. for (var prop in data) {
  2169. if (data.hasOwnProperty(prop)) {
  2170. var item = data[prop];
  2171. var value = item[field];
  2172. var exists = false;
  2173. for (i = 0; i < count; i++) {
  2174. if (values[i] == value) {
  2175. exists = true;
  2176. break;
  2177. }
  2178. }
  2179. if (!exists && (value !== undefined)) {
  2180. values[count] = value;
  2181. count++;
  2182. }
  2183. }
  2184. }
  2185. if (fieldType) {
  2186. for (i = 0; i < values.length; i++) {
  2187. values[i] = util.convert(values[i], fieldType);
  2188. }
  2189. }
  2190. return values;
  2191. };
  2192. /**
  2193. * Add a single item. Will fail when an item with the same id already exists.
  2194. * @param {Object} item
  2195. * @return {String} id
  2196. * @private
  2197. */
  2198. DataSet.prototype._addItem = function (item) {
  2199. var id = item[this._fieldId];
  2200. if (id != undefined) {
  2201. // check whether this id is already taken
  2202. if (this._data[id]) {
  2203. // item already exists
  2204. throw new Error('Cannot add item: item with id ' + id + ' already exists');
  2205. }
  2206. }
  2207. else {
  2208. // generate an id
  2209. id = util.randomUUID();
  2210. item[this._fieldId] = id;
  2211. }
  2212. var d = {};
  2213. for (var field in item) {
  2214. if (item.hasOwnProperty(field)) {
  2215. var fieldType = this._type[field]; // type may be undefined
  2216. d[field] = util.convert(item[field], fieldType);
  2217. }
  2218. }
  2219. this._data[id] = d;
  2220. return id;
  2221. };
  2222. /**
  2223. * Get an item. Fields can be converted to a specific type
  2224. * @param {String} id
  2225. * @param {Object.<String, String>} [types] field types to convert
  2226. * @return {Object | null} item
  2227. * @private
  2228. */
  2229. DataSet.prototype._getItem = function (id, types) {
  2230. var field, value;
  2231. // get the item from the dataset
  2232. var raw = this._data[id];
  2233. if (!raw) {
  2234. return null;
  2235. }
  2236. // convert the items field types
  2237. var converted = {};
  2238. if (types) {
  2239. for (field in raw) {
  2240. if (raw.hasOwnProperty(field)) {
  2241. value = raw[field];
  2242. converted[field] = util.convert(value, types[field]);
  2243. }
  2244. }
  2245. }
  2246. else {
  2247. // no field types specified, no converting needed
  2248. for (field in raw) {
  2249. if (raw.hasOwnProperty(field)) {
  2250. value = raw[field];
  2251. converted[field] = value;
  2252. }
  2253. }
  2254. }
  2255. return converted;
  2256. };
  2257. /**
  2258. * Update a single item: merge with existing item.
  2259. * Will fail when the item has no id, or when there does not exist an item
  2260. * with the same id.
  2261. * @param {Object} item
  2262. * @return {String} id
  2263. * @private
  2264. */
  2265. DataSet.prototype._updateItem = function (item) {
  2266. var id = item[this._fieldId];
  2267. if (id == undefined) {
  2268. throw new Error('Cannot update item: item has no id (item: ' + JSON.stringify(item) + ')');
  2269. }
  2270. var d = this._data[id];
  2271. if (!d) {
  2272. // item doesn't exist
  2273. throw new Error('Cannot update item: no item with id ' + id + ' found');
  2274. }
  2275. // merge with current item
  2276. for (var field in item) {
  2277. if (item.hasOwnProperty(field)) {
  2278. var fieldType = this._type[field]; // type may be undefined
  2279. d[field] = util.convert(item[field], fieldType);
  2280. }
  2281. }
  2282. return id;
  2283. };
  2284. /**
  2285. * Get an array with the column names of a Google DataTable
  2286. * @param {DataTable} dataTable
  2287. * @return {String[]} columnNames
  2288. * @private
  2289. */
  2290. DataSet.prototype._getColumnNames = function (dataTable) {
  2291. var columns = [];
  2292. for (var col = 0, cols = dataTable.getNumberOfColumns(); col < cols; col++) {
  2293. columns[col] = dataTable.getColumnId(col) || dataTable.getColumnLabel(col);
  2294. }
  2295. return columns;
  2296. };
  2297. /**
  2298. * Append an item as a row to the dataTable
  2299. * @param dataTable
  2300. * @param columns
  2301. * @param item
  2302. * @private
  2303. */
  2304. DataSet.prototype._appendRow = function (dataTable, columns, item) {
  2305. var row = dataTable.addRow();
  2306. for (var col = 0, cols = columns.length; col < cols; col++) {
  2307. var field = columns[col];
  2308. dataTable.setValue(row, col, item[field]);
  2309. }
  2310. };
  2311. module.exports = DataSet;
  2312. /***/ },
  2313. /* 4 */
  2314. /***/ function(module, exports, __webpack_require__) {
  2315. var util = __webpack_require__(1);
  2316. var DataSet = __webpack_require__(3);
  2317. /**
  2318. * DataView
  2319. *
  2320. * a dataview offers a filtered view on a dataset or an other dataview.
  2321. *
  2322. * @param {DataSet | DataView} data
  2323. * @param {Object} [options] Available options: see method get
  2324. *
  2325. * @constructor DataView
  2326. */
  2327. function DataView (data, options) {
  2328. this._data = null;
  2329. this._ids = {}; // ids of the items currently in memory (just contains a boolean true)
  2330. this._options = options || {};
  2331. this._fieldId = 'id'; // name of the field containing id
  2332. this._subscribers = {}; // event subscribers
  2333. var me = this;
  2334. this.listener = function () {
  2335. me._onEvent.apply(me, arguments);
  2336. };
  2337. this.setData(data);
  2338. }
  2339. // TODO: implement a function .config() to dynamically update things like configured filter
  2340. // and trigger changes accordingly
  2341. /**
  2342. * Set a data source for the view
  2343. * @param {DataSet | DataView} data
  2344. */
  2345. DataView.prototype.setData = function (data) {
  2346. var ids, i, len;
  2347. if (this._data) {
  2348. // unsubscribe from current dataset
  2349. if (this._data.unsubscribe) {
  2350. this._data.unsubscribe('*', this.listener);
  2351. }
  2352. // trigger a remove of all items in memory
  2353. ids = [];
  2354. for (var id in this._ids) {
  2355. if (this._ids.hasOwnProperty(id)) {
  2356. ids.push(id);
  2357. }
  2358. }
  2359. this._ids = {};
  2360. this._trigger('remove', {items: ids});
  2361. }
  2362. this._data = data;
  2363. if (this._data) {
  2364. // update fieldId
  2365. this._fieldId = this._options.fieldId ||
  2366. (this._data && this._data.options && this._data.options.fieldId) ||
  2367. 'id';
  2368. // trigger an add of all added items
  2369. ids = this._data.getIds({filter: this._options && this._options.filter});
  2370. for (i = 0, len = ids.length; i < len; i++) {
  2371. id = ids[i];
  2372. this._ids[id] = true;
  2373. }
  2374. this._trigger('add', {items: ids});
  2375. // subscribe to new dataset
  2376. if (this._data.on) {
  2377. this._data.on('*', this.listener);
  2378. }
  2379. }
  2380. };
  2381. /**
  2382. * Get data from the data view
  2383. *
  2384. * Usage:
  2385. *
  2386. * get()
  2387. * get(options: Object)
  2388. * get(options: Object, data: Array | DataTable)
  2389. *
  2390. * get(id: Number)
  2391. * get(id: Number, options: Object)
  2392. * get(id: Number, options: Object, data: Array | DataTable)
  2393. *
  2394. * get(ids: Number[])
  2395. * get(ids: Number[], options: Object)
  2396. * get(ids: Number[], options: Object, data: Array | DataTable)
  2397. *
  2398. * Where:
  2399. *
  2400. * {Number | String} id The id of an item
  2401. * {Number[] | String{}} ids An array with ids of items
  2402. * {Object} options An Object with options. Available options:
  2403. * {String} [type] Type of data to be returned. Can
  2404. * be 'DataTable' or 'Array' (default)
  2405. * {Object.<String, String>} [convert]
  2406. * {String[]} [fields] field names to be returned
  2407. * {function} [filter] filter items
  2408. * {String | function} [order] Order the items by
  2409. * a field name or custom sort function.
  2410. * {Array | DataTable} [data] If provided, items will be appended to this
  2411. * array or table. Required in case of Google
  2412. * DataTable.
  2413. * @param args
  2414. */
  2415. DataView.prototype.get = function (args) {
  2416. var me = this;
  2417. // parse the arguments
  2418. var ids, options, data;
  2419. var firstType = util.getType(arguments[0]);
  2420. if (firstType == 'String' || firstType == 'Number' || firstType == 'Array') {
  2421. // get(id(s) [, options] [, data])
  2422. ids = arguments[0]; // can be a single id or an array with ids
  2423. options = arguments[1];
  2424. data = arguments[2];
  2425. }
  2426. else {
  2427. // get([, options] [, data])
  2428. options = arguments[0];
  2429. data = arguments[1];
  2430. }
  2431. // extend the options with the default options and provided options
  2432. var viewOptions = util.extend({}, this._options, options);
  2433. // create a combined filter method when needed
  2434. if (this._options.filter && options && options.filter) {
  2435. viewOptions.filter = function (item) {
  2436. return me._options.filter(item) && options.filter(item);
  2437. }
  2438. }
  2439. // build up the call to the linked data set
  2440. var getArguments = [];
  2441. if (ids != undefined) {
  2442. getArguments.push(ids);
  2443. }
  2444. getArguments.push(viewOptions);
  2445. getArguments.push(data);
  2446. return this._data && this._data.get.apply(this._data, getArguments);
  2447. };
  2448. /**
  2449. * Get ids of all items or from a filtered set of items.
  2450. * @param {Object} [options] An Object with options. Available options:
  2451. * {function} [filter] filter items
  2452. * {String | function} [order] Order the items by
  2453. * a field name or custom sort function.
  2454. * @return {Array} ids
  2455. */
  2456. DataView.prototype.getIds = function (options) {
  2457. var ids;
  2458. if (this._data) {
  2459. var defaultFilter = this._options.filter;
  2460. var filter;
  2461. if (options && options.filter) {
  2462. if (defaultFilter) {
  2463. filter = function (item) {
  2464. return defaultFilter(item) && options.filter(item);
  2465. }
  2466. }
  2467. else {
  2468. filter = options.filter;
  2469. }
  2470. }
  2471. else {
  2472. filter = defaultFilter;
  2473. }
  2474. ids = this._data.getIds({
  2475. filter: filter,
  2476. order: options && options.order
  2477. });
  2478. }
  2479. else {
  2480. ids = [];
  2481. }
  2482. return ids;
  2483. };
  2484. /**
  2485. * Get the DataSet to which this DataView is connected. In case there is a chain
  2486. * of multiple DataViews, the root DataSet of this chain is returned.
  2487. * @return {DataSet} dataSet
  2488. */
  2489. DataView.prototype.getDataSet = function () {
  2490. var dataSet = this;
  2491. while (dataSet instanceof DataView) {
  2492. dataSet = dataSet._data;
  2493. }
  2494. return dataSet || null;
  2495. };
  2496. /**
  2497. * Event listener. Will propagate all events from the connected data set to
  2498. * the subscribers of the DataView, but will filter the items and only trigger
  2499. * when there are changes in the filtered data set.
  2500. * @param {String} event
  2501. * @param {Object | null} params
  2502. * @param {String} senderId
  2503. * @private
  2504. */
  2505. DataView.prototype._onEvent = function (event, params, senderId) {
  2506. var i, len, id, item,
  2507. ids = params && params.items,
  2508. data = this._data,
  2509. added = [],
  2510. updated = [],
  2511. removed = [];
  2512. if (ids && data) {
  2513. switch (event) {
  2514. case 'add':
  2515. // filter the ids of the added items
  2516. for (i = 0, len = ids.length; i < len; i++) {
  2517. id = ids[i];
  2518. item = this.get(id);
  2519. if (item) {
  2520. this._ids[id] = true;
  2521. added.push(id);
  2522. }
  2523. }
  2524. break;
  2525. case 'update':
  2526. // determine the event from the views viewpoint: an updated
  2527. // item can be added, updated, or removed from this view.
  2528. for (i = 0, len = ids.length; i < len; i++) {
  2529. id = ids[i];
  2530. item = this.get(id);
  2531. if (item) {
  2532. if (this._ids[id]) {
  2533. updated.push(id);
  2534. }
  2535. else {
  2536. this._ids[id] = true;
  2537. added.push(id);
  2538. }
  2539. }
  2540. else {
  2541. if (this._ids[id]) {
  2542. delete this._ids[id];
  2543. removed.push(id);
  2544. }
  2545. else {
  2546. // nothing interesting for me :-(
  2547. }
  2548. }
  2549. }
  2550. break;
  2551. case 'remove':
  2552. // filter the ids of the removed items
  2553. for (i = 0, len = ids.length; i < len; i++) {
  2554. id = ids[i];
  2555. if (this._ids[id]) {
  2556. delete this._ids[id];
  2557. removed.push(id);
  2558. }
  2559. }
  2560. break;
  2561. }
  2562. if (added.length) {
  2563. this._trigger('add', {items: added}, senderId);
  2564. }
  2565. if (updated.length) {
  2566. this._trigger('update', {items: updated}, senderId);
  2567. }
  2568. if (removed.length) {
  2569. this._trigger('remove', {items: removed}, senderId);
  2570. }
  2571. }
  2572. };
  2573. // copy subscription functionality from DataSet
  2574. DataView.prototype.on = DataSet.prototype.on;
  2575. DataView.prototype.off = DataSet.prototype.off;
  2576. DataView.prototype._trigger = DataSet.prototype._trigger;
  2577. // TODO: make these functions deprecated (replaced with `on` and `off` since version 0.5)
  2578. DataView.prototype.subscribe = DataView.prototype.on;
  2579. DataView.prototype.unsubscribe = DataView.prototype.off;
  2580. module.exports = DataView;
  2581. /***/ },
  2582. /* 5 */
  2583. /***/ function(module, exports, __webpack_require__) {
  2584. var Emitter = __webpack_require__(49);
  2585. var DataSet = __webpack_require__(3);
  2586. var DataView = __webpack_require__(4);
  2587. var util = __webpack_require__(1);
  2588. var Point3d = __webpack_require__(9);
  2589. var Point2d = __webpack_require__(8);
  2590. var Camera = __webpack_require__(6);
  2591. var Filter = __webpack_require__(7);
  2592. var Slider = __webpack_require__(10);
  2593. var StepNumber = __webpack_require__(11);
  2594. /**
  2595. * @constructor Graph3d
  2596. * Graph3d displays data in 3d.
  2597. *
  2598. * Graph3d is developed in javascript as a Google Visualization Chart.
  2599. *
  2600. * @param {Element} container The DOM element in which the Graph3d will
  2601. * be created. Normally a div element.
  2602. * @param {DataSet | DataView | Array} [data]
  2603. * @param {Object} [options]
  2604. */
  2605. function Graph3d(container, data, options) {
  2606. if (!(this instanceof Graph3d)) {
  2607. throw new SyntaxError('Constructor must be called with the new operator');
  2608. }
  2609. // create variables and set default values
  2610. this.containerElement = container;
  2611. this.width = '400px';
  2612. this.height = '400px';
  2613. this.margin = 10; // px
  2614. this.defaultXCenter = '55%';
  2615. this.defaultYCenter = '50%';
  2616. this.xLabel = 'x';
  2617. this.yLabel = 'y';
  2618. this.zLabel = 'z';
  2619. this.filterLabel = 'time';
  2620. this.legendLabel = 'value';
  2621. this.style = Graph3d.STYLE.DOT;
  2622. this.showPerspective = true;
  2623. this.showGrid = true;
  2624. this.keepAspectRatio = true;
  2625. this.showShadow = false;
  2626. this.showGrayBottom = false; // TODO: this does not work correctly
  2627. this.showTooltip = false;
  2628. this.verticalRatio = 0.5; // 0.1 to 1.0, where 1.0 results in a 'cube'
  2629. this.animationInterval = 1000; // milliseconds
  2630. this.animationPreload = false;
  2631. this.camera = new Camera();
  2632. this.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?
  2633. this.dataTable = null; // The original data table
  2634. this.dataPoints = null; // The table with point objects
  2635. // the column indexes
  2636. this.colX = undefined;
  2637. this.colY = undefined;
  2638. this.colZ = undefined;
  2639. this.colValue = undefined;
  2640. this.colFilter = undefined;
  2641. this.xMin = 0;
  2642. this.xStep = undefined; // auto by default
  2643. this.xMax = 1;
  2644. this.yMin = 0;
  2645. this.yStep = undefined; // auto by default
  2646. this.yMax = 1;
  2647. this.zMin = 0;
  2648. this.zStep = undefined; // auto by default
  2649. this.zMax = 1;
  2650. this.valueMin = 0;
  2651. this.valueMax = 1;
  2652. this.xBarWidth = 1;
  2653. this.yBarWidth = 1;
  2654. // TODO: customize axis range
  2655. // constants
  2656. this.colorAxis = '#4D4D4D';
  2657. this.colorGrid = '#D3D3D3';
  2658. this.colorDot = '#7DC1FF';
  2659. this.colorDotBorder = '#3267D2';
  2660. // create a frame and canvas
  2661. this.create();
  2662. // apply options (also when undefined)
  2663. this.setOptions(options);
  2664. // apply data
  2665. if (data) {
  2666. this.setData(data);
  2667. }
  2668. }
  2669. // Extend Graph3d with an Emitter mixin
  2670. Emitter(Graph3d.prototype);
  2671. /**
  2672. * Calculate the scaling values, dependent on the range in x, y, and z direction
  2673. */
  2674. Graph3d.prototype._setScale = function() {
  2675. this.scale = new Point3d(1 / (this.xMax - this.xMin),
  2676. 1 / (this.yMax - this.yMin),
  2677. 1 / (this.zMax - this.zMin));
  2678. // keep aspect ration between x and y scale if desired
  2679. if (this.keepAspectRatio) {
  2680. if (this.scale.x < this.scale.y) {
  2681. //noinspection JSSuspiciousNameCombination
  2682. this.scale.y = this.scale.x;
  2683. }
  2684. else {
  2685. //noinspection JSSuspiciousNameCombination
  2686. this.scale.x = this.scale.y;
  2687. }
  2688. }
  2689. // scale the vertical axis
  2690. this.scale.z *= this.verticalRatio;
  2691. // TODO: can this be automated? verticalRatio?
  2692. // determine scale for (optional) value
  2693. this.scale.value = 1 / (this.valueMax - this.valueMin);
  2694. // position the camera arm
  2695. var xCenter = (this.xMax + this.xMin) / 2 * this.scale.x;
  2696. var yCenter = (this.yMax + this.yMin) / 2 * this.scale.y;
  2697. var zCenter = (this.zMax + this.zMin) / 2 * this.scale.z;
  2698. this.camera.setArmLocation(xCenter, yCenter, zCenter);
  2699. };
  2700. /**
  2701. * Convert a 3D location to a 2D location on screen
  2702. * http://en.wikipedia.org/wiki/3D_projection
  2703. * @param {Point3d} point3d A 3D point with parameters x, y, z
  2704. * @return {Point2d} point2d A 2D point with parameters x, y
  2705. */
  2706. Graph3d.prototype._convert3Dto2D = function(point3d) {
  2707. var translation = this._convertPointToTranslation(point3d);
  2708. return this._convertTranslationToScreen(translation);
  2709. };
  2710. /**
  2711. * Convert a 3D location its translation seen from the camera
  2712. * http://en.wikipedia.org/wiki/3D_projection
  2713. * @param {Point3d} point3d A 3D point with parameters x, y, z
  2714. * @return {Point3d} translation A 3D point with parameters x, y, z This is
  2715. * the translation of the point, seen from the
  2716. * camera
  2717. */
  2718. Graph3d.prototype._convertPointToTranslation = function(point3d) {
  2719. var ax = point3d.x * this.scale.x,
  2720. ay = point3d.y * this.scale.y,
  2721. az = point3d.z * this.scale.z,
  2722. cx = this.camera.getCameraLocation().x,
  2723. cy = this.camera.getCameraLocation().y,
  2724. cz = this.camera.getCameraLocation().z,
  2725. // calculate angles
  2726. sinTx = Math.sin(this.camera.getCameraRotation().x),
  2727. cosTx = Math.cos(this.camera.getCameraRotation().x),
  2728. sinTy = Math.sin(this.camera.getCameraRotation().y),
  2729. cosTy = Math.cos(this.camera.getCameraRotation().y),
  2730. sinTz = Math.sin(this.camera.getCameraRotation().z),
  2731. cosTz = Math.cos(this.camera.getCameraRotation().z),
  2732. // calculate translation
  2733. dx = cosTy * (sinTz * (ay - cy) + cosTz * (ax - cx)) - sinTy * (az - cz),
  2734. dy = sinTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) + cosTx * (cosTz * (ay - cy) - sinTz * (ax-cx)),
  2735. dz = cosTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) - sinTx * (cosTz * (ay - cy) - sinTz * (ax-cx));
  2736. return new Point3d(dx, dy, dz);
  2737. };
  2738. /**
  2739. * Convert a translation point to a point on the screen
  2740. * @param {Point3d} translation A 3D point with parameters x, y, z This is
  2741. * the translation of the point, seen from the
  2742. * camera
  2743. * @return {Point2d} point2d A 2D point with parameters x, y
  2744. */
  2745. Graph3d.prototype._convertTranslationToScreen = function(translation) {
  2746. var ex = this.eye.x,
  2747. ey = this.eye.y,
  2748. ez = this.eye.z,
  2749. dx = translation.x,
  2750. dy = translation.y,
  2751. dz = translation.z;
  2752. // calculate position on screen from translation
  2753. var bx;
  2754. var by;
  2755. if (this.showPerspective) {
  2756. bx = (dx - ex) * (ez / dz);
  2757. by = (dy - ey) * (ez / dz);
  2758. }
  2759. else {
  2760. bx = dx * -(ez / this.camera.getArmLength());
  2761. by = dy * -(ez / this.camera.getArmLength());
  2762. }
  2763. // shift and scale the point to the center of the screen
  2764. // use the width of the graph to scale both horizontally and vertically.
  2765. return new Point2d(
  2766. this.xcenter + bx * this.frame.canvas.clientWidth,
  2767. this.ycenter - by * this.frame.canvas.clientWidth);
  2768. };
  2769. /**
  2770. * Set the background styling for the graph
  2771. * @param {string | {fill: string, stroke: string, strokeWidth: string}} backgroundColor
  2772. */
  2773. Graph3d.prototype._setBackgroundColor = function(backgroundColor) {
  2774. var fill = 'white';
  2775. var stroke = 'gray';
  2776. var strokeWidth = 1;
  2777. if (typeof(backgroundColor) === 'string') {
  2778. fill = backgroundColor;
  2779. stroke = 'none';
  2780. strokeWidth = 0;
  2781. }
  2782. else if (typeof(backgroundColor) === 'object') {
  2783. if (backgroundColor.fill !== undefined) fill = backgroundColor.fill;
  2784. if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke;
  2785. if (backgroundColor.strokeWidth !== undefined) strokeWidth = backgroundColor.strokeWidth;
  2786. }
  2787. else if (backgroundColor === undefined) {
  2788. // use use defaults
  2789. }
  2790. else {
  2791. throw 'Unsupported type of backgroundColor';
  2792. }
  2793. this.frame.style.backgroundColor = fill;
  2794. this.frame.style.borderColor = stroke;
  2795. this.frame.style.borderWidth = strokeWidth + 'px';
  2796. this.frame.style.borderStyle = 'solid';
  2797. };
  2798. /// enumerate the available styles
  2799. Graph3d.STYLE = {
  2800. BAR: 0,
  2801. BARCOLOR: 1,
  2802. BARSIZE: 2,
  2803. DOT : 3,
  2804. DOTLINE : 4,
  2805. DOTCOLOR: 5,
  2806. DOTSIZE: 6,
  2807. GRID : 7,
  2808. LINE: 8,
  2809. SURFACE : 9
  2810. };
  2811. /**
  2812. * Retrieve the style index from given styleName
  2813. * @param {string} styleName Style name such as 'dot', 'grid', 'dot-line'
  2814. * @return {Number} styleNumber Enumeration value representing the style, or -1
  2815. * when not found
  2816. */
  2817. Graph3d.prototype._getStyleNumber = function(styleName) {
  2818. switch (styleName) {
  2819. case 'dot': return Graph3d.STYLE.DOT;
  2820. case 'dot-line': return Graph3d.STYLE.DOTLINE;
  2821. case 'dot-color': return Graph3d.STYLE.DOTCOLOR;
  2822. case 'dot-size': return Graph3d.STYLE.DOTSIZE;
  2823. case 'line': return Graph3d.STYLE.LINE;
  2824. case 'grid': return Graph3d.STYLE.GRID;
  2825. case 'surface': return Graph3d.STYLE.SURFACE;
  2826. case 'bar': return Graph3d.STYLE.BAR;
  2827. case 'bar-color': return Graph3d.STYLE.BARCOLOR;
  2828. case 'bar-size': return Graph3d.STYLE.BARSIZE;
  2829. }
  2830. return -1;
  2831. };
  2832. /**
  2833. * Determine the indexes of the data columns, based on the given style and data
  2834. * @param {DataSet} data
  2835. * @param {Number} style
  2836. */
  2837. Graph3d.prototype._determineColumnIndexes = function(data, style) {
  2838. if (this.style === Graph3d.STYLE.DOT ||
  2839. this.style === Graph3d.STYLE.DOTLINE ||
  2840. this.style === Graph3d.STYLE.LINE ||
  2841. this.style === Graph3d.STYLE.GRID ||
  2842. this.style === Graph3d.STYLE.SURFACE ||
  2843. this.style === Graph3d.STYLE.BAR) {
  2844. // 3 columns expected, and optionally a 4th with filter values
  2845. this.colX = 0;
  2846. this.colY = 1;
  2847. this.colZ = 2;
  2848. this.colValue = undefined;
  2849. if (data.getNumberOfColumns() > 3) {
  2850. this.colFilter = 3;
  2851. }
  2852. }
  2853. else if (this.style === Graph3d.STYLE.DOTCOLOR ||
  2854. this.style === Graph3d.STYLE.DOTSIZE ||
  2855. this.style === Graph3d.STYLE.BARCOLOR ||
  2856. this.style === Graph3d.STYLE.BARSIZE) {
  2857. // 4 columns expected, and optionally a 5th with filter values
  2858. this.colX = 0;
  2859. this.colY = 1;
  2860. this.colZ = 2;
  2861. this.colValue = 3;
  2862. if (data.getNumberOfColumns() > 4) {
  2863. this.colFilter = 4;
  2864. }
  2865. }
  2866. else {
  2867. throw 'Unknown style "' + this.style + '"';
  2868. }
  2869. };
  2870. Graph3d.prototype.getNumberOfRows = function(data) {
  2871. return data.length;
  2872. }
  2873. Graph3d.prototype.getNumberOfColumns = function(data) {
  2874. var counter = 0;
  2875. for (var column in data[0]) {
  2876. if (data[0].hasOwnProperty(column)) {
  2877. counter++;
  2878. }
  2879. }
  2880. return counter;
  2881. }
  2882. Graph3d.prototype.getDistinctValues = function(data, column) {
  2883. var distinctValues = [];
  2884. for (var i = 0; i < data.length; i++) {
  2885. if (distinctValues.indexOf(data[i][column]) == -1) {
  2886. distinctValues.push(data[i][column]);
  2887. }
  2888. }
  2889. return distinctValues;
  2890. }
  2891. Graph3d.prototype.getColumnRange = function(data,column) {
  2892. var minMax = {min:data[0][column],max:data[0][column]};
  2893. for (var i = 0; i < data.length; i++) {
  2894. if (minMax.min > data[i][column]) { minMax.min = data[i][column]; }
  2895. if (minMax.max < data[i][column]) { minMax.max = data[i][column]; }
  2896. }
  2897. return minMax;
  2898. };
  2899. /**
  2900. * Initialize the data from the data table. Calculate minimum and maximum values
  2901. * and column index values
  2902. * @param {Array | DataSet | DataView} rawData The data containing the items for the Graph.
  2903. * @param {Number} style Style Number
  2904. */
  2905. Graph3d.prototype._dataInitialize = function (rawData, style) {
  2906. var me = this;
  2907. // unsubscribe from the dataTable
  2908. if (this.dataSet) {
  2909. this.dataSet.off('*', this._onChange);
  2910. }
  2911. if (rawData === undefined)
  2912. return;
  2913. if (Array.isArray(rawData)) {
  2914. rawData = new DataSet(rawData);
  2915. }
  2916. var data;
  2917. if (rawData instanceof DataSet || rawData instanceof DataView) {
  2918. data = rawData.get();
  2919. }
  2920. else {
  2921. throw new Error('Array, DataSet, or DataView expected');
  2922. }
  2923. if (data.length == 0)
  2924. return;
  2925. this.dataSet = rawData;
  2926. this.dataTable = data;
  2927. // subscribe to changes in the dataset
  2928. this._onChange = function () {
  2929. me.setData(me.dataSet);
  2930. };
  2931. this.dataSet.on('*', this._onChange);
  2932. // _determineColumnIndexes
  2933. // getNumberOfRows (points)
  2934. // getNumberOfColumns (x,y,z,v,t,t1,t2...)
  2935. // getDistinctValues (unique values?)
  2936. // getColumnRange
  2937. // determine the location of x,y,z,value,filter columns
  2938. this.colX = 'x';
  2939. this.colY = 'y';
  2940. this.colZ = 'z';
  2941. this.colValue = 'style';
  2942. this.colFilter = 'filter';
  2943. // check if a filter column is provided
  2944. if (data[0].hasOwnProperty('filter')) {
  2945. if (this.dataFilter === undefined) {
  2946. this.dataFilter = new Filter(rawData, this.colFilter, this);
  2947. this.dataFilter.setOnLoadCallback(function() {me.redraw();});
  2948. }
  2949. }
  2950. var withBars = this.style == Graph3d.STYLE.BAR ||
  2951. this.style == Graph3d.STYLE.BARCOLOR ||
  2952. this.style == Graph3d.STYLE.BARSIZE;
  2953. // determine barWidth from data
  2954. if (withBars) {
  2955. if (this.defaultXBarWidth !== undefined) {
  2956. this.xBarWidth = this.defaultXBarWidth;
  2957. }
  2958. else {
  2959. var dataX = this.getDistinctValues(data,this.colX);
  2960. this.xBarWidth = (dataX[1] - dataX[0]) || 1;
  2961. }
  2962. if (this.defaultYBarWidth !== undefined) {
  2963. this.yBarWidth = this.defaultYBarWidth;
  2964. }
  2965. else {
  2966. var dataY = this.getDistinctValues(data,this.colY);
  2967. this.yBarWidth = (dataY[1] - dataY[0]) || 1;
  2968. }
  2969. }
  2970. // calculate minimums and maximums
  2971. var xRange = this.getColumnRange(data,this.colX);
  2972. if (withBars) {
  2973. xRange.min -= this.xBarWidth / 2;
  2974. xRange.max += this.xBarWidth / 2;
  2975. }
  2976. this.xMin = (this.defaultXMin !== undefined) ? this.defaultXMin : xRange.min;
  2977. this.xMax = (this.defaultXMax !== undefined) ? this.defaultXMax : xRange.max;
  2978. if (this.xMax <= this.xMin) this.xMax = this.xMin + 1;
  2979. this.xStep = (this.defaultXStep !== undefined) ? this.defaultXStep : (this.xMax-this.xMin)/5;
  2980. var yRange = this.getColumnRange(data,this.colY);
  2981. if (withBars) {
  2982. yRange.min -= this.yBarWidth / 2;
  2983. yRange.max += this.yBarWidth / 2;
  2984. }
  2985. this.yMin = (this.defaultYMin !== undefined) ? this.defaultYMin : yRange.min;
  2986. this.yMax = (this.defaultYMax !== undefined) ? this.defaultYMax : yRange.max;
  2987. if (this.yMax <= this.yMin) this.yMax = this.yMin + 1;
  2988. this.yStep = (this.defaultYStep !== undefined) ? this.defaultYStep : (this.yMax-this.yMin)/5;
  2989. var zRange = this.getColumnRange(data,this.colZ);
  2990. this.zMin = (this.defaultZMin !== undefined) ? this.defaultZMin : zRange.min;
  2991. this.zMax = (this.defaultZMax !== undefined) ? this.defaultZMax : zRange.max;
  2992. if (this.zMax <= this.zMin) this.zMax = this.zMin + 1;
  2993. this.zStep = (this.defaultZStep !== undefined) ? this.defaultZStep : (this.zMax-this.zMin)/5;
  2994. if (this.colValue !== undefined) {
  2995. var valueRange = this.getColumnRange(data,this.colValue);
  2996. this.valueMin = (this.defaultValueMin !== undefined) ? this.defaultValueMin : valueRange.min;
  2997. this.valueMax = (this.defaultValueMax !== undefined) ? this.defaultValueMax : valueRange.max;
  2998. if (this.valueMax <= this.valueMin) this.valueMax = this.valueMin + 1;
  2999. }
  3000. // set the scale dependent on the ranges.
  3001. this._setScale();
  3002. };
  3003. /**
  3004. * Filter the data based on the current filter
  3005. * @param {Array} data
  3006. * @return {Array} dataPoints Array with point objects which can be drawn on screen
  3007. */
  3008. Graph3d.prototype._getDataPoints = function (data) {
  3009. // TODO: store the created matrix dataPoints in the filters instead of reloading each time
  3010. var x, y, i, z, obj, point;
  3011. var dataPoints = [];
  3012. if (this.style === Graph3d.STYLE.GRID ||
  3013. this.style === Graph3d.STYLE.SURFACE) {
  3014. // copy all values from the google data table to a matrix
  3015. // the provided values are supposed to form a grid of (x,y) positions
  3016. // create two lists with all present x and y values
  3017. var dataX = [];
  3018. var dataY = [];
  3019. for (i = 0; i < this.getNumberOfRows(data); i++) {
  3020. x = data[i][this.colX] || 0;
  3021. y = data[i][this.colY] || 0;
  3022. if (dataX.indexOf(x) === -1) {
  3023. dataX.push(x);
  3024. }
  3025. if (dataY.indexOf(y) === -1) {
  3026. dataY.push(y);
  3027. }
  3028. }
  3029. function sortNumber(a, b) {
  3030. return a - b;
  3031. }
  3032. dataX.sort(sortNumber);
  3033. dataY.sort(sortNumber);
  3034. // create a grid, a 2d matrix, with all values.
  3035. var dataMatrix = []; // temporary data matrix
  3036. for (i = 0; i < data.length; i++) {
  3037. x = data[i][this.colX] || 0;
  3038. y = data[i][this.colY] || 0;
  3039. z = data[i][this.colZ] || 0;
  3040. var xIndex = dataX.indexOf(x); // TODO: implement Array().indexOf() for Internet Explorer
  3041. var yIndex = dataY.indexOf(y);
  3042. if (dataMatrix[xIndex] === undefined) {
  3043. dataMatrix[xIndex] = [];
  3044. }
  3045. var point3d = new Point3d();
  3046. point3d.x = x;
  3047. point3d.y = y;
  3048. point3d.z = z;
  3049. obj = {};
  3050. obj.point = point3d;
  3051. obj.trans = undefined;
  3052. obj.screen = undefined;
  3053. obj.bottom = new Point3d(x, y, this.zMin);
  3054. dataMatrix[xIndex][yIndex] = obj;
  3055. dataPoints.push(obj);
  3056. }
  3057. // fill in the pointers to the neighbors.
  3058. for (x = 0; x < dataMatrix.length; x++) {
  3059. for (y = 0; y < dataMatrix[x].length; y++) {
  3060. if (dataMatrix[x][y]) {
  3061. dataMatrix[x][y].pointRight = (x < dataMatrix.length-1) ? dataMatrix[x+1][y] : undefined;
  3062. dataMatrix[x][y].pointTop = (y < dataMatrix[x].length-1) ? dataMatrix[x][y+1] : undefined;
  3063. dataMatrix[x][y].pointCross =
  3064. (x < dataMatrix.length-1 && y < dataMatrix[x].length-1) ?
  3065. dataMatrix[x+1][y+1] :
  3066. undefined;
  3067. }
  3068. }
  3069. }
  3070. }
  3071. else { // 'dot', 'dot-line', etc.
  3072. // copy all values from the google data table to a list with Point3d objects
  3073. for (i = 0; i < data.length; i++) {
  3074. point = new Point3d();
  3075. point.x = data[i][this.colX] || 0;
  3076. point.y = data[i][this.colY] || 0;
  3077. point.z = data[i][this.colZ] || 0;
  3078. if (this.colValue !== undefined) {
  3079. point.value = data[i][this.colValue] || 0;
  3080. }
  3081. obj = {};
  3082. obj.point = point;
  3083. obj.bottom = new Point3d(point.x, point.y, this.zMin);
  3084. obj.trans = undefined;
  3085. obj.screen = undefined;
  3086. dataPoints.push(obj);
  3087. }
  3088. }
  3089. return dataPoints;
  3090. };
  3091. /**
  3092. * Create the main frame for the Graph3d.
  3093. * This function is executed once when a Graph3d object is created. The frame
  3094. * contains a canvas, and this canvas contains all objects like the axis and
  3095. * nodes.
  3096. */
  3097. Graph3d.prototype.create = function () {
  3098. // remove all elements from the container element.
  3099. while (this.containerElement.hasChildNodes()) {
  3100. this.containerElement.removeChild(this.containerElement.firstChild);
  3101. }
  3102. this.frame = document.createElement('div');
  3103. this.frame.style.position = 'relative';
  3104. this.frame.style.overflow = 'hidden';
  3105. // create the graph canvas (HTML canvas element)
  3106. this.frame.canvas = document.createElement( 'canvas' );
  3107. this.frame.canvas.style.position = 'relative';
  3108. this.frame.appendChild(this.frame.canvas);
  3109. //if (!this.frame.canvas.getContext) {
  3110. {
  3111. var noCanvas = document.createElement( 'DIV' );
  3112. noCanvas.style.color = 'red';
  3113. noCanvas.style.fontWeight = 'bold' ;
  3114. noCanvas.style.padding = '10px';
  3115. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  3116. this.frame.canvas.appendChild(noCanvas);
  3117. }
  3118. this.frame.filter = document.createElement( 'div' );
  3119. this.frame.filter.style.position = 'absolute';
  3120. this.frame.filter.style.bottom = '0px';
  3121. this.frame.filter.style.left = '0px';
  3122. this.frame.filter.style.width = '100%';
  3123. this.frame.appendChild(this.frame.filter);
  3124. // add event listeners to handle moving and zooming the contents
  3125. var me = this;
  3126. var onmousedown = function (event) {me._onMouseDown(event);};
  3127. var ontouchstart = function (event) {me._onTouchStart(event);};
  3128. var onmousewheel = function (event) {me._onWheel(event);};
  3129. var ontooltip = function (event) {me._onTooltip(event);};
  3130. // TODO: these events are never cleaned up... can give a 'memory leakage'
  3131. util.addEventListener(this.frame.canvas, 'keydown', onkeydown);
  3132. util.addEventListener(this.frame.canvas, 'mousedown', onmousedown);
  3133. util.addEventListener(this.frame.canvas, 'touchstart', ontouchstart);
  3134. util.addEventListener(this.frame.canvas, 'mousewheel', onmousewheel);
  3135. util.addEventListener(this.frame.canvas, 'mousemove', ontooltip);
  3136. // add the new graph to the container element
  3137. this.containerElement.appendChild(this.frame);
  3138. };
  3139. /**
  3140. * Set a new size for the graph
  3141. * @param {string} width Width in pixels or percentage (for example '800px'
  3142. * or '50%')
  3143. * @param {string} height Height in pixels or percentage (for example '400px'
  3144. * or '30%')
  3145. */
  3146. Graph3d.prototype.setSize = function(width, height) {
  3147. this.frame.style.width = width;
  3148. this.frame.style.height = height;
  3149. this._resizeCanvas();
  3150. };
  3151. /**
  3152. * Resize the canvas to the current size of the frame
  3153. */
  3154. Graph3d.prototype._resizeCanvas = function() {
  3155. this.frame.canvas.style.width = '100%';
  3156. this.frame.canvas.style.height = '100%';
  3157. this.frame.canvas.width = this.frame.canvas.clientWidth;
  3158. this.frame.canvas.height = this.frame.canvas.clientHeight;
  3159. // adjust with for margin
  3160. this.frame.filter.style.width = (this.frame.canvas.clientWidth - 2 * 10) + 'px';
  3161. };
  3162. /**
  3163. * Start animation
  3164. */
  3165. Graph3d.prototype.animationStart = function() {
  3166. if (!this.frame.filter || !this.frame.filter.slider)
  3167. throw 'No animation available';
  3168. this.frame.filter.slider.play();
  3169. };
  3170. /**
  3171. * Stop animation
  3172. */
  3173. Graph3d.prototype.animationStop = function() {
  3174. if (!this.frame.filter || !this.frame.filter.slider) return;
  3175. this.frame.filter.slider.stop();
  3176. };
  3177. /**
  3178. * Resize the center position based on the current values in this.defaultXCenter
  3179. * and this.defaultYCenter (which are strings with a percentage or a value
  3180. * in pixels). The center positions are the variables this.xCenter
  3181. * and this.yCenter
  3182. */
  3183. Graph3d.prototype._resizeCenter = function() {
  3184. // calculate the horizontal center position
  3185. if (this.defaultXCenter.charAt(this.defaultXCenter.length-1) === '%') {
  3186. this.xcenter =
  3187. parseFloat(this.defaultXCenter) / 100 *
  3188. this.frame.canvas.clientWidth;
  3189. }
  3190. else {
  3191. this.xcenter = parseFloat(this.defaultXCenter); // supposed to be in px
  3192. }
  3193. // calculate the vertical center position
  3194. if (this.defaultYCenter.charAt(this.defaultYCenter.length-1) === '%') {
  3195. this.ycenter =
  3196. parseFloat(this.defaultYCenter) / 100 *
  3197. (this.frame.canvas.clientHeight - this.frame.filter.clientHeight);
  3198. }
  3199. else {
  3200. this.ycenter = parseFloat(this.defaultYCenter); // supposed to be in px
  3201. }
  3202. };
  3203. /**
  3204. * Set the rotation and distance of the camera
  3205. * @param {Object} pos An object with the camera position. The object
  3206. * contains three parameters:
  3207. * - horizontal {Number}
  3208. * The horizontal rotation, between 0 and 2*PI.
  3209. * Optional, can be left undefined.
  3210. * - vertical {Number}
  3211. * The vertical rotation, between 0 and 0.5*PI
  3212. * if vertical=0.5*PI, the graph is shown from the
  3213. * top. Optional, can be left undefined.
  3214. * - distance {Number}
  3215. * The (normalized) distance of the camera to the
  3216. * center of the graph, a value between 0.71 and 5.0.
  3217. * Optional, can be left undefined.
  3218. */
  3219. Graph3d.prototype.setCameraPosition = function(pos) {
  3220. if (pos === undefined) {
  3221. return;
  3222. }
  3223. if (pos.horizontal !== undefined && pos.vertical !== undefined) {
  3224. this.camera.setArmRotation(pos.horizontal, pos.vertical);
  3225. }
  3226. if (pos.distance !== undefined) {
  3227. this.camera.setArmLength(pos.distance);
  3228. }
  3229. this.redraw();
  3230. };
  3231. /**
  3232. * Retrieve the current camera rotation
  3233. * @return {object} An object with parameters horizontal, vertical, and
  3234. * distance
  3235. */
  3236. Graph3d.prototype.getCameraPosition = function() {
  3237. var pos = this.camera.getArmRotation();
  3238. pos.distance = this.camera.getArmLength();
  3239. return pos;
  3240. };
  3241. /**
  3242. * Load data into the 3D Graph
  3243. */
  3244. Graph3d.prototype._readData = function(data) {
  3245. // read the data
  3246. this._dataInitialize(data, this.style);
  3247. if (this.dataFilter) {
  3248. // apply filtering
  3249. this.dataPoints = this.dataFilter._getDataPoints();
  3250. }
  3251. else {
  3252. // no filtering. load all data
  3253. this.dataPoints = this._getDataPoints(this.dataTable);
  3254. }
  3255. // draw the filter
  3256. this._redrawFilter();
  3257. };
  3258. /**
  3259. * Replace the dataset of the Graph3d
  3260. * @param {Array | DataSet | DataView} data
  3261. */
  3262. Graph3d.prototype.setData = function (data) {
  3263. this._readData(data);
  3264. this.redraw();
  3265. // start animation when option is true
  3266. if (this.animationAutoStart && this.dataFilter) {
  3267. this.animationStart();
  3268. }
  3269. };
  3270. /**
  3271. * Update the options. Options will be merged with current options
  3272. * @param {Object} options
  3273. */
  3274. Graph3d.prototype.setOptions = function (options) {
  3275. var cameraPosition = undefined;
  3276. this.animationStop();
  3277. if (options !== undefined) {
  3278. // retrieve parameter values
  3279. if (options.width !== undefined) this.width = options.width;
  3280. if (options.height !== undefined) this.height = options.height;
  3281. if (options.xCenter !== undefined) this.defaultXCenter = options.xCenter;
  3282. if (options.yCenter !== undefined) this.defaultYCenter = options.yCenter;
  3283. if (options.filterLabel !== undefined) this.filterLabel = options.filterLabel;
  3284. if (options.legendLabel !== undefined) this.legendLabel = options.legendLabel;
  3285. if (options.xLabel !== undefined) this.xLabel = options.xLabel;
  3286. if (options.yLabel !== undefined) this.yLabel = options.yLabel;
  3287. if (options.zLabel !== undefined) this.zLabel = options.zLabel;
  3288. if (options.style !== undefined) {
  3289. var styleNumber = this._getStyleNumber(options.style);
  3290. if (styleNumber !== -1) {
  3291. this.style = styleNumber;
  3292. }
  3293. }
  3294. if (options.showGrid !== undefined) this.showGrid = options.showGrid;
  3295. if (options.showPerspective !== undefined) this.showPerspective = options.showPerspective;
  3296. if (options.showShadow !== undefined) this.showShadow = options.showShadow;
  3297. if (options.tooltip !== undefined) this.showTooltip = options.tooltip;
  3298. if (options.showAnimationControls !== undefined) this.showAnimationControls = options.showAnimationControls;
  3299. if (options.keepAspectRatio !== undefined) this.keepAspectRatio = options.keepAspectRatio;
  3300. if (options.verticalRatio !== undefined) this.verticalRatio = options.verticalRatio;
  3301. if (options.animationInterval !== undefined) this.animationInterval = options.animationInterval;
  3302. if (options.animationPreload !== undefined) this.animationPreload = options.animationPreload;
  3303. if (options.animationAutoStart !== undefined)this.animationAutoStart = options.animationAutoStart;
  3304. if (options.xBarWidth !== undefined) this.defaultXBarWidth = options.xBarWidth;
  3305. if (options.yBarWidth !== undefined) this.defaultYBarWidth = options.yBarWidth;
  3306. if (options.xMin !== undefined) this.defaultXMin = options.xMin;
  3307. if (options.xStep !== undefined) this.defaultXStep = options.xStep;
  3308. if (options.xMax !== undefined) this.defaultXMax = options.xMax;
  3309. if (options.yMin !== undefined) this.defaultYMin = options.yMin;
  3310. if (options.yStep !== undefined) this.defaultYStep = options.yStep;
  3311. if (options.yMax !== undefined) this.defaultYMax = options.yMax;
  3312. if (options.zMin !== undefined) this.defaultZMin = options.zMin;
  3313. if (options.zStep !== undefined) this.defaultZStep = options.zStep;
  3314. if (options.zMax !== undefined) this.defaultZMax = options.zMax;
  3315. if (options.valueMin !== undefined) this.defaultValueMin = options.valueMin;
  3316. if (options.valueMax !== undefined) this.defaultValueMax = options.valueMax;
  3317. if (options.cameraPosition !== undefined) cameraPosition = options.cameraPosition;
  3318. if (cameraPosition !== undefined) {
  3319. this.camera.setArmRotation(cameraPosition.horizontal, cameraPosition.vertical);
  3320. this.camera.setArmLength(cameraPosition.distance);
  3321. }
  3322. else {
  3323. this.camera.setArmRotation(1.0, 0.5);
  3324. this.camera.setArmLength(1.7);
  3325. }
  3326. }
  3327. this._setBackgroundColor(options && options.backgroundColor);
  3328. this.setSize(this.width, this.height);
  3329. // re-load the data
  3330. if (this.dataTable) {
  3331. this.setData(this.dataTable);
  3332. }
  3333. // start animation when option is true
  3334. if (this.animationAutoStart && this.dataFilter) {
  3335. this.animationStart();
  3336. }
  3337. };
  3338. /**
  3339. * Redraw the Graph.
  3340. */
  3341. Graph3d.prototype.redraw = function() {
  3342. if (this.dataPoints === undefined) {
  3343. throw 'Error: graph data not initialized';
  3344. }
  3345. this._resizeCanvas();
  3346. this._resizeCenter();
  3347. this._redrawSlider();
  3348. this._redrawClear();
  3349. this._redrawAxis();
  3350. if (this.style === Graph3d.STYLE.GRID ||
  3351. this.style === Graph3d.STYLE.SURFACE) {
  3352. this._redrawDataGrid();
  3353. }
  3354. else if (this.style === Graph3d.STYLE.LINE) {
  3355. this._redrawDataLine();
  3356. }
  3357. else if (this.style === Graph3d.STYLE.BAR ||
  3358. this.style === Graph3d.STYLE.BARCOLOR ||
  3359. this.style === Graph3d.STYLE.BARSIZE) {
  3360. this._redrawDataBar();
  3361. }
  3362. else {
  3363. // style is DOT, DOTLINE, DOTCOLOR, DOTSIZE
  3364. this._redrawDataDot();
  3365. }
  3366. this._redrawInfo();
  3367. this._redrawLegend();
  3368. };
  3369. /**
  3370. * Clear the canvas before redrawing
  3371. */
  3372. Graph3d.prototype._redrawClear = function() {
  3373. var canvas = this.frame.canvas;
  3374. var ctx = canvas.getContext('2d');
  3375. ctx.clearRect(0, 0, canvas.width, canvas.height);
  3376. };
  3377. /**
  3378. * Redraw the legend showing the colors
  3379. */
  3380. Graph3d.prototype._redrawLegend = function() {
  3381. var y;
  3382. if (this.style === Graph3d.STYLE.DOTCOLOR ||
  3383. this.style === Graph3d.STYLE.DOTSIZE) {
  3384. var dotSize = this.frame.clientWidth * 0.02;
  3385. var widthMin, widthMax;
  3386. if (this.style === Graph3d.STYLE.DOTSIZE) {
  3387. widthMin = dotSize / 2; // px
  3388. widthMax = dotSize / 2 + dotSize * 2; // Todo: put this in one function
  3389. }
  3390. else {
  3391. widthMin = 20; // px
  3392. widthMax = 20; // px
  3393. }
  3394. var height = Math.max(this.frame.clientHeight * 0.25, 100);
  3395. var top = this.margin;
  3396. var right = this.frame.clientWidth - this.margin;
  3397. var left = right - widthMax;
  3398. var bottom = top + height;
  3399. }
  3400. var canvas = this.frame.canvas;
  3401. var ctx = canvas.getContext('2d');
  3402. ctx.lineWidth = 1;
  3403. ctx.font = '14px arial'; // TODO: put in options
  3404. if (this.style === Graph3d.STYLE.DOTCOLOR) {
  3405. // draw the color bar
  3406. var ymin = 0;
  3407. var ymax = height; // Todo: make height customizable
  3408. for (y = ymin; y < ymax; y++) {
  3409. var f = (y - ymin) / (ymax - ymin);
  3410. //var width = (dotSize / 2 + (1-f) * dotSize * 2); // Todo: put this in one function
  3411. var hue = f * 240;
  3412. var color = this._hsv2rgb(hue, 1, 1);
  3413. ctx.strokeStyle = color;
  3414. ctx.beginPath();
  3415. ctx.moveTo(left, top + y);
  3416. ctx.lineTo(right, top + y);
  3417. ctx.stroke();
  3418. }
  3419. ctx.strokeStyle = this.colorAxis;
  3420. ctx.strokeRect(left, top, widthMax, height);
  3421. }
  3422. if (this.style === Graph3d.STYLE.DOTSIZE) {
  3423. // draw border around color bar
  3424. ctx.strokeStyle = this.colorAxis;
  3425. ctx.fillStyle = this.colorDot;
  3426. ctx.beginPath();
  3427. ctx.moveTo(left, top);
  3428. ctx.lineTo(right, top);
  3429. ctx.lineTo(right - widthMax + widthMin, bottom);
  3430. ctx.lineTo(left, bottom);
  3431. ctx.closePath();
  3432. ctx.fill();
  3433. ctx.stroke();
  3434. }
  3435. if (this.style === Graph3d.STYLE.DOTCOLOR ||
  3436. this.style === Graph3d.STYLE.DOTSIZE) {
  3437. // print values along the color bar
  3438. var gridLineLen = 5; // px
  3439. var step = new StepNumber(this.valueMin, this.valueMax, (this.valueMax-this.valueMin)/5, true);
  3440. step.start();
  3441. if (step.getCurrent() < this.valueMin) {
  3442. step.next();
  3443. }
  3444. while (!step.end()) {
  3445. y = bottom - (step.getCurrent() - this.valueMin) / (this.valueMax - this.valueMin) * height;
  3446. ctx.beginPath();
  3447. ctx.moveTo(left - gridLineLen, y);
  3448. ctx.lineTo(left, y);
  3449. ctx.stroke();
  3450. ctx.textAlign = 'right';
  3451. ctx.textBaseline = 'middle';
  3452. ctx.fillStyle = this.colorAxis;
  3453. ctx.fillText(step.getCurrent(), left - 2 * gridLineLen, y);
  3454. step.next();
  3455. }
  3456. ctx.textAlign = 'right';
  3457. ctx.textBaseline = 'top';
  3458. var label = this.legendLabel;
  3459. ctx.fillText(label, right, bottom + this.margin);
  3460. }
  3461. };
  3462. /**
  3463. * Redraw the filter
  3464. */
  3465. Graph3d.prototype._redrawFilter = function() {
  3466. this.frame.filter.innerHTML = '';
  3467. if (this.dataFilter) {
  3468. var options = {
  3469. 'visible': this.showAnimationControls
  3470. };
  3471. var slider = new Slider(this.frame.filter, options);
  3472. this.frame.filter.slider = slider;
  3473. // TODO: css here is not nice here...
  3474. this.frame.filter.style.padding = '10px';
  3475. //this.frame.filter.style.backgroundColor = '#EFEFEF';
  3476. slider.setValues(this.dataFilter.values);
  3477. slider.setPlayInterval(this.animationInterval);
  3478. // create an event handler
  3479. var me = this;
  3480. var onchange = function () {
  3481. var index = slider.getIndex();
  3482. me.dataFilter.selectValue(index);
  3483. me.dataPoints = me.dataFilter._getDataPoints();
  3484. me.redraw();
  3485. };
  3486. slider.setOnChangeCallback(onchange);
  3487. }
  3488. else {
  3489. this.frame.filter.slider = undefined;
  3490. }
  3491. };
  3492. /**
  3493. * Redraw the slider
  3494. */
  3495. Graph3d.prototype._redrawSlider = function() {
  3496. if ( this.frame.filter.slider !== undefined) {
  3497. this.frame.filter.slider.redraw();
  3498. }
  3499. };
  3500. /**
  3501. * Redraw common information
  3502. */
  3503. Graph3d.prototype._redrawInfo = function() {
  3504. if (this.dataFilter) {
  3505. var canvas = this.frame.canvas;
  3506. var ctx = canvas.getContext('2d');
  3507. ctx.font = '14px arial'; // TODO: put in options
  3508. ctx.lineStyle = 'gray';
  3509. ctx.fillStyle = 'gray';
  3510. ctx.textAlign = 'left';
  3511. ctx.textBaseline = 'top';
  3512. var x = this.margin;
  3513. var y = this.margin;
  3514. ctx.fillText(this.dataFilter.getLabel() + ': ' + this.dataFilter.getSelectedValue(), x, y);
  3515. }
  3516. };
  3517. /**
  3518. * Redraw the axis
  3519. */
  3520. Graph3d.prototype._redrawAxis = function() {
  3521. var canvas = this.frame.canvas,
  3522. ctx = canvas.getContext('2d'),
  3523. from, to, step, prettyStep,
  3524. text, xText, yText, zText,
  3525. offset, xOffset, yOffset,
  3526. xMin2d, xMax2d;
  3527. // TODO: get the actual rendered style of the containerElement
  3528. //ctx.font = this.containerElement.style.font;
  3529. ctx.font = 24 / this.camera.getArmLength() + 'px arial';
  3530. // calculate the length for the short grid lines
  3531. var gridLenX = 0.025 / this.scale.x;
  3532. var gridLenY = 0.025 / this.scale.y;
  3533. var textMargin = 5 / this.camera.getArmLength(); // px
  3534. var armAngle = this.camera.getArmRotation().horizontal;
  3535. // draw x-grid lines
  3536. ctx.lineWidth = 1;
  3537. prettyStep = (this.defaultXStep === undefined);
  3538. step = new StepNumber(this.xMin, this.xMax, this.xStep, prettyStep);
  3539. step.start();
  3540. if (step.getCurrent() < this.xMin) {
  3541. step.next();
  3542. }
  3543. while (!step.end()) {
  3544. var x = step.getCurrent();
  3545. if (this.showGrid) {
  3546. from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin));
  3547. to = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin));
  3548. ctx.strokeStyle = this.colorGrid;
  3549. ctx.beginPath();
  3550. ctx.moveTo(from.x, from.y);
  3551. ctx.lineTo(to.x, to.y);
  3552. ctx.stroke();
  3553. }
  3554. else {
  3555. from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin));
  3556. to = this._convert3Dto2D(new Point3d(x, this.yMin+gridLenX, this.zMin));
  3557. ctx.strokeStyle = this.colorAxis;
  3558. ctx.beginPath();
  3559. ctx.moveTo(from.x, from.y);
  3560. ctx.lineTo(to.x, to.y);
  3561. ctx.stroke();
  3562. from = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin));
  3563. to = this._convert3Dto2D(new Point3d(x, this.yMax-gridLenX, this.zMin));
  3564. ctx.strokeStyle = this.colorAxis;
  3565. ctx.beginPath();
  3566. ctx.moveTo(from.x, from.y);
  3567. ctx.lineTo(to.x, to.y);
  3568. ctx.stroke();
  3569. }
  3570. yText = (Math.cos(armAngle) > 0) ? this.yMin : this.yMax;
  3571. text = this._convert3Dto2D(new Point3d(x, yText, this.zMin));
  3572. if (Math.cos(armAngle * 2) > 0) {
  3573. ctx.textAlign = 'center';
  3574. ctx.textBaseline = 'top';
  3575. text.y += textMargin;
  3576. }
  3577. else if (Math.sin(armAngle * 2) < 0){
  3578. ctx.textAlign = 'right';
  3579. ctx.textBaseline = 'middle';
  3580. }
  3581. else {
  3582. ctx.textAlign = 'left';
  3583. ctx.textBaseline = 'middle';
  3584. }
  3585. ctx.fillStyle = this.colorAxis;
  3586. ctx.fillText(' ' + step.getCurrent() + ' ', text.x, text.y);
  3587. step.next();
  3588. }
  3589. // draw y-grid lines
  3590. ctx.lineWidth = 1;
  3591. prettyStep = (this.defaultYStep === undefined);
  3592. step = new StepNumber(this.yMin, this.yMax, this.yStep, prettyStep);
  3593. step.start();
  3594. if (step.getCurrent() < this.yMin) {
  3595. step.next();
  3596. }
  3597. while (!step.end()) {
  3598. if (this.showGrid) {
  3599. from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin));
  3600. to = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin));
  3601. ctx.strokeStyle = this.colorGrid;
  3602. ctx.beginPath();
  3603. ctx.moveTo(from.x, from.y);
  3604. ctx.lineTo(to.x, to.y);
  3605. ctx.stroke();
  3606. }
  3607. else {
  3608. from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin));
  3609. to = this._convert3Dto2D(new Point3d(this.xMin+gridLenY, step.getCurrent(), this.zMin));
  3610. ctx.strokeStyle = this.colorAxis;
  3611. ctx.beginPath();
  3612. ctx.moveTo(from.x, from.y);
  3613. ctx.lineTo(to.x, to.y);
  3614. ctx.stroke();
  3615. from = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin));
  3616. to = this._convert3Dto2D(new Point3d(this.xMax-gridLenY, step.getCurrent(), this.zMin));
  3617. ctx.strokeStyle = this.colorAxis;
  3618. ctx.beginPath();
  3619. ctx.moveTo(from.x, from.y);
  3620. ctx.lineTo(to.x, to.y);
  3621. ctx.stroke();
  3622. }
  3623. xText = (Math.sin(armAngle ) > 0) ? this.xMin : this.xMax;
  3624. text = this._convert3Dto2D(new Point3d(xText, step.getCurrent(), this.zMin));
  3625. if (Math.cos(armAngle * 2) < 0) {
  3626. ctx.textAlign = 'center';
  3627. ctx.textBaseline = 'top';
  3628. text.y += textMargin;
  3629. }
  3630. else if (Math.sin(armAngle * 2) > 0){
  3631. ctx.textAlign = 'right';
  3632. ctx.textBaseline = 'middle';
  3633. }
  3634. else {
  3635. ctx.textAlign = 'left';
  3636. ctx.textBaseline = 'middle';
  3637. }
  3638. ctx.fillStyle = this.colorAxis;
  3639. ctx.fillText(' ' + step.getCurrent() + ' ', text.x, text.y);
  3640. step.next();
  3641. }
  3642. // draw z-grid lines and axis
  3643. ctx.lineWidth = 1;
  3644. prettyStep = (this.defaultZStep === undefined);
  3645. step = new StepNumber(this.zMin, this.zMax, this.zStep, prettyStep);
  3646. step.start();
  3647. if (step.getCurrent() < this.zMin) {
  3648. step.next();
  3649. }
  3650. xText = (Math.cos(armAngle ) > 0) ? this.xMin : this.xMax;
  3651. yText = (Math.sin(armAngle ) < 0) ? this.yMin : this.yMax;
  3652. while (!step.end()) {
  3653. // TODO: make z-grid lines really 3d?
  3654. from = this._convert3Dto2D(new Point3d(xText, yText, step.getCurrent()));
  3655. ctx.strokeStyle = this.colorAxis;
  3656. ctx.beginPath();
  3657. ctx.moveTo(from.x, from.y);
  3658. ctx.lineTo(from.x - textMargin, from.y);
  3659. ctx.stroke();
  3660. ctx.textAlign = 'right';
  3661. ctx.textBaseline = 'middle';
  3662. ctx.fillStyle = this.colorAxis;
  3663. ctx.fillText(step.getCurrent() + ' ', from.x - 5, from.y);
  3664. step.next();
  3665. }
  3666. ctx.lineWidth = 1;
  3667. from = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  3668. to = this._convert3Dto2D(new Point3d(xText, yText, this.zMax));
  3669. ctx.strokeStyle = this.colorAxis;
  3670. ctx.beginPath();
  3671. ctx.moveTo(from.x, from.y);
  3672. ctx.lineTo(to.x, to.y);
  3673. ctx.stroke();
  3674. // draw x-axis
  3675. ctx.lineWidth = 1;
  3676. // line at yMin
  3677. xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin));
  3678. xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin));
  3679. ctx.strokeStyle = this.colorAxis;
  3680. ctx.beginPath();
  3681. ctx.moveTo(xMin2d.x, xMin2d.y);
  3682. ctx.lineTo(xMax2d.x, xMax2d.y);
  3683. ctx.stroke();
  3684. // line at ymax
  3685. xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin));
  3686. xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin));
  3687. ctx.strokeStyle = this.colorAxis;
  3688. ctx.beginPath();
  3689. ctx.moveTo(xMin2d.x, xMin2d.y);
  3690. ctx.lineTo(xMax2d.x, xMax2d.y);
  3691. ctx.stroke();
  3692. // draw y-axis
  3693. ctx.lineWidth = 1;
  3694. // line at xMin
  3695. from = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin));
  3696. to = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin));
  3697. ctx.strokeStyle = this.colorAxis;
  3698. ctx.beginPath();
  3699. ctx.moveTo(from.x, from.y);
  3700. ctx.lineTo(to.x, to.y);
  3701. ctx.stroke();
  3702. // line at xMax
  3703. from = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin));
  3704. to = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin));
  3705. ctx.strokeStyle = this.colorAxis;
  3706. ctx.beginPath();
  3707. ctx.moveTo(from.x, from.y);
  3708. ctx.lineTo(to.x, to.y);
  3709. ctx.stroke();
  3710. // draw x-label
  3711. var xLabel = this.xLabel;
  3712. if (xLabel.length > 0) {
  3713. yOffset = 0.1 / this.scale.y;
  3714. xText = (this.xMin + this.xMax) / 2;
  3715. yText = (Math.cos(armAngle) > 0) ? this.yMin - yOffset: this.yMax + yOffset;
  3716. text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  3717. if (Math.cos(armAngle * 2) > 0) {
  3718. ctx.textAlign = 'center';
  3719. ctx.textBaseline = 'top';
  3720. }
  3721. else if (Math.sin(armAngle * 2) < 0){
  3722. ctx.textAlign = 'right';
  3723. ctx.textBaseline = 'middle';
  3724. }
  3725. else {
  3726. ctx.textAlign = 'left';
  3727. ctx.textBaseline = 'middle';
  3728. }
  3729. ctx.fillStyle = this.colorAxis;
  3730. ctx.fillText(xLabel, text.x, text.y);
  3731. }
  3732. // draw y-label
  3733. var yLabel = this.yLabel;
  3734. if (yLabel.length > 0) {
  3735. xOffset = 0.1 / this.scale.x;
  3736. xText = (Math.sin(armAngle ) > 0) ? this.xMin - xOffset : this.xMax + xOffset;
  3737. yText = (this.yMin + this.yMax) / 2;
  3738. text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  3739. if (Math.cos(armAngle * 2) < 0) {
  3740. ctx.textAlign = 'center';
  3741. ctx.textBaseline = 'top';
  3742. }
  3743. else if (Math.sin(armAngle * 2) > 0){
  3744. ctx.textAlign = 'right';
  3745. ctx.textBaseline = 'middle';
  3746. }
  3747. else {
  3748. ctx.textAlign = 'left';
  3749. ctx.textBaseline = 'middle';
  3750. }
  3751. ctx.fillStyle = this.colorAxis;
  3752. ctx.fillText(yLabel, text.x, text.y);
  3753. }
  3754. // draw z-label
  3755. var zLabel = this.zLabel;
  3756. if (zLabel.length > 0) {
  3757. offset = 30; // pixels. // TODO: relate to the max width of the values on the z axis?
  3758. xText = (Math.cos(armAngle ) > 0) ? this.xMin : this.xMax;
  3759. yText = (Math.sin(armAngle ) < 0) ? this.yMin : this.yMax;
  3760. zText = (this.zMin + this.zMax) / 2;
  3761. text = this._convert3Dto2D(new Point3d(xText, yText, zText));
  3762. ctx.textAlign = 'right';
  3763. ctx.textBaseline = 'middle';
  3764. ctx.fillStyle = this.colorAxis;
  3765. ctx.fillText(zLabel, text.x - offset, text.y);
  3766. }
  3767. };
  3768. /**
  3769. * Calculate the color based on the given value.
  3770. * @param {Number} H Hue, a value be between 0 and 360
  3771. * @param {Number} S Saturation, a value between 0 and 1
  3772. * @param {Number} V Value, a value between 0 and 1
  3773. */
  3774. Graph3d.prototype._hsv2rgb = function(H, S, V) {
  3775. var R, G, B, C, Hi, X;
  3776. C = V * S;
  3777. Hi = Math.floor(H/60); // hi = 0,1,2,3,4,5
  3778. X = C * (1 - Math.abs(((H/60) % 2) - 1));
  3779. switch (Hi) {
  3780. case 0: R = C; G = X; B = 0; break;
  3781. case 1: R = X; G = C; B = 0; break;
  3782. case 2: R = 0; G = C; B = X; break;
  3783. case 3: R = 0; G = X; B = C; break;
  3784. case 4: R = X; G = 0; B = C; break;
  3785. case 5: R = C; G = 0; B = X; break;
  3786. default: R = 0; G = 0; B = 0; break;
  3787. }
  3788. return 'RGB(' + parseInt(R*255) + ',' + parseInt(G*255) + ',' + parseInt(B*255) + ')';
  3789. };
  3790. /**
  3791. * Draw all datapoints as a grid
  3792. * This function can be used when the style is 'grid'
  3793. */
  3794. Graph3d.prototype._redrawDataGrid = function() {
  3795. var canvas = this.frame.canvas,
  3796. ctx = canvas.getContext('2d'),
  3797. point, right, top, cross,
  3798. i,
  3799. topSideVisible, fillStyle, strokeStyle, lineWidth,
  3800. h, s, v, zAvg;
  3801. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  3802. return; // TODO: throw exception?
  3803. // calculate the translations and screen position of all points
  3804. for (i = 0; i < this.dataPoints.length; i++) {
  3805. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  3806. var screen = this._convertTranslationToScreen(trans);
  3807. this.dataPoints[i].trans = trans;
  3808. this.dataPoints[i].screen = screen;
  3809. // calculate the translation of the point at the bottom (needed for sorting)
  3810. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  3811. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  3812. }
  3813. // sort the points on depth of their (x,y) position (not on z)
  3814. var sortDepth = function (a, b) {
  3815. return b.dist - a.dist;
  3816. };
  3817. this.dataPoints.sort(sortDepth);
  3818. if (this.style === Graph3d.STYLE.SURFACE) {
  3819. for (i = 0; i < this.dataPoints.length; i++) {
  3820. point = this.dataPoints[i];
  3821. right = this.dataPoints[i].pointRight;
  3822. top = this.dataPoints[i].pointTop;
  3823. cross = this.dataPoints[i].pointCross;
  3824. if (point !== undefined && right !== undefined && top !== undefined && cross !== undefined) {
  3825. if (this.showGrayBottom || this.showShadow) {
  3826. // calculate the cross product of the two vectors from center
  3827. // to left and right, in order to know whether we are looking at the
  3828. // bottom or at the top side. We can also use the cross product
  3829. // for calculating light intensity
  3830. var aDiff = Point3d.subtract(cross.trans, point.trans);
  3831. var bDiff = Point3d.subtract(top.trans, right.trans);
  3832. var crossproduct = Point3d.crossProduct(aDiff, bDiff);
  3833. var len = crossproduct.length();
  3834. // FIXME: there is a bug with determining the surface side (shadow or colored)
  3835. topSideVisible = (crossproduct.z > 0);
  3836. }
  3837. else {
  3838. topSideVisible = true;
  3839. }
  3840. if (topSideVisible) {
  3841. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  3842. zAvg = (point.point.z + right.point.z + top.point.z + cross.point.z) / 4;
  3843. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  3844. s = 1; // saturation
  3845. if (this.showShadow) {
  3846. v = Math.min(1 + (crossproduct.x / len) / 2, 1); // value. TODO: scale
  3847. fillStyle = this._hsv2rgb(h, s, v);
  3848. strokeStyle = fillStyle;
  3849. }
  3850. else {
  3851. v = 1;
  3852. fillStyle = this._hsv2rgb(h, s, v);
  3853. strokeStyle = this.colorAxis;
  3854. }
  3855. }
  3856. else {
  3857. fillStyle = 'gray';
  3858. strokeStyle = this.colorAxis;
  3859. }
  3860. lineWidth = 0.5;
  3861. ctx.lineWidth = lineWidth;
  3862. ctx.fillStyle = fillStyle;
  3863. ctx.strokeStyle = strokeStyle;
  3864. ctx.beginPath();
  3865. ctx.moveTo(point.screen.x, point.screen.y);
  3866. ctx.lineTo(right.screen.x, right.screen.y);
  3867. ctx.lineTo(cross.screen.x, cross.screen.y);
  3868. ctx.lineTo(top.screen.x, top.screen.y);
  3869. ctx.closePath();
  3870. ctx.fill();
  3871. ctx.stroke();
  3872. }
  3873. }
  3874. }
  3875. else { // grid style
  3876. for (i = 0; i < this.dataPoints.length; i++) {
  3877. point = this.dataPoints[i];
  3878. right = this.dataPoints[i].pointRight;
  3879. top = this.dataPoints[i].pointTop;
  3880. if (point !== undefined) {
  3881. if (this.showPerspective) {
  3882. lineWidth = 2 / -point.trans.z;
  3883. }
  3884. else {
  3885. lineWidth = 2 * -(this.eye.z / this.camera.getArmLength());
  3886. }
  3887. }
  3888. if (point !== undefined && right !== undefined) {
  3889. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  3890. zAvg = (point.point.z + right.point.z) / 2;
  3891. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  3892. ctx.lineWidth = lineWidth;
  3893. ctx.strokeStyle = this._hsv2rgb(h, 1, 1);
  3894. ctx.beginPath();
  3895. ctx.moveTo(point.screen.x, point.screen.y);
  3896. ctx.lineTo(right.screen.x, right.screen.y);
  3897. ctx.stroke();
  3898. }
  3899. if (point !== undefined && top !== undefined) {
  3900. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  3901. zAvg = (point.point.z + top.point.z) / 2;
  3902. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  3903. ctx.lineWidth = lineWidth;
  3904. ctx.strokeStyle = this._hsv2rgb(h, 1, 1);
  3905. ctx.beginPath();
  3906. ctx.moveTo(point.screen.x, point.screen.y);
  3907. ctx.lineTo(top.screen.x, top.screen.y);
  3908. ctx.stroke();
  3909. }
  3910. }
  3911. }
  3912. };
  3913. /**
  3914. * Draw all datapoints as dots.
  3915. * This function can be used when the style is 'dot' or 'dot-line'
  3916. */
  3917. Graph3d.prototype._redrawDataDot = function() {
  3918. var canvas = this.frame.canvas;
  3919. var ctx = canvas.getContext('2d');
  3920. var i;
  3921. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  3922. return; // TODO: throw exception?
  3923. // calculate the translations of all points
  3924. for (i = 0; i < this.dataPoints.length; i++) {
  3925. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  3926. var screen = this._convertTranslationToScreen(trans);
  3927. this.dataPoints[i].trans = trans;
  3928. this.dataPoints[i].screen = screen;
  3929. // calculate the distance from the point at the bottom to the camera
  3930. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  3931. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  3932. }
  3933. // order the translated points by depth
  3934. var sortDepth = function (a, b) {
  3935. return b.dist - a.dist;
  3936. };
  3937. this.dataPoints.sort(sortDepth);
  3938. // draw the datapoints as colored circles
  3939. var dotSize = this.frame.clientWidth * 0.02; // px
  3940. for (i = 0; i < this.dataPoints.length; i++) {
  3941. var point = this.dataPoints[i];
  3942. if (this.style === Graph3d.STYLE.DOTLINE) {
  3943. // draw a vertical line from the bottom to the graph value
  3944. //var from = this._convert3Dto2D(new Point3d(point.point.x, point.point.y, this.zMin));
  3945. var from = this._convert3Dto2D(point.bottom);
  3946. ctx.lineWidth = 1;
  3947. ctx.strokeStyle = this.colorGrid;
  3948. ctx.beginPath();
  3949. ctx.moveTo(from.x, from.y);
  3950. ctx.lineTo(point.screen.x, point.screen.y);
  3951. ctx.stroke();
  3952. }
  3953. // calculate radius for the circle
  3954. var size;
  3955. if (this.style === Graph3d.STYLE.DOTSIZE) {
  3956. size = dotSize/2 + 2*dotSize * (point.point.value - this.valueMin) / (this.valueMax - this.valueMin);
  3957. }
  3958. else {
  3959. size = dotSize;
  3960. }
  3961. var radius;
  3962. if (this.showPerspective) {
  3963. radius = size / -point.trans.z;
  3964. }
  3965. else {
  3966. radius = size * -(this.eye.z / this.camera.getArmLength());
  3967. }
  3968. if (radius < 0) {
  3969. radius = 0;
  3970. }
  3971. var hue, color, borderColor;
  3972. if (this.style === Graph3d.STYLE.DOTCOLOR ) {
  3973. // calculate the color based on the value
  3974. hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240;
  3975. color = this._hsv2rgb(hue, 1, 1);
  3976. borderColor = this._hsv2rgb(hue, 1, 0.8);
  3977. }
  3978. else if (this.style === Graph3d.STYLE.DOTSIZE) {
  3979. color = this.colorDot;
  3980. borderColor = this.colorDotBorder;
  3981. }
  3982. else {
  3983. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  3984. hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  3985. color = this._hsv2rgb(hue, 1, 1);
  3986. borderColor = this._hsv2rgb(hue, 1, 0.8);
  3987. }
  3988. // draw the circle
  3989. ctx.lineWidth = 1.0;
  3990. ctx.strokeStyle = borderColor;
  3991. ctx.fillStyle = color;
  3992. ctx.beginPath();
  3993. ctx.arc(point.screen.x, point.screen.y, radius, 0, Math.PI*2, true);
  3994. ctx.fill();
  3995. ctx.stroke();
  3996. }
  3997. };
  3998. /**
  3999. * Draw all datapoints as bars.
  4000. * This function can be used when the style is 'bar', 'bar-color', or 'bar-size'
  4001. */
  4002. Graph3d.prototype._redrawDataBar = function() {
  4003. var canvas = this.frame.canvas;
  4004. var ctx = canvas.getContext('2d');
  4005. var i, j, surface, corners;
  4006. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  4007. return; // TODO: throw exception?
  4008. // calculate the translations of all points
  4009. for (i = 0; i < this.dataPoints.length; i++) {
  4010. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  4011. var screen = this._convertTranslationToScreen(trans);
  4012. this.dataPoints[i].trans = trans;
  4013. this.dataPoints[i].screen = screen;
  4014. // calculate the distance from the point at the bottom to the camera
  4015. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  4016. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  4017. }
  4018. // order the translated points by depth
  4019. var sortDepth = function (a, b) {
  4020. return b.dist - a.dist;
  4021. };
  4022. this.dataPoints.sort(sortDepth);
  4023. // draw the datapoints as bars
  4024. var xWidth = this.xBarWidth / 2;
  4025. var yWidth = this.yBarWidth / 2;
  4026. for (i = 0; i < this.dataPoints.length; i++) {
  4027. var point = this.dataPoints[i];
  4028. // determine color
  4029. var hue, color, borderColor;
  4030. if (this.style === Graph3d.STYLE.BARCOLOR ) {
  4031. // calculate the color based on the value
  4032. hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240;
  4033. color = this._hsv2rgb(hue, 1, 1);
  4034. borderColor = this._hsv2rgb(hue, 1, 0.8);
  4035. }
  4036. else if (this.style === Graph3d.STYLE.BARSIZE) {
  4037. color = this.colorDot;
  4038. borderColor = this.colorDotBorder;
  4039. }
  4040. else {
  4041. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  4042. hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  4043. color = this._hsv2rgb(hue, 1, 1);
  4044. borderColor = this._hsv2rgb(hue, 1, 0.8);
  4045. }
  4046. // calculate size for the bar
  4047. if (this.style === Graph3d.STYLE.BARSIZE) {
  4048. xWidth = (this.xBarWidth / 2) * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2);
  4049. yWidth = (this.yBarWidth / 2) * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2);
  4050. }
  4051. // calculate all corner points
  4052. var me = this;
  4053. var point3d = point.point;
  4054. var top = [
  4055. {point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, point3d.z)},
  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. ];
  4060. var bottom = [
  4061. {point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, this.zMin)},
  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. ];
  4066. // calculate screen location of the points
  4067. top.forEach(function (obj) {
  4068. obj.screen = me._convert3Dto2D(obj.point);
  4069. });
  4070. bottom.forEach(function (obj) {
  4071. obj.screen = me._convert3Dto2D(obj.point);
  4072. });
  4073. // create five sides, calculate both corner points and center points
  4074. var surfaces = [
  4075. {corners: top, center: Point3d.avg(bottom[0].point, bottom[2].point)},
  4076. {corners: [top[0], top[1], bottom[1], bottom[0]], center: Point3d.avg(bottom[1].point, bottom[0].point)},
  4077. {corners: [top[1], top[2], bottom[2], bottom[1]], center: Point3d.avg(bottom[2].point, bottom[1].point)},
  4078. {corners: [top[2], top[3], bottom[3], bottom[2]], center: Point3d.avg(bottom[3].point, bottom[2].point)},
  4079. {corners: [top[3], top[0], bottom[0], bottom[3]], center: Point3d.avg(bottom[0].point, bottom[3].point)}
  4080. ];
  4081. point.surfaces = surfaces;
  4082. // calculate the distance of each of the surface centers to the camera
  4083. for (j = 0; j < surfaces.length; j++) {
  4084. surface = surfaces[j];
  4085. var transCenter = this._convertPointToTranslation(surface.center);
  4086. surface.dist = this.showPerspective ? transCenter.length() : -transCenter.z;
  4087. // TODO: this dept calculation doesn't work 100% of the cases due to perspective,
  4088. // but the current solution is fast/simple and works in 99.9% of all cases
  4089. // the issue is visible in example 14, with graph.setCameraPosition({horizontal: 2.97, vertical: 0.5, distance: 0.9})
  4090. }
  4091. // order the surfaces by their (translated) depth
  4092. surfaces.sort(function (a, b) {
  4093. var diff = b.dist - a.dist;
  4094. if (diff) return diff;
  4095. // if equal depth, sort the top surface last
  4096. if (a.corners === top) return 1;
  4097. if (b.corners === top) return -1;
  4098. // both are equal
  4099. return 0;
  4100. });
  4101. // draw the ordered surfaces
  4102. ctx.lineWidth = 1;
  4103. ctx.strokeStyle = borderColor;
  4104. ctx.fillStyle = color;
  4105. // NOTE: we start at j=2 instead of j=0 as we don't need to draw the two surfaces at the backside
  4106. for (j = 2; j < surfaces.length; j++) {
  4107. surface = surfaces[j];
  4108. corners = surface.corners;
  4109. ctx.beginPath();
  4110. ctx.moveTo(corners[3].screen.x, corners[3].screen.y);
  4111. ctx.lineTo(corners[0].screen.x, corners[0].screen.y);
  4112. ctx.lineTo(corners[1].screen.x, corners[1].screen.y);
  4113. ctx.lineTo(corners[2].screen.x, corners[2].screen.y);
  4114. ctx.lineTo(corners[3].screen.x, corners[3].screen.y);
  4115. ctx.fill();
  4116. ctx.stroke();
  4117. }
  4118. }
  4119. };
  4120. /**
  4121. * Draw a line through all datapoints.
  4122. * This function can be used when the style is 'line'
  4123. */
  4124. Graph3d.prototype._redrawDataLine = function() {
  4125. var canvas = this.frame.canvas,
  4126. ctx = canvas.getContext('2d'),
  4127. point, i;
  4128. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  4129. return; // TODO: throw exception?
  4130. // calculate the translations of all points
  4131. for (i = 0; i < this.dataPoints.length; i++) {
  4132. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  4133. var screen = this._convertTranslationToScreen(trans);
  4134. this.dataPoints[i].trans = trans;
  4135. this.dataPoints[i].screen = screen;
  4136. }
  4137. // start the line
  4138. if (this.dataPoints.length > 0) {
  4139. point = this.dataPoints[0];
  4140. ctx.lineWidth = 1; // TODO: make customizable
  4141. ctx.strokeStyle = 'blue'; // TODO: make customizable
  4142. ctx.beginPath();
  4143. ctx.moveTo(point.screen.x, point.screen.y);
  4144. }
  4145. // draw the datapoints as colored circles
  4146. for (i = 1; i < this.dataPoints.length; i++) {
  4147. point = this.dataPoints[i];
  4148. ctx.lineTo(point.screen.x, point.screen.y);
  4149. }
  4150. // finish the line
  4151. if (this.dataPoints.length > 0) {
  4152. ctx.stroke();
  4153. }
  4154. };
  4155. /**
  4156. * Start a moving operation inside the provided parent element
  4157. * @param {Event} event The event that occurred (required for
  4158. * retrieving the mouse position)
  4159. */
  4160. Graph3d.prototype._onMouseDown = function(event) {
  4161. event = event || window.event;
  4162. // check if mouse is still down (may be up when focus is lost for example
  4163. // in an iframe)
  4164. if (this.leftButtonDown) {
  4165. this._onMouseUp(event);
  4166. }
  4167. // only react on left mouse button down
  4168. this.leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
  4169. if (!this.leftButtonDown && !this.touchDown) return;
  4170. // get mouse position (different code for IE and all other browsers)
  4171. this.startMouseX = getMouseX(event);
  4172. this.startMouseY = getMouseY(event);
  4173. this.startStart = new Date(this.start);
  4174. this.startEnd = new Date(this.end);
  4175. this.startArmRotation = this.camera.getArmRotation();
  4176. this.frame.style.cursor = 'move';
  4177. // add event listeners to handle moving the contents
  4178. // we store the function onmousemove and onmouseup in the graph, so we can
  4179. // remove the eventlisteners lateron in the function mouseUp()
  4180. var me = this;
  4181. this.onmousemove = function (event) {me._onMouseMove(event);};
  4182. this.onmouseup = function (event) {me._onMouseUp(event);};
  4183. util.addEventListener(document, 'mousemove', me.onmousemove);
  4184. util.addEventListener(document, 'mouseup', me.onmouseup);
  4185. util.preventDefault(event);
  4186. };
  4187. /**
  4188. * Perform moving operating.
  4189. * This function activated from within the funcion Graph.mouseDown().
  4190. * @param {Event} event Well, eehh, the event
  4191. */
  4192. Graph3d.prototype._onMouseMove = function (event) {
  4193. event = event || window.event;
  4194. // calculate change in mouse position
  4195. var diffX = parseFloat(getMouseX(event)) - this.startMouseX;
  4196. var diffY = parseFloat(getMouseY(event)) - this.startMouseY;
  4197. var horizontalNew = this.startArmRotation.horizontal + diffX / 200;
  4198. var verticalNew = this.startArmRotation.vertical + diffY / 200;
  4199. var snapAngle = 4; // degrees
  4200. var snapValue = Math.sin(snapAngle / 360 * 2 * Math.PI);
  4201. // snap horizontally to nice angles at 0pi, 0.5pi, 1pi, 1.5pi, etc...
  4202. // the -0.001 is to take care that the vertical axis is always drawn at the left front corner
  4203. if (Math.abs(Math.sin(horizontalNew)) < snapValue) {
  4204. horizontalNew = Math.round((horizontalNew / Math.PI)) * Math.PI - 0.001;
  4205. }
  4206. if (Math.abs(Math.cos(horizontalNew)) < snapValue) {
  4207. horizontalNew = (Math.round((horizontalNew/ Math.PI - 0.5)) + 0.5) * Math.PI - 0.001;
  4208. }
  4209. // snap vertically to nice angles
  4210. if (Math.abs(Math.sin(verticalNew)) < snapValue) {
  4211. verticalNew = Math.round((verticalNew / Math.PI)) * Math.PI;
  4212. }
  4213. if (Math.abs(Math.cos(verticalNew)) < snapValue) {
  4214. verticalNew = (Math.round((verticalNew/ Math.PI - 0.5)) + 0.5) * Math.PI;
  4215. }
  4216. this.camera.setArmRotation(horizontalNew, verticalNew);
  4217. this.redraw();
  4218. // fire a cameraPositionChange event
  4219. var parameters = this.getCameraPosition();
  4220. this.emit('cameraPositionChange', parameters);
  4221. util.preventDefault(event);
  4222. };
  4223. /**
  4224. * Stop moving operating.
  4225. * This function activated from within the funcion Graph.mouseDown().
  4226. * @param {event} event The event
  4227. */
  4228. Graph3d.prototype._onMouseUp = function (event) {
  4229. this.frame.style.cursor = 'auto';
  4230. this.leftButtonDown = false;
  4231. // remove event listeners here
  4232. util.removeEventListener(document, 'mousemove', this.onmousemove);
  4233. util.removeEventListener(document, 'mouseup', this.onmouseup);
  4234. util.preventDefault(event);
  4235. };
  4236. /**
  4237. * After having moved the mouse, a tooltip should pop up when the mouse is resting on a data point
  4238. * @param {Event} event A mouse move event
  4239. */
  4240. Graph3d.prototype._onTooltip = function (event) {
  4241. var delay = 300; // ms
  4242. var mouseX = getMouseX(event) - util.getAbsoluteLeft(this.frame);
  4243. var mouseY = getMouseY(event) - util.getAbsoluteTop(this.frame);
  4244. if (!this.showTooltip) {
  4245. return;
  4246. }
  4247. if (this.tooltipTimeout) {
  4248. clearTimeout(this.tooltipTimeout);
  4249. }
  4250. // (delayed) display of a tooltip only if no mouse button is down
  4251. if (this.leftButtonDown) {
  4252. this._hideTooltip();
  4253. return;
  4254. }
  4255. if (this.tooltip && this.tooltip.dataPoint) {
  4256. // tooltip is currently visible
  4257. var dataPoint = this._dataPointFromXY(mouseX, mouseY);
  4258. if (dataPoint !== this.tooltip.dataPoint) {
  4259. // datapoint changed
  4260. if (dataPoint) {
  4261. this._showTooltip(dataPoint);
  4262. }
  4263. else {
  4264. this._hideTooltip();
  4265. }
  4266. }
  4267. }
  4268. else {
  4269. // tooltip is currently not visible
  4270. var me = this;
  4271. this.tooltipTimeout = setTimeout(function () {
  4272. me.tooltipTimeout = null;
  4273. // show a tooltip if we have a data point
  4274. var dataPoint = me._dataPointFromXY(mouseX, mouseY);
  4275. if (dataPoint) {
  4276. me._showTooltip(dataPoint);
  4277. }
  4278. }, delay);
  4279. }
  4280. };
  4281. /**
  4282. * Event handler for touchstart event on mobile devices
  4283. */
  4284. Graph3d.prototype._onTouchStart = function(event) {
  4285. this.touchDown = true;
  4286. var me = this;
  4287. this.ontouchmove = function (event) {me._onTouchMove(event);};
  4288. this.ontouchend = function (event) {me._onTouchEnd(event);};
  4289. util.addEventListener(document, 'touchmove', me.ontouchmove);
  4290. util.addEventListener(document, 'touchend', me.ontouchend);
  4291. this._onMouseDown(event);
  4292. };
  4293. /**
  4294. * Event handler for touchmove event on mobile devices
  4295. */
  4296. Graph3d.prototype._onTouchMove = function(event) {
  4297. this._onMouseMove(event);
  4298. };
  4299. /**
  4300. * Event handler for touchend event on mobile devices
  4301. */
  4302. Graph3d.prototype._onTouchEnd = function(event) {
  4303. this.touchDown = false;
  4304. util.removeEventListener(document, 'touchmove', this.ontouchmove);
  4305. util.removeEventListener(document, 'touchend', this.ontouchend);
  4306. this._onMouseUp(event);
  4307. };
  4308. /**
  4309. * Event handler for mouse wheel event, used to zoom the graph
  4310. * Code from http://adomas.org/javascript-mouse-wheel/
  4311. * @param {event} event The event
  4312. */
  4313. Graph3d.prototype._onWheel = function(event) {
  4314. if (!event) /* For IE. */
  4315. event = window.event;
  4316. // retrieve delta
  4317. var delta = 0;
  4318. if (event.wheelDelta) { /* IE/Opera. */
  4319. delta = event.wheelDelta/120;
  4320. } else if (event.detail) { /* Mozilla case. */
  4321. // In Mozilla, sign of delta is different than in IE.
  4322. // Also, delta is multiple of 3.
  4323. delta = -event.detail/3;
  4324. }
  4325. // If delta is nonzero, handle it.
  4326. // Basically, delta is now positive if wheel was scrolled up,
  4327. // and negative, if wheel was scrolled down.
  4328. if (delta) {
  4329. var oldLength = this.camera.getArmLength();
  4330. var newLength = oldLength * (1 - delta / 10);
  4331. this.camera.setArmLength(newLength);
  4332. this.redraw();
  4333. this._hideTooltip();
  4334. }
  4335. // fire a cameraPositionChange event
  4336. var parameters = this.getCameraPosition();
  4337. this.emit('cameraPositionChange', parameters);
  4338. // Prevent default actions caused by mouse wheel.
  4339. // That might be ugly, but we handle scrolls somehow
  4340. // anyway, so don't bother here..
  4341. util.preventDefault(event);
  4342. };
  4343. /**
  4344. * Test whether a point lies inside given 2D triangle
  4345. * @param {Point2d} point
  4346. * @param {Point2d[]} triangle
  4347. * @return {boolean} Returns true if given point lies inside or on the edge of the triangle
  4348. * @private
  4349. */
  4350. Graph3d.prototype._insideTriangle = function (point, triangle) {
  4351. var a = triangle[0],
  4352. b = triangle[1],
  4353. c = triangle[2];
  4354. function sign (x) {
  4355. return x > 0 ? 1 : x < 0 ? -1 : 0;
  4356. }
  4357. var as = sign((b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x));
  4358. var bs = sign((c.x - b.x) * (point.y - b.y) - (c.y - b.y) * (point.x - b.x));
  4359. var cs = sign((a.x - c.x) * (point.y - c.y) - (a.y - c.y) * (point.x - c.x));
  4360. // each of the three signs must be either equal to each other or zero
  4361. return (as == 0 || bs == 0 || as == bs) &&
  4362. (bs == 0 || cs == 0 || bs == cs) &&
  4363. (as == 0 || cs == 0 || as == cs);
  4364. };
  4365. /**
  4366. * Find a data point close to given screen position (x, y)
  4367. * @param {Number} x
  4368. * @param {Number} y
  4369. * @return {Object | null} The closest data point or null if not close to any data point
  4370. * @private
  4371. */
  4372. Graph3d.prototype._dataPointFromXY = function (x, y) {
  4373. var i,
  4374. distMax = 100, // px
  4375. dataPoint = null,
  4376. closestDataPoint = null,
  4377. closestDist = null,
  4378. center = new Point2d(x, y);
  4379. if (this.style === Graph3d.STYLE.BAR ||
  4380. this.style === Graph3d.STYLE.BARCOLOR ||
  4381. this.style === Graph3d.STYLE.BARSIZE) {
  4382. // the data points are ordered from far away to closest
  4383. for (i = this.dataPoints.length - 1; i >= 0; i--) {
  4384. dataPoint = this.dataPoints[i];
  4385. var surfaces = dataPoint.surfaces;
  4386. if (surfaces) {
  4387. for (var s = surfaces.length - 1; s >= 0; s--) {
  4388. // split each surface in two triangles, and see if the center point is inside one of these
  4389. var surface = surfaces[s];
  4390. var corners = surface.corners;
  4391. var triangle1 = [corners[0].screen, corners[1].screen, corners[2].screen];
  4392. var triangle2 = [corners[2].screen, corners[3].screen, corners[0].screen];
  4393. if (this._insideTriangle(center, triangle1) ||
  4394. this._insideTriangle(center, triangle2)) {
  4395. // return immediately at the first hit
  4396. return dataPoint;
  4397. }
  4398. }
  4399. }
  4400. }
  4401. }
  4402. else {
  4403. // find the closest data point, using distance to the center of the point on 2d screen
  4404. for (i = 0; i < this.dataPoints.length; i++) {
  4405. dataPoint = this.dataPoints[i];
  4406. var point = dataPoint.screen;
  4407. if (point) {
  4408. var distX = Math.abs(x - point.x);
  4409. var distY = Math.abs(y - point.y);
  4410. var dist = Math.sqrt(distX * distX + distY * distY);
  4411. if ((closestDist === null || dist < closestDist) && dist < distMax) {
  4412. closestDist = dist;
  4413. closestDataPoint = dataPoint;
  4414. }
  4415. }
  4416. }
  4417. }
  4418. return closestDataPoint;
  4419. };
  4420. /**
  4421. * Display a tooltip for given data point
  4422. * @param {Object} dataPoint
  4423. * @private
  4424. */
  4425. Graph3d.prototype._showTooltip = function (dataPoint) {
  4426. var content, line, dot;
  4427. if (!this.tooltip) {
  4428. content = document.createElement('div');
  4429. content.style.position = 'absolute';
  4430. content.style.padding = '10px';
  4431. content.style.border = '1px solid #4d4d4d';
  4432. content.style.color = '#1a1a1a';
  4433. content.style.background = 'rgba(255,255,255,0.7)';
  4434. content.style.borderRadius = '2px';
  4435. content.style.boxShadow = '5px 5px 10px rgba(128,128,128,0.5)';
  4436. line = document.createElement('div');
  4437. line.style.position = 'absolute';
  4438. line.style.height = '40px';
  4439. line.style.width = '0';
  4440. line.style.borderLeft = '1px solid #4d4d4d';
  4441. dot = document.createElement('div');
  4442. dot.style.position = 'absolute';
  4443. dot.style.height = '0';
  4444. dot.style.width = '0';
  4445. dot.style.border = '5px solid #4d4d4d';
  4446. dot.style.borderRadius = '5px';
  4447. this.tooltip = {
  4448. dataPoint: null,
  4449. dom: {
  4450. content: content,
  4451. line: line,
  4452. dot: dot
  4453. }
  4454. };
  4455. }
  4456. else {
  4457. content = this.tooltip.dom.content;
  4458. line = this.tooltip.dom.line;
  4459. dot = this.tooltip.dom.dot;
  4460. }
  4461. this._hideTooltip();
  4462. this.tooltip.dataPoint = dataPoint;
  4463. if (typeof this.showTooltip === 'function') {
  4464. content.innerHTML = this.showTooltip(dataPoint.point);
  4465. }
  4466. else {
  4467. content.innerHTML = '<table>' +
  4468. '<tr><td>x:</td><td>' + dataPoint.point.x + '</td></tr>' +
  4469. '<tr><td>y:</td><td>' + dataPoint.point.y + '</td></tr>' +
  4470. '<tr><td>z:</td><td>' + dataPoint.point.z + '</td></tr>' +
  4471. '</table>';
  4472. }
  4473. content.style.left = '0';
  4474. content.style.top = '0';
  4475. this.frame.appendChild(content);
  4476. this.frame.appendChild(line);
  4477. this.frame.appendChild(dot);
  4478. // calculate sizes
  4479. var contentWidth = content.offsetWidth;
  4480. var contentHeight = content.offsetHeight;
  4481. var lineHeight = line.offsetHeight;
  4482. var dotWidth = dot.offsetWidth;
  4483. var dotHeight = dot.offsetHeight;
  4484. var left = dataPoint.screen.x - contentWidth / 2;
  4485. left = Math.min(Math.max(left, 10), this.frame.clientWidth - 10 - contentWidth);
  4486. line.style.left = dataPoint.screen.x + 'px';
  4487. line.style.top = (dataPoint.screen.y - lineHeight) + 'px';
  4488. content.style.left = left + 'px';
  4489. content.style.top = (dataPoint.screen.y - lineHeight - contentHeight) + 'px';
  4490. dot.style.left = (dataPoint.screen.x - dotWidth / 2) + 'px';
  4491. dot.style.top = (dataPoint.screen.y - dotHeight / 2) + 'px';
  4492. };
  4493. /**
  4494. * Hide the tooltip when displayed
  4495. * @private
  4496. */
  4497. Graph3d.prototype._hideTooltip = function () {
  4498. if (this.tooltip) {
  4499. this.tooltip.dataPoint = null;
  4500. for (var prop in this.tooltip.dom) {
  4501. if (this.tooltip.dom.hasOwnProperty(prop)) {
  4502. var elem = this.tooltip.dom[prop];
  4503. if (elem && elem.parentNode) {
  4504. elem.parentNode.removeChild(elem);
  4505. }
  4506. }
  4507. }
  4508. }
  4509. };
  4510. /**--------------------------------------------------------------------------**/
  4511. /**
  4512. * Get the horizontal mouse position from a mouse event
  4513. * @param {Event} event
  4514. * @return {Number} mouse x
  4515. */
  4516. getMouseX = function(event) {
  4517. if ('clientX' in event) return event.clientX;
  4518. return event.targetTouches[0] && event.targetTouches[0].clientX || 0;
  4519. };
  4520. /**
  4521. * Get the vertical mouse position from a mouse event
  4522. * @param {Event} event
  4523. * @return {Number} mouse y
  4524. */
  4525. getMouseY = function(event) {
  4526. if ('clientY' in event) return event.clientY;
  4527. return event.targetTouches[0] && event.targetTouches[0].clientY || 0;
  4528. };
  4529. module.exports = Graph3d;
  4530. /***/ },
  4531. /* 6 */
  4532. /***/ function(module, exports, __webpack_require__) {
  4533. var Point3d = __webpack_require__(9);
  4534. /**
  4535. * @class Camera
  4536. * The camera is mounted on a (virtual) camera arm. The camera arm can rotate
  4537. * The camera is always looking in the direction of the origin of the arm.
  4538. * This way, the camera always rotates around one fixed point, the location
  4539. * of the camera arm.
  4540. *
  4541. * Documentation:
  4542. * http://en.wikipedia.org/wiki/3D_projection
  4543. */
  4544. Camera = function () {
  4545. this.armLocation = new Point3d();
  4546. this.armRotation = {};
  4547. this.armRotation.horizontal = 0;
  4548. this.armRotation.vertical = 0;
  4549. this.armLength = 1.7;
  4550. this.cameraLocation = new Point3d();
  4551. this.cameraRotation = new Point3d(0.5*Math.PI, 0, 0);
  4552. this.calculateCameraOrientation();
  4553. };
  4554. /**
  4555. * Set the location (origin) of the arm
  4556. * @param {Number} x Normalized value of x
  4557. * @param {Number} y Normalized value of y
  4558. * @param {Number} z Normalized value of z
  4559. */
  4560. Camera.prototype.setArmLocation = function(x, y, z) {
  4561. this.armLocation.x = x;
  4562. this.armLocation.y = y;
  4563. this.armLocation.z = z;
  4564. this.calculateCameraOrientation();
  4565. };
  4566. /**
  4567. * Set the rotation of the camera arm
  4568. * @param {Number} horizontal The horizontal rotation, between 0 and 2*PI.
  4569. * Optional, can be left undefined.
  4570. * @param {Number} vertical The vertical rotation, between 0 and 0.5*PI
  4571. * if vertical=0.5*PI, the graph is shown from the
  4572. * top. Optional, can be left undefined.
  4573. */
  4574. Camera.prototype.setArmRotation = function(horizontal, vertical) {
  4575. if (horizontal !== undefined) {
  4576. this.armRotation.horizontal = horizontal;
  4577. }
  4578. if (vertical !== undefined) {
  4579. this.armRotation.vertical = vertical;
  4580. if (this.armRotation.vertical < 0) this.armRotation.vertical = 0;
  4581. if (this.armRotation.vertical > 0.5*Math.PI) this.armRotation.vertical = 0.5*Math.PI;
  4582. }
  4583. if (horizontal !== undefined || vertical !== undefined) {
  4584. this.calculateCameraOrientation();
  4585. }
  4586. };
  4587. /**
  4588. * Retrieve the current arm rotation
  4589. * @return {object} An object with parameters horizontal and vertical
  4590. */
  4591. Camera.prototype.getArmRotation = function() {
  4592. var rot = {};
  4593. rot.horizontal = this.armRotation.horizontal;
  4594. rot.vertical = this.armRotation.vertical;
  4595. return rot;
  4596. };
  4597. /**
  4598. * Set the (normalized) length of the camera arm.
  4599. * @param {Number} length A length between 0.71 and 5.0
  4600. */
  4601. Camera.prototype.setArmLength = function(length) {
  4602. if (length === undefined)
  4603. return;
  4604. this.armLength = length;
  4605. // Radius must be larger than the corner of the graph,
  4606. // which has a distance of sqrt(0.5^2+0.5^2) = 0.71 from the center of the
  4607. // graph
  4608. if (this.armLength < 0.71) this.armLength = 0.71;
  4609. if (this.armLength > 5.0) this.armLength = 5.0;
  4610. this.calculateCameraOrientation();
  4611. };
  4612. /**
  4613. * Retrieve the arm length
  4614. * @return {Number} length
  4615. */
  4616. Camera.prototype.getArmLength = function() {
  4617. return this.armLength;
  4618. };
  4619. /**
  4620. * Retrieve the camera location
  4621. * @return {Point3d} cameraLocation
  4622. */
  4623. Camera.prototype.getCameraLocation = function() {
  4624. return this.cameraLocation;
  4625. };
  4626. /**
  4627. * Retrieve the camera rotation
  4628. * @return {Point3d} cameraRotation
  4629. */
  4630. Camera.prototype.getCameraRotation = function() {
  4631. return this.cameraRotation;
  4632. };
  4633. /**
  4634. * Calculate the location and rotation of the camera based on the
  4635. * position and orientation of the camera arm
  4636. */
  4637. Camera.prototype.calculateCameraOrientation = function() {
  4638. // calculate location of the camera
  4639. this.cameraLocation.x = this.armLocation.x - this.armLength * Math.sin(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical);
  4640. this.cameraLocation.y = this.armLocation.y - this.armLength * Math.cos(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical);
  4641. this.cameraLocation.z = this.armLocation.z + this.armLength * Math.sin(this.armRotation.vertical);
  4642. // calculate rotation of the camera
  4643. this.cameraRotation.x = Math.PI/2 - this.armRotation.vertical;
  4644. this.cameraRotation.y = 0;
  4645. this.cameraRotation.z = -this.armRotation.horizontal;
  4646. };
  4647. module.exports = Camera;
  4648. /***/ },
  4649. /* 7 */
  4650. /***/ function(module, exports, __webpack_require__) {
  4651. var DataView = __webpack_require__(4);
  4652. /**
  4653. * @class Filter
  4654. *
  4655. * @param {DataSet} data The google data table
  4656. * @param {Number} column The index of the column to be filtered
  4657. * @param {Graph} graph The graph
  4658. */
  4659. function Filter (data, column, graph) {
  4660. this.data = data;
  4661. this.column = column;
  4662. this.graph = graph; // the parent graph
  4663. this.index = undefined;
  4664. this.value = undefined;
  4665. // read all distinct values and select the first one
  4666. this.values = graph.getDistinctValues(data.get(), this.column);
  4667. // sort both numeric and string values correctly
  4668. this.values.sort(function (a, b) {
  4669. return a > b ? 1 : a < b ? -1 : 0;
  4670. });
  4671. if (this.values.length > 0) {
  4672. this.selectValue(0);
  4673. }
  4674. // create an array with the filtered datapoints. this will be loaded afterwards
  4675. this.dataPoints = [];
  4676. this.loaded = false;
  4677. this.onLoadCallback = undefined;
  4678. if (graph.animationPreload) {
  4679. this.loaded = false;
  4680. this.loadInBackground();
  4681. }
  4682. else {
  4683. this.loaded = true;
  4684. }
  4685. };
  4686. /**
  4687. * Return the label
  4688. * @return {string} label
  4689. */
  4690. Filter.prototype.isLoaded = function() {
  4691. return this.loaded;
  4692. };
  4693. /**
  4694. * Return the loaded progress
  4695. * @return {Number} percentage between 0 and 100
  4696. */
  4697. Filter.prototype.getLoadedProgress = function() {
  4698. var len = this.values.length;
  4699. var i = 0;
  4700. while (this.dataPoints[i]) {
  4701. i++;
  4702. }
  4703. return Math.round(i / len * 100);
  4704. };
  4705. /**
  4706. * Return the label
  4707. * @return {string} label
  4708. */
  4709. Filter.prototype.getLabel = function() {
  4710. return this.graph.filterLabel;
  4711. };
  4712. /**
  4713. * Return the columnIndex of the filter
  4714. * @return {Number} columnIndex
  4715. */
  4716. Filter.prototype.getColumn = function() {
  4717. return this.column;
  4718. };
  4719. /**
  4720. * Return the currently selected value. Returns undefined if there is no selection
  4721. * @return {*} value
  4722. */
  4723. Filter.prototype.getSelectedValue = function() {
  4724. if (this.index === undefined)
  4725. return undefined;
  4726. return this.values[this.index];
  4727. };
  4728. /**
  4729. * Retrieve all values of the filter
  4730. * @return {Array} values
  4731. */
  4732. Filter.prototype.getValues = function() {
  4733. return this.values;
  4734. };
  4735. /**
  4736. * Retrieve one value of the filter
  4737. * @param {Number} index
  4738. * @return {*} value
  4739. */
  4740. Filter.prototype.getValue = function(index) {
  4741. if (index >= this.values.length)
  4742. throw 'Error: index out of range';
  4743. return this.values[index];
  4744. };
  4745. /**
  4746. * Retrieve the (filtered) dataPoints for the currently selected filter index
  4747. * @param {Number} [index] (optional)
  4748. * @return {Array} dataPoints
  4749. */
  4750. Filter.prototype._getDataPoints = function(index) {
  4751. if (index === undefined)
  4752. index = this.index;
  4753. if (index === undefined)
  4754. return [];
  4755. var dataPoints;
  4756. if (this.dataPoints[index]) {
  4757. dataPoints = this.dataPoints[index];
  4758. }
  4759. else {
  4760. var f = {};
  4761. f.column = this.column;
  4762. f.value = this.values[index];
  4763. var dataView = new DataView(this.data,{filter: function (item) {return (item[f.column] == f.value);}}).get();
  4764. dataPoints = this.graph._getDataPoints(dataView);
  4765. this.dataPoints[index] = dataPoints;
  4766. }
  4767. return dataPoints;
  4768. };
  4769. /**
  4770. * Set a callback function when the filter is fully loaded.
  4771. */
  4772. Filter.prototype.setOnLoadCallback = function(callback) {
  4773. this.onLoadCallback = callback;
  4774. };
  4775. /**
  4776. * Add a value to the list with available values for this filter
  4777. * No double entries will be created.
  4778. * @param {Number} index
  4779. */
  4780. Filter.prototype.selectValue = function(index) {
  4781. if (index >= this.values.length)
  4782. throw 'Error: index out of range';
  4783. this.index = index;
  4784. this.value = this.values[index];
  4785. };
  4786. /**
  4787. * Load all filtered rows in the background one by one
  4788. * Start this method without providing an index!
  4789. */
  4790. Filter.prototype.loadInBackground = function(index) {
  4791. if (index === undefined)
  4792. index = 0;
  4793. var frame = this.graph.frame;
  4794. if (index < this.values.length) {
  4795. var dataPointsTemp = this._getDataPoints(index);
  4796. //this.graph.redrawInfo(); // TODO: not neat
  4797. // create a progress box
  4798. if (frame.progress === undefined) {
  4799. frame.progress = document.createElement('DIV');
  4800. frame.progress.style.position = 'absolute';
  4801. frame.progress.style.color = 'gray';
  4802. frame.appendChild(frame.progress);
  4803. }
  4804. var progress = this.getLoadedProgress();
  4805. frame.progress.innerHTML = 'Loading animation... ' + progress + '%';
  4806. // TODO: this is no nice solution...
  4807. frame.progress.style.bottom = 60 + 'px'; // TODO: use height of slider
  4808. frame.progress.style.left = 10 + 'px';
  4809. var me = this;
  4810. setTimeout(function() {me.loadInBackground(index+1);}, 10);
  4811. this.loaded = false;
  4812. }
  4813. else {
  4814. this.loaded = true;
  4815. // remove the progress box
  4816. if (frame.progress !== undefined) {
  4817. frame.removeChild(frame.progress);
  4818. frame.progress = undefined;
  4819. }
  4820. if (this.onLoadCallback)
  4821. this.onLoadCallback();
  4822. }
  4823. };
  4824. module.exports = Filter;
  4825. /***/ },
  4826. /* 8 */
  4827. /***/ function(module, exports, __webpack_require__) {
  4828. /**
  4829. * @prototype Point2d
  4830. * @param {Number} [x]
  4831. * @param {Number} [y]
  4832. */
  4833. Point2d = function (x, y) {
  4834. this.x = x !== undefined ? x : 0;
  4835. this.y = y !== undefined ? y : 0;
  4836. };
  4837. module.exports = Point2d;
  4838. /***/ },
  4839. /* 9 */
  4840. /***/ function(module, exports, __webpack_require__) {
  4841. /**
  4842. * @prototype Point3d
  4843. * @param {Number} [x]
  4844. * @param {Number} [y]
  4845. * @param {Number} [z]
  4846. */
  4847. function Point3d(x, y, z) {
  4848. this.x = x !== undefined ? x : 0;
  4849. this.y = y !== undefined ? y : 0;
  4850. this.z = z !== undefined ? z : 0;
  4851. };
  4852. /**
  4853. * Subtract the two provided points, returns a-b
  4854. * @param {Point3d} a
  4855. * @param {Point3d} b
  4856. * @return {Point3d} a-b
  4857. */
  4858. Point3d.subtract = function(a, b) {
  4859. var sub = new Point3d();
  4860. sub.x = a.x - b.x;
  4861. sub.y = a.y - b.y;
  4862. sub.z = a.z - b.z;
  4863. return sub;
  4864. };
  4865. /**
  4866. * Add the two provided points, returns a+b
  4867. * @param {Point3d} a
  4868. * @param {Point3d} b
  4869. * @return {Point3d} a+b
  4870. */
  4871. Point3d.add = function(a, b) {
  4872. var sum = new Point3d();
  4873. sum.x = a.x + b.x;
  4874. sum.y = a.y + b.y;
  4875. sum.z = a.z + b.z;
  4876. return sum;
  4877. };
  4878. /**
  4879. * Calculate the average of two 3d points
  4880. * @param {Point3d} a
  4881. * @param {Point3d} b
  4882. * @return {Point3d} The average, (a+b)/2
  4883. */
  4884. Point3d.avg = function(a, b) {
  4885. return new Point3d(
  4886. (a.x + b.x) / 2,
  4887. (a.y + b.y) / 2,
  4888. (a.z + b.z) / 2
  4889. );
  4890. };
  4891. /**
  4892. * Calculate the cross product of the two provided points, returns axb
  4893. * Documentation: http://en.wikipedia.org/wiki/Cross_product
  4894. * @param {Point3d} a
  4895. * @param {Point3d} b
  4896. * @return {Point3d} cross product axb
  4897. */
  4898. Point3d.crossProduct = function(a, b) {
  4899. var crossproduct = new Point3d();
  4900. crossproduct.x = a.y * b.z - a.z * b.y;
  4901. crossproduct.y = a.z * b.x - a.x * b.z;
  4902. crossproduct.z = a.x * b.y - a.y * b.x;
  4903. return crossproduct;
  4904. };
  4905. /**
  4906. * Rtrieve the length of the vector (or the distance from this point to the origin
  4907. * @return {Number} length
  4908. */
  4909. Point3d.prototype.length = function() {
  4910. return Math.sqrt(
  4911. this.x * this.x +
  4912. this.y * this.y +
  4913. this.z * this.z
  4914. );
  4915. };
  4916. module.exports = Point3d;
  4917. /***/ },
  4918. /* 10 */
  4919. /***/ function(module, exports, __webpack_require__) {
  4920. var util = __webpack_require__(1);
  4921. /**
  4922. * @constructor Slider
  4923. *
  4924. * An html slider control with start/stop/prev/next buttons
  4925. * @param {Element} container The element where the slider will be created
  4926. * @param {Object} options Available options:
  4927. * {boolean} visible If true (default) the
  4928. * slider is visible.
  4929. */
  4930. function Slider(container, options) {
  4931. if (container === undefined) {
  4932. throw 'Error: No container element defined';
  4933. }
  4934. this.container = container;
  4935. this.visible = (options && options.visible != undefined) ? options.visible : true;
  4936. if (this.visible) {
  4937. this.frame = document.createElement('DIV');
  4938. //this.frame.style.backgroundColor = '#E5E5E5';
  4939. this.frame.style.width = '100%';
  4940. this.frame.style.position = 'relative';
  4941. this.container.appendChild(this.frame);
  4942. this.frame.prev = document.createElement('INPUT');
  4943. this.frame.prev.type = 'BUTTON';
  4944. this.frame.prev.value = 'Prev';
  4945. this.frame.appendChild(this.frame.prev);
  4946. this.frame.play = document.createElement('INPUT');
  4947. this.frame.play.type = 'BUTTON';
  4948. this.frame.play.value = 'Play';
  4949. this.frame.appendChild(this.frame.play);
  4950. this.frame.next = document.createElement('INPUT');
  4951. this.frame.next.type = 'BUTTON';
  4952. this.frame.next.value = 'Next';
  4953. this.frame.appendChild(this.frame.next);
  4954. this.frame.bar = document.createElement('INPUT');
  4955. this.frame.bar.type = 'BUTTON';
  4956. this.frame.bar.style.position = 'absolute';
  4957. this.frame.bar.style.border = '1px solid red';
  4958. this.frame.bar.style.width = '100px';
  4959. this.frame.bar.style.height = '6px';
  4960. this.frame.bar.style.borderRadius = '2px';
  4961. this.frame.bar.style.MozBorderRadius = '2px';
  4962. this.frame.bar.style.border = '1px solid #7F7F7F';
  4963. this.frame.bar.style.backgroundColor = '#E5E5E5';
  4964. this.frame.appendChild(this.frame.bar);
  4965. this.frame.slide = document.createElement('INPUT');
  4966. this.frame.slide.type = 'BUTTON';
  4967. this.frame.slide.style.margin = '0px';
  4968. this.frame.slide.value = ' ';
  4969. this.frame.slide.style.position = 'relative';
  4970. this.frame.slide.style.left = '-100px';
  4971. this.frame.appendChild(this.frame.slide);
  4972. // create events
  4973. var me = this;
  4974. this.frame.slide.onmousedown = function (event) {me._onMouseDown(event);};
  4975. this.frame.prev.onclick = function (event) {me.prev(event);};
  4976. this.frame.play.onclick = function (event) {me.togglePlay(event);};
  4977. this.frame.next.onclick = function (event) {me.next(event);};
  4978. }
  4979. this.onChangeCallback = undefined;
  4980. this.values = [];
  4981. this.index = undefined;
  4982. this.playTimeout = undefined;
  4983. this.playInterval = 1000; // milliseconds
  4984. this.playLoop = true;
  4985. }
  4986. /**
  4987. * Select the previous index
  4988. */
  4989. Slider.prototype.prev = function() {
  4990. var index = this.getIndex();
  4991. if (index > 0) {
  4992. index--;
  4993. this.setIndex(index);
  4994. }
  4995. };
  4996. /**
  4997. * Select the next index
  4998. */
  4999. Slider.prototype.next = function() {
  5000. var index = this.getIndex();
  5001. if (index < this.values.length - 1) {
  5002. index++;
  5003. this.setIndex(index);
  5004. }
  5005. };
  5006. /**
  5007. * Select the next index
  5008. */
  5009. Slider.prototype.playNext = function() {
  5010. var start = new Date();
  5011. var index = this.getIndex();
  5012. if (index < this.values.length - 1) {
  5013. index++;
  5014. this.setIndex(index);
  5015. }
  5016. else if (this.playLoop) {
  5017. // jump to the start
  5018. index = 0;
  5019. this.setIndex(index);
  5020. }
  5021. var end = new Date();
  5022. var diff = (end - start);
  5023. // calculate how much time it to to set the index and to execute the callback
  5024. // function.
  5025. var interval = Math.max(this.playInterval - diff, 0);
  5026. // document.title = diff // TODO: cleanup
  5027. var me = this;
  5028. this.playTimeout = setTimeout(function() {me.playNext();}, interval);
  5029. };
  5030. /**
  5031. * Toggle start or stop playing
  5032. */
  5033. Slider.prototype.togglePlay = function() {
  5034. if (this.playTimeout === undefined) {
  5035. this.play();
  5036. } else {
  5037. this.stop();
  5038. }
  5039. };
  5040. /**
  5041. * Start playing
  5042. */
  5043. Slider.prototype.play = function() {
  5044. // Test whether already playing
  5045. if (this.playTimeout) return;
  5046. this.playNext();
  5047. if (this.frame) {
  5048. this.frame.play.value = 'Stop';
  5049. }
  5050. };
  5051. /**
  5052. * Stop playing
  5053. */
  5054. Slider.prototype.stop = function() {
  5055. clearInterval(this.playTimeout);
  5056. this.playTimeout = undefined;
  5057. if (this.frame) {
  5058. this.frame.play.value = 'Play';
  5059. }
  5060. };
  5061. /**
  5062. * Set a callback function which will be triggered when the value of the
  5063. * slider bar has changed.
  5064. */
  5065. Slider.prototype.setOnChangeCallback = function(callback) {
  5066. this.onChangeCallback = callback;
  5067. };
  5068. /**
  5069. * Set the interval for playing the list
  5070. * @param {Number} interval The interval in milliseconds
  5071. */
  5072. Slider.prototype.setPlayInterval = function(interval) {
  5073. this.playInterval = interval;
  5074. };
  5075. /**
  5076. * Retrieve the current play interval
  5077. * @return {Number} interval The interval in milliseconds
  5078. */
  5079. Slider.prototype.getPlayInterval = function(interval) {
  5080. return this.playInterval;
  5081. };
  5082. /**
  5083. * Set looping on or off
  5084. * @pararm {boolean} doLoop If true, the slider will jump to the start when
  5085. * the end is passed, and will jump to the end
  5086. * when the start is passed.
  5087. */
  5088. Slider.prototype.setPlayLoop = function(doLoop) {
  5089. this.playLoop = doLoop;
  5090. };
  5091. /**
  5092. * Execute the onchange callback function
  5093. */
  5094. Slider.prototype.onChange = function() {
  5095. if (this.onChangeCallback !== undefined) {
  5096. this.onChangeCallback();
  5097. }
  5098. };
  5099. /**
  5100. * redraw the slider on the correct place
  5101. */
  5102. Slider.prototype.redraw = function() {
  5103. if (this.frame) {
  5104. // resize the bar
  5105. this.frame.bar.style.top = (this.frame.clientHeight/2 -
  5106. this.frame.bar.offsetHeight/2) + 'px';
  5107. this.frame.bar.style.width = (this.frame.clientWidth -
  5108. this.frame.prev.clientWidth -
  5109. this.frame.play.clientWidth -
  5110. this.frame.next.clientWidth - 30) + 'px';
  5111. // position the slider button
  5112. var left = this.indexToLeft(this.index);
  5113. this.frame.slide.style.left = (left) + 'px';
  5114. }
  5115. };
  5116. /**
  5117. * Set the list with values for the slider
  5118. * @param {Array} values A javascript array with values (any type)
  5119. */
  5120. Slider.prototype.setValues = function(values) {
  5121. this.values = values;
  5122. if (this.values.length > 0)
  5123. this.setIndex(0);
  5124. else
  5125. this.index = undefined;
  5126. };
  5127. /**
  5128. * Select a value by its index
  5129. * @param {Number} index
  5130. */
  5131. Slider.prototype.setIndex = function(index) {
  5132. if (index < this.values.length) {
  5133. this.index = index;
  5134. this.redraw();
  5135. this.onChange();
  5136. }
  5137. else {
  5138. throw 'Error: index out of range';
  5139. }
  5140. };
  5141. /**
  5142. * retrieve the index of the currently selected vaue
  5143. * @return {Number} index
  5144. */
  5145. Slider.prototype.getIndex = function() {
  5146. return this.index;
  5147. };
  5148. /**
  5149. * retrieve the currently selected value
  5150. * @return {*} value
  5151. */
  5152. Slider.prototype.get = function() {
  5153. return this.values[this.index];
  5154. };
  5155. Slider.prototype._onMouseDown = function(event) {
  5156. // only react on left mouse button down
  5157. var leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
  5158. if (!leftButtonDown) return;
  5159. this.startClientX = event.clientX;
  5160. this.startSlideX = parseFloat(this.frame.slide.style.left);
  5161. this.frame.style.cursor = 'move';
  5162. // add event listeners to handle moving the contents
  5163. // we store the function onmousemove and onmouseup in the graph, so we can
  5164. // remove the eventlisteners lateron in the function mouseUp()
  5165. var me = this;
  5166. this.onmousemove = function (event) {me._onMouseMove(event);};
  5167. this.onmouseup = function (event) {me._onMouseUp(event);};
  5168. util.addEventListener(document, 'mousemove', this.onmousemove);
  5169. util.addEventListener(document, 'mouseup', this.onmouseup);
  5170. util.preventDefault(event);
  5171. };
  5172. Slider.prototype.leftToIndex = function (left) {
  5173. var width = parseFloat(this.frame.bar.style.width) -
  5174. this.frame.slide.clientWidth - 10;
  5175. var x = left - 3;
  5176. var index = Math.round(x / width * (this.values.length-1));
  5177. if (index < 0) index = 0;
  5178. if (index > this.values.length-1) index = this.values.length-1;
  5179. return index;
  5180. };
  5181. Slider.prototype.indexToLeft = function (index) {
  5182. var width = parseFloat(this.frame.bar.style.width) -
  5183. this.frame.slide.clientWidth - 10;
  5184. var x = index / (this.values.length-1) * width;
  5185. var left = x + 3;
  5186. return left;
  5187. };
  5188. Slider.prototype._onMouseMove = function (event) {
  5189. var diff = event.clientX - this.startClientX;
  5190. var x = this.startSlideX + diff;
  5191. var index = this.leftToIndex(x);
  5192. this.setIndex(index);
  5193. util.preventDefault();
  5194. };
  5195. Slider.prototype._onMouseUp = function (event) {
  5196. this.frame.style.cursor = 'auto';
  5197. // remove event listeners
  5198. util.removeEventListener(document, 'mousemove', this.onmousemove);
  5199. util.removeEventListener(document, 'mouseup', this.onmouseup);
  5200. util.preventDefault();
  5201. };
  5202. module.exports = Slider;
  5203. /***/ },
  5204. /* 11 */
  5205. /***/ function(module, exports, __webpack_require__) {
  5206. /**
  5207. * @prototype StepNumber
  5208. * The class StepNumber is an iterator for Numbers. You provide a start and end
  5209. * value, and a best step size. StepNumber itself rounds to fixed values and
  5210. * a finds the step that best fits the provided step.
  5211. *
  5212. * If prettyStep is true, the step size is chosen as close as possible to the
  5213. * provided step, but being a round value like 1, 2, 5, 10, 20, 50, ....
  5214. *
  5215. * Example usage:
  5216. * var step = new StepNumber(0, 10, 2.5, true);
  5217. * step.start();
  5218. * while (!step.end()) {
  5219. * alert(step.getCurrent());
  5220. * step.next();
  5221. * }
  5222. *
  5223. * Version: 1.0
  5224. *
  5225. * @param {Number} start The start value
  5226. * @param {Number} end The end value
  5227. * @param {Number} step Optional. Step size. Must be a positive value.
  5228. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  5229. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  5230. */
  5231. function StepNumber(start, end, step, prettyStep) {
  5232. // set default values
  5233. this._start = 0;
  5234. this._end = 0;
  5235. this._step = 1;
  5236. this.prettyStep = true;
  5237. this.precision = 5;
  5238. this._current = 0;
  5239. this.setRange(start, end, step, prettyStep);
  5240. };
  5241. /**
  5242. * Set a new range: start, end and step.
  5243. *
  5244. * @param {Number} start The start value
  5245. * @param {Number} end The end value
  5246. * @param {Number} step Optional. Step size. Must be a positive value.
  5247. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  5248. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  5249. */
  5250. StepNumber.prototype.setRange = function(start, end, step, prettyStep) {
  5251. this._start = start ? start : 0;
  5252. this._end = end ? end : 0;
  5253. this.setStep(step, prettyStep);
  5254. };
  5255. /**
  5256. * Set a new step size
  5257. * @param {Number} step New step size. Must be a positive value
  5258. * @param {boolean} prettyStep Optional. If true, the provided step is rounded
  5259. * to a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  5260. */
  5261. StepNumber.prototype.setStep = function(step, prettyStep) {
  5262. if (step === undefined || step <= 0)
  5263. return;
  5264. if (prettyStep !== undefined)
  5265. this.prettyStep = prettyStep;
  5266. if (this.prettyStep === true)
  5267. this._step = StepNumber.calculatePrettyStep(step);
  5268. else
  5269. this._step = step;
  5270. };
  5271. /**
  5272. * Calculate a nice step size, closest to the desired step size.
  5273. * Returns a value in one of the ranges 1*10^n, 2*10^n, or 5*10^n, where n is an
  5274. * integer Number. For example 1, 2, 5, 10, 20, 50, etc...
  5275. * @param {Number} step Desired step size
  5276. * @return {Number} Nice step size
  5277. */
  5278. StepNumber.calculatePrettyStep = function (step) {
  5279. var log10 = function (x) {return Math.log(x) / Math.LN10;};
  5280. // try three steps (multiple of 1, 2, or 5
  5281. var step1 = Math.pow(10, Math.round(log10(step))),
  5282. step2 = 2 * Math.pow(10, Math.round(log10(step / 2))),
  5283. step5 = 5 * Math.pow(10, Math.round(log10(step / 5)));
  5284. // choose the best step (closest to minimum step)
  5285. var prettyStep = step1;
  5286. if (Math.abs(step2 - step) <= Math.abs(prettyStep - step)) prettyStep = step2;
  5287. if (Math.abs(step5 - step) <= Math.abs(prettyStep - step)) prettyStep = step5;
  5288. // for safety
  5289. if (prettyStep <= 0) {
  5290. prettyStep = 1;
  5291. }
  5292. return prettyStep;
  5293. };
  5294. /**
  5295. * returns the current value of the step
  5296. * @return {Number} current value
  5297. */
  5298. StepNumber.prototype.getCurrent = function () {
  5299. return parseFloat(this._current.toPrecision(this.precision));
  5300. };
  5301. /**
  5302. * returns the current step size
  5303. * @return {Number} current step size
  5304. */
  5305. StepNumber.prototype.getStep = function () {
  5306. return this._step;
  5307. };
  5308. /**
  5309. * Set the current value to the largest value smaller than start, which
  5310. * is a multiple of the step size
  5311. */
  5312. StepNumber.prototype.start = function() {
  5313. this._current = this._start - this._start % this._step;
  5314. };
  5315. /**
  5316. * Do a step, add the step size to the current value
  5317. */
  5318. StepNumber.prototype.next = function () {
  5319. this._current += this._step;
  5320. };
  5321. /**
  5322. * Returns true whether the end is reached
  5323. * @return {boolean} True if the current value has passed the end value.
  5324. */
  5325. StepNumber.prototype.end = function () {
  5326. return (this._current > this._end);
  5327. };
  5328. module.exports = StepNumber;
  5329. /***/ },
  5330. /* 12 */
  5331. /***/ function(module, exports, __webpack_require__) {
  5332. var Emitter = __webpack_require__(49);
  5333. var Hammer = __webpack_require__(41);
  5334. var util = __webpack_require__(1);
  5335. var DataSet = __webpack_require__(3);
  5336. var DataView = __webpack_require__(4);
  5337. var Range = __webpack_require__(15);
  5338. var Core = __webpack_require__(42);
  5339. var TimeAxis = __webpack_require__(27);
  5340. var CurrentTime = __webpack_require__(19);
  5341. var CustomTime = __webpack_require__(20);
  5342. var ItemSet = __webpack_require__(24);
  5343. /**
  5344. * Create a timeline visualization
  5345. * @param {HTMLElement} container
  5346. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  5347. * @param {Object} [options] See Timeline.setOptions for the available options.
  5348. * @constructor
  5349. * @extends Core
  5350. */
  5351. function Timeline (container, items, options) {
  5352. if (!(this instanceof Timeline)) {
  5353. throw new SyntaxError('Constructor must be called with the new operator');
  5354. }
  5355. var me = this;
  5356. this.defaultOptions = {
  5357. start: null,
  5358. end: null,
  5359. autoResize: true,
  5360. orientation: 'bottom',
  5361. width: null,
  5362. height: null,
  5363. maxHeight: null,
  5364. minHeight: null
  5365. };
  5366. this.options = util.deepExtend({}, this.defaultOptions);
  5367. // Create the DOM, props, and emitter
  5368. this._create(container);
  5369. // all components listed here will be repainted automatically
  5370. this.components = [];
  5371. this.body = {
  5372. dom: this.dom,
  5373. domProps: this.props,
  5374. emitter: {
  5375. on: this.on.bind(this),
  5376. off: this.off.bind(this),
  5377. emit: this.emit.bind(this)
  5378. },
  5379. util: {
  5380. snap: null, // will be specified after TimeAxis is created
  5381. toScreen: me._toScreen.bind(me),
  5382. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  5383. toTime: me._toTime.bind(me),
  5384. toGlobalTime : me._toGlobalTime.bind(me)
  5385. }
  5386. };
  5387. // range
  5388. this.range = new Range(this.body);
  5389. this.components.push(this.range);
  5390. this.body.range = this.range;
  5391. // time axis
  5392. this.timeAxis = new TimeAxis(this.body);
  5393. this.components.push(this.timeAxis);
  5394. this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
  5395. // current time bar
  5396. this.currentTime = new CurrentTime(this.body);
  5397. this.components.push(this.currentTime);
  5398. // custom time bar
  5399. // Note: time bar will be attached in this.setOptions when selected
  5400. this.customTime = new CustomTime(this.body);
  5401. this.components.push(this.customTime);
  5402. // item set
  5403. this.itemSet = new ItemSet(this.body);
  5404. this.components.push(this.itemSet);
  5405. this.itemsData = null; // DataSet
  5406. this.groupsData = null; // DataSet
  5407. // apply options
  5408. if (options) {
  5409. this.setOptions(options);
  5410. }
  5411. // create itemset
  5412. if (items) {
  5413. this.setItems(items);
  5414. }
  5415. else {
  5416. this.redraw();
  5417. }
  5418. }
  5419. // Extend the functionality from Core
  5420. Timeline.prototype = new Core();
  5421. /**
  5422. * Set items
  5423. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  5424. */
  5425. Timeline.prototype.setItems = function(items) {
  5426. var initialLoad = (this.itemsData == null);
  5427. // convert to type DataSet when needed
  5428. var newDataSet;
  5429. if (!items) {
  5430. newDataSet = null;
  5431. }
  5432. else if (items instanceof DataSet || items instanceof DataView) {
  5433. newDataSet = items;
  5434. }
  5435. else {
  5436. // turn an array into a dataset
  5437. newDataSet = new DataSet(items, {
  5438. type: {
  5439. start: 'Date',
  5440. end: 'Date'
  5441. }
  5442. });
  5443. }
  5444. // set items
  5445. this.itemsData = newDataSet;
  5446. this.itemSet && this.itemSet.setItems(newDataSet);
  5447. if (initialLoad) {
  5448. if (this.options.start != undefined || this.options.end != undefined) {
  5449. var start = this.options.start != undefined ? this.options.start : null;
  5450. var end = this.options.end != undefined ? this.options.end : null;
  5451. this.setWindow(start, end, {animate: false});
  5452. }
  5453. else {
  5454. this.fit({animate: false});
  5455. }
  5456. }
  5457. };
  5458. /**
  5459. * Set groups
  5460. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  5461. */
  5462. Timeline.prototype.setGroups = function(groups) {
  5463. // convert to type DataSet when needed
  5464. var newDataSet;
  5465. if (!groups) {
  5466. newDataSet = null;
  5467. }
  5468. else if (groups instanceof DataSet || groups instanceof DataView) {
  5469. newDataSet = groups;
  5470. }
  5471. else {
  5472. // turn an array into a dataset
  5473. newDataSet = new DataSet(groups);
  5474. }
  5475. this.groupsData = newDataSet;
  5476. this.itemSet.setGroups(newDataSet);
  5477. };
  5478. /**
  5479. * Set selected items by their id. Replaces the current selection
  5480. * Unknown id's are silently ignored.
  5481. * @param {string[] | string} [ids] An array with zero or more id's of the items to be
  5482. * selected. If ids is an empty array, all items will be
  5483. * unselected.
  5484. * @param {Object} [options] Available options:
  5485. * `focus: boolean`
  5486. * If true, focus will be set to the selected item(s)
  5487. * `animate: boolean | number`
  5488. * If true (default), the range is animated
  5489. * smoothly to the new window.
  5490. * If a number, the number is taken as duration
  5491. * for the animation. Default duration is 500 ms.
  5492. * Only applicable when option focus is true.
  5493. */
  5494. Timeline.prototype.setSelection = function(ids, options) {
  5495. this.itemSet && this.itemSet.setSelection(ids);
  5496. if (options && options.focus) {
  5497. this.focus(ids, options);
  5498. }
  5499. };
  5500. /**
  5501. * Get the selected items by their id
  5502. * @return {Array} ids The ids of the selected items
  5503. */
  5504. Timeline.prototype.getSelection = function() {
  5505. return this.itemSet && this.itemSet.getSelection() || [];
  5506. };
  5507. /**
  5508. * Adjust the visible window such that the selected item (or multiple items)
  5509. * are centered on screen.
  5510. * @param {String | String[]} id An item id or array with item ids
  5511. * @param {Object} [options] Available options:
  5512. * `animate: boolean | number`
  5513. * If true (default), the range is animated
  5514. * smoothly to the new window.
  5515. * If a number, the number is taken as duration
  5516. * for the animation. Default duration is 500 ms.
  5517. * Only applicable when option focus is true
  5518. */
  5519. Timeline.prototype.focus = function(id, options) {
  5520. if (!this.itemsData || id == undefined) return;
  5521. var ids = Array.isArray(id) ? id : [id];
  5522. // get the specified item(s)
  5523. var itemsData = this.itemsData.getDataSet().get(ids, {
  5524. type: {
  5525. start: 'Date',
  5526. end: 'Date'
  5527. }
  5528. });
  5529. // calculate minimum start and maximum end of specified items
  5530. var start = null;
  5531. var end = null;
  5532. itemsData.forEach(function (itemData) {
  5533. var s = itemData.start.valueOf();
  5534. var e = 'end' in itemData ? itemData.end.valueOf() : itemData.start.valueOf();
  5535. if (start === null || s < start) {
  5536. start = s;
  5537. }
  5538. if (end === null || e > end) {
  5539. end = e;
  5540. }
  5541. });
  5542. if (start !== null && end !== null) {
  5543. // calculate the new middle and interval for the window
  5544. var middle = (start + end) / 2;
  5545. var interval = Math.max((this.range.end - this.range.start), (end - start) * 1.1);
  5546. var animate = (options && options.animate !== undefined) ? options.animate : true;
  5547. this.range.setRange(middle - interval / 2, middle + interval / 2, animate);
  5548. }
  5549. };
  5550. /**
  5551. * Get the data range of the item set.
  5552. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  5553. * When no minimum is found, min==null
  5554. * When no maximum is found, max==null
  5555. */
  5556. Timeline.prototype.getItemRange = function() {
  5557. // calculate min from start filed
  5558. var dataset = this.itemsData.getDataSet(),
  5559. min = null,
  5560. max = null;
  5561. if (dataset) {
  5562. // calculate the minimum value of the field 'start'
  5563. var minItem = dataset.min('start');
  5564. min = minItem ? util.convert(minItem.start, 'Date').valueOf() : null;
  5565. // Note: we convert first to Date and then to number because else
  5566. // a conversion from ISODate to Number will fail
  5567. // calculate maximum value of fields 'start' and 'end'
  5568. var maxStartItem = dataset.max('start');
  5569. if (maxStartItem) {
  5570. max = util.convert(maxStartItem.start, 'Date').valueOf();
  5571. }
  5572. var maxEndItem = dataset.max('end');
  5573. if (maxEndItem) {
  5574. if (max == null) {
  5575. max = util.convert(maxEndItem.end, 'Date').valueOf();
  5576. }
  5577. else {
  5578. max = Math.max(max, util.convert(maxEndItem.end, 'Date').valueOf());
  5579. }
  5580. }
  5581. }
  5582. return {
  5583. min: (min != null) ? new Date(min) : null,
  5584. max: (max != null) ? new Date(max) : null
  5585. };
  5586. };
  5587. module.exports = Timeline;
  5588. /***/ },
  5589. /* 13 */
  5590. /***/ function(module, exports, __webpack_require__) {
  5591. var Emitter = __webpack_require__(49);
  5592. var Hammer = __webpack_require__(41);
  5593. var util = __webpack_require__(1);
  5594. var DataSet = __webpack_require__(3);
  5595. var DataView = __webpack_require__(4);
  5596. var Range = __webpack_require__(15);
  5597. var Core = __webpack_require__(42);
  5598. var TimeAxis = __webpack_require__(27);
  5599. var CurrentTime = __webpack_require__(19);
  5600. var CustomTime = __webpack_require__(20);
  5601. var LineGraph = __webpack_require__(26);
  5602. /**
  5603. * Create a timeline visualization
  5604. * @param {HTMLElement} container
  5605. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  5606. * @param {Object} [options] See Graph2d.setOptions for the available options.
  5607. * @constructor
  5608. * @extends Core
  5609. */
  5610. function Graph2d (container, items, options, groups) {
  5611. var me = this;
  5612. this.defaultOptions = {
  5613. start: null,
  5614. end: null,
  5615. autoResize: true,
  5616. orientation: 'bottom',
  5617. width: null,
  5618. height: null,
  5619. maxHeight: null,
  5620. minHeight: null
  5621. };
  5622. this.options = util.deepExtend({}, this.defaultOptions);
  5623. // Create the DOM, props, and emitter
  5624. this._create(container);
  5625. // all components listed here will be repainted automatically
  5626. this.components = [];
  5627. this.body = {
  5628. dom: this.dom,
  5629. domProps: this.props,
  5630. emitter: {
  5631. on: this.on.bind(this),
  5632. off: this.off.bind(this),
  5633. emit: this.emit.bind(this)
  5634. },
  5635. util: {
  5636. snap: null, // will be specified after TimeAxis is created
  5637. toScreen: me._toScreen.bind(me),
  5638. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  5639. toTime: me._toTime.bind(me),
  5640. toGlobalTime : me._toGlobalTime.bind(me)
  5641. }
  5642. };
  5643. // range
  5644. this.range = new Range(this.body);
  5645. this.components.push(this.range);
  5646. this.body.range = this.range;
  5647. // time axis
  5648. this.timeAxis = new TimeAxis(this.body);
  5649. this.components.push(this.timeAxis);
  5650. this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
  5651. // current time bar
  5652. this.currentTime = new CurrentTime(this.body);
  5653. this.components.push(this.currentTime);
  5654. // custom time bar
  5655. // Note: time bar will be attached in this.setOptions when selected
  5656. this.customTime = new CustomTime(this.body);
  5657. this.components.push(this.customTime);
  5658. // item set
  5659. this.linegraph = new LineGraph(this.body);
  5660. this.components.push(this.linegraph);
  5661. this.itemsData = null; // DataSet
  5662. this.groupsData = null; // DataSet
  5663. // apply options
  5664. if (options) {
  5665. this.setOptions(options);
  5666. }
  5667. // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
  5668. if (groups) {
  5669. this.setGroups(groups);
  5670. }
  5671. // create itemset
  5672. if (items) {
  5673. this.setItems(items);
  5674. }
  5675. else {
  5676. this.redraw();
  5677. }
  5678. }
  5679. // Extend the functionality from Core
  5680. Graph2d.prototype = new Core();
  5681. /**
  5682. * Set items
  5683. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  5684. */
  5685. Graph2d.prototype.setItems = function(items) {
  5686. var initialLoad = (this.itemsData == null);
  5687. // convert to type DataSet when needed
  5688. var newDataSet;
  5689. if (!items) {
  5690. newDataSet = null;
  5691. }
  5692. else if (items instanceof DataSet || items instanceof DataView) {
  5693. newDataSet = items;
  5694. }
  5695. else {
  5696. // turn an array into a dataset
  5697. newDataSet = new DataSet(items, {
  5698. type: {
  5699. start: 'Date',
  5700. end: 'Date'
  5701. }
  5702. });
  5703. }
  5704. // set items
  5705. this.itemsData = newDataSet;
  5706. this.linegraph && this.linegraph.setItems(newDataSet);
  5707. if (initialLoad && ('start' in this.options || 'end' in this.options)) {
  5708. this.fit();
  5709. var start = ('start' in this.options) ? util.convert(this.options.start, 'Date') : null;
  5710. var end = ('end' in this.options) ? util.convert(this.options.end, 'Date') : null;
  5711. this.setWindow(start, end);
  5712. }
  5713. };
  5714. /**
  5715. * Set groups
  5716. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  5717. */
  5718. Graph2d.prototype.setGroups = function(groups) {
  5719. // convert to type DataSet when needed
  5720. var newDataSet;
  5721. if (!groups) {
  5722. newDataSet = null;
  5723. }
  5724. else if (groups instanceof DataSet || groups instanceof DataView) {
  5725. newDataSet = groups;
  5726. }
  5727. else {
  5728. // turn an array into a dataset
  5729. newDataSet = new DataSet(groups);
  5730. }
  5731. this.groupsData = newDataSet;
  5732. this.linegraph.setGroups(newDataSet);
  5733. };
  5734. /**
  5735. * 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).
  5736. * @param groupId
  5737. * @param width
  5738. * @param height
  5739. */
  5740. Graph2d.prototype.getLegend = function(groupId, width, height) {
  5741. if (width === undefined) {width = 15;}
  5742. if (height === undefined) {height = 15;}
  5743. if (this.linegraph.groups[groupId] !== undefined) {
  5744. return this.linegraph.groups[groupId].getLegend(width,height);
  5745. }
  5746. else {
  5747. return "cannot find group:" + groupId;
  5748. }
  5749. }
  5750. /**
  5751. * This checks if the visible option of the supplied group (by ID) is true or false.
  5752. * @param groupId
  5753. * @returns {*}
  5754. */
  5755. Graph2d.prototype.isGroupVisible = function(groupId) {
  5756. if (this.linegraph.groups[groupId] !== undefined) {
  5757. return (this.linegraph.groups[groupId].visible && (this.linegraph.options.groups.visibility[groupId] === undefined || this.linegraph.options.groups.visibility[groupId] == true));
  5758. }
  5759. else {
  5760. return false;
  5761. }
  5762. }
  5763. /**
  5764. * Get the data range of the item set.
  5765. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  5766. * When no minimum is found, min==null
  5767. * When no maximum is found, max==null
  5768. */
  5769. Graph2d.prototype.getItemRange = function() {
  5770. var min = null;
  5771. var max = null;
  5772. // calculate min from start filed
  5773. for (var groupId in this.linegraph.groups) {
  5774. if (this.linegraph.groups.hasOwnProperty(groupId)) {
  5775. if (this.linegraph.groups[groupId].visible == true) {
  5776. for (var i = 0; i < this.linegraph.groups[groupId].itemsData.length; i++) {
  5777. var item = this.linegraph.groups[groupId].itemsData[i];
  5778. var value = util.convert(item.x, 'Date').valueOf();
  5779. min = min == null ? value : min > value ? value : min;
  5780. max = max == null ? value : max < value ? value : max;
  5781. }
  5782. }
  5783. }
  5784. }
  5785. return {
  5786. min: (min != null) ? new Date(min) : null,
  5787. max: (max != null) ? new Date(max) : null
  5788. };
  5789. };
  5790. module.exports = Graph2d;
  5791. /***/ },
  5792. /* 14 */
  5793. /***/ function(module, exports, __webpack_require__) {
  5794. /**
  5795. * @constructor DataStep
  5796. * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an
  5797. * end data point. The class itself determines the best scale (step size) based on the
  5798. * provided start Date, end Date, and minimumStep.
  5799. *
  5800. * If minimumStep is provided, the step size is chosen as close as possible
  5801. * to the minimumStep but larger than minimumStep. If minimumStep is not
  5802. * provided, the scale is set to 1 DAY.
  5803. * The minimumStep should correspond with the onscreen size of about 6 characters
  5804. *
  5805. * Alternatively, you can set a scale by hand.
  5806. * After creation, you can initialize the class by executing first(). Then you
  5807. * can iterate from the start date to the end date via next(). You can check if
  5808. * the end date is reached with the function hasNext(). After each step, you can
  5809. * retrieve the current date via getCurrent().
  5810. * The DataStep has scales ranging from milliseconds, seconds, minutes, hours,
  5811. * days, to years.
  5812. *
  5813. * Version: 1.2
  5814. *
  5815. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  5816. * or new Date(2010, 9, 21, 23, 45, 00)
  5817. * @param {Date} [end] The end date
  5818. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  5819. */
  5820. function DataStep(start, end, minimumStep, containerHeight, customRange) {
  5821. // variables
  5822. this.current = 0;
  5823. this.autoScale = true;
  5824. this.stepIndex = 0;
  5825. this.step = 1;
  5826. this.scale = 1;
  5827. this.marginStart;
  5828. this.marginEnd;
  5829. this.deadSpace = 0;
  5830. this.majorSteps = [1, 2, 5, 10];
  5831. this.minorSteps = [0.25, 0.5, 1, 2];
  5832. this.setRange(start, end, minimumStep, containerHeight, customRange);
  5833. }
  5834. /**
  5835. * Set a new range
  5836. * If minimumStep is provided, the step size is chosen as close as possible
  5837. * to the minimumStep but larger than minimumStep. If minimumStep is not
  5838. * provided, the scale is set to 1 DAY.
  5839. * The minimumStep should correspond with the onscreen size of about 6 characters
  5840. * @param {Number} [start] The start date and time.
  5841. * @param {Number} [end] The end date and time.
  5842. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  5843. */
  5844. DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, customRange) {
  5845. this._start = customRange.min === undefined ? start : customRange.min;
  5846. this._end = customRange.max === undefined ? end : customRange.max;
  5847. if (start == end) {
  5848. this._start = start - 0.75;
  5849. this._end = end + 1;
  5850. }
  5851. if (this.autoScale) {
  5852. this.setMinimumStep(minimumStep, containerHeight);
  5853. }
  5854. this.setFirst(customRange);
  5855. };
  5856. /**
  5857. * Automatically determine the scale that bests fits the provided minimum step
  5858. * @param {Number} [minimumStep] The minimum step size in milliseconds
  5859. */
  5860. DataStep.prototype.setMinimumStep = function(minimumStep, containerHeight) {
  5861. // round to floor
  5862. var size = this._end - this._start;
  5863. var safeSize = size * 1.2;
  5864. var minimumStepValue = minimumStep * (safeSize / containerHeight);
  5865. var orderOfMagnitude = Math.round(Math.log(safeSize)/Math.LN10);
  5866. var minorStepIdx = -1;
  5867. var magnitudefactor = Math.pow(10,orderOfMagnitude);
  5868. var start = 0;
  5869. if (orderOfMagnitude < 0) {
  5870. start = orderOfMagnitude;
  5871. }
  5872. var solutionFound = false;
  5873. for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) {
  5874. magnitudefactor = Math.pow(10,i);
  5875. for (var j = 0; j < this.minorSteps.length; j++) {
  5876. var stepSize = magnitudefactor * this.minorSteps[j];
  5877. if (stepSize >= minimumStepValue) {
  5878. solutionFound = true;
  5879. minorStepIdx = j;
  5880. break;
  5881. }
  5882. }
  5883. if (solutionFound == true) {
  5884. break;
  5885. }
  5886. }
  5887. this.stepIndex = minorStepIdx;
  5888. this.scale = magnitudefactor;
  5889. this.step = magnitudefactor * this.minorSteps[minorStepIdx];
  5890. };
  5891. /**
  5892. * Round the current date to the first minor date value
  5893. * This must be executed once when the current date is set to start Date
  5894. */
  5895. DataStep.prototype.setFirst = function(customRange) {
  5896. if (customRange === undefined) {
  5897. customRange = {};
  5898. }
  5899. var niceStart = customRange.min === undefined ? this._start - (this.scale * 2 * this.minorSteps[this.stepIndex]) : customRange.min;
  5900. var niceEnd = customRange.max === undefined ? this._end + (this.scale * this.minorSteps[this.stepIndex]) : customRange.max;
  5901. this.marginEnd = customRange.max === undefined ? this.roundToMinor(niceEnd) : customRange.max;
  5902. this.marginStart = customRange.min === undefined ? this.roundToMinor(niceStart) : customRange.min;
  5903. this.deadSpace = this.roundToMinor(niceEnd) - niceEnd + this.roundToMinor(niceStart) - niceStart;
  5904. this.marginRange = this.marginEnd - this.marginStart;
  5905. this.current = this.marginEnd;
  5906. };
  5907. DataStep.prototype.roundToMinor = function(value) {
  5908. var rounded = value - (value % (this.scale * this.minorSteps[this.stepIndex]));
  5909. if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) {
  5910. return rounded + (this.scale * this.minorSteps[this.stepIndex]);
  5911. }
  5912. else {
  5913. return rounded;
  5914. }
  5915. }
  5916. /**
  5917. * Check if the there is a next step
  5918. * @return {boolean} true if the current date has not passed the end date
  5919. */
  5920. DataStep.prototype.hasNext = function () {
  5921. return (this.current >= this.marginStart);
  5922. };
  5923. /**
  5924. * Do the next step
  5925. */
  5926. DataStep.prototype.next = function() {
  5927. var prev = this.current;
  5928. this.current -= this.step;
  5929. // safety mechanism: if current time is still unchanged, move to the end
  5930. if (this.current == prev) {
  5931. this.current = this._end;
  5932. }
  5933. };
  5934. /**
  5935. * Do the next step
  5936. */
  5937. DataStep.prototype.previous = function() {
  5938. this.current += this.step;
  5939. this.marginEnd += this.step;
  5940. this.marginRange = this.marginEnd - this.marginStart;
  5941. };
  5942. /**
  5943. * Get the current datetime
  5944. * @return {String} current The current date
  5945. */
  5946. DataStep.prototype.getCurrent = function() {
  5947. var toPrecision = '' + Number(this.current).toPrecision(5);
  5948. for (var i = toPrecision.length-1; i > 0; i--) {
  5949. if (toPrecision[i] == "0") {
  5950. toPrecision = toPrecision.slice(0,i);
  5951. }
  5952. else if (toPrecision[i] == "." || toPrecision[i] == ",") {
  5953. toPrecision = toPrecision.slice(0,i);
  5954. break;
  5955. }
  5956. else{
  5957. break;
  5958. }
  5959. }
  5960. return toPrecision;
  5961. };
  5962. /**
  5963. * Snap a date to a rounded value.
  5964. * The snap intervals are dependent on the current scale and step.
  5965. * @param {Date} date the date to be snapped.
  5966. * @return {Date} snappedDate
  5967. */
  5968. DataStep.prototype.snap = function(date) {
  5969. };
  5970. /**
  5971. * Check if the current value is a major value (for example when the step
  5972. * is DAY, a major value is each first day of the MONTH)
  5973. * @return {boolean} true if current date is major, else false.
  5974. */
  5975. DataStep.prototype.isMajor = function() {
  5976. return (this.current % (this.scale * this.majorSteps[this.stepIndex]) == 0);
  5977. };
  5978. module.exports = DataStep;
  5979. /***/ },
  5980. /* 15 */
  5981. /***/ function(module, exports, __webpack_require__) {
  5982. var util = __webpack_require__(1);
  5983. var hammerUtil = __webpack_require__(43);
  5984. var moment = __webpack_require__(40);
  5985. var Component = __webpack_require__(18);
  5986. /**
  5987. * @constructor Range
  5988. * A Range controls a numeric range with a start and end value.
  5989. * The Range adjusts the range based on mouse events or programmatic changes,
  5990. * and triggers events when the range is changing or has been changed.
  5991. * @param {{dom: Object, domProps: Object, emitter: Emitter}} body
  5992. * @param {Object} [options] See description at Range.setOptions
  5993. */
  5994. function Range(body, options) {
  5995. var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
  5996. this.start = now.clone().add('days', -3).valueOf(); // Number
  5997. this.end = now.clone().add('days', 4).valueOf(); // Number
  5998. this.body = body;
  5999. // default options
  6000. this.defaultOptions = {
  6001. start: null,
  6002. end: null,
  6003. direction: 'horizontal', // 'horizontal' or 'vertical'
  6004. moveable: true,
  6005. zoomable: true,
  6006. min: null,
  6007. max: null,
  6008. zoomMin: 10, // milliseconds
  6009. zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000 // milliseconds
  6010. };
  6011. this.options = util.extend({}, this.defaultOptions);
  6012. this.props = {
  6013. touch: {}
  6014. };
  6015. this.animateTimer = null;
  6016. // drag listeners for dragging
  6017. this.body.emitter.on('dragstart', this._onDragStart.bind(this));
  6018. this.body.emitter.on('drag', this._onDrag.bind(this));
  6019. this.body.emitter.on('dragend', this._onDragEnd.bind(this));
  6020. // ignore dragging when holding
  6021. this.body.emitter.on('hold', this._onHold.bind(this));
  6022. // mouse wheel for zooming
  6023. this.body.emitter.on('mousewheel', this._onMouseWheel.bind(this));
  6024. this.body.emitter.on('DOMMouseScroll', this._onMouseWheel.bind(this)); // For FF
  6025. // pinch to zoom
  6026. this.body.emitter.on('touch', this._onTouch.bind(this));
  6027. this.body.emitter.on('pinch', this._onPinch.bind(this));
  6028. this.setOptions(options);
  6029. }
  6030. Range.prototype = new Component();
  6031. /**
  6032. * Set options for the range controller
  6033. * @param {Object} options Available options:
  6034. * {Number | Date | String} start Start date for the range
  6035. * {Number | Date | String} end End date for the range
  6036. * {Number} min Minimum value for start
  6037. * {Number} max Maximum value for end
  6038. * {Number} zoomMin Set a minimum value for
  6039. * (end - start).
  6040. * {Number} zoomMax Set a maximum value for
  6041. * (end - start).
  6042. * {Boolean} moveable Enable moving of the range
  6043. * by dragging. True by default
  6044. * {Boolean} zoomable Enable zooming of the range
  6045. * by pinching/scrolling. True by default
  6046. */
  6047. Range.prototype.setOptions = function (options) {
  6048. if (options) {
  6049. // copy the options that we know
  6050. var fields = ['direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable', 'activate'];
  6051. util.selectiveExtend(fields, this.options, options);
  6052. if ('start' in options || 'end' in options) {
  6053. // apply a new range. both start and end are optional
  6054. this.setRange(options.start, options.end);
  6055. }
  6056. }
  6057. };
  6058. /**
  6059. * Test whether direction has a valid value
  6060. * @param {String} direction 'horizontal' or 'vertical'
  6061. */
  6062. function validateDirection (direction) {
  6063. if (direction != 'horizontal' && direction != 'vertical') {
  6064. throw new TypeError('Unknown direction "' + direction + '". ' +
  6065. 'Choose "horizontal" or "vertical".');
  6066. }
  6067. }
  6068. /**
  6069. * Set a new start and end range
  6070. * @param {Date | Number | String} [start]
  6071. * @param {Date | Number | String} [end]
  6072. * @param {boolean | number} [animate=false] If true, the range is animated
  6073. * smoothly to the new window.
  6074. * If animate is a number, the
  6075. * number is taken as duration
  6076. * Default duration is 500 ms.
  6077. *
  6078. */
  6079. Range.prototype.setRange = function(start, end, animate) {
  6080. var _start = start != undefined ? util.convert(start, 'Date').valueOf() : null;
  6081. var _end = end != undefined ? util.convert(end, 'Date').valueOf() : null;
  6082. this._cancelAnimation();
  6083. if (animate) {
  6084. var me = this;
  6085. var initStart = this.start;
  6086. var initEnd = this.end;
  6087. var duration = typeof animate === 'number' ? animate : 500;
  6088. var initTime = new Date().valueOf();
  6089. var anyChanged = false;
  6090. function next() {
  6091. if (!me.props.touch.dragging) {
  6092. var now = new Date().valueOf();
  6093. var time = now - initTime;
  6094. var done = time > duration;
  6095. var s = (done || _start === null) ? _start : util.easeInOutQuad(time, initStart, _start, duration);
  6096. var e = (done || _end === null) ? _end : util.easeInOutQuad(time, initEnd, _end, duration);
  6097. changed = me._applyRange(s, e);
  6098. anyChanged = anyChanged || changed;
  6099. if (changed) {
  6100. me.body.emitter.emit('rangechange', {start: new Date(me.start), end: new Date(me.end)});
  6101. }
  6102. if (done) {
  6103. if (anyChanged) {
  6104. me.body.emitter.emit('rangechanged', {start: new Date(me.start), end: new Date(me.end)});
  6105. }
  6106. }
  6107. else {
  6108. // animate with as high as possible frame rate, leave 20 ms in between
  6109. // each to prevent the browser from blocking
  6110. me.animateTimer = setTimeout(next, 20);
  6111. }
  6112. }
  6113. }
  6114. return next();
  6115. }
  6116. else {
  6117. var changed = this._applyRange(_start, _end);
  6118. if (changed) {
  6119. var params = {start: new Date(this.start), end: new Date(this.end)};
  6120. this.body.emitter.emit('rangechange', params);
  6121. this.body.emitter.emit('rangechanged', params);
  6122. }
  6123. }
  6124. };
  6125. /**
  6126. * Stop an animation
  6127. * @private
  6128. */
  6129. Range.prototype._cancelAnimation = function () {
  6130. if (this.animateTimer) {
  6131. clearTimeout(this.animateTimer);
  6132. this.animateTimer = null;
  6133. }
  6134. };
  6135. /**
  6136. * Set a new start and end range. This method is the same as setRange, but
  6137. * does not trigger a range change and range changed event, and it returns
  6138. * true when the range is changed
  6139. * @param {Number} [start]
  6140. * @param {Number} [end]
  6141. * @return {Boolean} changed
  6142. * @private
  6143. */
  6144. Range.prototype._applyRange = function(start, end) {
  6145. var newStart = (start != null) ? util.convert(start, 'Date').valueOf() : this.start,
  6146. newEnd = (end != null) ? util.convert(end, 'Date').valueOf() : this.end,
  6147. max = (this.options.max != null) ? util.convert(this.options.max, 'Date').valueOf() : null,
  6148. min = (this.options.min != null) ? util.convert(this.options.min, 'Date').valueOf() : null,
  6149. diff;
  6150. // check for valid number
  6151. if (isNaN(newStart) || newStart === null) {
  6152. throw new Error('Invalid start "' + start + '"');
  6153. }
  6154. if (isNaN(newEnd) || newEnd === null) {
  6155. throw new Error('Invalid end "' + end + '"');
  6156. }
  6157. // prevent start < end
  6158. if (newEnd < newStart) {
  6159. newEnd = newStart;
  6160. }
  6161. // prevent start < min
  6162. if (min !== null) {
  6163. if (newStart < min) {
  6164. diff = (min - newStart);
  6165. newStart += diff;
  6166. newEnd += diff;
  6167. // prevent end > max
  6168. if (max != null) {
  6169. if (newEnd > max) {
  6170. newEnd = max;
  6171. }
  6172. }
  6173. }
  6174. }
  6175. // prevent end > max
  6176. if (max !== null) {
  6177. if (newEnd > max) {
  6178. diff = (newEnd - max);
  6179. newStart -= diff;
  6180. newEnd -= diff;
  6181. // prevent start < min
  6182. if (min != null) {
  6183. if (newStart < min) {
  6184. newStart = min;
  6185. }
  6186. }
  6187. }
  6188. }
  6189. // prevent (end-start) < zoomMin
  6190. if (this.options.zoomMin !== null) {
  6191. var zoomMin = parseFloat(this.options.zoomMin);
  6192. if (zoomMin < 0) {
  6193. zoomMin = 0;
  6194. }
  6195. if ((newEnd - newStart) < zoomMin) {
  6196. if ((this.end - this.start) === zoomMin) {
  6197. // ignore this action, we are already zoomed to the minimum
  6198. newStart = this.start;
  6199. newEnd = this.end;
  6200. }
  6201. else {
  6202. // zoom to the minimum
  6203. diff = (zoomMin - (newEnd - newStart));
  6204. newStart -= diff / 2;
  6205. newEnd += diff / 2;
  6206. }
  6207. }
  6208. }
  6209. // prevent (end-start) > zoomMax
  6210. if (this.options.zoomMax !== null) {
  6211. var zoomMax = parseFloat(this.options.zoomMax);
  6212. if (zoomMax < 0) {
  6213. zoomMax = 0;
  6214. }
  6215. if ((newEnd - newStart) > zoomMax) {
  6216. if ((this.end - this.start) === zoomMax) {
  6217. // ignore this action, we are already zoomed to the maximum
  6218. newStart = this.start;
  6219. newEnd = this.end;
  6220. }
  6221. else {
  6222. // zoom to the maximum
  6223. diff = ((newEnd - newStart) - zoomMax);
  6224. newStart += diff / 2;
  6225. newEnd -= diff / 2;
  6226. }
  6227. }
  6228. }
  6229. var changed = (this.start != newStart || this.end != newEnd);
  6230. this.start = newStart;
  6231. this.end = newEnd;
  6232. return changed;
  6233. };
  6234. /**
  6235. * Retrieve the current range.
  6236. * @return {Object} An object with start and end properties
  6237. */
  6238. Range.prototype.getRange = function() {
  6239. return {
  6240. start: this.start,
  6241. end: this.end
  6242. };
  6243. };
  6244. /**
  6245. * Calculate the conversion offset and scale for current range, based on
  6246. * the provided width
  6247. * @param {Number} width
  6248. * @returns {{offset: number, scale: number}} conversion
  6249. */
  6250. Range.prototype.conversion = function (width) {
  6251. return Range.conversion(this.start, this.end, width);
  6252. };
  6253. /**
  6254. * Static method to calculate the conversion offset and scale for a range,
  6255. * based on the provided start, end, and width
  6256. * @param {Number} start
  6257. * @param {Number} end
  6258. * @param {Number} width
  6259. * @returns {{offset: number, scale: number}} conversion
  6260. */
  6261. Range.conversion = function (start, end, width) {
  6262. if (width != 0 && (end - start != 0)) {
  6263. return {
  6264. offset: start,
  6265. scale: width / (end - start)
  6266. }
  6267. }
  6268. else {
  6269. return {
  6270. offset: 0,
  6271. scale: 1
  6272. };
  6273. }
  6274. };
  6275. /**
  6276. * Start dragging horizontally or vertically
  6277. * @param {Event} event
  6278. * @private
  6279. */
  6280. Range.prototype._onDragStart = function(event) {
  6281. // only allow dragging when configured as movable
  6282. if (!this.options.moveable) return;
  6283. // refuse to drag when we where pinching to prevent the timeline make a jump
  6284. // when releasing the fingers in opposite order from the touch screen
  6285. if (!this.props.touch.allowDragging) return;
  6286. this.props.touch.start = this.start;
  6287. this.props.touch.end = this.end;
  6288. this.props.touch.dragging = true;
  6289. if (this.body.dom.root) {
  6290. this.body.dom.root.style.cursor = 'move';
  6291. }
  6292. };
  6293. /**
  6294. * Perform dragging operation
  6295. * @param {Event} event
  6296. * @private
  6297. */
  6298. Range.prototype._onDrag = function (event) {
  6299. // only allow dragging when configured as movable
  6300. if (!this.options.moveable) return;
  6301. var direction = this.options.direction;
  6302. validateDirection(direction);
  6303. // refuse to drag when we where pinching to prevent the timeline make a jump
  6304. // when releasing the fingers in opposite order from the touch screen
  6305. if (!this.props.touch.allowDragging) return;
  6306. var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY,
  6307. interval = (this.props.touch.end - this.props.touch.start),
  6308. width = (direction == 'horizontal') ? this.body.domProps.center.width : this.body.domProps.center.height,
  6309. diffRange = -delta / width * interval;
  6310. this._applyRange(this.props.touch.start + diffRange, this.props.touch.end + diffRange);
  6311. this.body.emitter.emit('rangechange', {
  6312. start: new Date(this.start),
  6313. end: new Date(this.end)
  6314. });
  6315. };
  6316. /**
  6317. * Stop dragging operation
  6318. * @param {event} event
  6319. * @private
  6320. */
  6321. Range.prototype._onDragEnd = function (event) {
  6322. // only allow dragging when configured as movable
  6323. if (!this.options.moveable) return;
  6324. // refuse to drag when we where pinching to prevent the timeline make a jump
  6325. // when releasing the fingers in opposite order from the touch screen
  6326. if (!this.props.touch.allowDragging) return;
  6327. this.props.touch.dragging = false;
  6328. if (this.body.dom.root) {
  6329. this.body.dom.root.style.cursor = 'auto';
  6330. }
  6331. // fire a rangechanged event
  6332. this.body.emitter.emit('rangechanged', {
  6333. start: new Date(this.start),
  6334. end: new Date(this.end)
  6335. });
  6336. };
  6337. /**
  6338. * Event handler for mouse wheel event, used to zoom
  6339. * Code from http://adomas.org/javascript-mouse-wheel/
  6340. * @param {Event} event
  6341. * @private
  6342. */
  6343. Range.prototype._onMouseWheel = function(event) {
  6344. // only allow zooming when configured as zoomable and moveable
  6345. if (!(this.options.zoomable && this.options.moveable)) return;
  6346. // retrieve delta
  6347. var delta = 0;
  6348. if (event.wheelDelta) { /* IE/Opera. */
  6349. delta = event.wheelDelta / 120;
  6350. } else if (event.detail) { /* Mozilla case. */
  6351. // In Mozilla, sign of delta is different than in IE.
  6352. // Also, delta is multiple of 3.
  6353. delta = -event.detail / 3;
  6354. }
  6355. // If delta is nonzero, handle it.
  6356. // Basically, delta is now positive if wheel was scrolled up,
  6357. // and negative, if wheel was scrolled down.
  6358. if (delta) {
  6359. // perform the zoom action. Delta is normally 1 or -1
  6360. // adjust a negative delta such that zooming in with delta 0.1
  6361. // equals zooming out with a delta -0.1
  6362. var scale;
  6363. if (delta < 0) {
  6364. scale = 1 - (delta / 5);
  6365. }
  6366. else {
  6367. scale = 1 / (1 + (delta / 5)) ;
  6368. }
  6369. // calculate center, the date to zoom around
  6370. var gesture = hammerUtil.fakeGesture(this, event),
  6371. pointer = getPointer(gesture.center, this.body.dom.center),
  6372. pointerDate = this._pointerToDate(pointer);
  6373. this.zoom(scale, pointerDate);
  6374. }
  6375. // Prevent default actions caused by mouse wheel
  6376. // (else the page and timeline both zoom and scroll)
  6377. event.preventDefault();
  6378. };
  6379. /**
  6380. * Start of a touch gesture
  6381. * @private
  6382. */
  6383. Range.prototype._onTouch = function (event) {
  6384. this.props.touch.start = this.start;
  6385. this.props.touch.end = this.end;
  6386. this.props.touch.allowDragging = true;
  6387. this.props.touch.center = null;
  6388. };
  6389. /**
  6390. * On start of a hold gesture
  6391. * @private
  6392. */
  6393. Range.prototype._onHold = function () {
  6394. this.props.touch.allowDragging = false;
  6395. };
  6396. /**
  6397. * Handle pinch event
  6398. * @param {Event} event
  6399. * @private
  6400. */
  6401. Range.prototype._onPinch = function (event) {
  6402. // only allow zooming when configured as zoomable and moveable
  6403. if (!(this.options.zoomable && this.options.moveable)) return;
  6404. this.props.touch.allowDragging = false;
  6405. if (event.gesture.touches.length > 1) {
  6406. if (!this.props.touch.center) {
  6407. this.props.touch.center = getPointer(event.gesture.center, this.body.dom.center);
  6408. }
  6409. var scale = 1 / event.gesture.scale,
  6410. initDate = this._pointerToDate(this.props.touch.center);
  6411. // calculate new start and end
  6412. var newStart = parseInt(initDate + (this.props.touch.start - initDate) * scale);
  6413. var newEnd = parseInt(initDate + (this.props.touch.end - initDate) * scale);
  6414. // apply new range
  6415. this.setRange(newStart, newEnd);
  6416. }
  6417. };
  6418. /**
  6419. * Helper function to calculate the center date for zooming
  6420. * @param {{x: Number, y: Number}} pointer
  6421. * @return {number} date
  6422. * @private
  6423. */
  6424. Range.prototype._pointerToDate = function (pointer) {
  6425. var conversion;
  6426. var direction = this.options.direction;
  6427. validateDirection(direction);
  6428. if (direction == 'horizontal') {
  6429. var width = this.body.domProps.center.width;
  6430. conversion = this.conversion(width);
  6431. return pointer.x / conversion.scale + conversion.offset;
  6432. }
  6433. else {
  6434. var height = this.body.domProps.center.height;
  6435. conversion = this.conversion(height);
  6436. return pointer.y / conversion.scale + conversion.offset;
  6437. }
  6438. };
  6439. /**
  6440. * Get the pointer location relative to the location of the dom element
  6441. * @param {{pageX: Number, pageY: Number}} touch
  6442. * @param {Element} element HTML DOM element
  6443. * @return {{x: Number, y: Number}} pointer
  6444. * @private
  6445. */
  6446. function getPointer (touch, element) {
  6447. return {
  6448. x: touch.pageX - util.getAbsoluteLeft(element),
  6449. y: touch.pageY - util.getAbsoluteTop(element)
  6450. };
  6451. }
  6452. /**
  6453. * Zoom the range the given scale in or out. Start and end date will
  6454. * be adjusted, and the timeline will be redrawn. You can optionally give a
  6455. * date around which to zoom.
  6456. * For example, try scale = 0.9 or 1.1
  6457. * @param {Number} scale Scaling factor. Values above 1 will zoom out,
  6458. * values below 1 will zoom in.
  6459. * @param {Number} [center] Value representing a date around which will
  6460. * be zoomed.
  6461. */
  6462. Range.prototype.zoom = function(scale, center) {
  6463. // if centerDate is not provided, take it half between start Date and end Date
  6464. if (center == null) {
  6465. center = (this.start + this.end) / 2;
  6466. }
  6467. // calculate new start and end
  6468. var newStart = center + (this.start - center) * scale;
  6469. var newEnd = center + (this.end - center) * scale;
  6470. this.setRange(newStart, newEnd);
  6471. };
  6472. /**
  6473. * Move the range with a given delta to the left or right. Start and end
  6474. * value will be adjusted. For example, try delta = 0.1 or -0.1
  6475. * @param {Number} delta Moving amount. Positive value will move right,
  6476. * negative value will move left
  6477. */
  6478. Range.prototype.move = function(delta) {
  6479. // zoom start Date and end Date relative to the centerDate
  6480. var diff = (this.end - this.start);
  6481. // apply new values
  6482. var newStart = this.start + diff * delta;
  6483. var newEnd = this.end + diff * delta;
  6484. // TODO: reckon with min and max range
  6485. this.start = newStart;
  6486. this.end = newEnd;
  6487. };
  6488. /**
  6489. * Move the range to a new center point
  6490. * @param {Number} moveTo New center point of the range
  6491. */
  6492. Range.prototype.moveTo = function(moveTo) {
  6493. var center = (this.start + this.end) / 2;
  6494. var diff = center - moveTo;
  6495. // calculate new start and end
  6496. var newStart = this.start - diff;
  6497. var newEnd = this.end - diff;
  6498. this.setRange(newStart, newEnd);
  6499. };
  6500. module.exports = Range;
  6501. /***/ },
  6502. /* 16 */
  6503. /***/ function(module, exports, __webpack_require__) {
  6504. // Utility functions for ordering and stacking of items
  6505. var EPSILON = 0.001; // used when checking collisions, to prevent round-off errors
  6506. /**
  6507. * Order items by their start data
  6508. * @param {Item[]} items
  6509. */
  6510. exports.orderByStart = function(items) {
  6511. items.sort(function (a, b) {
  6512. return a.data.start - b.data.start;
  6513. });
  6514. };
  6515. /**
  6516. * Order items by their end date. If they have no end date, their start date
  6517. * is used.
  6518. * @param {Item[]} items
  6519. */
  6520. exports.orderByEnd = function(items) {
  6521. items.sort(function (a, b) {
  6522. var aTime = ('end' in a.data) ? a.data.end : a.data.start,
  6523. bTime = ('end' in b.data) ? b.data.end : b.data.start;
  6524. return aTime - bTime;
  6525. });
  6526. };
  6527. /**
  6528. * Adjust vertical positions of the items such that they don't overlap each
  6529. * other.
  6530. * @param {Item[]} items
  6531. * All visible items
  6532. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  6533. * Margins between items and between items and the axis.
  6534. * @param {boolean} [force=false]
  6535. * If true, all items will be repositioned. If false (default), only
  6536. * items having a top===null will be re-stacked
  6537. */
  6538. exports.stack = function(items, margin, force) {
  6539. var i, iMax;
  6540. if (force) {
  6541. // reset top position of all items
  6542. for (i = 0, iMax = items.length; i < iMax; i++) {
  6543. items[i].top = null;
  6544. }
  6545. }
  6546. // calculate new, non-overlapping positions
  6547. for (i = 0, iMax = items.length; i < iMax; i++) {
  6548. var item = items[i];
  6549. if (item.top === null) {
  6550. // initialize top position
  6551. item.top = margin.axis;
  6552. do {
  6553. // TODO: optimize checking for overlap. when there is a gap without items,
  6554. // you only need to check for items from the next item on, not from zero
  6555. var collidingItem = null;
  6556. for (var j = 0, jj = items.length; j < jj; j++) {
  6557. var other = items[j];
  6558. if (other.top !== null && other !== item && exports.collision(item, other, margin.item)) {
  6559. collidingItem = other;
  6560. break;
  6561. }
  6562. }
  6563. if (collidingItem != null) {
  6564. // There is a collision. Reposition the items above the colliding element
  6565. item.top = collidingItem.top + collidingItem.height + margin.item.vertical;
  6566. }
  6567. } while (collidingItem);
  6568. }
  6569. }
  6570. };
  6571. /**
  6572. * Adjust vertical positions of the items without stacking them
  6573. * @param {Item[]} items
  6574. * All visible items
  6575. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  6576. * Margins between items and between items and the axis.
  6577. */
  6578. exports.nostack = function(items, margin) {
  6579. var i, iMax;
  6580. // reset top position of all items
  6581. for (i = 0, iMax = items.length; i < iMax; i++) {
  6582. items[i].top = margin.axis;
  6583. }
  6584. };
  6585. /**
  6586. * Test if the two provided items collide
  6587. * The items must have parameters left, width, top, and height.
  6588. * @param {Item} a The first item
  6589. * @param {Item} b The second item
  6590. * @param {{horizontal: number, vertical: number}} margin
  6591. * An object containing a horizontal and vertical
  6592. * minimum required margin.
  6593. * @return {boolean} true if a and b collide, else false
  6594. */
  6595. exports.collision = function(a, b, margin) {
  6596. return ((a.left - margin.horizontal + EPSILON) < (b.left + b.width) &&
  6597. (a.left + a.width + margin.horizontal - EPSILON) > b.left &&
  6598. (a.top - margin.vertical + EPSILON) < (b.top + b.height) &&
  6599. (a.top + a.height + margin.vertical - EPSILON) > b.top);
  6600. };
  6601. /***/ },
  6602. /* 17 */
  6603. /***/ function(module, exports, __webpack_require__) {
  6604. var moment = __webpack_require__(40);
  6605. /**
  6606. * @constructor TimeStep
  6607. * The class TimeStep is an iterator for dates. You provide a start date and an
  6608. * end date. The class itself determines the best scale (step size) based on the
  6609. * provided start Date, end Date, and minimumStep.
  6610. *
  6611. * If minimumStep is provided, the step size is chosen as close as possible
  6612. * to the minimumStep but larger than minimumStep. If minimumStep is not
  6613. * provided, the scale is set to 1 DAY.
  6614. * The minimumStep should correspond with the onscreen size of about 6 characters
  6615. *
  6616. * Alternatively, you can set a scale by hand.
  6617. * After creation, you can initialize the class by executing first(). Then you
  6618. * can iterate from the start date to the end date via next(). You can check if
  6619. * the end date is reached with the function hasNext(). After each step, you can
  6620. * retrieve the current date via getCurrent().
  6621. * The TimeStep has scales ranging from milliseconds, seconds, minutes, hours,
  6622. * days, to years.
  6623. *
  6624. * Version: 1.2
  6625. *
  6626. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  6627. * or new Date(2010, 9, 21, 23, 45, 00)
  6628. * @param {Date} [end] The end date
  6629. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  6630. */
  6631. function TimeStep(start, end, minimumStep) {
  6632. // variables
  6633. this.current = new Date();
  6634. this._start = new Date();
  6635. this._end = new Date();
  6636. this.autoScale = true;
  6637. this.scale = TimeStep.SCALE.DAY;
  6638. this.step = 1;
  6639. // initialize the range
  6640. this.setRange(start, end, minimumStep);
  6641. }
  6642. /// enum scale
  6643. TimeStep.SCALE = {
  6644. MILLISECOND: 1,
  6645. SECOND: 2,
  6646. MINUTE: 3,
  6647. HOUR: 4,
  6648. DAY: 5,
  6649. WEEKDAY: 6,
  6650. MONTH: 7,
  6651. YEAR: 8
  6652. };
  6653. /**
  6654. * Set a new range
  6655. * If minimumStep is provided, the step size is chosen as close as possible
  6656. * to the minimumStep but larger than minimumStep. If minimumStep is not
  6657. * provided, the scale is set to 1 DAY.
  6658. * The minimumStep should correspond with the onscreen size of about 6 characters
  6659. * @param {Date} [start] The start date and time.
  6660. * @param {Date} [end] The end date and time.
  6661. * @param {int} [minimumStep] Optional. Minimum step size in milliseconds
  6662. */
  6663. TimeStep.prototype.setRange = function(start, end, minimumStep) {
  6664. if (!(start instanceof Date) || !(end instanceof Date)) {
  6665. throw "No legal start or end date in method setRange";
  6666. }
  6667. this._start = (start != undefined) ? new Date(start.valueOf()) : new Date();
  6668. this._end = (end != undefined) ? new Date(end.valueOf()) : new Date();
  6669. if (this.autoScale) {
  6670. this.setMinimumStep(minimumStep);
  6671. }
  6672. };
  6673. /**
  6674. * Set the range iterator to the start date.
  6675. */
  6676. TimeStep.prototype.first = function() {
  6677. this.current = new Date(this._start.valueOf());
  6678. this.roundToMinor();
  6679. };
  6680. /**
  6681. * Round the current date to the first minor date value
  6682. * This must be executed once when the current date is set to start Date
  6683. */
  6684. TimeStep.prototype.roundToMinor = function() {
  6685. // round to floor
  6686. // IMPORTANT: we have no breaks in this switch! (this is no bug)
  6687. //noinspection FallthroughInSwitchStatementJS
  6688. switch (this.scale) {
  6689. case TimeStep.SCALE.YEAR:
  6690. this.current.setFullYear(this.step * Math.floor(this.current.getFullYear() / this.step));
  6691. this.current.setMonth(0);
  6692. case TimeStep.SCALE.MONTH: this.current.setDate(1);
  6693. case TimeStep.SCALE.DAY: // intentional fall through
  6694. case TimeStep.SCALE.WEEKDAY: this.current.setHours(0);
  6695. case TimeStep.SCALE.HOUR: this.current.setMinutes(0);
  6696. case TimeStep.SCALE.MINUTE: this.current.setSeconds(0);
  6697. case TimeStep.SCALE.SECOND: this.current.setMilliseconds(0);
  6698. //case TimeStep.SCALE.MILLISECOND: // nothing to do for milliseconds
  6699. }
  6700. if (this.step != 1) {
  6701. // round down to the first minor value that is a multiple of the current step size
  6702. switch (this.scale) {
  6703. case TimeStep.SCALE.MILLISECOND: this.current.setMilliseconds(this.current.getMilliseconds() - this.current.getMilliseconds() % this.step); break;
  6704. case TimeStep.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() - this.current.getSeconds() % this.step); break;
  6705. case TimeStep.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() - this.current.getMinutes() % this.step); break;
  6706. case TimeStep.SCALE.HOUR: this.current.setHours(this.current.getHours() - this.current.getHours() % this.step); break;
  6707. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  6708. case TimeStep.SCALE.DAY: this.current.setDate((this.current.getDate()-1) - (this.current.getDate()-1) % this.step + 1); break;
  6709. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() - this.current.getMonth() % this.step); break;
  6710. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() - this.current.getFullYear() % this.step); break;
  6711. default: break;
  6712. }
  6713. }
  6714. };
  6715. /**
  6716. * Check if the there is a next step
  6717. * @return {boolean} true if the current date has not passed the end date
  6718. */
  6719. TimeStep.prototype.hasNext = function () {
  6720. return (this.current.valueOf() <= this._end.valueOf());
  6721. };
  6722. /**
  6723. * Do the next step
  6724. */
  6725. TimeStep.prototype.next = function() {
  6726. var prev = this.current.valueOf();
  6727. // Two cases, needed to prevent issues with switching daylight savings
  6728. // (end of March and end of October)
  6729. if (this.current.getMonth() < 6) {
  6730. switch (this.scale) {
  6731. case TimeStep.SCALE.MILLISECOND:
  6732. this.current = new Date(this.current.valueOf() + this.step); break;
  6733. case TimeStep.SCALE.SECOND: this.current = new Date(this.current.valueOf() + this.step * 1000); break;
  6734. case TimeStep.SCALE.MINUTE: this.current = new Date(this.current.valueOf() + this.step * 1000 * 60); break;
  6735. case TimeStep.SCALE.HOUR:
  6736. this.current = new Date(this.current.valueOf() + this.step * 1000 * 60 * 60);
  6737. // in case of skipping an hour for daylight savings, adjust the hour again (else you get: 0h 5h 9h ... instead of 0h 4h 8h ...)
  6738. var h = this.current.getHours();
  6739. this.current.setHours(h - (h % this.step));
  6740. break;
  6741. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  6742. case TimeStep.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break;
  6743. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break;
  6744. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break;
  6745. default: break;
  6746. }
  6747. }
  6748. else {
  6749. switch (this.scale) {
  6750. case TimeStep.SCALE.MILLISECOND: this.current = new Date(this.current.valueOf() + this.step); break;
  6751. case TimeStep.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() + this.step); break;
  6752. case TimeStep.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() + this.step); break;
  6753. case TimeStep.SCALE.HOUR: this.current.setHours(this.current.getHours() + this.step); break;
  6754. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  6755. case TimeStep.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break;
  6756. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break;
  6757. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break;
  6758. default: break;
  6759. }
  6760. }
  6761. if (this.step != 1) {
  6762. // round down to the correct major value
  6763. switch (this.scale) {
  6764. case TimeStep.SCALE.MILLISECOND: if(this.current.getMilliseconds() < this.step) this.current.setMilliseconds(0); break;
  6765. case TimeStep.SCALE.SECOND: if(this.current.getSeconds() < this.step) this.current.setSeconds(0); break;
  6766. case TimeStep.SCALE.MINUTE: if(this.current.getMinutes() < this.step) this.current.setMinutes(0); break;
  6767. case TimeStep.SCALE.HOUR: if(this.current.getHours() < this.step) this.current.setHours(0); break;
  6768. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  6769. case TimeStep.SCALE.DAY: if(this.current.getDate() < this.step+1) this.current.setDate(1); break;
  6770. case TimeStep.SCALE.MONTH: if(this.current.getMonth() < this.step) this.current.setMonth(0); break;
  6771. case TimeStep.SCALE.YEAR: break; // nothing to do for year
  6772. default: break;
  6773. }
  6774. }
  6775. // safety mechanism: if current time is still unchanged, move to the end
  6776. if (this.current.valueOf() == prev) {
  6777. this.current = new Date(this._end.valueOf());
  6778. }
  6779. };
  6780. /**
  6781. * Get the current datetime
  6782. * @return {Date} current The current date
  6783. */
  6784. TimeStep.prototype.getCurrent = function() {
  6785. return this.current;
  6786. };
  6787. /**
  6788. * Set a custom scale. Autoscaling will be disabled.
  6789. * For example setScale(SCALE.MINUTES, 5) will result
  6790. * in minor steps of 5 minutes, and major steps of an hour.
  6791. *
  6792. * @param {TimeStep.SCALE} newScale
  6793. * A scale. Choose from SCALE.MILLISECOND,
  6794. * SCALE.SECOND, SCALE.MINUTE, SCALE.HOUR,
  6795. * SCALE.WEEKDAY, SCALE.DAY, SCALE.MONTH,
  6796. * SCALE.YEAR.
  6797. * @param {Number} newStep A step size, by default 1. Choose for
  6798. * example 1, 2, 5, or 10.
  6799. */
  6800. TimeStep.prototype.setScale = function(newScale, newStep) {
  6801. this.scale = newScale;
  6802. if (newStep > 0) {
  6803. this.step = newStep;
  6804. }
  6805. this.autoScale = false;
  6806. };
  6807. /**
  6808. * Enable or disable autoscaling
  6809. * @param {boolean} enable If true, autoascaling is set true
  6810. */
  6811. TimeStep.prototype.setAutoScale = function (enable) {
  6812. this.autoScale = enable;
  6813. };
  6814. /**
  6815. * Automatically determine the scale that bests fits the provided minimum step
  6816. * @param {Number} [minimumStep] The minimum step size in milliseconds
  6817. */
  6818. TimeStep.prototype.setMinimumStep = function(minimumStep) {
  6819. if (minimumStep == undefined) {
  6820. return;
  6821. }
  6822. var stepYear = (1000 * 60 * 60 * 24 * 30 * 12);
  6823. var stepMonth = (1000 * 60 * 60 * 24 * 30);
  6824. var stepDay = (1000 * 60 * 60 * 24);
  6825. var stepHour = (1000 * 60 * 60);
  6826. var stepMinute = (1000 * 60);
  6827. var stepSecond = (1000);
  6828. var stepMillisecond= (1);
  6829. // find the smallest step that is larger than the provided minimumStep
  6830. if (stepYear*1000 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1000;}
  6831. if (stepYear*500 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 500;}
  6832. if (stepYear*100 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 100;}
  6833. if (stepYear*50 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 50;}
  6834. if (stepYear*10 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 10;}
  6835. if (stepYear*5 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 5;}
  6836. if (stepYear > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1;}
  6837. if (stepMonth*3 > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 3;}
  6838. if (stepMonth > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 1;}
  6839. if (stepDay*5 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 5;}
  6840. if (stepDay*2 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 2;}
  6841. if (stepDay > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 1;}
  6842. if (stepDay/2 > minimumStep) {this.scale = TimeStep.SCALE.WEEKDAY; this.step = 1;}
  6843. if (stepHour*4 > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 4;}
  6844. if (stepHour > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 1;}
  6845. if (stepMinute*15 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 15;}
  6846. if (stepMinute*10 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 10;}
  6847. if (stepMinute*5 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 5;}
  6848. if (stepMinute > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 1;}
  6849. if (stepSecond*15 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 15;}
  6850. if (stepSecond*10 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 10;}
  6851. if (stepSecond*5 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 5;}
  6852. if (stepSecond > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 1;}
  6853. if (stepMillisecond*200 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 200;}
  6854. if (stepMillisecond*100 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 100;}
  6855. if (stepMillisecond*50 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 50;}
  6856. if (stepMillisecond*10 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 10;}
  6857. if (stepMillisecond*5 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 5;}
  6858. if (stepMillisecond > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 1;}
  6859. };
  6860. /**
  6861. * Snap a date to a rounded value.
  6862. * The snap intervals are dependent on the current scale and step.
  6863. * @param {Date} date the date to be snapped.
  6864. * @return {Date} snappedDate
  6865. */
  6866. TimeStep.prototype.snap = function(date) {
  6867. var clone = new Date(date.valueOf());
  6868. if (this.scale == TimeStep.SCALE.YEAR) {
  6869. var year = clone.getFullYear() + Math.round(clone.getMonth() / 12);
  6870. clone.setFullYear(Math.round(year / this.step) * this.step);
  6871. clone.setMonth(0);
  6872. clone.setDate(0);
  6873. clone.setHours(0);
  6874. clone.setMinutes(0);
  6875. clone.setSeconds(0);
  6876. clone.setMilliseconds(0);
  6877. }
  6878. else if (this.scale == TimeStep.SCALE.MONTH) {
  6879. if (clone.getDate() > 15) {
  6880. clone.setDate(1);
  6881. clone.setMonth(clone.getMonth() + 1);
  6882. // important: first set Date to 1, after that change the month.
  6883. }
  6884. else {
  6885. clone.setDate(1);
  6886. }
  6887. clone.setHours(0);
  6888. clone.setMinutes(0);
  6889. clone.setSeconds(0);
  6890. clone.setMilliseconds(0);
  6891. }
  6892. else if (this.scale == TimeStep.SCALE.DAY) {
  6893. //noinspection FallthroughInSwitchStatementJS
  6894. switch (this.step) {
  6895. case 5:
  6896. case 2:
  6897. clone.setHours(Math.round(clone.getHours() / 24) * 24); break;
  6898. default:
  6899. clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
  6900. }
  6901. clone.setMinutes(0);
  6902. clone.setSeconds(0);
  6903. clone.setMilliseconds(0);
  6904. }
  6905. else if (this.scale == TimeStep.SCALE.WEEKDAY) {
  6906. //noinspection FallthroughInSwitchStatementJS
  6907. switch (this.step) {
  6908. case 5:
  6909. case 2:
  6910. clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
  6911. default:
  6912. clone.setHours(Math.round(clone.getHours() / 6) * 6); break;
  6913. }
  6914. clone.setMinutes(0);
  6915. clone.setSeconds(0);
  6916. clone.setMilliseconds(0);
  6917. }
  6918. else if (this.scale == TimeStep.SCALE.HOUR) {
  6919. switch (this.step) {
  6920. case 4:
  6921. clone.setMinutes(Math.round(clone.getMinutes() / 60) * 60); break;
  6922. default:
  6923. clone.setMinutes(Math.round(clone.getMinutes() / 30) * 30); break;
  6924. }
  6925. clone.setSeconds(0);
  6926. clone.setMilliseconds(0);
  6927. } else if (this.scale == TimeStep.SCALE.MINUTE) {
  6928. //noinspection FallthroughInSwitchStatementJS
  6929. switch (this.step) {
  6930. case 15:
  6931. case 10:
  6932. clone.setMinutes(Math.round(clone.getMinutes() / 5) * 5);
  6933. clone.setSeconds(0);
  6934. break;
  6935. case 5:
  6936. clone.setSeconds(Math.round(clone.getSeconds() / 60) * 60); break;
  6937. default:
  6938. clone.setSeconds(Math.round(clone.getSeconds() / 30) * 30); break;
  6939. }
  6940. clone.setMilliseconds(0);
  6941. }
  6942. else if (this.scale == TimeStep.SCALE.SECOND) {
  6943. //noinspection FallthroughInSwitchStatementJS
  6944. switch (this.step) {
  6945. case 15:
  6946. case 10:
  6947. clone.setSeconds(Math.round(clone.getSeconds() / 5) * 5);
  6948. clone.setMilliseconds(0);
  6949. break;
  6950. case 5:
  6951. clone.setMilliseconds(Math.round(clone.getMilliseconds() / 1000) * 1000); break;
  6952. default:
  6953. clone.setMilliseconds(Math.round(clone.getMilliseconds() / 500) * 500); break;
  6954. }
  6955. }
  6956. else if (this.scale == TimeStep.SCALE.MILLISECOND) {
  6957. var step = this.step > 5 ? this.step / 2 : 1;
  6958. clone.setMilliseconds(Math.round(clone.getMilliseconds() / step) * step);
  6959. }
  6960. return clone;
  6961. };
  6962. /**
  6963. * Check if the current value is a major value (for example when the step
  6964. * is DAY, a major value is each first day of the MONTH)
  6965. * @return {boolean} true if current date is major, else false.
  6966. */
  6967. TimeStep.prototype.isMajor = function() {
  6968. switch (this.scale) {
  6969. case TimeStep.SCALE.MILLISECOND:
  6970. return (this.current.getMilliseconds() == 0);
  6971. case TimeStep.SCALE.SECOND:
  6972. return (this.current.getSeconds() == 0);
  6973. case TimeStep.SCALE.MINUTE:
  6974. return (this.current.getHours() == 0) && (this.current.getMinutes() == 0);
  6975. // Note: this is no bug. Major label is equal for both minute and hour scale
  6976. case TimeStep.SCALE.HOUR:
  6977. return (this.current.getHours() == 0);
  6978. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  6979. case TimeStep.SCALE.DAY:
  6980. return (this.current.getDate() == 1);
  6981. case TimeStep.SCALE.MONTH:
  6982. return (this.current.getMonth() == 0);
  6983. case TimeStep.SCALE.YEAR:
  6984. return false;
  6985. default:
  6986. return false;
  6987. }
  6988. };
  6989. /**
  6990. * Returns formatted text for the minor axislabel, depending on the current
  6991. * date and the scale. For example when scale is MINUTE, the current time is
  6992. * formatted as "hh:mm".
  6993. * @param {Date} [date] custom date. if not provided, current date is taken
  6994. */
  6995. TimeStep.prototype.getLabelMinor = function(date) {
  6996. if (date == undefined) {
  6997. date = this.current;
  6998. }
  6999. switch (this.scale) {
  7000. case TimeStep.SCALE.MILLISECOND: return moment(date).format('SSS');
  7001. case TimeStep.SCALE.SECOND: return moment(date).format('s');
  7002. case TimeStep.SCALE.MINUTE: return moment(date).format('HH:mm');
  7003. case TimeStep.SCALE.HOUR: return moment(date).format('HH:mm');
  7004. case TimeStep.SCALE.WEEKDAY: return moment(date).format('ddd D');
  7005. case TimeStep.SCALE.DAY: return moment(date).format('D');
  7006. case TimeStep.SCALE.MONTH: return moment(date).format('MMM');
  7007. case TimeStep.SCALE.YEAR: return moment(date).format('YYYY');
  7008. default: return '';
  7009. }
  7010. };
  7011. /**
  7012. * Returns formatted text for the major axis label, depending on the current
  7013. * date and the scale. For example when scale is MINUTE, the major scale is
  7014. * hours, and the hour will be formatted as "hh".
  7015. * @param {Date} [date] custom date. if not provided, current date is taken
  7016. */
  7017. TimeStep.prototype.getLabelMajor = function(date) {
  7018. if (date == undefined) {
  7019. date = this.current;
  7020. }
  7021. //noinspection FallthroughInSwitchStatementJS
  7022. switch (this.scale) {
  7023. case TimeStep.SCALE.MILLISECOND:return moment(date).format('HH:mm:ss');
  7024. case TimeStep.SCALE.SECOND: return moment(date).format('D MMMM HH:mm');
  7025. case TimeStep.SCALE.MINUTE:
  7026. case TimeStep.SCALE.HOUR: return moment(date).format('ddd D MMMM');
  7027. case TimeStep.SCALE.WEEKDAY:
  7028. case TimeStep.SCALE.DAY: return moment(date).format('MMMM YYYY');
  7029. case TimeStep.SCALE.MONTH: return moment(date).format('YYYY');
  7030. case TimeStep.SCALE.YEAR: return '';
  7031. default: return '';
  7032. }
  7033. };
  7034. module.exports = TimeStep;
  7035. /***/ },
  7036. /* 18 */
  7037. /***/ function(module, exports, __webpack_require__) {
  7038. /**
  7039. * Prototype for visual components
  7040. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} [body]
  7041. * @param {Object} [options]
  7042. */
  7043. function Component (body, options) {
  7044. this.options = null;
  7045. this.props = null;
  7046. }
  7047. /**
  7048. * Set options for the component. The new options will be merged into the
  7049. * current options.
  7050. * @param {Object} options
  7051. */
  7052. Component.prototype.setOptions = function(options) {
  7053. if (options) {
  7054. util.extend(this.options, options);
  7055. }
  7056. };
  7057. /**
  7058. * Repaint the component
  7059. * @return {boolean} Returns true if the component is resized
  7060. */
  7061. Component.prototype.redraw = function() {
  7062. // should be implemented by the component
  7063. return false;
  7064. };
  7065. /**
  7066. * Destroy the component. Cleanup DOM and event listeners
  7067. */
  7068. Component.prototype.destroy = function() {
  7069. // should be implemented by the component
  7070. };
  7071. /**
  7072. * Test whether the component is resized since the last time _isResized() was
  7073. * called.
  7074. * @return {Boolean} Returns true if the component is resized
  7075. * @protected
  7076. */
  7077. Component.prototype._isResized = function() {
  7078. var resized = (this.props._previousWidth !== this.props.width ||
  7079. this.props._previousHeight !== this.props.height);
  7080. this.props._previousWidth = this.props.width;
  7081. this.props._previousHeight = this.props.height;
  7082. return resized;
  7083. };
  7084. module.exports = Component;
  7085. /***/ },
  7086. /* 19 */
  7087. /***/ function(module, exports, __webpack_require__) {
  7088. var util = __webpack_require__(1);
  7089. var Component = __webpack_require__(18);
  7090. var moment = __webpack_require__(40);
  7091. var locales = __webpack_require__(44);
  7092. /**
  7093. * A current time bar
  7094. * @param {{range: Range, dom: Object, domProps: Object}} body
  7095. * @param {Object} [options] Available parameters:
  7096. * {Boolean} [showCurrentTime]
  7097. * @constructor CurrentTime
  7098. * @extends Component
  7099. */
  7100. function CurrentTime (body, options) {
  7101. this.body = body;
  7102. // default options
  7103. this.defaultOptions = {
  7104. showCurrentTime: true,
  7105. locales: locales,
  7106. locale: 'en'
  7107. };
  7108. this.options = util.extend({}, this.defaultOptions);
  7109. this.offset = 0;
  7110. this._create();
  7111. this.setOptions(options);
  7112. }
  7113. CurrentTime.prototype = new Component();
  7114. /**
  7115. * Create the HTML DOM for the current time bar
  7116. * @private
  7117. */
  7118. CurrentTime.prototype._create = function() {
  7119. var bar = document.createElement('div');
  7120. bar.className = 'currenttime';
  7121. bar.style.position = 'absolute';
  7122. bar.style.top = '0px';
  7123. bar.style.height = '100%';
  7124. this.bar = bar;
  7125. };
  7126. /**
  7127. * Destroy the CurrentTime bar
  7128. */
  7129. CurrentTime.prototype.destroy = function () {
  7130. this.options.showCurrentTime = false;
  7131. this.redraw(); // will remove the bar from the DOM and stop refreshing
  7132. this.body = null;
  7133. };
  7134. /**
  7135. * Set options for the component. Options will be merged in current options.
  7136. * @param {Object} options Available parameters:
  7137. * {boolean} [showCurrentTime]
  7138. */
  7139. CurrentTime.prototype.setOptions = function(options) {
  7140. if (options) {
  7141. // copy all options that we know
  7142. util.selectiveExtend(['showCurrentTime', 'locale', 'locales'], this.options, options);
  7143. }
  7144. };
  7145. /**
  7146. * Repaint the component
  7147. * @return {boolean} Returns true if the component is resized
  7148. */
  7149. CurrentTime.prototype.redraw = function() {
  7150. if (this.options.showCurrentTime) {
  7151. var parent = this.body.dom.backgroundVertical;
  7152. if (this.bar.parentNode != parent) {
  7153. // attach to the dom
  7154. if (this.bar.parentNode) {
  7155. this.bar.parentNode.removeChild(this.bar);
  7156. }
  7157. parent.appendChild(this.bar);
  7158. this.start();
  7159. }
  7160. var now = new Date(new Date().valueOf() + this.offset);
  7161. var x = this.body.util.toScreen(now);
  7162. var locale = this.options.locales[this.options.locale];
  7163. var title = locale.current + ' ' + locale.time + ': ' + moment(now).format('dddd, MMMM Do YYYY, H:mm:ss');
  7164. title = title.charAt(0).toUpperCase() + title.substring(1);
  7165. this.bar.style.left = x + 'px';
  7166. this.bar.title = title;
  7167. }
  7168. else {
  7169. // remove the line from the DOM
  7170. if (this.bar.parentNode) {
  7171. this.bar.parentNode.removeChild(this.bar);
  7172. }
  7173. this.stop();
  7174. }
  7175. return false;
  7176. };
  7177. /**
  7178. * Start auto refreshing the current time bar
  7179. */
  7180. CurrentTime.prototype.start = function() {
  7181. var me = this;
  7182. function update () {
  7183. me.stop();
  7184. // determine interval to refresh
  7185. var scale = me.body.range.conversion(me.body.domProps.center.width).scale;
  7186. var interval = 1 / scale / 10;
  7187. if (interval < 30) interval = 30;
  7188. if (interval > 1000) interval = 1000;
  7189. me.redraw();
  7190. // start a timer to adjust for the new time
  7191. me.currentTimeTimer = setTimeout(update, interval);
  7192. }
  7193. update();
  7194. };
  7195. /**
  7196. * Stop auto refreshing the current time bar
  7197. */
  7198. CurrentTime.prototype.stop = function() {
  7199. if (this.currentTimeTimer !== undefined) {
  7200. clearTimeout(this.currentTimeTimer);
  7201. delete this.currentTimeTimer;
  7202. }
  7203. };
  7204. /**
  7205. * Set a current time. This can be used for example to ensure that a client's
  7206. * time is synchronized with a shared server time.
  7207. * @param {Date | String | Number} time A Date, unix timestamp, or
  7208. * ISO date string.
  7209. */
  7210. CurrentTime.prototype.setCurrentTime = function(time) {
  7211. var t = util.convert(time, 'Date').valueOf();
  7212. var now = new Date().valueOf();
  7213. this.offset = t - now;
  7214. this.redraw();
  7215. };
  7216. /**
  7217. * Get the current time.
  7218. * @return {Date} Returns the current time.
  7219. */
  7220. CurrentTime.prototype.getCurrentTime = function() {
  7221. return new Date(new Date().valueOf() + this.offset);
  7222. };
  7223. module.exports = CurrentTime;
  7224. /***/ },
  7225. /* 20 */
  7226. /***/ function(module, exports, __webpack_require__) {
  7227. var Hammer = __webpack_require__(41);
  7228. var util = __webpack_require__(1);
  7229. var Component = __webpack_require__(18);
  7230. var moment = __webpack_require__(40);
  7231. var locales = __webpack_require__(44);
  7232. /**
  7233. * A custom time bar
  7234. * @param {{range: Range, dom: Object}} body
  7235. * @param {Object} [options] Available parameters:
  7236. * {Boolean} [showCustomTime]
  7237. * @constructor CustomTime
  7238. * @extends Component
  7239. */
  7240. function CustomTime (body, options) {
  7241. this.body = body;
  7242. // default options
  7243. this.defaultOptions = {
  7244. showCustomTime: false,
  7245. locales: locales,
  7246. locale: 'en'
  7247. };
  7248. this.options = util.extend({}, this.defaultOptions);
  7249. this.customTime = new Date();
  7250. this.eventParams = {}; // stores state parameters while dragging the bar
  7251. // create the DOM
  7252. this._create();
  7253. this.setOptions(options);
  7254. }
  7255. CustomTime.prototype = new Component();
  7256. /**
  7257. * Set options for the component. Options will be merged in current options.
  7258. * @param {Object} options Available parameters:
  7259. * {boolean} [showCustomTime]
  7260. */
  7261. CustomTime.prototype.setOptions = function(options) {
  7262. if (options) {
  7263. // copy all options that we know
  7264. util.selectiveExtend(['showCustomTime', 'locale', 'locales'], this.options, options);
  7265. }
  7266. };
  7267. /**
  7268. * Create the DOM for the custom time
  7269. * @private
  7270. */
  7271. CustomTime.prototype._create = function() {
  7272. var bar = document.createElement('div');
  7273. bar.className = 'customtime';
  7274. bar.style.position = 'absolute';
  7275. bar.style.top = '0px';
  7276. bar.style.height = '100%';
  7277. this.bar = bar;
  7278. var drag = document.createElement('div');
  7279. drag.style.position = 'relative';
  7280. drag.style.top = '0px';
  7281. drag.style.left = '-10px';
  7282. drag.style.height = '100%';
  7283. drag.style.width = '20px';
  7284. bar.appendChild(drag);
  7285. // attach event listeners
  7286. this.hammer = Hammer(bar, {
  7287. prevent_default: true
  7288. });
  7289. this.hammer.on('dragstart', this._onDragStart.bind(this));
  7290. this.hammer.on('drag', this._onDrag.bind(this));
  7291. this.hammer.on('dragend', this._onDragEnd.bind(this));
  7292. };
  7293. /**
  7294. * Destroy the CustomTime bar
  7295. */
  7296. CustomTime.prototype.destroy = function () {
  7297. this.options.showCustomTime = false;
  7298. this.redraw(); // will remove the bar from the DOM
  7299. this.hammer.enable(false);
  7300. this.hammer = null;
  7301. this.body = null;
  7302. };
  7303. /**
  7304. * Repaint the component
  7305. * @return {boolean} Returns true if the component is resized
  7306. */
  7307. CustomTime.prototype.redraw = function () {
  7308. if (this.options.showCustomTime) {
  7309. var parent = this.body.dom.backgroundVertical;
  7310. if (this.bar.parentNode != parent) {
  7311. // attach to the dom
  7312. if (this.bar.parentNode) {
  7313. this.bar.parentNode.removeChild(this.bar);
  7314. }
  7315. parent.appendChild(this.bar);
  7316. }
  7317. var x = this.body.util.toScreen(this.customTime);
  7318. var locale = this.options.locales[this.options.locale];
  7319. var title = locale.time + ': ' + moment(this.customTime).format('dddd, MMMM Do YYYY, H:mm:ss');
  7320. title = title.charAt(0).toUpperCase() + title.substring(1);
  7321. this.bar.style.left = x + 'px';
  7322. this.bar.title = title;
  7323. }
  7324. else {
  7325. // remove the line from the DOM
  7326. if (this.bar.parentNode) {
  7327. this.bar.parentNode.removeChild(this.bar);
  7328. }
  7329. }
  7330. return false;
  7331. };
  7332. /**
  7333. * Set custom time.
  7334. * @param {Date | number | string} time
  7335. */
  7336. CustomTime.prototype.setCustomTime = function(time) {
  7337. this.customTime = util.convert(time, 'Date');
  7338. this.redraw();
  7339. };
  7340. /**
  7341. * Retrieve the current custom time.
  7342. * @return {Date} customTime
  7343. */
  7344. CustomTime.prototype.getCustomTime = function() {
  7345. return new Date(this.customTime.valueOf());
  7346. };
  7347. /**
  7348. * Start moving horizontally
  7349. * @param {Event} event
  7350. * @private
  7351. */
  7352. CustomTime.prototype._onDragStart = function(event) {
  7353. this.eventParams.dragging = true;
  7354. this.eventParams.customTime = this.customTime;
  7355. event.stopPropagation();
  7356. event.preventDefault();
  7357. };
  7358. /**
  7359. * Perform moving operating.
  7360. * @param {Event} event
  7361. * @private
  7362. */
  7363. CustomTime.prototype._onDrag = function (event) {
  7364. if (!this.eventParams.dragging) return;
  7365. var deltaX = event.gesture.deltaX,
  7366. x = this.body.util.toScreen(this.eventParams.customTime) + deltaX,
  7367. time = this.body.util.toTime(x);
  7368. this.setCustomTime(time);
  7369. // fire a timechange event
  7370. this.body.emitter.emit('timechange', {
  7371. time: new Date(this.customTime.valueOf())
  7372. });
  7373. event.stopPropagation();
  7374. event.preventDefault();
  7375. };
  7376. /**
  7377. * Stop moving operating.
  7378. * @param {event} event
  7379. * @private
  7380. */
  7381. CustomTime.prototype._onDragEnd = function (event) {
  7382. if (!this.eventParams.dragging) return;
  7383. // fire a timechanged event
  7384. this.body.emitter.emit('timechanged', {
  7385. time: new Date(this.customTime.valueOf())
  7386. });
  7387. event.stopPropagation();
  7388. event.preventDefault();
  7389. };
  7390. module.exports = CustomTime;
  7391. /***/ },
  7392. /* 21 */
  7393. /***/ function(module, exports, __webpack_require__) {
  7394. var util = __webpack_require__(1);
  7395. var DOMutil = __webpack_require__(2);
  7396. var Component = __webpack_require__(18);
  7397. var DataStep = __webpack_require__(14);
  7398. /**
  7399. * A horizontal time axis
  7400. * @param {Object} [options] See DataAxis.setOptions for the available
  7401. * options.
  7402. * @constructor DataAxis
  7403. * @extends Component
  7404. * @param body
  7405. */
  7406. function DataAxis (body, options, svg, linegraphOptions) {
  7407. this.id = util.randomUUID();
  7408. this.body = body;
  7409. this.defaultOptions = {
  7410. orientation: 'left', // supported: 'left', 'right'
  7411. showMinorLabels: true,
  7412. showMajorLabels: true,
  7413. icons: true,
  7414. majorLinesOffset: 7,
  7415. minorLinesOffset: 4,
  7416. labelOffsetX: 10,
  7417. labelOffsetY: 2,
  7418. iconWidth: 20,
  7419. width: '40px',
  7420. visible: true,
  7421. customRange: {
  7422. left: {min:undefined, max:undefined},
  7423. right: {min:undefined, max:undefined}
  7424. }
  7425. };
  7426. this.linegraphOptions = linegraphOptions;
  7427. this.linegraphSVG = svg;
  7428. this.props = {};
  7429. this.DOMelements = { // dynamic elements
  7430. lines: {},
  7431. labels: {}
  7432. };
  7433. this.dom = {};
  7434. this.range = {start:0, end:0};
  7435. this.options = util.extend({}, this.defaultOptions);
  7436. this.conversionFactor = 1;
  7437. this.setOptions(options);
  7438. this.width = Number(('' + this.options.width).replace("px",""));
  7439. this.minWidth = this.width;
  7440. this.height = this.linegraphSVG.offsetHeight;
  7441. this.stepPixels = 25;
  7442. this.stepPixelsForced = 25;
  7443. this.lineOffset = 0;
  7444. this.master = true;
  7445. this.svgElements = {};
  7446. this.groups = {};
  7447. this.amountOfGroups = 0;
  7448. // create the HTML DOM
  7449. this._create();
  7450. }
  7451. DataAxis.prototype = new Component();
  7452. DataAxis.prototype.addGroup = function(label, graphOptions) {
  7453. if (!this.groups.hasOwnProperty(label)) {
  7454. this.groups[label] = graphOptions;
  7455. }
  7456. this.amountOfGroups += 1;
  7457. };
  7458. DataAxis.prototype.updateGroup = function(label, graphOptions) {
  7459. this.groups[label] = graphOptions;
  7460. };
  7461. DataAxis.prototype.removeGroup = function(label) {
  7462. if (this.groups.hasOwnProperty(label)) {
  7463. delete this.groups[label];
  7464. this.amountOfGroups -= 1;
  7465. }
  7466. };
  7467. DataAxis.prototype.setOptions = function (options) {
  7468. if (options) {
  7469. var redraw = false;
  7470. if (this.options.orientation != options.orientation && options.orientation !== undefined) {
  7471. redraw = true;
  7472. }
  7473. var fields = [
  7474. 'orientation',
  7475. 'showMinorLabels',
  7476. 'showMajorLabels',
  7477. 'icons',
  7478. 'majorLinesOffset',
  7479. 'minorLinesOffset',
  7480. 'labelOffsetX',
  7481. 'labelOffsetY',
  7482. 'iconWidth',
  7483. 'width',
  7484. 'visible',
  7485. 'customRange'
  7486. ];
  7487. util.selectiveExtend(fields, this.options, options);
  7488. this.minWidth = Number(('' + this.options.width).replace("px",""));
  7489. if (redraw == true && this.dom.frame) {
  7490. this.hide();
  7491. this.show();
  7492. }
  7493. }
  7494. };
  7495. /**
  7496. * Create the HTML DOM for the DataAxis
  7497. */
  7498. DataAxis.prototype._create = function() {
  7499. this.dom.frame = document.createElement('div');
  7500. this.dom.frame.style.width = this.options.width;
  7501. this.dom.frame.style.height = this.height;
  7502. this.dom.lineContainer = document.createElement('div');
  7503. this.dom.lineContainer.style.width = '100%';
  7504. this.dom.lineContainer.style.height = this.height;
  7505. // create svg element for graph drawing.
  7506. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  7507. this.svg.style.position = "absolute";
  7508. this.svg.style.top = '0px';
  7509. this.svg.style.height = '100%';
  7510. this.svg.style.width = '100%';
  7511. this.svg.style.display = "block";
  7512. this.dom.frame.appendChild(this.svg);
  7513. };
  7514. DataAxis.prototype._redrawGroupIcons = function () {
  7515. DOMutil.prepareElements(this.svgElements);
  7516. var x;
  7517. var iconWidth = this.options.iconWidth;
  7518. var iconHeight = 15;
  7519. var iconOffset = 4;
  7520. var y = iconOffset + 0.5 * iconHeight;
  7521. if (this.options.orientation == 'left') {
  7522. x = iconOffset;
  7523. }
  7524. else {
  7525. x = this.width - iconWidth - iconOffset;
  7526. }
  7527. for (var groupId in this.groups) {
  7528. if (this.groups.hasOwnProperty(groupId)) {
  7529. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  7530. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  7531. y += iconHeight + iconOffset;
  7532. }
  7533. }
  7534. }
  7535. DOMutil.cleanupElements(this.svgElements);
  7536. };
  7537. /**
  7538. * Create the HTML DOM for the DataAxis
  7539. */
  7540. DataAxis.prototype.show = function() {
  7541. if (!this.dom.frame.parentNode) {
  7542. if (this.options.orientation == 'left') {
  7543. this.body.dom.left.appendChild(this.dom.frame);
  7544. }
  7545. else {
  7546. this.body.dom.right.appendChild(this.dom.frame);
  7547. }
  7548. }
  7549. if (!this.dom.lineContainer.parentNode) {
  7550. this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer);
  7551. }
  7552. };
  7553. /**
  7554. * Create the HTML DOM for the DataAxis
  7555. */
  7556. DataAxis.prototype.hide = function() {
  7557. if (this.dom.frame.parentNode) {
  7558. this.dom.frame.parentNode.removeChild(this.dom.frame);
  7559. }
  7560. if (this.dom.lineContainer.parentNode) {
  7561. this.dom.lineContainer.parentNode.removeChild(this.dom.lineContainer);
  7562. }
  7563. };
  7564. /**
  7565. * Set a range (start and end)
  7566. * @param end
  7567. * @param start
  7568. * @param end
  7569. */
  7570. DataAxis.prototype.setRange = function (start, end) {
  7571. this.range.start = start;
  7572. this.range.end = end;
  7573. };
  7574. /**
  7575. * Repaint the component
  7576. * @return {boolean} Returns true if the component is resized
  7577. */
  7578. DataAxis.prototype.redraw = function () {
  7579. var changeCalled = false;
  7580. var activeGroups = 0;
  7581. for (var groupId in this.groups) {
  7582. if (this.groups.hasOwnProperty(groupId)) {
  7583. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  7584. activeGroups++;
  7585. }
  7586. }
  7587. }
  7588. if (this.amountOfGroups == 0 || activeGroups == 0) {
  7589. this.hide();
  7590. }
  7591. else {
  7592. this.show();
  7593. this.height = Number(this.linegraphSVG.style.height.replace("px",""));
  7594. // svg offsetheight did not work in firefox and explorer...
  7595. this.dom.lineContainer.style.height = this.height + 'px';
  7596. this.width = this.options.visible == true ? Number(('' + this.options.width).replace("px","")) : 0;
  7597. var props = this.props;
  7598. var frame = this.dom.frame;
  7599. // update classname
  7600. frame.className = 'dataaxis';
  7601. // calculate character width and height
  7602. this._calculateCharSize();
  7603. var orientation = this.options.orientation;
  7604. var showMinorLabels = this.options.showMinorLabels;
  7605. var showMajorLabels = this.options.showMajorLabels;
  7606. // determine the width and height of the elemens for the axis
  7607. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  7608. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  7609. props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset;
  7610. props.minorLineHeight = 1;
  7611. props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset;
  7612. props.majorLineHeight = 1;
  7613. // take frame offline while updating (is almost twice as fast)
  7614. if (orientation == 'left') {
  7615. frame.style.top = '0';
  7616. frame.style.left = '0';
  7617. frame.style.bottom = '';
  7618. frame.style.width = this.width + 'px';
  7619. frame.style.height = this.height + "px";
  7620. }
  7621. else { // right
  7622. frame.style.top = '';
  7623. frame.style.bottom = '0';
  7624. frame.style.left = '0';
  7625. frame.style.width = this.width + 'px';
  7626. frame.style.height = this.height + "px";
  7627. }
  7628. changeCalled = this._redrawLabels();
  7629. if (this.options.icons == true) {
  7630. this._redrawGroupIcons();
  7631. }
  7632. }
  7633. return changeCalled;
  7634. };
  7635. /**
  7636. * Repaint major and minor text labels and vertical grid lines
  7637. * @private
  7638. */
  7639. DataAxis.prototype._redrawLabels = function () {
  7640. DOMutil.prepareElements(this.DOMelements.lines);
  7641. DOMutil.prepareElements(this.DOMelements.labels);
  7642. var orientation = this.options['orientation'];
  7643. // calculate range and step (step such that we have space for 7 characters per label)
  7644. var minimumStep = this.master ? this.props.majorCharHeight || 10 : this.stepPixelsForced;
  7645. var step = new DataStep(this.range.start, this.range.end, minimumStep, this.dom.frame.offsetHeight, this.options.customRange[this.options.orientation]);
  7646. this.step = step;
  7647. // get the distance in pixels for a step
  7648. // dead space is space that is "left over" after a step
  7649. var stepPixels = (this.dom.frame.offsetHeight - (step.deadSpace * (this.dom.frame.offsetHeight / step.marginRange))) / (((step.marginRange - step.deadSpace) / step.step));
  7650. this.stepPixels = stepPixels;
  7651. var amountOfSteps = this.height / stepPixels;
  7652. var stepDifference = 0;
  7653. if (this.master == false) {
  7654. stepPixels = this.stepPixelsForced;
  7655. stepDifference = Math.round((this.dom.frame.offsetHeight / stepPixels) - amountOfSteps);
  7656. for (var i = 0; i < 0.5 * stepDifference; i++) {
  7657. step.previous();
  7658. }
  7659. amountOfSteps = this.height / stepPixels;
  7660. }
  7661. else {
  7662. amountOfSteps += 0.25;
  7663. }
  7664. this.valueAtZero = step.marginEnd;
  7665. var marginStartPos = 0;
  7666. // do not draw the first label
  7667. var max = 1;
  7668. this.maxLabelSize = 0;
  7669. var y = 0;
  7670. while (max < Math.round(amountOfSteps)) {
  7671. step.next();
  7672. y = Math.round(max * stepPixels);
  7673. marginStartPos = max * stepPixels;
  7674. var isMajor = step.isMajor();
  7675. if (this.options['showMinorLabels'] && isMajor == false || this.master == false && this.options['showMinorLabels'] == true) {
  7676. this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis minor', this.props.minorCharHeight);
  7677. }
  7678. if (isMajor && this.options['showMajorLabels'] && this.master == true ||
  7679. this.options['showMinorLabels'] == false && this.master == false && isMajor == true) {
  7680. if (y >= 0) {
  7681. this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis major', this.props.majorCharHeight);
  7682. }
  7683. this._redrawLine(y, orientation, 'grid horizontal major', this.options.majorLinesOffset, this.props.majorLineWidth);
  7684. }
  7685. else {
  7686. this._redrawLine(y, orientation, 'grid horizontal minor', this.options.minorLinesOffset, this.props.minorLineWidth);
  7687. }
  7688. max++;
  7689. }
  7690. if (this.master == false) {
  7691. this.conversionFactor = y / (this.valueAtZero - step.current);
  7692. }
  7693. else {
  7694. this.conversionFactor = this.dom.frame.offsetHeight / step.marginRange;
  7695. }
  7696. var offset = this.options.icons == true ? this.options.iconWidth + this.options.labelOffsetX + 15 : this.options.labelOffsetX + 15;
  7697. // this will resize the yAxis to accomodate the labels.
  7698. if (this.maxLabelSize > (this.width - offset) && this.options.visible == true) {
  7699. this.width = this.maxLabelSize + offset;
  7700. this.options.width = this.width + "px";
  7701. DOMutil.cleanupElements(this.DOMelements.lines);
  7702. DOMutil.cleanupElements(this.DOMelements.labels);
  7703. this.redraw();
  7704. return true;
  7705. }
  7706. // this will resize the yAxis if it is too big for the labels.
  7707. else if (this.maxLabelSize < (this.width - offset) && this.options.visible == true && this.width > this.minWidth) {
  7708. this.width = Math.max(this.minWidth,this.maxLabelSize + offset);
  7709. this.options.width = this.width + "px";
  7710. DOMutil.cleanupElements(this.DOMelements.lines);
  7711. DOMutil.cleanupElements(this.DOMelements.labels);
  7712. this.redraw();
  7713. return true;
  7714. }
  7715. else {
  7716. DOMutil.cleanupElements(this.DOMelements.lines);
  7717. DOMutil.cleanupElements(this.DOMelements.labels);
  7718. return false;
  7719. }
  7720. };
  7721. DataAxis.prototype.convertValue = function (value) {
  7722. var invertedValue = this.valueAtZero - value;
  7723. var convertedValue = invertedValue * this.conversionFactor;
  7724. return convertedValue;
  7725. };
  7726. /**
  7727. * Create a label for the axis at position x
  7728. * @private
  7729. * @param y
  7730. * @param text
  7731. * @param orientation
  7732. * @param className
  7733. * @param characterHeight
  7734. */
  7735. DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) {
  7736. // reuse redundant label
  7737. var label = DOMutil.getDOMElement('div',this.DOMelements.labels, this.dom.frame); //this.dom.redundant.labels.shift();
  7738. label.className = className;
  7739. label.innerHTML = text;
  7740. if (orientation == 'left') {
  7741. label.style.left = '-' + this.options.labelOffsetX + 'px';
  7742. label.style.textAlign = "right";
  7743. }
  7744. else {
  7745. label.style.right = '-' + this.options.labelOffsetX + 'px';
  7746. label.style.textAlign = "left";
  7747. }
  7748. label.style.top = y - 0.5 * characterHeight + this.options.labelOffsetY + 'px';
  7749. text += '';
  7750. var largestWidth = Math.max(this.props.majorCharWidth,this.props.minorCharWidth);
  7751. if (this.maxLabelSize < text.length * largestWidth) {
  7752. this.maxLabelSize = text.length * largestWidth;
  7753. }
  7754. };
  7755. /**
  7756. * Create a minor line for the axis at position y
  7757. * @param y
  7758. * @param orientation
  7759. * @param className
  7760. * @param offset
  7761. * @param width
  7762. */
  7763. DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) {
  7764. if (this.master == true) {
  7765. var line = DOMutil.getDOMElement('div',this.DOMelements.lines, this.dom.lineContainer);//this.dom.redundant.lines.shift();
  7766. line.className = className;
  7767. line.innerHTML = '';
  7768. if (orientation == 'left') {
  7769. line.style.left = (this.width - offset) + 'px';
  7770. }
  7771. else {
  7772. line.style.right = (this.width - offset) + 'px';
  7773. }
  7774. line.style.width = width + 'px';
  7775. line.style.top = y + 'px';
  7776. }
  7777. };
  7778. /**
  7779. * Determine the size of text on the axis (both major and minor axis).
  7780. * The size is calculated only once and then cached in this.props.
  7781. * @private
  7782. */
  7783. DataAxis.prototype._calculateCharSize = function () {
  7784. // determine the char width and height on the minor axis
  7785. if (!('minorCharHeight' in this.props)) {
  7786. var textMinor = document.createTextNode('0');
  7787. var measureCharMinor = document.createElement('DIV');
  7788. measureCharMinor.className = 'yAxis minor measure';
  7789. measureCharMinor.appendChild(textMinor);
  7790. this.dom.frame.appendChild(measureCharMinor);
  7791. this.props.minorCharHeight = measureCharMinor.clientHeight;
  7792. this.props.minorCharWidth = measureCharMinor.clientWidth;
  7793. this.dom.frame.removeChild(measureCharMinor);
  7794. }
  7795. if (!('majorCharHeight' in this.props)) {
  7796. var textMajor = document.createTextNode('0');
  7797. var measureCharMajor = document.createElement('DIV');
  7798. measureCharMajor.className = 'yAxis major measure';
  7799. measureCharMajor.appendChild(textMajor);
  7800. this.dom.frame.appendChild(measureCharMajor);
  7801. this.props.majorCharHeight = measureCharMajor.clientHeight;
  7802. this.props.majorCharWidth = measureCharMajor.clientWidth;
  7803. this.dom.frame.removeChild(measureCharMajor);
  7804. }
  7805. };
  7806. /**
  7807. * Snap a date to a rounded value.
  7808. * The snap intervals are dependent on the current scale and step.
  7809. * @param {Date} date the date to be snapped.
  7810. * @return {Date} snappedDate
  7811. */
  7812. DataAxis.prototype.snap = function(date) {
  7813. return this.step.snap(date);
  7814. };
  7815. module.exports = DataAxis;
  7816. /***/ },
  7817. /* 22 */
  7818. /***/ function(module, exports, __webpack_require__) {
  7819. var util = __webpack_require__(1);
  7820. var DOMutil = __webpack_require__(2);
  7821. /**
  7822. * @constructor Group
  7823. * @param {Number | String} groupId
  7824. * @param {Object} data
  7825. * @param {ItemSet} itemSet
  7826. */
  7827. function GraphGroup (group, groupId, options, groupsUsingDefaultStyles) {
  7828. this.id = groupId;
  7829. var fields = ['sampling','style','sort','yAxisOrientation','barChart','drawPoints','shaded','catmullRom']
  7830. this.options = util.selectiveBridgeObject(fields,options);
  7831. this.usingDefaultStyle = group.className === undefined;
  7832. this.groupsUsingDefaultStyles = groupsUsingDefaultStyles;
  7833. this.zeroPosition = 0;
  7834. this.update(group);
  7835. if (this.usingDefaultStyle == true) {
  7836. this.groupsUsingDefaultStyles[0] += 1;
  7837. }
  7838. this.itemsData = [];
  7839. this.visible = group.visible === undefined ? true : group.visible;
  7840. }
  7841. GraphGroup.prototype.setItems = function(items) {
  7842. if (items != null) {
  7843. this.itemsData = items;
  7844. if (this.options.sort == true) {
  7845. this.itemsData.sort(function (a,b) {return a.x - b.x;})
  7846. }
  7847. }
  7848. else {
  7849. this.itemsData = [];
  7850. }
  7851. };
  7852. GraphGroup.prototype.setZeroPosition = function(pos) {
  7853. this.zeroPosition = pos;
  7854. };
  7855. GraphGroup.prototype.setOptions = function(options) {
  7856. if (options !== undefined) {
  7857. var fields = ['sampling','style','sort','yAxisOrientation','barChart'];
  7858. util.selectiveDeepExtend(fields, this.options, options);
  7859. util.mergeOptions(this.options, options,'catmullRom');
  7860. util.mergeOptions(this.options, options,'drawPoints');
  7861. util.mergeOptions(this.options, options,'shaded');
  7862. if (options.catmullRom) {
  7863. if (typeof options.catmullRom == 'object') {
  7864. if (options.catmullRom.parametrization) {
  7865. if (options.catmullRom.parametrization == 'uniform') {
  7866. this.options.catmullRom.alpha = 0;
  7867. }
  7868. else if (options.catmullRom.parametrization == 'chordal') {
  7869. this.options.catmullRom.alpha = 1.0;
  7870. }
  7871. else {
  7872. this.options.catmullRom.parametrization = 'centripetal';
  7873. this.options.catmullRom.alpha = 0.5;
  7874. }
  7875. }
  7876. }
  7877. }
  7878. }
  7879. };
  7880. GraphGroup.prototype.update = function(group) {
  7881. this.group = group;
  7882. this.content = group.content || 'graph';
  7883. this.className = group.className || this.className || "graphGroup" + this.groupsUsingDefaultStyles[0] % 10;
  7884. this.visible = group.visible === undefined ? true : group.visible;
  7885. this.setOptions(group.options);
  7886. };
  7887. GraphGroup.prototype.drawIcon = function(x, y, JSONcontainer, SVGcontainer, iconWidth, iconHeight) {
  7888. var fillHeight = iconHeight * 0.5;
  7889. var path, fillPath;
  7890. var outline = DOMutil.getSVGElement("rect", JSONcontainer, SVGcontainer);
  7891. outline.setAttributeNS(null, "x", x);
  7892. outline.setAttributeNS(null, "y", y - fillHeight);
  7893. outline.setAttributeNS(null, "width", iconWidth);
  7894. outline.setAttributeNS(null, "height", 2*fillHeight);
  7895. outline.setAttributeNS(null, "class", "outline");
  7896. if (this.options.style == 'line') {
  7897. path = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
  7898. path.setAttributeNS(null, "class", this.className);
  7899. path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+"");
  7900. if (this.options.shaded.enabled == true) {
  7901. fillPath = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
  7902. if (this.options.shaded.orientation == 'top') {
  7903. fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) +
  7904. "L"+x+","+y+" L"+ (x + iconWidth) + ","+y+" L"+ (x + iconWidth) + "," + (y - fillHeight));
  7905. }
  7906. else {
  7907. fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " +
  7908. "L"+x+"," + (y + fillHeight) + " " +
  7909. "L"+ (x + iconWidth) + "," + (y + fillHeight) +
  7910. "L"+ (x + iconWidth) + ","+y);
  7911. }
  7912. fillPath.setAttributeNS(null, "class", this.className + " iconFill");
  7913. }
  7914. if (this.options.drawPoints.enabled == true) {
  7915. DOMutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer);
  7916. }
  7917. }
  7918. else {
  7919. var barWidth = Math.round(0.3 * iconWidth);
  7920. var bar1Height = Math.round(0.4 * iconHeight);
  7921. var bar2Height = Math.round(0.75 * iconHeight);
  7922. var offset = Math.round((iconWidth - (2 * barWidth))/3);
  7923. DOMutil.drawBar(x + 0.5*barWidth + offset , y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' bar', JSONcontainer, SVGcontainer);
  7924. DOMutil.drawBar(x + 1.5*barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' bar', JSONcontainer, SVGcontainer);
  7925. }
  7926. };
  7927. /**
  7928. *
  7929. * @param iconWidth
  7930. * @param iconHeight
  7931. * @returns {{icon: HTMLElement, label: (group.content|*|string), orientation: (.options.yAxisOrientation|*)}}
  7932. */
  7933. GraphGroup.prototype.getLegend = function(iconWidth, iconHeight) {
  7934. var svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  7935. this.drawIcon(0,0.5*iconHeight,[],svg,iconWidth,iconHeight);
  7936. return {icon: svg, label: this.content, orientation:this.options.yAxisOrientation};
  7937. }
  7938. module.exports = GraphGroup;
  7939. /***/ },
  7940. /* 23 */
  7941. /***/ function(module, exports, __webpack_require__) {
  7942. var util = __webpack_require__(1);
  7943. var stack = __webpack_require__(16);
  7944. var ItemRange = __webpack_require__(31);
  7945. /**
  7946. * @constructor Group
  7947. * @param {Number | String} groupId
  7948. * @param {Object} data
  7949. * @param {ItemSet} itemSet
  7950. */
  7951. function Group (groupId, data, itemSet) {
  7952. this.groupId = groupId;
  7953. this.itemSet = itemSet;
  7954. this.dom = {};
  7955. this.props = {
  7956. label: {
  7957. width: 0,
  7958. height: 0
  7959. }
  7960. };
  7961. this.className = null;
  7962. this.items = {}; // items filtered by groupId of this group
  7963. this.visibleItems = []; // items currently visible in window
  7964. this.orderedItems = { // items sorted by start and by end
  7965. byStart: [],
  7966. byEnd: []
  7967. };
  7968. this._create();
  7969. this.setData(data);
  7970. }
  7971. /**
  7972. * Create DOM elements for the group
  7973. * @private
  7974. */
  7975. Group.prototype._create = function() {
  7976. var label = document.createElement('div');
  7977. label.className = 'vlabel';
  7978. this.dom.label = label;
  7979. var inner = document.createElement('div');
  7980. inner.className = 'inner';
  7981. label.appendChild(inner);
  7982. this.dom.inner = inner;
  7983. var foreground = document.createElement('div');
  7984. foreground.className = 'group';
  7985. foreground['timeline-group'] = this;
  7986. this.dom.foreground = foreground;
  7987. this.dom.background = document.createElement('div');
  7988. this.dom.background.className = 'group';
  7989. this.dom.axis = document.createElement('div');
  7990. this.dom.axis.className = 'group';
  7991. // create a hidden marker to detect when the Timelines container is attached
  7992. // to the DOM, or the style of a parent of the Timeline is changed from
  7993. // display:none is changed to visible.
  7994. this.dom.marker = document.createElement('div');
  7995. this.dom.marker.style.visibility = 'hidden';
  7996. this.dom.marker.innerHTML = '?';
  7997. this.dom.background.appendChild(this.dom.marker);
  7998. };
  7999. /**
  8000. * Set the group data for this group
  8001. * @param {Object} data Group data, can contain properties content and className
  8002. */
  8003. Group.prototype.setData = function(data) {
  8004. // update contents
  8005. var content = data && data.content;
  8006. if (content instanceof Element) {
  8007. this.dom.inner.appendChild(content);
  8008. }
  8009. else if (content !== undefined && content !== null) {
  8010. this.dom.inner.innerHTML = content;
  8011. }
  8012. else {
  8013. this.dom.inner.innerHTML = this.groupId || ''; // groupId can be null
  8014. }
  8015. // update title
  8016. this.dom.label.title = data && data.title || '';
  8017. if (!this.dom.inner.firstChild) {
  8018. util.addClassName(this.dom.inner, 'hidden');
  8019. }
  8020. else {
  8021. util.removeClassName(this.dom.inner, 'hidden');
  8022. }
  8023. // update className
  8024. var className = data && data.className || null;
  8025. if (className != this.className) {
  8026. if (this.className) {
  8027. util.removeClassName(this.dom.label, className);
  8028. util.removeClassName(this.dom.foreground, className);
  8029. util.removeClassName(this.dom.background, className);
  8030. util.removeClassName(this.dom.axis, className);
  8031. }
  8032. util.addClassName(this.dom.label, className);
  8033. util.addClassName(this.dom.foreground, className);
  8034. util.addClassName(this.dom.background, className);
  8035. util.addClassName(this.dom.axis, className);
  8036. }
  8037. };
  8038. /**
  8039. * Get the width of the group label
  8040. * @return {number} width
  8041. */
  8042. Group.prototype.getLabelWidth = function() {
  8043. return this.props.label.width;
  8044. };
  8045. /**
  8046. * Repaint this group
  8047. * @param {{start: number, end: number}} range
  8048. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  8049. * @param {boolean} [restack=false] Force restacking of all items
  8050. * @return {boolean} Returns true if the group is resized
  8051. */
  8052. Group.prototype.redraw = function(range, margin, restack) {
  8053. var resized = false;
  8054. this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
  8055. // force recalculation of the height of the items when the marker height changed
  8056. // (due to the Timeline being attached to the DOM or changed from display:none to visible)
  8057. var markerHeight = this.dom.marker.clientHeight;
  8058. if (markerHeight != this.lastMarkerHeight) {
  8059. this.lastMarkerHeight = markerHeight;
  8060. util.forEach(this.items, function (item) {
  8061. item.dirty = true;
  8062. if (item.displayed) item.redraw();
  8063. });
  8064. restack = true;
  8065. }
  8066. // reposition visible items vertically
  8067. if (this.itemSet.options.stack) { // TODO: ugly way to access options...
  8068. stack.stack(this.visibleItems, margin, restack);
  8069. }
  8070. else { // no stacking
  8071. stack.nostack(this.visibleItems, margin);
  8072. }
  8073. // recalculate the height of the group
  8074. var height;
  8075. var visibleItems = this.visibleItems;
  8076. if (visibleItems.length) {
  8077. var min = visibleItems[0].top;
  8078. var max = visibleItems[0].top + visibleItems[0].height;
  8079. util.forEach(visibleItems, function (item) {
  8080. min = Math.min(min, item.top);
  8081. max = Math.max(max, (item.top + item.height));
  8082. });
  8083. if (min > margin.axis) {
  8084. // there is an empty gap between the lowest item and the axis
  8085. var offset = min - margin.axis;
  8086. max -= offset;
  8087. util.forEach(visibleItems, function (item) {
  8088. item.top -= offset;
  8089. });
  8090. }
  8091. height = max + margin.item.vertical / 2;
  8092. }
  8093. else {
  8094. height = margin.axis + margin.item.vertical;
  8095. }
  8096. height = Math.max(height, this.props.label.height);
  8097. // calculate actual size and position
  8098. var foreground = this.dom.foreground;
  8099. this.top = foreground.offsetTop;
  8100. this.left = foreground.offsetLeft;
  8101. this.width = foreground.offsetWidth;
  8102. resized = util.updateProperty(this, 'height', height) || resized;
  8103. // recalculate size of label
  8104. resized = util.updateProperty(this.props.label, 'width', this.dom.inner.clientWidth) || resized;
  8105. resized = util.updateProperty(this.props.label, 'height', this.dom.inner.clientHeight) || resized;
  8106. // apply new height
  8107. this.dom.background.style.height = height + 'px';
  8108. this.dom.foreground.style.height = height + 'px';
  8109. this.dom.label.style.height = height + 'px';
  8110. // update vertical position of items after they are re-stacked and the height of the group is calculated
  8111. for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
  8112. var item = this.visibleItems[i];
  8113. item.repositionY();
  8114. }
  8115. return resized;
  8116. };
  8117. /**
  8118. * Show this group: attach to the DOM
  8119. */
  8120. Group.prototype.show = function() {
  8121. if (!this.dom.label.parentNode) {
  8122. this.itemSet.dom.labelSet.appendChild(this.dom.label);
  8123. }
  8124. if (!this.dom.foreground.parentNode) {
  8125. this.itemSet.dom.foreground.appendChild(this.dom.foreground);
  8126. }
  8127. if (!this.dom.background.parentNode) {
  8128. this.itemSet.dom.background.appendChild(this.dom.background);
  8129. }
  8130. if (!this.dom.axis.parentNode) {
  8131. this.itemSet.dom.axis.appendChild(this.dom.axis);
  8132. }
  8133. };
  8134. /**
  8135. * Hide this group: remove from the DOM
  8136. */
  8137. Group.prototype.hide = function() {
  8138. var label = this.dom.label;
  8139. if (label.parentNode) {
  8140. label.parentNode.removeChild(label);
  8141. }
  8142. var foreground = this.dom.foreground;
  8143. if (foreground.parentNode) {
  8144. foreground.parentNode.removeChild(foreground);
  8145. }
  8146. var background = this.dom.background;
  8147. if (background.parentNode) {
  8148. background.parentNode.removeChild(background);
  8149. }
  8150. var axis = this.dom.axis;
  8151. if (axis.parentNode) {
  8152. axis.parentNode.removeChild(axis);
  8153. }
  8154. };
  8155. /**
  8156. * Add an item to the group
  8157. * @param {Item} item
  8158. */
  8159. Group.prototype.add = function(item) {
  8160. this.items[item.id] = item;
  8161. item.setParent(this);
  8162. if (this.visibleItems.indexOf(item) == -1) {
  8163. var range = this.itemSet.body.range; // TODO: not nice accessing the range like this
  8164. this._checkIfVisible(item, this.visibleItems, range);
  8165. }
  8166. };
  8167. /**
  8168. * Remove an item from the group
  8169. * @param {Item} item
  8170. */
  8171. Group.prototype.remove = function(item) {
  8172. delete this.items[item.id];
  8173. item.setParent(this.itemSet);
  8174. // remove from visible items
  8175. var index = this.visibleItems.indexOf(item);
  8176. if (index != -1) this.visibleItems.splice(index, 1);
  8177. // TODO: also remove from ordered items?
  8178. };
  8179. /**
  8180. * Remove an item from the corresponding DataSet
  8181. * @param {Item} item
  8182. */
  8183. Group.prototype.removeFromDataSet = function(item) {
  8184. this.itemSet.removeItem(item.id);
  8185. };
  8186. /**
  8187. * Reorder the items
  8188. */
  8189. Group.prototype.order = function() {
  8190. var array = util.toArray(this.items);
  8191. this.orderedItems.byStart = array;
  8192. this.orderedItems.byEnd = this._constructByEndArray(array);
  8193. stack.orderByStart(this.orderedItems.byStart);
  8194. stack.orderByEnd(this.orderedItems.byEnd);
  8195. };
  8196. /**
  8197. * Create an array containing all items being a range (having an end date)
  8198. * @param {Item[]} array
  8199. * @returns {ItemRange[]}
  8200. * @private
  8201. */
  8202. Group.prototype._constructByEndArray = function(array) {
  8203. var endArray = [];
  8204. for (var i = 0; i < array.length; i++) {
  8205. if (array[i] instanceof ItemRange) {
  8206. endArray.push(array[i]);
  8207. }
  8208. }
  8209. return endArray;
  8210. };
  8211. /**
  8212. * Update the visible items
  8213. * @param {{byStart: Item[], byEnd: Item[]}} orderedItems All items ordered by start date and by end date
  8214. * @param {Item[]} visibleItems The previously visible items.
  8215. * @param {{start: number, end: number}} range Visible range
  8216. * @return {Item[]} visibleItems The new visible items.
  8217. * @private
  8218. */
  8219. Group.prototype._updateVisibleItems = function(orderedItems, visibleItems, range) {
  8220. var initialPosByStart,
  8221. newVisibleItems = [],
  8222. i;
  8223. // first check if the items that were in view previously are still in view.
  8224. // this handles the case for the ItemRange that is both before and after the current one.
  8225. if (visibleItems.length > 0) {
  8226. for (i = 0; i < visibleItems.length; i++) {
  8227. this._checkIfVisible(visibleItems[i], newVisibleItems, range);
  8228. }
  8229. }
  8230. // If there were no visible items previously, use binarySearch to find a visible ItemPoint or ItemRange (based on startTime)
  8231. if (newVisibleItems.length == 0) {
  8232. initialPosByStart = util.binarySearch(orderedItems.byStart, range, 'data','start');
  8233. }
  8234. else {
  8235. initialPosByStart = orderedItems.byStart.indexOf(newVisibleItems[0]);
  8236. }
  8237. // use visible search to find a visible ItemRange (only based on endTime)
  8238. var initialPosByEnd = util.binarySearch(orderedItems.byEnd, range, 'data','end');
  8239. // if we found a initial ID to use, trace it up and down until we meet an invisible item.
  8240. if (initialPosByStart != -1) {
  8241. for (i = initialPosByStart; i >= 0; i--) {
  8242. if (this._checkIfInvisible(orderedItems.byStart[i], newVisibleItems, range)) {break;}
  8243. }
  8244. for (i = initialPosByStart + 1; i < orderedItems.byStart.length; i++) {
  8245. if (this._checkIfInvisible(orderedItems.byStart[i], newVisibleItems, range)) {break;}
  8246. }
  8247. }
  8248. // if we found a initial ID to use, trace it up and down until we meet an invisible item.
  8249. if (initialPosByEnd != -1) {
  8250. for (i = initialPosByEnd; i >= 0; i--) {
  8251. if (this._checkIfInvisible(orderedItems.byEnd[i], newVisibleItems, range)) {break;}
  8252. }
  8253. for (i = initialPosByEnd + 1; i < orderedItems.byEnd.length; i++) {
  8254. if (this._checkIfInvisible(orderedItems.byEnd[i], newVisibleItems, range)) {break;}
  8255. }
  8256. }
  8257. return newVisibleItems;
  8258. };
  8259. /**
  8260. * this function checks if an item is invisible. If it is NOT we make it visible
  8261. * and add it to the global visible items. If it is, return true.
  8262. *
  8263. * @param {Item} item
  8264. * @param {Item[]} visibleItems
  8265. * @param {{start:number, end:number}} range
  8266. * @returns {boolean}
  8267. * @private
  8268. */
  8269. Group.prototype._checkIfInvisible = function(item, visibleItems, range) {
  8270. if (item.isVisible(range)) {
  8271. if (!item.displayed) item.show();
  8272. item.repositionX();
  8273. if (visibleItems.indexOf(item) == -1) {
  8274. visibleItems.push(item);
  8275. }
  8276. return false;
  8277. }
  8278. else {
  8279. if (item.displayed) item.hide();
  8280. return true;
  8281. }
  8282. };
  8283. /**
  8284. * this function is very similar to the _checkIfInvisible() but it does not
  8285. * return booleans, hides the item if it should not be seen and always adds to
  8286. * the visibleItems.
  8287. * this one is for brute forcing and hiding.
  8288. *
  8289. * @param {Item} item
  8290. * @param {Array} visibleItems
  8291. * @param {{start:number, end:number}} range
  8292. * @private
  8293. */
  8294. Group.prototype._checkIfVisible = function(item, visibleItems, range) {
  8295. if (item.isVisible(range)) {
  8296. if (!item.displayed) item.show();
  8297. // reposition item horizontally
  8298. item.repositionX();
  8299. visibleItems.push(item);
  8300. }
  8301. else {
  8302. if (item.displayed) item.hide();
  8303. }
  8304. };
  8305. module.exports = Group;
  8306. /***/ },
  8307. /* 24 */
  8308. /***/ function(module, exports, __webpack_require__) {
  8309. var Hammer = __webpack_require__(41);
  8310. var util = __webpack_require__(1);
  8311. var DataSet = __webpack_require__(3);
  8312. var DataView = __webpack_require__(4);
  8313. var Component = __webpack_require__(18);
  8314. var Group = __webpack_require__(23);
  8315. var ItemBox = __webpack_require__(29);
  8316. var ItemPoint = __webpack_require__(30);
  8317. var ItemRange = __webpack_require__(31);
  8318. var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
  8319. /**
  8320. * An ItemSet holds a set of items and ranges which can be displayed in a
  8321. * range. The width is determined by the parent of the ItemSet, and the height
  8322. * is determined by the size of the items.
  8323. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
  8324. * @param {Object} [options] See ItemSet.setOptions for the available options.
  8325. * @constructor ItemSet
  8326. * @extends Component
  8327. */
  8328. function ItemSet(body, options) {
  8329. this.body = body;
  8330. this.defaultOptions = {
  8331. type: null, // 'box', 'point', 'range'
  8332. orientation: 'bottom', // 'top' or 'bottom'
  8333. align: 'auto', // alignment of box items
  8334. stack: true,
  8335. groupOrder: null,
  8336. selectable: true,
  8337. editable: {
  8338. updateTime: false,
  8339. updateGroup: false,
  8340. add: false,
  8341. remove: false
  8342. },
  8343. onAdd: function (item, callback) {
  8344. callback(item);
  8345. },
  8346. onUpdate: function (item, callback) {
  8347. callback(item);
  8348. },
  8349. onMove: function (item, callback) {
  8350. callback(item);
  8351. },
  8352. onMoving: null,
  8353. onRemove: function (item, callback) {
  8354. callback(item);
  8355. },
  8356. margin: {
  8357. item: {
  8358. horizontal: 10,
  8359. vertical: 10
  8360. },
  8361. axis: 20
  8362. },
  8363. padding: 5
  8364. };
  8365. // options is shared by this ItemSet and all its items
  8366. this.options = util.extend({}, this.defaultOptions);
  8367. // options for getting items from the DataSet with the correct type
  8368. this.itemOptions = {
  8369. type: {start: 'Date', end: 'Date'}
  8370. };
  8371. this.conversion = {
  8372. toScreen: body.util.toScreen,
  8373. toTime: body.util.toTime
  8374. };
  8375. this.dom = {};
  8376. this.props = {};
  8377. this.hammer = null;
  8378. var me = this;
  8379. this.itemsData = null; // DataSet
  8380. this.groupsData = null; // DataSet
  8381. // listeners for the DataSet of the items
  8382. this.itemListeners = {
  8383. 'add': function (event, params, senderId) {
  8384. me._onAdd(params.items);
  8385. },
  8386. 'update': function (event, params, senderId) {
  8387. me._onUpdate(params.items);
  8388. },
  8389. 'remove': function (event, params, senderId) {
  8390. me._onRemove(params.items);
  8391. }
  8392. };
  8393. // listeners for the DataSet of the groups
  8394. this.groupListeners = {
  8395. 'add': function (event, params, senderId) {
  8396. me._onAddGroups(params.items);
  8397. },
  8398. 'update': function (event, params, senderId) {
  8399. me._onUpdateGroups(params.items);
  8400. },
  8401. 'remove': function (event, params, senderId) {
  8402. me._onRemoveGroups(params.items);
  8403. }
  8404. };
  8405. this.items = {}; // object with an Item for every data item
  8406. this.groups = {}; // Group object for every group
  8407. this.groupIds = [];
  8408. this.selection = []; // list with the ids of all selected nodes
  8409. this.stackDirty = true; // if true, all items will be restacked on next redraw
  8410. this.touchParams = {}; // stores properties while dragging
  8411. // create the HTML DOM
  8412. this._create();
  8413. this.setOptions(options);
  8414. }
  8415. ItemSet.prototype = new Component();
  8416. // available item types will be registered here
  8417. ItemSet.types = {
  8418. box: ItemBox,
  8419. range: ItemRange,
  8420. point: ItemPoint
  8421. };
  8422. /**
  8423. * Create the HTML DOM for the ItemSet
  8424. */
  8425. ItemSet.prototype._create = function(){
  8426. var frame = document.createElement('div');
  8427. frame.className = 'itemset';
  8428. frame['timeline-itemset'] = this;
  8429. this.dom.frame = frame;
  8430. // create background panel
  8431. var background = document.createElement('div');
  8432. background.className = 'background';
  8433. frame.appendChild(background);
  8434. this.dom.background = background;
  8435. // create foreground panel
  8436. var foreground = document.createElement('div');
  8437. foreground.className = 'foreground';
  8438. frame.appendChild(foreground);
  8439. this.dom.foreground = foreground;
  8440. // create axis panel
  8441. var axis = document.createElement('div');
  8442. axis.className = 'axis';
  8443. this.dom.axis = axis;
  8444. // create labelset
  8445. var labelSet = document.createElement('div');
  8446. labelSet.className = 'labelset';
  8447. this.dom.labelSet = labelSet;
  8448. // create ungrouped Group
  8449. this._updateUngrouped();
  8450. // attach event listeners
  8451. // Note: we bind to the centerContainer for the case where the height
  8452. // of the center container is larger than of the ItemSet, so we
  8453. // can click in the empty area to create a new item or deselect an item.
  8454. this.hammer = Hammer(this.body.dom.centerContainer, {
  8455. prevent_default: true
  8456. });
  8457. // drag items when selected
  8458. this.hammer.on('touch', this._onTouch.bind(this));
  8459. this.hammer.on('dragstart', this._onDragStart.bind(this));
  8460. this.hammer.on('drag', this._onDrag.bind(this));
  8461. this.hammer.on('dragend', this._onDragEnd.bind(this));
  8462. // single select (or unselect) when tapping an item
  8463. this.hammer.on('tap', this._onSelectItem.bind(this));
  8464. // multi select when holding mouse/touch, or on ctrl+click
  8465. this.hammer.on('hold', this._onMultiSelectItem.bind(this));
  8466. // add item on doubletap
  8467. this.hammer.on('doubletap', this._onAddItem.bind(this));
  8468. // attach to the DOM
  8469. this.show();
  8470. };
  8471. /**
  8472. * Set options for the ItemSet. Existing options will be extended/overwritten.
  8473. * @param {Object} [options] The following options are available:
  8474. * {String} type
  8475. * Default type for the items. Choose from 'box'
  8476. * (default), 'point', or 'range'. The default
  8477. * Style can be overwritten by individual items.
  8478. * {String} align
  8479. * Alignment for the items, only applicable for
  8480. * ItemBox. Choose 'center' (default), 'left', or
  8481. * 'right'.
  8482. * {String} orientation
  8483. * Orientation of the item set. Choose 'top' or
  8484. * 'bottom' (default).
  8485. * {Function} groupOrder
  8486. * A sorting function for ordering groups
  8487. * {Boolean} stack
  8488. * If true (deafult), items will be stacked on
  8489. * top of each other.
  8490. * {Number} margin.axis
  8491. * Margin between the axis and the items in pixels.
  8492. * Default is 20.
  8493. * {Number} margin.item.horizontal
  8494. * Horizontal margin between items in pixels.
  8495. * Default is 10.
  8496. * {Number} margin.item.vertical
  8497. * Vertical Margin between items in pixels.
  8498. * Default is 10.
  8499. * {Number} margin.item
  8500. * Margin between items in pixels in both horizontal
  8501. * and vertical direction. Default is 10.
  8502. * {Number} margin
  8503. * Set margin for both axis and items in pixels.
  8504. * {Number} padding
  8505. * Padding of the contents of an item in pixels.
  8506. * Must correspond with the items css. Default is 5.
  8507. * {Boolean} selectable
  8508. * If true (default), items can be selected.
  8509. * {Boolean} editable
  8510. * Set all editable options to true or false
  8511. * {Boolean} editable.updateTime
  8512. * Allow dragging an item to an other moment in time
  8513. * {Boolean} editable.updateGroup
  8514. * Allow dragging an item to an other group
  8515. * {Boolean} editable.add
  8516. * Allow creating new items on double tap
  8517. * {Boolean} editable.remove
  8518. * Allow removing items by clicking the delete button
  8519. * top right of a selected item.
  8520. * {Function(item: Item, callback: Function)} onAdd
  8521. * Callback function triggered when an item is about to be added:
  8522. * when the user double taps an empty space in the Timeline.
  8523. * {Function(item: Item, callback: Function)} onUpdate
  8524. * Callback function fired when an item is about to be updated.
  8525. * This function typically has to show a dialog where the user
  8526. * change the item. If not implemented, nothing happens.
  8527. * {Function(item: Item, callback: Function)} onMove
  8528. * Fired when an item has been moved. If not implemented,
  8529. * the move action will be accepted.
  8530. * {Function(item: Item, callback: Function)} onRemove
  8531. * Fired when an item is about to be deleted.
  8532. * If not implemented, the item will be always removed.
  8533. */
  8534. ItemSet.prototype.setOptions = function(options) {
  8535. if (options) {
  8536. // copy all options that we know
  8537. var fields = ['type', 'align', 'orientation', 'padding', 'stack', 'selectable', 'groupOrder'];
  8538. util.selectiveExtend(fields, this.options, options);
  8539. if ('margin' in options) {
  8540. if (typeof options.margin === 'number') {
  8541. this.options.margin.axis = options.margin;
  8542. this.options.margin.item.horizontal = options.margin;
  8543. this.options.margin.item.vertical = options.margin;
  8544. }
  8545. else if (typeof options.margin === 'object') {
  8546. util.selectiveExtend(['axis'], this.options.margin, options.margin);
  8547. if ('item' in options.margin) {
  8548. if (typeof options.margin.item === 'number') {
  8549. this.options.margin.item.horizontal = options.margin.item;
  8550. this.options.margin.item.vertical = options.margin.item;
  8551. }
  8552. else if (typeof options.margin.item === 'object') {
  8553. util.selectiveExtend(['horizontal', 'vertical'], this.options.margin.item, options.margin.item);
  8554. }
  8555. }
  8556. }
  8557. }
  8558. if ('editable' in options) {
  8559. if (typeof options.editable === 'boolean') {
  8560. this.options.editable.updateTime = options.editable;
  8561. this.options.editable.updateGroup = options.editable;
  8562. this.options.editable.add = options.editable;
  8563. this.options.editable.remove = options.editable;
  8564. }
  8565. else if (typeof options.editable === 'object') {
  8566. util.selectiveExtend(['updateTime', 'updateGroup', 'add', 'remove'], this.options.editable, options.editable);
  8567. }
  8568. }
  8569. // callback functions
  8570. var addCallback = (function (name) {
  8571. if (name in options) {
  8572. var fn = options[name];
  8573. if (!(fn instanceof Function)) {
  8574. throw new Error('option ' + name + ' must be a function ' + name + '(item, callback)');
  8575. }
  8576. this.options[name] = fn;
  8577. }
  8578. }).bind(this);
  8579. ['onAdd', 'onUpdate', 'onRemove', 'onMove', 'onMoving'].forEach(addCallback);
  8580. // force the itemSet to refresh: options like orientation and margins may be changed
  8581. this.markDirty();
  8582. }
  8583. };
  8584. /**
  8585. * Mark the ItemSet dirty so it will refresh everything with next redraw
  8586. */
  8587. ItemSet.prototype.markDirty = function() {
  8588. this.groupIds = [];
  8589. this.stackDirty = true;
  8590. };
  8591. /**
  8592. * Destroy the ItemSet
  8593. */
  8594. ItemSet.prototype.destroy = function() {
  8595. this.hide();
  8596. this.setItems(null);
  8597. this.setGroups(null);
  8598. this.hammer = null;
  8599. this.body = null;
  8600. this.conversion = null;
  8601. };
  8602. /**
  8603. * Hide the component from the DOM
  8604. */
  8605. ItemSet.prototype.hide = function() {
  8606. // remove the frame containing the items
  8607. if (this.dom.frame.parentNode) {
  8608. this.dom.frame.parentNode.removeChild(this.dom.frame);
  8609. }
  8610. // remove the axis with dots
  8611. if (this.dom.axis.parentNode) {
  8612. this.dom.axis.parentNode.removeChild(this.dom.axis);
  8613. }
  8614. // remove the labelset containing all group labels
  8615. if (this.dom.labelSet.parentNode) {
  8616. this.dom.labelSet.parentNode.removeChild(this.dom.labelSet);
  8617. }
  8618. };
  8619. /**
  8620. * Show the component in the DOM (when not already visible).
  8621. * @return {Boolean} changed
  8622. */
  8623. ItemSet.prototype.show = function() {
  8624. // show frame containing the items
  8625. if (!this.dom.frame.parentNode) {
  8626. this.body.dom.center.appendChild(this.dom.frame);
  8627. }
  8628. // show axis with dots
  8629. if (!this.dom.axis.parentNode) {
  8630. this.body.dom.top.appendChild(this.dom.axis);
  8631. }
  8632. // show labelset containing labels
  8633. if (!this.dom.labelSet.parentNode) {
  8634. this.body.dom.left.appendChild(this.dom.labelSet);
  8635. }
  8636. };
  8637. /**
  8638. * Set selected items by their id. Replaces the current selection
  8639. * Unknown id's are silently ignored.
  8640. * @param {string[] | string} [ids] An array with zero or more id's of the items to be
  8641. * selected, or a single item id. If ids is undefined
  8642. * or an empty array, all items will be unselected.
  8643. */
  8644. ItemSet.prototype.setSelection = function(ids) {
  8645. var i, ii, id, item;
  8646. if (ids == undefined) ids = [];
  8647. if (!Array.isArray(ids)) ids = [ids];
  8648. // unselect currently selected items
  8649. for (i = 0, ii = this.selection.length; i < ii; i++) {
  8650. id = this.selection[i];
  8651. item = this.items[id];
  8652. if (item) item.unselect();
  8653. }
  8654. // select items
  8655. this.selection = [];
  8656. for (i = 0, ii = ids.length; i < ii; i++) {
  8657. id = ids[i];
  8658. item = this.items[id];
  8659. if (item) {
  8660. this.selection.push(id);
  8661. item.select();
  8662. }
  8663. }
  8664. };
  8665. /**
  8666. * Get the selected items by their id
  8667. * @return {Array} ids The ids of the selected items
  8668. */
  8669. ItemSet.prototype.getSelection = function() {
  8670. return this.selection.concat([]);
  8671. };
  8672. /**
  8673. * Get the id's of the currently visible items.
  8674. * @returns {Array} The ids of the visible items
  8675. */
  8676. ItemSet.prototype.getVisibleItems = function() {
  8677. var range = this.body.range.getRange();
  8678. var left = this.body.util.toScreen(range.start);
  8679. var right = this.body.util.toScreen(range.end);
  8680. var ids = [];
  8681. for (var groupId in this.groups) {
  8682. if (this.groups.hasOwnProperty(groupId)) {
  8683. var group = this.groups[groupId];
  8684. var rawVisibleItems = group.visibleItems;
  8685. // filter the "raw" set with visibleItems into a set which is really
  8686. // visible by pixels
  8687. for (var i = 0; i < rawVisibleItems.length; i++) {
  8688. var item = rawVisibleItems[i];
  8689. // TODO: also check whether visible vertically
  8690. if ((item.left < right) && (item.left + item.width > left)) {
  8691. ids.push(item.id);
  8692. }
  8693. }
  8694. }
  8695. }
  8696. return ids;
  8697. };
  8698. /**
  8699. * Deselect a selected item
  8700. * @param {String | Number} id
  8701. * @private
  8702. */
  8703. ItemSet.prototype._deselect = function(id) {
  8704. var selection = this.selection;
  8705. for (var i = 0, ii = selection.length; i < ii; i++) {
  8706. if (selection[i] == id) { // non-strict comparison!
  8707. selection.splice(i, 1);
  8708. break;
  8709. }
  8710. }
  8711. };
  8712. /**
  8713. * Repaint the component
  8714. * @return {boolean} Returns true if the component is resized
  8715. */
  8716. ItemSet.prototype.redraw = function() {
  8717. var margin = this.options.margin,
  8718. range = this.body.range,
  8719. asSize = util.option.asSize,
  8720. options = this.options,
  8721. orientation = options.orientation,
  8722. resized = false,
  8723. frame = this.dom.frame,
  8724. editable = options.editable.updateTime || options.editable.updateGroup;
  8725. // update class name
  8726. frame.className = 'itemset' + (editable ? ' editable' : '');
  8727. // reorder the groups (if needed)
  8728. resized = this._orderGroups() || resized;
  8729. // check whether zoomed (in that case we need to re-stack everything)
  8730. // TODO: would be nicer to get this as a trigger from Range
  8731. var visibleInterval = range.end - range.start;
  8732. var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.props.width != this.props.lastWidth);
  8733. if (zoomed) this.stackDirty = true;
  8734. this.lastVisibleInterval = visibleInterval;
  8735. this.props.lastWidth = this.props.width;
  8736. // redraw all groups
  8737. var restack = this.stackDirty,
  8738. firstGroup = this._firstGroup(),
  8739. firstMargin = {
  8740. item: margin.item,
  8741. axis: margin.axis
  8742. },
  8743. nonFirstMargin = {
  8744. item: margin.item,
  8745. axis: margin.item.vertical / 2
  8746. },
  8747. height = 0,
  8748. minHeight = margin.axis + margin.item.vertical;
  8749. util.forEach(this.groups, function (group) {
  8750. var groupMargin = (group == firstGroup) ? firstMargin : nonFirstMargin;
  8751. var groupResized = group.redraw(range, groupMargin, restack);
  8752. resized = groupResized || resized;
  8753. height += group.height;
  8754. });
  8755. height = Math.max(height, minHeight);
  8756. this.stackDirty = false;
  8757. // update frame height
  8758. frame.style.height = asSize(height);
  8759. // calculate actual size and position
  8760. this.props.top = frame.offsetTop;
  8761. this.props.left = frame.offsetLeft;
  8762. this.props.width = frame.offsetWidth;
  8763. this.props.height = height;
  8764. // reposition axis
  8765. this.dom.axis.style.top = asSize((orientation == 'top') ?
  8766. (this.body.domProps.top.height + this.body.domProps.border.top) :
  8767. (this.body.domProps.top.height + this.body.domProps.centerContainer.height));
  8768. this.dom.axis.style.left = '0';
  8769. // check if this component is resized
  8770. resized = this._isResized() || resized;
  8771. return resized;
  8772. };
  8773. /**
  8774. * Get the first group, aligned with the axis
  8775. * @return {Group | null} firstGroup
  8776. * @private
  8777. */
  8778. ItemSet.prototype._firstGroup = function() {
  8779. var firstGroupIndex = (this.options.orientation == 'top') ? 0 : (this.groupIds.length - 1);
  8780. var firstGroupId = this.groupIds[firstGroupIndex];
  8781. var firstGroup = this.groups[firstGroupId] || this.groups[UNGROUPED];
  8782. return firstGroup || null;
  8783. };
  8784. /**
  8785. * Create or delete the group holding all ungrouped items. This group is used when
  8786. * there are no groups specified.
  8787. * @protected
  8788. */
  8789. ItemSet.prototype._updateUngrouped = function() {
  8790. var ungrouped = this.groups[UNGROUPED];
  8791. if (this.groupsData) {
  8792. // remove the group holding all ungrouped items
  8793. if (ungrouped) {
  8794. ungrouped.hide();
  8795. delete this.groups[UNGROUPED];
  8796. }
  8797. }
  8798. else {
  8799. // create a group holding all (unfiltered) items
  8800. if (!ungrouped) {
  8801. var id = null;
  8802. var data = null;
  8803. ungrouped = new Group(id, data, this);
  8804. this.groups[UNGROUPED] = ungrouped;
  8805. for (var itemId in this.items) {
  8806. if (this.items.hasOwnProperty(itemId)) {
  8807. ungrouped.add(this.items[itemId]);
  8808. }
  8809. }
  8810. ungrouped.show();
  8811. }
  8812. }
  8813. };
  8814. /**
  8815. * Get the element for the labelset
  8816. * @return {HTMLElement} labelSet
  8817. */
  8818. ItemSet.prototype.getLabelSet = function() {
  8819. return this.dom.labelSet;
  8820. };
  8821. /**
  8822. * Set items
  8823. * @param {vis.DataSet | null} items
  8824. */
  8825. ItemSet.prototype.setItems = function(items) {
  8826. var me = this,
  8827. ids,
  8828. oldItemsData = this.itemsData;
  8829. // replace the dataset
  8830. if (!items) {
  8831. this.itemsData = null;
  8832. }
  8833. else if (items instanceof DataSet || items instanceof DataView) {
  8834. this.itemsData = items;
  8835. }
  8836. else {
  8837. throw new TypeError('Data must be an instance of DataSet or DataView');
  8838. }
  8839. if (oldItemsData) {
  8840. // unsubscribe from old dataset
  8841. util.forEach(this.itemListeners, function (callback, event) {
  8842. oldItemsData.off(event, callback);
  8843. });
  8844. // remove all drawn items
  8845. ids = oldItemsData.getIds();
  8846. this._onRemove(ids);
  8847. }
  8848. if (this.itemsData) {
  8849. // subscribe to new dataset
  8850. var id = this.id;
  8851. util.forEach(this.itemListeners, function (callback, event) {
  8852. me.itemsData.on(event, callback, id);
  8853. });
  8854. // add all new items
  8855. ids = this.itemsData.getIds();
  8856. this._onAdd(ids);
  8857. // update the group holding all ungrouped items
  8858. this._updateUngrouped();
  8859. }
  8860. };
  8861. /**
  8862. * Get the current items
  8863. * @returns {vis.DataSet | null}
  8864. */
  8865. ItemSet.prototype.getItems = function() {
  8866. return this.itemsData;
  8867. };
  8868. /**
  8869. * Set groups
  8870. * @param {vis.DataSet} groups
  8871. */
  8872. ItemSet.prototype.setGroups = function(groups) {
  8873. var me = this,
  8874. ids;
  8875. // unsubscribe from current dataset
  8876. if (this.groupsData) {
  8877. util.forEach(this.groupListeners, function (callback, event) {
  8878. me.groupsData.unsubscribe(event, callback);
  8879. });
  8880. // remove all drawn groups
  8881. ids = this.groupsData.getIds();
  8882. this.groupsData = null;
  8883. this._onRemoveGroups(ids); // note: this will cause a redraw
  8884. }
  8885. // replace the dataset
  8886. if (!groups) {
  8887. this.groupsData = null;
  8888. }
  8889. else if (groups instanceof DataSet || groups instanceof DataView) {
  8890. this.groupsData = groups;
  8891. }
  8892. else {
  8893. throw new TypeError('Data must be an instance of DataSet or DataView');
  8894. }
  8895. if (this.groupsData) {
  8896. // subscribe to new dataset
  8897. var id = this.id;
  8898. util.forEach(this.groupListeners, function (callback, event) {
  8899. me.groupsData.on(event, callback, id);
  8900. });
  8901. // draw all ms
  8902. ids = this.groupsData.getIds();
  8903. this._onAddGroups(ids);
  8904. }
  8905. // update the group holding all ungrouped items
  8906. this._updateUngrouped();
  8907. // update the order of all items in each group
  8908. this._order();
  8909. this.body.emitter.emit('change');
  8910. };
  8911. /**
  8912. * Get the current groups
  8913. * @returns {vis.DataSet | null} groups
  8914. */
  8915. ItemSet.prototype.getGroups = function() {
  8916. return this.groupsData;
  8917. };
  8918. /**
  8919. * Remove an item by its id
  8920. * @param {String | Number} id
  8921. */
  8922. ItemSet.prototype.removeItem = function(id) {
  8923. var item = this.itemsData.get(id),
  8924. dataset = this.itemsData.getDataSet();
  8925. if (item) {
  8926. // confirm deletion
  8927. this.options.onRemove(item, function (item) {
  8928. if (item) {
  8929. // remove by id here, it is possible that an item has no id defined
  8930. // itself, so better not delete by the item itself
  8931. dataset.remove(id);
  8932. }
  8933. });
  8934. }
  8935. };
  8936. /**
  8937. * Handle updated items
  8938. * @param {Number[]} ids
  8939. * @protected
  8940. */
  8941. ItemSet.prototype._onUpdate = function(ids) {
  8942. var me = this;
  8943. ids.forEach(function (id) {
  8944. var itemData = me.itemsData.get(id, me.itemOptions),
  8945. item = me.items[id],
  8946. type = itemData.type || me.options.type || (itemData.end ? 'range' : 'box');
  8947. var constructor = ItemSet.types[type];
  8948. if (item) {
  8949. // update item
  8950. if (!constructor || !(item instanceof constructor)) {
  8951. // item type has changed, delete the item and recreate it
  8952. me._removeItem(item);
  8953. item = null;
  8954. }
  8955. else {
  8956. me._updateItem(item, itemData);
  8957. }
  8958. }
  8959. if (!item) {
  8960. // create item
  8961. if (constructor) {
  8962. item = new constructor(itemData, me.conversion, me.options);
  8963. item.id = id; // TODO: not so nice setting id afterwards
  8964. me._addItem(item);
  8965. }
  8966. else if (type == 'rangeoverflow') {
  8967. // TODO: deprecated since version 2.1.0 (or 3.0.0?). cleanup some day
  8968. throw new TypeError('Item type "rangeoverflow" is deprecated. Use css styling instead: ' +
  8969. '.vis.timeline .item.range .content {overflow: visible;}');
  8970. }
  8971. else {
  8972. throw new TypeError('Unknown item type "' + type + '"');
  8973. }
  8974. }
  8975. });
  8976. this._order();
  8977. this.stackDirty = true; // force re-stacking of all items next redraw
  8978. this.body.emitter.emit('change');
  8979. };
  8980. /**
  8981. * Handle added items
  8982. * @param {Number[]} ids
  8983. * @protected
  8984. */
  8985. ItemSet.prototype._onAdd = ItemSet.prototype._onUpdate;
  8986. /**
  8987. * Handle removed items
  8988. * @param {Number[]} ids
  8989. * @protected
  8990. */
  8991. ItemSet.prototype._onRemove = function(ids) {
  8992. var count = 0;
  8993. var me = this;
  8994. ids.forEach(function (id) {
  8995. var item = me.items[id];
  8996. if (item) {
  8997. count++;
  8998. me._removeItem(item);
  8999. }
  9000. });
  9001. if (count) {
  9002. // update order
  9003. this._order();
  9004. this.stackDirty = true; // force re-stacking of all items next redraw
  9005. this.body.emitter.emit('change');
  9006. }
  9007. };
  9008. /**
  9009. * Update the order of item in all groups
  9010. * @private
  9011. */
  9012. ItemSet.prototype._order = function() {
  9013. // reorder the items in all groups
  9014. // TODO: optimization: only reorder groups affected by the changed items
  9015. util.forEach(this.groups, function (group) {
  9016. group.order();
  9017. });
  9018. };
  9019. /**
  9020. * Handle updated groups
  9021. * @param {Number[]} ids
  9022. * @private
  9023. */
  9024. ItemSet.prototype._onUpdateGroups = function(ids) {
  9025. this._onAddGroups(ids);
  9026. };
  9027. /**
  9028. * Handle changed groups
  9029. * @param {Number[]} ids
  9030. * @private
  9031. */
  9032. ItemSet.prototype._onAddGroups = function(ids) {
  9033. var me = this;
  9034. ids.forEach(function (id) {
  9035. var groupData = me.groupsData.get(id);
  9036. var group = me.groups[id];
  9037. if (!group) {
  9038. // check for reserved ids
  9039. if (id == UNGROUPED) {
  9040. throw new Error('Illegal group id. ' + id + ' is a reserved id.');
  9041. }
  9042. var groupOptions = Object.create(me.options);
  9043. util.extend(groupOptions, {
  9044. height: null
  9045. });
  9046. group = new Group(id, groupData, me);
  9047. me.groups[id] = group;
  9048. // add items with this groupId to the new group
  9049. for (var itemId in me.items) {
  9050. if (me.items.hasOwnProperty(itemId)) {
  9051. var item = me.items[itemId];
  9052. if (item.data.group == id) {
  9053. group.add(item);
  9054. }
  9055. }
  9056. }
  9057. group.order();
  9058. group.show();
  9059. }
  9060. else {
  9061. // update group
  9062. group.setData(groupData);
  9063. }
  9064. });
  9065. this.body.emitter.emit('change');
  9066. };
  9067. /**
  9068. * Handle removed groups
  9069. * @param {Number[]} ids
  9070. * @private
  9071. */
  9072. ItemSet.prototype._onRemoveGroups = function(ids) {
  9073. var groups = this.groups;
  9074. ids.forEach(function (id) {
  9075. var group = groups[id];
  9076. if (group) {
  9077. group.hide();
  9078. delete groups[id];
  9079. }
  9080. });
  9081. this.markDirty();
  9082. this.body.emitter.emit('change');
  9083. };
  9084. /**
  9085. * Reorder the groups if needed
  9086. * @return {boolean} changed
  9087. * @private
  9088. */
  9089. ItemSet.prototype._orderGroups = function () {
  9090. if (this.groupsData) {
  9091. // reorder the groups
  9092. var groupIds = this.groupsData.getIds({
  9093. order: this.options.groupOrder
  9094. });
  9095. var changed = !util.equalArray(groupIds, this.groupIds);
  9096. if (changed) {
  9097. // hide all groups, removes them from the DOM
  9098. var groups = this.groups;
  9099. groupIds.forEach(function (groupId) {
  9100. groups[groupId].hide();
  9101. });
  9102. // show the groups again, attach them to the DOM in correct order
  9103. groupIds.forEach(function (groupId) {
  9104. groups[groupId].show();
  9105. });
  9106. this.groupIds = groupIds;
  9107. }
  9108. return changed;
  9109. }
  9110. else {
  9111. return false;
  9112. }
  9113. };
  9114. /**
  9115. * Add a new item
  9116. * @param {Item} item
  9117. * @private
  9118. */
  9119. ItemSet.prototype._addItem = function(item) {
  9120. this.items[item.id] = item;
  9121. // add to group
  9122. var groupId = this.groupsData ? item.data.group : UNGROUPED;
  9123. var group = this.groups[groupId];
  9124. if (group) group.add(item);
  9125. };
  9126. /**
  9127. * Update an existing item
  9128. * @param {Item} item
  9129. * @param {Object} itemData
  9130. * @private
  9131. */
  9132. ItemSet.prototype._updateItem = function(item, itemData) {
  9133. var oldGroupId = item.data.group;
  9134. item.data = itemData;
  9135. if (item.displayed) {
  9136. item.redraw();
  9137. }
  9138. // update group
  9139. if (oldGroupId != item.data.group) {
  9140. var oldGroup = this.groups[oldGroupId];
  9141. if (oldGroup) oldGroup.remove(item);
  9142. var groupId = this.groupsData ? item.data.group : UNGROUPED;
  9143. var group = this.groups[groupId];
  9144. if (group) group.add(item);
  9145. }
  9146. };
  9147. /**
  9148. * Delete an item from the ItemSet: remove it from the DOM, from the map
  9149. * with items, and from the map with visible items, and from the selection
  9150. * @param {Item} item
  9151. * @private
  9152. */
  9153. ItemSet.prototype._removeItem = function(item) {
  9154. // remove from DOM
  9155. item.hide();
  9156. // remove from items
  9157. delete this.items[item.id];
  9158. // remove from selection
  9159. var index = this.selection.indexOf(item.id);
  9160. if (index != -1) this.selection.splice(index, 1);
  9161. // remove from group
  9162. var groupId = this.groupsData ? item.data.group : UNGROUPED;
  9163. var group = this.groups[groupId];
  9164. if (group) group.remove(item);
  9165. };
  9166. /**
  9167. * Create an array containing all items being a range (having an end date)
  9168. * @param array
  9169. * @returns {Array}
  9170. * @private
  9171. */
  9172. ItemSet.prototype._constructByEndArray = function(array) {
  9173. var endArray = [];
  9174. for (var i = 0; i < array.length; i++) {
  9175. if (array[i] instanceof ItemRange) {
  9176. endArray.push(array[i]);
  9177. }
  9178. }
  9179. return endArray;
  9180. };
  9181. /**
  9182. * Register the clicked item on touch, before dragStart is initiated.
  9183. *
  9184. * dragStart is initiated from a mousemove event, which can have left the item
  9185. * already resulting in an item == null
  9186. *
  9187. * @param {Event} event
  9188. * @private
  9189. */
  9190. ItemSet.prototype._onTouch = function (event) {
  9191. // store the touched item, used in _onDragStart
  9192. this.touchParams.item = ItemSet.itemFromTarget(event);
  9193. };
  9194. /**
  9195. * Start dragging the selected events
  9196. * @param {Event} event
  9197. * @private
  9198. */
  9199. ItemSet.prototype._onDragStart = function (event) {
  9200. if (!this.options.editable.updateTime && !this.options.editable.updateGroup) {
  9201. return;
  9202. }
  9203. var item = this.touchParams.item || null,
  9204. me = this,
  9205. props;
  9206. if (item && item.selected) {
  9207. var dragLeftItem = event.target.dragLeftItem;
  9208. var dragRightItem = event.target.dragRightItem;
  9209. if (dragLeftItem) {
  9210. props = {
  9211. item: dragLeftItem
  9212. };
  9213. if (me.options.editable.updateTime) {
  9214. props.start = item.data.start.valueOf();
  9215. }
  9216. if (me.options.editable.updateGroup) {
  9217. if ('group' in item.data) props.group = item.data.group;
  9218. }
  9219. this.touchParams.itemProps = [props];
  9220. }
  9221. else if (dragRightItem) {
  9222. props = {
  9223. item: dragRightItem
  9224. };
  9225. if (me.options.editable.updateTime) {
  9226. props.end = item.data.end.valueOf();
  9227. }
  9228. if (me.options.editable.updateGroup) {
  9229. if ('group' in item.data) props.group = item.data.group;
  9230. }
  9231. this.touchParams.itemProps = [props];
  9232. }
  9233. else {
  9234. this.touchParams.itemProps = this.getSelection().map(function (id) {
  9235. var item = me.items[id];
  9236. var props = {
  9237. item: item
  9238. };
  9239. if (me.options.editable.updateTime) {
  9240. if ('start' in item.data) props.start = item.data.start.valueOf();
  9241. if ('end' in item.data) props.end = item.data.end.valueOf();
  9242. }
  9243. if (me.options.editable.updateGroup) {
  9244. if ('group' in item.data) props.group = item.data.group;
  9245. }
  9246. return props;
  9247. });
  9248. }
  9249. event.stopPropagation();
  9250. }
  9251. };
  9252. /**
  9253. * Drag selected items
  9254. * @param {Event} event
  9255. * @private
  9256. */
  9257. ItemSet.prototype._onDrag = function (event) {
  9258. if (this.touchParams.itemProps) {
  9259. var me = this;
  9260. var range = this.body.range;
  9261. var snap = this.body.util.snap || null;
  9262. var deltaX = event.gesture.deltaX;
  9263. var scale = (this.props.width / (range.end - range.start));
  9264. var offset = deltaX / scale;
  9265. // move
  9266. this.touchParams.itemProps.forEach(function (props) {
  9267. var newProps = {};
  9268. if ('start' in props) {
  9269. var start = new Date(props.start + offset);
  9270. newProps.start = snap ? snap(start) : start;
  9271. }
  9272. if ('end' in props) {
  9273. var end = new Date(props.end + offset);
  9274. newProps.end = snap ? snap(end) : end;
  9275. }
  9276. if ('group' in props) {
  9277. // drag from one group to another
  9278. var group = ItemSet.groupFromTarget(event);
  9279. newProps.group = group && group.groupId;
  9280. }
  9281. if (me.options.onMoving) {
  9282. var itemData = util.extend({}, props.item.data, newProps);
  9283. me.options.onMoving(itemData, function (itemData) {
  9284. if (itemData) {
  9285. me._updateItemProps(props.item, itemData);
  9286. }
  9287. });
  9288. }
  9289. else {
  9290. me._updateItemProps(props.item, newProps);
  9291. }
  9292. });
  9293. // TODO: implement onMoving handler
  9294. this.stackDirty = true; // force re-stacking of all items next redraw
  9295. this.body.emitter.emit('change');
  9296. event.stopPropagation();
  9297. }
  9298. };
  9299. /**
  9300. * Update an items properties
  9301. * @param {Item} item
  9302. * @param {Object} props Can contain properties start, end, and group.
  9303. * @private
  9304. */
  9305. ItemSet.prototype._updateItemProps = function(item, props) {
  9306. if ('start' in props) item.data.start = props.start;
  9307. if ('end' in props) item.data.end = props.end;
  9308. if ('group' in props && item.data.group != props.group) {
  9309. this._moveToGroup(item, props.group)
  9310. }
  9311. };
  9312. /**
  9313. * Move an item to another group
  9314. * @param {Item} item
  9315. * @param {String | Number} groupId
  9316. * @private
  9317. */
  9318. ItemSet.prototype._moveToGroup = function(item, groupId) {
  9319. var group = this.groups[groupId];
  9320. if (group && group.groupId != item.data.group) {
  9321. var oldGroup = item.parent;
  9322. oldGroup.remove(item);
  9323. oldGroup.order();
  9324. group.add(item);
  9325. group.order();
  9326. item.data.group = group.groupId;
  9327. }
  9328. };
  9329. /**
  9330. * End of dragging selected items
  9331. * @param {Event} event
  9332. * @private
  9333. */
  9334. ItemSet.prototype._onDragEnd = function (event) {
  9335. if (this.touchParams.itemProps) {
  9336. // prepare a change set for the changed items
  9337. var changes = [],
  9338. me = this,
  9339. dataset = this.itemsData.getDataSet();
  9340. var itemProps = this.touchParams.itemProps ;
  9341. this.touchParams.itemProps = null;
  9342. itemProps.forEach(function (props) {
  9343. var id = props.item.id,
  9344. itemData = me.itemsData.get(id, me.itemOptions);
  9345. var changed = false;
  9346. if ('start' in props.item.data) {
  9347. changed = (props.start != props.item.data.start.valueOf());
  9348. itemData.start = util.convert(props.item.data.start,
  9349. dataset._options.type && dataset._options.type.start || 'Date');
  9350. }
  9351. if ('end' in props.item.data) {
  9352. changed = changed || (props.end != props.item.data.end.valueOf());
  9353. itemData.end = util.convert(props.item.data.end,
  9354. dataset._options.type && dataset._options.type.end || 'Date');
  9355. }
  9356. if ('group' in props.item.data) {
  9357. changed = changed || (props.group != props.item.data.group);
  9358. itemData.group = props.item.data.group;
  9359. }
  9360. // only apply changes when start or end is actually changed
  9361. if (changed) {
  9362. me.options.onMove(itemData, function (itemData) {
  9363. if (itemData) {
  9364. // apply changes
  9365. itemData[dataset._fieldId] = id; // ensure the item contains its id (can be undefined)
  9366. changes.push(itemData);
  9367. }
  9368. else {
  9369. // restore original values
  9370. me._updateItemProps(props.item, props);
  9371. me.stackDirty = true; // force re-stacking of all items next redraw
  9372. me.body.emitter.emit('change');
  9373. }
  9374. });
  9375. }
  9376. });
  9377. // apply the changes to the data (if there are changes)
  9378. if (changes.length) {
  9379. dataset.update(changes);
  9380. }
  9381. event.stopPropagation();
  9382. }
  9383. };
  9384. /**
  9385. * Handle selecting/deselecting an item when tapping it
  9386. * @param {Event} event
  9387. * @private
  9388. */
  9389. ItemSet.prototype._onSelectItem = function (event) {
  9390. if (!this.options.selectable) return;
  9391. var ctrlKey = event.gesture.srcEvent && event.gesture.srcEvent.ctrlKey;
  9392. var shiftKey = event.gesture.srcEvent && event.gesture.srcEvent.shiftKey;
  9393. if (ctrlKey || shiftKey) {
  9394. this._onMultiSelectItem(event);
  9395. return;
  9396. }
  9397. var oldSelection = this.getSelection();
  9398. var item = ItemSet.itemFromTarget(event);
  9399. var selection = item ? [item.id] : [];
  9400. this.setSelection(selection);
  9401. var newSelection = this.getSelection();
  9402. // emit a select event,
  9403. // except when old selection is empty and new selection is still empty
  9404. if (newSelection.length > 0 || oldSelection.length > 0) {
  9405. this.body.emitter.emit('select', {
  9406. items: this.getSelection()
  9407. });
  9408. }
  9409. event.stopPropagation();
  9410. };
  9411. /**
  9412. * Handle creation and updates of an item on double tap
  9413. * @param event
  9414. * @private
  9415. */
  9416. ItemSet.prototype._onAddItem = function (event) {
  9417. if (!this.options.selectable) return;
  9418. if (!this.options.editable.add) return;
  9419. var me = this,
  9420. snap = this.body.util.snap || null,
  9421. item = ItemSet.itemFromTarget(event);
  9422. if (item) {
  9423. // update item
  9424. // execute async handler to update the item (or cancel it)
  9425. var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset
  9426. this.options.onUpdate(itemData, function (itemData) {
  9427. if (itemData) {
  9428. me.itemsData.update(itemData);
  9429. }
  9430. });
  9431. }
  9432. else {
  9433. // add item
  9434. var xAbs = util.getAbsoluteLeft(this.dom.frame);
  9435. var x = event.gesture.center.pageX - xAbs;
  9436. var start = this.body.util.toTime(x);
  9437. var newItem = {
  9438. start: snap ? snap(start) : start,
  9439. content: 'new item'
  9440. };
  9441. // when default type is a range, add a default end date to the new item
  9442. if (this.options.type === 'range') {
  9443. var end = this.body.util.toTime(x + this.props.width / 5);
  9444. newItem.end = snap ? snap(end) : end;
  9445. }
  9446. newItem[this.itemsData.fieldId] = util.randomUUID();
  9447. var group = ItemSet.groupFromTarget(event);
  9448. if (group) {
  9449. newItem.group = group.groupId;
  9450. }
  9451. // execute async handler to customize (or cancel) adding an item
  9452. this.options.onAdd(newItem, function (item) {
  9453. if (item) {
  9454. me.itemsData.add(newItem);
  9455. // TODO: need to trigger a redraw?
  9456. }
  9457. });
  9458. }
  9459. };
  9460. /**
  9461. * Handle selecting/deselecting multiple items when holding an item
  9462. * @param {Event} event
  9463. * @private
  9464. */
  9465. ItemSet.prototype._onMultiSelectItem = function (event) {
  9466. if (!this.options.selectable) return;
  9467. var selection,
  9468. item = ItemSet.itemFromTarget(event);
  9469. if (item) {
  9470. // multi select items
  9471. selection = this.getSelection(); // current selection
  9472. var index = selection.indexOf(item.id);
  9473. if (index == -1) {
  9474. // item is not yet selected -> select it
  9475. selection.push(item.id);
  9476. }
  9477. else {
  9478. // item is already selected -> deselect it
  9479. selection.splice(index, 1);
  9480. }
  9481. this.setSelection(selection);
  9482. this.body.emitter.emit('select', {
  9483. items: this.getSelection()
  9484. });
  9485. event.stopPropagation();
  9486. }
  9487. };
  9488. /**
  9489. * Find an item from an event target:
  9490. * searches for the attribute 'timeline-item' in the event target's element tree
  9491. * @param {Event} event
  9492. * @return {Item | null} item
  9493. */
  9494. ItemSet.itemFromTarget = function(event) {
  9495. var target = event.target;
  9496. while (target) {
  9497. if (target.hasOwnProperty('timeline-item')) {
  9498. return target['timeline-item'];
  9499. }
  9500. target = target.parentNode;
  9501. }
  9502. return null;
  9503. };
  9504. /**
  9505. * Find the Group from an event target:
  9506. * searches for the attribute 'timeline-group' in the event target's element tree
  9507. * @param {Event} event
  9508. * @return {Group | null} group
  9509. */
  9510. ItemSet.groupFromTarget = function(event) {
  9511. var target = event.target;
  9512. while (target) {
  9513. if (target.hasOwnProperty('timeline-group')) {
  9514. return target['timeline-group'];
  9515. }
  9516. target = target.parentNode;
  9517. }
  9518. return null;
  9519. };
  9520. /**
  9521. * Find the ItemSet from an event target:
  9522. * searches for the attribute 'timeline-itemset' in the event target's element tree
  9523. * @param {Event} event
  9524. * @return {ItemSet | null} item
  9525. */
  9526. ItemSet.itemSetFromTarget = function(event) {
  9527. var target = event.target;
  9528. while (target) {
  9529. if (target.hasOwnProperty('timeline-itemset')) {
  9530. return target['timeline-itemset'];
  9531. }
  9532. target = target.parentNode;
  9533. }
  9534. return null;
  9535. };
  9536. module.exports = ItemSet;
  9537. /***/ },
  9538. /* 25 */
  9539. /***/ function(module, exports, __webpack_require__) {
  9540. var util = __webpack_require__(1);
  9541. var DOMutil = __webpack_require__(2);
  9542. var Component = __webpack_require__(18);
  9543. /**
  9544. * Legend for Graph2d
  9545. */
  9546. function Legend(body, options, side, linegraphOptions) {
  9547. this.body = body;
  9548. this.defaultOptions = {
  9549. enabled: true,
  9550. icons: true,
  9551. iconSize: 20,
  9552. iconSpacing: 6,
  9553. left: {
  9554. visible: true,
  9555. position: 'top-left' // top/bottom - left,center,right
  9556. },
  9557. right: {
  9558. visible: true,
  9559. position: 'top-left' // top/bottom - left,center,right
  9560. }
  9561. }
  9562. this.side = side;
  9563. this.options = util.extend({},this.defaultOptions);
  9564. this.linegraphOptions = linegraphOptions;
  9565. this.svgElements = {};
  9566. this.dom = {};
  9567. this.groups = {};
  9568. this.amountOfGroups = 0;
  9569. this._create();
  9570. this.setOptions(options);
  9571. }
  9572. Legend.prototype = new Component();
  9573. Legend.prototype.addGroup = function(label, graphOptions) {
  9574. if (!this.groups.hasOwnProperty(label)) {
  9575. this.groups[label] = graphOptions;
  9576. }
  9577. this.amountOfGroups += 1;
  9578. };
  9579. Legend.prototype.updateGroup = function(label, graphOptions) {
  9580. this.groups[label] = graphOptions;
  9581. };
  9582. Legend.prototype.removeGroup = function(label) {
  9583. if (this.groups.hasOwnProperty(label)) {
  9584. delete this.groups[label];
  9585. this.amountOfGroups -= 1;
  9586. }
  9587. };
  9588. Legend.prototype._create = function() {
  9589. this.dom.frame = document.createElement('div');
  9590. this.dom.frame.className = 'legend';
  9591. this.dom.frame.style.position = "absolute";
  9592. this.dom.frame.style.top = "10px";
  9593. this.dom.frame.style.display = "block";
  9594. this.dom.textArea = document.createElement('div');
  9595. this.dom.textArea.className = 'legendText';
  9596. this.dom.textArea.style.position = "relative";
  9597. this.dom.textArea.style.top = "0px";
  9598. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  9599. this.svg.style.position = 'absolute';
  9600. this.svg.style.top = 0 +'px';
  9601. this.svg.style.width = this.options.iconSize + 5 + 'px';
  9602. this.dom.frame.appendChild(this.svg);
  9603. this.dom.frame.appendChild(this.dom.textArea);
  9604. };
  9605. /**
  9606. * Hide the component from the DOM
  9607. */
  9608. Legend.prototype.hide = function() {
  9609. // remove the frame containing the items
  9610. if (this.dom.frame.parentNode) {
  9611. this.dom.frame.parentNode.removeChild(this.dom.frame);
  9612. }
  9613. };
  9614. /**
  9615. * Show the component in the DOM (when not already visible).
  9616. * @return {Boolean} changed
  9617. */
  9618. Legend.prototype.show = function() {
  9619. // show frame containing the items
  9620. if (!this.dom.frame.parentNode) {
  9621. this.body.dom.center.appendChild(this.dom.frame);
  9622. }
  9623. };
  9624. Legend.prototype.setOptions = function(options) {
  9625. var fields = ['enabled','orientation','icons','left','right'];
  9626. util.selectiveDeepExtend(fields, this.options, options);
  9627. };
  9628. Legend.prototype.redraw = function() {
  9629. var activeGroups = 0;
  9630. for (var groupId in this.groups) {
  9631. if (this.groups.hasOwnProperty(groupId)) {
  9632. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  9633. activeGroups++;
  9634. }
  9635. }
  9636. }
  9637. if (this.options[this.side].visible == false || this.amountOfGroups == 0 || this.options.enabled == false || activeGroups == 0) {
  9638. this.hide();
  9639. }
  9640. else {
  9641. this.show();
  9642. if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'bottom-left') {
  9643. this.dom.frame.style.left = '4px';
  9644. this.dom.frame.style.textAlign = "left";
  9645. this.dom.textArea.style.textAlign = "left";
  9646. this.dom.textArea.style.left = (this.options.iconSize + 15) + 'px';
  9647. this.dom.textArea.style.right = '';
  9648. this.svg.style.left = 0 +'px';
  9649. this.svg.style.right = '';
  9650. }
  9651. else {
  9652. this.dom.frame.style.right = '4px';
  9653. this.dom.frame.style.textAlign = "right";
  9654. this.dom.textArea.style.textAlign = "right";
  9655. this.dom.textArea.style.right = (this.options.iconSize + 15) + 'px';
  9656. this.dom.textArea.style.left = '';
  9657. this.svg.style.right = 0 +'px';
  9658. this.svg.style.left = '';
  9659. }
  9660. if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'top-right') {
  9661. this.dom.frame.style.top = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
  9662. this.dom.frame.style.bottom = '';
  9663. }
  9664. else {
  9665. this.dom.frame.style.bottom = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
  9666. this.dom.frame.style.top = '';
  9667. }
  9668. if (this.options.icons == false) {
  9669. this.dom.frame.style.width = this.dom.textArea.offsetWidth + 10 + 'px';
  9670. this.dom.textArea.style.right = '';
  9671. this.dom.textArea.style.left = '';
  9672. this.svg.style.width = '0px';
  9673. }
  9674. else {
  9675. this.dom.frame.style.width = this.options.iconSize + 15 + this.dom.textArea.offsetWidth + 10 + 'px'
  9676. this.drawLegendIcons();
  9677. }
  9678. var content = '';
  9679. for (var groupId in this.groups) {
  9680. if (this.groups.hasOwnProperty(groupId)) {
  9681. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  9682. content += this.groups[groupId].content + '<br />';
  9683. }
  9684. }
  9685. }
  9686. this.dom.textArea.innerHTML = content;
  9687. this.dom.textArea.style.lineHeight = ((0.75 * this.options.iconSize) + this.options.iconSpacing) + 'px';
  9688. }
  9689. };
  9690. Legend.prototype.drawLegendIcons = function() {
  9691. if (this.dom.frame.parentNode) {
  9692. DOMutil.prepareElements(this.svgElements);
  9693. var padding = window.getComputedStyle(this.dom.frame).paddingTop;
  9694. var iconOffset = Number(padding.replace('px',''));
  9695. var x = iconOffset;
  9696. var iconWidth = this.options.iconSize;
  9697. var iconHeight = 0.75 * this.options.iconSize;
  9698. var y = iconOffset + 0.5 * iconHeight + 3;
  9699. this.svg.style.width = iconWidth + 5 + iconOffset + 'px';
  9700. for (var groupId in this.groups) {
  9701. if (this.groups.hasOwnProperty(groupId)) {
  9702. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  9703. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  9704. y += iconHeight + this.options.iconSpacing;
  9705. }
  9706. }
  9707. }
  9708. DOMutil.cleanupElements(this.svgElements);
  9709. }
  9710. };
  9711. module.exports = Legend;
  9712. /***/ },
  9713. /* 26 */
  9714. /***/ function(module, exports, __webpack_require__) {
  9715. var util = __webpack_require__(1);
  9716. var DOMutil = __webpack_require__(2);
  9717. var DataSet = __webpack_require__(3);
  9718. var DataView = __webpack_require__(4);
  9719. var Component = __webpack_require__(18);
  9720. var DataAxis = __webpack_require__(21);
  9721. var GraphGroup = __webpack_require__(22);
  9722. var Legend = __webpack_require__(25);
  9723. var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
  9724. /**
  9725. * This is the constructor of the LineGraph. It requires a Timeline body and options.
  9726. *
  9727. * @param body
  9728. * @param options
  9729. * @constructor
  9730. */
  9731. function LineGraph(body, options) {
  9732. this.id = util.randomUUID();
  9733. this.body = body;
  9734. this.defaultOptions = {
  9735. yAxisOrientation: 'left',
  9736. defaultGroup: 'default',
  9737. sort: true,
  9738. sampling: true,
  9739. graphHeight: '400px',
  9740. shaded: {
  9741. enabled: false,
  9742. orientation: 'bottom' // top, bottom
  9743. },
  9744. style: 'line', // line, bar
  9745. barChart: {
  9746. width: 50,
  9747. handleOverlap: 'overlap',
  9748. align: 'center' // left, center, right
  9749. },
  9750. catmullRom: {
  9751. enabled: true,
  9752. parametrization: 'centripetal', // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5)
  9753. alpha: 0.5
  9754. },
  9755. drawPoints: {
  9756. enabled: true,
  9757. size: 6,
  9758. style: 'square' // square, circle
  9759. },
  9760. dataAxis: {
  9761. showMinorLabels: true,
  9762. showMajorLabels: true,
  9763. icons: false,
  9764. width: '40px',
  9765. visible: true,
  9766. customRange: {
  9767. left: {min:undefined, max:undefined},
  9768. right: {min:undefined, max:undefined}
  9769. }
  9770. },
  9771. legend: {
  9772. enabled: false,
  9773. icons: true,
  9774. left: {
  9775. visible: true,
  9776. position: 'top-left' // top/bottom - left,right
  9777. },
  9778. right: {
  9779. visible: true,
  9780. position: 'top-right' // top/bottom - left,right
  9781. }
  9782. },
  9783. groups: {
  9784. visibility: {}
  9785. }
  9786. };
  9787. // options is shared by this ItemSet and all its items
  9788. this.options = util.extend({}, this.defaultOptions);
  9789. this.dom = {};
  9790. this.props = {};
  9791. this.hammer = null;
  9792. this.groups = {};
  9793. this.abortedGraphUpdate = false;
  9794. var me = this;
  9795. this.itemsData = null; // DataSet
  9796. this.groupsData = null; // DataSet
  9797. // listeners for the DataSet of the items
  9798. this.itemListeners = {
  9799. 'add': function (event, params, senderId) {
  9800. me._onAdd(params.items);
  9801. },
  9802. 'update': function (event, params, senderId) {
  9803. me._onUpdate(params.items);
  9804. },
  9805. 'remove': function (event, params, senderId) {
  9806. me._onRemove(params.items);
  9807. }
  9808. };
  9809. // listeners for the DataSet of the groups
  9810. this.groupListeners = {
  9811. 'add': function (event, params, senderId) {
  9812. me._onAddGroups(params.items);
  9813. },
  9814. 'update': function (event, params, senderId) {
  9815. me._onUpdateGroups(params.items);
  9816. },
  9817. 'remove': function (event, params, senderId) {
  9818. me._onRemoveGroups(params.items);
  9819. }
  9820. };
  9821. this.items = {}; // object with an Item for every data item
  9822. this.selection = []; // list with the ids of all selected nodes
  9823. this.lastStart = this.body.range.start;
  9824. this.touchParams = {}; // stores properties while dragging
  9825. this.svgElements = {};
  9826. this.setOptions(options);
  9827. this.groupsUsingDefaultStyles = [0];
  9828. this.body.emitter.on("rangechanged", function() {
  9829. me.lastStart = me.body.range.start;
  9830. me.svg.style.left = util.option.asSize(-me.width);
  9831. me._updateGraph.apply(me);
  9832. });
  9833. // create the HTML DOM
  9834. this._create();
  9835. this.body.emitter.emit("change");
  9836. }
  9837. LineGraph.prototype = new Component();
  9838. /**
  9839. * Create the HTML DOM for the ItemSet
  9840. */
  9841. LineGraph.prototype._create = function(){
  9842. var frame = document.createElement('div');
  9843. frame.className = 'LineGraph';
  9844. this.dom.frame = frame;
  9845. // create svg element for graph drawing.
  9846. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  9847. this.svg.style.position = "relative";
  9848. this.svg.style.height = ('' + this.options.graphHeight).replace("px",'') + 'px';
  9849. this.svg.style.display = "block";
  9850. frame.appendChild(this.svg);
  9851. // data axis
  9852. this.options.dataAxis.orientation = 'left';
  9853. this.yAxisLeft = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
  9854. this.options.dataAxis.orientation = 'right';
  9855. this.yAxisRight = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
  9856. delete this.options.dataAxis.orientation;
  9857. // legends
  9858. this.legendLeft = new Legend(this.body, this.options.legend, 'left', this.options.groups);
  9859. this.legendRight = new Legend(this.body, this.options.legend, 'right', this.options.groups);
  9860. this.show();
  9861. };
  9862. /**
  9863. * set the options of the LineGraph. the mergeOptions is used for subObjects that have an enabled element.
  9864. * @param options
  9865. */
  9866. LineGraph.prototype.setOptions = function(options) {
  9867. if (options) {
  9868. var fields = ['sampling','defaultGroup','graphHeight','yAxisOrientation','style','barChart','dataAxis','sort','groups'];
  9869. util.selectiveDeepExtend(fields, this.options, options);
  9870. util.mergeOptions(this.options, options,'catmullRom');
  9871. util.mergeOptions(this.options, options,'drawPoints');
  9872. util.mergeOptions(this.options, options,'shaded');
  9873. util.mergeOptions(this.options, options,'legend');
  9874. if (options.catmullRom) {
  9875. if (typeof options.catmullRom == 'object') {
  9876. if (options.catmullRom.parametrization) {
  9877. if (options.catmullRom.parametrization == 'uniform') {
  9878. this.options.catmullRom.alpha = 0;
  9879. }
  9880. else if (options.catmullRom.parametrization == 'chordal') {
  9881. this.options.catmullRom.alpha = 1.0;
  9882. }
  9883. else {
  9884. this.options.catmullRom.parametrization = 'centripetal';
  9885. this.options.catmullRom.alpha = 0.5;
  9886. }
  9887. }
  9888. }
  9889. }
  9890. if (this.yAxisLeft) {
  9891. if (options.dataAxis !== undefined) {
  9892. this.yAxisLeft.setOptions(this.options.dataAxis);
  9893. this.yAxisRight.setOptions(this.options.dataAxis);
  9894. }
  9895. }
  9896. if (this.legendLeft) {
  9897. if (options.legend !== undefined) {
  9898. this.legendLeft.setOptions(this.options.legend);
  9899. this.legendRight.setOptions(this.options.legend);
  9900. }
  9901. }
  9902. if (this.groups.hasOwnProperty(UNGROUPED)) {
  9903. this.groups[UNGROUPED].setOptions(options);
  9904. }
  9905. }
  9906. if (this.dom.frame) {
  9907. this._updateGraph();
  9908. }
  9909. };
  9910. /**
  9911. * Hide the component from the DOM
  9912. */
  9913. LineGraph.prototype.hide = function() {
  9914. // remove the frame containing the items
  9915. if (this.dom.frame.parentNode) {
  9916. this.dom.frame.parentNode.removeChild(this.dom.frame);
  9917. }
  9918. };
  9919. /**
  9920. * Show the component in the DOM (when not already visible).
  9921. * @return {Boolean} changed
  9922. */
  9923. LineGraph.prototype.show = function() {
  9924. // show frame containing the items
  9925. if (!this.dom.frame.parentNode) {
  9926. this.body.dom.center.appendChild(this.dom.frame);
  9927. }
  9928. };
  9929. /**
  9930. * Set items
  9931. * @param {vis.DataSet | null} items
  9932. */
  9933. LineGraph.prototype.setItems = function(items) {
  9934. var me = this,
  9935. ids,
  9936. oldItemsData = this.itemsData;
  9937. // replace the dataset
  9938. if (!items) {
  9939. this.itemsData = null;
  9940. }
  9941. else if (items instanceof DataSet || items instanceof DataView) {
  9942. this.itemsData = items;
  9943. }
  9944. else {
  9945. throw new TypeError('Data must be an instance of DataSet or DataView');
  9946. }
  9947. if (oldItemsData) {
  9948. // unsubscribe from old dataset
  9949. util.forEach(this.itemListeners, function (callback, event) {
  9950. oldItemsData.off(event, callback);
  9951. });
  9952. // remove all drawn items
  9953. ids = oldItemsData.getIds();
  9954. this._onRemove(ids);
  9955. }
  9956. if (this.itemsData) {
  9957. // subscribe to new dataset
  9958. var id = this.id;
  9959. util.forEach(this.itemListeners, function (callback, event) {
  9960. me.itemsData.on(event, callback, id);
  9961. });
  9962. // add all new items
  9963. ids = this.itemsData.getIds();
  9964. this._onAdd(ids);
  9965. }
  9966. this._updateUngrouped();
  9967. this._updateGraph();
  9968. this.redraw();
  9969. };
  9970. /**
  9971. * Set groups
  9972. * @param {vis.DataSet} groups
  9973. */
  9974. LineGraph.prototype.setGroups = function(groups) {
  9975. var me = this,
  9976. ids;
  9977. // unsubscribe from current dataset
  9978. if (this.groupsData) {
  9979. util.forEach(this.groupListeners, function (callback, event) {
  9980. me.groupsData.unsubscribe(event, callback);
  9981. });
  9982. // remove all drawn groups
  9983. ids = this.groupsData.getIds();
  9984. this.groupsData = null;
  9985. this._onRemoveGroups(ids); // note: this will cause a redraw
  9986. }
  9987. // replace the dataset
  9988. if (!groups) {
  9989. this.groupsData = null;
  9990. }
  9991. else if (groups instanceof DataSet || groups instanceof DataView) {
  9992. this.groupsData = groups;
  9993. }
  9994. else {
  9995. throw new TypeError('Data must be an instance of DataSet or DataView');
  9996. }
  9997. if (this.groupsData) {
  9998. // subscribe to new dataset
  9999. var id = this.id;
  10000. util.forEach(this.groupListeners, function (callback, event) {
  10001. me.groupsData.on(event, callback, id);
  10002. });
  10003. // draw all ms
  10004. ids = this.groupsData.getIds();
  10005. this._onAddGroups(ids);
  10006. }
  10007. this._onUpdate();
  10008. };
  10009. /**
  10010. * Update the datapoints
  10011. * @param [ids]
  10012. * @private
  10013. */
  10014. LineGraph.prototype._onUpdate = function(ids) {
  10015. this._updateUngrouped();
  10016. this._updateAllGroupData();
  10017. this._updateGraph();
  10018. this.redraw();
  10019. };
  10020. LineGraph.prototype._onAdd = function (ids) {this._onUpdate(ids);};
  10021. LineGraph.prototype._onRemove = function (ids) {this._onUpdate(ids);};
  10022. LineGraph.prototype._onUpdateGroups = function (groupIds) {
  10023. for (var i = 0; i < groupIds.length; i++) {
  10024. var group = this.groupsData.get(groupIds[i]);
  10025. this._updateGroup(group, groupIds[i]);
  10026. }
  10027. this._updateGraph();
  10028. this.redraw();
  10029. };
  10030. LineGraph.prototype._onAddGroups = function (groupIds) {this._onUpdateGroups(groupIds);};
  10031. LineGraph.prototype._onRemoveGroups = function (groupIds) {
  10032. for (var i = 0; i < groupIds.length; i++) {
  10033. if (!this.groups.hasOwnProperty(groupIds[i])) {
  10034. if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') {
  10035. this.yAxisRight.removeGroup(groupIds[i]);
  10036. this.legendRight.removeGroup(groupIds[i]);
  10037. this.legendRight.redraw();
  10038. }
  10039. else {
  10040. this.yAxisLeft.removeGroup(groupIds[i]);
  10041. this.legendLeft.removeGroup(groupIds[i]);
  10042. this.legendLeft.redraw();
  10043. }
  10044. delete this.groups[groupIds[i]];
  10045. }
  10046. }
  10047. this._updateUngrouped();
  10048. this._updateGraph();
  10049. this.redraw();
  10050. };
  10051. /**
  10052. * update a group object
  10053. *
  10054. * @param group
  10055. * @param groupId
  10056. * @private
  10057. */
  10058. LineGraph.prototype._updateGroup = function (group, groupId) {
  10059. if (!this.groups.hasOwnProperty(groupId)) {
  10060. this.groups[groupId] = new GraphGroup(group, groupId, this.options, this.groupsUsingDefaultStyles);
  10061. if (this.groups[groupId].options.yAxisOrientation == 'right') {
  10062. this.yAxisRight.addGroup(groupId, this.groups[groupId]);
  10063. this.legendRight.addGroup(groupId, this.groups[groupId]);
  10064. }
  10065. else {
  10066. this.yAxisLeft.addGroup(groupId, this.groups[groupId]);
  10067. this.legendLeft.addGroup(groupId, this.groups[groupId]);
  10068. }
  10069. }
  10070. else {
  10071. this.groups[groupId].update(group);
  10072. if (this.groups[groupId].options.yAxisOrientation == 'right') {
  10073. this.yAxisRight.updateGroup(groupId, this.groups[groupId]);
  10074. this.legendRight.updateGroup(groupId, this.groups[groupId]);
  10075. }
  10076. else {
  10077. this.yAxisLeft.updateGroup(groupId, this.groups[groupId]);
  10078. this.legendLeft.updateGroup(groupId, this.groups[groupId]);
  10079. }
  10080. }
  10081. this.legendLeft.redraw();
  10082. this.legendRight.redraw();
  10083. };
  10084. LineGraph.prototype._updateAllGroupData = function () {
  10085. if (this.itemsData != null) {
  10086. var groupsContent = {};
  10087. var groupId;
  10088. for (groupId in this.groups) {
  10089. if (this.groups.hasOwnProperty(groupId)) {
  10090. groupsContent[groupId] = [];
  10091. }
  10092. }
  10093. for (var itemId in this.itemsData._data) {
  10094. if (this.itemsData._data.hasOwnProperty(itemId)) {
  10095. var item = this.itemsData._data[itemId];
  10096. item.x = util.convert(item.x,"Date");
  10097. groupsContent[item.group].push(item);
  10098. }
  10099. }
  10100. for (groupId in this.groups) {
  10101. if (this.groups.hasOwnProperty(groupId)) {
  10102. this.groups[groupId].setItems(groupsContent[groupId]);
  10103. }
  10104. }
  10105. }
  10106. };
  10107. /**
  10108. * Create or delete the group holding all ungrouped items. This group is used when
  10109. * there are no groups specified. This anonymous group is called 'graph'.
  10110. * @protected
  10111. */
  10112. LineGraph.prototype._updateUngrouped = function() {
  10113. if (this.itemsData != null) {
  10114. // var t0 = new Date();
  10115. var group = {id: UNGROUPED, content: this.options.defaultGroup};
  10116. this._updateGroup(group, UNGROUPED);
  10117. var ungroupedCounter = 0;
  10118. if (this.itemsData) {
  10119. for (var itemId in this.itemsData._data) {
  10120. if (this.itemsData._data.hasOwnProperty(itemId)) {
  10121. var item = this.itemsData._data[itemId];
  10122. if (item != undefined) {
  10123. if (item.hasOwnProperty('group')) {
  10124. if (item.group === undefined) {
  10125. item.group = UNGROUPED;
  10126. }
  10127. }
  10128. else {
  10129. item.group = UNGROUPED;
  10130. }
  10131. ungroupedCounter = item.group == UNGROUPED ? ungroupedCounter + 1 : ungroupedCounter;
  10132. }
  10133. }
  10134. }
  10135. }
  10136. if (ungroupedCounter == 0) {
  10137. delete this.groups[UNGROUPED];
  10138. this.legendLeft.removeGroup(UNGROUPED);
  10139. this.legendRight.removeGroup(UNGROUPED);
  10140. this.yAxisLeft.removeGroup(UNGROUPED);
  10141. this.yAxisRight.removeGroup(UNGROUPED);
  10142. }
  10143. }
  10144. else {
  10145. delete this.groups[UNGROUPED];
  10146. this.legendLeft.removeGroup(UNGROUPED);
  10147. this.legendRight.removeGroup(UNGROUPED);
  10148. this.yAxisLeft.removeGroup(UNGROUPED);
  10149. this.yAxisRight.removeGroup(UNGROUPED);
  10150. }
  10151. this.legendLeft.redraw();
  10152. this.legendRight.redraw();
  10153. };
  10154. /**
  10155. * Redraw the component, mandatory function
  10156. * @return {boolean} Returns true if the component is resized
  10157. */
  10158. LineGraph.prototype.redraw = function() {
  10159. var resized = false;
  10160. this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px';
  10161. if (this.lastWidth === undefined && this.width || this.lastWidth != this.width) {
  10162. resized = true;
  10163. }
  10164. // check if this component is resized
  10165. resized = this._isResized() || resized;
  10166. // check whether zoomed (in that case we need to re-stack everything)
  10167. var visibleInterval = this.body.range.end - this.body.range.start;
  10168. var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.width != this.lastWidth);
  10169. this.lastVisibleInterval = visibleInterval;
  10170. this.lastWidth = this.width;
  10171. // calculate actual size and position
  10172. this.width = this.dom.frame.offsetWidth;
  10173. // the svg element is three times as big as the width, this allows for fully dragging left and right
  10174. // without reloading the graph. the controls for this are bound to events in the constructor
  10175. if (resized == true) {
  10176. this.svg.style.width = util.option.asSize(3*this.width);
  10177. this.svg.style.left = util.option.asSize(-this.width);
  10178. }
  10179. if (zoomed == true || this.abortedGraphUpdate == true) {
  10180. this._updateGraph();
  10181. }
  10182. else {
  10183. // move the whole svg while dragging
  10184. if (this.lastStart != 0) {
  10185. var offset = this.body.range.start - this.lastStart;
  10186. var range = this.body.range.end - this.body.range.start;
  10187. if (this.width != 0) {
  10188. var rangePerPixelInv = this.width/range;
  10189. var xOffset = offset * rangePerPixelInv;
  10190. this.svg.style.left = (-this.width - xOffset) + "px";
  10191. }
  10192. }
  10193. }
  10194. this.legendLeft.redraw();
  10195. this.legendRight.redraw();
  10196. return resized;
  10197. };
  10198. /**
  10199. * Update and redraw the graph.
  10200. *
  10201. */
  10202. LineGraph.prototype._updateGraph = function () {
  10203. // reset the svg elements
  10204. DOMutil.prepareElements(this.svgElements);
  10205. if (this.width != 0 && this.itemsData != null) {
  10206. var group, i;
  10207. var preprocessedGroupData = {};
  10208. var processedGroupData = {};
  10209. var groupRanges = {};
  10210. var changeCalled = false;
  10211. // getting group Ids
  10212. var groupIds = [];
  10213. for (var groupId in this.groups) {
  10214. if (this.groups.hasOwnProperty(groupId)) {
  10215. group = this.groups[groupId];
  10216. if (group.visible == true && (this.options.groups.visibility[groupId] === undefined || this.options.groups.visibility[groupId] == true)) {
  10217. groupIds.push(groupId);
  10218. }
  10219. }
  10220. }
  10221. if (groupIds.length > 0) {
  10222. // this is the range of the SVG canvas
  10223. var minDate = this.body.util.toGlobalTime(- this.body.domProps.root.width);
  10224. var maxDate = this.body.util.toGlobalTime(2 * this.body.domProps.root.width);
  10225. var groupsData = {};
  10226. // fill groups data
  10227. this._getRelevantData(groupIds, groupsData, minDate, maxDate);
  10228. // we transform the X coordinates to detect collisions
  10229. for (i = 0; i < groupIds.length; i++) {
  10230. preprocessedGroupData[groupIds[i]] = this._convertXcoordinates(groupsData[groupIds[i]]);
  10231. }
  10232. // now all needed data has been collected we start the processing.
  10233. this._getYRanges(groupIds, preprocessedGroupData, groupRanges);
  10234. // update the Y axis first, we use this data to draw at the correct Y points
  10235. // changeCalled is required to clean the SVG on a change emit.
  10236. changeCalled = this._updateYAxis(groupIds, groupRanges);
  10237. if (changeCalled == true) {
  10238. DOMutil.cleanupElements(this.svgElements);
  10239. this.abortedGraphUpdate = true;
  10240. this.body.emitter.emit("change");
  10241. return;
  10242. }
  10243. this.abortedGraphUpdate = false;
  10244. // With the yAxis scaled correctly, use this to get the Y values of the points.
  10245. for (i = 0; i < groupIds.length; i++) {
  10246. group = this.groups[groupIds[i]];
  10247. processedGroupData[groupIds[i]] = this._convertYcoordinates(groupsData[groupIds[i]], group);
  10248. }
  10249. // draw the groups
  10250. for (i = 0; i < groupIds.length; i++) {
  10251. group = this.groups[groupIds[i]];
  10252. if (group.options.style == 'line') {
  10253. this._drawLineGraph(processedGroupData[groupIds[i]], group);
  10254. }
  10255. }
  10256. this._drawBarGraphs(groupIds, processedGroupData);
  10257. }
  10258. }
  10259. // cleanup unused svg elements
  10260. DOMutil.cleanupElements(this.svgElements);
  10261. };
  10262. LineGraph.prototype._getRelevantData = function (groupIds, groupsData, minDate, maxDate) {
  10263. // first select and preprocess the data from the datasets.
  10264. // the groups have their preselection of data, we now loop over this data to see
  10265. // what data we need to draw. Sorted data is much faster.
  10266. // more optimization is possible by doing the sampling before and using the binary search
  10267. // to find the end date to determine the increment.
  10268. var group, i, j, item;
  10269. if (groupIds.length > 0) {
  10270. for (i = 0; i < groupIds.length; i++) {
  10271. group = this.groups[groupIds[i]];
  10272. groupsData[groupIds[i]] = [];
  10273. var dataContainer = groupsData[groupIds[i]];
  10274. // optimization for sorted data
  10275. if (group.options.sort == true) {
  10276. var guess = Math.max(0, util.binarySearchGeneric(group.itemsData, minDate, 'x', 'before'));
  10277. for (j = guess; j < group.itemsData.length; j++) {
  10278. item = group.itemsData[j];
  10279. if (item !== undefined) {
  10280. if (item.x > maxDate) {
  10281. dataContainer.push(item);
  10282. break;
  10283. }
  10284. else {
  10285. dataContainer.push(item);
  10286. }
  10287. }
  10288. }
  10289. }
  10290. else {
  10291. for (j = 0; j < group.itemsData.length; j++) {
  10292. item = group.itemsData[j];
  10293. if (item !== undefined) {
  10294. if (item.x > minDate && item.x < maxDate) {
  10295. dataContainer.push(item);
  10296. }
  10297. }
  10298. }
  10299. }
  10300. }
  10301. }
  10302. this._applySampling(groupIds, groupsData);
  10303. };
  10304. LineGraph.prototype._applySampling = function (groupIds, groupsData) {
  10305. var group;
  10306. if (groupIds.length > 0) {
  10307. for (var i = 0; i < groupIds.length; i++) {
  10308. group = this.groups[groupIds[i]];
  10309. if (group.options.sampling == true) {
  10310. var dataContainer = groupsData[groupIds[i]];
  10311. if (dataContainer.length > 0) {
  10312. var increment = 1;
  10313. var amountOfPoints = dataContainer.length;
  10314. // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop
  10315. // of width changing of the yAxis.
  10316. var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x);
  10317. var pointsPerPixel = amountOfPoints / xDistance;
  10318. increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel)));
  10319. var sampledData = [];
  10320. for (var j = 0; j < amountOfPoints; j += increment) {
  10321. sampledData.push(dataContainer[j]);
  10322. }
  10323. groupsData[groupIds[i]] = sampledData;
  10324. }
  10325. }
  10326. }
  10327. }
  10328. };
  10329. LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) {
  10330. var groupData, group, i,j;
  10331. var barCombinedDataLeft = [];
  10332. var barCombinedDataRight = [];
  10333. var barCombinedData;
  10334. if (groupIds.length > 0) {
  10335. for (i = 0; i < groupIds.length; i++) {
  10336. groupData = groupsData[groupIds[i]];
  10337. if (groupData.length > 0) {
  10338. group = this.groups[groupIds[i]];
  10339. if (group.options.style == 'line' || group.options.barChart.handleOverlap != "stack") {
  10340. var yMin = groupData[0].y;
  10341. var yMax = groupData[0].y;
  10342. for (j = 0; j < groupData.length; j++) {
  10343. yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
  10344. yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
  10345. }
  10346. groupRanges[groupIds[i]] = {min: yMin, max: yMax, yAxisOrientation: group.options.yAxisOrientation};
  10347. }
  10348. else if (group.options.style == 'bar') {
  10349. if (group.options.yAxisOrientation == 'left') {
  10350. barCombinedData = barCombinedDataLeft;
  10351. }
  10352. else {
  10353. barCombinedData = barCombinedDataRight;
  10354. }
  10355. groupRanges[groupIds[i]] = {min: 0, max: 0, yAxisOrientation: group.options.yAxisOrientation, ignore: true};
  10356. // combine data
  10357. for (j = 0; j < groupData.length; j++) {
  10358. barCombinedData.push({
  10359. x: groupData[j].x,
  10360. y: groupData[j].y,
  10361. groupId: groupIds[i]
  10362. });
  10363. }
  10364. }
  10365. }
  10366. }
  10367. var intersections;
  10368. if (barCombinedDataLeft.length > 0) {
  10369. // sort by time and by group
  10370. barCombinedDataLeft.sort(function (a, b) {
  10371. if (a.x == b.x) {
  10372. return a.groupId - b.groupId;
  10373. } else {
  10374. return a.x - b.x;
  10375. }
  10376. });
  10377. intersections = {};
  10378. this._getDataIntersections(intersections, barCombinedDataLeft);
  10379. groupRanges["__barchartLeft"] = this._getStackedBarYRange(intersections, barCombinedDataLeft);
  10380. groupRanges["__barchartLeft"].yAxisOrientation = "left";
  10381. groupIds.push("__barchartLeft");
  10382. }
  10383. if (barCombinedDataRight.length > 0) {
  10384. // sort by time and by group
  10385. barCombinedDataRight.sort(function (a, b) {
  10386. if (a.x == b.x) {
  10387. return a.groupId - b.groupId;
  10388. } else {
  10389. return a.x - b.x;
  10390. }
  10391. });
  10392. intersections = {};
  10393. this._getDataIntersections(intersections, barCombinedDataRight);
  10394. groupRanges["__barchartRight"] = this._getStackedBarYRange(intersections, barCombinedDataRight);
  10395. groupRanges["__barchartRight"].yAxisOrientation = "right";
  10396. groupIds.push("__barchartRight");
  10397. }
  10398. }
  10399. };
  10400. LineGraph.prototype._getStackedBarYRange = function (intersections, combinedData) {
  10401. var key;
  10402. var yMin = combinedData[0].y;
  10403. var yMax = combinedData[0].y;
  10404. for (var i = 0; i < combinedData.length; i++) {
  10405. key = combinedData[i].x;
  10406. if (intersections[key] === undefined) {
  10407. yMin = yMin > combinedData[i].y ? combinedData[i].y : yMin;
  10408. yMax = yMax < combinedData[i].y ? combinedData[i].y : yMax;
  10409. }
  10410. else {
  10411. intersections[key].accumulated += combinedData[i].y;
  10412. }
  10413. }
  10414. for (var xpos in intersections) {
  10415. if (intersections.hasOwnProperty(xpos)) {
  10416. yMin = yMin > intersections[xpos].accumulated ? intersections[xpos].accumulated : yMin;
  10417. yMax = yMax < intersections[xpos].accumulated ? intersections[xpos].accumulated : yMax;
  10418. }
  10419. }
  10420. return {min: yMin, max: yMax};
  10421. };
  10422. /**
  10423. * this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden.
  10424. * @param {Array} groupIds
  10425. * @param {Object} groupRanges
  10426. * @private
  10427. */
  10428. LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
  10429. var changeCalled = false;
  10430. var yAxisLeftUsed = false;
  10431. var yAxisRightUsed = false;
  10432. var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal;
  10433. // if groups are present
  10434. if (groupIds.length > 0) {
  10435. for (var i = 0; i < groupIds.length; i++) {
  10436. if (groupRanges.hasOwnProperty(groupIds[i])) {
  10437. if (groupRanges[groupIds[i]].ignore !== true) {
  10438. minVal = groupRanges[groupIds[i]].min;
  10439. maxVal = groupRanges[groupIds[i]].max;
  10440. if (groupRanges[groupIds[i]].yAxisOrientation == 'left') {
  10441. yAxisLeftUsed = true;
  10442. minLeft = minLeft > minVal ? minVal : minLeft;
  10443. maxLeft = maxLeft < maxVal ? maxVal : maxLeft;
  10444. }
  10445. else {
  10446. yAxisRightUsed = true;
  10447. minRight = minRight > minVal ? minVal : minRight;
  10448. maxRight = maxRight < maxVal ? maxVal : maxRight;
  10449. }
  10450. }
  10451. }
  10452. }
  10453. if (yAxisLeftUsed == true) {
  10454. this.yAxisLeft.setRange(minLeft, maxLeft);
  10455. }
  10456. if (yAxisRightUsed == true) {
  10457. this.yAxisRight.setRange(minRight, maxRight);
  10458. }
  10459. }
  10460. changeCalled = this._toggleAxisVisiblity(yAxisLeftUsed , this.yAxisLeft) || changeCalled;
  10461. changeCalled = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || changeCalled;
  10462. if (yAxisRightUsed == true && yAxisLeftUsed == true) {
  10463. this.yAxisLeft.drawIcons = true;
  10464. this.yAxisRight.drawIcons = true;
  10465. }
  10466. else {
  10467. this.yAxisLeft.drawIcons = false;
  10468. this.yAxisRight.drawIcons = false;
  10469. }
  10470. this.yAxisRight.master = !yAxisLeftUsed;
  10471. if (this.yAxisRight.master == false) {
  10472. if (yAxisRightUsed == true) {this.yAxisLeft.lineOffset = this.yAxisRight.width;}
  10473. else {this.yAxisLeft.lineOffset = 0;}
  10474. changeCalled = this.yAxisLeft.redraw() || changeCalled;
  10475. this.yAxisRight.stepPixelsForced = this.yAxisLeft.stepPixels;
  10476. changeCalled = this.yAxisRight.redraw() || changeCalled;
  10477. }
  10478. else {
  10479. changeCalled = this.yAxisRight.redraw() || changeCalled;
  10480. }
  10481. // clean the accumulated lists
  10482. if (groupIds.indexOf("__barchartLeft") != -1) {
  10483. groupIds.splice(groupIds.indexOf("__barchartLeft"),1);
  10484. }
  10485. if (groupIds.indexOf("__barchartRight") != -1) {
  10486. groupIds.splice(groupIds.indexOf("__barchartRight"),1);
  10487. }
  10488. return changeCalled;
  10489. };
  10490. /**
  10491. * This shows or hides the Y axis if needed. If there is a change, the changed event is emitted by the updateYAxis function
  10492. *
  10493. * @param {boolean} axisUsed
  10494. * @returns {boolean}
  10495. * @private
  10496. * @param axis
  10497. */
  10498. LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) {
  10499. var changed = false;
  10500. if (axisUsed == false) {
  10501. if (axis.dom.frame.parentNode) {
  10502. axis.hide();
  10503. changed = true;
  10504. }
  10505. }
  10506. else {
  10507. if (!axis.dom.frame.parentNode) {
  10508. axis.show();
  10509. changed = true;
  10510. }
  10511. }
  10512. return changed;
  10513. };
  10514. /**
  10515. * draw a bar graph
  10516. *
  10517. * @param groupIds
  10518. * @param processedGroupData
  10519. */
  10520. LineGraph.prototype._drawBarGraphs = function (groupIds, processedGroupData) {
  10521. var combinedData = [];
  10522. var intersections = {};
  10523. var coreDistance;
  10524. var key, drawData;
  10525. var group;
  10526. var i,j;
  10527. var barPoints = 0;
  10528. // combine all barchart data
  10529. for (i = 0; i < groupIds.length; i++) {
  10530. group = this.groups[groupIds[i]];
  10531. if (group.options.style == 'bar') {
  10532. if (group.visible == true && (this.options.groups.visibility[groupIds[i]] === undefined || this.options.groups.visibility[groupIds[i]] == true)) {
  10533. for (j = 0; j < processedGroupData[groupIds[i]].length; j++) {
  10534. combinedData.push({
  10535. x: processedGroupData[groupIds[i]][j].x,
  10536. y: processedGroupData[groupIds[i]][j].y,
  10537. groupId: groupIds[i]
  10538. });
  10539. barPoints += 1;
  10540. }
  10541. }
  10542. }
  10543. }
  10544. if (barPoints == 0) {return;}
  10545. // sort by time and by group
  10546. combinedData.sort(function (a, b) {
  10547. if (a.x == b.x) {
  10548. return a.groupId - b.groupId;
  10549. } else {
  10550. return a.x - b.x;
  10551. }
  10552. });
  10553. // get intersections
  10554. this._getDataIntersections(intersections, combinedData);
  10555. // plot barchart
  10556. for (i = 0; i < combinedData.length; i++) {
  10557. group = this.groups[combinedData[i].groupId];
  10558. var minWidth = 0.1 * group.options.barChart.width;
  10559. key = combinedData[i].x;
  10560. var heightOffset = 0;
  10561. if (intersections[key] === undefined) {
  10562. if (i+1 < combinedData.length) {coreDistance = Math.abs(combinedData[i+1].x - key);}
  10563. if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[i-1].x - key));}
  10564. drawData = this._getSafeDrawData(coreDistance, group, minWidth);
  10565. }
  10566. else {
  10567. var nextKey = i + (intersections[key].amount - intersections[key].resolved);
  10568. var prevKey = i - (intersections[key].resolved + 1);
  10569. if (nextKey < combinedData.length) {coreDistance = Math.abs(combinedData[nextKey].x - key);}
  10570. if (prevKey > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[prevKey].x - key));}
  10571. drawData = this._getSafeDrawData(coreDistance, group, minWidth);
  10572. intersections[key].resolved += 1;
  10573. if (group.options.barChart.handleOverlap == 'stack') {
  10574. heightOffset = intersections[key].accumulated;
  10575. intersections[key].accumulated += group.zeroPosition - combinedData[i].y;
  10576. }
  10577. else if (group.options.barChart.handleOverlap == 'sideBySide') {
  10578. drawData.width = drawData.width / intersections[key].amount;
  10579. drawData.offset += (intersections[key].resolved) * drawData.width - (0.5*drawData.width * (intersections[key].amount+1));
  10580. if (group.options.barChart.align == 'left') {drawData.offset -= 0.5*drawData.width;}
  10581. else if (group.options.barChart.align == 'right') {drawData.offset += 0.5*drawData.width;}
  10582. }
  10583. }
  10584. 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);
  10585. // draw points
  10586. if (group.options.drawPoints.enabled == true) {
  10587. DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, group, this.svgElements, this.svg);
  10588. }
  10589. }
  10590. };
  10591. /**
  10592. * Fill the intersections object with counters of how many datapoints share the same x coordinates
  10593. * @param intersections
  10594. * @param combinedData
  10595. * @private
  10596. */
  10597. LineGraph.prototype._getDataIntersections = function (intersections, combinedData) {
  10598. // get intersections
  10599. var coreDistance;
  10600. for (var i = 0; i < combinedData.length; i++) {
  10601. if (i + 1 < combinedData.length) {
  10602. coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x);
  10603. }
  10604. if (i > 0) {
  10605. coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x));
  10606. }
  10607. if (coreDistance == 0) {
  10608. if (intersections[combinedData[i].x] === undefined) {
  10609. intersections[combinedData[i].x] = {amount: 0, resolved: 0, accumulated: 0};
  10610. }
  10611. intersections[combinedData[i].x].amount += 1;
  10612. }
  10613. }
  10614. };
  10615. /**
  10616. * Get the width and offset for bargraphs based on the coredistance between datapoints
  10617. *
  10618. * @param coreDistance
  10619. * @param group
  10620. * @param minWidth
  10621. * @returns {{width: Number, offset: Number}}
  10622. * @private
  10623. */
  10624. LineGraph.prototype._getSafeDrawData = function (coreDistance, group, minWidth) {
  10625. var width, offset;
  10626. if (coreDistance < group.options.barChart.width && coreDistance > 0) {
  10627. width = coreDistance < minWidth ? minWidth : coreDistance;
  10628. offset = 0; // recalculate offset with the new width;
  10629. if (group.options.barChart.align == 'left') {
  10630. offset -= 0.5 * coreDistance;
  10631. }
  10632. else if (group.options.barChart.align == 'right') {
  10633. offset += 0.5 * coreDistance;
  10634. }
  10635. }
  10636. else {
  10637. // default settings
  10638. width = group.options.barChart.width;
  10639. offset = 0;
  10640. if (group.options.barChart.align == 'left') {
  10641. offset -= 0.5 * group.options.barChart.width;
  10642. }
  10643. else if (group.options.barChart.align == 'right') {
  10644. offset += 0.5 * group.options.barChart.width;
  10645. }
  10646. }
  10647. return {width: width, offset: offset};
  10648. };
  10649. /**
  10650. * draw a line graph
  10651. *
  10652. * @param dataset
  10653. * @param group
  10654. */
  10655. LineGraph.prototype._drawLineGraph = function (dataset, group) {
  10656. if (dataset != null) {
  10657. if (dataset.length > 0) {
  10658. var path, d;
  10659. var svgHeight = Number(this.svg.style.height.replace("px",""));
  10660. path = DOMutil.getSVGElement('path', this.svgElements, this.svg);
  10661. path.setAttributeNS(null, "class", group.className);
  10662. // construct path from dataset
  10663. if (group.options.catmullRom.enabled == true) {
  10664. d = this._catmullRom(dataset, group);
  10665. }
  10666. else {
  10667. d = this._linear(dataset);
  10668. }
  10669. // append with points for fill and finalize the path
  10670. if (group.options.shaded.enabled == true) {
  10671. var fillPath = DOMutil.getSVGElement('path',this.svgElements, this.svg);
  10672. var dFill;
  10673. if (group.options.shaded.orientation == 'top') {
  10674. dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0;
  10675. }
  10676. else {
  10677. dFill = "M" + dataset[0].x + "," + svgHeight + " " + d + "L" + dataset[dataset.length - 1].x + "," + svgHeight;
  10678. }
  10679. fillPath.setAttributeNS(null, "class", group.className + " fill");
  10680. fillPath.setAttributeNS(null, "d", dFill);
  10681. }
  10682. // copy properties to path for drawing.
  10683. path.setAttributeNS(null, "d", "M" + d);
  10684. // draw points
  10685. if (group.options.drawPoints.enabled == true) {
  10686. this._drawPoints(dataset, group, this.svgElements, this.svg);
  10687. }
  10688. }
  10689. }
  10690. };
  10691. /**
  10692. * draw the data points
  10693. *
  10694. * @param {Array} dataset
  10695. * @param {Object} JSONcontainer
  10696. * @param {Object} svg | SVG DOM element
  10697. * @param {GraphGroup} group
  10698. * @param {Number} [offset]
  10699. */
  10700. LineGraph.prototype._drawPoints = function (dataset, group, JSONcontainer, svg, offset) {
  10701. if (offset === undefined) {offset = 0;}
  10702. for (var i = 0; i < dataset.length; i++) {
  10703. DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, group, JSONcontainer, svg);
  10704. }
  10705. };
  10706. /**
  10707. * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
  10708. * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
  10709. * the yAxis.
  10710. *
  10711. * @param datapoints
  10712. * @returns {Array}
  10713. * @private
  10714. */
  10715. LineGraph.prototype._convertXcoordinates = function (datapoints) {
  10716. var extractedData = [];
  10717. var xValue, yValue;
  10718. var toScreen = this.body.util.toScreen;
  10719. for (var i = 0; i < datapoints.length; i++) {
  10720. xValue = toScreen(datapoints[i].x) + this.width - 1;
  10721. yValue = datapoints[i].y;
  10722. extractedData.push({x: xValue, y: yValue});
  10723. }
  10724. return extractedData;
  10725. };
  10726. /**
  10727. * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
  10728. * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
  10729. * the yAxis.
  10730. *
  10731. * @param datapoints
  10732. * @returns {Array}
  10733. * @private
  10734. */
  10735. LineGraph.prototype._convertYcoordinates = function (datapoints, group) {
  10736. var extractedData = [];
  10737. var xValue, yValue;
  10738. var toScreen = this.body.util.toScreen;
  10739. var axis = this.yAxisLeft;
  10740. var svgHeight = Number(this.svg.style.height.replace("px",""));
  10741. if (group.options.yAxisOrientation == 'right') {
  10742. axis = this.yAxisRight;
  10743. }
  10744. for (var i = 0; i < datapoints.length; i++) {
  10745. xValue = toScreen(datapoints[i].x) + this.width - 1;
  10746. yValue = Math.round(axis.convertValue(datapoints[i].y));
  10747. extractedData.push({x: xValue, y: yValue});
  10748. }
  10749. group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0)));
  10750. return extractedData;
  10751. };
  10752. /**
  10753. * This uses an uniform parametrization of the CatmullRom algorithm:
  10754. * "On the Parameterization of Catmull-Rom Curves" by Cem Yuksel et al.
  10755. * @param data
  10756. * @returns {string}
  10757. * @private
  10758. */
  10759. LineGraph.prototype._catmullRomUniform = function(data) {
  10760. // catmull rom
  10761. var p0, p1, p2, p3, bp1, bp2;
  10762. var d = Math.round(data[0].x) + "," + Math.round(data[0].y) + " ";
  10763. var normalization = 1/6;
  10764. var length = data.length;
  10765. for (var i = 0; i < length - 1; i++) {
  10766. p0 = (i == 0) ? data[0] : data[i-1];
  10767. p1 = data[i];
  10768. p2 = data[i+1];
  10769. p3 = (i + 2 < length) ? data[i+2] : p2;
  10770. // Catmull-Rom to Cubic Bezier conversion matrix
  10771. // 0 1 0 0
  10772. // -1/6 1 1/6 0
  10773. // 0 1/6 1 -1/6
  10774. // 0 0 1 0
  10775. // bp0 = { x: p1.x, y: p1.y };
  10776. bp1 = { x: ((-p0.x + 6*p1.x + p2.x) *normalization), y: ((-p0.y + 6*p1.y + p2.y) *normalization)};
  10777. bp2 = { x: (( p1.x + 6*p2.x - p3.x) *normalization), y: (( p1.y + 6*p2.y - p3.y) *normalization)};
  10778. // bp0 = { x: p2.x, y: p2.y };
  10779. d += "C" +
  10780. bp1.x + "," +
  10781. bp1.y + " " +
  10782. bp2.x + "," +
  10783. bp2.y + " " +
  10784. p2.x + "," +
  10785. p2.y + " ";
  10786. }
  10787. return d;
  10788. };
  10789. /**
  10790. * This uses either the chordal or centripetal parameterization of the catmull-rom algorithm.
  10791. * By default, the centripetal parameterization is used because this gives the nicest results.
  10792. * These parameterizations are relatively heavy because the distance between 4 points have to be calculated.
  10793. *
  10794. * One optimization can be used to reuse distances since this is a sliding window approach.
  10795. * @param data
  10796. * @returns {string}
  10797. * @private
  10798. */
  10799. LineGraph.prototype._catmullRom = function(data, group) {
  10800. var alpha = group.options.catmullRom.alpha;
  10801. if (alpha == 0 || alpha === undefined) {
  10802. return this._catmullRomUniform(data);
  10803. }
  10804. else {
  10805. var p0, p1, p2, p3, bp1, bp2, d1,d2,d3, A, B, N, M;
  10806. var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA;
  10807. var d = Math.round(data[0].x) + "," + Math.round(data[0].y) + " ";
  10808. var length = data.length;
  10809. for (var i = 0; i < length - 1; i++) {
  10810. p0 = (i == 0) ? data[0] : data[i-1];
  10811. p1 = data[i];
  10812. p2 = data[i+1];
  10813. p3 = (i + 2 < length) ? data[i+2] : p2;
  10814. d1 = Math.sqrt(Math.pow(p0.x - p1.x,2) + Math.pow(p0.y - p1.y,2));
  10815. d2 = Math.sqrt(Math.pow(p1.x - p2.x,2) + Math.pow(p1.y - p2.y,2));
  10816. d3 = Math.sqrt(Math.pow(p2.x - p3.x,2) + Math.pow(p2.y - p3.y,2));
  10817. // Catmull-Rom to Cubic Bezier conversion matrix
  10818. //
  10819. // A = 2d1^2a + 3d1^a * d2^a + d3^2a
  10820. // B = 2d3^2a + 3d3^a * d2^a + d2^2a
  10821. //
  10822. // [ 0 1 0 0 ]
  10823. // [ -d2^2a/N A/N d1^2a/N 0 ]
  10824. // [ 0 d3^2a/M B/M -d2^2a/M ]
  10825. // [ 0 0 1 0 ]
  10826. // [ 0 1 0 0 ]
  10827. // [ -d2pow2a/N A/N d1pow2a/N 0 ]
  10828. // [ 0 d3pow2a/M B/M -d2pow2a/M ]
  10829. // [ 0 0 1 0 ]
  10830. d3powA = Math.pow(d3, alpha);
  10831. d3pow2A = Math.pow(d3,2*alpha);
  10832. d2powA = Math.pow(d2, alpha);
  10833. d2pow2A = Math.pow(d2,2*alpha);
  10834. d1powA = Math.pow(d1, alpha);
  10835. d1pow2A = Math.pow(d1,2*alpha);
  10836. A = 2*d1pow2A + 3*d1powA * d2powA + d2pow2A;
  10837. B = 2*d3pow2A + 3*d3powA * d2powA + d2pow2A;
  10838. N = 3*d1powA * (d1powA + d2powA);
  10839. if (N > 0) {N = 1 / N;}
  10840. M = 3*d3powA * (d3powA + d2powA);
  10841. if (M > 0) {M = 1 / M;}
  10842. bp1 = { x: ((-d2pow2A * p0.x + A*p1.x + d1pow2A * p2.x) * N),
  10843. y: ((-d2pow2A * p0.y + A*p1.y + d1pow2A * p2.y) * N)};
  10844. bp2 = { x: (( d3pow2A * p1.x + B*p2.x - d2pow2A * p3.x) * M),
  10845. y: (( d3pow2A * p1.y + B*p2.y - d2pow2A * p3.y) * M)};
  10846. if (bp1.x == 0 && bp1.y == 0) {bp1 = p1;}
  10847. if (bp2.x == 0 && bp2.y == 0) {bp2 = p2;}
  10848. d += "C" +
  10849. bp1.x + "," +
  10850. bp1.y + " " +
  10851. bp2.x + "," +
  10852. bp2.y + " " +
  10853. p2.x + "," +
  10854. p2.y + " ";
  10855. }
  10856. return d;
  10857. }
  10858. };
  10859. /**
  10860. * this generates the SVG path for a linear drawing between datapoints.
  10861. * @param data
  10862. * @returns {string}
  10863. * @private
  10864. */
  10865. LineGraph.prototype._linear = function(data) {
  10866. // linear
  10867. var d = "";
  10868. for (var i = 0; i < data.length; i++) {
  10869. if (i == 0) {
  10870. d += data[i].x + "," + data[i].y;
  10871. }
  10872. else {
  10873. d += " " + data[i].x + "," + data[i].y;
  10874. }
  10875. }
  10876. return d;
  10877. };
  10878. module.exports = LineGraph;
  10879. /***/ },
  10880. /* 27 */
  10881. /***/ function(module, exports, __webpack_require__) {
  10882. var util = __webpack_require__(1);
  10883. var Component = __webpack_require__(18);
  10884. var TimeStep = __webpack_require__(17);
  10885. var moment = __webpack_require__(40);
  10886. /**
  10887. * A horizontal time axis
  10888. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
  10889. * @param {Object} [options] See TimeAxis.setOptions for the available
  10890. * options.
  10891. * @constructor TimeAxis
  10892. * @extends Component
  10893. */
  10894. function TimeAxis (body, options) {
  10895. this.dom = {
  10896. foreground: null,
  10897. majorLines: [],
  10898. majorTexts: [],
  10899. minorLines: [],
  10900. minorTexts: [],
  10901. redundant: {
  10902. majorLines: [],
  10903. majorTexts: [],
  10904. minorLines: [],
  10905. minorTexts: []
  10906. }
  10907. };
  10908. this.props = {
  10909. range: {
  10910. start: 0,
  10911. end: 0,
  10912. minimumStep: 0
  10913. },
  10914. lineTop: 0
  10915. };
  10916. this.defaultOptions = {
  10917. orientation: 'bottom', // supported: 'top', 'bottom'
  10918. // TODO: implement timeaxis orientations 'left' and 'right'
  10919. showMinorLabels: true,
  10920. showMajorLabels: true
  10921. };
  10922. this.options = util.extend({}, this.defaultOptions);
  10923. this.body = body;
  10924. // create the HTML DOM
  10925. this._create();
  10926. this.setOptions(options);
  10927. }
  10928. TimeAxis.prototype = new Component();
  10929. /**
  10930. * Set options for the TimeAxis.
  10931. * Parameters will be merged in current options.
  10932. * @param {Object} options Available options:
  10933. * {string} [orientation]
  10934. * {boolean} [showMinorLabels]
  10935. * {boolean} [showMajorLabels]
  10936. */
  10937. TimeAxis.prototype.setOptions = function(options) {
  10938. if (options) {
  10939. // copy all options that we know
  10940. util.selectiveExtend(['orientation', 'showMinorLabels', 'showMajorLabels'], this.options, options);
  10941. // apply locale to moment.js
  10942. // TODO: not so nice, this is applied globally to moment.js
  10943. if ('locale' in options) {
  10944. if (typeof moment.locale === 'function') {
  10945. // moment.js 2.8.1+
  10946. moment.locale(options.locale);
  10947. }
  10948. else {
  10949. moment.lang(options.locale);
  10950. }
  10951. }
  10952. }
  10953. };
  10954. /**
  10955. * Create the HTML DOM for the TimeAxis
  10956. */
  10957. TimeAxis.prototype._create = function() {
  10958. this.dom.foreground = document.createElement('div');
  10959. this.dom.background = document.createElement('div');
  10960. this.dom.foreground.className = 'timeaxis foreground';
  10961. this.dom.background.className = 'timeaxis background';
  10962. };
  10963. /**
  10964. * Destroy the TimeAxis
  10965. */
  10966. TimeAxis.prototype.destroy = function() {
  10967. // remove from DOM
  10968. if (this.dom.foreground.parentNode) {
  10969. this.dom.foreground.parentNode.removeChild(this.dom.foreground);
  10970. }
  10971. if (this.dom.background.parentNode) {
  10972. this.dom.background.parentNode.removeChild(this.dom.background);
  10973. }
  10974. this.body = null;
  10975. };
  10976. /**
  10977. * Repaint the component
  10978. * @return {boolean} Returns true if the component is resized
  10979. */
  10980. TimeAxis.prototype.redraw = function () {
  10981. var options = this.options,
  10982. props = this.props,
  10983. foreground = this.dom.foreground,
  10984. background = this.dom.background;
  10985. // determine the correct parent DOM element (depending on option orientation)
  10986. var parent = (options.orientation == 'top') ? this.body.dom.top : this.body.dom.bottom;
  10987. var parentChanged = (foreground.parentNode !== parent);
  10988. // calculate character width and height
  10989. this._calculateCharSize();
  10990. // TODO: recalculate sizes only needed when parent is resized or options is changed
  10991. var orientation = this.options.orientation,
  10992. showMinorLabels = this.options.showMinorLabels,
  10993. showMajorLabels = this.options.showMajorLabels;
  10994. // determine the width and height of the elemens for the axis
  10995. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  10996. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  10997. props.height = props.minorLabelHeight + props.majorLabelHeight;
  10998. props.width = foreground.offsetWidth;
  10999. props.minorLineHeight = this.body.domProps.root.height - props.majorLabelHeight -
  11000. (options.orientation == 'top' ? this.body.domProps.bottom.height : this.body.domProps.top.height);
  11001. props.minorLineWidth = 1; // TODO: really calculate width
  11002. props.majorLineHeight = props.minorLineHeight + props.majorLabelHeight;
  11003. props.majorLineWidth = 1; // TODO: really calculate width
  11004. // take foreground and background offline while updating (is almost twice as fast)
  11005. var foregroundNextSibling = foreground.nextSibling;
  11006. var backgroundNextSibling = background.nextSibling;
  11007. foreground.parentNode && foreground.parentNode.removeChild(foreground);
  11008. background.parentNode && background.parentNode.removeChild(background);
  11009. foreground.style.height = this.props.height + 'px';
  11010. this._repaintLabels();
  11011. // put DOM online again (at the same place)
  11012. if (foregroundNextSibling) {
  11013. parent.insertBefore(foreground, foregroundNextSibling);
  11014. }
  11015. else {
  11016. parent.appendChild(foreground)
  11017. }
  11018. if (backgroundNextSibling) {
  11019. this.body.dom.backgroundVertical.insertBefore(background, backgroundNextSibling);
  11020. }
  11021. else {
  11022. this.body.dom.backgroundVertical.appendChild(background)
  11023. }
  11024. return this._isResized() || parentChanged;
  11025. };
  11026. /**
  11027. * Repaint major and minor text labels and vertical grid lines
  11028. * @private
  11029. */
  11030. TimeAxis.prototype._repaintLabels = function () {
  11031. var orientation = this.options.orientation;
  11032. // calculate range and step (step such that we have space for 7 characters per label)
  11033. var start = util.convert(this.body.range.start, 'Number'),
  11034. end = util.convert(this.body.range.end, 'Number'),
  11035. minimumStep = this.body.util.toTime((this.props.minorCharWidth || 10) * 7).valueOf()
  11036. -this.body.util.toTime(0).valueOf();
  11037. var step = new TimeStep(new Date(start), new Date(end), minimumStep);
  11038. this.step = step;
  11039. // Move all DOM elements to a "redundant" list, where they
  11040. // can be picked for re-use, and clear the lists with lines and texts.
  11041. // At the end of the function _repaintLabels, left over elements will be cleaned up
  11042. var dom = this.dom;
  11043. dom.redundant.majorLines = dom.majorLines;
  11044. dom.redundant.majorTexts = dom.majorTexts;
  11045. dom.redundant.minorLines = dom.minorLines;
  11046. dom.redundant.minorTexts = dom.minorTexts;
  11047. dom.majorLines = [];
  11048. dom.majorTexts = [];
  11049. dom.minorLines = [];
  11050. dom.minorTexts = [];
  11051. step.first();
  11052. var xFirstMajorLabel = undefined;
  11053. var max = 0;
  11054. while (step.hasNext() && max < 1000) {
  11055. max++;
  11056. var cur = step.getCurrent(),
  11057. x = this.body.util.toScreen(cur),
  11058. isMajor = step.isMajor();
  11059. // TODO: lines must have a width, such that we can create css backgrounds
  11060. if (this.options.showMinorLabels) {
  11061. this._repaintMinorText(x, step.getLabelMinor(), orientation);
  11062. }
  11063. if (isMajor && this.options.showMajorLabels) {
  11064. if (x > 0) {
  11065. if (xFirstMajorLabel == undefined) {
  11066. xFirstMajorLabel = x;
  11067. }
  11068. this._repaintMajorText(x, step.getLabelMajor(), orientation);
  11069. }
  11070. this._repaintMajorLine(x, orientation);
  11071. }
  11072. else {
  11073. this._repaintMinorLine(x, orientation);
  11074. }
  11075. step.next();
  11076. }
  11077. // create a major label on the left when needed
  11078. if (this.options.showMajorLabels) {
  11079. var leftTime = this.body.util.toTime(0),
  11080. leftText = step.getLabelMajor(leftTime),
  11081. widthText = leftText.length * (this.props.majorCharWidth || 10) + 10; // upper bound estimation
  11082. if (xFirstMajorLabel == undefined || widthText < xFirstMajorLabel) {
  11083. this._repaintMajorText(0, leftText, orientation);
  11084. }
  11085. }
  11086. // Cleanup leftover DOM elements from the redundant list
  11087. util.forEach(this.dom.redundant, function (arr) {
  11088. while (arr.length) {
  11089. var elem = arr.pop();
  11090. if (elem && elem.parentNode) {
  11091. elem.parentNode.removeChild(elem);
  11092. }
  11093. }
  11094. });
  11095. };
  11096. /**
  11097. * Create a minor label for the axis at position x
  11098. * @param {Number} x
  11099. * @param {String} text
  11100. * @param {String} orientation "top" or "bottom" (default)
  11101. * @private
  11102. */
  11103. TimeAxis.prototype._repaintMinorText = function (x, text, orientation) {
  11104. // reuse redundant label
  11105. var label = this.dom.redundant.minorTexts.shift();
  11106. if (!label) {
  11107. // create new label
  11108. var content = document.createTextNode('');
  11109. label = document.createElement('div');
  11110. label.appendChild(content);
  11111. label.className = 'text minor';
  11112. this.dom.foreground.appendChild(label);
  11113. }
  11114. this.dom.minorTexts.push(label);
  11115. label.childNodes[0].nodeValue = text;
  11116. label.style.top = (orientation == 'top') ? (this.props.majorLabelHeight + 'px') : '0';
  11117. label.style.left = x + 'px';
  11118. //label.title = title; // TODO: this is a heavy operation
  11119. };
  11120. /**
  11121. * Create a Major label for the axis at position x
  11122. * @param {Number} x
  11123. * @param {String} text
  11124. * @param {String} orientation "top" or "bottom" (default)
  11125. * @private
  11126. */
  11127. TimeAxis.prototype._repaintMajorText = function (x, text, orientation) {
  11128. // reuse redundant label
  11129. var label = this.dom.redundant.majorTexts.shift();
  11130. if (!label) {
  11131. // create label
  11132. var content = document.createTextNode(text);
  11133. label = document.createElement('div');
  11134. label.className = 'text major';
  11135. label.appendChild(content);
  11136. this.dom.foreground.appendChild(label);
  11137. }
  11138. this.dom.majorTexts.push(label);
  11139. label.childNodes[0].nodeValue = text;
  11140. //label.title = title; // TODO: this is a heavy operation
  11141. label.style.top = (orientation == 'top') ? '0' : (this.props.minorLabelHeight + 'px');
  11142. label.style.left = x + 'px';
  11143. };
  11144. /**
  11145. * Create a minor line for the axis at position x
  11146. * @param {Number} x
  11147. * @param {String} orientation "top" or "bottom" (default)
  11148. * @private
  11149. */
  11150. TimeAxis.prototype._repaintMinorLine = function (x, orientation) {
  11151. // reuse redundant line
  11152. var line = this.dom.redundant.minorLines.shift();
  11153. if (!line) {
  11154. // create vertical line
  11155. line = document.createElement('div');
  11156. line.className = 'grid vertical minor';
  11157. this.dom.background.appendChild(line);
  11158. }
  11159. this.dom.minorLines.push(line);
  11160. var props = this.props;
  11161. if (orientation == 'top') {
  11162. line.style.top = props.majorLabelHeight + 'px';
  11163. }
  11164. else {
  11165. line.style.top = this.body.domProps.top.height + 'px';
  11166. }
  11167. line.style.height = props.minorLineHeight + 'px';
  11168. line.style.left = (x - props.minorLineWidth / 2) + 'px';
  11169. };
  11170. /**
  11171. * Create a Major line for the axis at position x
  11172. * @param {Number} x
  11173. * @param {String} orientation "top" or "bottom" (default)
  11174. * @private
  11175. */
  11176. TimeAxis.prototype._repaintMajorLine = function (x, orientation) {
  11177. // reuse redundant line
  11178. var line = this.dom.redundant.majorLines.shift();
  11179. if (!line) {
  11180. // create vertical line
  11181. line = document.createElement('DIV');
  11182. line.className = 'grid vertical major';
  11183. this.dom.background.appendChild(line);
  11184. }
  11185. this.dom.majorLines.push(line);
  11186. var props = this.props;
  11187. if (orientation == 'top') {
  11188. line.style.top = '0';
  11189. }
  11190. else {
  11191. line.style.top = this.body.domProps.top.height + 'px';
  11192. }
  11193. line.style.left = (x - props.majorLineWidth / 2) + 'px';
  11194. line.style.height = props.majorLineHeight + 'px';
  11195. };
  11196. /**
  11197. * Determine the size of text on the axis (both major and minor axis).
  11198. * The size is calculated only once and then cached in this.props.
  11199. * @private
  11200. */
  11201. TimeAxis.prototype._calculateCharSize = function () {
  11202. // Note: We calculate char size with every redraw. Size may change, for
  11203. // example when any of the timelines parents had display:none for example.
  11204. // determine the char width and height on the minor axis
  11205. if (!this.dom.measureCharMinor) {
  11206. this.dom.measureCharMinor = document.createElement('DIV');
  11207. this.dom.measureCharMinor.className = 'text minor measure';
  11208. this.dom.measureCharMinor.style.position = 'absolute';
  11209. this.dom.measureCharMinor.appendChild(document.createTextNode('0'));
  11210. this.dom.foreground.appendChild(this.dom.measureCharMinor);
  11211. }
  11212. this.props.minorCharHeight = this.dom.measureCharMinor.clientHeight;
  11213. this.props.minorCharWidth = this.dom.measureCharMinor.clientWidth;
  11214. // determine the char width and height on the major axis
  11215. if (!this.dom.measureCharMajor) {
  11216. this.dom.measureCharMajor = document.createElement('DIV');
  11217. this.dom.measureCharMajor.className = 'text minor measure';
  11218. this.dom.measureCharMajor.style.position = 'absolute';
  11219. this.dom.measureCharMajor.appendChild(document.createTextNode('0'));
  11220. this.dom.foreground.appendChild(this.dom.measureCharMajor);
  11221. }
  11222. this.props.majorCharHeight = this.dom.measureCharMajor.clientHeight;
  11223. this.props.majorCharWidth = this.dom.measureCharMajor.clientWidth;
  11224. };
  11225. /**
  11226. * Snap a date to a rounded value.
  11227. * The snap intervals are dependent on the current scale and step.
  11228. * @param {Date} date the date to be snapped.
  11229. * @return {Date} snappedDate
  11230. */
  11231. TimeAxis.prototype.snap = function(date) {
  11232. return this.step.snap(date);
  11233. };
  11234. module.exports = TimeAxis;
  11235. /***/ },
  11236. /* 28 */
  11237. /***/ function(module, exports, __webpack_require__) {
  11238. var Hammer = __webpack_require__(41);
  11239. /**
  11240. * @constructor Item
  11241. * @param {Object} data Object containing (optional) parameters type,
  11242. * start, end, content, group, className.
  11243. * @param {{toScreen: function, toTime: function}} conversion
  11244. * Conversion functions from time to screen and vice versa
  11245. * @param {Object} options Configuration options
  11246. * // TODO: describe available options
  11247. */
  11248. function Item (data, conversion, options) {
  11249. this.id = null;
  11250. this.parent = null;
  11251. this.data = data;
  11252. this.dom = null;
  11253. this.conversion = conversion || {};
  11254. this.options = options || {};
  11255. this.selected = false;
  11256. this.displayed = false;
  11257. this.dirty = true;
  11258. this.top = null;
  11259. this.left = null;
  11260. this.width = null;
  11261. this.height = null;
  11262. }
  11263. /**
  11264. * Select current item
  11265. */
  11266. Item.prototype.select = function() {
  11267. this.selected = true;
  11268. if (this.displayed) this.redraw();
  11269. };
  11270. /**
  11271. * Unselect current item
  11272. */
  11273. Item.prototype.unselect = function() {
  11274. this.selected = false;
  11275. if (this.displayed) this.redraw();
  11276. };
  11277. /**
  11278. * Set a parent for the item
  11279. * @param {ItemSet | Group} parent
  11280. */
  11281. Item.prototype.setParent = function(parent) {
  11282. if (this.displayed) {
  11283. this.hide();
  11284. this.parent = parent;
  11285. if (this.parent) {
  11286. this.show();
  11287. }
  11288. }
  11289. else {
  11290. this.parent = parent;
  11291. }
  11292. };
  11293. /**
  11294. * Check whether this item is visible inside given range
  11295. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  11296. * @returns {boolean} True if visible
  11297. */
  11298. Item.prototype.isVisible = function(range) {
  11299. // Should be implemented by Item implementations
  11300. return false;
  11301. };
  11302. /**
  11303. * Show the Item in the DOM (when not already visible)
  11304. * @return {Boolean} changed
  11305. */
  11306. Item.prototype.show = function() {
  11307. return false;
  11308. };
  11309. /**
  11310. * Hide the Item from the DOM (when visible)
  11311. * @return {Boolean} changed
  11312. */
  11313. Item.prototype.hide = function() {
  11314. return false;
  11315. };
  11316. /**
  11317. * Repaint the item
  11318. */
  11319. Item.prototype.redraw = function() {
  11320. // should be implemented by the item
  11321. };
  11322. /**
  11323. * Reposition the Item horizontally
  11324. */
  11325. Item.prototype.repositionX = function() {
  11326. // should be implemented by the item
  11327. };
  11328. /**
  11329. * Reposition the Item vertically
  11330. */
  11331. Item.prototype.repositionY = function() {
  11332. // should be implemented by the item
  11333. };
  11334. /**
  11335. * Repaint a delete button on the top right of the item when the item is selected
  11336. * @param {HTMLElement} anchor
  11337. * @protected
  11338. */
  11339. Item.prototype._repaintDeleteButton = function (anchor) {
  11340. if (this.selected && this.options.editable.remove && !this.dom.deleteButton) {
  11341. // create and show button
  11342. var me = this;
  11343. var deleteButton = document.createElement('div');
  11344. deleteButton.className = 'delete';
  11345. deleteButton.title = 'Delete this item';
  11346. Hammer(deleteButton, {
  11347. preventDefault: true
  11348. }).on('tap', function (event) {
  11349. me.parent.removeFromDataSet(me);
  11350. event.stopPropagation();
  11351. });
  11352. anchor.appendChild(deleteButton);
  11353. this.dom.deleteButton = deleteButton;
  11354. }
  11355. else if (!this.selected && this.dom.deleteButton) {
  11356. // remove button
  11357. if (this.dom.deleteButton.parentNode) {
  11358. this.dom.deleteButton.parentNode.removeChild(this.dom.deleteButton);
  11359. }
  11360. this.dom.deleteButton = null;
  11361. }
  11362. };
  11363. module.exports = Item;
  11364. /***/ },
  11365. /* 29 */
  11366. /***/ function(module, exports, __webpack_require__) {
  11367. var Item = __webpack_require__(28);
  11368. /**
  11369. * @constructor ItemBox
  11370. * @extends Item
  11371. * @param {Object} data Object containing parameters start
  11372. * content, className.
  11373. * @param {{toScreen: function, toTime: function}} conversion
  11374. * Conversion functions from time to screen and vice versa
  11375. * @param {Object} [options] Configuration options
  11376. * // TODO: describe available options
  11377. */
  11378. function ItemBox (data, conversion, options) {
  11379. this.props = {
  11380. dot: {
  11381. width: 0,
  11382. height: 0
  11383. },
  11384. line: {
  11385. width: 0,
  11386. height: 0
  11387. }
  11388. };
  11389. // validate data
  11390. if (data) {
  11391. if (data.start == undefined) {
  11392. throw new Error('Property "start" missing in item ' + data);
  11393. }
  11394. }
  11395. Item.call(this, data, conversion, options);
  11396. }
  11397. ItemBox.prototype = new Item (null, null, null);
  11398. /**
  11399. * Check whether this item is visible inside given range
  11400. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  11401. * @returns {boolean} True if visible
  11402. */
  11403. ItemBox.prototype.isVisible = function(range) {
  11404. // determine visibility
  11405. // TODO: account for the real width of the item. Right now we just add 1/4 to the window
  11406. var interval = (range.end - range.start) / 4;
  11407. return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
  11408. };
  11409. /**
  11410. * Repaint the item
  11411. */
  11412. ItemBox.prototype.redraw = function() {
  11413. var dom = this.dom;
  11414. if (!dom) {
  11415. // create DOM
  11416. this.dom = {};
  11417. dom = this.dom;
  11418. // create main box
  11419. dom.box = document.createElement('DIV');
  11420. // contents box (inside the background box). used for making margins
  11421. dom.content = document.createElement('DIV');
  11422. dom.content.className = 'content';
  11423. dom.box.appendChild(dom.content);
  11424. // line to axis
  11425. dom.line = document.createElement('DIV');
  11426. dom.line.className = 'line';
  11427. // dot on axis
  11428. dom.dot = document.createElement('DIV');
  11429. dom.dot.className = 'dot';
  11430. // attach this item as attribute
  11431. dom.box['timeline-item'] = this;
  11432. }
  11433. // append DOM to parent DOM
  11434. if (!this.parent) {
  11435. throw new Error('Cannot redraw item: no parent attached');
  11436. }
  11437. if (!dom.box.parentNode) {
  11438. var foreground = this.parent.dom.foreground;
  11439. if (!foreground) throw new Error('Cannot redraw time axis: parent has no foreground container element');
  11440. foreground.appendChild(dom.box);
  11441. }
  11442. if (!dom.line.parentNode) {
  11443. var background = this.parent.dom.background;
  11444. if (!background) throw new Error('Cannot redraw time axis: parent has no background container element');
  11445. background.appendChild(dom.line);
  11446. }
  11447. if (!dom.dot.parentNode) {
  11448. var axis = this.parent.dom.axis;
  11449. if (!background) throw new Error('Cannot redraw time axis: parent has no axis container element');
  11450. axis.appendChild(dom.dot);
  11451. }
  11452. this.displayed = true;
  11453. // update contents
  11454. if (this.data.content != this.content) {
  11455. this.content = this.data.content;
  11456. if (this.content instanceof Element) {
  11457. dom.content.innerHTML = '';
  11458. dom.content.appendChild(this.content);
  11459. }
  11460. else if (this.data.content != undefined) {
  11461. dom.content.innerHTML = this.content;
  11462. }
  11463. else {
  11464. throw new Error('Property "content" missing in item ' + this.data.id);
  11465. }
  11466. this.dirty = true;
  11467. }
  11468. // update title
  11469. if (this.data.title != this.title) {
  11470. dom.box.title = this.data.title;
  11471. this.title = this.data.title;
  11472. }
  11473. // update class
  11474. var className = (this.data.className? ' ' + this.data.className : '') +
  11475. (this.selected ? ' selected' : '');
  11476. if (this.className != className) {
  11477. this.className = className;
  11478. dom.box.className = 'item box' + className;
  11479. dom.line.className = 'item line' + className;
  11480. dom.dot.className = 'item dot' + className;
  11481. this.dirty = true;
  11482. }
  11483. // recalculate size
  11484. if (this.dirty) {
  11485. this.props.dot.height = dom.dot.offsetHeight;
  11486. this.props.dot.width = dom.dot.offsetWidth;
  11487. this.props.line.width = dom.line.offsetWidth;
  11488. this.width = dom.box.offsetWidth;
  11489. this.height = dom.box.offsetHeight;
  11490. this.dirty = false;
  11491. }
  11492. this._repaintDeleteButton(dom.box);
  11493. };
  11494. /**
  11495. * Show the item in the DOM (when not already displayed). The items DOM will
  11496. * be created when needed.
  11497. */
  11498. ItemBox.prototype.show = function() {
  11499. if (!this.displayed) {
  11500. this.redraw();
  11501. }
  11502. };
  11503. /**
  11504. * Hide the item from the DOM (when visible)
  11505. */
  11506. ItemBox.prototype.hide = function() {
  11507. if (this.displayed) {
  11508. var dom = this.dom;
  11509. if (dom.box.parentNode) dom.box.parentNode.removeChild(dom.box);
  11510. if (dom.line.parentNode) dom.line.parentNode.removeChild(dom.line);
  11511. if (dom.dot.parentNode) dom.dot.parentNode.removeChild(dom.dot);
  11512. this.top = null;
  11513. this.left = null;
  11514. this.displayed = false;
  11515. }
  11516. };
  11517. /**
  11518. * Reposition the item horizontally
  11519. * @Override
  11520. */
  11521. ItemBox.prototype.repositionX = function() {
  11522. var start = this.conversion.toScreen(this.data.start);
  11523. var align = this.options.align;
  11524. var left;
  11525. var box = this.dom.box;
  11526. var line = this.dom.line;
  11527. var dot = this.dom.dot;
  11528. // calculate left position of the box
  11529. if (align == 'right') {
  11530. this.left = start - this.width;
  11531. }
  11532. else if (align == 'left') {
  11533. this.left = start;
  11534. }
  11535. else {
  11536. // default or 'center'
  11537. this.left = start - this.width / 2;
  11538. }
  11539. // reposition box
  11540. box.style.left = this.left + 'px';
  11541. // reposition line
  11542. line.style.left = (start - this.props.line.width / 2) + 'px';
  11543. // reposition dot
  11544. dot.style.left = (start - this.props.dot.width / 2) + 'px';
  11545. };
  11546. /**
  11547. * Reposition the item vertically
  11548. * @Override
  11549. */
  11550. ItemBox.prototype.repositionY = function() {
  11551. var orientation = this.options.orientation;
  11552. var box = this.dom.box;
  11553. var line = this.dom.line;
  11554. var dot = this.dom.dot;
  11555. if (orientation == 'top') {
  11556. box.style.top = (this.top || 0) + 'px';
  11557. line.style.top = '0';
  11558. line.style.height = (this.parent.top + this.top + 1) + 'px';
  11559. line.style.bottom = '';
  11560. }
  11561. else { // orientation 'bottom'
  11562. var itemSetHeight = this.parent.itemSet.props.height; // TODO: this is nasty
  11563. var lineHeight = itemSetHeight - this.parent.top - this.parent.height + this.top;
  11564. box.style.top = (this.parent.height - this.top - this.height || 0) + 'px';
  11565. line.style.top = (itemSetHeight - lineHeight) + 'px';
  11566. line.style.bottom = '0';
  11567. }
  11568. dot.style.top = (-this.props.dot.height / 2) + 'px';
  11569. };
  11570. module.exports = ItemBox;
  11571. /***/ },
  11572. /* 30 */
  11573. /***/ function(module, exports, __webpack_require__) {
  11574. var Item = __webpack_require__(28);
  11575. /**
  11576. * @constructor ItemPoint
  11577. * @extends Item
  11578. * @param {Object} data Object containing parameters start
  11579. * content, className.
  11580. * @param {{toScreen: function, toTime: function}} conversion
  11581. * Conversion functions from time to screen and vice versa
  11582. * @param {Object} [options] Configuration options
  11583. * // TODO: describe available options
  11584. */
  11585. function ItemPoint (data, conversion, options) {
  11586. this.props = {
  11587. dot: {
  11588. top: 0,
  11589. width: 0,
  11590. height: 0
  11591. },
  11592. content: {
  11593. height: 0,
  11594. marginLeft: 0
  11595. }
  11596. };
  11597. // validate data
  11598. if (data) {
  11599. if (data.start == undefined) {
  11600. throw new Error('Property "start" missing in item ' + data);
  11601. }
  11602. }
  11603. Item.call(this, data, conversion, options);
  11604. }
  11605. ItemPoint.prototype = new Item (null, null, null);
  11606. /**
  11607. * Check whether this item is visible inside given range
  11608. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  11609. * @returns {boolean} True if visible
  11610. */
  11611. ItemPoint.prototype.isVisible = function(range) {
  11612. // determine visibility
  11613. // TODO: account for the real width of the item. Right now we just add 1/4 to the window
  11614. var interval = (range.end - range.start) / 4;
  11615. return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
  11616. };
  11617. /**
  11618. * Repaint the item
  11619. */
  11620. ItemPoint.prototype.redraw = function() {
  11621. var dom = this.dom;
  11622. if (!dom) {
  11623. // create DOM
  11624. this.dom = {};
  11625. dom = this.dom;
  11626. // background box
  11627. dom.point = document.createElement('div');
  11628. // className is updated in redraw()
  11629. // contents box, right from the dot
  11630. dom.content = document.createElement('div');
  11631. dom.content.className = 'content';
  11632. dom.point.appendChild(dom.content);
  11633. // dot at start
  11634. dom.dot = document.createElement('div');
  11635. dom.point.appendChild(dom.dot);
  11636. // attach this item as attribute
  11637. dom.point['timeline-item'] = this;
  11638. }
  11639. // append DOM to parent DOM
  11640. if (!this.parent) {
  11641. throw new Error('Cannot redraw item: no parent attached');
  11642. }
  11643. if (!dom.point.parentNode) {
  11644. var foreground = this.parent.dom.foreground;
  11645. if (!foreground) {
  11646. throw new Error('Cannot redraw time axis: parent has no foreground container element');
  11647. }
  11648. foreground.appendChild(dom.point);
  11649. }
  11650. this.displayed = true;
  11651. // update contents
  11652. if (this.data.content != this.content) {
  11653. this.content = this.data.content;
  11654. if (this.content instanceof Element) {
  11655. dom.content.innerHTML = '';
  11656. dom.content.appendChild(this.content);
  11657. }
  11658. else if (this.data.content != undefined) {
  11659. dom.content.innerHTML = this.content;
  11660. }
  11661. else {
  11662. throw new Error('Property "content" missing in item ' + this.data.id);
  11663. }
  11664. this.dirty = true;
  11665. }
  11666. // update title
  11667. if (this.data.title != this.title) {
  11668. dom.point.title = this.data.title;
  11669. this.title = this.data.title;
  11670. }
  11671. // update class
  11672. var className = (this.data.className? ' ' + this.data.className : '') +
  11673. (this.selected ? ' selected' : '');
  11674. if (this.className != className) {
  11675. this.className = className;
  11676. dom.point.className = 'item point' + className;
  11677. dom.dot.className = 'item dot' + className;
  11678. this.dirty = true;
  11679. }
  11680. // recalculate size
  11681. if (this.dirty) {
  11682. this.width = dom.point.offsetWidth;
  11683. this.height = dom.point.offsetHeight;
  11684. this.props.dot.width = dom.dot.offsetWidth;
  11685. this.props.dot.height = dom.dot.offsetHeight;
  11686. this.props.content.height = dom.content.offsetHeight;
  11687. // resize contents
  11688. dom.content.style.marginLeft = 2 * this.props.dot.width + 'px';
  11689. //dom.content.style.marginRight = ... + 'px'; // TODO: margin right
  11690. dom.dot.style.top = ((this.height - this.props.dot.height) / 2) + 'px';
  11691. dom.dot.style.left = (this.props.dot.width / 2) + 'px';
  11692. this.dirty = false;
  11693. }
  11694. this._repaintDeleteButton(dom.point);
  11695. };
  11696. /**
  11697. * Show the item in the DOM (when not already visible). The items DOM will
  11698. * be created when needed.
  11699. */
  11700. ItemPoint.prototype.show = function() {
  11701. if (!this.displayed) {
  11702. this.redraw();
  11703. }
  11704. };
  11705. /**
  11706. * Hide the item from the DOM (when visible)
  11707. */
  11708. ItemPoint.prototype.hide = function() {
  11709. if (this.displayed) {
  11710. if (this.dom.point.parentNode) {
  11711. this.dom.point.parentNode.removeChild(this.dom.point);
  11712. }
  11713. this.top = null;
  11714. this.left = null;
  11715. this.displayed = false;
  11716. }
  11717. };
  11718. /**
  11719. * Reposition the item horizontally
  11720. * @Override
  11721. */
  11722. ItemPoint.prototype.repositionX = function() {
  11723. var start = this.conversion.toScreen(this.data.start);
  11724. this.left = start - this.props.dot.width;
  11725. // reposition point
  11726. this.dom.point.style.left = this.left + 'px';
  11727. };
  11728. /**
  11729. * Reposition the item vertically
  11730. * @Override
  11731. */
  11732. ItemPoint.prototype.repositionY = function() {
  11733. var orientation = this.options.orientation,
  11734. point = this.dom.point;
  11735. if (orientation == 'top') {
  11736. point.style.top = this.top + 'px';
  11737. }
  11738. else {
  11739. point.style.top = (this.parent.height - this.top - this.height) + 'px';
  11740. }
  11741. };
  11742. module.exports = ItemPoint;
  11743. /***/ },
  11744. /* 31 */
  11745. /***/ function(module, exports, __webpack_require__) {
  11746. var Hammer = __webpack_require__(41);
  11747. var Item = __webpack_require__(28);
  11748. /**
  11749. * @constructor ItemRange
  11750. * @extends Item
  11751. * @param {Object} data Object containing parameters start, end
  11752. * content, className.
  11753. * @param {{toScreen: function, toTime: function}} conversion
  11754. * Conversion functions from time to screen and vice versa
  11755. * @param {Object} [options] Configuration options
  11756. * // TODO: describe options
  11757. */
  11758. function ItemRange (data, conversion, options) {
  11759. this.props = {
  11760. content: {
  11761. width: 0
  11762. }
  11763. };
  11764. this.overflow = false; // if contents can overflow (css styling), this flag is set to true
  11765. // validate data
  11766. if (data) {
  11767. if (data.start == undefined) {
  11768. throw new Error('Property "start" missing in item ' + data.id);
  11769. }
  11770. if (data.end == undefined) {
  11771. throw new Error('Property "end" missing in item ' + data.id);
  11772. }
  11773. }
  11774. Item.call(this, data, conversion, options);
  11775. }
  11776. ItemRange.prototype = new Item (null, null, null);
  11777. ItemRange.prototype.baseClassName = 'item range';
  11778. /**
  11779. * Check whether this item is visible inside given range
  11780. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  11781. * @returns {boolean} True if visible
  11782. */
  11783. ItemRange.prototype.isVisible = function(range) {
  11784. // determine visibility
  11785. return (this.data.start < range.end) && (this.data.end > range.start);
  11786. };
  11787. /**
  11788. * Repaint the item
  11789. */
  11790. ItemRange.prototype.redraw = function() {
  11791. var dom = this.dom;
  11792. if (!dom) {
  11793. // create DOM
  11794. this.dom = {};
  11795. dom = this.dom;
  11796. // background box
  11797. dom.box = document.createElement('div');
  11798. // className is updated in redraw()
  11799. // contents box
  11800. dom.content = document.createElement('div');
  11801. dom.content.className = 'content';
  11802. dom.box.appendChild(dom.content);
  11803. // attach this item as attribute
  11804. dom.box['timeline-item'] = this;
  11805. }
  11806. // append DOM to parent DOM
  11807. if (!this.parent) {
  11808. throw new Error('Cannot redraw item: no parent attached');
  11809. }
  11810. if (!dom.box.parentNode) {
  11811. var foreground = this.parent.dom.foreground;
  11812. if (!foreground) {
  11813. throw new Error('Cannot redraw time axis: parent has no foreground container element');
  11814. }
  11815. foreground.appendChild(dom.box);
  11816. }
  11817. this.displayed = true;
  11818. // update contents
  11819. if (this.data.content != this.content) {
  11820. this.content = this.data.content;
  11821. if (this.content instanceof Element) {
  11822. dom.content.innerHTML = '';
  11823. dom.content.appendChild(this.content);
  11824. }
  11825. else if (this.data.content != undefined) {
  11826. dom.content.innerHTML = this.content;
  11827. }
  11828. else {
  11829. throw new Error('Property "content" missing in item ' + this.data.id);
  11830. }
  11831. this.dirty = true;
  11832. }
  11833. // update title
  11834. if (this.data.title != this.title) {
  11835. dom.box.title = this.data.title;
  11836. this.title = this.data.title;
  11837. }
  11838. // update class
  11839. var className = (this.data.className ? (' ' + this.data.className) : '') +
  11840. (this.selected ? ' selected' : '');
  11841. if (this.className != className) {
  11842. this.className = className;
  11843. dom.box.className = this.baseClassName + className;
  11844. this.dirty = true;
  11845. }
  11846. // recalculate size
  11847. if (this.dirty) {
  11848. // determine from css whether this box has overflow
  11849. this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden';
  11850. this.props.content.width = this.dom.content.offsetWidth;
  11851. this.height = this.dom.box.offsetHeight;
  11852. this.dirty = false;
  11853. }
  11854. this._repaintDeleteButton(dom.box);
  11855. this._repaintDragLeft();
  11856. this._repaintDragRight();
  11857. };
  11858. /**
  11859. * Show the item in the DOM (when not already visible). The items DOM will
  11860. * be created when needed.
  11861. */
  11862. ItemRange.prototype.show = function() {
  11863. if (!this.displayed) {
  11864. this.redraw();
  11865. }
  11866. };
  11867. /**
  11868. * Hide the item from the DOM (when visible)
  11869. * @return {Boolean} changed
  11870. */
  11871. ItemRange.prototype.hide = function() {
  11872. if (this.displayed) {
  11873. var box = this.dom.box;
  11874. if (box.parentNode) {
  11875. box.parentNode.removeChild(box);
  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. ItemRange.prototype.repositionX = function() {
  11887. var parentWidth = this.parent.width;
  11888. var start = this.conversion.toScreen(this.data.start);
  11889. var end = this.conversion.toScreen(this.data.end);
  11890. var contentLeft;
  11891. var contentWidth;
  11892. // limit the width of the this, as browsers cannot draw very wide divs
  11893. if (start < -parentWidth) {
  11894. start = -parentWidth;
  11895. }
  11896. if (end > 2 * parentWidth) {
  11897. end = 2 * parentWidth;
  11898. }
  11899. var boxWidth = Math.max(end - start, 1);
  11900. if (this.overflow) {
  11901. this.left = start;
  11902. this.width = boxWidth + this.props.content.width;
  11903. contentWidth = this.props.content.width;
  11904. // Note: The calculation of width is an optimistic calculation, giving
  11905. // a width which will not change when moving the Timeline
  11906. // So no re-stacking needed, which is nicer for the eye;
  11907. }
  11908. else {
  11909. this.left = start;
  11910. this.width = boxWidth;
  11911. contentWidth = Math.min(end - start, this.props.content.width);
  11912. }
  11913. this.dom.box.style.left = this.left + 'px';
  11914. this.dom.box.style.width = boxWidth + 'px';
  11915. switch (this.options.align) {
  11916. case 'left':
  11917. this.dom.content.style.left = '0';
  11918. break;
  11919. case 'right':
  11920. this.dom.content.style.left = Math.max((boxWidth - contentWidth - 2 * this.options.padding), 0) + 'px';
  11921. break;
  11922. case 'center':
  11923. this.dom.content.style.left = Math.max((boxWidth - contentWidth - 2 * this.options.padding) / 2, 0) + 'px';
  11924. break;
  11925. default: // 'auto'
  11926. if (this.overflow) {
  11927. // when range exceeds left of the window, position the contents at the left of the visible area
  11928. contentLeft = Math.max(-start, 0);
  11929. }
  11930. else {
  11931. // when range exceeds left of the window, position the contents at the left of the visible area
  11932. if (start < 0) {
  11933. contentLeft = Math.min(-start,
  11934. (end - start - this.props.content.width - 2 * this.options.padding));
  11935. // TODO: remove the need for options.padding. it's terrible.
  11936. }
  11937. else {
  11938. contentLeft = 0;
  11939. }
  11940. }
  11941. this.dom.content.style.left = contentLeft + 'px';
  11942. }
  11943. };
  11944. /**
  11945. * Reposition the item vertically
  11946. * @Override
  11947. */
  11948. ItemRange.prototype.repositionY = function() {
  11949. var orientation = this.options.orientation,
  11950. box = this.dom.box;
  11951. if (orientation == 'top') {
  11952. box.style.top = this.top + 'px';
  11953. }
  11954. else {
  11955. box.style.top = (this.parent.height - this.top - this.height) + 'px';
  11956. }
  11957. };
  11958. /**
  11959. * Repaint a drag area on the left side of the range when the range is selected
  11960. * @protected
  11961. */
  11962. ItemRange.prototype._repaintDragLeft = function () {
  11963. if (this.selected && this.options.editable.updateTime && !this.dom.dragLeft) {
  11964. // create and show drag area
  11965. var dragLeft = document.createElement('div');
  11966. dragLeft.className = 'drag-left';
  11967. dragLeft.dragLeftItem = this;
  11968. // TODO: this should be redundant?
  11969. Hammer(dragLeft, {
  11970. preventDefault: true
  11971. }).on('drag', function () {
  11972. //console.log('drag left')
  11973. });
  11974. this.dom.box.appendChild(dragLeft);
  11975. this.dom.dragLeft = dragLeft;
  11976. }
  11977. else if (!this.selected && this.dom.dragLeft) {
  11978. // delete drag area
  11979. if (this.dom.dragLeft.parentNode) {
  11980. this.dom.dragLeft.parentNode.removeChild(this.dom.dragLeft);
  11981. }
  11982. this.dom.dragLeft = null;
  11983. }
  11984. };
  11985. /**
  11986. * Repaint a drag area on the right side of the range when the range is selected
  11987. * @protected
  11988. */
  11989. ItemRange.prototype._repaintDragRight = function () {
  11990. if (this.selected && this.options.editable.updateTime && !this.dom.dragRight) {
  11991. // create and show drag area
  11992. var dragRight = document.createElement('div');
  11993. dragRight.className = 'drag-right';
  11994. dragRight.dragRightItem = this;
  11995. // TODO: this should be redundant?
  11996. Hammer(dragRight, {
  11997. preventDefault: true
  11998. }).on('drag', function () {
  11999. //console.log('drag right')
  12000. });
  12001. this.dom.box.appendChild(dragRight);
  12002. this.dom.dragRight = dragRight;
  12003. }
  12004. else if (!this.selected && this.dom.dragRight) {
  12005. // delete drag area
  12006. if (this.dom.dragRight.parentNode) {
  12007. this.dom.dragRight.parentNode.removeChild(this.dom.dragRight);
  12008. }
  12009. this.dom.dragRight = null;
  12010. }
  12011. };
  12012. module.exports = ItemRange;
  12013. /***/ },
  12014. /* 32 */
  12015. /***/ function(module, exports, __webpack_require__) {
  12016. var Emitter = __webpack_require__(49);
  12017. var Hammer = __webpack_require__(41);
  12018. var mousetrap = __webpack_require__(50);
  12019. var util = __webpack_require__(1);
  12020. var hammerUtil = __webpack_require__(43);
  12021. var DataSet = __webpack_require__(3);
  12022. var DataView = __webpack_require__(4);
  12023. var dotparser = __webpack_require__(38);
  12024. var gephiParser = __webpack_require__(39);
  12025. var Groups = __webpack_require__(34);
  12026. var Images = __webpack_require__(35);
  12027. var Node = __webpack_require__(36);
  12028. var Edge = __webpack_require__(33);
  12029. var Popup = __webpack_require__(37);
  12030. var MixinLoader = __webpack_require__(47);
  12031. var Activator = __webpack_require__(48);
  12032. var locales = __webpack_require__(45);
  12033. // Load custom shapes into CanvasRenderingContext2D
  12034. __webpack_require__(46);
  12035. /**
  12036. * @constructor Network
  12037. * Create a network visualization, displaying nodes and edges.
  12038. *
  12039. * @param {Element} container The DOM element in which the Network will
  12040. * be created. Normally a div element.
  12041. * @param {Object} data An object containing parameters
  12042. * {Array} nodes
  12043. * {Array} edges
  12044. * @param {Object} options Options
  12045. */
  12046. function Network (container, data, options) {
  12047. if (!(this instanceof Network)) {
  12048. throw new SyntaxError('Constructor must be called with the new operator');
  12049. }
  12050. this._initializeMixinLoaders();
  12051. // create variables and set default values
  12052. this.containerElement = container;
  12053. // render and calculation settings
  12054. this.renderRefreshRate = 60; // hz (fps)
  12055. this.renderTimestep = 1000 / this.renderRefreshRate; // ms -- saves calculation later on
  12056. this.renderTime = 0.5 * this.renderTimestep; // measured time it takes to render a frame
  12057. this.maxPhysicsTicksPerRender = 3; // max amount of physics ticks per render step.
  12058. this.physicsDiscreteStepsize = 0.50; // discrete stepsize of the simulation
  12059. this.initializing = true;
  12060. this.triggerFunctions = {add:null,edit:null,editEdge:null,connect:null,del:null};
  12061. // set constant values
  12062. this.defaultOptions = {
  12063. nodes: {
  12064. mass: 1,
  12065. radiusMin: 10,
  12066. radiusMax: 30,
  12067. radius: 10,
  12068. shape: 'ellipse',
  12069. image: undefined,
  12070. widthMin: 16, // px
  12071. widthMax: 64, // px
  12072. fixed: false,
  12073. fontColor: 'black',
  12074. fontSize: 14, // px
  12075. fontFace: 'verdana',
  12076. level: -1,
  12077. color: {
  12078. border: '#2B7CE9',
  12079. background: '#97C2FC',
  12080. highlight: {
  12081. border: '#2B7CE9',
  12082. background: '#D2E5FF'
  12083. },
  12084. hover: {
  12085. border: '#2B7CE9',
  12086. background: '#D2E5FF'
  12087. }
  12088. },
  12089. borderColor: '#2B7CE9',
  12090. backgroundColor: '#97C2FC',
  12091. highlightColor: '#D2E5FF',
  12092. group: undefined,
  12093. borderWidth: 1
  12094. },
  12095. edges: {
  12096. widthMin: 1,
  12097. widthMax: 15,
  12098. width: 1,
  12099. widthSelectionMultiplier: 2,
  12100. hoverWidth: 1.5,
  12101. style: 'line',
  12102. color: {
  12103. color:'#848484',
  12104. highlight:'#848484',
  12105. hover: '#848484'
  12106. },
  12107. fontColor: '#343434',
  12108. fontSize: 14, // px
  12109. fontFace: 'arial',
  12110. fontFill: 'white',
  12111. arrowScaleFactor: 1,
  12112. dash: {
  12113. length: 10,
  12114. gap: 5,
  12115. altLength: undefined
  12116. },
  12117. inheritColor: "from" // to, from, false, true (== from)
  12118. },
  12119. configurePhysics:false,
  12120. physics: {
  12121. barnesHut: {
  12122. enabled: true,
  12123. theta: 1 / 0.6, // inverted to save time during calculation
  12124. gravitationalConstant: -2000,
  12125. centralGravity: 0.3,
  12126. springLength: 95,
  12127. springConstant: 0.04,
  12128. damping: 0.09
  12129. },
  12130. repulsion: {
  12131. centralGravity: 0.0,
  12132. springLength: 200,
  12133. springConstant: 0.05,
  12134. nodeDistance: 100,
  12135. damping: 0.09
  12136. },
  12137. hierarchicalRepulsion: {
  12138. enabled: false,
  12139. centralGravity: 0.0,
  12140. springLength: 100,
  12141. springConstant: 0.01,
  12142. nodeDistance: 150,
  12143. damping: 0.09
  12144. },
  12145. damping: null,
  12146. centralGravity: null,
  12147. springLength: null,
  12148. springConstant: null
  12149. },
  12150. clustering: { // Per Node in Cluster = PNiC
  12151. enabled: false, // (Boolean) | global on/off switch for clustering.
  12152. initialMaxNodes: 100, // (# nodes) | if the initial amount of nodes is larger than this, we cluster until the total number is less than this threshold.
  12153. 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
  12154. 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
  12155. chainThreshold: 0.4, // (% of all drawn nodes)| maximum percentage of allowed chainnodes (long strings of connected nodes) within all nodes. (lower means less chains).
  12156. clusterEdgeThreshold: 20, // (px) | edge length threshold. if smaller, this node is clustered.
  12157. sectorThreshold: 100, // (# nodes in cluster) | cluster size threshold. If larger, expanding in own sector.
  12158. 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.
  12159. fontSizeMultiplier: 4.0, // (px PNiC) | how much the cluster font size grows per node in cluster (in px).
  12160. maxFontSize: 1000,
  12161. forceAmplification: 0.1, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster).
  12162. distanceAmplification: 0.1, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster).
  12163. edgeGrowth: 20, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
  12164. nodeScaling: {width: 1, // (px PNiC) | growth of the width per node in cluster.
  12165. height: 1, // (px PNiC) | growth of the height per node in cluster.
  12166. radius: 1}, // (px PNiC) | growth of the radius per node in cluster.
  12167. maxNodeSizeIncrements: 600, // (# increments) | max growth of the width per node in cluster.
  12168. activeAreaBoxSize: 80, // (px) | box area around the curser where clusters are popped open.
  12169. clusterLevelDifference: 2
  12170. },
  12171. navigation: {
  12172. enabled: false
  12173. },
  12174. keyboard: {
  12175. enabled: false,
  12176. speed: {x: 10, y: 10, zoom: 0.02}
  12177. },
  12178. dataManipulation: {
  12179. enabled: false,
  12180. initiallyVisible: false
  12181. },
  12182. hierarchicalLayout: {
  12183. enabled:false,
  12184. levelSeparation: 150,
  12185. nodeSpacing: 100,
  12186. direction: "UD", // UD, DU, LR, RL
  12187. layout: "hubsize" // hubsize, directed
  12188. },
  12189. freezeForStabilization: false,
  12190. smoothCurves: {
  12191. enabled: true,
  12192. dynamic: true,
  12193. type: "continuous",
  12194. roundness: 0.5
  12195. },
  12196. dynamicSmoothCurves: true,
  12197. maxVelocity: 30,
  12198. minVelocity: 0.1, // px/s
  12199. stabilize: true, // stabilize before displaying the network
  12200. stabilizationIterations: 1000, // maximum number of iteration to stabilize
  12201. locale: 'en',
  12202. locales: locales,
  12203. tooltip: {
  12204. delay: 300,
  12205. fontColor: 'black',
  12206. fontSize: 14, // px
  12207. fontFace: 'verdana',
  12208. color: {
  12209. border: '#666',
  12210. background: '#FFFFC6'
  12211. }
  12212. },
  12213. dragNetwork: true,
  12214. dragNodes: true,
  12215. zoomable: true,
  12216. hover: false,
  12217. hideEdgesOnDrag: false,
  12218. hideNodesOnDrag: false,
  12219. width : '100%',
  12220. height : '100%',
  12221. selectable: true
  12222. };
  12223. this.constants = util.extend({}, this.defaultOptions);
  12224. this.hoverObj = {nodes:{},edges:{}};
  12225. this.controlNodesActive = false;
  12226. // Node variables
  12227. var network = this;
  12228. this.groups = new Groups(); // object with groups
  12229. this.images = new Images(); // object with images
  12230. this.images.setOnloadCallback(function () {
  12231. network._redraw();
  12232. });
  12233. // keyboard navigation variables
  12234. this.xIncrement = 0;
  12235. this.yIncrement = 0;
  12236. this.zoomIncrement = 0;
  12237. // loading all the mixins:
  12238. // load the force calculation functions, grouped under the physics system.
  12239. this._loadPhysicsSystem();
  12240. // create a frame and canvas
  12241. this._create();
  12242. // load the sector system. (mandatory, fully integrated with Network)
  12243. this._loadSectorSystem();
  12244. // load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it)
  12245. this._loadClusterSystem();
  12246. // load the selection system. (mandatory, required by Network)
  12247. this._loadSelectionSystem();
  12248. // load the selection system. (mandatory, required by Network)
  12249. this._loadHierarchySystem();
  12250. // apply options
  12251. this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
  12252. this._setScale(1);
  12253. this.setOptions(options);
  12254. // other vars
  12255. this.freezeSimulation = false;// freeze the simulation
  12256. this.cachedFunctions = {};
  12257. this.stabilized = false;
  12258. this.stabilizationIterations = null;
  12259. // containers for nodes and edges
  12260. this.calculationNodes = {};
  12261. this.calculationNodeIndices = [];
  12262. this.nodeIndices = []; // array with all the indices of the nodes. Used to speed up forces calculation
  12263. this.nodes = {}; // object with Node objects
  12264. this.edges = {}; // object with Edge objects
  12265. // position and scale variables and objects
  12266. this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw.
  12267. this.canvasBottomRight = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  12268. this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  12269. this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action
  12270. this.scale = 1; // defining the global scale variable in the constructor
  12271. this.previousScale = this.scale; // this is used to check if the zoom operation is zooming in or out
  12272. // datasets or dataviews
  12273. this.nodesData = null; // A DataSet or DataView
  12274. this.edgesData = null; // A DataSet or DataView
  12275. // create event listeners used to subscribe on the DataSets of the nodes and edges
  12276. this.nodesListeners = {
  12277. 'add': function (event, params) {
  12278. network._addNodes(params.items);
  12279. network.start();
  12280. },
  12281. 'update': function (event, params) {
  12282. network._updateNodes(params.items);
  12283. network.start();
  12284. },
  12285. 'remove': function (event, params) {
  12286. network._removeNodes(params.items);
  12287. network.start();
  12288. }
  12289. };
  12290. this.edgesListeners = {
  12291. 'add': function (event, params) {
  12292. network._addEdges(params.items);
  12293. network.start();
  12294. },
  12295. 'update': function (event, params) {
  12296. network._updateEdges(params.items);
  12297. network.start();
  12298. },
  12299. 'remove': function (event, params) {
  12300. network._removeEdges(params.items);
  12301. network.start();
  12302. }
  12303. };
  12304. // properties for the animation
  12305. this.moving = true;
  12306. this.timer = undefined; // Scheduling function. Is definded in this.start();
  12307. // load data (the disable start variable will be the same as the enabled clustering)
  12308. this.setData(data,this.constants.clustering.enabled || this.constants.hierarchicalLayout.enabled);
  12309. // hierarchical layout
  12310. this.initializing = false;
  12311. if (this.constants.hierarchicalLayout.enabled == true) {
  12312. this._setupHierarchicalLayout();
  12313. }
  12314. else {
  12315. // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
  12316. if (this.constants.stabilize == false) {
  12317. this.zoomExtent(true,this.constants.clustering.enabled);
  12318. }
  12319. }
  12320. // if clustering is disabled, the simulation will have started in the setData function
  12321. if (this.constants.clustering.enabled) {
  12322. this.startWithClustering();
  12323. }
  12324. }
  12325. // Extend Network with an Emitter mixin
  12326. Emitter(Network.prototype);
  12327. /**
  12328. * Get the script path where the vis.js library is located
  12329. *
  12330. * @returns {string | null} path Path or null when not found. Path does not
  12331. * end with a slash.
  12332. * @private
  12333. */
  12334. Network.prototype._getScriptPath = function() {
  12335. var scripts = document.getElementsByTagName( 'script' );
  12336. // find script named vis.js or vis.min.js
  12337. for (var i = 0; i < scripts.length; i++) {
  12338. var src = scripts[i].src;
  12339. var match = src && /\/?vis(.min)?\.js$/.exec(src);
  12340. if (match) {
  12341. // return path without the script name
  12342. return src.substring(0, src.length - match[0].length);
  12343. }
  12344. }
  12345. return null;
  12346. };
  12347. /**
  12348. * Find the center position of the network
  12349. * @private
  12350. */
  12351. Network.prototype._getRange = function() {
  12352. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  12353. for (var nodeId in this.nodes) {
  12354. if (this.nodes.hasOwnProperty(nodeId)) {
  12355. node = this.nodes[nodeId];
  12356. if (minX > (node.x)) {minX = node.x;}
  12357. if (maxX < (node.x)) {maxX = node.x;}
  12358. if (minY > (node.y)) {minY = node.y;}
  12359. if (maxY < (node.y)) {maxY = node.y;}
  12360. }
  12361. }
  12362. if (minX == 1e9 && maxX == -1e9 && minY == 1e9 && maxY == -1e9) {
  12363. minY = 0, maxY = 0, minX = 0, maxX = 0;
  12364. }
  12365. return {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  12366. };
  12367. /**
  12368. * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  12369. * @returns {{x: number, y: number}}
  12370. * @private
  12371. */
  12372. Network.prototype._findCenter = function(range) {
  12373. return {x: (0.5 * (range.maxX + range.minX)),
  12374. y: (0.5 * (range.maxY + range.minY))};
  12375. };
  12376. /**
  12377. * center the network
  12378. *
  12379. * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  12380. */
  12381. Network.prototype._centerNetwork = function(range) {
  12382. var center = this._findCenter(range);
  12383. center.x *= this.scale;
  12384. center.y *= this.scale;
  12385. center.x -= 0.5 * this.frame.canvas.clientWidth;
  12386. center.y -= 0.5 * this.frame.canvas.clientHeight;
  12387. this._setTranslation(-center.x,-center.y); // set at 0,0
  12388. };
  12389. /**
  12390. * This function zooms out to fit all data on screen based on amount of nodes
  12391. *
  12392. * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
  12393. * @param {Boolean} [disableStart] | If true, start is not called.
  12394. */
  12395. Network.prototype.zoomExtent = function(initialZoom, disableStart) {
  12396. if (initialZoom === undefined) {
  12397. initialZoom = false;
  12398. }
  12399. if (disableStart === undefined) {
  12400. disableStart = false;
  12401. }
  12402. var range = this._getRange();
  12403. var zoomLevel;
  12404. if (initialZoom == true) {
  12405. var numberOfNodes = this.nodeIndices.length;
  12406. if (this.constants.smoothCurves == true) {
  12407. if (this.constants.clustering.enabled == true &&
  12408. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  12409. 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.
  12410. }
  12411. else {
  12412. zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  12413. }
  12414. }
  12415. else {
  12416. if (this.constants.clustering.enabled == true &&
  12417. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  12418. 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.
  12419. }
  12420. else {
  12421. zoomLevel = 30.5062972 / (numberOfNodes + 19.93597763) + 0.08413486; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  12422. }
  12423. }
  12424. // correct for larger canvasses.
  12425. var factor = Math.min(this.frame.canvas.clientWidth / 600, this.frame.canvas.clientHeight / 600);
  12426. zoomLevel *= factor;
  12427. }
  12428. else {
  12429. var xDistance = (Math.abs(range.minX) + Math.abs(range.maxX)) * 1.1;
  12430. var yDistance = (Math.abs(range.minY) + Math.abs(range.maxY)) * 1.1;
  12431. var xZoomLevel = this.frame.canvas.clientWidth / xDistance;
  12432. var yZoomLevel = this.frame.canvas.clientHeight / yDistance;
  12433. zoomLevel = (xZoomLevel <= yZoomLevel) ? xZoomLevel : yZoomLevel;
  12434. }
  12435. if (zoomLevel > 1.0) {
  12436. zoomLevel = 1.0;
  12437. }
  12438. this._setScale(zoomLevel);
  12439. this._centerNetwork(range);
  12440. if (disableStart == false) {
  12441. this.moving = true;
  12442. this.start();
  12443. }
  12444. };
  12445. /**
  12446. * Update the this.nodeIndices with the most recent node index list
  12447. * @private
  12448. */
  12449. Network.prototype._updateNodeIndexList = function() {
  12450. this._clearNodeIndexList();
  12451. for (var idx in this.nodes) {
  12452. if (this.nodes.hasOwnProperty(idx)) {
  12453. this.nodeIndices.push(idx);
  12454. }
  12455. }
  12456. };
  12457. /**
  12458. * Set nodes and edges, and optionally options as well.
  12459. *
  12460. * @param {Object} data Object containing parameters:
  12461. * {Array | DataSet | DataView} [nodes] Array with nodes
  12462. * {Array | DataSet | DataView} [edges] Array with edges
  12463. * {String} [dot] String containing data in DOT format
  12464. * {String} [gephi] String containing data in gephi JSON format
  12465. * {Options} [options] Object with options
  12466. * @param {Boolean} [disableStart] | optional: disable the calling of the start function.
  12467. */
  12468. Network.prototype.setData = function(data, disableStart) {
  12469. if (disableStart === undefined) {
  12470. disableStart = false;
  12471. }
  12472. // we set initializing to true to ensure that the hierarchical layout is not performed until both nodes and edges are added.
  12473. this.initializing = true;
  12474. if (data && data.dot && (data.nodes || data.edges)) {
  12475. throw new SyntaxError('Data must contain either parameter "dot" or ' +
  12476. ' parameter pair "nodes" and "edges", but not both.');
  12477. }
  12478. // set options
  12479. this.setOptions(data && data.options);
  12480. // set all data
  12481. if (data && data.dot) {
  12482. // parse DOT file
  12483. if(data && data.dot) {
  12484. var dotData = dotparser.DOTToGraph(data.dot);
  12485. this.setData(dotData);
  12486. return;
  12487. }
  12488. }
  12489. else if (data && data.gephi) {
  12490. // parse DOT file
  12491. if(data && data.gephi) {
  12492. var gephiData = gephiParser.parseGephi(data.gephi);
  12493. this.setData(gephiData);
  12494. return;
  12495. }
  12496. }
  12497. else {
  12498. this._setNodes(data && data.nodes);
  12499. this._setEdges(data && data.edges);
  12500. }
  12501. this._putDataInSector();
  12502. if (disableStart == false) {
  12503. if (this.constants.hierarchicalLayout.enabled == true) {
  12504. this._resetLevels();
  12505. this._setupHierarchicalLayout();
  12506. }
  12507. else {
  12508. // find a stable position or start animating to a stable position
  12509. if (this.constants.stabilize) {
  12510. this._stabilize();
  12511. }
  12512. }
  12513. this.start();
  12514. }
  12515. this.initializing = false;
  12516. };
  12517. /**
  12518. * Set options
  12519. * @param {Object} options
  12520. */
  12521. Network.prototype.setOptions = function (options) {
  12522. if (options) {
  12523. var prop;
  12524. var fields = ['nodes','edges','smoothCurves','hierarchicalLayout','clustering','navigation','keyboard','dataManipulation',
  12525. 'onAdd','onEdit','onEditEdge','onConnect','onDelete','clickToUse'
  12526. ];
  12527. util.selectiveNotDeepExtend(fields,this.constants, options);
  12528. util.selectiveNotDeepExtend(['color'],this.constants.nodes, options.nodes);
  12529. util.selectiveNotDeepExtend(['color','length'],this.constants.edges, options.edges);
  12530. if (options.physics) {
  12531. util.mergeOptions(this.constants.physics, options.physics,'barnesHut');
  12532. util.mergeOptions(this.constants.physics, options.physics,'repulsion');
  12533. if (options.physics.hierarchicalRepulsion) {
  12534. this.constants.hierarchicalLayout.enabled = true;
  12535. this.constants.physics.hierarchicalRepulsion.enabled = true;
  12536. this.constants.physics.barnesHut.enabled = false;
  12537. for (prop in options.physics.hierarchicalRepulsion) {
  12538. if (options.physics.hierarchicalRepulsion.hasOwnProperty(prop)) {
  12539. this.constants.physics.hierarchicalRepulsion[prop] = options.physics.hierarchicalRepulsion[prop];
  12540. }
  12541. }
  12542. }
  12543. }
  12544. if (options.onAdd) {this.triggerFunctions.add = options.onAdd;}
  12545. if (options.onEdit) {this.triggerFunctions.edit = options.onEdit;}
  12546. if (options.onEditEdge) {this.triggerFunctions.editEdge = options.onEditEdge;}
  12547. if (options.onConnect) {this.triggerFunctions.connect = options.onConnect;}
  12548. if (options.onDelete) {this.triggerFunctions.del = options.onDelete;}
  12549. util.mergeOptions(this.constants, options,'smoothCurves');
  12550. util.mergeOptions(this.constants, options,'hierarchicalLayout');
  12551. util.mergeOptions(this.constants, options,'clustering');
  12552. util.mergeOptions(this.constants, options,'navigation');
  12553. util.mergeOptions(this.constants, options,'keyboard');
  12554. util.mergeOptions(this.constants, options,'dataManipulation');
  12555. if (options.dataManipulation) {
  12556. this.editMode = this.constants.dataManipulation.initiallyVisible;
  12557. }
  12558. // TODO: work out these options and document them
  12559. if (options.edges) {
  12560. if (options.edges.color !== undefined) {
  12561. if (util.isString(options.edges.color)) {
  12562. this.constants.edges.color = {};
  12563. this.constants.edges.color.color = options.edges.color;
  12564. this.constants.edges.color.highlight = options.edges.color;
  12565. this.constants.edges.color.hover = options.edges.color;
  12566. }
  12567. else {
  12568. if (options.edges.color.color !== undefined) {this.constants.edges.color.color = options.edges.color.color;}
  12569. if (options.edges.color.highlight !== undefined) {this.constants.edges.color.highlight = options.edges.color.highlight;}
  12570. if (options.edges.color.hover !== undefined) {this.constants.edges.color.hover = options.edges.color.hover;}
  12571. }
  12572. }
  12573. if (!options.edges.fontColor) {
  12574. if (options.edges.color !== undefined) {
  12575. if (util.isString(options.edges.color)) {this.constants.edges.fontColor = options.edges.color;}
  12576. else if (options.edges.color.color !== undefined) {this.constants.edges.fontColor = options.edges.color.color;}
  12577. }
  12578. }
  12579. }
  12580. if (options.nodes) {
  12581. if (options.nodes.color) {
  12582. var newColorObj = util.parseColor(options.nodes.color);
  12583. this.constants.nodes.color.background = newColorObj.background;
  12584. this.constants.nodes.color.border = newColorObj.border;
  12585. this.constants.nodes.color.highlight.background = newColorObj.highlight.background;
  12586. this.constants.nodes.color.highlight.border = newColorObj.highlight.border;
  12587. this.constants.nodes.color.hover.background = newColorObj.hover.background;
  12588. this.constants.nodes.color.hover.border = newColorObj.hover.border;
  12589. }
  12590. }
  12591. if (options.groups) {
  12592. for (var groupname in options.groups) {
  12593. if (options.groups.hasOwnProperty(groupname)) {
  12594. var group = options.groups[groupname];
  12595. this.groups.add(groupname, group);
  12596. }
  12597. }
  12598. }
  12599. if (options.tooltip) {
  12600. for (prop in options.tooltip) {
  12601. if (options.tooltip.hasOwnProperty(prop)) {
  12602. this.constants.tooltip[prop] = options.tooltip[prop];
  12603. }
  12604. }
  12605. if (options.tooltip.color) {
  12606. this.constants.tooltip.color = util.parseColor(options.tooltip.color);
  12607. }
  12608. }
  12609. if ('clickToUse' in options) {
  12610. if (options.clickToUse) {
  12611. this.activator = new Activator(this.frame);
  12612. this.activator.on('change', this._createKeyBinds.bind(this));
  12613. }
  12614. else {
  12615. if (this.activator) {
  12616. this.activator.destroy();
  12617. delete this.activator;
  12618. }
  12619. }
  12620. }
  12621. if (options.labels) {
  12622. throw new Error('Option "labels" is deprecated. Use options "locale" and "locales" instead.');
  12623. }
  12624. }
  12625. // (Re)loading the mixins that can be enabled or disabled in the options.
  12626. // load the force calculation functions, grouped under the physics system.
  12627. this._loadPhysicsSystem();
  12628. // load the navigation system.
  12629. this._loadNavigationControls();
  12630. // load the data manipulation system
  12631. this._loadManipulationSystem();
  12632. // configure the smooth curves
  12633. this._configureSmoothCurves();
  12634. // bind keys. If disabled, this will not do anything;
  12635. this._createKeyBinds();
  12636. this.setSize(this.constants.width, this.constants.height);
  12637. this.moving = true;
  12638. this.start();
  12639. };
  12640. /**
  12641. * Create the main frame for the Network.
  12642. * This function is executed once when a Network object is created. The frame
  12643. * contains a canvas, and this canvas contains all objects like the axis and
  12644. * nodes.
  12645. * @private
  12646. */
  12647. Network.prototype._create = function () {
  12648. // remove all elements from the container element.
  12649. while (this.containerElement.hasChildNodes()) {
  12650. this.containerElement.removeChild(this.containerElement.firstChild);
  12651. }
  12652. this.frame = document.createElement('div');
  12653. this.frame.className = 'vis network-frame';
  12654. this.frame.style.position = 'relative';
  12655. this.frame.style.overflow = 'hidden';
  12656. // create the network canvas (HTML canvas element)
  12657. this.frame.canvas = document.createElement( 'canvas' );
  12658. this.frame.canvas.style.position = 'relative';
  12659. this.frame.appendChild(this.frame.canvas);
  12660. if (!this.frame.canvas.getContext) {
  12661. var noCanvas = document.createElement( 'DIV' );
  12662. noCanvas.style.color = 'red';
  12663. noCanvas.style.fontWeight = 'bold' ;
  12664. noCanvas.style.padding = '10px';
  12665. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  12666. this.frame.canvas.appendChild(noCanvas);
  12667. }
  12668. var me = this;
  12669. this.drag = {};
  12670. this.pinch = {};
  12671. this.hammer = Hammer(this.frame.canvas, {
  12672. prevent_default: true
  12673. });
  12674. this.hammer.on('tap', me._onTap.bind(me) );
  12675. this.hammer.on('doubletap', me._onDoubleTap.bind(me) );
  12676. this.hammer.on('hold', me._onHold.bind(me) );
  12677. this.hammer.on('pinch', me._onPinch.bind(me) );
  12678. this.hammer.on('touch', me._onTouch.bind(me) );
  12679. this.hammer.on('dragstart', me._onDragStart.bind(me) );
  12680. this.hammer.on('drag', me._onDrag.bind(me) );
  12681. this.hammer.on('dragend', me._onDragEnd.bind(me) );
  12682. this.hammer.on('release', me._onRelease.bind(me) );
  12683. this.hammer.on('mousewheel',me._onMouseWheel.bind(me) );
  12684. this.hammer.on('DOMMouseScroll',me._onMouseWheel.bind(me) ); // for FF
  12685. this.hammer.on('mousemove', me._onMouseMoveTitle.bind(me) );
  12686. // add the frame to the container element
  12687. this.containerElement.appendChild(this.frame);
  12688. };
  12689. /**
  12690. * Binding the keys for keyboard navigation. These functions are defined in the NavigationMixin
  12691. * @private
  12692. */
  12693. Network.prototype._createKeyBinds = function() {
  12694. var me = this;
  12695. this.mousetrap = mousetrap;
  12696. this.mousetrap.reset();
  12697. if (this.constants.keyboard.enabled && this.isActive()) {
  12698. this.mousetrap.bind("up", this._moveUp.bind(me) , "keydown");
  12699. this.mousetrap.bind("up", this._yStopMoving.bind(me), "keyup");
  12700. this.mousetrap.bind("down", this._moveDown.bind(me) , "keydown");
  12701. this.mousetrap.bind("down", this._yStopMoving.bind(me), "keyup");
  12702. this.mousetrap.bind("left", this._moveLeft.bind(me) , "keydown");
  12703. this.mousetrap.bind("left", this._xStopMoving.bind(me), "keyup");
  12704. this.mousetrap.bind("right",this._moveRight.bind(me), "keydown");
  12705. this.mousetrap.bind("right",this._xStopMoving.bind(me), "keyup");
  12706. this.mousetrap.bind("=", this._zoomIn.bind(me), "keydown");
  12707. this.mousetrap.bind("=", this._stopZoom.bind(me), "keyup");
  12708. this.mousetrap.bind("-", this._zoomOut.bind(me), "keydown");
  12709. this.mousetrap.bind("-", this._stopZoom.bind(me), "keyup");
  12710. this.mousetrap.bind("[", this._zoomIn.bind(me), "keydown");
  12711. this.mousetrap.bind("[", this._stopZoom.bind(me), "keyup");
  12712. this.mousetrap.bind("]", this._zoomOut.bind(me), "keydown");
  12713. this.mousetrap.bind("]", this._stopZoom.bind(me), "keyup");
  12714. this.mousetrap.bind("pageup",this._zoomIn.bind(me), "keydown");
  12715. this.mousetrap.bind("pageup",this._stopZoom.bind(me), "keyup");
  12716. this.mousetrap.bind("pagedown",this._zoomOut.bind(me),"keydown");
  12717. this.mousetrap.bind("pagedown",this._stopZoom.bind(me), "keyup");
  12718. }
  12719. if (this.constants.dataManipulation.enabled == true) {
  12720. this.mousetrap.bind("escape",this._createManipulatorBar.bind(me));
  12721. this.mousetrap.bind("del",this._deleteSelected.bind(me));
  12722. }
  12723. };
  12724. /**
  12725. * Get the pointer location from a touch location
  12726. * @param {{pageX: Number, pageY: Number}} touch
  12727. * @return {{x: Number, y: Number}} pointer
  12728. * @private
  12729. */
  12730. Network.prototype._getPointer = function (touch) {
  12731. return {
  12732. x: touch.pageX - util.getAbsoluteLeft(this.frame.canvas),
  12733. y: touch.pageY - util.getAbsoluteTop(this.frame.canvas)
  12734. };
  12735. };
  12736. /**
  12737. * On start of a touch gesture, store the pointer
  12738. * @param event
  12739. * @private
  12740. */
  12741. Network.prototype._onTouch = function (event) {
  12742. this.drag.pointer = this._getPointer(event.gesture.center);
  12743. this.drag.pinched = false;
  12744. this.pinch.scale = this._getScale();
  12745. this._handleTouch(this.drag.pointer);
  12746. };
  12747. /**
  12748. * handle drag start event
  12749. * @private
  12750. */
  12751. Network.prototype._onDragStart = function () {
  12752. this._handleDragStart();
  12753. };
  12754. /**
  12755. * This function is called by _onDragStart.
  12756. * It is separated out because we can then overload it for the datamanipulation system.
  12757. *
  12758. * @private
  12759. */
  12760. Network.prototype._handleDragStart = function() {
  12761. var drag = this.drag;
  12762. var node = this._getNodeAt(drag.pointer);
  12763. // note: drag.pointer is set in _onTouch to get the initial touch location
  12764. drag.dragging = true;
  12765. drag.selection = [];
  12766. drag.translation = this._getTranslation();
  12767. drag.nodeId = null;
  12768. if (node != null) {
  12769. drag.nodeId = node.id;
  12770. // select the clicked node if not yet selected
  12771. if (!node.isSelected()) {
  12772. this._selectObject(node,false);
  12773. }
  12774. // create an array with the selected nodes and their original location and status
  12775. for (var objectId in this.selectionObj.nodes) {
  12776. if (this.selectionObj.nodes.hasOwnProperty(objectId)) {
  12777. var object = this.selectionObj.nodes[objectId];
  12778. var s = {
  12779. id: object.id,
  12780. node: object,
  12781. // store original x, y, xFixed and yFixed, make the node temporarily Fixed
  12782. x: object.x,
  12783. y: object.y,
  12784. xFixed: object.xFixed,
  12785. yFixed: object.yFixed
  12786. };
  12787. object.xFixed = true;
  12788. object.yFixed = true;
  12789. drag.selection.push(s);
  12790. }
  12791. }
  12792. }
  12793. };
  12794. /**
  12795. * handle drag event
  12796. * @private
  12797. */
  12798. Network.prototype._onDrag = function (event) {
  12799. this._handleOnDrag(event)
  12800. };
  12801. /**
  12802. * This function is called by _onDrag.
  12803. * It is separated out because we can then overload it for the datamanipulation system.
  12804. *
  12805. * @private
  12806. */
  12807. Network.prototype._handleOnDrag = function(event) {
  12808. if (this.drag.pinched) {
  12809. return;
  12810. }
  12811. var pointer = this._getPointer(event.gesture.center);
  12812. var me = this;
  12813. var drag = this.drag;
  12814. var selection = drag.selection;
  12815. if (selection && selection.length && this.constants.dragNodes == true) {
  12816. // calculate delta's and new location
  12817. var deltaX = pointer.x - drag.pointer.x;
  12818. var deltaY = pointer.y - drag.pointer.y;
  12819. // update position of all selected nodes
  12820. selection.forEach(function (s) {
  12821. var node = s.node;
  12822. if (!s.xFixed) {
  12823. node.x = me._XconvertDOMtoCanvas(me._XconvertCanvasToDOM(s.x) + deltaX);
  12824. }
  12825. if (!s.yFixed) {
  12826. node.y = me._YconvertDOMtoCanvas(me._YconvertCanvasToDOM(s.y) + deltaY);
  12827. }
  12828. });
  12829. // start _animationStep if not yet running
  12830. if (!this.moving) {
  12831. this.moving = true;
  12832. this.start();
  12833. }
  12834. }
  12835. else {
  12836. if (this.constants.dragNetwork == true) {
  12837. // move the network
  12838. var diffX = pointer.x - this.drag.pointer.x;
  12839. var diffY = pointer.y - this.drag.pointer.y;
  12840. this._setTranslation(
  12841. this.drag.translation.x + diffX,
  12842. this.drag.translation.y + diffY
  12843. );
  12844. this._redraw();
  12845. // this.moving = true;
  12846. // this.start();
  12847. }
  12848. }
  12849. };
  12850. /**
  12851. * handle drag start event
  12852. * @private
  12853. */
  12854. Network.prototype._onDragEnd = function () {
  12855. this.drag.dragging = false;
  12856. var selection = this.drag.selection;
  12857. if (selection && selection.length) {
  12858. selection.forEach(function (s) {
  12859. // restore original xFixed and yFixed
  12860. s.node.xFixed = s.xFixed;
  12861. s.node.yFixed = s.yFixed;
  12862. });
  12863. this.moving = true;
  12864. this.start();
  12865. }
  12866. else {
  12867. this._redraw();
  12868. }
  12869. };
  12870. /**
  12871. * handle tap/click event: select/unselect a node
  12872. * @private
  12873. */
  12874. Network.prototype._onTap = function (event) {
  12875. var pointer = this._getPointer(event.gesture.center);
  12876. this.pointerPosition = pointer;
  12877. this._handleTap(pointer);
  12878. };
  12879. /**
  12880. * handle doubletap event
  12881. * @private
  12882. */
  12883. Network.prototype._onDoubleTap = function (event) {
  12884. var pointer = this._getPointer(event.gesture.center);
  12885. this._handleDoubleTap(pointer);
  12886. };
  12887. /**
  12888. * handle long tap event: multi select nodes
  12889. * @private
  12890. */
  12891. Network.prototype._onHold = function (event) {
  12892. var pointer = this._getPointer(event.gesture.center);
  12893. this.pointerPosition = pointer;
  12894. this._handleOnHold(pointer);
  12895. };
  12896. /**
  12897. * handle the release of the screen
  12898. *
  12899. * @private
  12900. */
  12901. Network.prototype._onRelease = function (event) {
  12902. var pointer = this._getPointer(event.gesture.center);
  12903. this._handleOnRelease(pointer);
  12904. };
  12905. /**
  12906. * Handle pinch event
  12907. * @param event
  12908. * @private
  12909. */
  12910. Network.prototype._onPinch = function (event) {
  12911. var pointer = this._getPointer(event.gesture.center);
  12912. this.drag.pinched = true;
  12913. if (!('scale' in this.pinch)) {
  12914. this.pinch.scale = 1;
  12915. }
  12916. // TODO: enabled moving while pinching?
  12917. var scale = this.pinch.scale * event.gesture.scale;
  12918. this._zoom(scale, pointer)
  12919. };
  12920. /**
  12921. * Zoom the network in or out
  12922. * @param {Number} scale a number around 1, and between 0.01 and 10
  12923. * @param {{x: Number, y: Number}} pointer Position on screen
  12924. * @return {Number} appliedScale scale is limited within the boundaries
  12925. * @private
  12926. */
  12927. Network.prototype._zoom = function(scale, pointer) {
  12928. if (this.constants.zoomable == true) {
  12929. var scaleOld = this._getScale();
  12930. if (scale < 0.00001) {
  12931. scale = 0.00001;
  12932. }
  12933. if (scale > 10) {
  12934. scale = 10;
  12935. }
  12936. var preScaleDragPointer = null;
  12937. if (this.drag !== undefined) {
  12938. if (this.drag.dragging == true) {
  12939. preScaleDragPointer = this.DOMtoCanvas(this.drag.pointer);
  12940. }
  12941. }
  12942. // + this.frame.canvas.clientHeight / 2
  12943. var translation = this._getTranslation();
  12944. var scaleFrac = scale / scaleOld;
  12945. var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
  12946. var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
  12947. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  12948. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  12949. this._setScale(scale);
  12950. this._setTranslation(tx, ty);
  12951. this.updateClustersDefault();
  12952. if (preScaleDragPointer != null) {
  12953. var postScaleDragPointer = this.canvasToDOM(preScaleDragPointer);
  12954. this.drag.pointer.x = postScaleDragPointer.x;
  12955. this.drag.pointer.y = postScaleDragPointer.y;
  12956. }
  12957. this._redraw();
  12958. if (scaleOld < scale) {
  12959. this.emit("zoom", {direction:"+"});
  12960. }
  12961. else {
  12962. this.emit("zoom", {direction:"-"});
  12963. }
  12964. return scale;
  12965. }
  12966. };
  12967. /**
  12968. * Event handler for mouse wheel event, used to zoom the timeline
  12969. * See http://adomas.org/javascript-mouse-wheel/
  12970. * https://github.com/EightMedia/hammer.js/issues/256
  12971. * @param {MouseEvent} event
  12972. * @private
  12973. */
  12974. Network.prototype._onMouseWheel = function(event) {
  12975. // retrieve delta
  12976. var delta = 0;
  12977. if (event.wheelDelta) { /* IE/Opera. */
  12978. delta = event.wheelDelta/120;
  12979. } else if (event.detail) { /* Mozilla case. */
  12980. // In Mozilla, sign of delta is different than in IE.
  12981. // Also, delta is multiple of 3.
  12982. delta = -event.detail/3;
  12983. }
  12984. // If delta is nonzero, handle it.
  12985. // Basically, delta is now positive if wheel was scrolled up,
  12986. // and negative, if wheel was scrolled down.
  12987. if (delta) {
  12988. // calculate the new scale
  12989. var scale = this._getScale();
  12990. var zoom = delta / 10;
  12991. if (delta < 0) {
  12992. zoom = zoom / (1 - zoom);
  12993. }
  12994. scale *= (1 + zoom);
  12995. // calculate the pointer location
  12996. var gesture = hammerUtil.fakeGesture(this, event);
  12997. var pointer = this._getPointer(gesture.center);
  12998. // apply the new scale
  12999. this._zoom(scale, pointer);
  13000. }
  13001. // Prevent default actions caused by mouse wheel.
  13002. event.preventDefault();
  13003. };
  13004. /**
  13005. * Mouse move handler for checking whether the title moves over a node with a title.
  13006. * @param {Event} event
  13007. * @private
  13008. */
  13009. Network.prototype._onMouseMoveTitle = function (event) {
  13010. var gesture = hammerUtil.fakeGesture(this, event);
  13011. var pointer = this._getPointer(gesture.center);
  13012. // check if the previously selected node is still selected
  13013. if (this.popupObj) {
  13014. this._checkHidePopup(pointer);
  13015. }
  13016. // start a timeout that will check if the mouse is positioned above
  13017. // an element
  13018. var me = this;
  13019. var checkShow = function() {
  13020. me._checkShowPopup(pointer);
  13021. };
  13022. if (this.popupTimer) {
  13023. clearInterval(this.popupTimer); // stop any running calculationTimer
  13024. }
  13025. if (!this.drag.dragging) {
  13026. this.popupTimer = setTimeout(checkShow, this.constants.tooltip.delay);
  13027. }
  13028. /**
  13029. * Adding hover highlights
  13030. */
  13031. if (this.constants.hover == true) {
  13032. // removing all hover highlights
  13033. for (var edgeId in this.hoverObj.edges) {
  13034. if (this.hoverObj.edges.hasOwnProperty(edgeId)) {
  13035. this.hoverObj.edges[edgeId].hover = false;
  13036. delete this.hoverObj.edges[edgeId];
  13037. }
  13038. }
  13039. // adding hover highlights
  13040. var obj = this._getNodeAt(pointer);
  13041. if (obj == null) {
  13042. obj = this._getEdgeAt(pointer);
  13043. }
  13044. if (obj != null) {
  13045. this._hoverObject(obj);
  13046. }
  13047. // removing all node hover highlights except for the selected one.
  13048. for (var nodeId in this.hoverObj.nodes) {
  13049. if (this.hoverObj.nodes.hasOwnProperty(nodeId)) {
  13050. if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj == null) {
  13051. this._blurObject(this.hoverObj.nodes[nodeId]);
  13052. delete this.hoverObj.nodes[nodeId];
  13053. }
  13054. }
  13055. }
  13056. this.redraw();
  13057. }
  13058. };
  13059. /**
  13060. * Check if there is an element on the given position in the network
  13061. * (a node or edge). If so, and if this element has a title,
  13062. * show a popup window with its title.
  13063. *
  13064. * @param {{x:Number, y:Number}} pointer
  13065. * @private
  13066. */
  13067. Network.prototype._checkShowPopup = function (pointer) {
  13068. var obj = {
  13069. left: this._XconvertDOMtoCanvas(pointer.x),
  13070. top: this._YconvertDOMtoCanvas(pointer.y),
  13071. right: this._XconvertDOMtoCanvas(pointer.x),
  13072. bottom: this._YconvertDOMtoCanvas(pointer.y)
  13073. };
  13074. var id;
  13075. var lastPopupNode = this.popupObj;
  13076. if (this.popupObj == undefined) {
  13077. // search the nodes for overlap, select the top one in case of multiple nodes
  13078. var nodes = this.nodes;
  13079. for (id in nodes) {
  13080. if (nodes.hasOwnProperty(id)) {
  13081. var node = nodes[id];
  13082. if (node.getTitle() !== undefined && node.isOverlappingWith(obj)) {
  13083. this.popupObj = node;
  13084. break;
  13085. }
  13086. }
  13087. }
  13088. }
  13089. if (this.popupObj === undefined) {
  13090. // search the edges for overlap
  13091. var edges = this.edges;
  13092. for (id in edges) {
  13093. if (edges.hasOwnProperty(id)) {
  13094. var edge = edges[id];
  13095. if (edge.connected && (edge.getTitle() !== undefined) &&
  13096. edge.isOverlappingWith(obj)) {
  13097. this.popupObj = edge;
  13098. break;
  13099. }
  13100. }
  13101. }
  13102. }
  13103. if (this.popupObj) {
  13104. // show popup message window
  13105. if (this.popupObj != lastPopupNode) {
  13106. var me = this;
  13107. if (!me.popup) {
  13108. me.popup = new Popup(me.frame, me.constants.tooltip);
  13109. }
  13110. // adjust a small offset such that the mouse cursor is located in the
  13111. // bottom left location of the popup, and you can easily move over the
  13112. // popup area
  13113. me.popup.setPosition(pointer.x - 3, pointer.y - 3);
  13114. me.popup.setText(me.popupObj.getTitle());
  13115. me.popup.show();
  13116. }
  13117. }
  13118. else {
  13119. if (this.popup) {
  13120. this.popup.hide();
  13121. }
  13122. }
  13123. };
  13124. /**
  13125. * Check if the popup must be hided, which is the case when the mouse is no
  13126. * longer hovering on the object
  13127. * @param {{x:Number, y:Number}} pointer
  13128. * @private
  13129. */
  13130. Network.prototype._checkHidePopup = function (pointer) {
  13131. if (!this.popupObj || !this._getNodeAt(pointer) ) {
  13132. this.popupObj = undefined;
  13133. if (this.popup) {
  13134. this.popup.hide();
  13135. }
  13136. }
  13137. };
  13138. /**
  13139. * Set a new size for the network
  13140. * @param {string} width Width in pixels or percentage (for example '800px'
  13141. * or '50%')
  13142. * @param {string} height Height in pixels or percentage (for example '400px'
  13143. * or '30%')
  13144. */
  13145. Network.prototype.setSize = function(width, height) {
  13146. this.frame.style.width = width;
  13147. this.frame.style.height = height;
  13148. this.frame.canvas.style.width = '100%';
  13149. this.frame.canvas.style.height = '100%';
  13150. this.frame.canvas.width = this.frame.canvas.clientWidth;
  13151. this.frame.canvas.height = this.frame.canvas.clientHeight;
  13152. this.emit('resize', {width:this.frame.canvas.width,height:this.frame.canvas.height});
  13153. };
  13154. /**
  13155. * Set a data set with nodes for the network
  13156. * @param {Array | DataSet | DataView} nodes The data containing the nodes.
  13157. * @private
  13158. */
  13159. Network.prototype._setNodes = function(nodes) {
  13160. var oldNodesData = this.nodesData;
  13161. if (nodes instanceof DataSet || nodes instanceof DataView) {
  13162. this.nodesData = nodes;
  13163. }
  13164. else if (nodes instanceof Array) {
  13165. this.nodesData = new DataSet();
  13166. this.nodesData.add(nodes);
  13167. }
  13168. else if (!nodes) {
  13169. this.nodesData = new DataSet();
  13170. }
  13171. else {
  13172. throw new TypeError('Array or DataSet expected');
  13173. }
  13174. if (oldNodesData) {
  13175. // unsubscribe from old dataset
  13176. util.forEach(this.nodesListeners, function (callback, event) {
  13177. oldNodesData.off(event, callback);
  13178. });
  13179. }
  13180. // remove drawn nodes
  13181. this.nodes = {};
  13182. if (this.nodesData) {
  13183. // subscribe to new dataset
  13184. var me = this;
  13185. util.forEach(this.nodesListeners, function (callback, event) {
  13186. me.nodesData.on(event, callback);
  13187. });
  13188. // draw all new nodes
  13189. var ids = this.nodesData.getIds();
  13190. this._addNodes(ids);
  13191. }
  13192. this._updateSelection();
  13193. };
  13194. /**
  13195. * Add nodes
  13196. * @param {Number[] | String[]} ids
  13197. * @private
  13198. */
  13199. Network.prototype._addNodes = function(ids) {
  13200. var id;
  13201. for (var i = 0, len = ids.length; i < len; i++) {
  13202. id = ids[i];
  13203. var data = this.nodesData.get(id);
  13204. var node = new Node(data, this.images, this.groups, this.constants);
  13205. this.nodes[id] = node; // note: this may replace an existing node
  13206. if ((node.xFixed == false || node.yFixed == false) && (node.x === null || node.y === null)) {
  13207. var radius = 10 * 0.1*ids.length + 10;
  13208. var angle = 2 * Math.PI * Math.random();
  13209. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  13210. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  13211. }
  13212. this.moving = true;
  13213. }
  13214. this._updateNodeIndexList();
  13215. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  13216. this._resetLevels();
  13217. this._setupHierarchicalLayout();
  13218. }
  13219. this._updateCalculationNodes();
  13220. this._reconnectEdges();
  13221. this._updateValueRange(this.nodes);
  13222. this.updateLabels();
  13223. };
  13224. /**
  13225. * Update existing nodes, or create them when not yet existing
  13226. * @param {Number[] | String[]} ids
  13227. * @private
  13228. */
  13229. Network.prototype._updateNodes = function(ids) {
  13230. var nodes = this.nodes,
  13231. nodesData = this.nodesData;
  13232. for (var i = 0, len = ids.length; i < len; i++) {
  13233. var id = ids[i];
  13234. var node = nodes[id];
  13235. var data = nodesData.get(id);
  13236. if (node) {
  13237. // update node
  13238. node.setProperties(data, this.constants);
  13239. }
  13240. else {
  13241. // create node
  13242. node = new Node(properties, this.images, this.groups, this.constants);
  13243. nodes[id] = node;
  13244. }
  13245. }
  13246. this.moving = true;
  13247. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  13248. this._resetLevels();
  13249. this._setupHierarchicalLayout();
  13250. }
  13251. this._updateNodeIndexList();
  13252. this._reconnectEdges();
  13253. this._updateValueRange(nodes);
  13254. };
  13255. /**
  13256. * Remove existing nodes. If nodes do not exist, the method will just ignore it.
  13257. * @param {Number[] | String[]} ids
  13258. * @private
  13259. */
  13260. Network.prototype._removeNodes = function(ids) {
  13261. var nodes = this.nodes;
  13262. for (var i = 0, len = ids.length; i < len; i++) {
  13263. var id = ids[i];
  13264. delete nodes[id];
  13265. }
  13266. this._updateNodeIndexList();
  13267. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  13268. this._resetLevels();
  13269. this._setupHierarchicalLayout();
  13270. }
  13271. this._updateCalculationNodes();
  13272. this._reconnectEdges();
  13273. this._updateSelection();
  13274. this._updateValueRange(nodes);
  13275. };
  13276. /**
  13277. * Load edges by reading the data table
  13278. * @param {Array | DataSet | DataView} edges The data containing the edges.
  13279. * @private
  13280. * @private
  13281. */
  13282. Network.prototype._setEdges = function(edges) {
  13283. var oldEdgesData = this.edgesData;
  13284. if (edges instanceof DataSet || edges instanceof DataView) {
  13285. this.edgesData = edges;
  13286. }
  13287. else if (edges instanceof Array) {
  13288. this.edgesData = new DataSet();
  13289. this.edgesData.add(edges);
  13290. }
  13291. else if (!edges) {
  13292. this.edgesData = new DataSet();
  13293. }
  13294. else {
  13295. throw new TypeError('Array or DataSet expected');
  13296. }
  13297. if (oldEdgesData) {
  13298. // unsubscribe from old dataset
  13299. util.forEach(this.edgesListeners, function (callback, event) {
  13300. oldEdgesData.off(event, callback);
  13301. });
  13302. }
  13303. // remove drawn edges
  13304. this.edges = {};
  13305. if (this.edgesData) {
  13306. // subscribe to new dataset
  13307. var me = this;
  13308. util.forEach(this.edgesListeners, function (callback, event) {
  13309. me.edgesData.on(event, callback);
  13310. });
  13311. // draw all new nodes
  13312. var ids = this.edgesData.getIds();
  13313. this._addEdges(ids);
  13314. }
  13315. this._reconnectEdges();
  13316. };
  13317. /**
  13318. * Add edges
  13319. * @param {Number[] | String[]} ids
  13320. * @private
  13321. */
  13322. Network.prototype._addEdges = function (ids) {
  13323. var edges = this.edges,
  13324. edgesData = this.edgesData;
  13325. for (var i = 0, len = ids.length; i < len; i++) {
  13326. var id = ids[i];
  13327. var oldEdge = edges[id];
  13328. if (oldEdge) {
  13329. oldEdge.disconnect();
  13330. }
  13331. var data = edgesData.get(id, {"showInternalIds" : true});
  13332. edges[id] = new Edge(data, this, this.constants);
  13333. }
  13334. this.moving = true;
  13335. this._updateValueRange(edges);
  13336. this._createBezierNodes();
  13337. this._updateCalculationNodes();
  13338. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  13339. this._resetLevels();
  13340. this._setupHierarchicalLayout();
  13341. }
  13342. };
  13343. /**
  13344. * Update existing edges, or create them when not yet existing
  13345. * @param {Number[] | String[]} ids
  13346. * @private
  13347. */
  13348. Network.prototype._updateEdges = function (ids) {
  13349. var edges = this.edges,
  13350. edgesData = this.edgesData;
  13351. for (var i = 0, len = ids.length; i < len; i++) {
  13352. var id = ids[i];
  13353. var data = edgesData.get(id);
  13354. var edge = edges[id];
  13355. if (edge) {
  13356. // update edge
  13357. edge.disconnect();
  13358. edge.setProperties(data, this.constants);
  13359. edge.connect();
  13360. }
  13361. else {
  13362. // create edge
  13363. edge = new Edge(data, this, this.constants);
  13364. this.edges[id] = edge;
  13365. }
  13366. }
  13367. this._createBezierNodes();
  13368. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  13369. this._resetLevels();
  13370. this._setupHierarchicalLayout();
  13371. }
  13372. this.moving = true;
  13373. this._updateValueRange(edges);
  13374. };
  13375. /**
  13376. * Remove existing edges. Non existing ids will be ignored
  13377. * @param {Number[] | String[]} ids
  13378. * @private
  13379. */
  13380. Network.prototype._removeEdges = function (ids) {
  13381. var edges = this.edges;
  13382. for (var i = 0, len = ids.length; i < len; i++) {
  13383. var id = ids[i];
  13384. var edge = edges[id];
  13385. if (edge) {
  13386. if (edge.via != null) {
  13387. delete this.sectors['support']['nodes'][edge.via.id];
  13388. }
  13389. edge.disconnect();
  13390. delete edges[id];
  13391. }
  13392. }
  13393. this.moving = true;
  13394. this._updateValueRange(edges);
  13395. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  13396. this._resetLevels();
  13397. this._setupHierarchicalLayout();
  13398. }
  13399. this._updateCalculationNodes();
  13400. };
  13401. /**
  13402. * Reconnect all edges
  13403. * @private
  13404. */
  13405. Network.prototype._reconnectEdges = function() {
  13406. var id,
  13407. nodes = this.nodes,
  13408. edges = this.edges;
  13409. for (id in nodes) {
  13410. if (nodes.hasOwnProperty(id)) {
  13411. nodes[id].edges = [];
  13412. }
  13413. }
  13414. for (id in edges) {
  13415. if (edges.hasOwnProperty(id)) {
  13416. var edge = edges[id];
  13417. edge.from = null;
  13418. edge.to = null;
  13419. edge.connect();
  13420. }
  13421. }
  13422. };
  13423. /**
  13424. * Update the values of all object in the given array according to the current
  13425. * value range of the objects in the array.
  13426. * @param {Object} obj An object containing a set of Edges or Nodes
  13427. * The objects must have a method getValue() and
  13428. * setValueRange(min, max).
  13429. * @private
  13430. */
  13431. Network.prototype._updateValueRange = function(obj) {
  13432. var id;
  13433. // determine the range of the objects
  13434. var valueMin = undefined;
  13435. var valueMax = undefined;
  13436. for (id in obj) {
  13437. if (obj.hasOwnProperty(id)) {
  13438. var value = obj[id].getValue();
  13439. if (value !== undefined) {
  13440. valueMin = (valueMin === undefined) ? value : Math.min(value, valueMin);
  13441. valueMax = (valueMax === undefined) ? value : Math.max(value, valueMax);
  13442. }
  13443. }
  13444. }
  13445. // adjust the range of all objects
  13446. if (valueMin !== undefined && valueMax !== undefined) {
  13447. for (id in obj) {
  13448. if (obj.hasOwnProperty(id)) {
  13449. obj[id].setValueRange(valueMin, valueMax);
  13450. }
  13451. }
  13452. }
  13453. };
  13454. /**
  13455. * Redraw the network with the current data
  13456. * chart will be resized too.
  13457. */
  13458. Network.prototype.redraw = function() {
  13459. this.setSize(this.constants.width, this.constants.height);
  13460. this._redraw();
  13461. };
  13462. /**
  13463. * Redraw the network with the current data
  13464. * @private
  13465. */
  13466. Network.prototype._redraw = function() {
  13467. var ctx = this.frame.canvas.getContext('2d');
  13468. // clear the canvas
  13469. var w = this.frame.canvas.width;
  13470. var h = this.frame.canvas.height;
  13471. ctx.clearRect(0, 0, w, h);
  13472. // set scaling and translation
  13473. ctx.save();
  13474. ctx.translate(this.translation.x, this.translation.y);
  13475. ctx.scale(this.scale, this.scale);
  13476. this.canvasTopLeft = {
  13477. "x": this._XconvertDOMtoCanvas(0),
  13478. "y": this._YconvertDOMtoCanvas(0)
  13479. };
  13480. this.canvasBottomRight = {
  13481. "x": this._XconvertDOMtoCanvas(this.frame.canvas.clientWidth),
  13482. "y": this._YconvertDOMtoCanvas(this.frame.canvas.clientHeight)
  13483. };
  13484. this._doInAllSectors("_drawAllSectorNodes",ctx);
  13485. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideEdgesOnDrag == false) {
  13486. this._doInAllSectors("_drawEdges",ctx);
  13487. }
  13488. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideNodesOnDrag == false) {
  13489. this._doInAllSectors("_drawNodes",ctx,false);
  13490. }
  13491. if (this.controlNodesActive == true) {
  13492. this._doInAllSectors("_drawControlNodes",ctx);
  13493. }
  13494. // this._doInSupportSector("_drawNodes",ctx,true);
  13495. // this._drawTree(ctx,"#F00F0F");
  13496. // restore original scaling and translation
  13497. ctx.restore();
  13498. };
  13499. /**
  13500. * Set the translation of the network
  13501. * @param {Number} offsetX Horizontal offset
  13502. * @param {Number} offsetY Vertical offset
  13503. * @private
  13504. */
  13505. Network.prototype._setTranslation = function(offsetX, offsetY) {
  13506. if (this.translation === undefined) {
  13507. this.translation = {
  13508. x: 0,
  13509. y: 0
  13510. };
  13511. }
  13512. if (offsetX !== undefined) {
  13513. this.translation.x = offsetX;
  13514. }
  13515. if (offsetY !== undefined) {
  13516. this.translation.y = offsetY;
  13517. }
  13518. this.emit('viewChanged');
  13519. };
  13520. /**
  13521. * Get the translation of the network
  13522. * @return {Object} translation An object with parameters x and y, both a number
  13523. * @private
  13524. */
  13525. Network.prototype._getTranslation = function() {
  13526. return {
  13527. x: this.translation.x,
  13528. y: this.translation.y
  13529. };
  13530. };
  13531. /**
  13532. * Scale the network
  13533. * @param {Number} scale Scaling factor 1.0 is unscaled
  13534. * @private
  13535. */
  13536. Network.prototype._setScale = function(scale) {
  13537. this.scale = scale;
  13538. };
  13539. /**
  13540. * Get the current scale of the network
  13541. * @return {Number} scale Scaling factor 1.0 is unscaled
  13542. * @private
  13543. */
  13544. Network.prototype._getScale = function() {
  13545. return this.scale;
  13546. };
  13547. /**
  13548. * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to
  13549. * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  13550. * @param {number} x
  13551. * @returns {number}
  13552. * @private
  13553. */
  13554. Network.prototype._XconvertDOMtoCanvas = function(x) {
  13555. return (x - this.translation.x) / this.scale;
  13556. };
  13557. /**
  13558. * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  13559. * the X coordinate in DOM-space (coordinate point in browser relative to the container div)
  13560. * @param {number} x
  13561. * @returns {number}
  13562. * @private
  13563. */
  13564. Network.prototype._XconvertCanvasToDOM = function(x) {
  13565. return x * this.scale + this.translation.x;
  13566. };
  13567. /**
  13568. * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to
  13569. * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  13570. * @param {number} y
  13571. * @returns {number}
  13572. * @private
  13573. */
  13574. Network.prototype._YconvertDOMtoCanvas = function(y) {
  13575. return (y - this.translation.y) / this.scale;
  13576. };
  13577. /**
  13578. * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  13579. * the Y coordinate in DOM-space (coordinate point in browser relative to the container div)
  13580. * @param {number} y
  13581. * @returns {number}
  13582. * @private
  13583. */
  13584. Network.prototype._YconvertCanvasToDOM = function(y) {
  13585. return y * this.scale + this.translation.y ;
  13586. };
  13587. /**
  13588. *
  13589. * @param {object} pos = {x: number, y: number}
  13590. * @returns {{x: number, y: number}}
  13591. * @constructor
  13592. */
  13593. Network.prototype.canvasToDOM = function(pos) {
  13594. return {x:this._XconvertCanvasToDOM(pos.x),y:this._YconvertCanvasToDOM(pos.y)};
  13595. }
  13596. /**
  13597. *
  13598. * @param {object} pos = {x: number, y: number}
  13599. * @returns {{x: number, y: number}}
  13600. * @constructor
  13601. */
  13602. Network.prototype.DOMtoCanvas = function(pos) {
  13603. return {x:this._XconvertDOMtoCanvas(pos.x),y:this._YconvertDOMtoCanvas(pos.y)};
  13604. }
  13605. /**
  13606. * Redraw all nodes
  13607. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  13608. * @param {CanvasRenderingContext2D} ctx
  13609. * @param {Boolean} [alwaysShow]
  13610. * @private
  13611. */
  13612. Network.prototype._drawNodes = function(ctx,alwaysShow) {
  13613. if (alwaysShow === undefined) {
  13614. alwaysShow = false;
  13615. }
  13616. // first draw the unselected nodes
  13617. var nodes = this.nodes;
  13618. var selected = [];
  13619. for (var id in nodes) {
  13620. if (nodes.hasOwnProperty(id)) {
  13621. nodes[id].setScaleAndPos(this.scale,this.canvasTopLeft,this.canvasBottomRight);
  13622. if (nodes[id].isSelected()) {
  13623. selected.push(id);
  13624. }
  13625. else {
  13626. if (nodes[id].inArea() || alwaysShow) {
  13627. nodes[id].draw(ctx);
  13628. }
  13629. }
  13630. }
  13631. }
  13632. // draw the selected nodes on top
  13633. for (var s = 0, sMax = selected.length; s < sMax; s++) {
  13634. if (nodes[selected[s]].inArea() || alwaysShow) {
  13635. nodes[selected[s]].draw(ctx);
  13636. }
  13637. }
  13638. };
  13639. /**
  13640. * Redraw all edges
  13641. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  13642. * @param {CanvasRenderingContext2D} ctx
  13643. * @private
  13644. */
  13645. Network.prototype._drawEdges = function(ctx) {
  13646. var edges = this.edges;
  13647. for (var id in edges) {
  13648. if (edges.hasOwnProperty(id)) {
  13649. var edge = edges[id];
  13650. edge.setScale(this.scale);
  13651. if (edge.connected) {
  13652. edges[id].draw(ctx);
  13653. }
  13654. }
  13655. }
  13656. };
  13657. /**
  13658. * Redraw all edges
  13659. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  13660. * @param {CanvasRenderingContext2D} ctx
  13661. * @private
  13662. */
  13663. Network.prototype._drawControlNodes = function(ctx) {
  13664. var edges = this.edges;
  13665. for (var id in edges) {
  13666. if (edges.hasOwnProperty(id)) {
  13667. edges[id]._drawControlNodes(ctx);
  13668. }
  13669. }
  13670. };
  13671. /**
  13672. * Find a stable position for all nodes
  13673. * @private
  13674. */
  13675. Network.prototype._stabilize = function() {
  13676. if (this.constants.freezeForStabilization == true) {
  13677. this._freezeDefinedNodes();
  13678. }
  13679. // find stable position
  13680. var count = 0;
  13681. while (this.moving && count < this.constants.stabilizationIterations) {
  13682. this._physicsTick();
  13683. count++;
  13684. }
  13685. this.zoomExtent(false,true);
  13686. if (this.constants.freezeForStabilization == true) {
  13687. this._restoreFrozenNodes();
  13688. }
  13689. };
  13690. /**
  13691. * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization
  13692. * because only the supportnodes for the smoothCurves have to settle.
  13693. *
  13694. * @private
  13695. */
  13696. Network.prototype._freezeDefinedNodes = function() {
  13697. var nodes = this.nodes;
  13698. for (var id in nodes) {
  13699. if (nodes.hasOwnProperty(id)) {
  13700. if (nodes[id].x != null && nodes[id].y != null) {
  13701. nodes[id].fixedData.x = nodes[id].xFixed;
  13702. nodes[id].fixedData.y = nodes[id].yFixed;
  13703. nodes[id].xFixed = true;
  13704. nodes[id].yFixed = true;
  13705. }
  13706. }
  13707. }
  13708. };
  13709. /**
  13710. * Unfreezes the nodes that have been frozen by _freezeDefinedNodes.
  13711. *
  13712. * @private
  13713. */
  13714. Network.prototype._restoreFrozenNodes = function() {
  13715. var nodes = this.nodes;
  13716. for (var id in nodes) {
  13717. if (nodes.hasOwnProperty(id)) {
  13718. if (nodes[id].fixedData.x != null) {
  13719. nodes[id].xFixed = nodes[id].fixedData.x;
  13720. nodes[id].yFixed = nodes[id].fixedData.y;
  13721. }
  13722. }
  13723. }
  13724. };
  13725. /**
  13726. * Check if any of the nodes is still moving
  13727. * @param {number} vmin the minimum velocity considered as 'moving'
  13728. * @return {boolean} true if moving, false if non of the nodes is moving
  13729. * @private
  13730. */
  13731. Network.prototype._isMoving = function(vmin) {
  13732. var nodes = this.nodes;
  13733. for (var id in nodes) {
  13734. if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vmin)) {
  13735. return true;
  13736. }
  13737. }
  13738. return false;
  13739. };
  13740. /**
  13741. * /**
  13742. * Perform one discrete step for all nodes
  13743. *
  13744. * @private
  13745. */
  13746. Network.prototype._discreteStepNodes = function() {
  13747. var interval = this.physicsDiscreteStepsize;
  13748. var nodes = this.nodes;
  13749. var nodeId;
  13750. var nodesPresent = false;
  13751. if (this.constants.maxVelocity > 0) {
  13752. for (nodeId in nodes) {
  13753. if (nodes.hasOwnProperty(nodeId)) {
  13754. nodes[nodeId].discreteStepLimited(interval, this.constants.maxVelocity);
  13755. nodesPresent = true;
  13756. }
  13757. }
  13758. }
  13759. else {
  13760. for (nodeId in nodes) {
  13761. if (nodes.hasOwnProperty(nodeId)) {
  13762. nodes[nodeId].discreteStep(interval);
  13763. nodesPresent = true;
  13764. }
  13765. }
  13766. }
  13767. if (nodesPresent == true) {
  13768. var vminCorrected = this.constants.minVelocity / Math.max(this.scale,0.05);
  13769. if (vminCorrected > 0.5*this.constants.maxVelocity) {
  13770. return true;
  13771. }
  13772. else {
  13773. return this._isMoving(vminCorrected);
  13774. }
  13775. }
  13776. return false;
  13777. };
  13778. /**
  13779. * A single simulation step (or "tick") in the physics simulation
  13780. *
  13781. * @private
  13782. */
  13783. Network.prototype._physicsTick = function() {
  13784. if (!this.freezeSimulation) {
  13785. if (this.moving == true) {
  13786. var mainMovingStatus = false;
  13787. var supportMovingStatus = false;
  13788. this._doInAllActiveSectors("_initializeForceCalculation");
  13789. var mainMoving = this._doInAllActiveSectors("_discreteStepNodes");
  13790. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  13791. supportMovingStatus = this._doInSupportSector("_discreteStepNodes");
  13792. }
  13793. // gather movement data from all sectors, if one moves, we are NOT stabilzied
  13794. for (var i = 0; i < mainMoving.length; i++) {mainMovingStatus = mainMoving[0] || mainMovingStatus;}
  13795. // determine if the network has stabilzied
  13796. this.moving = mainMovingStatus || supportMovingStatus;
  13797. this.stabilizationIterations++;
  13798. }
  13799. }
  13800. };
  13801. /**
  13802. * This function runs one step of the animation. It calls an x amount of physics ticks and one render tick.
  13803. * It reschedules itself at the beginning of the function
  13804. *
  13805. * @private
  13806. */
  13807. Network.prototype._animationStep = function() {
  13808. // reset the timer so a new scheduled animation step can be set
  13809. this.timer = undefined;
  13810. // handle the keyboad movement
  13811. this._handleNavigation();
  13812. // this schedules a new animation step
  13813. this.start();
  13814. // start the physics simulation
  13815. var calculationTime = Date.now();
  13816. var maxSteps = 1;
  13817. this._physicsTick();
  13818. var timeRequired = Date.now() - calculationTime;
  13819. while (timeRequired < 0.9*(this.renderTimestep - this.renderTime) && maxSteps < this.maxPhysicsTicksPerRender) {
  13820. this._physicsTick();
  13821. timeRequired = Date.now() - calculationTime;
  13822. maxSteps++;
  13823. }
  13824. // start the rendering process
  13825. var renderTime = Date.now();
  13826. this._redraw();
  13827. this.renderTime = Date.now() - renderTime;
  13828. };
  13829. if (typeof window !== 'undefined') {
  13830. window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
  13831. window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  13832. }
  13833. /**
  13834. * Schedule a animation step with the refreshrate interval.
  13835. */
  13836. Network.prototype.start = function() {
  13837. if (this.moving == true || this.xIncrement != 0 || this.yIncrement != 0 || this.zoomIncrement != 0) {
  13838. if (!this.timer) {
  13839. var ua = navigator.userAgent.toLowerCase();
  13840. var requiresTimeout = false;
  13841. if (ua.indexOf('msie 9.0') != -1) { // IE 9
  13842. requiresTimeout = true;
  13843. }
  13844. else if (ua.indexOf('safari') != -1) { // safari
  13845. if (ua.indexOf('chrome') <= -1) {
  13846. requiresTimeout = true;
  13847. }
  13848. }
  13849. if (requiresTimeout == true) {
  13850. this.timer = window.setTimeout(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
  13851. }
  13852. else{
  13853. this.timer = window.requestAnimationFrame(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
  13854. }
  13855. }
  13856. }
  13857. else {
  13858. this._redraw();
  13859. if (this.stabilizationIterations > 0) {
  13860. // trigger the "stabilized" event.
  13861. // The event is triggered on the next tick, to prevent the case that
  13862. // it is fired while initializing the Network, in which case you would not
  13863. // be able to catch it
  13864. var me = this;
  13865. var params = {
  13866. iterations: me.stabilizationIterations
  13867. };
  13868. me.stabilizationIterations = 0;
  13869. setTimeout(function () {
  13870. me.emit("stabilized", params);
  13871. }, 0);
  13872. }
  13873. }
  13874. };
  13875. /**
  13876. * Move the network according to the keyboard presses.
  13877. *
  13878. * @private
  13879. */
  13880. Network.prototype._handleNavigation = function() {
  13881. if (this.xIncrement != 0 || this.yIncrement != 0) {
  13882. var translation = this._getTranslation();
  13883. this._setTranslation(translation.x+this.xIncrement, translation.y+this.yIncrement);
  13884. }
  13885. if (this.zoomIncrement != 0) {
  13886. var center = {
  13887. x: this.frame.canvas.clientWidth / 2,
  13888. y: this.frame.canvas.clientHeight / 2
  13889. };
  13890. this._zoom(this.scale*(1 + this.zoomIncrement), center);
  13891. }
  13892. };
  13893. /**
  13894. * Freeze the _animationStep
  13895. */
  13896. Network.prototype.toggleFreeze = function() {
  13897. if (this.freezeSimulation == false) {
  13898. this.freezeSimulation = true;
  13899. }
  13900. else {
  13901. this.freezeSimulation = false;
  13902. this.start();
  13903. }
  13904. };
  13905. /**
  13906. * This function cleans the support nodes if they are not needed and adds them when they are.
  13907. *
  13908. * @param {boolean} [disableStart]
  13909. * @private
  13910. */
  13911. Network.prototype._configureSmoothCurves = function(disableStart) {
  13912. if (disableStart === undefined) {
  13913. disableStart = true;
  13914. }
  13915. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  13916. this._createBezierNodes();
  13917. // cleanup unused support nodes
  13918. for (var nodeId in this.sectors['support']['nodes']) {
  13919. if (this.sectors['support']['nodes'].hasOwnProperty(nodeId)) {
  13920. if (this.edges[this.sectors['support']['nodes'][nodeId].parentEdgeId] === undefined) {
  13921. delete this.sectors['support']['nodes'][nodeId];
  13922. }
  13923. }
  13924. }
  13925. }
  13926. else {
  13927. // delete the support nodes
  13928. this.sectors['support']['nodes'] = {};
  13929. for (var edgeId in this.edges) {
  13930. if (this.edges.hasOwnProperty(edgeId)) {
  13931. this.edges[edgeId].via = null;
  13932. }
  13933. }
  13934. }
  13935. this._updateCalculationNodes();
  13936. if (!disableStart) {
  13937. this.moving = true;
  13938. this.start();
  13939. }
  13940. };
  13941. /**
  13942. * Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
  13943. * are used for the force calculation.
  13944. *
  13945. * @private
  13946. */
  13947. Network.prototype._createBezierNodes = function() {
  13948. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  13949. for (var edgeId in this.edges) {
  13950. if (this.edges.hasOwnProperty(edgeId)) {
  13951. var edge = this.edges[edgeId];
  13952. if (edge.via == null) {
  13953. var nodeId = "edgeId:".concat(edge.id);
  13954. this.sectors['support']['nodes'][nodeId] = new Node(
  13955. {id:nodeId,
  13956. mass:1,
  13957. shape:'circle',
  13958. image:"",
  13959. internalMultiplier:1
  13960. },{},{},this.constants);
  13961. edge.via = this.sectors['support']['nodes'][nodeId];
  13962. edge.via.parentEdgeId = edge.id;
  13963. edge.positionBezierNode();
  13964. }
  13965. }
  13966. }
  13967. }
  13968. };
  13969. /**
  13970. * load the functions that load the mixins into the prototype.
  13971. *
  13972. * @private
  13973. */
  13974. Network.prototype._initializeMixinLoaders = function () {
  13975. for (var mixin in MixinLoader) {
  13976. if (MixinLoader.hasOwnProperty(mixin)) {
  13977. Network.prototype[mixin] = MixinLoader[mixin];
  13978. }
  13979. }
  13980. };
  13981. /**
  13982. * Load the XY positions of the nodes into the dataset.
  13983. */
  13984. Network.prototype.storePosition = function() {
  13985. var dataArray = [];
  13986. for (var nodeId in this.nodes) {
  13987. if (this.nodes.hasOwnProperty(nodeId)) {
  13988. var node = this.nodes[nodeId];
  13989. var allowedToMoveX = !this.nodes.xFixed;
  13990. var allowedToMoveY = !this.nodes.yFixed;
  13991. if (this.nodesData._data[nodeId].x != Math.round(node.x) || this.nodesData._data[nodeId].y != Math.round(node.y)) {
  13992. dataArray.push({id:nodeId,x:Math.round(node.x),y:Math.round(node.y),allowedToMoveX:allowedToMoveX,allowedToMoveY:allowedToMoveY});
  13993. }
  13994. }
  13995. }
  13996. this.nodesData.update(dataArray);
  13997. };
  13998. /**
  13999. * Center a node in view.
  14000. *
  14001. * @param {Number} nodeId
  14002. * @param {Number} [zoomLevel]
  14003. */
  14004. Network.prototype.focusOnNode = function (nodeId, zoomLevel) {
  14005. if (this.nodes.hasOwnProperty(nodeId)) {
  14006. if (zoomLevel === undefined) {
  14007. zoomLevel = this._getScale();
  14008. }
  14009. var nodePosition= {x: this.nodes[nodeId].x, y: this.nodes[nodeId].y};
  14010. var requiredScale = zoomLevel;
  14011. this._setScale(requiredScale);
  14012. var canvasCenter = this.DOMtoCanvas({x:0.5 * this.frame.canvas.width,y:0.5 * this.frame.canvas.height});
  14013. var translation = this._getTranslation();
  14014. var distanceFromCenter = {x:canvasCenter.x - nodePosition.x,
  14015. y:canvasCenter.y - nodePosition.y};
  14016. this._setTranslation(translation.x + requiredScale * distanceFromCenter.x,
  14017. translation.y + requiredScale * distanceFromCenter.y);
  14018. this.redraw();
  14019. }
  14020. else {
  14021. console.log("This nodeId cannot be found.");
  14022. }
  14023. };
  14024. /**
  14025. * Returns true when the Timeline is active.
  14026. * @returns {boolean}
  14027. */
  14028. Network.prototype.isActive = function () {
  14029. return !this.activator || this.activator.active;
  14030. };
  14031. module.exports = Network;
  14032. /***/ },
  14033. /* 33 */
  14034. /***/ function(module, exports, __webpack_require__) {
  14035. var util = __webpack_require__(1);
  14036. var Node = __webpack_require__(36);
  14037. /**
  14038. * @class Edge
  14039. *
  14040. * A edge connects two nodes
  14041. * @param {Object} properties Object with properties. Must contain
  14042. * At least properties from and to.
  14043. * Available properties: from (number),
  14044. * to (number), label (string, color (string),
  14045. * width (number), style (string),
  14046. * length (number), title (string)
  14047. * @param {Network} network A Network object, used to find and edge to
  14048. * nodes.
  14049. * @param {Object} constants An object with default values for
  14050. * example for the color
  14051. */
  14052. function Edge (properties, network, networkConstants) {
  14053. if (!network) {
  14054. throw "No network provided";
  14055. }
  14056. var fields = ['edges','physics'];
  14057. var constants = util.selectiveBridgeObject(fields,networkConstants);
  14058. this.options = constants.edges;
  14059. this.physics = constants.physics;
  14060. this.options['smoothCurves'] = networkConstants['smoothCurves'];
  14061. this.network = network;
  14062. // initialize variables
  14063. this.id = undefined;
  14064. this.fromId = undefined;
  14065. this.toId = undefined;
  14066. this.title = undefined;
  14067. this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
  14068. this.value = undefined;
  14069. this.selected = false;
  14070. this.hover = false;
  14071. this.from = null; // a node
  14072. this.to = null; // a node
  14073. this.via = null; // a temp node
  14074. // we use this to be able to reconnect the edge to a cluster if its node is put into a cluster
  14075. // by storing the original information we can revert to the original connection when the cluser is opened.
  14076. this.originalFromId = [];
  14077. this.originalToId = [];
  14078. this.connected = false;
  14079. this.widthFixed = false;
  14080. this.lengthFixed = false;
  14081. this.setProperties(properties);
  14082. this.controlNodesEnabled = false;
  14083. this.controlNodes = {from:null, to:null, positions:{}};
  14084. this.connectedNode = null;
  14085. }
  14086. /**
  14087. * Set or overwrite properties for the edge
  14088. * @param {Object} properties an object with properties
  14089. * @param {Object} constants and object with default, global properties
  14090. */
  14091. Edge.prototype.setProperties = function(properties) {
  14092. if (!properties) {
  14093. return;
  14094. }
  14095. var fields = ['style','fontSize','fontFace','fontColor','fontFill','width',
  14096. 'widthSelectionMultiplier','hoverWidth','arrowScaleFactor','dash'
  14097. ];
  14098. util.selectiveDeepExtend(fields, this.options, properties);
  14099. if (properties.from !== undefined) {this.fromId = properties.from;}
  14100. if (properties.to !== undefined) {this.toId = properties.to;}
  14101. if (properties.id !== undefined) {this.id = properties.id;}
  14102. if (properties.label !== undefined) {this.label = properties.label;}
  14103. if (properties.title !== undefined) {this.title = properties.title;}
  14104. if (properties.value !== undefined) {this.value = properties.value;}
  14105. if (properties.length !== undefined) {this.physics.springLength = properties.length;}
  14106. // scale the arrow
  14107. if (properties.arrowScaleFactor !== undefined) {this.options.arrowScaleFactor = properties.arrowScaleFactor;}
  14108. if (properties.inheritColor !== undefined) {this.options.inheritColor = properties.inheritColor;}
  14109. if (properties.color !== undefined) {
  14110. this.options.inheritColor = false;
  14111. if (util.isString(properties.color)) {
  14112. this.options.color.color = properties.color;
  14113. this.options.color.highlight = properties.color;
  14114. }
  14115. else {
  14116. if (properties.color.color !== undefined) {this.options.color.color = properties.color.color;}
  14117. if (properties.color.highlight !== undefined) {this.options.color.highlight = properties.color.highlight;}
  14118. if (properties.color.hover !== undefined) {this.options.color.hover = properties.color.hover;}
  14119. }
  14120. }
  14121. // A node is connected when it has a from and to node.
  14122. this.connect();
  14123. this.widthFixed = this.widthFixed || (properties.width !== undefined);
  14124. this.lengthFixed = this.lengthFixed || (properties.length !== undefined);
  14125. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  14126. // set draw method based on style
  14127. switch (this.options.style) {
  14128. case 'line': this.draw = this._drawLine; break;
  14129. case 'arrow': this.draw = this._drawArrow; break;
  14130. case 'arrow-center': this.draw = this._drawArrowCenter; break;
  14131. case 'dash-line': this.draw = this._drawDashLine; break;
  14132. default: this.draw = this._drawLine; break;
  14133. }
  14134. };
  14135. /**
  14136. * Connect an edge to its nodes
  14137. */
  14138. Edge.prototype.connect = function () {
  14139. this.disconnect();
  14140. this.from = this.network.nodes[this.fromId] || null;
  14141. this.to = this.network.nodes[this.toId] || null;
  14142. this.connected = (this.from && this.to);
  14143. if (this.connected) {
  14144. this.from.attachEdge(this);
  14145. this.to.attachEdge(this);
  14146. }
  14147. else {
  14148. if (this.from) {
  14149. this.from.detachEdge(this);
  14150. }
  14151. if (this.to) {
  14152. this.to.detachEdge(this);
  14153. }
  14154. }
  14155. };
  14156. /**
  14157. * Disconnect an edge from its nodes
  14158. */
  14159. Edge.prototype.disconnect = function () {
  14160. if (this.from) {
  14161. this.from.detachEdge(this);
  14162. this.from = null;
  14163. }
  14164. if (this.to) {
  14165. this.to.detachEdge(this);
  14166. this.to = null;
  14167. }
  14168. this.connected = false;
  14169. };
  14170. /**
  14171. * get the title of this edge.
  14172. * @return {string} title The title of the edge, or undefined when no title
  14173. * has been set.
  14174. */
  14175. Edge.prototype.getTitle = function() {
  14176. return typeof this.title === "function" ? this.title() : this.title;
  14177. };
  14178. /**
  14179. * Retrieve the value of the edge. Can be undefined
  14180. * @return {Number} value
  14181. */
  14182. Edge.prototype.getValue = function() {
  14183. return this.value;
  14184. };
  14185. /**
  14186. * Adjust the value range of the edge. The edge will adjust it's width
  14187. * based on its value.
  14188. * @param {Number} min
  14189. * @param {Number} max
  14190. */
  14191. Edge.prototype.setValueRange = function(min, max) {
  14192. if (!this.widthFixed && this.value !== undefined) {
  14193. var scale = (this.options.widthMax - this.options.widthMin) / (max - min);
  14194. this.options.width= (this.value - min) * scale + this.options.widthMin;
  14195. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  14196. }
  14197. };
  14198. /**
  14199. * Redraw a edge
  14200. * Draw this edge in the given canvas
  14201. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  14202. * @param {CanvasRenderingContext2D} ctx
  14203. */
  14204. Edge.prototype.draw = function(ctx) {
  14205. throw "Method draw not initialized in edge";
  14206. };
  14207. /**
  14208. * Check if this object is overlapping with the provided object
  14209. * @param {Object} obj an object with parameters left, top
  14210. * @return {boolean} True if location is located on the edge
  14211. */
  14212. Edge.prototype.isOverlappingWith = function(obj) {
  14213. if (this.connected) {
  14214. var distMax = 10;
  14215. var xFrom = this.from.x;
  14216. var yFrom = this.from.y;
  14217. var xTo = this.to.x;
  14218. var yTo = this.to.y;
  14219. var xObj = obj.left;
  14220. var yObj = obj.top;
  14221. var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  14222. return (dist < distMax);
  14223. }
  14224. else {
  14225. return false
  14226. }
  14227. };
  14228. Edge.prototype._getColor = function() {
  14229. var colorObj = this.options.color;
  14230. if (this.options.inheritColor == "to") {
  14231. colorObj = {
  14232. highlight: this.to.options.color.highlight.border,
  14233. hover: this.to.options.color.hover.border,
  14234. color: this.to.options.color.border
  14235. };
  14236. }
  14237. else if (this.options.inheritColor == "from" || this.options.inheritColor == true) {
  14238. colorObj = {
  14239. highlight: this.from.options.color.highlight.border,
  14240. hover: this.from.options.color.hover.border,
  14241. color: this.from.options.color.border
  14242. };
  14243. }
  14244. if (this.selected == true) {return colorObj.highlight;}
  14245. else if (this.hover == true) {return colorObj.hover;}
  14246. else {return colorObj.color;}
  14247. }
  14248. /**
  14249. * Redraw a edge as a line
  14250. * Draw this edge in the given canvas
  14251. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  14252. * @param {CanvasRenderingContext2D} ctx
  14253. * @private
  14254. */
  14255. Edge.prototype._drawLine = function(ctx) {
  14256. // set style
  14257. ctx.strokeStyle = this._getColor();
  14258. ctx.lineWidth = this._getLineWidth();
  14259. if (this.from != this.to) {
  14260. // draw line
  14261. var via = this._line(ctx);
  14262. // draw label
  14263. var point;
  14264. if (this.label) {
  14265. if (this.options.smoothCurves.enabled == true && via != null) {
  14266. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  14267. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  14268. point = {x:midpointX, y:midpointY};
  14269. }
  14270. else {
  14271. point = this._pointOnLine(0.5);
  14272. }
  14273. this._label(ctx, this.label, point.x, point.y);
  14274. }
  14275. }
  14276. else {
  14277. var x, y;
  14278. var radius = this.physics.springLength / 4;
  14279. var node = this.from;
  14280. if (!node.width) {
  14281. node.resize(ctx);
  14282. }
  14283. if (node.width > node.height) {
  14284. x = node.x + node.width / 2;
  14285. y = node.y - radius;
  14286. }
  14287. else {
  14288. x = node.x + radius;
  14289. y = node.y - node.height / 2;
  14290. }
  14291. this._circle(ctx, x, y, radius);
  14292. point = this._pointOnCircle(x, y, radius, 0.5);
  14293. this._label(ctx, this.label, point.x, point.y);
  14294. }
  14295. };
  14296. /**
  14297. * Get the line width of the edge. Depends on width and whether one of the
  14298. * connected nodes is selected.
  14299. * @return {Number} width
  14300. * @private
  14301. */
  14302. Edge.prototype._getLineWidth = function() {
  14303. if (this.selected == true) {
  14304. return Math.max(Math.min(this.widthSelected, this.options.widthMax), 0.3*this.networkScaleInv);
  14305. }
  14306. else {
  14307. if (this.hover == true) {
  14308. return Math.max(Math.min(this.options.hoverWidth, this.options.widthMax), 0.3*this.networkScaleInv);
  14309. }
  14310. else {
  14311. return Math.max(this.options.width, 0.3*this.networkScaleInv);
  14312. }
  14313. }
  14314. };
  14315. Edge.prototype._getViaCoordinates = function () {
  14316. var xVia = null;
  14317. var yVia = null;
  14318. var factor = this.options.smoothCurves.roundness;
  14319. var type = this.options.smoothCurves.type;
  14320. var dx = Math.abs(this.from.x - this.to.x);
  14321. var dy = Math.abs(this.from.y - this.to.y);
  14322. if (type == 'discrete' || type == 'diagonalCross') {
  14323. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  14324. if (this.from.y > this.to.y) {
  14325. if (this.from.x < this.to.x) {
  14326. xVia = this.from.x + factor * dy;
  14327. yVia = this.from.y - factor * dy;
  14328. }
  14329. else if (this.from.x > this.to.x) {
  14330. xVia = this.from.x - factor * dy;
  14331. yVia = this.from.y - factor * dy;
  14332. }
  14333. }
  14334. else if (this.from.y < this.to.y) {
  14335. if (this.from.x < this.to.x) {
  14336. xVia = this.from.x + factor * dy;
  14337. yVia = this.from.y + factor * dy;
  14338. }
  14339. else if (this.from.x > this.to.x) {
  14340. xVia = this.from.x - factor * dy;
  14341. yVia = this.from.y + factor * dy;
  14342. }
  14343. }
  14344. if (type == "discrete") {
  14345. xVia = dx < factor * dy ? this.from.x : xVia;
  14346. }
  14347. }
  14348. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  14349. if (this.from.y > this.to.y) {
  14350. if (this.from.x < this.to.x) {
  14351. xVia = this.from.x + factor * dx;
  14352. yVia = this.from.y - factor * dx;
  14353. }
  14354. else if (this.from.x > this.to.x) {
  14355. xVia = this.from.x - factor * dx;
  14356. yVia = this.from.y - factor * dx;
  14357. }
  14358. }
  14359. else if (this.from.y < this.to.y) {
  14360. if (this.from.x < this.to.x) {
  14361. xVia = this.from.x + factor * dx;
  14362. yVia = this.from.y + factor * dx;
  14363. }
  14364. else if (this.from.x > this.to.x) {
  14365. xVia = this.from.x - factor * dx;
  14366. yVia = this.from.y + factor * dx;
  14367. }
  14368. }
  14369. if (type == "discrete") {
  14370. yVia = dy < factor * dx ? this.from.y : yVia;
  14371. }
  14372. }
  14373. }
  14374. else if (type == "straightCross") {
  14375. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { // up - down
  14376. xVia = this.from.x;
  14377. if (this.from.y < this.to.y) {
  14378. yVia = this.to.y - (1-factor) * dy;
  14379. }
  14380. else {
  14381. yVia = this.to.y + (1-factor) * dy;
  14382. }
  14383. }
  14384. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { // left - right
  14385. if (this.from.x < this.to.x) {
  14386. xVia = this.to.x - (1-factor) * dx;
  14387. }
  14388. else {
  14389. xVia = this.to.x + (1-factor) * dx;
  14390. }
  14391. yVia = this.from.y;
  14392. }
  14393. }
  14394. else if (type == 'horizontal') {
  14395. if (this.from.x < this.to.x) {
  14396. xVia = this.to.x - (1-factor) * dx;
  14397. }
  14398. else {
  14399. xVia = this.to.x + (1-factor) * dx;
  14400. }
  14401. yVia = this.from.y;
  14402. }
  14403. else if (type == 'vertical') {
  14404. xVia = this.from.x;
  14405. if (this.from.y < this.to.y) {
  14406. yVia = this.to.y - (1-factor) * dy;
  14407. }
  14408. else {
  14409. yVia = this.to.y + (1-factor) * dy;
  14410. }
  14411. }
  14412. else { // continuous
  14413. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  14414. if (this.from.y > this.to.y) {
  14415. if (this.from.x < this.to.x) {
  14416. // console.log(1)
  14417. xVia = this.from.x + factor * dy;
  14418. yVia = this.from.y - factor * dy;
  14419. xVia = this.to.x < xVia ? this.to.x : xVia;
  14420. }
  14421. else if (this.from.x > this.to.x) {
  14422. // console.log(2)
  14423. xVia = this.from.x - factor * dy;
  14424. yVia = this.from.y - factor * dy;
  14425. xVia = this.to.x > xVia ? this.to.x :xVia;
  14426. }
  14427. }
  14428. else if (this.from.y < this.to.y) {
  14429. if (this.from.x < this.to.x) {
  14430. // console.log(3)
  14431. xVia = this.from.x + factor * dy;
  14432. yVia = this.from.y + factor * dy;
  14433. xVia = this.to.x < xVia ? this.to.x : xVia;
  14434. }
  14435. else if (this.from.x > this.to.x) {
  14436. // console.log(4, this.from.x, this.to.x)
  14437. xVia = this.from.x - factor * dy;
  14438. yVia = this.from.y + factor * dy;
  14439. xVia = this.to.x > xVia ? this.to.x : xVia;
  14440. }
  14441. }
  14442. }
  14443. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  14444. if (this.from.y > this.to.y) {
  14445. if (this.from.x < this.to.x) {
  14446. // console.log(5)
  14447. xVia = this.from.x + factor * dx;
  14448. yVia = this.from.y - factor * dx;
  14449. yVia = this.to.y > yVia ? this.to.y : yVia;
  14450. }
  14451. else if (this.from.x > this.to.x) {
  14452. // console.log(6)
  14453. xVia = this.from.x - factor * dx;
  14454. yVia = this.from.y - factor * dx;
  14455. yVia = this.to.y > yVia ? this.to.y : yVia;
  14456. }
  14457. }
  14458. else if (this.from.y < this.to.y) {
  14459. if (this.from.x < this.to.x) {
  14460. // console.log(7)
  14461. xVia = this.from.x + factor * dx;
  14462. yVia = this.from.y + factor * dx;
  14463. yVia = this.to.y < yVia ? this.to.y : yVia;
  14464. }
  14465. else if (this.from.x > this.to.x) {
  14466. // console.log(8)
  14467. xVia = this.from.x - factor * dx;
  14468. yVia = this.from.y + factor * dx;
  14469. yVia = this.to.y < yVia ? this.to.y : yVia;
  14470. }
  14471. }
  14472. }
  14473. }
  14474. return {x:xVia, y:yVia};
  14475. }
  14476. /**
  14477. * Draw a line between two nodes
  14478. * @param {CanvasRenderingContext2D} ctx
  14479. * @private
  14480. */
  14481. Edge.prototype._line = function (ctx) {
  14482. // draw a straight line
  14483. ctx.beginPath();
  14484. ctx.moveTo(this.from.x, this.from.y);
  14485. if (this.options.smoothCurves.enabled == true) {
  14486. if (this.options.smoothCurves.dynamic == false) {
  14487. var via = this._getViaCoordinates();
  14488. if (via.x == null) {
  14489. ctx.lineTo(this.to.x, this.to.y);
  14490. ctx.stroke();
  14491. return null;
  14492. }
  14493. else {
  14494. // this.via.x = via.x;
  14495. // this.via.y = via.y;
  14496. ctx.quadraticCurveTo(via.x,via.y,this.to.x, this.to.y);
  14497. ctx.stroke();
  14498. return via;
  14499. }
  14500. }
  14501. else {
  14502. ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
  14503. ctx.stroke();
  14504. return this.via;
  14505. }
  14506. }
  14507. else {
  14508. ctx.lineTo(this.to.x, this.to.y);
  14509. ctx.stroke();
  14510. return null;
  14511. }
  14512. };
  14513. /**
  14514. * Draw a line from a node to itself, a circle
  14515. * @param {CanvasRenderingContext2D} ctx
  14516. * @param {Number} x
  14517. * @param {Number} y
  14518. * @param {Number} radius
  14519. * @private
  14520. */
  14521. Edge.prototype._circle = function (ctx, x, y, radius) {
  14522. // draw a circle
  14523. ctx.beginPath();
  14524. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  14525. ctx.stroke();
  14526. };
  14527. /**
  14528. * Draw label with white background and with the middle at (x, y)
  14529. * @param {CanvasRenderingContext2D} ctx
  14530. * @param {String} text
  14531. * @param {Number} x
  14532. * @param {Number} y
  14533. * @private
  14534. */
  14535. Edge.prototype._label = function (ctx, text, x, y) {
  14536. if (text) {
  14537. // TODO: cache the calculated size
  14538. ctx.font = ((this.from.selected || this.to.selected) ? "bold " : "") +
  14539. this.options.fontSize + "px " + this.options.fontFace;
  14540. ctx.fillStyle = this.options.fontFill;
  14541. var width = ctx.measureText(text).width;
  14542. var height = this.options.fontSize;
  14543. var left = x - width / 2;
  14544. var top = y - height / 2;
  14545. ctx.fillRect(left, top, width, height);
  14546. // draw text
  14547. ctx.fillStyle = this.options.fontColor || "black";
  14548. ctx.textAlign = "left";
  14549. ctx.textBaseline = "top";
  14550. ctx.fillText(text, left, top);
  14551. }
  14552. };
  14553. /**
  14554. * Redraw a edge as a dashed line
  14555. * Draw this edge in the given canvas
  14556. * @author David Jordan
  14557. * @date 2012-08-08
  14558. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  14559. * @param {CanvasRenderingContext2D} ctx
  14560. * @private
  14561. */
  14562. Edge.prototype._drawDashLine = function(ctx) {
  14563. // set style
  14564. if (this.selected == true) {ctx.strokeStyle = this.options.color.highlight;}
  14565. else if (this.hover == true) {ctx.strokeStyle = this.options.color.hover;}
  14566. else {ctx.strokeStyle = this.options.color.color;}
  14567. ctx.lineWidth = this._getLineWidth();
  14568. var via = null;
  14569. // only firefox and chrome support this method, else we use the legacy one.
  14570. if (ctx.mozDash !== undefined || ctx.setLineDash !== undefined) {
  14571. // configure the dash pattern
  14572. var pattern = [0];
  14573. if (this.options.dash.length !== undefined && this.options.dash.gap !== undefined) {
  14574. pattern = [this.options.dash.length,this.options.dash.gap];
  14575. }
  14576. else {
  14577. pattern = [5,5];
  14578. }
  14579. // set dash settings for chrome or firefox
  14580. if (typeof ctx.setLineDash !== 'undefined') { //Chrome
  14581. ctx.setLineDash(pattern);
  14582. ctx.lineDashOffset = 0;
  14583. } else { //Firefox
  14584. ctx.mozDash = pattern;
  14585. ctx.mozDashOffset = 0;
  14586. }
  14587. // draw the line
  14588. via = this._line(ctx);
  14589. // restore the dash settings.
  14590. if (typeof ctx.setLineDash !== 'undefined') { //Chrome
  14591. ctx.setLineDash([0]);
  14592. ctx.lineDashOffset = 0;
  14593. } else { //Firefox
  14594. ctx.mozDash = [0];
  14595. ctx.mozDashOffset = 0;
  14596. }
  14597. }
  14598. else { // unsupporting smooth lines
  14599. // draw dashed line
  14600. ctx.beginPath();
  14601. ctx.lineCap = 'round';
  14602. if (this.options.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
  14603. {
  14604. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  14605. [this.options.dash.length,this.options.dash.gap,this.options.dash.altLength,this.options.dash.gap]);
  14606. }
  14607. 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
  14608. {
  14609. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  14610. [this.options.dash.length,this.options.dash.gap]);
  14611. }
  14612. else //If all else fails draw a line
  14613. {
  14614. ctx.moveTo(this.from.x, this.from.y);
  14615. ctx.lineTo(this.to.x, this.to.y);
  14616. }
  14617. ctx.stroke();
  14618. }
  14619. // draw label
  14620. if (this.label) {
  14621. var point;
  14622. if (this.options.smoothCurves.enabled == true && via != null) {
  14623. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  14624. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  14625. point = {x:midpointX, y:midpointY};
  14626. }
  14627. else {
  14628. point = this._pointOnLine(0.5);
  14629. }
  14630. this._label(ctx, this.label, point.x, point.y);
  14631. }
  14632. };
  14633. /**
  14634. * Get a point on a line
  14635. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  14636. * @return {Object} point
  14637. * @private
  14638. */
  14639. Edge.prototype._pointOnLine = function (percentage) {
  14640. return {
  14641. x: (1 - percentage) * this.from.x + percentage * this.to.x,
  14642. y: (1 - percentage) * this.from.y + percentage * this.to.y
  14643. }
  14644. };
  14645. /**
  14646. * Get a point on a circle
  14647. * @param {Number} x
  14648. * @param {Number} y
  14649. * @param {Number} radius
  14650. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  14651. * @return {Object} point
  14652. * @private
  14653. */
  14654. Edge.prototype._pointOnCircle = function (x, y, radius, percentage) {
  14655. var angle = (percentage - 3/8) * 2 * Math.PI;
  14656. return {
  14657. x: x + radius * Math.cos(angle),
  14658. y: y - radius * Math.sin(angle)
  14659. }
  14660. };
  14661. /**
  14662. * Redraw a edge as a line with an arrow halfway the line
  14663. * Draw this edge in the given canvas
  14664. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  14665. * @param {CanvasRenderingContext2D} ctx
  14666. * @private
  14667. */
  14668. Edge.prototype._drawArrowCenter = function(ctx) {
  14669. var point;
  14670. // set style
  14671. if (this.selected == true) {ctx.strokeStyle = this.options.color.highlight; ctx.fillStyle = this.options.color.highlight;}
  14672. else if (this.hover == true) {ctx.strokeStyle = this.options.color.hover; ctx.fillStyle = this.options.color.hover;}
  14673. else {ctx.strokeStyle = this.options.color.color; ctx.fillStyle = this.options.color.color;}
  14674. ctx.lineWidth = this._getLineWidth();
  14675. if (this.from != this.to) {
  14676. // draw line
  14677. var via = this._line(ctx);
  14678. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  14679. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  14680. // draw an arrow halfway the line
  14681. if (this.options.smoothCurves.enabled == true && via != null) {
  14682. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  14683. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  14684. point = {x:midpointX, y:midpointY};
  14685. }
  14686. else {
  14687. point = this._pointOnLine(0.5);
  14688. }
  14689. ctx.arrow(point.x, point.y, angle, length);
  14690. ctx.fill();
  14691. ctx.stroke();
  14692. // draw label
  14693. if (this.label) {
  14694. this._label(ctx, this.label, point.x, point.y);
  14695. }
  14696. }
  14697. else {
  14698. // draw circle
  14699. var x, y;
  14700. var radius = 0.25 * Math.max(100,this.physics.springLength);
  14701. var node = this.from;
  14702. if (!node.width) {
  14703. node.resize(ctx);
  14704. }
  14705. if (node.width > node.height) {
  14706. x = node.x + node.width * 0.5;
  14707. y = node.y - radius;
  14708. }
  14709. else {
  14710. x = node.x + radius;
  14711. y = node.y - node.height * 0.5;
  14712. }
  14713. this._circle(ctx, x, y, radius);
  14714. // draw all arrows
  14715. var angle = 0.2 * Math.PI;
  14716. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  14717. point = this._pointOnCircle(x, y, radius, 0.5);
  14718. ctx.arrow(point.x, point.y, angle, length);
  14719. ctx.fill();
  14720. ctx.stroke();
  14721. // draw label
  14722. if (this.label) {
  14723. point = this._pointOnCircle(x, y, radius, 0.5);
  14724. this._label(ctx, this.label, point.x, point.y);
  14725. }
  14726. }
  14727. };
  14728. /**
  14729. * Redraw a edge as a line with an arrow
  14730. * Draw this edge in the given canvas
  14731. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  14732. * @param {CanvasRenderingContext2D} ctx
  14733. * @private
  14734. */
  14735. Edge.prototype._drawArrow = function(ctx) {
  14736. // set style
  14737. if (this.selected == true) {ctx.strokeStyle = this.options.color.highlight; ctx.fillStyle = this.options.color.highlight;}
  14738. else if (this.hover == true) {ctx.strokeStyle = this.options.color.hover; ctx.fillStyle = this.options.color.hover;}
  14739. else {ctx.strokeStyle = this.options.color.color; ctx.fillStyle = this.options.color.color;}
  14740. ctx.lineWidth = this._getLineWidth();
  14741. var angle, length;
  14742. //draw a line
  14743. if (this.from != this.to) {
  14744. angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  14745. var dx = (this.to.x - this.from.x);
  14746. var dy = (this.to.y - this.from.y);
  14747. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  14748. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  14749. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  14750. var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  14751. var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  14752. var via;
  14753. if (this.options.smoothCurves.dynamic == true && this.options.smoothCurves.enabled == true ) {
  14754. via = this.via;
  14755. }
  14756. else if (this.options.smoothCurves.enabled == true) {
  14757. via = this._getViaCoordinates();
  14758. }
  14759. if (this.options.smoothCurves.enabled == true && via.x != null) {
  14760. angle = Math.atan2((this.to.y - via.y), (this.to.x - via.x));
  14761. dx = (this.to.x - via.x);
  14762. dy = (this.to.y - via.y);
  14763. edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  14764. }
  14765. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  14766. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  14767. var xTo,yTo;
  14768. if (this.options.smoothCurves.enabled == true && via.x != null) {
  14769. xTo = (1 - toBorderPoint) * via.x + toBorderPoint * this.to.x;
  14770. yTo = (1 - toBorderPoint) * via.y + toBorderPoint * this.to.y;
  14771. }
  14772. else {
  14773. xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  14774. yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  14775. }
  14776. ctx.beginPath();
  14777. ctx.moveTo(xFrom,yFrom);
  14778. if (this.options.smoothCurves.enabled == true && via.x != null) {
  14779. ctx.quadraticCurveTo(via.x,via.y,xTo, yTo);
  14780. }
  14781. else {
  14782. ctx.lineTo(xTo, yTo);
  14783. }
  14784. ctx.stroke();
  14785. // draw arrow at the end of the line
  14786. length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  14787. ctx.arrow(xTo, yTo, angle, length);
  14788. ctx.fill();
  14789. ctx.stroke();
  14790. // draw label
  14791. if (this.label) {
  14792. var point;
  14793. if (this.options.smoothCurves.enabled == true && via != null) {
  14794. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  14795. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  14796. point = {x:midpointX, y:midpointY};
  14797. }
  14798. else {
  14799. point = this._pointOnLine(0.5);
  14800. }
  14801. this._label(ctx, this.label, point.x, point.y);
  14802. }
  14803. }
  14804. else {
  14805. // draw circle
  14806. var node = this.from;
  14807. var x, y, arrow;
  14808. var radius = 0.25 * Math.max(100,this.physics.springLength);
  14809. if (!node.width) {
  14810. node.resize(ctx);
  14811. }
  14812. if (node.width > node.height) {
  14813. x = node.x + node.width * 0.5;
  14814. y = node.y - radius;
  14815. arrow = {
  14816. x: x,
  14817. y: node.y,
  14818. angle: 0.9 * Math.PI
  14819. };
  14820. }
  14821. else {
  14822. x = node.x + radius;
  14823. y = node.y - node.height * 0.5;
  14824. arrow = {
  14825. x: node.x,
  14826. y: y,
  14827. angle: 0.6 * Math.PI
  14828. };
  14829. }
  14830. ctx.beginPath();
  14831. // TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
  14832. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  14833. ctx.stroke();
  14834. // draw all arrows
  14835. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  14836. ctx.arrow(arrow.x, arrow.y, arrow.angle, length);
  14837. ctx.fill();
  14838. ctx.stroke();
  14839. // draw label
  14840. if (this.label) {
  14841. point = this._pointOnCircle(x, y, radius, 0.5);
  14842. this._label(ctx, this.label, point.x, point.y);
  14843. }
  14844. }
  14845. };
  14846. /**
  14847. * Calculate the distance between a point (x3,y3) and a line segment from
  14848. * (x1,y1) to (x2,y2).
  14849. * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
  14850. * @param {number} x1
  14851. * @param {number} y1
  14852. * @param {number} x2
  14853. * @param {number} y2
  14854. * @param {number} x3
  14855. * @param {number} y3
  14856. * @private
  14857. */
  14858. Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
  14859. if (this.from != this.to) {
  14860. if (this.options.smoothCurves.enabled == true) {
  14861. var xVia, yVia;
  14862. if (this.options.smoothCurves.enabled == true && this.options.smoothCurves.dynamic == true) {
  14863. xVia = this.via.x;
  14864. yVia = this.via.y;
  14865. }
  14866. else {
  14867. var via = this._getViaCoordinates();
  14868. xVia = via.x;
  14869. yVia = via.y;
  14870. }
  14871. var minDistance = 1e9;
  14872. var distance;
  14873. var i,t,x,y, lastX, lastY;
  14874. for (i = 0; i < 10; i++) {
  14875. t = 0.1*i;
  14876. x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*xVia + Math.pow(t,2)*x2;
  14877. y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*yVia + Math.pow(t,2)*y2;
  14878. if (i > 0) {
  14879. distance = this._getDistanceToLine(lastX,lastY,x,y, x3,y3);
  14880. minDistance = distance < minDistance ? distance : minDistance;
  14881. }
  14882. lastX = x; lastY = y;
  14883. }
  14884. return minDistance
  14885. }
  14886. else {
  14887. return this._getDistanceToLine(x1,y1,x2,y2,x3,y3);
  14888. }
  14889. }
  14890. else {
  14891. var x, y, dx, dy;
  14892. var radius = 0.25 * this.physics.springLength;
  14893. var node = this.from;
  14894. if (node.width > node.height) {
  14895. x = node.x + 0.5 * node.width;
  14896. y = node.y - radius;
  14897. }
  14898. else {
  14899. x = node.x + radius;
  14900. y = node.y - 0.5 * node.height;
  14901. }
  14902. dx = x - x3;
  14903. dy = y - y3;
  14904. return Math.abs(Math.sqrt(dx*dx + dy*dy) - radius);
  14905. }
  14906. };
  14907. Edge.prototype._getDistanceToLine = function(x1,y1,x2,y2,x3,y3) {
  14908. var px = x2-x1,
  14909. py = y2-y1,
  14910. something = px*px + py*py,
  14911. u = ((x3 - x1) * px + (y3 - y1) * py) / something;
  14912. if (u > 1) {
  14913. u = 1;
  14914. }
  14915. else if (u < 0) {
  14916. u = 0;
  14917. }
  14918. var x = x1 + u * px,
  14919. y = y1 + u * py,
  14920. dx = x - x3,
  14921. dy = y - y3;
  14922. //# Note: If the actual distance does not matter,
  14923. //# if you only want to compare what this function
  14924. //# returns to other results of this function, you
  14925. //# can just return the squared distance instead
  14926. //# (i.e. remove the sqrt) to gain a little performance
  14927. return Math.sqrt(dx*dx + dy*dy);
  14928. }
  14929. /**
  14930. * This allows the zoom level of the network to influence the rendering
  14931. *
  14932. * @param scale
  14933. */
  14934. Edge.prototype.setScale = function(scale) {
  14935. this.networkScaleInv = 1.0/scale;
  14936. };
  14937. Edge.prototype.select = function() {
  14938. this.selected = true;
  14939. };
  14940. Edge.prototype.unselect = function() {
  14941. this.selected = false;
  14942. };
  14943. Edge.prototype.positionBezierNode = function() {
  14944. if (this.via !== null && this.from !== null && this.to !== null) {
  14945. this.via.x = 0.5 * (this.from.x + this.to.x);
  14946. this.via.y = 0.5 * (this.from.y + this.to.y);
  14947. }
  14948. };
  14949. /**
  14950. * This function draws the control nodes for the manipulator. In order to enable this, only set the this.controlNodesEnabled to true.
  14951. * @param ctx
  14952. */
  14953. Edge.prototype._drawControlNodes = function(ctx) {
  14954. if (this.controlNodesEnabled == true) {
  14955. if (this.controlNodes.from === null && this.controlNodes.to === null) {
  14956. var nodeIdFrom = "edgeIdFrom:".concat(this.id);
  14957. var nodeIdTo = "edgeIdTo:".concat(this.id);
  14958. var constants = {
  14959. nodes:{group:'', radius:8},
  14960. physics:{damping:0},
  14961. clustering: {maxNodeSizeIncrements: 0 ,nodeScaling: {width:0, height: 0, radius:0}}
  14962. };
  14963. this.controlNodes.from = new Node(
  14964. {id:nodeIdFrom,
  14965. shape:'dot',
  14966. color:{background:'#ff4e00', border:'#3c3c3c', highlight: {background:'#07f968'}}
  14967. },{},{},constants);
  14968. this.controlNodes.to = new Node(
  14969. {id:nodeIdTo,
  14970. shape:'dot',
  14971. color:{background:'#ff4e00', border:'#3c3c3c', highlight: {background:'#07f968'}}
  14972. },{},{},constants);
  14973. }
  14974. if (this.controlNodes.from.selected == false && this.controlNodes.to.selected == false) {
  14975. this.controlNodes.positions = this.getControlNodePositions(ctx);
  14976. this.controlNodes.from.x = this.controlNodes.positions.from.x;
  14977. this.controlNodes.from.y = this.controlNodes.positions.from.y;
  14978. this.controlNodes.to.x = this.controlNodes.positions.to.x;
  14979. this.controlNodes.to.y = this.controlNodes.positions.to.y;
  14980. }
  14981. this.controlNodes.from.draw(ctx);
  14982. this.controlNodes.to.draw(ctx);
  14983. }
  14984. else {
  14985. this.controlNodes = {from:null, to:null, positions:{}};
  14986. }
  14987. };
  14988. /**
  14989. * Enable control nodes.
  14990. * @private
  14991. */
  14992. Edge.prototype._enableControlNodes = function() {
  14993. this.controlNodesEnabled = true;
  14994. };
  14995. /**
  14996. * disable control nodes
  14997. * @private
  14998. */
  14999. Edge.prototype._disableControlNodes = function() {
  15000. this.controlNodesEnabled = false;
  15001. };
  15002. /**
  15003. * This checks if one of the control nodes is selected and if so, returns the control node object. Else it returns null.
  15004. * @param x
  15005. * @param y
  15006. * @returns {null}
  15007. * @private
  15008. */
  15009. Edge.prototype._getSelectedControlNode = function(x,y) {
  15010. var positions = this.controlNodes.positions;
  15011. var fromDistance = Math.sqrt(Math.pow(x - positions.from.x,2) + Math.pow(y - positions.from.y,2));
  15012. var toDistance = Math.sqrt(Math.pow(x - positions.to.x ,2) + Math.pow(y - positions.to.y ,2));
  15013. if (fromDistance < 15) {
  15014. this.connectedNode = this.from;
  15015. this.from = this.controlNodes.from;
  15016. return this.controlNodes.from;
  15017. }
  15018. else if (toDistance < 15) {
  15019. this.connectedNode = this.to;
  15020. this.to = this.controlNodes.to;
  15021. return this.controlNodes.to;
  15022. }
  15023. else {
  15024. return null;
  15025. }
  15026. };
  15027. /**
  15028. * this resets the control nodes to their original position.
  15029. * @private
  15030. */
  15031. Edge.prototype._restoreControlNodes = function() {
  15032. if (this.controlNodes.from.selected == true) {
  15033. this.from = this.connectedNode;
  15034. this.connectedNode = null;
  15035. this.controlNodes.from.unselect();
  15036. }
  15037. if (this.controlNodes.to.selected == true) {
  15038. this.to = this.connectedNode;
  15039. this.connectedNode = null;
  15040. this.controlNodes.to.unselect();
  15041. }
  15042. };
  15043. /**
  15044. * this calculates the position of the control nodes on the edges of the parent nodes.
  15045. *
  15046. * @param ctx
  15047. * @returns {{from: {x: number, y: number}, to: {x: *, y: *}}}
  15048. */
  15049. Edge.prototype.getControlNodePositions = function(ctx) {
  15050. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  15051. var dx = (this.to.x - this.from.x);
  15052. var dy = (this.to.y - this.from.y);
  15053. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  15054. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  15055. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  15056. var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  15057. var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  15058. var via;
  15059. if (this.options.smoothCurves.dynamic == true && this.options.smoothCurves.enabled == true) {
  15060. via = this.via;
  15061. }
  15062. else if (this.options.smoothCurves.enabled == true) {
  15063. via = this._getViaCoordinates();
  15064. }
  15065. if (this.options.smoothCurves.enabled == true && via.x != null) {
  15066. angle = Math.atan2((this.to.y - via.y), (this.to.x - via.x));
  15067. dx = (this.to.x - via.x);
  15068. dy = (this.to.y - via.y);
  15069. edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  15070. }
  15071. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  15072. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  15073. var xTo,yTo;
  15074. if (this.options.smoothCurves.enabled == true && via.x != null) {
  15075. xTo = (1 - toBorderPoint) * via.x + toBorderPoint * this.to.x;
  15076. yTo = (1 - toBorderPoint) * via.y + toBorderPoint * this.to.y;
  15077. }
  15078. else {
  15079. xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  15080. yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  15081. }
  15082. return {from:{x:xFrom,y:yFrom},to:{x:xTo,y:yTo}};
  15083. };
  15084. module.exports = Edge;
  15085. /***/ },
  15086. /* 34 */
  15087. /***/ function(module, exports, __webpack_require__) {
  15088. var util = __webpack_require__(1);
  15089. /**
  15090. * @class Groups
  15091. * This class can store groups and properties specific for groups.
  15092. */
  15093. function Groups() {
  15094. this.clear();
  15095. this.defaultIndex = 0;
  15096. }
  15097. /**
  15098. * default constants for group colors
  15099. */
  15100. Groups.DEFAULT = [
  15101. {border: "#2B7CE9", background: "#97C2FC", highlight: {border: "#2B7CE9", background: "#D2E5FF"}, hover: {border: "#2B7CE9", background: "#D2E5FF"}}, // blue
  15102. {border: "#FFA500", background: "#FFFF00", highlight: {border: "#FFA500", background: "#FFFFA3"}, hover: {border: "#FFA500", background: "#FFFFA3"}}, // yellow
  15103. {border: "#FA0A10", background: "#FB7E81", highlight: {border: "#FA0A10", background: "#FFAFB1"}, hover: {border: "#FA0A10", background: "#FFAFB1"}}, // red
  15104. {border: "#41A906", background: "#7BE141", highlight: {border: "#41A906", background: "#A1EC76"}, hover: {border: "#41A906", background: "#A1EC76"}}, // green
  15105. {border: "#E129F0", background: "#EB7DF4", highlight: {border: "#E129F0", background: "#F0B3F5"}, hover: {border: "#E129F0", background: "#F0B3F5"}}, // magenta
  15106. {border: "#7C29F0", background: "#AD85E4", highlight: {border: "#7C29F0", background: "#D3BDF0"}, hover: {border: "#7C29F0", background: "#D3BDF0"}}, // purple
  15107. {border: "#C37F00", background: "#FFA807", highlight: {border: "#C37F00", background: "#FFCA66"}, hover: {border: "#C37F00", background: "#FFCA66"}}, // orange
  15108. {border: "#4220FB", background: "#6E6EFD", highlight: {border: "#4220FB", background: "#9B9BFD"}, hover: {border: "#4220FB", background: "#9B9BFD"}}, // darkblue
  15109. {border: "#FD5A77", background: "#FFC0CB", highlight: {border: "#FD5A77", background: "#FFD1D9"}, hover: {border: "#FD5A77", background: "#FFD1D9"}}, // pink
  15110. {border: "#4AD63A", background: "#C2FABC", highlight: {border: "#4AD63A", background: "#E6FFE3"}, hover: {border: "#4AD63A", background: "#E6FFE3"}} // mint
  15111. ];
  15112. /**
  15113. * Clear all groups
  15114. */
  15115. Groups.prototype.clear = function () {
  15116. this.groups = {};
  15117. this.groups.length = function()
  15118. {
  15119. var i = 0;
  15120. for ( var p in this ) {
  15121. if (this.hasOwnProperty(p)) {
  15122. i++;
  15123. }
  15124. }
  15125. return i;
  15126. }
  15127. };
  15128. /**
  15129. * get group properties of a groupname. If groupname is not found, a new group
  15130. * is added.
  15131. * @param {*} groupname Can be a number, string, Date, etc.
  15132. * @return {Object} group The created group, containing all group properties
  15133. */
  15134. Groups.prototype.get = function (groupname) {
  15135. var group = this.groups[groupname];
  15136. if (group == undefined) {
  15137. // create new group
  15138. var index = this.defaultIndex % Groups.DEFAULT.length;
  15139. this.defaultIndex++;
  15140. group = {};
  15141. group.color = Groups.DEFAULT[index];
  15142. this.groups[groupname] = group;
  15143. }
  15144. return group;
  15145. };
  15146. /**
  15147. * Add a custom group style
  15148. * @param {String} groupname
  15149. * @param {Object} style An object containing borderColor,
  15150. * backgroundColor, etc.
  15151. * @return {Object} group The created group object
  15152. */
  15153. Groups.prototype.add = function (groupname, style) {
  15154. this.groups[groupname] = style;
  15155. if (style.color) {
  15156. style.color = util.parseColor(style.color);
  15157. }
  15158. return style;
  15159. };
  15160. module.exports = Groups;
  15161. /***/ },
  15162. /* 35 */
  15163. /***/ function(module, exports, __webpack_require__) {
  15164. /**
  15165. * @class Images
  15166. * This class loads images and keeps them stored.
  15167. */
  15168. function Images() {
  15169. this.images = {};
  15170. this.callback = undefined;
  15171. }
  15172. /**
  15173. * Set an onload callback function. This will be called each time an image
  15174. * is loaded
  15175. * @param {function} callback
  15176. */
  15177. Images.prototype.setOnloadCallback = function(callback) {
  15178. this.callback = callback;
  15179. };
  15180. /**
  15181. *
  15182. * @param {string} url Url of the image
  15183. * @return {Image} img The image object
  15184. */
  15185. Images.prototype.load = function(url) {
  15186. var img = this.images[url];
  15187. if (img == undefined) {
  15188. // create the image
  15189. var images = this;
  15190. img = new Image();
  15191. this.images[url] = img;
  15192. img.onload = function() {
  15193. if (images.callback) {
  15194. images.callback(this);
  15195. }
  15196. };
  15197. img.src = url;
  15198. }
  15199. return img;
  15200. };
  15201. module.exports = Images;
  15202. /***/ },
  15203. /* 36 */
  15204. /***/ function(module, exports, __webpack_require__) {
  15205. var util = __webpack_require__(1);
  15206. /**
  15207. * @class Node
  15208. * A node. A node can be connected to other nodes via one or multiple edges.
  15209. * @param {object} properties An object containing properties for the node. All
  15210. * properties are optional, except for the id.
  15211. * {number} id Id of the node. Required
  15212. * {string} label Text label for the node
  15213. * {number} x Horizontal position of the node
  15214. * {number} y Vertical position of the node
  15215. * {string} shape Node shape, available:
  15216. * "database", "circle", "ellipse",
  15217. * "box", "image", "text", "dot",
  15218. * "star", "triangle", "triangleDown",
  15219. * "square"
  15220. * {string} image An image url
  15221. * {string} title An title text, can be HTML
  15222. * {anytype} group A group name or number
  15223. * @param {Network.Images} imagelist A list with images. Only needed
  15224. * when the node has an image
  15225. * @param {Network.Groups} grouplist A list with groups. Needed for
  15226. * retrieving group properties
  15227. * @param {Object} constants An object with default values for
  15228. * example for the color
  15229. *
  15230. */
  15231. function Node(properties, imagelist, grouplist, networkConstants) {
  15232. var constants = util.selectiveBridgeObject(['nodes'],networkConstants);
  15233. this.options = constants.nodes;
  15234. this.selected = false;
  15235. this.hover = false;
  15236. this.edges = []; // all edges connected to this node
  15237. this.dynamicEdges = [];
  15238. this.reroutedEdges = {};
  15239. this.fontDrawThreshold = 3;
  15240. // set defaults for the properties
  15241. this.id = undefined;
  15242. this.x = null;
  15243. this.y = null;
  15244. this.xFixed = false;
  15245. this.yFixed = false;
  15246. this.horizontalAlignLeft = true; // these are for the navigation controls
  15247. this.verticalAlignTop = true; // these are for the navigation controls
  15248. this.baseRadiusValue = networkConstants.nodes.radius;
  15249. this.radiusFixed = false;
  15250. this.level = -1;
  15251. this.preassignedLevel = false;
  15252. this.hierarchyEnumerated = false;
  15253. this.imagelist = imagelist;
  15254. this.grouplist = grouplist;
  15255. // physics properties
  15256. this.fx = 0.0; // external force x
  15257. this.fy = 0.0; // external force y
  15258. this.vx = 0.0; // velocity x
  15259. this.vy = 0.0; // velocity y
  15260. this.damping = networkConstants.physics.damping; // written every time gravity is calculated
  15261. this.fixedData = {x:null,y:null};
  15262. this.setProperties(properties, constants);
  15263. // creating the variables for clustering
  15264. this.resetCluster();
  15265. this.dynamicEdgesLength = 0;
  15266. this.clusterSession = 0;
  15267. this.clusterSizeWidthFactor = networkConstants.clustering.nodeScaling.width;
  15268. this.clusterSizeHeightFactor = networkConstants.clustering.nodeScaling.height;
  15269. this.clusterSizeRadiusFactor = networkConstants.clustering.nodeScaling.radius;
  15270. this.maxNodeSizeIncrements = networkConstants.clustering.maxNodeSizeIncrements;
  15271. this.growthIndicator = 0;
  15272. // variables to tell the node about the network.
  15273. this.networkScaleInv = 1;
  15274. this.networkScale = 1;
  15275. this.canvasTopLeft = {"x": -300, "y": -300};
  15276. this.canvasBottomRight = {"x": 300, "y": 300};
  15277. this.parentEdgeId = null;
  15278. }
  15279. /**
  15280. * (re)setting the clustering variables and objects
  15281. */
  15282. Node.prototype.resetCluster = function() {
  15283. // clustering variables
  15284. this.formationScale = undefined; // this is used to determine when to open the cluster
  15285. this.clusterSize = 1; // this signifies the total amount of nodes in this cluster
  15286. this.containedNodes = {};
  15287. this.containedEdges = {};
  15288. this.clusterSessions = [];
  15289. };
  15290. /**
  15291. * Attach a edge to the node
  15292. * @param {Edge} edge
  15293. */
  15294. Node.prototype.attachEdge = function(edge) {
  15295. if (this.edges.indexOf(edge) == -1) {
  15296. this.edges.push(edge);
  15297. }
  15298. if (this.dynamicEdges.indexOf(edge) == -1) {
  15299. this.dynamicEdges.push(edge);
  15300. }
  15301. this.dynamicEdgesLength = this.dynamicEdges.length;
  15302. };
  15303. /**
  15304. * Detach a edge from the node
  15305. * @param {Edge} edge
  15306. */
  15307. Node.prototype.detachEdge = function(edge) {
  15308. var index = this.edges.indexOf(edge);
  15309. if (index != -1) {
  15310. this.edges.splice(index, 1);
  15311. this.dynamicEdges.splice(index, 1);
  15312. }
  15313. this.dynamicEdgesLength = this.dynamicEdges.length;
  15314. };
  15315. /**
  15316. * Set or overwrite properties for the node
  15317. * @param {Object} properties an object with properties
  15318. * @param {Object} constants and object with default, global properties
  15319. */
  15320. Node.prototype.setProperties = function(properties, constants) {
  15321. if (!properties) {
  15322. return;
  15323. }
  15324. var fields = ['borderWidth','borderWidthSelected','shape','image','radius','fontColor',
  15325. 'fontSize','fontFace','group','mass'
  15326. ];
  15327. util.selectiveDeepExtend(fields, this.options, properties);
  15328. this.originalLabel = undefined;
  15329. // basic properties
  15330. if (properties.id !== undefined) {this.id = properties.id;}
  15331. if (properties.label !== undefined) {this.label = properties.label; this.originalLabel = properties.label;}
  15332. if (properties.title !== undefined) {this.title = properties.title;}
  15333. if (properties.x !== undefined) {this.x = properties.x;}
  15334. if (properties.y !== undefined) {this.y = properties.y;}
  15335. if (properties.value !== undefined) {this.value = properties.value;}
  15336. if (properties.level !== undefined) {this.level = properties.level; this.preassignedLevel = true;}
  15337. // navigation controls properties
  15338. if (properties.horizontalAlignLeft !== undefined) {this.horizontalAlignLeft = properties.horizontalAlignLeft;}
  15339. if (properties.verticalAlignTop !== undefined) {this.verticalAlignTop = properties.verticalAlignTop;}
  15340. if (properties.triggerFunction !== undefined) {this.triggerFunction = properties.triggerFunction;}
  15341. if (this.id === undefined) {
  15342. throw "Node must have an id";
  15343. }
  15344. // copy group properties
  15345. if (typeof this.options.group === 'number' || (typeof this.options.group === 'string' && this.options.group != '')) {
  15346. var groupObj = this.grouplist.get(this.options.group);
  15347. for (var prop in groupObj) {
  15348. if (groupObj.hasOwnProperty(prop)) {
  15349. this.options[prop] = groupObj[prop];
  15350. }
  15351. }
  15352. }
  15353. // individual shape properties
  15354. if (properties.radius !== undefined) {this.baseRadiusValue = this.options.radius;}
  15355. if (properties.color !== undefined) {this.options.color = util.parseColor(properties.color);}
  15356. if (this.options.image!== undefined && this.options.image!= "") {
  15357. if (this.imagelist) {
  15358. this.imageObj = this.imagelist.load(this.options.image);
  15359. }
  15360. else {
  15361. throw "No imagelist provided";
  15362. }
  15363. }
  15364. this.xFixed = this.xFixed || (properties.x !== undefined && !properties.allowedToMoveX);
  15365. this.yFixed = this.yFixed || (properties.y !== undefined && !properties.allowedToMoveY);
  15366. this.radiusFixed = this.radiusFixed || (properties.radius !== undefined);
  15367. if (this.options.shape == 'image') {
  15368. this.options.radiusMin = constants.nodes.widthMin;
  15369. this.options.radiusMax = constants.nodes.widthMax;
  15370. }
  15371. // choose draw method depending on the shape
  15372. switch (this.options.shape) {
  15373. case 'database': this.draw = this._drawDatabase; this.resize = this._resizeDatabase; break;
  15374. case 'box': this.draw = this._drawBox; this.resize = this._resizeBox; break;
  15375. case 'circle': this.draw = this._drawCircle; this.resize = this._resizeCircle; break;
  15376. case 'ellipse': this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
  15377. // TODO: add diamond shape
  15378. case 'image': this.draw = this._drawImage; this.resize = this._resizeImage; break;
  15379. case 'text': this.draw = this._drawText; this.resize = this._resizeText; break;
  15380. case 'dot': this.draw = this._drawDot; this.resize = this._resizeShape; break;
  15381. case 'square': this.draw = this._drawSquare; this.resize = this._resizeShape; break;
  15382. case 'triangle': this.draw = this._drawTriangle; this.resize = this._resizeShape; break;
  15383. case 'triangleDown': this.draw = this._drawTriangleDown; this.resize = this._resizeShape; break;
  15384. case 'star': this.draw = this._drawStar; this.resize = this._resizeShape; break;
  15385. default: this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
  15386. }
  15387. // reset the size of the node, this can be changed
  15388. this._reset();
  15389. };
  15390. /**
  15391. * select this node
  15392. */
  15393. Node.prototype.select = function() {
  15394. this.selected = true;
  15395. this._reset();
  15396. };
  15397. /**
  15398. * unselect this node
  15399. */
  15400. Node.prototype.unselect = function() {
  15401. this.selected = false;
  15402. this._reset();
  15403. };
  15404. /**
  15405. * Reset the calculated size of the node, forces it to recalculate its size
  15406. */
  15407. Node.prototype.clearSizeCache = function() {
  15408. this._reset();
  15409. };
  15410. /**
  15411. * Reset the calculated size of the node, forces it to recalculate its size
  15412. * @private
  15413. */
  15414. Node.prototype._reset = function() {
  15415. this.width = undefined;
  15416. this.height = undefined;
  15417. };
  15418. /**
  15419. * get the title of this node.
  15420. * @return {string} title The title of the node, or undefined when no title
  15421. * has been set.
  15422. */
  15423. Node.prototype.getTitle = function() {
  15424. return typeof this.title === "function" ? this.title() : this.title;
  15425. };
  15426. /**
  15427. * Calculate the distance to the border of the Node
  15428. * @param {CanvasRenderingContext2D} ctx
  15429. * @param {Number} angle Angle in radians
  15430. * @returns {number} distance Distance to the border in pixels
  15431. */
  15432. Node.prototype.distanceToBorder = function (ctx, angle) {
  15433. var borderWidth = 1;
  15434. if (!this.width) {
  15435. this.resize(ctx);
  15436. }
  15437. switch (this.options.shape) {
  15438. case 'circle':
  15439. case 'dot':
  15440. return this.options.radius+ borderWidth;
  15441. case 'ellipse':
  15442. var a = this.width / 2;
  15443. var b = this.height / 2;
  15444. var w = (Math.sin(angle) * a);
  15445. var h = (Math.cos(angle) * b);
  15446. return a * b / Math.sqrt(w * w + h * h);
  15447. // TODO: implement distanceToBorder for database
  15448. // TODO: implement distanceToBorder for triangle
  15449. // TODO: implement distanceToBorder for triangleDown
  15450. case 'box':
  15451. case 'image':
  15452. case 'text':
  15453. default:
  15454. if (this.width) {
  15455. return Math.min(
  15456. Math.abs(this.width / 2 / Math.cos(angle)),
  15457. Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth;
  15458. // TODO: reckon with border radius too in case of box
  15459. }
  15460. else {
  15461. return 0;
  15462. }
  15463. }
  15464. // TODO: implement calculation of distance to border for all shapes
  15465. };
  15466. /**
  15467. * Set forces acting on the node
  15468. * @param {number} fx Force in horizontal direction
  15469. * @param {number} fy Force in vertical direction
  15470. */
  15471. Node.prototype._setForce = function(fx, fy) {
  15472. this.fx = fx;
  15473. this.fy = fy;
  15474. };
  15475. /**
  15476. * Add forces acting on the node
  15477. * @param {number} fx Force in horizontal direction
  15478. * @param {number} fy Force in vertical direction
  15479. * @private
  15480. */
  15481. Node.prototype._addForce = function(fx, fy) {
  15482. this.fx += fx;
  15483. this.fy += fy;
  15484. };
  15485. /**
  15486. * Perform one discrete step for the node
  15487. * @param {number} interval Time interval in seconds
  15488. */
  15489. Node.prototype.discreteStep = function(interval) {
  15490. if (!this.xFixed) {
  15491. var dx = this.damping * this.vx; // damping force
  15492. var ax = (this.fx - dx) / this.options.mass; // acceleration
  15493. this.vx += ax * interval; // velocity
  15494. this.x += this.vx * interval; // position
  15495. }
  15496. if (!this.yFixed) {
  15497. var dy = this.damping * this.vy; // damping force
  15498. var ay = (this.fy - dy) / this.options.mass; // acceleration
  15499. this.vy += ay * interval; // velocity
  15500. this.y += this.vy * interval; // position
  15501. }
  15502. };
  15503. /**
  15504. * Perform one discrete step for the node
  15505. * @param {number} interval Time interval in seconds
  15506. * @param {number} maxVelocity The speed limit imposed on the velocity
  15507. */
  15508. Node.prototype.discreteStepLimited = function(interval, maxVelocity) {
  15509. if (!this.xFixed) {
  15510. var dx = this.damping * this.vx; // damping force
  15511. var ax = (this.fx - dx) / this.options.mass; // acceleration
  15512. this.vx += ax * interval; // velocity
  15513. this.vx = (Math.abs(this.vx) > maxVelocity) ? ((this.vx > 0) ? maxVelocity : -maxVelocity) : this.vx;
  15514. this.x += this.vx * interval; // position
  15515. }
  15516. else {
  15517. this.fx = 0;
  15518. }
  15519. if (!this.yFixed) {
  15520. var dy = this.damping * this.vy; // damping force
  15521. var ay = (this.fy - dy) / this.options.mass; // acceleration
  15522. this.vy += ay * interval; // velocity
  15523. this.vy = (Math.abs(this.vy) > maxVelocity) ? ((this.vy > 0) ? maxVelocity : -maxVelocity) : this.vy;
  15524. this.y += this.vy * interval; // position
  15525. }
  15526. else {
  15527. this.fy = 0;
  15528. }
  15529. };
  15530. /**
  15531. * Check if this node has a fixed x and y position
  15532. * @return {boolean} true if fixed, false if not
  15533. */
  15534. Node.prototype.isFixed = function() {
  15535. return (this.xFixed && this.yFixed);
  15536. };
  15537. /**
  15538. * Check if this node is moving
  15539. * @param {number} vmin the minimum velocity considered as "moving"
  15540. * @return {boolean} true if moving, false if it has no velocity
  15541. */
  15542. Node.prototype.isMoving = function(vmin) {
  15543. var velocity = Math.sqrt(Math.pow(this.vx,2) + Math.pow(this.vy,2));
  15544. // this.velocity = Math.sqrt(Math.pow(this.vx,2) + Math.pow(this.vy,2))
  15545. return (velocity > vmin);
  15546. };
  15547. /**
  15548. * check if this node is selecte
  15549. * @return {boolean} selected True if node is selected, else false
  15550. */
  15551. Node.prototype.isSelected = function() {
  15552. return this.selected;
  15553. };
  15554. /**
  15555. * Retrieve the value of the node. Can be undefined
  15556. * @return {Number} value
  15557. */
  15558. Node.prototype.getValue = function() {
  15559. return this.value;
  15560. };
  15561. /**
  15562. * Calculate the distance from the nodes location to the given location (x,y)
  15563. * @param {Number} x
  15564. * @param {Number} y
  15565. * @return {Number} value
  15566. */
  15567. Node.prototype.getDistance = function(x, y) {
  15568. var dx = this.x - x,
  15569. dy = this.y - y;
  15570. return Math.sqrt(dx * dx + dy * dy);
  15571. };
  15572. /**
  15573. * Adjust the value range of the node. The node will adjust it's radius
  15574. * based on its value.
  15575. * @param {Number} min
  15576. * @param {Number} max
  15577. */
  15578. Node.prototype.setValueRange = function(min, max) {
  15579. if (!this.radiusFixed && this.value !== undefined) {
  15580. if (max == min) {
  15581. this.options.radius= (this.options.radiusMin + this.options.radiusMax) / 2;
  15582. }
  15583. else {
  15584. var scale = (this.options.radiusMax - this.options.radiusMin) / (max - min);
  15585. this.options.radius= (this.value - min) * scale + this.options.radiusMin;
  15586. }
  15587. }
  15588. this.baseRadiusValue = this.options.radius;
  15589. };
  15590. /**
  15591. * Draw this node in the given canvas
  15592. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  15593. * @param {CanvasRenderingContext2D} ctx
  15594. */
  15595. Node.prototype.draw = function(ctx) {
  15596. throw "Draw method not initialized for node";
  15597. };
  15598. /**
  15599. * Recalculate the size of this node in the given canvas
  15600. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  15601. * @param {CanvasRenderingContext2D} ctx
  15602. */
  15603. Node.prototype.resize = function(ctx) {
  15604. throw "Resize method not initialized for node";
  15605. };
  15606. /**
  15607. * Check if this object is overlapping with the provided object
  15608. * @param {Object} obj an object with parameters left, top, right, bottom
  15609. * @return {boolean} True if location is located on node
  15610. */
  15611. Node.prototype.isOverlappingWith = function(obj) {
  15612. return (this.left < obj.right &&
  15613. this.left + this.width > obj.left &&
  15614. this.top < obj.bottom &&
  15615. this.top + this.height > obj.top);
  15616. };
  15617. Node.prototype._resizeImage = function (ctx) {
  15618. // TODO: pre calculate the image size
  15619. if (!this.width || !this.height) { // undefined or 0
  15620. var width, height;
  15621. if (this.value) {
  15622. this.options.radius= this.baseRadiusValue;
  15623. var scale = this.imageObj.height / this.imageObj.width;
  15624. if (scale !== undefined) {
  15625. width = this.options.radius|| this.imageObj.width;
  15626. height = this.options.radius* scale || this.imageObj.height;
  15627. }
  15628. else {
  15629. width = 0;
  15630. height = 0;
  15631. }
  15632. }
  15633. else {
  15634. width = this.imageObj.width;
  15635. height = this.imageObj.height;
  15636. }
  15637. this.width = width;
  15638. this.height = height;
  15639. this.growthIndicator = 0;
  15640. if (this.width > 0 && this.height > 0) {
  15641. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  15642. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  15643. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  15644. this.growthIndicator = this.width - width;
  15645. }
  15646. }
  15647. };
  15648. Node.prototype._drawImage = function (ctx) {
  15649. this._resizeImage(ctx);
  15650. this.left = this.x - this.width / 2;
  15651. this.top = this.y - this.height / 2;
  15652. var yLabel;
  15653. if (this.imageObj.width != 0 ) {
  15654. // draw the shade
  15655. if (this.clusterSize > 1) {
  15656. var lineWidth = ((this.clusterSize > 1) ? 10 : 0.0);
  15657. lineWidth *= this.networkScaleInv;
  15658. lineWidth = Math.min(0.2 * this.width,lineWidth);
  15659. ctx.globalAlpha = 0.5;
  15660. ctx.drawImage(this.imageObj, this.left - lineWidth, this.top - lineWidth, this.width + 2*lineWidth, this.height + 2*lineWidth);
  15661. }
  15662. // draw the image
  15663. ctx.globalAlpha = 1.0;
  15664. ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height);
  15665. yLabel = this.y + this.height / 2;
  15666. }
  15667. else {
  15668. // image still loading... just draw the label for now
  15669. yLabel = this.y;
  15670. }
  15671. this._label(ctx, this.label, this.x, yLabel, undefined, "top");
  15672. };
  15673. Node.prototype._resizeBox = function (ctx) {
  15674. if (!this.width) {
  15675. var margin = 5;
  15676. var textSize = this.getTextSize(ctx);
  15677. this.width = textSize.width + 2 * margin;
  15678. this.height = textSize.height + 2 * margin;
  15679. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  15680. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  15681. this.growthIndicator = this.width - (textSize.width + 2 * margin);
  15682. // this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  15683. }
  15684. };
  15685. Node.prototype._drawBox = function (ctx) {
  15686. this._resizeBox(ctx);
  15687. this.left = this.x - this.width / 2;
  15688. this.top = this.y - this.height / 2;
  15689. var clusterLineWidth = 2.5;
  15690. var borderWidth = this.options.borderWidth;
  15691. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  15692. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  15693. // draw the outer border
  15694. if (this.clusterSize > 1) {
  15695. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  15696. ctx.lineWidth *= this.networkScaleInv;
  15697. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  15698. 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);
  15699. ctx.stroke();
  15700. }
  15701. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  15702. ctx.lineWidth *= this.networkScaleInv;
  15703. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  15704. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.options.color.background;
  15705. ctx.roundRect(this.left, this.top, this.width, this.height, this.options.radius);
  15706. ctx.fill();
  15707. ctx.stroke();
  15708. this._label(ctx, this.label, this.x, this.y);
  15709. };
  15710. Node.prototype._resizeDatabase = function (ctx) {
  15711. if (!this.width) {
  15712. var margin = 5;
  15713. var textSize = this.getTextSize(ctx);
  15714. var size = textSize.width + 2 * margin;
  15715. this.width = size;
  15716. this.height = size;
  15717. // scaling used for clustering
  15718. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  15719. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  15720. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  15721. this.growthIndicator = this.width - size;
  15722. }
  15723. };
  15724. Node.prototype._drawDatabase = function (ctx) {
  15725. this._resizeDatabase(ctx);
  15726. this.left = this.x - this.width / 2;
  15727. this.top = this.y - this.height / 2;
  15728. var clusterLineWidth = 2.5;
  15729. var borderWidth = this.options.borderWidth;
  15730. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  15731. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  15732. // draw the outer border
  15733. if (this.clusterSize > 1) {
  15734. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  15735. ctx.lineWidth *= this.networkScaleInv;
  15736. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  15737. 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);
  15738. ctx.stroke();
  15739. }
  15740. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  15741. ctx.lineWidth *= this.networkScaleInv;
  15742. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  15743. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  15744. ctx.database(this.x - this.width/2, this.y - this.height*0.5, this.width, this.height);
  15745. ctx.fill();
  15746. ctx.stroke();
  15747. this._label(ctx, this.label, this.x, this.y);
  15748. };
  15749. Node.prototype._resizeCircle = function (ctx) {
  15750. if (!this.width) {
  15751. var margin = 5;
  15752. var textSize = this.getTextSize(ctx);
  15753. var diameter = Math.max(textSize.width, textSize.height) + 2 * margin;
  15754. this.options.radius = diameter / 2;
  15755. this.width = diameter;
  15756. this.height = diameter;
  15757. // scaling used for clustering
  15758. // this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  15759. // this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  15760. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  15761. this.growthIndicator = this.options.radius- 0.5*diameter;
  15762. }
  15763. };
  15764. Node.prototype._drawCircle = function (ctx) {
  15765. this._resizeCircle(ctx);
  15766. this.left = this.x - this.width / 2;
  15767. this.top = this.y - this.height / 2;
  15768. var clusterLineWidth = 2.5;
  15769. var borderWidth = this.options.borderWidth;
  15770. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  15771. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  15772. // draw the outer border
  15773. if (this.clusterSize > 1) {
  15774. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  15775. ctx.lineWidth *= this.networkScaleInv;
  15776. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  15777. ctx.circle(this.x, this.y, this.options.radius+2*ctx.lineWidth);
  15778. ctx.stroke();
  15779. }
  15780. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  15781. ctx.lineWidth *= this.networkScaleInv;
  15782. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  15783. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  15784. ctx.circle(this.x, this.y, this.options.radius);
  15785. ctx.fill();
  15786. ctx.stroke();
  15787. this._label(ctx, this.label, this.x, this.y);
  15788. };
  15789. Node.prototype._resizeEllipse = function (ctx) {
  15790. if (!this.width) {
  15791. var textSize = this.getTextSize(ctx);
  15792. this.width = textSize.width * 1.5;
  15793. this.height = textSize.height * 2;
  15794. if (this.width < this.height) {
  15795. this.width = this.height;
  15796. }
  15797. var defaultSize = this.width;
  15798. // scaling used for clustering
  15799. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  15800. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  15801. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  15802. this.growthIndicator = this.width - defaultSize;
  15803. }
  15804. };
  15805. Node.prototype._drawEllipse = function (ctx) {
  15806. this._resizeEllipse(ctx);
  15807. this.left = this.x - this.width / 2;
  15808. this.top = this.y - this.height / 2;
  15809. var clusterLineWidth = 2.5;
  15810. var borderWidth = this.options.borderWidth;
  15811. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  15812. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  15813. // draw the outer border
  15814. if (this.clusterSize > 1) {
  15815. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  15816. ctx.lineWidth *= this.networkScaleInv;
  15817. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  15818. ctx.ellipse(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth);
  15819. ctx.stroke();
  15820. }
  15821. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  15822. ctx.lineWidth *= this.networkScaleInv;
  15823. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  15824. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  15825. ctx.ellipse(this.left, this.top, this.width, this.height);
  15826. ctx.fill();
  15827. ctx.stroke();
  15828. this._label(ctx, this.label, this.x, this.y);
  15829. };
  15830. Node.prototype._drawDot = function (ctx) {
  15831. this._drawShape(ctx, 'circle');
  15832. };
  15833. Node.prototype._drawTriangle = function (ctx) {
  15834. this._drawShape(ctx, 'triangle');
  15835. };
  15836. Node.prototype._drawTriangleDown = function (ctx) {
  15837. this._drawShape(ctx, 'triangleDown');
  15838. };
  15839. Node.prototype._drawSquare = function (ctx) {
  15840. this._drawShape(ctx, 'square');
  15841. };
  15842. Node.prototype._drawStar = function (ctx) {
  15843. this._drawShape(ctx, 'star');
  15844. };
  15845. Node.prototype._resizeShape = function (ctx) {
  15846. if (!this.width) {
  15847. this.options.radius= this.baseRadiusValue;
  15848. var size = 2 * this.options.radius;
  15849. this.width = size;
  15850. this.height = size;
  15851. // scaling used for clustering
  15852. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  15853. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  15854. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  15855. this.growthIndicator = this.width - size;
  15856. }
  15857. };
  15858. Node.prototype._drawShape = function (ctx, shape) {
  15859. this._resizeShape(ctx);
  15860. this.left = this.x - this.width / 2;
  15861. this.top = this.y - this.height / 2;
  15862. var clusterLineWidth = 2.5;
  15863. var borderWidth = this.options.borderWidth;
  15864. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  15865. var radiusMultiplier = 2;
  15866. // choose draw method depending on the shape
  15867. switch (shape) {
  15868. case 'dot': radiusMultiplier = 2; break;
  15869. case 'square': radiusMultiplier = 2; break;
  15870. case 'triangle': radiusMultiplier = 3; break;
  15871. case 'triangleDown': radiusMultiplier = 3; break;
  15872. case 'star': radiusMultiplier = 4; break;
  15873. }
  15874. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  15875. // draw the outer border
  15876. if (this.clusterSize > 1) {
  15877. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  15878. ctx.lineWidth *= this.networkScaleInv;
  15879. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  15880. ctx[shape](this.x, this.y, this.options.radius+ radiusMultiplier * ctx.lineWidth);
  15881. ctx.stroke();
  15882. }
  15883. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  15884. ctx.lineWidth *= this.networkScaleInv;
  15885. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  15886. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  15887. ctx[shape](this.x, this.y, this.options.radius);
  15888. ctx.fill();
  15889. ctx.stroke();
  15890. if (this.label) {
  15891. this._label(ctx, this.label, this.x, this.y + this.height / 2, undefined, 'top',true);
  15892. }
  15893. };
  15894. Node.prototype._resizeText = function (ctx) {
  15895. if (!this.width) {
  15896. var margin = 5;
  15897. var textSize = this.getTextSize(ctx);
  15898. this.width = textSize.width + 2 * margin;
  15899. this.height = textSize.height + 2 * margin;
  15900. // scaling used for clustering
  15901. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  15902. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  15903. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  15904. this.growthIndicator = this.width - (textSize.width + 2 * margin);
  15905. }
  15906. };
  15907. Node.prototype._drawText = function (ctx) {
  15908. this._resizeText(ctx);
  15909. this.left = this.x - this.width / 2;
  15910. this.top = this.y - this.height / 2;
  15911. this._label(ctx, this.label, this.x, this.y);
  15912. };
  15913. Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNode) {
  15914. if (text && Number(this.options.fontSize) * this.networkScale > this.fontDrawThreshold) {
  15915. ctx.font = (this.selected ? "bold " : "") + this.options.fontSize + "px " + this.options.fontFace;
  15916. ctx.fillStyle = this.options.fontColor || "black";
  15917. ctx.textAlign = align || "center";
  15918. ctx.textBaseline = baseline || "middle";
  15919. var lines = text.split('\n');
  15920. var lineCount = lines.length;
  15921. var fontSize = (Number(this.options.fontSize) + 4);
  15922. var yLine = y + (1 - lineCount) / 2 * fontSize;
  15923. if (labelUnderNode == true) {
  15924. yLine = y + (1 - lineCount) / (2 * fontSize);
  15925. }
  15926. for (var i = 0; i < lineCount; i++) {
  15927. ctx.fillText(lines[i], x, yLine);
  15928. yLine += fontSize;
  15929. }
  15930. }
  15931. };
  15932. Node.prototype.getTextSize = function(ctx) {
  15933. if (this.label !== undefined) {
  15934. ctx.font = (this.selected ? "bold " : "") + this.options.fontSize + "px " + this.options.fontFace;
  15935. var lines = this.label.split('\n'),
  15936. height = (Number(this.options.fontSize) + 4) * lines.length,
  15937. width = 0;
  15938. for (var i = 0, iMax = lines.length; i < iMax; i++) {
  15939. width = Math.max(width, ctx.measureText(lines[i]).width);
  15940. }
  15941. return {"width": width, "height": height};
  15942. }
  15943. else {
  15944. return {"width": 0, "height": 0};
  15945. }
  15946. };
  15947. /**
  15948. * this is used to determine if a node is visible at all. this is used to determine when it needs to be drawn.
  15949. * there is a safety margin of 0.3 * width;
  15950. *
  15951. * @returns {boolean}
  15952. */
  15953. Node.prototype.inArea = function() {
  15954. if (this.width !== undefined) {
  15955. return (this.x + this.width *this.networkScaleInv >= this.canvasTopLeft.x &&
  15956. this.x - this.width *this.networkScaleInv < this.canvasBottomRight.x &&
  15957. this.y + this.height*this.networkScaleInv >= this.canvasTopLeft.y &&
  15958. this.y - this.height*this.networkScaleInv < this.canvasBottomRight.y);
  15959. }
  15960. else {
  15961. return true;
  15962. }
  15963. };
  15964. /**
  15965. * checks if the core of the node is in the display area, this is used for opening clusters around zoom
  15966. * @returns {boolean}
  15967. */
  15968. Node.prototype.inView = function() {
  15969. return (this.x >= this.canvasTopLeft.x &&
  15970. this.x < this.canvasBottomRight.x &&
  15971. this.y >= this.canvasTopLeft.y &&
  15972. this.y < this.canvasBottomRight.y);
  15973. };
  15974. /**
  15975. * This allows the zoom level of the network to influence the rendering
  15976. * We store the inverted scale and the coordinates of the top left, and bottom right points of the canvas
  15977. *
  15978. * @param scale
  15979. * @param canvasTopLeft
  15980. * @param canvasBottomRight
  15981. */
  15982. Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight) {
  15983. this.networkScaleInv = 1.0/scale;
  15984. this.networkScale = scale;
  15985. this.canvasTopLeft = canvasTopLeft;
  15986. this.canvasBottomRight = canvasBottomRight;
  15987. };
  15988. /**
  15989. * This allows the zoom level of the network to influence the rendering
  15990. *
  15991. * @param scale
  15992. */
  15993. Node.prototype.setScale = function(scale) {
  15994. this.networkScaleInv = 1.0/scale;
  15995. this.networkScale = scale;
  15996. };
  15997. /**
  15998. * set the velocity at 0. Is called when this node is contained in another during clustering
  15999. */
  16000. Node.prototype.clearVelocity = function() {
  16001. this.vx = 0;
  16002. this.vy = 0;
  16003. };
  16004. /**
  16005. * Basic preservation of (kinectic) energy
  16006. *
  16007. * @param massBeforeClustering
  16008. */
  16009. Node.prototype.updateVelocity = function(massBeforeClustering) {
  16010. var energyBefore = this.vx * this.vx * massBeforeClustering;
  16011. //this.vx = (this.vx < 0) ? -Math.sqrt(energyBefore/this.options.mass) : Math.sqrt(energyBefore/this.options.mass);
  16012. this.vx = Math.sqrt(energyBefore/this.options.mass);
  16013. energyBefore = this.vy * this.vy * massBeforeClustering;
  16014. //this.vy = (this.vy < 0) ? -Math.sqrt(energyBefore/this.options.mass) : Math.sqrt(energyBefore/this.options.mass);
  16015. this.vy = Math.sqrt(energyBefore/this.options.mass);
  16016. };
  16017. module.exports = Node;
  16018. /***/ },
  16019. /* 37 */
  16020. /***/ function(module, exports, __webpack_require__) {
  16021. /**
  16022. * Popup is a class to create a popup window with some text
  16023. * @param {Element} container The container object.
  16024. * @param {Number} [x]
  16025. * @param {Number} [y]
  16026. * @param {String} [text]
  16027. * @param {Object} [style] An object containing borderColor,
  16028. * backgroundColor, etc.
  16029. */
  16030. function Popup(container, x, y, text, style) {
  16031. if (container) {
  16032. this.container = container;
  16033. }
  16034. else {
  16035. this.container = document.body;
  16036. }
  16037. // x, y and text are optional, see if a style object was passed in their place
  16038. if (style === undefined) {
  16039. if (typeof x === "object") {
  16040. style = x;
  16041. x = undefined;
  16042. } else if (typeof text === "object") {
  16043. style = text;
  16044. text = undefined;
  16045. } else {
  16046. // for backwards compatibility, in case clients other than Network are creating Popup directly
  16047. style = {
  16048. fontColor: 'black',
  16049. fontSize: 14, // px
  16050. fontFace: 'verdana',
  16051. color: {
  16052. border: '#666',
  16053. background: '#FFFFC6'
  16054. }
  16055. }
  16056. }
  16057. }
  16058. this.x = 0;
  16059. this.y = 0;
  16060. this.padding = 5;
  16061. if (x !== undefined && y !== undefined ) {
  16062. this.setPosition(x, y);
  16063. }
  16064. if (text !== undefined) {
  16065. this.setText(text);
  16066. }
  16067. // create the frame
  16068. this.frame = document.createElement("div");
  16069. var styleAttr = this.frame.style;
  16070. styleAttr.position = "absolute";
  16071. styleAttr.visibility = "hidden";
  16072. styleAttr.border = "1px solid " + style.color.border;
  16073. styleAttr.color = style.fontColor;
  16074. styleAttr.fontSize = style.fontSize + "px";
  16075. styleAttr.fontFamily = style.fontFace;
  16076. styleAttr.padding = this.padding + "px";
  16077. styleAttr.backgroundColor = style.color.background;
  16078. styleAttr.borderRadius = "3px";
  16079. styleAttr.MozBorderRadius = "3px";
  16080. styleAttr.WebkitBorderRadius = "3px";
  16081. styleAttr.boxShadow = "3px 3px 10px rgba(128, 128, 128, 0.5)";
  16082. styleAttr.whiteSpace = "nowrap";
  16083. this.container.appendChild(this.frame);
  16084. }
  16085. /**
  16086. * @param {number} x Horizontal position of the popup window
  16087. * @param {number} y Vertical position of the popup window
  16088. */
  16089. Popup.prototype.setPosition = function(x, y) {
  16090. this.x = parseInt(x);
  16091. this.y = parseInt(y);
  16092. };
  16093. /**
  16094. * Set the text for the popup window. This can be HTML code
  16095. * @param {string} text
  16096. */
  16097. Popup.prototype.setText = function(text) {
  16098. this.frame.innerHTML = text;
  16099. };
  16100. /**
  16101. * Show the popup window
  16102. * @param {boolean} show Optional. Show or hide the window
  16103. */
  16104. Popup.prototype.show = function (show) {
  16105. if (show === undefined) {
  16106. show = true;
  16107. }
  16108. if (show) {
  16109. var height = this.frame.clientHeight;
  16110. var width = this.frame.clientWidth;
  16111. var maxHeight = this.frame.parentNode.clientHeight;
  16112. var maxWidth = this.frame.parentNode.clientWidth;
  16113. var top = (this.y - height);
  16114. if (top + height + this.padding > maxHeight) {
  16115. top = maxHeight - height - this.padding;
  16116. }
  16117. if (top < this.padding) {
  16118. top = this.padding;
  16119. }
  16120. var left = this.x;
  16121. if (left + width + this.padding > maxWidth) {
  16122. left = maxWidth - width - this.padding;
  16123. }
  16124. if (left < this.padding) {
  16125. left = this.padding;
  16126. }
  16127. this.frame.style.left = left + "px";
  16128. this.frame.style.top = top + "px";
  16129. this.frame.style.visibility = "visible";
  16130. }
  16131. else {
  16132. this.hide();
  16133. }
  16134. };
  16135. /**
  16136. * Hide the popup window
  16137. */
  16138. Popup.prototype.hide = function () {
  16139. this.frame.style.visibility = "hidden";
  16140. };
  16141. module.exports = Popup;
  16142. /***/ },
  16143. /* 38 */
  16144. /***/ function(module, exports, __webpack_require__) {
  16145. /**
  16146. * Parse a text source containing data in DOT language into a JSON object.
  16147. * The object contains two lists: one with nodes and one with edges.
  16148. *
  16149. * DOT language reference: http://www.graphviz.org/doc/info/lang.html
  16150. *
  16151. * @param {String} data Text containing a graph in DOT-notation
  16152. * @return {Object} graph An object containing two parameters:
  16153. * {Object[]} nodes
  16154. * {Object[]} edges
  16155. */
  16156. function parseDOT (data) {
  16157. dot = data;
  16158. return parseGraph();
  16159. }
  16160. // token types enumeration
  16161. var TOKENTYPE = {
  16162. NULL : 0,
  16163. DELIMITER : 1,
  16164. IDENTIFIER: 2,
  16165. UNKNOWN : 3
  16166. };
  16167. // map with all delimiters
  16168. var DELIMITERS = {
  16169. '{': true,
  16170. '}': true,
  16171. '[': true,
  16172. ']': true,
  16173. ';': true,
  16174. '=': true,
  16175. ',': true,
  16176. '->': true,
  16177. '--': true
  16178. };
  16179. var dot = ''; // current dot file
  16180. var index = 0; // current index in dot file
  16181. var c = ''; // current token character in expr
  16182. var token = ''; // current token
  16183. var tokenType = TOKENTYPE.NULL; // type of the token
  16184. /**
  16185. * Get the first character from the dot file.
  16186. * The character is stored into the char c. If the end of the dot file is
  16187. * reached, the function puts an empty string in c.
  16188. */
  16189. function first() {
  16190. index = 0;
  16191. c = dot.charAt(0);
  16192. }
  16193. /**
  16194. * Get the next character from the dot file.
  16195. * The character is stored into the char c. If the end of the dot file is
  16196. * reached, the function puts an empty string in c.
  16197. */
  16198. function next() {
  16199. index++;
  16200. c = dot.charAt(index);
  16201. }
  16202. /**
  16203. * Preview the next character from the dot file.
  16204. * @return {String} cNext
  16205. */
  16206. function nextPreview() {
  16207. return dot.charAt(index + 1);
  16208. }
  16209. /**
  16210. * Test whether given character is alphabetic or numeric
  16211. * @param {String} c
  16212. * @return {Boolean} isAlphaNumeric
  16213. */
  16214. var regexAlphaNumeric = /[a-zA-Z_0-9.:#]/;
  16215. function isAlphaNumeric(c) {
  16216. return regexAlphaNumeric.test(c);
  16217. }
  16218. /**
  16219. * Merge all properties of object b into object b
  16220. * @param {Object} a
  16221. * @param {Object} b
  16222. * @return {Object} a
  16223. */
  16224. function merge (a, b) {
  16225. if (!a) {
  16226. a = {};
  16227. }
  16228. if (b) {
  16229. for (var name in b) {
  16230. if (b.hasOwnProperty(name)) {
  16231. a[name] = b[name];
  16232. }
  16233. }
  16234. }
  16235. return a;
  16236. }
  16237. /**
  16238. * Set a value in an object, where the provided parameter name can be a
  16239. * path with nested parameters. For example:
  16240. *
  16241. * var obj = {a: 2};
  16242. * setValue(obj, 'b.c', 3); // obj = {a: 2, b: {c: 3}}
  16243. *
  16244. * @param {Object} obj
  16245. * @param {String} path A parameter name or dot-separated parameter path,
  16246. * like "color.highlight.border".
  16247. * @param {*} value
  16248. */
  16249. function setValue(obj, path, value) {
  16250. var keys = path.split('.');
  16251. var o = obj;
  16252. while (keys.length) {
  16253. var key = keys.shift();
  16254. if (keys.length) {
  16255. // this isn't the end point
  16256. if (!o[key]) {
  16257. o[key] = {};
  16258. }
  16259. o = o[key];
  16260. }
  16261. else {
  16262. // this is the end point
  16263. o[key] = value;
  16264. }
  16265. }
  16266. }
  16267. /**
  16268. * Add a node to a graph object. If there is already a node with
  16269. * the same id, their attributes will be merged.
  16270. * @param {Object} graph
  16271. * @param {Object} node
  16272. */
  16273. function addNode(graph, node) {
  16274. var i, len;
  16275. var current = null;
  16276. // find root graph (in case of subgraph)
  16277. var graphs = [graph]; // list with all graphs from current graph to root graph
  16278. var root = graph;
  16279. while (root.parent) {
  16280. graphs.push(root.parent);
  16281. root = root.parent;
  16282. }
  16283. // find existing node (at root level) by its id
  16284. if (root.nodes) {
  16285. for (i = 0, len = root.nodes.length; i < len; i++) {
  16286. if (node.id === root.nodes[i].id) {
  16287. current = root.nodes[i];
  16288. break;
  16289. }
  16290. }
  16291. }
  16292. if (!current) {
  16293. // this is a new node
  16294. current = {
  16295. id: node.id
  16296. };
  16297. if (graph.node) {
  16298. // clone default attributes
  16299. current.attr = merge(current.attr, graph.node);
  16300. }
  16301. }
  16302. // add node to this (sub)graph and all its parent graphs
  16303. for (i = graphs.length - 1; i >= 0; i--) {
  16304. var g = graphs[i];
  16305. if (!g.nodes) {
  16306. g.nodes = [];
  16307. }
  16308. if (g.nodes.indexOf(current) == -1) {
  16309. g.nodes.push(current);
  16310. }
  16311. }
  16312. // merge attributes
  16313. if (node.attr) {
  16314. current.attr = merge(current.attr, node.attr);
  16315. }
  16316. }
  16317. /**
  16318. * Add an edge to a graph object
  16319. * @param {Object} graph
  16320. * @param {Object} edge
  16321. */
  16322. function addEdge(graph, edge) {
  16323. if (!graph.edges) {
  16324. graph.edges = [];
  16325. }
  16326. graph.edges.push(edge);
  16327. if (graph.edge) {
  16328. var attr = merge({}, graph.edge); // clone default attributes
  16329. edge.attr = merge(attr, edge.attr); // merge attributes
  16330. }
  16331. }
  16332. /**
  16333. * Create an edge to a graph object
  16334. * @param {Object} graph
  16335. * @param {String | Number | Object} from
  16336. * @param {String | Number | Object} to
  16337. * @param {String} type
  16338. * @param {Object | null} attr
  16339. * @return {Object} edge
  16340. */
  16341. function createEdge(graph, from, to, type, attr) {
  16342. var edge = {
  16343. from: from,
  16344. to: to,
  16345. type: type
  16346. };
  16347. if (graph.edge) {
  16348. edge.attr = merge({}, graph.edge); // clone default attributes
  16349. }
  16350. edge.attr = merge(edge.attr || {}, attr); // merge attributes
  16351. return edge;
  16352. }
  16353. /**
  16354. * Get next token in the current dot file.
  16355. * The token and token type are available as token and tokenType
  16356. */
  16357. function getToken() {
  16358. tokenType = TOKENTYPE.NULL;
  16359. token = '';
  16360. // skip over whitespaces
  16361. while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter
  16362. next();
  16363. }
  16364. do {
  16365. var isComment = false;
  16366. // skip comment
  16367. if (c == '#') {
  16368. // find the previous non-space character
  16369. var i = index - 1;
  16370. while (dot.charAt(i) == ' ' || dot.charAt(i) == '\t') {
  16371. i--;
  16372. }
  16373. if (dot.charAt(i) == '\n' || dot.charAt(i) == '') {
  16374. // the # is at the start of a line, this is indeed a line comment
  16375. while (c != '' && c != '\n') {
  16376. next();
  16377. }
  16378. isComment = true;
  16379. }
  16380. }
  16381. if (c == '/' && nextPreview() == '/') {
  16382. // skip line comment
  16383. while (c != '' && c != '\n') {
  16384. next();
  16385. }
  16386. isComment = true;
  16387. }
  16388. if (c == '/' && nextPreview() == '*') {
  16389. // skip block comment
  16390. while (c != '') {
  16391. if (c == '*' && nextPreview() == '/') {
  16392. // end of block comment found. skip these last two characters
  16393. next();
  16394. next();
  16395. break;
  16396. }
  16397. else {
  16398. next();
  16399. }
  16400. }
  16401. isComment = true;
  16402. }
  16403. // skip over whitespaces
  16404. while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter
  16405. next();
  16406. }
  16407. }
  16408. while (isComment);
  16409. // check for end of dot file
  16410. if (c == '') {
  16411. // token is still empty
  16412. tokenType = TOKENTYPE.DELIMITER;
  16413. return;
  16414. }
  16415. // check for delimiters consisting of 2 characters
  16416. var c2 = c + nextPreview();
  16417. if (DELIMITERS[c2]) {
  16418. tokenType = TOKENTYPE.DELIMITER;
  16419. token = c2;
  16420. next();
  16421. next();
  16422. return;
  16423. }
  16424. // check for delimiters consisting of 1 character
  16425. if (DELIMITERS[c]) {
  16426. tokenType = TOKENTYPE.DELIMITER;
  16427. token = c;
  16428. next();
  16429. return;
  16430. }
  16431. // check for an identifier (number or string)
  16432. // TODO: more precise parsing of numbers/strings (and the port separator ':')
  16433. if (isAlphaNumeric(c) || c == '-') {
  16434. token += c;
  16435. next();
  16436. while (isAlphaNumeric(c)) {
  16437. token += c;
  16438. next();
  16439. }
  16440. if (token == 'false') {
  16441. token = false; // convert to boolean
  16442. }
  16443. else if (token == 'true') {
  16444. token = true; // convert to boolean
  16445. }
  16446. else if (!isNaN(Number(token))) {
  16447. token = Number(token); // convert to number
  16448. }
  16449. tokenType = TOKENTYPE.IDENTIFIER;
  16450. return;
  16451. }
  16452. // check for a string enclosed by double quotes
  16453. if (c == '"') {
  16454. next();
  16455. while (c != '' && (c != '"' || (c == '"' && nextPreview() == '"'))) {
  16456. token += c;
  16457. if (c == '"') { // skip the escape character
  16458. next();
  16459. }
  16460. next();
  16461. }
  16462. if (c != '"') {
  16463. throw newSyntaxError('End of string " expected');
  16464. }
  16465. next();
  16466. tokenType = TOKENTYPE.IDENTIFIER;
  16467. return;
  16468. }
  16469. // something unknown is found, wrong characters, a syntax error
  16470. tokenType = TOKENTYPE.UNKNOWN;
  16471. while (c != '') {
  16472. token += c;
  16473. next();
  16474. }
  16475. throw new SyntaxError('Syntax error in part "' + chop(token, 30) + '"');
  16476. }
  16477. /**
  16478. * Parse a graph.
  16479. * @returns {Object} graph
  16480. */
  16481. function parseGraph() {
  16482. var graph = {};
  16483. first();
  16484. getToken();
  16485. // optional strict keyword
  16486. if (token == 'strict') {
  16487. graph.strict = true;
  16488. getToken();
  16489. }
  16490. // graph or digraph keyword
  16491. if (token == 'graph' || token == 'digraph') {
  16492. graph.type = token;
  16493. getToken();
  16494. }
  16495. // optional graph id
  16496. if (tokenType == TOKENTYPE.IDENTIFIER) {
  16497. graph.id = token;
  16498. getToken();
  16499. }
  16500. // open angle bracket
  16501. if (token != '{') {
  16502. throw newSyntaxError('Angle bracket { expected');
  16503. }
  16504. getToken();
  16505. // statements
  16506. parseStatements(graph);
  16507. // close angle bracket
  16508. if (token != '}') {
  16509. throw newSyntaxError('Angle bracket } expected');
  16510. }
  16511. getToken();
  16512. // end of file
  16513. if (token !== '') {
  16514. throw newSyntaxError('End of file expected');
  16515. }
  16516. getToken();
  16517. // remove temporary default properties
  16518. delete graph.node;
  16519. delete graph.edge;
  16520. delete graph.graph;
  16521. return graph;
  16522. }
  16523. /**
  16524. * Parse a list with statements.
  16525. * @param {Object} graph
  16526. */
  16527. function parseStatements (graph) {
  16528. while (token !== '' && token != '}') {
  16529. parseStatement(graph);
  16530. if (token == ';') {
  16531. getToken();
  16532. }
  16533. }
  16534. }
  16535. /**
  16536. * Parse a single statement. Can be a an attribute statement, node
  16537. * statement, a series of node statements and edge statements, or a
  16538. * parameter.
  16539. * @param {Object} graph
  16540. */
  16541. function parseStatement(graph) {
  16542. // parse subgraph
  16543. var subgraph = parseSubgraph(graph);
  16544. if (subgraph) {
  16545. // edge statements
  16546. parseEdge(graph, subgraph);
  16547. return;
  16548. }
  16549. // parse an attribute statement
  16550. var attr = parseAttributeStatement(graph);
  16551. if (attr) {
  16552. return;
  16553. }
  16554. // parse node
  16555. if (tokenType != TOKENTYPE.IDENTIFIER) {
  16556. throw newSyntaxError('Identifier expected');
  16557. }
  16558. var id = token; // id can be a string or a number
  16559. getToken();
  16560. if (token == '=') {
  16561. // id statement
  16562. getToken();
  16563. if (tokenType != TOKENTYPE.IDENTIFIER) {
  16564. throw newSyntaxError('Identifier expected');
  16565. }
  16566. graph[id] = token;
  16567. getToken();
  16568. // TODO: implement comma separated list with "a_list: ID=ID [','] [a_list] "
  16569. }
  16570. else {
  16571. parseNodeStatement(graph, id);
  16572. }
  16573. }
  16574. /**
  16575. * Parse a subgraph
  16576. * @param {Object} graph parent graph object
  16577. * @return {Object | null} subgraph
  16578. */
  16579. function parseSubgraph (graph) {
  16580. var subgraph = null;
  16581. // optional subgraph keyword
  16582. if (token == 'subgraph') {
  16583. subgraph = {};
  16584. subgraph.type = 'subgraph';
  16585. getToken();
  16586. // optional graph id
  16587. if (tokenType == TOKENTYPE.IDENTIFIER) {
  16588. subgraph.id = token;
  16589. getToken();
  16590. }
  16591. }
  16592. // open angle bracket
  16593. if (token == '{') {
  16594. getToken();
  16595. if (!subgraph) {
  16596. subgraph = {};
  16597. }
  16598. subgraph.parent = graph;
  16599. subgraph.node = graph.node;
  16600. subgraph.edge = graph.edge;
  16601. subgraph.graph = graph.graph;
  16602. // statements
  16603. parseStatements(subgraph);
  16604. // close angle bracket
  16605. if (token != '}') {
  16606. throw newSyntaxError('Angle bracket } expected');
  16607. }
  16608. getToken();
  16609. // remove temporary default properties
  16610. delete subgraph.node;
  16611. delete subgraph.edge;
  16612. delete subgraph.graph;
  16613. delete subgraph.parent;
  16614. // register at the parent graph
  16615. if (!graph.subgraphs) {
  16616. graph.subgraphs = [];
  16617. }
  16618. graph.subgraphs.push(subgraph);
  16619. }
  16620. return subgraph;
  16621. }
  16622. /**
  16623. * parse an attribute statement like "node [shape=circle fontSize=16]".
  16624. * Available keywords are 'node', 'edge', 'graph'.
  16625. * The previous list with default attributes will be replaced
  16626. * @param {Object} graph
  16627. * @returns {String | null} keyword Returns the name of the parsed attribute
  16628. * (node, edge, graph), or null if nothing
  16629. * is parsed.
  16630. */
  16631. function parseAttributeStatement (graph) {
  16632. // attribute statements
  16633. if (token == 'node') {
  16634. getToken();
  16635. // node attributes
  16636. graph.node = parseAttributeList();
  16637. return 'node';
  16638. }
  16639. else if (token == 'edge') {
  16640. getToken();
  16641. // edge attributes
  16642. graph.edge = parseAttributeList();
  16643. return 'edge';
  16644. }
  16645. else if (token == 'graph') {
  16646. getToken();
  16647. // graph attributes
  16648. graph.graph = parseAttributeList();
  16649. return 'graph';
  16650. }
  16651. return null;
  16652. }
  16653. /**
  16654. * parse a node statement
  16655. * @param {Object} graph
  16656. * @param {String | Number} id
  16657. */
  16658. function parseNodeStatement(graph, id) {
  16659. // node statement
  16660. var node = {
  16661. id: id
  16662. };
  16663. var attr = parseAttributeList();
  16664. if (attr) {
  16665. node.attr = attr;
  16666. }
  16667. addNode(graph, node);
  16668. // edge statements
  16669. parseEdge(graph, id);
  16670. }
  16671. /**
  16672. * Parse an edge or a series of edges
  16673. * @param {Object} graph
  16674. * @param {String | Number} from Id of the from node
  16675. */
  16676. function parseEdge(graph, from) {
  16677. while (token == '->' || token == '--') {
  16678. var to;
  16679. var type = token;
  16680. getToken();
  16681. var subgraph = parseSubgraph(graph);
  16682. if (subgraph) {
  16683. to = subgraph;
  16684. }
  16685. else {
  16686. if (tokenType != TOKENTYPE.IDENTIFIER) {
  16687. throw newSyntaxError('Identifier or subgraph expected');
  16688. }
  16689. to = token;
  16690. addNode(graph, {
  16691. id: to
  16692. });
  16693. getToken();
  16694. }
  16695. // parse edge attributes
  16696. var attr = parseAttributeList();
  16697. // create edge
  16698. var edge = createEdge(graph, from, to, type, attr);
  16699. addEdge(graph, edge);
  16700. from = to;
  16701. }
  16702. }
  16703. /**
  16704. * Parse a set with attributes,
  16705. * for example [label="1.000", shape=solid]
  16706. * @return {Object | null} attr
  16707. */
  16708. function parseAttributeList() {
  16709. var attr = null;
  16710. while (token == '[') {
  16711. getToken();
  16712. attr = {};
  16713. while (token !== '' && token != ']') {
  16714. if (tokenType != TOKENTYPE.IDENTIFIER) {
  16715. throw newSyntaxError('Attribute name expected');
  16716. }
  16717. var name = token;
  16718. getToken();
  16719. if (token != '=') {
  16720. throw newSyntaxError('Equal sign = expected');
  16721. }
  16722. getToken();
  16723. if (tokenType != TOKENTYPE.IDENTIFIER) {
  16724. throw newSyntaxError('Attribute value expected');
  16725. }
  16726. var value = token;
  16727. setValue(attr, name, value); // name can be a path
  16728. getToken();
  16729. if (token ==',') {
  16730. getToken();
  16731. }
  16732. }
  16733. if (token != ']') {
  16734. throw newSyntaxError('Bracket ] expected');
  16735. }
  16736. getToken();
  16737. }
  16738. return attr;
  16739. }
  16740. /**
  16741. * Create a syntax error with extra information on current token and index.
  16742. * @param {String} message
  16743. * @returns {SyntaxError} err
  16744. */
  16745. function newSyntaxError(message) {
  16746. return new SyntaxError(message + ', got "' + chop(token, 30) + '" (char ' + index + ')');
  16747. }
  16748. /**
  16749. * Chop off text after a maximum length
  16750. * @param {String} text
  16751. * @param {Number} maxLength
  16752. * @returns {String}
  16753. */
  16754. function chop (text, maxLength) {
  16755. return (text.length <= maxLength) ? text : (text.substr(0, 27) + '...');
  16756. }
  16757. /**
  16758. * Execute a function fn for each pair of elements in two arrays
  16759. * @param {Array | *} array1
  16760. * @param {Array | *} array2
  16761. * @param {function} fn
  16762. */
  16763. function forEach2(array1, array2, fn) {
  16764. if (array1 instanceof Array) {
  16765. array1.forEach(function (elem1) {
  16766. if (array2 instanceof Array) {
  16767. array2.forEach(function (elem2) {
  16768. fn(elem1, elem2);
  16769. });
  16770. }
  16771. else {
  16772. fn(elem1, array2);
  16773. }
  16774. });
  16775. }
  16776. else {
  16777. if (array2 instanceof Array) {
  16778. array2.forEach(function (elem2) {
  16779. fn(array1, elem2);
  16780. });
  16781. }
  16782. else {
  16783. fn(array1, array2);
  16784. }
  16785. }
  16786. }
  16787. /**
  16788. * Convert a string containing a graph in DOT language into a map containing
  16789. * with nodes and edges in the format of graph.
  16790. * @param {String} data Text containing a graph in DOT-notation
  16791. * @return {Object} graphData
  16792. */
  16793. function DOTToGraph (data) {
  16794. // parse the DOT file
  16795. var dotData = parseDOT(data);
  16796. var graphData = {
  16797. nodes: [],
  16798. edges: [],
  16799. options: {}
  16800. };
  16801. // copy the nodes
  16802. if (dotData.nodes) {
  16803. dotData.nodes.forEach(function (dotNode) {
  16804. var graphNode = {
  16805. id: dotNode.id,
  16806. label: String(dotNode.label || dotNode.id)
  16807. };
  16808. merge(graphNode, dotNode.attr);
  16809. if (graphNode.image) {
  16810. graphNode.shape = 'image';
  16811. }
  16812. graphData.nodes.push(graphNode);
  16813. });
  16814. }
  16815. // copy the edges
  16816. if (dotData.edges) {
  16817. /**
  16818. * Convert an edge in DOT format to an edge with VisGraph format
  16819. * @param {Object} dotEdge
  16820. * @returns {Object} graphEdge
  16821. */
  16822. function convertEdge(dotEdge) {
  16823. var graphEdge = {
  16824. from: dotEdge.from,
  16825. to: dotEdge.to
  16826. };
  16827. merge(graphEdge, dotEdge.attr);
  16828. graphEdge.style = (dotEdge.type == '->') ? 'arrow' : 'line';
  16829. return graphEdge;
  16830. }
  16831. dotData.edges.forEach(function (dotEdge) {
  16832. var from, to;
  16833. if (dotEdge.from instanceof Object) {
  16834. from = dotEdge.from.nodes;
  16835. }
  16836. else {
  16837. from = {
  16838. id: dotEdge.from
  16839. }
  16840. }
  16841. if (dotEdge.to instanceof Object) {
  16842. to = dotEdge.to.nodes;
  16843. }
  16844. else {
  16845. to = {
  16846. id: dotEdge.to
  16847. }
  16848. }
  16849. if (dotEdge.from instanceof Object && dotEdge.from.edges) {
  16850. dotEdge.from.edges.forEach(function (subEdge) {
  16851. var graphEdge = convertEdge(subEdge);
  16852. graphData.edges.push(graphEdge);
  16853. });
  16854. }
  16855. forEach2(from, to, function (from, to) {
  16856. var subEdge = createEdge(graphData, from.id, to.id, dotEdge.type, dotEdge.attr);
  16857. var graphEdge = convertEdge(subEdge);
  16858. graphData.edges.push(graphEdge);
  16859. });
  16860. if (dotEdge.to instanceof Object && dotEdge.to.edges) {
  16861. dotEdge.to.edges.forEach(function (subEdge) {
  16862. var graphEdge = convertEdge(subEdge);
  16863. graphData.edges.push(graphEdge);
  16864. });
  16865. }
  16866. });
  16867. }
  16868. // copy the options
  16869. if (dotData.attr) {
  16870. graphData.options = dotData.attr;
  16871. }
  16872. return graphData;
  16873. }
  16874. // exports
  16875. exports.parseDOT = parseDOT;
  16876. exports.DOTToGraph = DOTToGraph;
  16877. /***/ },
  16878. /* 39 */
  16879. /***/ function(module, exports, __webpack_require__) {
  16880. function parseGephi(gephiJSON, options) {
  16881. var edges = [];
  16882. var nodes = [];
  16883. this.options = {
  16884. edges: {
  16885. inheritColor: true
  16886. },
  16887. nodes: {
  16888. allowedToMove: false,
  16889. parseColor: false
  16890. }
  16891. };
  16892. if (options !== undefined) {
  16893. this.options.nodes['allowedToMove'] = options.allowedToMove | false;
  16894. this.options.nodes['parseColor'] = options.parseColor | false;
  16895. this.options.edges['inheritColor'] = options.inheritColor | true;
  16896. }
  16897. var gEdges = gephiJSON.edges;
  16898. var gNodes = gephiJSON.nodes;
  16899. for (var i = 0; i < gEdges.length; i++) {
  16900. var edge = {};
  16901. var gEdge = gEdges[i];
  16902. edge['id'] = gEdge.id;
  16903. edge['from'] = gEdge.source;
  16904. edge['to'] = gEdge.target;
  16905. edge['attributes'] = gEdge.attributes;
  16906. // edge['value'] = gEdge.attributes !== undefined ? gEdge.attributes.Weight : undefined;
  16907. // edge['width'] = edge['value'] !== undefined ? undefined : edgegEdge.size;
  16908. edge['color'] = gEdge.color;
  16909. edge['inheritColor'] = edge['color'] !== undefined ? false : this.options.inheritColor;
  16910. edges.push(edge);
  16911. }
  16912. for (var i = 0; i < gNodes.length; i++) {
  16913. var node = {};
  16914. var gNode = gNodes[i];
  16915. node['id'] = gNode.id;
  16916. node['attributes'] = gNode.attributes;
  16917. node['x'] = gNode.x;
  16918. node['y'] = gNode.y;
  16919. node['label'] = gNode.label;
  16920. if (this.options.nodes.parseColor == true) {
  16921. node['color'] = gNode.color;
  16922. }
  16923. else {
  16924. node['color'] = gNode.color !== undefined ? {background:gNode.color, border:gNode.color} : undefined;
  16925. }
  16926. node['radius'] = gNode.size;
  16927. node['allowedToMoveX'] = this.options.nodes.allowedToMove;
  16928. node['allowedToMoveY'] = this.options.nodes.allowedToMove;
  16929. nodes.push(node);
  16930. }
  16931. return {nodes:nodes, edges:edges};
  16932. }
  16933. exports.parseGephi = parseGephi;
  16934. /***/ },
  16935. /* 40 */
  16936. /***/ function(module, exports, __webpack_require__) {
  16937. // first check if moment.js is already loaded in the browser window, if so,
  16938. // use this instance. Else, load via commonjs.
  16939. module.exports = (typeof window !== 'undefined') && window['moment'] || __webpack_require__(51);
  16940. /***/ },
  16941. /* 41 */
  16942. /***/ function(module, exports, __webpack_require__) {
  16943. // Only load hammer.js when in a browser environment
  16944. // (loading hammer.js in a node.js environment gives errors)
  16945. if (typeof window !== 'undefined') {
  16946. module.exports = window['Hammer'] || __webpack_require__(52);
  16947. }
  16948. else {
  16949. module.exports = function () {
  16950. throw Error('hammer.js is only available in a browser, not in node.js.');
  16951. }
  16952. }
  16953. /***/ },
  16954. /* 42 */
  16955. /***/ function(module, exports, __webpack_require__) {
  16956. var Emitter = __webpack_require__(49);
  16957. var Hammer = __webpack_require__(41);
  16958. var util = __webpack_require__(1);
  16959. var DataSet = __webpack_require__(3);
  16960. var DataView = __webpack_require__(4);
  16961. var Range = __webpack_require__(15);
  16962. var TimeAxis = __webpack_require__(27);
  16963. var CurrentTime = __webpack_require__(19);
  16964. var CustomTime = __webpack_require__(20);
  16965. var ItemSet = __webpack_require__(24);
  16966. var Activator = __webpack_require__(48);
  16967. /**
  16968. * Create a timeline visualization
  16969. * @param {HTMLElement} container
  16970. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  16971. * @param {Object} [options] See Core.setOptions for the available options.
  16972. * @constructor
  16973. */
  16974. function Core () {}
  16975. // turn Core into an event emitter
  16976. Emitter(Core.prototype);
  16977. /**
  16978. * Create the main DOM for the Core: a root panel containing left, right,
  16979. * top, bottom, content, and background panel.
  16980. * @param {Element} container The container element where the Core will
  16981. * be attached.
  16982. * @private
  16983. */
  16984. Core.prototype._create = function (container) {
  16985. this.dom = {};
  16986. this.dom.root = document.createElement('div');
  16987. this.dom.background = document.createElement('div');
  16988. this.dom.backgroundVertical = document.createElement('div');
  16989. this.dom.backgroundHorizontal = document.createElement('div');
  16990. this.dom.centerContainer = document.createElement('div');
  16991. this.dom.leftContainer = document.createElement('div');
  16992. this.dom.rightContainer = document.createElement('div');
  16993. this.dom.center = document.createElement('div');
  16994. this.dom.left = document.createElement('div');
  16995. this.dom.right = document.createElement('div');
  16996. this.dom.top = document.createElement('div');
  16997. this.dom.bottom = document.createElement('div');
  16998. this.dom.shadowTop = document.createElement('div');
  16999. this.dom.shadowBottom = document.createElement('div');
  17000. this.dom.shadowTopLeft = document.createElement('div');
  17001. this.dom.shadowBottomLeft = document.createElement('div');
  17002. this.dom.shadowTopRight = document.createElement('div');
  17003. this.dom.shadowBottomRight = document.createElement('div');
  17004. this.dom.root.className = 'vis timeline root';
  17005. this.dom.background.className = 'vispanel background';
  17006. this.dom.backgroundVertical.className = 'vispanel background vertical';
  17007. this.dom.backgroundHorizontal.className = 'vispanel background horizontal';
  17008. this.dom.centerContainer.className = 'vispanel center';
  17009. this.dom.leftContainer.className = 'vispanel left';
  17010. this.dom.rightContainer.className = 'vispanel right';
  17011. this.dom.top.className = 'vispanel top';
  17012. this.dom.bottom.className = 'vispanel bottom';
  17013. this.dom.left.className = 'content';
  17014. this.dom.center.className = 'content';
  17015. this.dom.right.className = 'content';
  17016. this.dom.shadowTop.className = 'shadow top';
  17017. this.dom.shadowBottom.className = 'shadow bottom';
  17018. this.dom.shadowTopLeft.className = 'shadow top';
  17019. this.dom.shadowBottomLeft.className = 'shadow bottom';
  17020. this.dom.shadowTopRight.className = 'shadow top';
  17021. this.dom.shadowBottomRight.className = 'shadow bottom';
  17022. this.dom.root.appendChild(this.dom.background);
  17023. this.dom.root.appendChild(this.dom.backgroundVertical);
  17024. this.dom.root.appendChild(this.dom.backgroundHorizontal);
  17025. this.dom.root.appendChild(this.dom.centerContainer);
  17026. this.dom.root.appendChild(this.dom.leftContainer);
  17027. this.dom.root.appendChild(this.dom.rightContainer);
  17028. this.dom.root.appendChild(this.dom.top);
  17029. this.dom.root.appendChild(this.dom.bottom);
  17030. this.dom.centerContainer.appendChild(this.dom.center);
  17031. this.dom.leftContainer.appendChild(this.dom.left);
  17032. this.dom.rightContainer.appendChild(this.dom.right);
  17033. this.dom.centerContainer.appendChild(this.dom.shadowTop);
  17034. this.dom.centerContainer.appendChild(this.dom.shadowBottom);
  17035. this.dom.leftContainer.appendChild(this.dom.shadowTopLeft);
  17036. this.dom.leftContainer.appendChild(this.dom.shadowBottomLeft);
  17037. this.dom.rightContainer.appendChild(this.dom.shadowTopRight);
  17038. this.dom.rightContainer.appendChild(this.dom.shadowBottomRight);
  17039. this.on('rangechange', this.redraw.bind(this));
  17040. this.on('change', this.redraw.bind(this));
  17041. this.on('touch', this._onTouch.bind(this));
  17042. this.on('pinch', this._onPinch.bind(this));
  17043. this.on('dragstart', this._onDragStart.bind(this));
  17044. this.on('drag', this._onDrag.bind(this));
  17045. // create event listeners for all interesting events, these events will be
  17046. // emitted via emitter
  17047. this.hammer = Hammer(this.dom.root, {
  17048. prevent_default: true
  17049. });
  17050. this.listeners = {};
  17051. var me = this;
  17052. var events = [
  17053. 'touch', 'pinch',
  17054. 'tap', 'doubletap', 'hold',
  17055. 'dragstart', 'drag', 'dragend',
  17056. 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
  17057. ];
  17058. events.forEach(function (event) {
  17059. var listener = function () {
  17060. var args = [event].concat(Array.prototype.slice.call(arguments, 0));
  17061. if (me.isActive()) {
  17062. me.emit.apply(me, args);
  17063. }
  17064. };
  17065. me.hammer.on(event, listener);
  17066. me.listeners[event] = listener;
  17067. });
  17068. // size properties of each of the panels
  17069. this.props = {
  17070. root: {},
  17071. background: {},
  17072. centerContainer: {},
  17073. leftContainer: {},
  17074. rightContainer: {},
  17075. center: {},
  17076. left: {},
  17077. right: {},
  17078. top: {},
  17079. bottom: {},
  17080. border: {},
  17081. scrollTop: 0,
  17082. scrollTopMin: 0
  17083. };
  17084. this.touch = {}; // store state information needed for touch events
  17085. // attach the root panel to the provided container
  17086. if (!container) throw new Error('No container provided');
  17087. container.appendChild(this.dom.root);
  17088. };
  17089. /**
  17090. * Set options. Options will be passed to all components loaded in the Timeline.
  17091. * @param {Object} [options]
  17092. * {String} orientation
  17093. * Vertical orientation for the Timeline,
  17094. * can be 'bottom' (default) or 'top'.
  17095. * {String | Number} width
  17096. * Width for the timeline, a number in pixels or
  17097. * a css string like '1000px' or '75%'. '100%' by default.
  17098. * {String | Number} height
  17099. * Fixed height for the Timeline, a number in pixels or
  17100. * a css string like '400px' or '75%'. If undefined,
  17101. * The Timeline will automatically size such that
  17102. * its contents fit.
  17103. * {String | Number} minHeight
  17104. * Minimum height for the Timeline, a number in pixels or
  17105. * a css string like '400px' or '75%'.
  17106. * {String | Number} maxHeight
  17107. * Maximum height for the Timeline, a number in pixels or
  17108. * a css string like '400px' or '75%'.
  17109. * {Number | Date | String} start
  17110. * Start date for the visible window
  17111. * {Number | Date | String} end
  17112. * End date for the visible window
  17113. */
  17114. Core.prototype.setOptions = function (options) {
  17115. if (options) {
  17116. // copy the known options
  17117. var fields = ['width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'start', 'end', 'orientation', 'clickToUse'];
  17118. util.selectiveExtend(fields, this.options, options);
  17119. if ('clickToUse' in options) {
  17120. if (options.clickToUse) {
  17121. this.activator = new Activator(this.dom.root);
  17122. }
  17123. else {
  17124. if (this.activator) {
  17125. this.activator.destroy();
  17126. delete this.activator;
  17127. }
  17128. }
  17129. }
  17130. // enable/disable autoResize
  17131. this._initAutoResize();
  17132. }
  17133. // propagate options to all components
  17134. this.components.forEach(function (component) {
  17135. component.setOptions(options);
  17136. });
  17137. // TODO: remove deprecation error one day (deprecated since version 0.8.0)
  17138. if (options && options.order) {
  17139. throw new Error('Option order is deprecated. There is no replacement for this feature.');
  17140. }
  17141. // redraw everything
  17142. this.redraw();
  17143. };
  17144. /**
  17145. * Returns true when the Timeline is active.
  17146. * @returns {boolean}
  17147. */
  17148. Core.prototype.isActive = function () {
  17149. return !this.activator || this.activator.active;
  17150. };
  17151. /**
  17152. * Destroy the Core, clean up all DOM elements and event listeners.
  17153. */
  17154. Core.prototype.destroy = function () {
  17155. // unbind datasets
  17156. this.clear();
  17157. // remove all event listeners
  17158. this.off();
  17159. // stop checking for changed size
  17160. this._stopAutoResize();
  17161. // remove from DOM
  17162. if (this.dom.root.parentNode) {
  17163. this.dom.root.parentNode.removeChild(this.dom.root);
  17164. }
  17165. this.dom = null;
  17166. // remove Activator
  17167. if (this.activator) {
  17168. this.activator.destroy();
  17169. delete this.activator;
  17170. }
  17171. // cleanup hammer touch events
  17172. for (var event in this.listeners) {
  17173. if (this.listeners.hasOwnProperty(event)) {
  17174. delete this.listeners[event];
  17175. }
  17176. }
  17177. this.listeners = null;
  17178. this.hammer = null;
  17179. // give all components the opportunity to cleanup
  17180. this.components.forEach(function (component) {
  17181. component.destroy();
  17182. });
  17183. this.body = null;
  17184. };
  17185. /**
  17186. * Set a custom time bar
  17187. * @param {Date} time
  17188. */
  17189. Core.prototype.setCustomTime = function (time) {
  17190. if (!this.customTime) {
  17191. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  17192. }
  17193. this.customTime.setCustomTime(time);
  17194. };
  17195. /**
  17196. * Retrieve the current custom time.
  17197. * @return {Date} customTime
  17198. */
  17199. Core.prototype.getCustomTime = function() {
  17200. if (!this.customTime) {
  17201. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  17202. }
  17203. return this.customTime.getCustomTime();
  17204. };
  17205. /**
  17206. * Get the id's of the currently visible items.
  17207. * @returns {Array} The ids of the visible items
  17208. */
  17209. Core.prototype.getVisibleItems = function() {
  17210. return this.itemSet && this.itemSet.getVisibleItems() || [];
  17211. };
  17212. /**
  17213. * Clear the Core. By Default, items, groups and options are cleared.
  17214. * Example usage:
  17215. *
  17216. * timeline.clear(); // clear items, groups, and options
  17217. * timeline.clear({options: true}); // clear options only
  17218. *
  17219. * @param {Object} [what] Optionally specify what to clear. By default:
  17220. * {items: true, groups: true, options: true}
  17221. */
  17222. Core.prototype.clear = function(what) {
  17223. // clear items
  17224. if (!what || what.items) {
  17225. this.setItems(null);
  17226. }
  17227. // clear groups
  17228. if (!what || what.groups) {
  17229. this.setGroups(null);
  17230. }
  17231. // clear options of timeline and of each of the components
  17232. if (!what || what.options) {
  17233. this.components.forEach(function (component) {
  17234. component.setOptions(component.defaultOptions);
  17235. });
  17236. this.setOptions(this.defaultOptions); // this will also do a redraw
  17237. }
  17238. };
  17239. /**
  17240. * Set Core window such that it fits all items
  17241. * @param {Object} [options] Available options:
  17242. * `animate: boolean | number`
  17243. * If true (default), the range is animated
  17244. * smoothly to the new window.
  17245. * If a number, the number is taken as duration
  17246. * for the animation. Default duration is 500 ms.
  17247. */
  17248. Core.prototype.fit = function(options) {
  17249. // apply the data range as range
  17250. var dataRange = this.getItemRange();
  17251. // add 5% space on both sides
  17252. var start = dataRange.min;
  17253. var end = dataRange.max;
  17254. if (start != null && end != null) {
  17255. var interval = (end.valueOf() - start.valueOf());
  17256. if (interval <= 0) {
  17257. // prevent an empty interval
  17258. interval = 24 * 60 * 60 * 1000; // 1 day
  17259. }
  17260. start = new Date(start.valueOf() - interval * 0.05);
  17261. end = new Date(end.valueOf() + interval * 0.05);
  17262. }
  17263. // skip range set if there is no start and end date
  17264. if (start === null && end === null) {
  17265. return;
  17266. }
  17267. var animate = (options && options.animate !== undefined) ? options.animate : true;
  17268. this.range.setRange(start, end, animate);
  17269. };
  17270. /**
  17271. * Set the visible window. Both parameters are optional, you can change only
  17272. * start or only end. Syntax:
  17273. *
  17274. * TimeLine.setWindow(start, end)
  17275. * TimeLine.setWindow(range)
  17276. *
  17277. * Where start and end can be a Date, number, or string, and range is an
  17278. * object with properties start and end.
  17279. *
  17280. * @param {Date | Number | String | Object} [start] Start date of visible window
  17281. * @param {Date | Number | String} [end] End date of visible window
  17282. * @param {Object} [options] Available options:
  17283. * `animate: boolean | number`
  17284. * If true (default), the range is animated
  17285. * smoothly to the new window.
  17286. * If a number, the number is taken as duration
  17287. * for the animation. Default duration is 500 ms.
  17288. */
  17289. Core.prototype.setWindow = function(start, end, options) {
  17290. var animate = (options && options.animate !== undefined) ? options.animate : true;
  17291. if (arguments.length == 1) {
  17292. var range = arguments[0];
  17293. this.range.setRange(range.start, range.end, animate);
  17294. }
  17295. else {
  17296. this.range.setRange(start, end, animate);
  17297. }
  17298. };
  17299. /**
  17300. * Move the window such that given time is centered on screen.
  17301. * @param {Date | Number | String} time
  17302. * @param {Object} [options] Available options:
  17303. * `animate: boolean | number`
  17304. * If true (default), the range is animated
  17305. * smoothly to the new window.
  17306. * If a number, the number is taken as duration
  17307. * for the animation. Default duration is 500 ms.
  17308. */
  17309. Core.prototype.moveTo = function(time, options) {
  17310. var interval = this.range.end - this.range.start;
  17311. var t = util.convert(time, 'Date').valueOf();
  17312. var start = t - interval / 2;
  17313. var end = t + interval / 2;
  17314. var animate = (options && options.animate !== undefined) ? options.animate : true;
  17315. this.range.setRange(start, end, animate);
  17316. };
  17317. /**
  17318. * Get the visible window
  17319. * @return {{start: Date, end: Date}} Visible range
  17320. */
  17321. Core.prototype.getWindow = function() {
  17322. var range = this.range.getRange();
  17323. return {
  17324. start: new Date(range.start),
  17325. end: new Date(range.end)
  17326. };
  17327. };
  17328. /**
  17329. * Force a redraw of the Core. Can be useful to manually redraw when
  17330. * option autoResize=false
  17331. */
  17332. Core.prototype.redraw = function() {
  17333. var resized = false,
  17334. options = this.options,
  17335. props = this.props,
  17336. dom = this.dom;
  17337. if (!dom) return; // when destroyed
  17338. // update class names
  17339. if (options.orientation == 'top') {
  17340. util.addClassName(dom.root, 'top');
  17341. util.removeClassName(dom.root, 'bottom');
  17342. }
  17343. else {
  17344. util.removeClassName(dom.root, 'top');
  17345. util.addClassName(dom.root, 'bottom');
  17346. }
  17347. // update root width and height options
  17348. dom.root.style.maxHeight = util.option.asSize(options.maxHeight, '');
  17349. dom.root.style.minHeight = util.option.asSize(options.minHeight, '');
  17350. dom.root.style.width = util.option.asSize(options.width, '');
  17351. // calculate border widths
  17352. props.border.left = (dom.centerContainer.offsetWidth - dom.centerContainer.clientWidth) / 2;
  17353. props.border.right = props.border.left;
  17354. props.border.top = (dom.centerContainer.offsetHeight - dom.centerContainer.clientHeight) / 2;
  17355. props.border.bottom = props.border.top;
  17356. var borderRootHeight= dom.root.offsetHeight - dom.root.clientHeight;
  17357. var borderRootWidth = dom.root.offsetWidth - dom.root.clientWidth;
  17358. // calculate the heights. If any of the side panels is empty, we set the height to
  17359. // minus the border width, such that the border will be invisible
  17360. props.center.height = dom.center.offsetHeight;
  17361. props.left.height = dom.left.offsetHeight;
  17362. props.right.height = dom.right.offsetHeight;
  17363. props.top.height = dom.top.clientHeight || -props.border.top;
  17364. props.bottom.height = dom.bottom.clientHeight || -props.border.bottom;
  17365. // TODO: compensate borders when any of the panels is empty.
  17366. // apply auto height
  17367. // TODO: only calculate autoHeight when needed (else we cause an extra reflow/repaint of the DOM)
  17368. var contentHeight = Math.max(props.left.height, props.center.height, props.right.height);
  17369. var autoHeight = props.top.height + contentHeight + props.bottom.height +
  17370. borderRootHeight + props.border.top + props.border.bottom;
  17371. dom.root.style.height = util.option.asSize(options.height, autoHeight + 'px');
  17372. // calculate heights of the content panels
  17373. props.root.height = dom.root.offsetHeight;
  17374. props.background.height = props.root.height - borderRootHeight;
  17375. var containerHeight = props.root.height - props.top.height - props.bottom.height -
  17376. borderRootHeight;
  17377. props.centerContainer.height = containerHeight;
  17378. props.leftContainer.height = containerHeight;
  17379. props.rightContainer.height = props.leftContainer.height;
  17380. // calculate the widths of the panels
  17381. props.root.width = dom.root.offsetWidth;
  17382. props.background.width = props.root.width - borderRootWidth;
  17383. props.left.width = dom.leftContainer.clientWidth || -props.border.left;
  17384. props.leftContainer.width = props.left.width;
  17385. props.right.width = dom.rightContainer.clientWidth || -props.border.right;
  17386. props.rightContainer.width = props.right.width;
  17387. var centerWidth = props.root.width - props.left.width - props.right.width - borderRootWidth;
  17388. props.center.width = centerWidth;
  17389. props.centerContainer.width = centerWidth;
  17390. props.top.width = centerWidth;
  17391. props.bottom.width = centerWidth;
  17392. // resize the panels
  17393. dom.background.style.height = props.background.height + 'px';
  17394. dom.backgroundVertical.style.height = props.background.height + 'px';
  17395. dom.backgroundHorizontal.style.height = props.centerContainer.height + 'px';
  17396. dom.centerContainer.style.height = props.centerContainer.height + 'px';
  17397. dom.leftContainer.style.height = props.leftContainer.height + 'px';
  17398. dom.rightContainer.style.height = props.rightContainer.height + 'px';
  17399. dom.background.style.width = props.background.width + 'px';
  17400. dom.backgroundVertical.style.width = props.centerContainer.width + 'px';
  17401. dom.backgroundHorizontal.style.width = props.background.width + 'px';
  17402. dom.centerContainer.style.width = props.center.width + 'px';
  17403. dom.top.style.width = props.top.width + 'px';
  17404. dom.bottom.style.width = props.bottom.width + 'px';
  17405. // reposition the panels
  17406. dom.background.style.left = '0';
  17407. dom.background.style.top = '0';
  17408. dom.backgroundVertical.style.left = props.left.width + 'px';
  17409. dom.backgroundVertical.style.top = '0';
  17410. dom.backgroundHorizontal.style.left = '0';
  17411. dom.backgroundHorizontal.style.top = props.top.height + 'px';
  17412. dom.centerContainer.style.left = props.left.width + 'px';
  17413. dom.centerContainer.style.top = props.top.height + 'px';
  17414. dom.leftContainer.style.left = '0';
  17415. dom.leftContainer.style.top = props.top.height + 'px';
  17416. dom.rightContainer.style.left = (props.left.width + props.center.width) + 'px';
  17417. dom.rightContainer.style.top = props.top.height + 'px';
  17418. dom.top.style.left = props.left.width + 'px';
  17419. dom.top.style.top = '0';
  17420. dom.bottom.style.left = props.left.width + 'px';
  17421. dom.bottom.style.top = (props.top.height + props.centerContainer.height) + 'px';
  17422. // update the scrollTop, feasible range for the offset can be changed
  17423. // when the height of the Core or of the contents of the center changed
  17424. this._updateScrollTop();
  17425. // reposition the scrollable contents
  17426. var offset = this.props.scrollTop;
  17427. if (options.orientation == 'bottom') {
  17428. offset += Math.max(this.props.centerContainer.height - this.props.center.height -
  17429. this.props.border.top - this.props.border.bottom, 0);
  17430. }
  17431. dom.center.style.left = '0';
  17432. dom.center.style.top = offset + 'px';
  17433. dom.left.style.left = '0';
  17434. dom.left.style.top = offset + 'px';
  17435. dom.right.style.left = '0';
  17436. dom.right.style.top = offset + 'px';
  17437. // show shadows when vertical scrolling is available
  17438. var visibilityTop = this.props.scrollTop == 0 ? 'hidden' : '';
  17439. var visibilityBottom = this.props.scrollTop == this.props.scrollTopMin ? 'hidden' : '';
  17440. dom.shadowTop.style.visibility = visibilityTop;
  17441. dom.shadowBottom.style.visibility = visibilityBottom;
  17442. dom.shadowTopLeft.style.visibility = visibilityTop;
  17443. dom.shadowBottomLeft.style.visibility = visibilityBottom;
  17444. dom.shadowTopRight.style.visibility = visibilityTop;
  17445. dom.shadowBottomRight.style.visibility = visibilityBottom;
  17446. // redraw all components
  17447. this.components.forEach(function (component) {
  17448. resized = component.redraw() || resized;
  17449. });
  17450. if (resized) {
  17451. // keep repainting until all sizes are settled
  17452. this.redraw();
  17453. }
  17454. };
  17455. // TODO: deprecated since version 1.1.0, remove some day
  17456. Core.prototype.repaint = function () {
  17457. throw new Error('Function repaint is deprecated. Use redraw instead.');
  17458. };
  17459. /**
  17460. * Set a current time. This can be used for example to ensure that a client's
  17461. * time is synchronized with a shared server time.
  17462. * Only applicable when option `showCurrentTime` is true.
  17463. * @param {Date | String | Number} time A Date, unix timestamp, or
  17464. * ISO date string.
  17465. */
  17466. Core.prototype.setCurrentTime = function(time) {
  17467. if (!this.currentTime) {
  17468. throw new Error('Option showCurrentTime must be true');
  17469. }
  17470. this.currentTime.setCurrentTime(time);
  17471. };
  17472. /**
  17473. * Get the current time.
  17474. * Only applicable when option `showCurrentTime` is true.
  17475. * @return {Date} Returns the current time.
  17476. */
  17477. Core.prototype.getCurrentTime = function() {
  17478. if (!this.currentTime) {
  17479. throw new Error('Option showCurrentTime must be true');
  17480. }
  17481. return this.currentTime.getCurrentTime();
  17482. };
  17483. /**
  17484. * Convert a position on screen (pixels) to a datetime
  17485. * @param {int} x Position on the screen in pixels
  17486. * @return {Date} time The datetime the corresponds with given position x
  17487. * @private
  17488. */
  17489. // TODO: move this function to Range
  17490. Core.prototype._toTime = function(x) {
  17491. var conversion = this.range.conversion(this.props.center.width);
  17492. return new Date(x / conversion.scale + conversion.offset);
  17493. };
  17494. /**
  17495. * Convert a position on the global screen (pixels) to a datetime
  17496. * @param {int} x Position on the screen in pixels
  17497. * @return {Date} time The datetime the corresponds with given position x
  17498. * @private
  17499. */
  17500. // TODO: move this function to Range
  17501. Core.prototype._toGlobalTime = function(x) {
  17502. var conversion = this.range.conversion(this.props.root.width);
  17503. return new Date(x / conversion.scale + conversion.offset);
  17504. };
  17505. /**
  17506. * Convert a datetime (Date object) into a position on the screen
  17507. * @param {Date} time A date
  17508. * @return {int} x The position on the screen in pixels which corresponds
  17509. * with the given date.
  17510. * @private
  17511. */
  17512. // TODO: move this function to Range
  17513. Core.prototype._toScreen = function(time) {
  17514. var conversion = this.range.conversion(this.props.center.width);
  17515. return (time.valueOf() - conversion.offset) * conversion.scale;
  17516. };
  17517. /**
  17518. * Convert a datetime (Date object) into a position on the root
  17519. * This is used to get the pixel density estimate for the screen, not the center panel
  17520. * @param {Date} time A date
  17521. * @return {int} x The position on root in pixels which corresponds
  17522. * with the given date.
  17523. * @private
  17524. */
  17525. // TODO: move this function to Range
  17526. Core.prototype._toGlobalScreen = function(time) {
  17527. var conversion = this.range.conversion(this.props.root.width);
  17528. return (time.valueOf() - conversion.offset) * conversion.scale;
  17529. };
  17530. /**
  17531. * Initialize watching when option autoResize is true
  17532. * @private
  17533. */
  17534. Core.prototype._initAutoResize = function () {
  17535. if (this.options.autoResize == true) {
  17536. this._startAutoResize();
  17537. }
  17538. else {
  17539. this._stopAutoResize();
  17540. }
  17541. };
  17542. /**
  17543. * Watch for changes in the size of the container. On resize, the Panel will
  17544. * automatically redraw itself.
  17545. * @private
  17546. */
  17547. Core.prototype._startAutoResize = function () {
  17548. var me = this;
  17549. this._stopAutoResize();
  17550. this._onResize = function() {
  17551. if (me.options.autoResize != true) {
  17552. // stop watching when the option autoResize is changed to false
  17553. me._stopAutoResize();
  17554. return;
  17555. }
  17556. if (me.dom.root) {
  17557. // check whether the frame is resized
  17558. if ((me.dom.root.clientWidth != me.props.lastWidth) ||
  17559. (me.dom.root.clientHeight != me.props.lastHeight)) {
  17560. me.props.lastWidth = me.dom.root.clientWidth;
  17561. me.props.lastHeight = me.dom.root.clientHeight;
  17562. me.emit('change');
  17563. }
  17564. }
  17565. };
  17566. // add event listener to window resize
  17567. util.addEventListener(window, 'resize', this._onResize);
  17568. this.watchTimer = setInterval(this._onResize, 1000);
  17569. };
  17570. /**
  17571. * Stop watching for a resize of the frame.
  17572. * @private
  17573. */
  17574. Core.prototype._stopAutoResize = function () {
  17575. if (this.watchTimer) {
  17576. clearInterval(this.watchTimer);
  17577. this.watchTimer = undefined;
  17578. }
  17579. // remove event listener on window.resize
  17580. util.removeEventListener(window, 'resize', this._onResize);
  17581. this._onResize = null;
  17582. };
  17583. /**
  17584. * Start moving the timeline vertically
  17585. * @param {Event} event
  17586. * @private
  17587. */
  17588. Core.prototype._onTouch = function (event) {
  17589. this.touch.allowDragging = true;
  17590. };
  17591. /**
  17592. * Start moving the timeline vertically
  17593. * @param {Event} event
  17594. * @private
  17595. */
  17596. Core.prototype._onPinch = function (event) {
  17597. this.touch.allowDragging = false;
  17598. };
  17599. /**
  17600. * Start moving the timeline vertically
  17601. * @param {Event} event
  17602. * @private
  17603. */
  17604. Core.prototype._onDragStart = function (event) {
  17605. this.touch.initialScrollTop = this.props.scrollTop;
  17606. };
  17607. /**
  17608. * Move the timeline vertically
  17609. * @param {Event} event
  17610. * @private
  17611. */
  17612. Core.prototype._onDrag = function (event) {
  17613. // refuse to drag when we where pinching to prevent the timeline make a jump
  17614. // when releasing the fingers in opposite order from the touch screen
  17615. if (!this.touch.allowDragging) return;
  17616. var delta = event.gesture.deltaY;
  17617. var oldScrollTop = this._getScrollTop();
  17618. var newScrollTop = this._setScrollTop(this.touch.initialScrollTop + delta);
  17619. if (newScrollTop != oldScrollTop) {
  17620. this.redraw(); // TODO: this causes two redraws when dragging, the other is triggered by rangechange already
  17621. }
  17622. };
  17623. /**
  17624. * Apply a scrollTop
  17625. * @param {Number} scrollTop
  17626. * @returns {Number} scrollTop Returns the applied scrollTop
  17627. * @private
  17628. */
  17629. Core.prototype._setScrollTop = function (scrollTop) {
  17630. this.props.scrollTop = scrollTop;
  17631. this._updateScrollTop();
  17632. return this.props.scrollTop;
  17633. };
  17634. /**
  17635. * Update the current scrollTop when the height of the containers has been changed
  17636. * @returns {Number} scrollTop Returns the applied scrollTop
  17637. * @private
  17638. */
  17639. Core.prototype._updateScrollTop = function () {
  17640. // recalculate the scrollTopMin
  17641. var scrollTopMin = Math.min(this.props.centerContainer.height - this.props.center.height, 0); // is negative or zero
  17642. if (scrollTopMin != this.props.scrollTopMin) {
  17643. // in case of bottom orientation, change the scrollTop such that the contents
  17644. // do not move relative to the time axis at the bottom
  17645. if (this.options.orientation == 'bottom') {
  17646. this.props.scrollTop += (scrollTopMin - this.props.scrollTopMin);
  17647. }
  17648. this.props.scrollTopMin = scrollTopMin;
  17649. }
  17650. // limit the scrollTop to the feasible scroll range
  17651. if (this.props.scrollTop > 0) this.props.scrollTop = 0;
  17652. if (this.props.scrollTop < scrollTopMin) this.props.scrollTop = scrollTopMin;
  17653. return this.props.scrollTop;
  17654. };
  17655. /**
  17656. * Get the current scrollTop
  17657. * @returns {number} scrollTop
  17658. * @private
  17659. */
  17660. Core.prototype._getScrollTop = function () {
  17661. return this.props.scrollTop;
  17662. };
  17663. module.exports = Core;
  17664. /***/ },
  17665. /* 43 */
  17666. /***/ function(module, exports, __webpack_require__) {
  17667. var Hammer = __webpack_require__(41);
  17668. /**
  17669. * Fake a hammer.js gesture. Event can be a ScrollEvent or MouseMoveEvent
  17670. * @param {Element} element
  17671. * @param {Event} event
  17672. */
  17673. exports.fakeGesture = function(element, event) {
  17674. var eventType = null;
  17675. // for hammer.js 1.0.5
  17676. // var gesture = Hammer.event.collectEventData(this, eventType, event);
  17677. // for hammer.js 1.0.6+
  17678. var touches = Hammer.event.getTouchList(event, eventType);
  17679. var gesture = Hammer.event.collectEventData(this, eventType, touches, event);
  17680. // on IE in standards mode, no touches are recognized by hammer.js,
  17681. // resulting in NaN values for center.pageX and center.pageY
  17682. if (isNaN(gesture.center.pageX)) {
  17683. gesture.center.pageX = event.pageX;
  17684. }
  17685. if (isNaN(gesture.center.pageY)) {
  17686. gesture.center.pageY = event.pageY;
  17687. }
  17688. return gesture;
  17689. };
  17690. /***/ },
  17691. /* 44 */
  17692. /***/ function(module, exports, __webpack_require__) {
  17693. // English
  17694. exports['en'] = {
  17695. current: 'current',
  17696. time: 'time'
  17697. };
  17698. exports['en_EN'] = exports['en'];
  17699. exports['en_US'] = exports['en'];
  17700. // Dutch
  17701. exports['nl'] = {
  17702. custom: 'aangepaste',
  17703. time: 'tijd'
  17704. };
  17705. exports['nl_NL'] = exports['nl'];
  17706. exports['nl_BE'] = exports['nl'];
  17707. /***/ },
  17708. /* 45 */
  17709. /***/ function(module, exports, __webpack_require__) {
  17710. // English
  17711. exports['en'] = {
  17712. edit: 'Edit',
  17713. del: 'Delete selected',
  17714. back: 'Back',
  17715. addNode: 'Add Node',
  17716. addEdge: 'Add Edge',
  17717. editNode: 'Edit Node',
  17718. editEdge: 'Edit Edge',
  17719. addDescription: 'Click in an empty space to place a new node.',
  17720. edgeDescription: 'Click on a node and drag the edge to another node to connect them.',
  17721. editEdgeDescription: 'Click on the control points and drag them to a node to connect to it.',
  17722. createEdgeError: 'Cannot link edges to a cluster.',
  17723. deleteClusterError: 'Clusters cannot be deleted.'
  17724. };
  17725. exports['en_EN'] = exports['en'];
  17726. exports['en_US'] = exports['en'];
  17727. // Dutch
  17728. exports['nl'] = {
  17729. edit: 'Wijzigen',
  17730. del: 'Selectie verwijderen',
  17731. back: 'Terug',
  17732. addNode: 'Node toevoegen',
  17733. addEdge: 'Link toevoegen',
  17734. editNode: 'Node wijzigen',
  17735. editEdge: 'Link wijzigen',
  17736. addDescription: 'Klik op een leeg gebied om een nieuwe node te maken.',
  17737. edgeDescription: 'Klik op een node en sleep de link naar een andere node om ze te verbinden.',
  17738. editEdgeDescription: 'Klik op de verbindingspunten en sleep ze naar een node om daarmee te verbinden.',
  17739. createEdgeError: 'Kan geen link maken naar een cluster.',
  17740. deleteClusterError: 'Clusters kunnen niet worden verwijderd.'
  17741. };
  17742. exports['nl_NL'] = exports['nl'];
  17743. exports['nl_BE'] = exports['nl'];
  17744. /***/ },
  17745. /* 46 */
  17746. /***/ function(module, exports, __webpack_require__) {
  17747. /**
  17748. * Canvas shapes used by Network
  17749. */
  17750. if (typeof CanvasRenderingContext2D !== 'undefined') {
  17751. /**
  17752. * Draw a circle shape
  17753. */
  17754. CanvasRenderingContext2D.prototype.circle = function(x, y, r) {
  17755. this.beginPath();
  17756. this.arc(x, y, r, 0, 2*Math.PI, false);
  17757. };
  17758. /**
  17759. * Draw a square shape
  17760. * @param {Number} x horizontal center
  17761. * @param {Number} y vertical center
  17762. * @param {Number} r size, width and height of the square
  17763. */
  17764. CanvasRenderingContext2D.prototype.square = function(x, y, r) {
  17765. this.beginPath();
  17766. this.rect(x - r, y - r, r * 2, r * 2);
  17767. };
  17768. /**
  17769. * Draw a triangle shape
  17770. * @param {Number} x horizontal center
  17771. * @param {Number} y vertical center
  17772. * @param {Number} r radius, half the length of the sides of the triangle
  17773. */
  17774. CanvasRenderingContext2D.prototype.triangle = function(x, y, r) {
  17775. // http://en.wikipedia.org/wiki/Equilateral_triangle
  17776. this.beginPath();
  17777. var s = r * 2;
  17778. var s2 = s / 2;
  17779. var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
  17780. var h = Math.sqrt(s * s - s2 * s2); // height
  17781. this.moveTo(x, y - (h - ir));
  17782. this.lineTo(x + s2, y + ir);
  17783. this.lineTo(x - s2, y + ir);
  17784. this.lineTo(x, y - (h - ir));
  17785. this.closePath();
  17786. };
  17787. /**
  17788. * Draw a triangle shape in downward orientation
  17789. * @param {Number} x horizontal center
  17790. * @param {Number} y vertical center
  17791. * @param {Number} r radius
  17792. */
  17793. CanvasRenderingContext2D.prototype.triangleDown = function(x, y, r) {
  17794. // http://en.wikipedia.org/wiki/Equilateral_triangle
  17795. this.beginPath();
  17796. var s = r * 2;
  17797. var s2 = s / 2;
  17798. var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
  17799. var h = Math.sqrt(s * s - s2 * s2); // height
  17800. this.moveTo(x, y + (h - ir));
  17801. this.lineTo(x + s2, y - ir);
  17802. this.lineTo(x - s2, y - ir);
  17803. this.lineTo(x, y + (h - ir));
  17804. this.closePath();
  17805. };
  17806. /**
  17807. * Draw a star shape, a star with 5 points
  17808. * @param {Number} x horizontal center
  17809. * @param {Number} y vertical center
  17810. * @param {Number} r radius, half the length of the sides of the triangle
  17811. */
  17812. CanvasRenderingContext2D.prototype.star = function(x, y, r) {
  17813. // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/
  17814. this.beginPath();
  17815. for (var n = 0; n < 10; n++) {
  17816. var radius = (n % 2 === 0) ? r * 1.3 : r * 0.5;
  17817. this.lineTo(
  17818. x + radius * Math.sin(n * 2 * Math.PI / 10),
  17819. y - radius * Math.cos(n * 2 * Math.PI / 10)
  17820. );
  17821. }
  17822. this.closePath();
  17823. };
  17824. /**
  17825. * http://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas
  17826. */
  17827. CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
  17828. var r2d = Math.PI/180;
  17829. if( w - ( 2 * r ) < 0 ) { r = ( w / 2 ); } //ensure that the radius isn't too large for x
  17830. if( h - ( 2 * r ) < 0 ) { r = ( h / 2 ); } //ensure that the radius isn't too large for y
  17831. this.beginPath();
  17832. this.moveTo(x+r,y);
  17833. this.lineTo(x+w-r,y);
  17834. this.arc(x+w-r,y+r,r,r2d*270,r2d*360,false);
  17835. this.lineTo(x+w,y+h-r);
  17836. this.arc(x+w-r,y+h-r,r,0,r2d*90,false);
  17837. this.lineTo(x+r,y+h);
  17838. this.arc(x+r,y+h-r,r,r2d*90,r2d*180,false);
  17839. this.lineTo(x,y+r);
  17840. this.arc(x+r,y+r,r,r2d*180,r2d*270,false);
  17841. };
  17842. /**
  17843. * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  17844. */
  17845. CanvasRenderingContext2D.prototype.ellipse = function(x, y, w, h) {
  17846. var kappa = .5522848,
  17847. ox = (w / 2) * kappa, // control point offset horizontal
  17848. oy = (h / 2) * kappa, // control point offset vertical
  17849. xe = x + w, // x-end
  17850. ye = y + h, // y-end
  17851. xm = x + w / 2, // x-middle
  17852. ym = y + h / 2; // y-middle
  17853. this.beginPath();
  17854. this.moveTo(x, ym);
  17855. this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  17856. this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  17857. this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  17858. this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  17859. };
  17860. /**
  17861. * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  17862. */
  17863. CanvasRenderingContext2D.prototype.database = function(x, y, w, h) {
  17864. var f = 1/3;
  17865. var wEllipse = w;
  17866. var hEllipse = h * f;
  17867. var kappa = .5522848,
  17868. ox = (wEllipse / 2) * kappa, // control point offset horizontal
  17869. oy = (hEllipse / 2) * kappa, // control point offset vertical
  17870. xe = x + wEllipse, // x-end
  17871. ye = y + hEllipse, // y-end
  17872. xm = x + wEllipse / 2, // x-middle
  17873. ym = y + hEllipse / 2, // y-middle
  17874. ymb = y + (h - hEllipse/2), // y-midlle, bottom ellipse
  17875. yeb = y + h; // y-end, bottom ellipse
  17876. this.beginPath();
  17877. this.moveTo(xe, ym);
  17878. this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  17879. this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  17880. this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  17881. this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  17882. this.lineTo(xe, ymb);
  17883. this.bezierCurveTo(xe, ymb + oy, xm + ox, yeb, xm, yeb);
  17884. this.bezierCurveTo(xm - ox, yeb, x, ymb + oy, x, ymb);
  17885. this.lineTo(x, ym);
  17886. };
  17887. /**
  17888. * Draw an arrow point (no line)
  17889. */
  17890. CanvasRenderingContext2D.prototype.arrow = function(x, y, angle, length) {
  17891. // tail
  17892. var xt = x - length * Math.cos(angle);
  17893. var yt = y - length * Math.sin(angle);
  17894. // inner tail
  17895. // TODO: allow to customize different shapes
  17896. var xi = x - length * 0.9 * Math.cos(angle);
  17897. var yi = y - length * 0.9 * Math.sin(angle);
  17898. // left
  17899. var xl = xt + length / 3 * Math.cos(angle + 0.5 * Math.PI);
  17900. var yl = yt + length / 3 * Math.sin(angle + 0.5 * Math.PI);
  17901. // right
  17902. var xr = xt + length / 3 * Math.cos(angle - 0.5 * Math.PI);
  17903. var yr = yt + length / 3 * Math.sin(angle - 0.5 * Math.PI);
  17904. this.beginPath();
  17905. this.moveTo(x, y);
  17906. this.lineTo(xl, yl);
  17907. this.lineTo(xi, yi);
  17908. this.lineTo(xr, yr);
  17909. this.closePath();
  17910. };
  17911. /**
  17912. * Sets up the dashedLine functionality for drawing
  17913. * Original code came from http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
  17914. * @author David Jordan
  17915. * @date 2012-08-08
  17916. */
  17917. CanvasRenderingContext2D.prototype.dashedLine = function(x,y,x2,y2,dashArray){
  17918. if (!dashArray) dashArray=[10,5];
  17919. if (dashLength==0) dashLength = 0.001; // Hack for Safari
  17920. var dashCount = dashArray.length;
  17921. this.moveTo(x, y);
  17922. var dx = (x2-x), dy = (y2-y);
  17923. var slope = dy/dx;
  17924. var distRemaining = Math.sqrt( dx*dx + dy*dy );
  17925. var dashIndex=0, draw=true;
  17926. while (distRemaining>=0.1){
  17927. var dashLength = dashArray[dashIndex++%dashCount];
  17928. if (dashLength > distRemaining) dashLength = distRemaining;
  17929. var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) );
  17930. if (dx<0) xStep = -xStep;
  17931. x += xStep;
  17932. y += slope*xStep;
  17933. this[draw ? 'lineTo' : 'moveTo'](x,y);
  17934. distRemaining -= dashLength;
  17935. draw = !draw;
  17936. }
  17937. };
  17938. // TODO: add diamond shape
  17939. }
  17940. /***/ },
  17941. /* 47 */
  17942. /***/ function(module, exports, __webpack_require__) {
  17943. var PhysicsMixin = __webpack_require__(59);
  17944. var ClusterMixin = __webpack_require__(53);
  17945. var SectorsMixin = __webpack_require__(54);
  17946. var SelectionMixin = __webpack_require__(55);
  17947. var ManipulationMixin = __webpack_require__(56);
  17948. var NavigationMixin = __webpack_require__(57);
  17949. var HierarchicalLayoutMixin = __webpack_require__(58);
  17950. /**
  17951. * Load a mixin into the network object
  17952. *
  17953. * @param {Object} sourceVariable | this object has to contain functions.
  17954. * @private
  17955. */
  17956. exports._loadMixin = function (sourceVariable) {
  17957. for (var mixinFunction in sourceVariable) {
  17958. if (sourceVariable.hasOwnProperty(mixinFunction)) {
  17959. this[mixinFunction] = sourceVariable[mixinFunction];
  17960. }
  17961. }
  17962. };
  17963. /**
  17964. * removes a mixin from the network object.
  17965. *
  17966. * @param {Object} sourceVariable | this object has to contain functions.
  17967. * @private
  17968. */
  17969. exports._clearMixin = function (sourceVariable) {
  17970. for (var mixinFunction in sourceVariable) {
  17971. if (sourceVariable.hasOwnProperty(mixinFunction)) {
  17972. this[mixinFunction] = undefined;
  17973. }
  17974. }
  17975. };
  17976. /**
  17977. * Mixin the physics system and initialize the parameters required.
  17978. *
  17979. * @private
  17980. */
  17981. exports._loadPhysicsSystem = function () {
  17982. this._loadMixin(PhysicsMixin);
  17983. this._loadSelectedForceSolver();
  17984. if (this.constants.configurePhysics == true) {
  17985. this._loadPhysicsConfiguration();
  17986. }
  17987. };
  17988. /**
  17989. * Mixin the cluster system and initialize the parameters required.
  17990. *
  17991. * @private
  17992. */
  17993. exports._loadClusterSystem = function () {
  17994. this.clusterSession = 0;
  17995. this.hubThreshold = 5;
  17996. this._loadMixin(ClusterMixin);
  17997. };
  17998. /**
  17999. * Mixin the sector system and initialize the parameters required
  18000. *
  18001. * @private
  18002. */
  18003. exports._loadSectorSystem = function () {
  18004. this.sectors = {};
  18005. this.activeSector = ["default"];
  18006. this.sectors["active"] = {};
  18007. this.sectors["active"]["default"] = {"nodes": {},
  18008. "edges": {},
  18009. "nodeIndices": [],
  18010. "formationScale": 1.0,
  18011. "drawingNode": undefined };
  18012. this.sectors["frozen"] = {};
  18013. this.sectors["support"] = {"nodes": {},
  18014. "edges": {},
  18015. "nodeIndices": [],
  18016. "formationScale": 1.0,
  18017. "drawingNode": undefined };
  18018. this.nodeIndices = this.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields
  18019. this._loadMixin(SectorsMixin);
  18020. };
  18021. /**
  18022. * Mixin the selection system and initialize the parameters required
  18023. *
  18024. * @private
  18025. */
  18026. exports._loadSelectionSystem = function () {
  18027. this.selectionObj = {nodes: {}, edges: {}};
  18028. this._loadMixin(SelectionMixin);
  18029. };
  18030. /**
  18031. * Mixin the navigationUI (User Interface) system and initialize the parameters required
  18032. *
  18033. * @private
  18034. */
  18035. exports._loadManipulationSystem = function () {
  18036. // reset global variables -- these are used by the selection of nodes and edges.
  18037. this.blockConnectingEdgeSelection = false;
  18038. this.forceAppendSelection = false;
  18039. if (this.constants.dataManipulation.enabled == true) {
  18040. // load the manipulator HTML elements. All styling done in css.
  18041. if (this.manipulationDiv === undefined) {
  18042. this.manipulationDiv = document.createElement('div');
  18043. this.manipulationDiv.className = 'network-manipulationDiv';
  18044. this.manipulationDiv.id = 'network-manipulationDiv';
  18045. if (this.editMode == true) {
  18046. this.manipulationDiv.style.display = "block";
  18047. }
  18048. else {
  18049. this.manipulationDiv.style.display = "none";
  18050. }
  18051. this.frame.appendChild(this.manipulationDiv);
  18052. }
  18053. if (this.editModeDiv === undefined) {
  18054. this.editModeDiv = document.createElement('div');
  18055. this.editModeDiv.className = 'network-manipulation-editMode';
  18056. this.editModeDiv.id = 'network-manipulation-editMode';
  18057. if (this.editMode == true) {
  18058. this.editModeDiv.style.display = "none";
  18059. }
  18060. else {
  18061. this.editModeDiv.style.display = "block";
  18062. }
  18063. this.frame.appendChild(this.editModeDiv);
  18064. }
  18065. if (this.closeDiv === undefined) {
  18066. this.closeDiv = document.createElement('div');
  18067. this.closeDiv.className = 'network-manipulation-closeDiv';
  18068. this.closeDiv.id = 'network-manipulation-closeDiv';
  18069. this.closeDiv.style.display = this.manipulationDiv.style.display;
  18070. this.frame.appendChild(this.closeDiv);
  18071. }
  18072. // load the manipulation functions
  18073. this._loadMixin(ManipulationMixin);
  18074. // create the manipulator toolbar
  18075. this._createManipulatorBar();
  18076. }
  18077. else {
  18078. if (this.manipulationDiv !== undefined) {
  18079. // removes all the bindings and overloads
  18080. this._createManipulatorBar();
  18081. // remove the manipulation divs
  18082. this.containerElement.removeChild(this.manipulationDiv);
  18083. this.containerElement.removeChild(this.editModeDiv);
  18084. this.containerElement.removeChild(this.closeDiv);
  18085. this.manipulationDiv = undefined;
  18086. this.editModeDiv = undefined;
  18087. this.closeDiv = undefined;
  18088. // remove the mixin functions
  18089. this._clearMixin(ManipulationMixin);
  18090. }
  18091. }
  18092. };
  18093. /**
  18094. * Mixin the navigation (User Interface) system and initialize the parameters required
  18095. *
  18096. * @private
  18097. */
  18098. exports._loadNavigationControls = function () {
  18099. this._loadMixin(NavigationMixin);
  18100. // the clean function removes the button divs, this is done to remove the bindings.
  18101. this._cleanNavigation();
  18102. if (this.constants.navigation.enabled == true) {
  18103. this._loadNavigationElements();
  18104. }
  18105. };
  18106. /**
  18107. * Mixin the hierarchical layout system.
  18108. *
  18109. * @private
  18110. */
  18111. exports._loadHierarchySystem = function () {
  18112. this._loadMixin(HierarchicalLayoutMixin);
  18113. };
  18114. /***/ },
  18115. /* 48 */
  18116. /***/ function(module, exports, __webpack_require__) {
  18117. var mousetrap = __webpack_require__(50);
  18118. var Emitter = __webpack_require__(49);
  18119. var Hammer = __webpack_require__(41);
  18120. var util = __webpack_require__(1);
  18121. /**
  18122. * Turn an element into an clickToUse element.
  18123. * When not active, the element has a transparent overlay. When the overlay is
  18124. * clicked, the mode is changed to active.
  18125. * When active, the element is displayed with a blue border around it, and
  18126. * the interactive contents of the element can be used. When clicked outside
  18127. * the element, the elements mode is changed to inactive.
  18128. * @param {Element} container
  18129. * @constructor
  18130. */
  18131. function Activator(container) {
  18132. this.active = false;
  18133. this.dom = {
  18134. container: container
  18135. };
  18136. this.dom.overlay = document.createElement('div');
  18137. this.dom.overlay.className = 'overlay';
  18138. this.dom.container.appendChild(this.dom.overlay);
  18139. this.hammer = Hammer(this.dom.overlay, {prevent_default: false});
  18140. this.hammer.on('tap', this._onTapOverlay.bind(this));
  18141. // block all touch events (except tap)
  18142. var me = this;
  18143. var events = [
  18144. 'touch', 'pinch',
  18145. 'doubletap', 'hold',
  18146. 'dragstart', 'drag', 'dragend',
  18147. 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
  18148. ];
  18149. events.forEach(function (event) {
  18150. me.hammer.on(event, function (event) {
  18151. event.stopPropagation();
  18152. });
  18153. });
  18154. // attach a tap event to the window, in order to deactivate when clicking outside the timeline
  18155. this.windowHammer = Hammer(window, {prevent_default: false});
  18156. this.windowHammer.on('tap', function (event) {
  18157. // deactivate when clicked outside the container
  18158. if (!_hasParent(event.target, container)) {
  18159. me.deactivate();
  18160. }
  18161. });
  18162. // mousetrap listener only bounded when active)
  18163. this.escListener = this.deactivate.bind(this);
  18164. }
  18165. // turn into an event emitter
  18166. Emitter(Activator.prototype);
  18167. // The currently active activator
  18168. Activator.current = null;
  18169. /**
  18170. * Destroy the activator. Cleans up all created DOM and event listeners
  18171. */
  18172. Activator.prototype.destroy = function () {
  18173. this.deactivate();
  18174. // remove dom
  18175. this.dom.overlay.parentNode.removeChild(this.dom.overlay);
  18176. // cleanup hammer instances
  18177. this.hammer = null;
  18178. this.windowHammer = null;
  18179. // FIXME: cleaning up hammer instances doesn't work (Timeline not removed from memory)
  18180. };
  18181. /**
  18182. * Activate the element
  18183. * Overlay is hidden, element is decorated with a blue shadow border
  18184. */
  18185. Activator.prototype.activate = function () {
  18186. // we allow only one active activator at a time
  18187. if (Activator.current) {
  18188. Activator.current.deactivate();
  18189. }
  18190. Activator.current = this;
  18191. this.active = true;
  18192. this.dom.overlay.style.display = 'none';
  18193. util.addClassName(this.dom.container, 'vis-active');
  18194. this.emit('change');
  18195. this.emit('activate');
  18196. // ugly hack: bind ESC after emitting the events, as the Network rebinds all
  18197. // keyboard events on a 'change' event
  18198. mousetrap.bind('esc', this.escListener);
  18199. };
  18200. /**
  18201. * Deactivate the element
  18202. * Overlay is displayed on top of the element
  18203. */
  18204. Activator.prototype.deactivate = function () {
  18205. this.active = false;
  18206. this.dom.overlay.style.display = '';
  18207. util.removeClassName(this.dom.container, 'vis-active');
  18208. mousetrap.unbind('esc', this.escListener);
  18209. this.emit('change');
  18210. this.emit('deactivate');
  18211. };
  18212. /**
  18213. * Handle a tap event: activate the container
  18214. * @param event
  18215. * @private
  18216. */
  18217. Activator.prototype._onTapOverlay = function (event) {
  18218. // activate the container
  18219. this.activate();
  18220. event.stopPropagation();
  18221. };
  18222. /**
  18223. * Test whether the element has the requested parent element somewhere in
  18224. * its chain of parent nodes.
  18225. * @param {HTMLElement} element
  18226. * @param {HTMLElement} parent
  18227. * @returns {boolean} Returns true when the parent is found somewhere in the
  18228. * chain of parent nodes.
  18229. * @private
  18230. */
  18231. function _hasParent(element, parent) {
  18232. while (element) {
  18233. if (element === parent) {
  18234. return true
  18235. }
  18236. element = element.parentNode;
  18237. }
  18238. return false;
  18239. }
  18240. module.exports = Activator;
  18241. /***/ },
  18242. /* 49 */
  18243. /***/ function(module, exports, __webpack_require__) {
  18244. /**
  18245. * Expose `Emitter`.
  18246. */
  18247. module.exports = Emitter;
  18248. /**
  18249. * Initialize a new `Emitter`.
  18250. *
  18251. * @api public
  18252. */
  18253. function Emitter(obj) {
  18254. if (obj) return mixin(obj);
  18255. };
  18256. /**
  18257. * Mixin the emitter properties.
  18258. *
  18259. * @param {Object} obj
  18260. * @return {Object}
  18261. * @api private
  18262. */
  18263. function mixin(obj) {
  18264. for (var key in Emitter.prototype) {
  18265. obj[key] = Emitter.prototype[key];
  18266. }
  18267. return obj;
  18268. }
  18269. /**
  18270. * Listen on the given `event` with `fn`.
  18271. *
  18272. * @param {String} event
  18273. * @param {Function} fn
  18274. * @return {Emitter}
  18275. * @api public
  18276. */
  18277. Emitter.prototype.on =
  18278. Emitter.prototype.addEventListener = function(event, fn){
  18279. this._callbacks = this._callbacks || {};
  18280. (this._callbacks[event] = this._callbacks[event] || [])
  18281. .push(fn);
  18282. return this;
  18283. };
  18284. /**
  18285. * Adds an `event` listener that will be invoked a single
  18286. * time then automatically removed.
  18287. *
  18288. * @param {String} event
  18289. * @param {Function} fn
  18290. * @return {Emitter}
  18291. * @api public
  18292. */
  18293. Emitter.prototype.once = function(event, fn){
  18294. var self = this;
  18295. this._callbacks = this._callbacks || {};
  18296. function on() {
  18297. self.off(event, on);
  18298. fn.apply(this, arguments);
  18299. }
  18300. on.fn = fn;
  18301. this.on(event, on);
  18302. return this;
  18303. };
  18304. /**
  18305. * Remove the given callback for `event` or all
  18306. * registered callbacks.
  18307. *
  18308. * @param {String} event
  18309. * @param {Function} fn
  18310. * @return {Emitter}
  18311. * @api public
  18312. */
  18313. Emitter.prototype.off =
  18314. Emitter.prototype.removeListener =
  18315. Emitter.prototype.removeAllListeners =
  18316. Emitter.prototype.removeEventListener = function(event, fn){
  18317. this._callbacks = this._callbacks || {};
  18318. // all
  18319. if (0 == arguments.length) {
  18320. this._callbacks = {};
  18321. return this;
  18322. }
  18323. // specific event
  18324. var callbacks = this._callbacks[event];
  18325. if (!callbacks) return this;
  18326. // remove all handlers
  18327. if (1 == arguments.length) {
  18328. delete this._callbacks[event];
  18329. return this;
  18330. }
  18331. // remove specific handler
  18332. var cb;
  18333. for (var i = 0; i < callbacks.length; i++) {
  18334. cb = callbacks[i];
  18335. if (cb === fn || cb.fn === fn) {
  18336. callbacks.splice(i, 1);
  18337. break;
  18338. }
  18339. }
  18340. return this;
  18341. };
  18342. /**
  18343. * Emit `event` with the given args.
  18344. *
  18345. * @param {String} event
  18346. * @param {Mixed} ...
  18347. * @return {Emitter}
  18348. */
  18349. Emitter.prototype.emit = function(event){
  18350. this._callbacks = this._callbacks || {};
  18351. var args = [].slice.call(arguments, 1)
  18352. , callbacks = this._callbacks[event];
  18353. if (callbacks) {
  18354. callbacks = callbacks.slice(0);
  18355. for (var i = 0, len = callbacks.length; i < len; ++i) {
  18356. callbacks[i].apply(this, args);
  18357. }
  18358. }
  18359. return this;
  18360. };
  18361. /**
  18362. * Return array of callbacks for `event`.
  18363. *
  18364. * @param {String} event
  18365. * @return {Array}
  18366. * @api public
  18367. */
  18368. Emitter.prototype.listeners = function(event){
  18369. this._callbacks = this._callbacks || {};
  18370. return this._callbacks[event] || [];
  18371. };
  18372. /**
  18373. * Check if this emitter has `event` handlers.
  18374. *
  18375. * @param {String} event
  18376. * @return {Boolean}
  18377. * @api public
  18378. */
  18379. Emitter.prototype.hasListeners = function(event){
  18380. return !! this.listeners(event).length;
  18381. };
  18382. /***/ },
  18383. /* 50 */
  18384. /***/ function(module, exports, __webpack_require__) {
  18385. /**
  18386. * Copyright 2012 Craig Campbell
  18387. *
  18388. * Licensed under the Apache License, Version 2.0 (the "License");
  18389. * you may not use this file except in compliance with the License.
  18390. * You may obtain a copy of the License at
  18391. *
  18392. * http://www.apache.org/licenses/LICENSE-2.0
  18393. *
  18394. * Unless required by applicable law or agreed to in writing, software
  18395. * distributed under the License is distributed on an "AS IS" BASIS,
  18396. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18397. * See the License for the specific language governing permissions and
  18398. * limitations under the License.
  18399. *
  18400. * Mousetrap is a simple keyboard shortcut library for Javascript with
  18401. * no external dependencies
  18402. *
  18403. * @version 1.1.2
  18404. * @url craig.is/killing/mice
  18405. */
  18406. /**
  18407. * mapping of special keycodes to their corresponding keys
  18408. *
  18409. * everything in this dictionary cannot use keypress events
  18410. * so it has to be here to map to the correct keycodes for
  18411. * keyup/keydown events
  18412. *
  18413. * @type {Object}
  18414. */
  18415. var _MAP = {
  18416. 8: 'backspace',
  18417. 9: 'tab',
  18418. 13: 'enter',
  18419. 16: 'shift',
  18420. 17: 'ctrl',
  18421. 18: 'alt',
  18422. 20: 'capslock',
  18423. 27: 'esc',
  18424. 32: 'space',
  18425. 33: 'pageup',
  18426. 34: 'pagedown',
  18427. 35: 'end',
  18428. 36: 'home',
  18429. 37: 'left',
  18430. 38: 'up',
  18431. 39: 'right',
  18432. 40: 'down',
  18433. 45: 'ins',
  18434. 46: 'del',
  18435. 91: 'meta',
  18436. 93: 'meta',
  18437. 224: 'meta'
  18438. },
  18439. /**
  18440. * mapping for special characters so they can support
  18441. *
  18442. * this dictionary is only used incase you want to bind a
  18443. * keyup or keydown event to one of these keys
  18444. *
  18445. * @type {Object}
  18446. */
  18447. _KEYCODE_MAP = {
  18448. 106: '*',
  18449. 107: '+',
  18450. 109: '-',
  18451. 110: '.',
  18452. 111 : '/',
  18453. 186: ';',
  18454. 187: '=',
  18455. 188: ',',
  18456. 189: '-',
  18457. 190: '.',
  18458. 191: '/',
  18459. 192: '`',
  18460. 219: '[',
  18461. 220: '\\',
  18462. 221: ']',
  18463. 222: '\''
  18464. },
  18465. /**
  18466. * this is a mapping of keys that require shift on a US keypad
  18467. * back to the non shift equivelents
  18468. *
  18469. * this is so you can use keyup events with these keys
  18470. *
  18471. * note that this will only work reliably on US keyboards
  18472. *
  18473. * @type {Object}
  18474. */
  18475. _SHIFT_MAP = {
  18476. '~': '`',
  18477. '!': '1',
  18478. '@': '2',
  18479. '#': '3',
  18480. '$': '4',
  18481. '%': '5',
  18482. '^': '6',
  18483. '&': '7',
  18484. '*': '8',
  18485. '(': '9',
  18486. ')': '0',
  18487. '_': '-',
  18488. '+': '=',
  18489. ':': ';',
  18490. '\"': '\'',
  18491. '<': ',',
  18492. '>': '.',
  18493. '?': '/',
  18494. '|': '\\'
  18495. },
  18496. /**
  18497. * this is a list of special strings you can use to map
  18498. * to modifier keys when you specify your keyboard shortcuts
  18499. *
  18500. * @type {Object}
  18501. */
  18502. _SPECIAL_ALIASES = {
  18503. 'option': 'alt',
  18504. 'command': 'meta',
  18505. 'return': 'enter',
  18506. 'escape': 'esc'
  18507. },
  18508. /**
  18509. * variable to store the flipped version of _MAP from above
  18510. * needed to check if we should use keypress or not when no action
  18511. * is specified
  18512. *
  18513. * @type {Object|undefined}
  18514. */
  18515. _REVERSE_MAP,
  18516. /**
  18517. * a list of all the callbacks setup via Mousetrap.bind()
  18518. *
  18519. * @type {Object}
  18520. */
  18521. _callbacks = {},
  18522. /**
  18523. * direct map of string combinations to callbacks used for trigger()
  18524. *
  18525. * @type {Object}
  18526. */
  18527. _direct_map = {},
  18528. /**
  18529. * keeps track of what level each sequence is at since multiple
  18530. * sequences can start out with the same sequence
  18531. *
  18532. * @type {Object}
  18533. */
  18534. _sequence_levels = {},
  18535. /**
  18536. * variable to store the setTimeout call
  18537. *
  18538. * @type {null|number}
  18539. */
  18540. _reset_timer,
  18541. /**
  18542. * temporary state where we will ignore the next keyup
  18543. *
  18544. * @type {boolean|string}
  18545. */
  18546. _ignore_next_keyup = false,
  18547. /**
  18548. * are we currently inside of a sequence?
  18549. * type of action ("keyup" or "keydown" or "keypress") or false
  18550. *
  18551. * @type {boolean|string}
  18552. */
  18553. _inside_sequence = false;
  18554. /**
  18555. * loop through the f keys, f1 to f19 and add them to the map
  18556. * programatically
  18557. */
  18558. for (var i = 1; i < 20; ++i) {
  18559. _MAP[111 + i] = 'f' + i;
  18560. }
  18561. /**
  18562. * loop through to map numbers on the numeric keypad
  18563. */
  18564. for (i = 0; i <= 9; ++i) {
  18565. _MAP[i + 96] = i;
  18566. }
  18567. /**
  18568. * cross browser add event method
  18569. *
  18570. * @param {Element|HTMLDocument} object
  18571. * @param {string} type
  18572. * @param {Function} callback
  18573. * @returns void
  18574. */
  18575. function _addEvent(object, type, callback) {
  18576. if (object.addEventListener) {
  18577. return object.addEventListener(type, callback, false);
  18578. }
  18579. object.attachEvent('on' + type, callback);
  18580. }
  18581. /**
  18582. * takes the event and returns the key character
  18583. *
  18584. * @param {Event} e
  18585. * @return {string}
  18586. */
  18587. function _characterFromEvent(e) {
  18588. // for keypress events we should return the character as is
  18589. if (e.type == 'keypress') {
  18590. return String.fromCharCode(e.which);
  18591. }
  18592. // for non keypress events the special maps are needed
  18593. if (_MAP[e.which]) {
  18594. return _MAP[e.which];
  18595. }
  18596. if (_KEYCODE_MAP[e.which]) {
  18597. return _KEYCODE_MAP[e.which];
  18598. }
  18599. // if it is not in the special map
  18600. return String.fromCharCode(e.which).toLowerCase();
  18601. }
  18602. /**
  18603. * should we stop this event before firing off callbacks
  18604. *
  18605. * @param {Event} e
  18606. * @return {boolean}
  18607. */
  18608. function _stop(e) {
  18609. var element = e.target || e.srcElement,
  18610. tag_name = element.tagName;
  18611. // if the element has the class "mousetrap" then no need to stop
  18612. if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
  18613. return false;
  18614. }
  18615. // stop for input, select, and textarea
  18616. return tag_name == 'INPUT' || tag_name == 'SELECT' || tag_name == 'TEXTAREA' || (element.contentEditable && element.contentEditable == 'true');
  18617. }
  18618. /**
  18619. * checks if two arrays are equal
  18620. *
  18621. * @param {Array} modifiers1
  18622. * @param {Array} modifiers2
  18623. * @returns {boolean}
  18624. */
  18625. function _modifiersMatch(modifiers1, modifiers2) {
  18626. return modifiers1.sort().join(',') === modifiers2.sort().join(',');
  18627. }
  18628. /**
  18629. * resets all sequence counters except for the ones passed in
  18630. *
  18631. * @param {Object} do_not_reset
  18632. * @returns void
  18633. */
  18634. function _resetSequences(do_not_reset) {
  18635. do_not_reset = do_not_reset || {};
  18636. var active_sequences = false,
  18637. key;
  18638. for (key in _sequence_levels) {
  18639. if (do_not_reset[key]) {
  18640. active_sequences = true;
  18641. continue;
  18642. }
  18643. _sequence_levels[key] = 0;
  18644. }
  18645. if (!active_sequences) {
  18646. _inside_sequence = false;
  18647. }
  18648. }
  18649. /**
  18650. * finds all callbacks that match based on the keycode, modifiers,
  18651. * and action
  18652. *
  18653. * @param {string} character
  18654. * @param {Array} modifiers
  18655. * @param {string} action
  18656. * @param {boolean=} remove - should we remove any matches
  18657. * @param {string=} combination
  18658. * @returns {Array}
  18659. */
  18660. function _getMatches(character, modifiers, action, remove, combination) {
  18661. var i,
  18662. callback,
  18663. matches = [];
  18664. // if there are no events related to this keycode
  18665. if (!_callbacks[character]) {
  18666. return [];
  18667. }
  18668. // if a modifier key is coming up on its own we should allow it
  18669. if (action == 'keyup' && _isModifier(character)) {
  18670. modifiers = [character];
  18671. }
  18672. // loop through all callbacks for the key that was pressed
  18673. // and see if any of them match
  18674. for (i = 0; i < _callbacks[character].length; ++i) {
  18675. callback = _callbacks[character][i];
  18676. // if this is a sequence but it is not at the right level
  18677. // then move onto the next match
  18678. if (callback.seq && _sequence_levels[callback.seq] != callback.level) {
  18679. continue;
  18680. }
  18681. // if the action we are looking for doesn't match the action we got
  18682. // then we should keep going
  18683. if (action != callback.action) {
  18684. continue;
  18685. }
  18686. // if this is a keypress event that means that we need to only
  18687. // look at the character, otherwise check the modifiers as
  18688. // well
  18689. if (action == 'keypress' || _modifiersMatch(modifiers, callback.modifiers)) {
  18690. // remove is used so if you change your mind and call bind a
  18691. // second time with a new function the first one is overwritten
  18692. if (remove && callback.combo == combination) {
  18693. _callbacks[character].splice(i, 1);
  18694. }
  18695. matches.push(callback);
  18696. }
  18697. }
  18698. return matches;
  18699. }
  18700. /**
  18701. * takes a key event and figures out what the modifiers are
  18702. *
  18703. * @param {Event} e
  18704. * @returns {Array}
  18705. */
  18706. function _eventModifiers(e) {
  18707. var modifiers = [];
  18708. if (e.shiftKey) {
  18709. modifiers.push('shift');
  18710. }
  18711. if (e.altKey) {
  18712. modifiers.push('alt');
  18713. }
  18714. if (e.ctrlKey) {
  18715. modifiers.push('ctrl');
  18716. }
  18717. if (e.metaKey) {
  18718. modifiers.push('meta');
  18719. }
  18720. return modifiers;
  18721. }
  18722. /**
  18723. * actually calls the callback function
  18724. *
  18725. * if your callback function returns false this will use the jquery
  18726. * convention - prevent default and stop propogation on the event
  18727. *
  18728. * @param {Function} callback
  18729. * @param {Event} e
  18730. * @returns void
  18731. */
  18732. function _fireCallback(callback, e) {
  18733. if (callback(e) === false) {
  18734. if (e.preventDefault) {
  18735. e.preventDefault();
  18736. }
  18737. if (e.stopPropagation) {
  18738. e.stopPropagation();
  18739. }
  18740. e.returnValue = false;
  18741. e.cancelBubble = true;
  18742. }
  18743. }
  18744. /**
  18745. * handles a character key event
  18746. *
  18747. * @param {string} character
  18748. * @param {Event} e
  18749. * @returns void
  18750. */
  18751. function _handleCharacter(character, e) {
  18752. // if this event should not happen stop here
  18753. if (_stop(e)) {
  18754. return;
  18755. }
  18756. var callbacks = _getMatches(character, _eventModifiers(e), e.type),
  18757. i,
  18758. do_not_reset = {},
  18759. processed_sequence_callback = false;
  18760. // loop through matching callbacks for this key event
  18761. for (i = 0; i < callbacks.length; ++i) {
  18762. // fire for all sequence callbacks
  18763. // this is because if for example you have multiple sequences
  18764. // bound such as "g i" and "g t" they both need to fire the
  18765. // callback for matching g cause otherwise you can only ever
  18766. // match the first one
  18767. if (callbacks[i].seq) {
  18768. processed_sequence_callback = true;
  18769. // keep a list of which sequences were matches for later
  18770. do_not_reset[callbacks[i].seq] = 1;
  18771. _fireCallback(callbacks[i].callback, e);
  18772. continue;
  18773. }
  18774. // if there were no sequence matches but we are still here
  18775. // that means this is a regular match so we should fire that
  18776. if (!processed_sequence_callback && !_inside_sequence) {
  18777. _fireCallback(callbacks[i].callback, e);
  18778. }
  18779. }
  18780. // if you are inside of a sequence and the key you are pressing
  18781. // is not a modifier key then we should reset all sequences
  18782. // that were not matched by this key event
  18783. if (e.type == _inside_sequence && !_isModifier(character)) {
  18784. _resetSequences(do_not_reset);
  18785. }
  18786. }
  18787. /**
  18788. * handles a keydown event
  18789. *
  18790. * @param {Event} e
  18791. * @returns void
  18792. */
  18793. function _handleKey(e) {
  18794. // normalize e.which for key events
  18795. // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
  18796. e.which = typeof e.which == "number" ? e.which : e.keyCode;
  18797. var character = _characterFromEvent(e);
  18798. // no character found then stop
  18799. if (!character) {
  18800. return;
  18801. }
  18802. if (e.type == 'keyup' && _ignore_next_keyup == character) {
  18803. _ignore_next_keyup = false;
  18804. return;
  18805. }
  18806. _handleCharacter(character, e);
  18807. }
  18808. /**
  18809. * determines if the keycode specified is a modifier key or not
  18810. *
  18811. * @param {string} key
  18812. * @returns {boolean}
  18813. */
  18814. function _isModifier(key) {
  18815. return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta';
  18816. }
  18817. /**
  18818. * called to set a 1 second timeout on the specified sequence
  18819. *
  18820. * this is so after each key press in the sequence you have 1 second
  18821. * to press the next key before you have to start over
  18822. *
  18823. * @returns void
  18824. */
  18825. function _resetSequenceTimer() {
  18826. clearTimeout(_reset_timer);
  18827. _reset_timer = setTimeout(_resetSequences, 1000);
  18828. }
  18829. /**
  18830. * reverses the map lookup so that we can look for specific keys
  18831. * to see what can and can't use keypress
  18832. *
  18833. * @return {Object}
  18834. */
  18835. function _getReverseMap() {
  18836. if (!_REVERSE_MAP) {
  18837. _REVERSE_MAP = {};
  18838. for (var key in _MAP) {
  18839. // pull out the numeric keypad from here cause keypress should
  18840. // be able to detect the keys from the character
  18841. if (key > 95 && key < 112) {
  18842. continue;
  18843. }
  18844. if (_MAP.hasOwnProperty(key)) {
  18845. _REVERSE_MAP[_MAP[key]] = key;
  18846. }
  18847. }
  18848. }
  18849. return _REVERSE_MAP;
  18850. }
  18851. /**
  18852. * picks the best action based on the key combination
  18853. *
  18854. * @param {string} key - character for key
  18855. * @param {Array} modifiers
  18856. * @param {string=} action passed in
  18857. */
  18858. function _pickBestAction(key, modifiers, action) {
  18859. // if no action was picked in we should try to pick the one
  18860. // that we think would work best for this key
  18861. if (!action) {
  18862. action = _getReverseMap()[key] ? 'keydown' : 'keypress';
  18863. }
  18864. // modifier keys don't work as expected with keypress,
  18865. // switch to keydown
  18866. if (action == 'keypress' && modifiers.length) {
  18867. action = 'keydown';
  18868. }
  18869. return action;
  18870. }
  18871. /**
  18872. * binds a key sequence to an event
  18873. *
  18874. * @param {string} combo - combo specified in bind call
  18875. * @param {Array} keys
  18876. * @param {Function} callback
  18877. * @param {string=} action
  18878. * @returns void
  18879. */
  18880. function _bindSequence(combo, keys, callback, action) {
  18881. // start off by adding a sequence level record for this combination
  18882. // and setting the level to 0
  18883. _sequence_levels[combo] = 0;
  18884. // if there is no action pick the best one for the first key
  18885. // in the sequence
  18886. if (!action) {
  18887. action = _pickBestAction(keys[0], []);
  18888. }
  18889. /**
  18890. * callback to increase the sequence level for this sequence and reset
  18891. * all other sequences that were active
  18892. *
  18893. * @param {Event} e
  18894. * @returns void
  18895. */
  18896. var _increaseSequence = function(e) {
  18897. _inside_sequence = action;
  18898. ++_sequence_levels[combo];
  18899. _resetSequenceTimer();
  18900. },
  18901. /**
  18902. * wraps the specified callback inside of another function in order
  18903. * to reset all sequence counters as soon as this sequence is done
  18904. *
  18905. * @param {Event} e
  18906. * @returns void
  18907. */
  18908. _callbackAndReset = function(e) {
  18909. _fireCallback(callback, e);
  18910. // we should ignore the next key up if the action is key down
  18911. // or keypress. this is so if you finish a sequence and
  18912. // release the key the final key will not trigger a keyup
  18913. if (action !== 'keyup') {
  18914. _ignore_next_keyup = _characterFromEvent(e);
  18915. }
  18916. // weird race condition if a sequence ends with the key
  18917. // another sequence begins with
  18918. setTimeout(_resetSequences, 10);
  18919. },
  18920. i;
  18921. // loop through keys one at a time and bind the appropriate callback
  18922. // function. for any key leading up to the final one it should
  18923. // increase the sequence. after the final, it should reset all sequences
  18924. for (i = 0; i < keys.length; ++i) {
  18925. _bindSingle(keys[i], i < keys.length - 1 ? _increaseSequence : _callbackAndReset, action, combo, i);
  18926. }
  18927. }
  18928. /**
  18929. * binds a single keyboard combination
  18930. *
  18931. * @param {string} combination
  18932. * @param {Function} callback
  18933. * @param {string=} action
  18934. * @param {string=} sequence_name - name of sequence if part of sequence
  18935. * @param {number=} level - what part of the sequence the command is
  18936. * @returns void
  18937. */
  18938. function _bindSingle(combination, callback, action, sequence_name, level) {
  18939. // make sure multiple spaces in a row become a single space
  18940. combination = combination.replace(/\s+/g, ' ');
  18941. var sequence = combination.split(' '),
  18942. i,
  18943. key,
  18944. keys,
  18945. modifiers = [];
  18946. // if this pattern is a sequence of keys then run through this method
  18947. // to reprocess each pattern one key at a time
  18948. if (sequence.length > 1) {
  18949. return _bindSequence(combination, sequence, callback, action);
  18950. }
  18951. // take the keys from this pattern and figure out what the actual
  18952. // pattern is all about
  18953. keys = combination === '+' ? ['+'] : combination.split('+');
  18954. for (i = 0; i < keys.length; ++i) {
  18955. key = keys[i];
  18956. // normalize key names
  18957. if (_SPECIAL_ALIASES[key]) {
  18958. key = _SPECIAL_ALIASES[key];
  18959. }
  18960. // if this is not a keypress event then we should
  18961. // be smart about using shift keys
  18962. // this will only work for US keyboards however
  18963. if (action && action != 'keypress' && _SHIFT_MAP[key]) {
  18964. key = _SHIFT_MAP[key];
  18965. modifiers.push('shift');
  18966. }
  18967. // if this key is a modifier then add it to the list of modifiers
  18968. if (_isModifier(key)) {
  18969. modifiers.push(key);
  18970. }
  18971. }
  18972. // depending on what the key combination is
  18973. // we will try to pick the best event for it
  18974. action = _pickBestAction(key, modifiers, action);
  18975. // make sure to initialize array if this is the first time
  18976. // a callback is added for this key
  18977. if (!_callbacks[key]) {
  18978. _callbacks[key] = [];
  18979. }
  18980. // remove an existing match if there is one
  18981. _getMatches(key, modifiers, action, !sequence_name, combination);
  18982. // add this call back to the array
  18983. // if it is a sequence put it at the beginning
  18984. // if not put it at the end
  18985. //
  18986. // this is important because the way these are processed expects
  18987. // the sequence ones to come first
  18988. _callbacks[key][sequence_name ? 'unshift' : 'push']({
  18989. callback: callback,
  18990. modifiers: modifiers,
  18991. action: action,
  18992. seq: sequence_name,
  18993. level: level,
  18994. combo: combination
  18995. });
  18996. }
  18997. /**
  18998. * binds multiple combinations to the same callback
  18999. *
  19000. * @param {Array} combinations
  19001. * @param {Function} callback
  19002. * @param {string|undefined} action
  19003. * @returns void
  19004. */
  19005. function _bindMultiple(combinations, callback, action) {
  19006. for (var i = 0; i < combinations.length; ++i) {
  19007. _bindSingle(combinations[i], callback, action);
  19008. }
  19009. }
  19010. // start!
  19011. _addEvent(document, 'keypress', _handleKey);
  19012. _addEvent(document, 'keydown', _handleKey);
  19013. _addEvent(document, 'keyup', _handleKey);
  19014. var mousetrap = {
  19015. /**
  19016. * binds an event to mousetrap
  19017. *
  19018. * can be a single key, a combination of keys separated with +,
  19019. * a comma separated list of keys, an array of keys, or
  19020. * a sequence of keys separated by spaces
  19021. *
  19022. * be sure to list the modifier keys first to make sure that the
  19023. * correct key ends up getting bound (the last key in the pattern)
  19024. *
  19025. * @param {string|Array} keys
  19026. * @param {Function} callback
  19027. * @param {string=} action - 'keypress', 'keydown', or 'keyup'
  19028. * @returns void
  19029. */
  19030. bind: function(keys, callback, action) {
  19031. _bindMultiple(keys instanceof Array ? keys : [keys], callback, action);
  19032. _direct_map[keys + ':' + action] = callback;
  19033. return this;
  19034. },
  19035. /**
  19036. * unbinds an event to mousetrap
  19037. *
  19038. * the unbinding sets the callback function of the specified key combo
  19039. * to an empty function and deletes the corresponding key in the
  19040. * _direct_map dict.
  19041. *
  19042. * the keycombo+action has to be exactly the same as
  19043. * it was defined in the bind method
  19044. *
  19045. * TODO: actually remove this from the _callbacks dictionary instead
  19046. * of binding an empty function
  19047. *
  19048. * @param {string|Array} keys
  19049. * @param {string} action
  19050. * @returns void
  19051. */
  19052. unbind: function(keys, action) {
  19053. if (_direct_map[keys + ':' + action]) {
  19054. delete _direct_map[keys + ':' + action];
  19055. this.bind(keys, function() {}, action);
  19056. }
  19057. return this;
  19058. },
  19059. /**
  19060. * triggers an event that has already been bound
  19061. *
  19062. * @param {string} keys
  19063. * @param {string=} action
  19064. * @returns void
  19065. */
  19066. trigger: function(keys, action) {
  19067. _direct_map[keys + ':' + action]();
  19068. return this;
  19069. },
  19070. /**
  19071. * resets the library back to its initial state. this is useful
  19072. * if you want to clear out the current keyboard shortcuts and bind
  19073. * new ones - for example if you switch to another page
  19074. *
  19075. * @returns void
  19076. */
  19077. reset: function() {
  19078. _callbacks = {};
  19079. _direct_map = {};
  19080. return this;
  19081. }
  19082. };
  19083. module.exports = mousetrap;
  19084. /***/ },
  19085. /* 51 */
  19086. /***/ function(module, exports, __webpack_require__) {
  19087. var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(global, module) {//! moment.js
  19088. //! version : 2.8.2
  19089. //! authors : Tim Wood, Iskren Chernev, Moment.js contributors
  19090. //! license : MIT
  19091. //! momentjs.com
  19092. (function (undefined) {
  19093. /************************************
  19094. Constants
  19095. ************************************/
  19096. var moment,
  19097. VERSION = '2.8.2',
  19098. // the global-scope this is NOT the global object in Node.js
  19099. globalScope = typeof global !== 'undefined' ? global : this,
  19100. oldGlobalMoment,
  19101. round = Math.round,
  19102. hasOwnProperty = Object.prototype.hasOwnProperty,
  19103. i,
  19104. YEAR = 0,
  19105. MONTH = 1,
  19106. DATE = 2,
  19107. HOUR = 3,
  19108. MINUTE = 4,
  19109. SECOND = 5,
  19110. MILLISECOND = 6,
  19111. // internal storage for locale config files
  19112. locales = {},
  19113. // extra moment internal properties (plugins register props here)
  19114. momentProperties = [],
  19115. // check for nodeJS
  19116. hasModule = (typeof module !== 'undefined' && module.exports),
  19117. // ASP.NET json date format regex
  19118. aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
  19119. aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,
  19120. // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
  19121. // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
  19122. isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,
  19123. // format tokens
  19124. 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,
  19125. localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,
  19126. // parsing token regexes
  19127. parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
  19128. parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
  19129. parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999
  19130. parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
  19131. parseTokenDigits = /\d+/, // nonzero number of digits
  19132. 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.
  19133. parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
  19134. parseTokenT = /T/i, // T (ISO separator)
  19135. parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
  19136. parseTokenOrdinal = /\d{1,2}/,
  19137. //strict parsing regexes
  19138. parseTokenOneDigit = /\d/, // 0 - 9
  19139. parseTokenTwoDigits = /\d\d/, // 00 - 99
  19140. parseTokenThreeDigits = /\d{3}/, // 000 - 999
  19141. parseTokenFourDigits = /\d{4}/, // 0000 - 9999
  19142. parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999
  19143. parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf
  19144. // iso 8601 regex
  19145. // 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)
  19146. 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)?)?$/,
  19147. isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
  19148. isoDates = [
  19149. ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/],
  19150. ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/],
  19151. ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/],
  19152. ['GGGG-[W]WW', /\d{4}-W\d{2}/],
  19153. ['YYYY-DDD', /\d{4}-\d{3}/]
  19154. ],
  19155. // iso time formats and regexes
  19156. isoTimes = [
  19157. ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/],
  19158. ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
  19159. ['HH:mm', /(T| )\d\d:\d\d/],
  19160. ['HH', /(T| )\d\d/]
  19161. ],
  19162. // timezone chunker '+10:00' > ['10', '00'] or '-1530' > ['-15', '30']
  19163. parseTimezoneChunker = /([\+\-]|\d\d)/gi,
  19164. // getter and setter names
  19165. proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
  19166. unitMillisecondFactors = {
  19167. 'Milliseconds' : 1,
  19168. 'Seconds' : 1e3,
  19169. 'Minutes' : 6e4,
  19170. 'Hours' : 36e5,
  19171. 'Days' : 864e5,
  19172. 'Months' : 2592e6,
  19173. 'Years' : 31536e6
  19174. },
  19175. unitAliases = {
  19176. ms : 'millisecond',
  19177. s : 'second',
  19178. m : 'minute',
  19179. h : 'hour',
  19180. d : 'day',
  19181. D : 'date',
  19182. w : 'week',
  19183. W : 'isoWeek',
  19184. M : 'month',
  19185. Q : 'quarter',
  19186. y : 'year',
  19187. DDD : 'dayOfYear',
  19188. e : 'weekday',
  19189. E : 'isoWeekday',
  19190. gg: 'weekYear',
  19191. GG: 'isoWeekYear'
  19192. },
  19193. camelFunctions = {
  19194. dayofyear : 'dayOfYear',
  19195. isoweekday : 'isoWeekday',
  19196. isoweek : 'isoWeek',
  19197. weekyear : 'weekYear',
  19198. isoweekyear : 'isoWeekYear'
  19199. },
  19200. // format function strings
  19201. formatFunctions = {},
  19202. // default relative time thresholds
  19203. relativeTimeThresholds = {
  19204. s: 45, // seconds to minute
  19205. m: 45, // minutes to hour
  19206. h: 22, // hours to day
  19207. d: 26, // days to month
  19208. M: 11 // months to year
  19209. },
  19210. // tokens to ordinalize and pad
  19211. ordinalizeTokens = 'DDD w W M D d'.split(' '),
  19212. paddedTokens = 'M D H h m s w W'.split(' '),
  19213. formatTokenFunctions = {
  19214. M : function () {
  19215. return this.month() + 1;
  19216. },
  19217. MMM : function (format) {
  19218. return this.localeData().monthsShort(this, format);
  19219. },
  19220. MMMM : function (format) {
  19221. return this.localeData().months(this, format);
  19222. },
  19223. D : function () {
  19224. return this.date();
  19225. },
  19226. DDD : function () {
  19227. return this.dayOfYear();
  19228. },
  19229. d : function () {
  19230. return this.day();
  19231. },
  19232. dd : function (format) {
  19233. return this.localeData().weekdaysMin(this, format);
  19234. },
  19235. ddd : function (format) {
  19236. return this.localeData().weekdaysShort(this, format);
  19237. },
  19238. dddd : function (format) {
  19239. return this.localeData().weekdays(this, format);
  19240. },
  19241. w : function () {
  19242. return this.week();
  19243. },
  19244. W : function () {
  19245. return this.isoWeek();
  19246. },
  19247. YY : function () {
  19248. return leftZeroFill(this.year() % 100, 2);
  19249. },
  19250. YYYY : function () {
  19251. return leftZeroFill(this.year(), 4);
  19252. },
  19253. YYYYY : function () {
  19254. return leftZeroFill(this.year(), 5);
  19255. },
  19256. YYYYYY : function () {
  19257. var y = this.year(), sign = y >= 0 ? '+' : '-';
  19258. return sign + leftZeroFill(Math.abs(y), 6);
  19259. },
  19260. gg : function () {
  19261. return leftZeroFill(this.weekYear() % 100, 2);
  19262. },
  19263. gggg : function () {
  19264. return leftZeroFill(this.weekYear(), 4);
  19265. },
  19266. ggggg : function () {
  19267. return leftZeroFill(this.weekYear(), 5);
  19268. },
  19269. GG : function () {
  19270. return leftZeroFill(this.isoWeekYear() % 100, 2);
  19271. },
  19272. GGGG : function () {
  19273. return leftZeroFill(this.isoWeekYear(), 4);
  19274. },
  19275. GGGGG : function () {
  19276. return leftZeroFill(this.isoWeekYear(), 5);
  19277. },
  19278. e : function () {
  19279. return this.weekday();
  19280. },
  19281. E : function () {
  19282. return this.isoWeekday();
  19283. },
  19284. a : function () {
  19285. return this.localeData().meridiem(this.hours(), this.minutes(), true);
  19286. },
  19287. A : function () {
  19288. return this.localeData().meridiem(this.hours(), this.minutes(), false);
  19289. },
  19290. H : function () {
  19291. return this.hours();
  19292. },
  19293. h : function () {
  19294. return this.hours() % 12 || 12;
  19295. },
  19296. m : function () {
  19297. return this.minutes();
  19298. },
  19299. s : function () {
  19300. return this.seconds();
  19301. },
  19302. S : function () {
  19303. return toInt(this.milliseconds() / 100);
  19304. },
  19305. SS : function () {
  19306. return leftZeroFill(toInt(this.milliseconds() / 10), 2);
  19307. },
  19308. SSS : function () {
  19309. return leftZeroFill(this.milliseconds(), 3);
  19310. },
  19311. SSSS : function () {
  19312. return leftZeroFill(this.milliseconds(), 3);
  19313. },
  19314. Z : function () {
  19315. var a = -this.zone(),
  19316. b = '+';
  19317. if (a < 0) {
  19318. a = -a;
  19319. b = '-';
  19320. }
  19321. return b + leftZeroFill(toInt(a / 60), 2) + ':' + leftZeroFill(toInt(a) % 60, 2);
  19322. },
  19323. ZZ : function () {
  19324. var a = -this.zone(),
  19325. b = '+';
  19326. if (a < 0) {
  19327. a = -a;
  19328. b = '-';
  19329. }
  19330. return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2);
  19331. },
  19332. z : function () {
  19333. return this.zoneAbbr();
  19334. },
  19335. zz : function () {
  19336. return this.zoneName();
  19337. },
  19338. X : function () {
  19339. return this.unix();
  19340. },
  19341. Q : function () {
  19342. return this.quarter();
  19343. }
  19344. },
  19345. deprecations = {},
  19346. lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'];
  19347. // Pick the first defined of two or three arguments. dfl comes from
  19348. // default.
  19349. function dfl(a, b, c) {
  19350. switch (arguments.length) {
  19351. case 2: return a != null ? a : b;
  19352. case 3: return a != null ? a : b != null ? b : c;
  19353. default: throw new Error('Implement me');
  19354. }
  19355. }
  19356. function hasOwnProp(a, b) {
  19357. return hasOwnProperty.call(a, b);
  19358. }
  19359. function defaultParsingFlags() {
  19360. // We need to deep clone this object, and es5 standard is not very
  19361. // helpful.
  19362. return {
  19363. empty : false,
  19364. unusedTokens : [],
  19365. unusedInput : [],
  19366. overflow : -2,
  19367. charsLeftOver : 0,
  19368. nullInput : false,
  19369. invalidMonth : null,
  19370. invalidFormat : false,
  19371. userInvalidated : false,
  19372. iso: false
  19373. };
  19374. }
  19375. function printMsg(msg) {
  19376. if (moment.suppressDeprecationWarnings === false &&
  19377. typeof console !== 'undefined' && console.warn) {
  19378. console.warn('Deprecation warning: ' + msg);
  19379. }
  19380. }
  19381. function deprecate(msg, fn) {
  19382. var firstTime = true;
  19383. return extend(function () {
  19384. if (firstTime) {
  19385. printMsg(msg);
  19386. firstTime = false;
  19387. }
  19388. return fn.apply(this, arguments);
  19389. }, fn);
  19390. }
  19391. function deprecateSimple(name, msg) {
  19392. if (!deprecations[name]) {
  19393. printMsg(msg);
  19394. deprecations[name] = true;
  19395. }
  19396. }
  19397. function padToken(func, count) {
  19398. return function (a) {
  19399. return leftZeroFill(func.call(this, a), count);
  19400. };
  19401. }
  19402. function ordinalizeToken(func, period) {
  19403. return function (a) {
  19404. return this.localeData().ordinal(func.call(this, a), period);
  19405. };
  19406. }
  19407. while (ordinalizeTokens.length) {
  19408. i = ordinalizeTokens.pop();
  19409. formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i);
  19410. }
  19411. while (paddedTokens.length) {
  19412. i = paddedTokens.pop();
  19413. formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2);
  19414. }
  19415. formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3);
  19416. /************************************
  19417. Constructors
  19418. ************************************/
  19419. function Locale() {
  19420. }
  19421. // Moment prototype object
  19422. function Moment(config, skipOverflow) {
  19423. if (skipOverflow !== false) {
  19424. checkOverflow(config);
  19425. }
  19426. copyConfig(this, config);
  19427. this._d = new Date(+config._d);
  19428. }
  19429. // Duration Constructor
  19430. function Duration(duration) {
  19431. var normalizedInput = normalizeObjectUnits(duration),
  19432. years = normalizedInput.year || 0,
  19433. quarters = normalizedInput.quarter || 0,
  19434. months = normalizedInput.month || 0,
  19435. weeks = normalizedInput.week || 0,
  19436. days = normalizedInput.day || 0,
  19437. hours = normalizedInput.hour || 0,
  19438. minutes = normalizedInput.minute || 0,
  19439. seconds = normalizedInput.second || 0,
  19440. milliseconds = normalizedInput.millisecond || 0;
  19441. // representation for dateAddRemove
  19442. this._milliseconds = +milliseconds +
  19443. seconds * 1e3 + // 1000
  19444. minutes * 6e4 + // 1000 * 60
  19445. hours * 36e5; // 1000 * 60 * 60
  19446. // Because of dateAddRemove treats 24 hours as different from a
  19447. // day when working around DST, we need to store them separately
  19448. this._days = +days +
  19449. weeks * 7;
  19450. // It is impossible translate months into days without knowing
  19451. // which months you are are talking about, so we have to store
  19452. // it separately.
  19453. this._months = +months +
  19454. quarters * 3 +
  19455. years * 12;
  19456. this._data = {};
  19457. this._locale = moment.localeData();
  19458. this._bubble();
  19459. }
  19460. /************************************
  19461. Helpers
  19462. ************************************/
  19463. function extend(a, b) {
  19464. for (var i in b) {
  19465. if (hasOwnProp(b, i)) {
  19466. a[i] = b[i];
  19467. }
  19468. }
  19469. if (hasOwnProp(b, 'toString')) {
  19470. a.toString = b.toString;
  19471. }
  19472. if (hasOwnProp(b, 'valueOf')) {
  19473. a.valueOf = b.valueOf;
  19474. }
  19475. return a;
  19476. }
  19477. function copyConfig(to, from) {
  19478. var i, prop, val;
  19479. if (typeof from._isAMomentObject !== 'undefined') {
  19480. to._isAMomentObject = from._isAMomentObject;
  19481. }
  19482. if (typeof from._i !== 'undefined') {
  19483. to._i = from._i;
  19484. }
  19485. if (typeof from._f !== 'undefined') {
  19486. to._f = from._f;
  19487. }
  19488. if (typeof from._l !== 'undefined') {
  19489. to._l = from._l;
  19490. }
  19491. if (typeof from._strict !== 'undefined') {
  19492. to._strict = from._strict;
  19493. }
  19494. if (typeof from._tzm !== 'undefined') {
  19495. to._tzm = from._tzm;
  19496. }
  19497. if (typeof from._isUTC !== 'undefined') {
  19498. to._isUTC = from._isUTC;
  19499. }
  19500. if (typeof from._offset !== 'undefined') {
  19501. to._offset = from._offset;
  19502. }
  19503. if (typeof from._pf !== 'undefined') {
  19504. to._pf = from._pf;
  19505. }
  19506. if (typeof from._locale !== 'undefined') {
  19507. to._locale = from._locale;
  19508. }
  19509. if (momentProperties.length > 0) {
  19510. for (i in momentProperties) {
  19511. prop = momentProperties[i];
  19512. val = from[prop];
  19513. if (typeof val !== 'undefined') {
  19514. to[prop] = val;
  19515. }
  19516. }
  19517. }
  19518. return to;
  19519. }
  19520. function absRound(number) {
  19521. if (number < 0) {
  19522. return Math.ceil(number);
  19523. } else {
  19524. return Math.floor(number);
  19525. }
  19526. }
  19527. // left zero fill a number
  19528. // see http://jsperf.com/left-zero-filling for performance comparison
  19529. function leftZeroFill(number, targetLength, forceSign) {
  19530. var output = '' + Math.abs(number),
  19531. sign = number >= 0;
  19532. while (output.length < targetLength) {
  19533. output = '0' + output;
  19534. }
  19535. return (sign ? (forceSign ? '+' : '') : '-') + output;
  19536. }
  19537. function positiveMomentsDifference(base, other) {
  19538. var res = {milliseconds: 0, months: 0};
  19539. res.months = other.month() - base.month() +
  19540. (other.year() - base.year()) * 12;
  19541. if (base.clone().add(res.months, 'M').isAfter(other)) {
  19542. --res.months;
  19543. }
  19544. res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
  19545. return res;
  19546. }
  19547. function momentsDifference(base, other) {
  19548. var res;
  19549. other = makeAs(other, base);
  19550. if (base.isBefore(other)) {
  19551. res = positiveMomentsDifference(base, other);
  19552. } else {
  19553. res = positiveMomentsDifference(other, base);
  19554. res.milliseconds = -res.milliseconds;
  19555. res.months = -res.months;
  19556. }
  19557. return res;
  19558. }
  19559. // TODO: remove 'name' arg after deprecation is removed
  19560. function createAdder(direction, name) {
  19561. return function (val, period) {
  19562. var dur, tmp;
  19563. //invert the arguments, but complain about it
  19564. if (period !== null && !isNaN(+period)) {
  19565. deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).');
  19566. tmp = val; val = period; period = tmp;
  19567. }
  19568. val = typeof val === 'string' ? +val : val;
  19569. dur = moment.duration(val, period);
  19570. addOrSubtractDurationFromMoment(this, dur, direction);
  19571. return this;
  19572. };
  19573. }
  19574. function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) {
  19575. var milliseconds = duration._milliseconds,
  19576. days = duration._days,
  19577. months = duration._months;
  19578. updateOffset = updateOffset == null ? true : updateOffset;
  19579. if (milliseconds) {
  19580. mom._d.setTime(+mom._d + milliseconds * isAdding);
  19581. }
  19582. if (days) {
  19583. rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding);
  19584. }
  19585. if (months) {
  19586. rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding);
  19587. }
  19588. if (updateOffset) {
  19589. moment.updateOffset(mom, days || months);
  19590. }
  19591. }
  19592. // check if is an array
  19593. function isArray(input) {
  19594. return Object.prototype.toString.call(input) === '[object Array]';
  19595. }
  19596. function isDate(input) {
  19597. return Object.prototype.toString.call(input) === '[object Date]' ||
  19598. input instanceof Date;
  19599. }
  19600. // compare two arrays, return the number of differences
  19601. function compareArrays(array1, array2, dontConvert) {
  19602. var len = Math.min(array1.length, array2.length),
  19603. lengthDiff = Math.abs(array1.length - array2.length),
  19604. diffs = 0,
  19605. i;
  19606. for (i = 0; i < len; i++) {
  19607. if ((dontConvert && array1[i] !== array2[i]) ||
  19608. (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
  19609. diffs++;
  19610. }
  19611. }
  19612. return diffs + lengthDiff;
  19613. }
  19614. function normalizeUnits(units) {
  19615. if (units) {
  19616. var lowered = units.toLowerCase().replace(/(.)s$/, '$1');
  19617. units = unitAliases[units] || camelFunctions[lowered] || lowered;
  19618. }
  19619. return units;
  19620. }
  19621. function normalizeObjectUnits(inputObject) {
  19622. var normalizedInput = {},
  19623. normalizedProp,
  19624. prop;
  19625. for (prop in inputObject) {
  19626. if (hasOwnProp(inputObject, prop)) {
  19627. normalizedProp = normalizeUnits(prop);
  19628. if (normalizedProp) {
  19629. normalizedInput[normalizedProp] = inputObject[prop];
  19630. }
  19631. }
  19632. }
  19633. return normalizedInput;
  19634. }
  19635. function makeList(field) {
  19636. var count, setter;
  19637. if (field.indexOf('week') === 0) {
  19638. count = 7;
  19639. setter = 'day';
  19640. }
  19641. else if (field.indexOf('month') === 0) {
  19642. count = 12;
  19643. setter = 'month';
  19644. }
  19645. else {
  19646. return;
  19647. }
  19648. moment[field] = function (format, index) {
  19649. var i, getter,
  19650. method = moment._locale[field],
  19651. results = [];
  19652. if (typeof format === 'number') {
  19653. index = format;
  19654. format = undefined;
  19655. }
  19656. getter = function (i) {
  19657. var m = moment().utc().set(setter, i);
  19658. return method.call(moment._locale, m, format || '');
  19659. };
  19660. if (index != null) {
  19661. return getter(index);
  19662. }
  19663. else {
  19664. for (i = 0; i < count; i++) {
  19665. results.push(getter(i));
  19666. }
  19667. return results;
  19668. }
  19669. };
  19670. }
  19671. function toInt(argumentForCoercion) {
  19672. var coercedNumber = +argumentForCoercion,
  19673. value = 0;
  19674. if (coercedNumber !== 0 && isFinite(coercedNumber)) {
  19675. if (coercedNumber >= 0) {
  19676. value = Math.floor(coercedNumber);
  19677. } else {
  19678. value = Math.ceil(coercedNumber);
  19679. }
  19680. }
  19681. return value;
  19682. }
  19683. function daysInMonth(year, month) {
  19684. return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
  19685. }
  19686. function weeksInYear(year, dow, doy) {
  19687. return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week;
  19688. }
  19689. function daysInYear(year) {
  19690. return isLeapYear(year) ? 366 : 365;
  19691. }
  19692. function isLeapYear(year) {
  19693. return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
  19694. }
  19695. function checkOverflow(m) {
  19696. var overflow;
  19697. if (m._a && m._pf.overflow === -2) {
  19698. overflow =
  19699. m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH :
  19700. m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE :
  19701. m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR :
  19702. m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE :
  19703. m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND :
  19704. m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND :
  19705. -1;
  19706. if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
  19707. overflow = DATE;
  19708. }
  19709. m._pf.overflow = overflow;
  19710. }
  19711. }
  19712. function isValid(m) {
  19713. if (m._isValid == null) {
  19714. m._isValid = !isNaN(m._d.getTime()) &&
  19715. m._pf.overflow < 0 &&
  19716. !m._pf.empty &&
  19717. !m._pf.invalidMonth &&
  19718. !m._pf.nullInput &&
  19719. !m._pf.invalidFormat &&
  19720. !m._pf.userInvalidated;
  19721. if (m._strict) {
  19722. m._isValid = m._isValid &&
  19723. m._pf.charsLeftOver === 0 &&
  19724. m._pf.unusedTokens.length === 0;
  19725. }
  19726. }
  19727. return m._isValid;
  19728. }
  19729. function normalizeLocale(key) {
  19730. return key ? key.toLowerCase().replace('_', '-') : key;
  19731. }
  19732. // pick the locale from the array
  19733. // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
  19734. // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
  19735. function chooseLocale(names) {
  19736. var i = 0, j, next, locale, split;
  19737. while (i < names.length) {
  19738. split = normalizeLocale(names[i]).split('-');
  19739. j = split.length;
  19740. next = normalizeLocale(names[i + 1]);
  19741. next = next ? next.split('-') : null;
  19742. while (j > 0) {
  19743. locale = loadLocale(split.slice(0, j).join('-'));
  19744. if (locale) {
  19745. return locale;
  19746. }
  19747. if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
  19748. //the next array item is better than a shallower substring of this one
  19749. break;
  19750. }
  19751. j--;
  19752. }
  19753. i++;
  19754. }
  19755. return null;
  19756. }
  19757. function loadLocale(name) {
  19758. var oldLocale = null;
  19759. if (!locales[name] && hasModule) {
  19760. try {
  19761. oldLocale = moment.locale();
  19762. !(function webpackMissingModule() { var e = new Error("Cannot find module \"./locale\""); e.code = 'MODULE_NOT_FOUND'; throw e; }());
  19763. // because defineLocale currently also sets the global locale, we want to undo that for lazy loaded locales
  19764. moment.locale(oldLocale);
  19765. } catch (e) { }
  19766. }
  19767. return locales[name];
  19768. }
  19769. // Return a moment from input, that is local/utc/zone equivalent to model.
  19770. function makeAs(input, model) {
  19771. return model._isUTC ? moment(input).zone(model._offset || 0) :
  19772. moment(input).local();
  19773. }
  19774. /************************************
  19775. Locale
  19776. ************************************/
  19777. extend(Locale.prototype, {
  19778. set : function (config) {
  19779. var prop, i;
  19780. for (i in config) {
  19781. prop = config[i];
  19782. if (typeof prop === 'function') {
  19783. this[i] = prop;
  19784. } else {
  19785. this['_' + i] = prop;
  19786. }
  19787. }
  19788. },
  19789. _months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
  19790. months : function (m) {
  19791. return this._months[m.month()];
  19792. },
  19793. _monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
  19794. monthsShort : function (m) {
  19795. return this._monthsShort[m.month()];
  19796. },
  19797. monthsParse : function (monthName) {
  19798. var i, mom, regex;
  19799. if (!this._monthsParse) {
  19800. this._monthsParse = [];
  19801. }
  19802. for (i = 0; i < 12; i++) {
  19803. // make the regex if we don't have it already
  19804. if (!this._monthsParse[i]) {
  19805. mom = moment.utc([2000, i]);
  19806. regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
  19807. this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
  19808. }
  19809. // test the regex
  19810. if (this._monthsParse[i].test(monthName)) {
  19811. return i;
  19812. }
  19813. }
  19814. },
  19815. _weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
  19816. weekdays : function (m) {
  19817. return this._weekdays[m.day()];
  19818. },
  19819. _weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
  19820. weekdaysShort : function (m) {
  19821. return this._weekdaysShort[m.day()];
  19822. },
  19823. _weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
  19824. weekdaysMin : function (m) {
  19825. return this._weekdaysMin[m.day()];
  19826. },
  19827. weekdaysParse : function (weekdayName) {
  19828. var i, mom, regex;
  19829. if (!this._weekdaysParse) {
  19830. this._weekdaysParse = [];
  19831. }
  19832. for (i = 0; i < 7; i++) {
  19833. // make the regex if we don't have it already
  19834. if (!this._weekdaysParse[i]) {
  19835. mom = moment([2000, 1]).day(i);
  19836. regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
  19837. this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
  19838. }
  19839. // test the regex
  19840. if (this._weekdaysParse[i].test(weekdayName)) {
  19841. return i;
  19842. }
  19843. }
  19844. },
  19845. _longDateFormat : {
  19846. LT : 'h:mm A',
  19847. L : 'MM/DD/YYYY',
  19848. LL : 'MMMM D, YYYY',
  19849. LLL : 'MMMM D, YYYY LT',
  19850. LLLL : 'dddd, MMMM D, YYYY LT'
  19851. },
  19852. longDateFormat : function (key) {
  19853. var output = this._longDateFormat[key];
  19854. if (!output && this._longDateFormat[key.toUpperCase()]) {
  19855. output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
  19856. return val.slice(1);
  19857. });
  19858. this._longDateFormat[key] = output;
  19859. }
  19860. return output;
  19861. },
  19862. isPM : function (input) {
  19863. // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
  19864. // Using charAt should be more compatible.
  19865. return ((input + '').toLowerCase().charAt(0) === 'p');
  19866. },
  19867. _meridiemParse : /[ap]\.?m?\.?/i,
  19868. meridiem : function (hours, minutes, isLower) {
  19869. if (hours > 11) {
  19870. return isLower ? 'pm' : 'PM';
  19871. } else {
  19872. return isLower ? 'am' : 'AM';
  19873. }
  19874. },
  19875. _calendar : {
  19876. sameDay : '[Today at] LT',
  19877. nextDay : '[Tomorrow at] LT',
  19878. nextWeek : 'dddd [at] LT',
  19879. lastDay : '[Yesterday at] LT',
  19880. lastWeek : '[Last] dddd [at] LT',
  19881. sameElse : 'L'
  19882. },
  19883. calendar : function (key, mom) {
  19884. var output = this._calendar[key];
  19885. return typeof output === 'function' ? output.apply(mom) : output;
  19886. },
  19887. _relativeTime : {
  19888. future : 'in %s',
  19889. past : '%s ago',
  19890. s : 'a few seconds',
  19891. m : 'a minute',
  19892. mm : '%d minutes',
  19893. h : 'an hour',
  19894. hh : '%d hours',
  19895. d : 'a day',
  19896. dd : '%d days',
  19897. M : 'a month',
  19898. MM : '%d months',
  19899. y : 'a year',
  19900. yy : '%d years'
  19901. },
  19902. relativeTime : function (number, withoutSuffix, string, isFuture) {
  19903. var output = this._relativeTime[string];
  19904. return (typeof output === 'function') ?
  19905. output(number, withoutSuffix, string, isFuture) :
  19906. output.replace(/%d/i, number);
  19907. },
  19908. pastFuture : function (diff, output) {
  19909. var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
  19910. return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
  19911. },
  19912. ordinal : function (number) {
  19913. return this._ordinal.replace('%d', number);
  19914. },
  19915. _ordinal : '%d',
  19916. preparse : function (string) {
  19917. return string;
  19918. },
  19919. postformat : function (string) {
  19920. return string;
  19921. },
  19922. week : function (mom) {
  19923. return weekOfYear(mom, this._week.dow, this._week.doy).week;
  19924. },
  19925. _week : {
  19926. dow : 0, // Sunday is the first day of the week.
  19927. doy : 6 // The week that contains Jan 1st is the first week of the year.
  19928. },
  19929. _invalidDate: 'Invalid date',
  19930. invalidDate: function () {
  19931. return this._invalidDate;
  19932. }
  19933. });
  19934. /************************************
  19935. Formatting
  19936. ************************************/
  19937. function removeFormattingTokens(input) {
  19938. if (input.match(/\[[\s\S]/)) {
  19939. return input.replace(/^\[|\]$/g, '');
  19940. }
  19941. return input.replace(/\\/g, '');
  19942. }
  19943. function makeFormatFunction(format) {
  19944. var array = format.match(formattingTokens), i, length;
  19945. for (i = 0, length = array.length; i < length; i++) {
  19946. if (formatTokenFunctions[array[i]]) {
  19947. array[i] = formatTokenFunctions[array[i]];
  19948. } else {
  19949. array[i] = removeFormattingTokens(array[i]);
  19950. }
  19951. }
  19952. return function (mom) {
  19953. var output = '';
  19954. for (i = 0; i < length; i++) {
  19955. output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
  19956. }
  19957. return output;
  19958. };
  19959. }
  19960. // format date using native date object
  19961. function formatMoment(m, format) {
  19962. if (!m.isValid()) {
  19963. return m.localeData().invalidDate();
  19964. }
  19965. format = expandFormat(format, m.localeData());
  19966. if (!formatFunctions[format]) {
  19967. formatFunctions[format] = makeFormatFunction(format);
  19968. }
  19969. return formatFunctions[format](m);
  19970. }
  19971. function expandFormat(format, locale) {
  19972. var i = 5;
  19973. function replaceLongDateFormatTokens(input) {
  19974. return locale.longDateFormat(input) || input;
  19975. }
  19976. localFormattingTokens.lastIndex = 0;
  19977. while (i >= 0 && localFormattingTokens.test(format)) {
  19978. format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
  19979. localFormattingTokens.lastIndex = 0;
  19980. i -= 1;
  19981. }
  19982. return format;
  19983. }
  19984. /************************************
  19985. Parsing
  19986. ************************************/
  19987. // get the regex to find the next token
  19988. function getParseRegexForToken(token, config) {
  19989. var a, strict = config._strict;
  19990. switch (token) {
  19991. case 'Q':
  19992. return parseTokenOneDigit;
  19993. case 'DDDD':
  19994. return parseTokenThreeDigits;
  19995. case 'YYYY':
  19996. case 'GGGG':
  19997. case 'gggg':
  19998. return strict ? parseTokenFourDigits : parseTokenOneToFourDigits;
  19999. case 'Y':
  20000. case 'G':
  20001. case 'g':
  20002. return parseTokenSignedNumber;
  20003. case 'YYYYYY':
  20004. case 'YYYYY':
  20005. case 'GGGGG':
  20006. case 'ggggg':
  20007. return strict ? parseTokenSixDigits : parseTokenOneToSixDigits;
  20008. case 'S':
  20009. if (strict) {
  20010. return parseTokenOneDigit;
  20011. }
  20012. /* falls through */
  20013. case 'SS':
  20014. if (strict) {
  20015. return parseTokenTwoDigits;
  20016. }
  20017. /* falls through */
  20018. case 'SSS':
  20019. if (strict) {
  20020. return parseTokenThreeDigits;
  20021. }
  20022. /* falls through */
  20023. case 'DDD':
  20024. return parseTokenOneToThreeDigits;
  20025. case 'MMM':
  20026. case 'MMMM':
  20027. case 'dd':
  20028. case 'ddd':
  20029. case 'dddd':
  20030. return parseTokenWord;
  20031. case 'a':
  20032. case 'A':
  20033. return config._locale._meridiemParse;
  20034. case 'X':
  20035. return parseTokenTimestampMs;
  20036. case 'Z':
  20037. case 'ZZ':
  20038. return parseTokenTimezone;
  20039. case 'T':
  20040. return parseTokenT;
  20041. case 'SSSS':
  20042. return parseTokenDigits;
  20043. case 'MM':
  20044. case 'DD':
  20045. case 'YY':
  20046. case 'GG':
  20047. case 'gg':
  20048. case 'HH':
  20049. case 'hh':
  20050. case 'mm':
  20051. case 'ss':
  20052. case 'ww':
  20053. case 'WW':
  20054. return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits;
  20055. case 'M':
  20056. case 'D':
  20057. case 'd':
  20058. case 'H':
  20059. case 'h':
  20060. case 'm':
  20061. case 's':
  20062. case 'w':
  20063. case 'W':
  20064. case 'e':
  20065. case 'E':
  20066. return parseTokenOneOrTwoDigits;
  20067. case 'Do':
  20068. return parseTokenOrdinal;
  20069. default :
  20070. a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), 'i'));
  20071. return a;
  20072. }
  20073. }
  20074. function timezoneMinutesFromString(string) {
  20075. string = string || '';
  20076. var possibleTzMatches = (string.match(parseTokenTimezone) || []),
  20077. tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [],
  20078. parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
  20079. minutes = +(parts[1] * 60) + toInt(parts[2]);
  20080. return parts[0] === '+' ? -minutes : minutes;
  20081. }
  20082. // function to convert string input to date
  20083. function addTimeToArrayFromToken(token, input, config) {
  20084. var a, datePartArray = config._a;
  20085. switch (token) {
  20086. // QUARTER
  20087. case 'Q':
  20088. if (input != null) {
  20089. datePartArray[MONTH] = (toInt(input) - 1) * 3;
  20090. }
  20091. break;
  20092. // MONTH
  20093. case 'M' : // fall through to MM
  20094. case 'MM' :
  20095. if (input != null) {
  20096. datePartArray[MONTH] = toInt(input) - 1;
  20097. }
  20098. break;
  20099. case 'MMM' : // fall through to MMMM
  20100. case 'MMMM' :
  20101. a = config._locale.monthsParse(input);
  20102. // if we didn't find a month name, mark the date as invalid.
  20103. if (a != null) {
  20104. datePartArray[MONTH] = a;
  20105. } else {
  20106. config._pf.invalidMonth = input;
  20107. }
  20108. break;
  20109. // DAY OF MONTH
  20110. case 'D' : // fall through to DD
  20111. case 'DD' :
  20112. if (input != null) {
  20113. datePartArray[DATE] = toInt(input);
  20114. }
  20115. break;
  20116. case 'Do' :
  20117. if (input != null) {
  20118. datePartArray[DATE] = toInt(parseInt(input, 10));
  20119. }
  20120. break;
  20121. // DAY OF YEAR
  20122. case 'DDD' : // fall through to DDDD
  20123. case 'DDDD' :
  20124. if (input != null) {
  20125. config._dayOfYear = toInt(input);
  20126. }
  20127. break;
  20128. // YEAR
  20129. case 'YY' :
  20130. datePartArray[YEAR] = moment.parseTwoDigitYear(input);
  20131. break;
  20132. case 'YYYY' :
  20133. case 'YYYYY' :
  20134. case 'YYYYYY' :
  20135. datePartArray[YEAR] = toInt(input);
  20136. break;
  20137. // AM / PM
  20138. case 'a' : // fall through to A
  20139. case 'A' :
  20140. config._isPm = config._locale.isPM(input);
  20141. break;
  20142. // 24 HOUR
  20143. case 'H' : // fall through to hh
  20144. case 'HH' : // fall through to hh
  20145. case 'h' : // fall through to hh
  20146. case 'hh' :
  20147. datePartArray[HOUR] = toInt(input);
  20148. break;
  20149. // MINUTE
  20150. case 'm' : // fall through to mm
  20151. case 'mm' :
  20152. datePartArray[MINUTE] = toInt(input);
  20153. break;
  20154. // SECOND
  20155. case 's' : // fall through to ss
  20156. case 'ss' :
  20157. datePartArray[SECOND] = toInt(input);
  20158. break;
  20159. // MILLISECOND
  20160. case 'S' :
  20161. case 'SS' :
  20162. case 'SSS' :
  20163. case 'SSSS' :
  20164. datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000);
  20165. break;
  20166. // UNIX TIMESTAMP WITH MS
  20167. case 'X':
  20168. config._d = new Date(parseFloat(input) * 1000);
  20169. break;
  20170. // TIMEZONE
  20171. case 'Z' : // fall through to ZZ
  20172. case 'ZZ' :
  20173. config._useUTC = true;
  20174. config._tzm = timezoneMinutesFromString(input);
  20175. break;
  20176. // WEEKDAY - human
  20177. case 'dd':
  20178. case 'ddd':
  20179. case 'dddd':
  20180. a = config._locale.weekdaysParse(input);
  20181. // if we didn't get a weekday name, mark the date as invalid
  20182. if (a != null) {
  20183. config._w = config._w || {};
  20184. config._w['d'] = a;
  20185. } else {
  20186. config._pf.invalidWeekday = input;
  20187. }
  20188. break;
  20189. // WEEK, WEEK DAY - numeric
  20190. case 'w':
  20191. case 'ww':
  20192. case 'W':
  20193. case 'WW':
  20194. case 'd':
  20195. case 'e':
  20196. case 'E':
  20197. token = token.substr(0, 1);
  20198. /* falls through */
  20199. case 'gggg':
  20200. case 'GGGG':
  20201. case 'GGGGG':
  20202. token = token.substr(0, 2);
  20203. if (input) {
  20204. config._w = config._w || {};
  20205. config._w[token] = toInt(input);
  20206. }
  20207. break;
  20208. case 'gg':
  20209. case 'GG':
  20210. config._w = config._w || {};
  20211. config._w[token] = moment.parseTwoDigitYear(input);
  20212. }
  20213. }
  20214. function dayOfYearFromWeekInfo(config) {
  20215. var w, weekYear, week, weekday, dow, doy, temp;
  20216. w = config._w;
  20217. if (w.GG != null || w.W != null || w.E != null) {
  20218. dow = 1;
  20219. doy = 4;
  20220. // TODO: We need to take the current isoWeekYear, but that depends on
  20221. // how we interpret now (local, utc, fixed offset). So create
  20222. // a now version of current config (take local/utc/offset flags, and
  20223. // create now).
  20224. weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year);
  20225. week = dfl(w.W, 1);
  20226. weekday = dfl(w.E, 1);
  20227. } else {
  20228. dow = config._locale._week.dow;
  20229. doy = config._locale._week.doy;
  20230. weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year);
  20231. week = dfl(w.w, 1);
  20232. if (w.d != null) {
  20233. // weekday -- low day numbers are considered next week
  20234. weekday = w.d;
  20235. if (weekday < dow) {
  20236. ++week;
  20237. }
  20238. } else if (w.e != null) {
  20239. // local weekday -- counting starts from begining of week
  20240. weekday = w.e + dow;
  20241. } else {
  20242. // default to begining of week
  20243. weekday = dow;
  20244. }
  20245. }
  20246. temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow);
  20247. config._a[YEAR] = temp.year;
  20248. config._dayOfYear = temp.dayOfYear;
  20249. }
  20250. // convert an array to a date.
  20251. // the array should mirror the parameters below
  20252. // note: all values past the year are optional and will default to the lowest possible value.
  20253. // [year, month, day , hour, minute, second, millisecond]
  20254. function dateFromConfig(config) {
  20255. var i, date, input = [], currentDate, yearToUse;
  20256. if (config._d) {
  20257. return;
  20258. }
  20259. currentDate = currentDateArray(config);
  20260. //compute day of the year from weeks and weekdays
  20261. if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
  20262. dayOfYearFromWeekInfo(config);
  20263. }
  20264. //if the day of the year is set, figure out what it is
  20265. if (config._dayOfYear) {
  20266. yearToUse = dfl(config._a[YEAR], currentDate[YEAR]);
  20267. if (config._dayOfYear > daysInYear(yearToUse)) {
  20268. config._pf._overflowDayOfYear = true;
  20269. }
  20270. date = makeUTCDate(yearToUse, 0, config._dayOfYear);
  20271. config._a[MONTH] = date.getUTCMonth();
  20272. config._a[DATE] = date.getUTCDate();
  20273. }
  20274. // Default to current date.
  20275. // * if no year, month, day of month are given, default to today
  20276. // * if day of month is given, default month and year
  20277. // * if month is given, default only year
  20278. // * if year is given, don't default anything
  20279. for (i = 0; i < 3 && config._a[i] == null; ++i) {
  20280. config._a[i] = input[i] = currentDate[i];
  20281. }
  20282. // Zero out whatever was not defaulted, including time
  20283. for (; i < 7; i++) {
  20284. config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
  20285. }
  20286. config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input);
  20287. // Apply timezone offset from input. The actual zone can be changed
  20288. // with parseZone.
  20289. if (config._tzm != null) {
  20290. config._d.setUTCMinutes(config._d.getUTCMinutes() + config._tzm);
  20291. }
  20292. }
  20293. function dateFromObject(config) {
  20294. var normalizedInput;
  20295. if (config._d) {
  20296. return;
  20297. }
  20298. normalizedInput = normalizeObjectUnits(config._i);
  20299. config._a = [
  20300. normalizedInput.year,
  20301. normalizedInput.month,
  20302. normalizedInput.day,
  20303. normalizedInput.hour,
  20304. normalizedInput.minute,
  20305. normalizedInput.second,
  20306. normalizedInput.millisecond
  20307. ];
  20308. dateFromConfig(config);
  20309. }
  20310. function currentDateArray(config) {
  20311. var now = new Date();
  20312. if (config._useUTC) {
  20313. return [
  20314. now.getUTCFullYear(),
  20315. now.getUTCMonth(),
  20316. now.getUTCDate()
  20317. ];
  20318. } else {
  20319. return [now.getFullYear(), now.getMonth(), now.getDate()];
  20320. }
  20321. }
  20322. // date from string and format string
  20323. function makeDateFromStringAndFormat(config) {
  20324. if (config._f === moment.ISO_8601) {
  20325. parseISO(config);
  20326. return;
  20327. }
  20328. config._a = [];
  20329. config._pf.empty = true;
  20330. // This array is used to make a Date, either with `new Date` or `Date.UTC`
  20331. var string = '' + config._i,
  20332. i, parsedInput, tokens, token, skipped,
  20333. stringLength = string.length,
  20334. totalParsedInputLength = 0;
  20335. tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
  20336. for (i = 0; i < tokens.length; i++) {
  20337. token = tokens[i];
  20338. parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
  20339. if (parsedInput) {
  20340. skipped = string.substr(0, string.indexOf(parsedInput));
  20341. if (skipped.length > 0) {
  20342. config._pf.unusedInput.push(skipped);
  20343. }
  20344. string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
  20345. totalParsedInputLength += parsedInput.length;
  20346. }
  20347. // don't parse if it's not a known token
  20348. if (formatTokenFunctions[token]) {
  20349. if (parsedInput) {
  20350. config._pf.empty = false;
  20351. }
  20352. else {
  20353. config._pf.unusedTokens.push(token);
  20354. }
  20355. addTimeToArrayFromToken(token, parsedInput, config);
  20356. }
  20357. else if (config._strict && !parsedInput) {
  20358. config._pf.unusedTokens.push(token);
  20359. }
  20360. }
  20361. // add remaining unparsed input length to the string
  20362. config._pf.charsLeftOver = stringLength - totalParsedInputLength;
  20363. if (string.length > 0) {
  20364. config._pf.unusedInput.push(string);
  20365. }
  20366. // handle am pm
  20367. if (config._isPm && config._a[HOUR] < 12) {
  20368. config._a[HOUR] += 12;
  20369. }
  20370. // if is 12 am, change hours to 0
  20371. if (config._isPm === false && config._a[HOUR] === 12) {
  20372. config._a[HOUR] = 0;
  20373. }
  20374. dateFromConfig(config);
  20375. checkOverflow(config);
  20376. }
  20377. function unescapeFormat(s) {
  20378. return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
  20379. return p1 || p2 || p3 || p4;
  20380. });
  20381. }
  20382. // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
  20383. function regexpEscape(s) {
  20384. return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
  20385. }
  20386. // date from string and array of format strings
  20387. function makeDateFromStringAndArray(config) {
  20388. var tempConfig,
  20389. bestMoment,
  20390. scoreToBeat,
  20391. i,
  20392. currentScore;
  20393. if (config._f.length === 0) {
  20394. config._pf.invalidFormat = true;
  20395. config._d = new Date(NaN);
  20396. return;
  20397. }
  20398. for (i = 0; i < config._f.length; i++) {
  20399. currentScore = 0;
  20400. tempConfig = copyConfig({}, config);
  20401. tempConfig._pf = defaultParsingFlags();
  20402. tempConfig._f = config._f[i];
  20403. makeDateFromStringAndFormat(tempConfig);
  20404. if (!isValid(tempConfig)) {
  20405. continue;
  20406. }
  20407. // if there is any input that was not parsed add a penalty for that format
  20408. currentScore += tempConfig._pf.charsLeftOver;
  20409. //or tokens
  20410. currentScore += tempConfig._pf.unusedTokens.length * 10;
  20411. tempConfig._pf.score = currentScore;
  20412. if (scoreToBeat == null || currentScore < scoreToBeat) {
  20413. scoreToBeat = currentScore;
  20414. bestMoment = tempConfig;
  20415. }
  20416. }
  20417. extend(config, bestMoment || tempConfig);
  20418. }
  20419. // date from iso format
  20420. function parseISO(config) {
  20421. var i, l,
  20422. string = config._i,
  20423. match = isoRegex.exec(string);
  20424. if (match) {
  20425. config._pf.iso = true;
  20426. for (i = 0, l = isoDates.length; i < l; i++) {
  20427. if (isoDates[i][1].exec(string)) {
  20428. // match[5] should be 'T' or undefined
  20429. config._f = isoDates[i][0] + (match[6] || ' ');
  20430. break;
  20431. }
  20432. }
  20433. for (i = 0, l = isoTimes.length; i < l; i++) {
  20434. if (isoTimes[i][1].exec(string)) {
  20435. config._f += isoTimes[i][0];
  20436. break;
  20437. }
  20438. }
  20439. if (string.match(parseTokenTimezone)) {
  20440. config._f += 'Z';
  20441. }
  20442. makeDateFromStringAndFormat(config);
  20443. } else {
  20444. config._isValid = false;
  20445. }
  20446. }
  20447. // date from iso format or fallback
  20448. function makeDateFromString(config) {
  20449. parseISO(config);
  20450. if (config._isValid === false) {
  20451. delete config._isValid;
  20452. moment.createFromInputFallback(config);
  20453. }
  20454. }
  20455. function makeDateFromInput(config) {
  20456. var input = config._i, matched;
  20457. if (input === undefined) {
  20458. config._d = new Date();
  20459. } else if (isDate(input)) {
  20460. config._d = new Date(+input);
  20461. } else if ((matched = aspNetJsonRegex.exec(input)) !== null) {
  20462. config._d = new Date(+matched[1]);
  20463. } else if (typeof input === 'string') {
  20464. makeDateFromString(config);
  20465. } else if (isArray(input)) {
  20466. config._a = input.slice(0);
  20467. dateFromConfig(config);
  20468. } else if (typeof(input) === 'object') {
  20469. dateFromObject(config);
  20470. } else if (typeof(input) === 'number') {
  20471. // from milliseconds
  20472. config._d = new Date(input);
  20473. } else {
  20474. moment.createFromInputFallback(config);
  20475. }
  20476. }
  20477. function makeDate(y, m, d, h, M, s, ms) {
  20478. //can't just apply() to create a date:
  20479. //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
  20480. var date = new Date(y, m, d, h, M, s, ms);
  20481. //the date constructor doesn't accept years < 1970
  20482. if (y < 1970) {
  20483. date.setFullYear(y);
  20484. }
  20485. return date;
  20486. }
  20487. function makeUTCDate(y) {
  20488. var date = new Date(Date.UTC.apply(null, arguments));
  20489. if (y < 1970) {
  20490. date.setUTCFullYear(y);
  20491. }
  20492. return date;
  20493. }
  20494. function parseWeekday(input, locale) {
  20495. if (typeof input === 'string') {
  20496. if (!isNaN(input)) {
  20497. input = parseInt(input, 10);
  20498. }
  20499. else {
  20500. input = locale.weekdaysParse(input);
  20501. if (typeof input !== 'number') {
  20502. return null;
  20503. }
  20504. }
  20505. }
  20506. return input;
  20507. }
  20508. /************************************
  20509. Relative Time
  20510. ************************************/
  20511. // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
  20512. function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
  20513. return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
  20514. }
  20515. function relativeTime(posNegDuration, withoutSuffix, locale) {
  20516. var duration = moment.duration(posNegDuration).abs(),
  20517. seconds = round(duration.as('s')),
  20518. minutes = round(duration.as('m')),
  20519. hours = round(duration.as('h')),
  20520. days = round(duration.as('d')),
  20521. months = round(duration.as('M')),
  20522. years = round(duration.as('y')),
  20523. args = seconds < relativeTimeThresholds.s && ['s', seconds] ||
  20524. minutes === 1 && ['m'] ||
  20525. minutes < relativeTimeThresholds.m && ['mm', minutes] ||
  20526. hours === 1 && ['h'] ||
  20527. hours < relativeTimeThresholds.h && ['hh', hours] ||
  20528. days === 1 && ['d'] ||
  20529. days < relativeTimeThresholds.d && ['dd', days] ||
  20530. months === 1 && ['M'] ||
  20531. months < relativeTimeThresholds.M && ['MM', months] ||
  20532. years === 1 && ['y'] || ['yy', years];
  20533. args[2] = withoutSuffix;
  20534. args[3] = +posNegDuration > 0;
  20535. args[4] = locale;
  20536. return substituteTimeAgo.apply({}, args);
  20537. }
  20538. /************************************
  20539. Week of Year
  20540. ************************************/
  20541. // firstDayOfWeek 0 = sun, 6 = sat
  20542. // the day of the week that starts the week
  20543. // (usually sunday or monday)
  20544. // firstDayOfWeekOfYear 0 = sun, 6 = sat
  20545. // the first week is the week that contains the first
  20546. // of this day of the week
  20547. // (eg. ISO weeks use thursday (4))
  20548. function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
  20549. var end = firstDayOfWeekOfYear - firstDayOfWeek,
  20550. daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(),
  20551. adjustedMoment;
  20552. if (daysToDayOfWeek > end) {
  20553. daysToDayOfWeek -= 7;
  20554. }
  20555. if (daysToDayOfWeek < end - 7) {
  20556. daysToDayOfWeek += 7;
  20557. }
  20558. adjustedMoment = moment(mom).add(daysToDayOfWeek, 'd');
  20559. return {
  20560. week: Math.ceil(adjustedMoment.dayOfYear() / 7),
  20561. year: adjustedMoment.year()
  20562. };
  20563. }
  20564. //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
  20565. function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) {
  20566. var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear;
  20567. d = d === 0 ? 7 : d;
  20568. weekday = weekday != null ? weekday : firstDayOfWeek;
  20569. daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0);
  20570. dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1;
  20571. return {
  20572. year: dayOfYear > 0 ? year : year - 1,
  20573. dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear
  20574. };
  20575. }
  20576. /************************************
  20577. Top Level Functions
  20578. ************************************/
  20579. function makeMoment(config) {
  20580. var input = config._i,
  20581. format = config._f;
  20582. config._locale = config._locale || moment.localeData(config._l);
  20583. if (input === null || (format === undefined && input === '')) {
  20584. return moment.invalid({nullInput: true});
  20585. }
  20586. if (typeof input === 'string') {
  20587. config._i = input = config._locale.preparse(input);
  20588. }
  20589. if (moment.isMoment(input)) {
  20590. return new Moment(input, true);
  20591. } else if (format) {
  20592. if (isArray(format)) {
  20593. makeDateFromStringAndArray(config);
  20594. } else {
  20595. makeDateFromStringAndFormat(config);
  20596. }
  20597. } else {
  20598. makeDateFromInput(config);
  20599. }
  20600. return new Moment(config);
  20601. }
  20602. moment = function (input, format, locale, strict) {
  20603. var c;
  20604. if (typeof(locale) === 'boolean') {
  20605. strict = locale;
  20606. locale = undefined;
  20607. }
  20608. // object construction must be done this way.
  20609. // https://github.com/moment/moment/issues/1423
  20610. c = {};
  20611. c._isAMomentObject = true;
  20612. c._i = input;
  20613. c._f = format;
  20614. c._l = locale;
  20615. c._strict = strict;
  20616. c._isUTC = false;
  20617. c._pf = defaultParsingFlags();
  20618. return makeMoment(c);
  20619. };
  20620. moment.suppressDeprecationWarnings = false;
  20621. moment.createFromInputFallback = deprecate(
  20622. 'moment construction falls back to js Date. This is ' +
  20623. 'discouraged and will be removed in upcoming major ' +
  20624. 'release. Please refer to ' +
  20625. 'https://github.com/moment/moment/issues/1407 for more info.',
  20626. function (config) {
  20627. config._d = new Date(config._i);
  20628. }
  20629. );
  20630. // Pick a moment m from moments so that m[fn](other) is true for all
  20631. // other. This relies on the function fn to be transitive.
  20632. //
  20633. // moments should either be an array of moment objects or an array, whose
  20634. // first element is an array of moment objects.
  20635. function pickBy(fn, moments) {
  20636. var res, i;
  20637. if (moments.length === 1 && isArray(moments[0])) {
  20638. moments = moments[0];
  20639. }
  20640. if (!moments.length) {
  20641. return moment();
  20642. }
  20643. res = moments[0];
  20644. for (i = 1; i < moments.length; ++i) {
  20645. if (moments[i][fn](res)) {
  20646. res = moments[i];
  20647. }
  20648. }
  20649. return res;
  20650. }
  20651. moment.min = function () {
  20652. var args = [].slice.call(arguments, 0);
  20653. return pickBy('isBefore', args);
  20654. };
  20655. moment.max = function () {
  20656. var args = [].slice.call(arguments, 0);
  20657. return pickBy('isAfter', args);
  20658. };
  20659. // creating with utc
  20660. moment.utc = function (input, format, locale, strict) {
  20661. var c;
  20662. if (typeof(locale) === 'boolean') {
  20663. strict = locale;
  20664. locale = undefined;
  20665. }
  20666. // object construction must be done this way.
  20667. // https://github.com/moment/moment/issues/1423
  20668. c = {};
  20669. c._isAMomentObject = true;
  20670. c._useUTC = true;
  20671. c._isUTC = true;
  20672. c._l = locale;
  20673. c._i = input;
  20674. c._f = format;
  20675. c._strict = strict;
  20676. c._pf = defaultParsingFlags();
  20677. return makeMoment(c).utc();
  20678. };
  20679. // creating with unix timestamp (in seconds)
  20680. moment.unix = function (input) {
  20681. return moment(input * 1000);
  20682. };
  20683. // duration
  20684. moment.duration = function (input, key) {
  20685. var duration = input,
  20686. // matching against regexp is expensive, do it on demand
  20687. match = null,
  20688. sign,
  20689. ret,
  20690. parseIso,
  20691. diffRes;
  20692. if (moment.isDuration(input)) {
  20693. duration = {
  20694. ms: input._milliseconds,
  20695. d: input._days,
  20696. M: input._months
  20697. };
  20698. } else if (typeof input === 'number') {
  20699. duration = {};
  20700. if (key) {
  20701. duration[key] = input;
  20702. } else {
  20703. duration.milliseconds = input;
  20704. }
  20705. } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) {
  20706. sign = (match[1] === '-') ? -1 : 1;
  20707. duration = {
  20708. y: 0,
  20709. d: toInt(match[DATE]) * sign,
  20710. h: toInt(match[HOUR]) * sign,
  20711. m: toInt(match[MINUTE]) * sign,
  20712. s: toInt(match[SECOND]) * sign,
  20713. ms: toInt(match[MILLISECOND]) * sign
  20714. };
  20715. } else if (!!(match = isoDurationRegex.exec(input))) {
  20716. sign = (match[1] === '-') ? -1 : 1;
  20717. parseIso = function (inp) {
  20718. // We'd normally use ~~inp for this, but unfortunately it also
  20719. // converts floats to ints.
  20720. // inp may be undefined, so careful calling replace on it.
  20721. var res = inp && parseFloat(inp.replace(',', '.'));
  20722. // apply sign while we're at it
  20723. return (isNaN(res) ? 0 : res) * sign;
  20724. };
  20725. duration = {
  20726. y: parseIso(match[2]),
  20727. M: parseIso(match[3]),
  20728. d: parseIso(match[4]),
  20729. h: parseIso(match[5]),
  20730. m: parseIso(match[6]),
  20731. s: parseIso(match[7]),
  20732. w: parseIso(match[8])
  20733. };
  20734. } else if (typeof duration === 'object' &&
  20735. ('from' in duration || 'to' in duration)) {
  20736. diffRes = momentsDifference(moment(duration.from), moment(duration.to));
  20737. duration = {};
  20738. duration.ms = diffRes.milliseconds;
  20739. duration.M = diffRes.months;
  20740. }
  20741. ret = new Duration(duration);
  20742. if (moment.isDuration(input) && hasOwnProp(input, '_locale')) {
  20743. ret._locale = input._locale;
  20744. }
  20745. return ret;
  20746. };
  20747. // version number
  20748. moment.version = VERSION;
  20749. // default format
  20750. moment.defaultFormat = isoFormat;
  20751. // constant that refers to the ISO standard
  20752. moment.ISO_8601 = function () {};
  20753. // Plugins that add properties should also add the key here (null value),
  20754. // so we can properly clone ourselves.
  20755. moment.momentProperties = momentProperties;
  20756. // This function will be called whenever a moment is mutated.
  20757. // It is intended to keep the offset in sync with the timezone.
  20758. moment.updateOffset = function () {};
  20759. // This function allows you to set a threshold for relative time strings
  20760. moment.relativeTimeThreshold = function (threshold, limit) {
  20761. if (relativeTimeThresholds[threshold] === undefined) {
  20762. return false;
  20763. }
  20764. if (limit === undefined) {
  20765. return relativeTimeThresholds[threshold];
  20766. }
  20767. relativeTimeThresholds[threshold] = limit;
  20768. return true;
  20769. };
  20770. moment.lang = deprecate(
  20771. 'moment.lang is deprecated. Use moment.locale instead.',
  20772. function (key, value) {
  20773. return moment.locale(key, value);
  20774. }
  20775. );
  20776. // This function will load locale and then set the global locale. If
  20777. // no arguments are passed in, it will simply return the current global
  20778. // locale key.
  20779. moment.locale = function (key, values) {
  20780. var data;
  20781. if (key) {
  20782. if (typeof(values) !== 'undefined') {
  20783. data = moment.defineLocale(key, values);
  20784. }
  20785. else {
  20786. data = moment.localeData(key);
  20787. }
  20788. if (data) {
  20789. moment.duration._locale = moment._locale = data;
  20790. }
  20791. }
  20792. return moment._locale._abbr;
  20793. };
  20794. moment.defineLocale = function (name, values) {
  20795. if (values !== null) {
  20796. values.abbr = name;
  20797. if (!locales[name]) {
  20798. locales[name] = new Locale();
  20799. }
  20800. locales[name].set(values);
  20801. // backwards compat for now: also set the locale
  20802. moment.locale(name);
  20803. return locales[name];
  20804. } else {
  20805. // useful for testing
  20806. delete locales[name];
  20807. return null;
  20808. }
  20809. };
  20810. moment.langData = deprecate(
  20811. 'moment.langData is deprecated. Use moment.localeData instead.',
  20812. function (key) {
  20813. return moment.localeData(key);
  20814. }
  20815. );
  20816. // returns locale data
  20817. moment.localeData = function (key) {
  20818. var locale;
  20819. if (key && key._locale && key._locale._abbr) {
  20820. key = key._locale._abbr;
  20821. }
  20822. if (!key) {
  20823. return moment._locale;
  20824. }
  20825. if (!isArray(key)) {
  20826. //short-circuit everything else
  20827. locale = loadLocale(key);
  20828. if (locale) {
  20829. return locale;
  20830. }
  20831. key = [key];
  20832. }
  20833. return chooseLocale(key);
  20834. };
  20835. // compare moment object
  20836. moment.isMoment = function (obj) {
  20837. return obj instanceof Moment ||
  20838. (obj != null && hasOwnProp(obj, '_isAMomentObject'));
  20839. };
  20840. // for typechecking Duration objects
  20841. moment.isDuration = function (obj) {
  20842. return obj instanceof Duration;
  20843. };
  20844. for (i = lists.length - 1; i >= 0; --i) {
  20845. makeList(lists[i]);
  20846. }
  20847. moment.normalizeUnits = function (units) {
  20848. return normalizeUnits(units);
  20849. };
  20850. moment.invalid = function (flags) {
  20851. var m = moment.utc(NaN);
  20852. if (flags != null) {
  20853. extend(m._pf, flags);
  20854. }
  20855. else {
  20856. m._pf.userInvalidated = true;
  20857. }
  20858. return m;
  20859. };
  20860. moment.parseZone = function () {
  20861. return moment.apply(null, arguments).parseZone();
  20862. };
  20863. moment.parseTwoDigitYear = function (input) {
  20864. return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
  20865. };
  20866. /************************************
  20867. Moment Prototype
  20868. ************************************/
  20869. extend(moment.fn = Moment.prototype, {
  20870. clone : function () {
  20871. return moment(this);
  20872. },
  20873. valueOf : function () {
  20874. return +this._d + ((this._offset || 0) * 60000);
  20875. },
  20876. unix : function () {
  20877. return Math.floor(+this / 1000);
  20878. },
  20879. toString : function () {
  20880. return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
  20881. },
  20882. toDate : function () {
  20883. return this._offset ? new Date(+this) : this._d;
  20884. },
  20885. toISOString : function () {
  20886. var m = moment(this).utc();
  20887. if (0 < m.year() && m.year() <= 9999) {
  20888. return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  20889. } else {
  20890. return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  20891. }
  20892. },
  20893. toArray : function () {
  20894. var m = this;
  20895. return [
  20896. m.year(),
  20897. m.month(),
  20898. m.date(),
  20899. m.hours(),
  20900. m.minutes(),
  20901. m.seconds(),
  20902. m.milliseconds()
  20903. ];
  20904. },
  20905. isValid : function () {
  20906. return isValid(this);
  20907. },
  20908. isDSTShifted : function () {
  20909. if (this._a) {
  20910. return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0;
  20911. }
  20912. return false;
  20913. },
  20914. parsingFlags : function () {
  20915. return extend({}, this._pf);
  20916. },
  20917. invalidAt: function () {
  20918. return this._pf.overflow;
  20919. },
  20920. utc : function (keepLocalTime) {
  20921. return this.zone(0, keepLocalTime);
  20922. },
  20923. local : function (keepLocalTime) {
  20924. if (this._isUTC) {
  20925. this.zone(0, keepLocalTime);
  20926. this._isUTC = false;
  20927. if (keepLocalTime) {
  20928. this.add(this._d.getTimezoneOffset(), 'm');
  20929. }
  20930. }
  20931. return this;
  20932. },
  20933. format : function (inputString) {
  20934. var output = formatMoment(this, inputString || moment.defaultFormat);
  20935. return this.localeData().postformat(output);
  20936. },
  20937. add : createAdder(1, 'add'),
  20938. subtract : createAdder(-1, 'subtract'),
  20939. diff : function (input, units, asFloat) {
  20940. var that = makeAs(input, this),
  20941. zoneDiff = (this.zone() - that.zone()) * 6e4,
  20942. diff, output;
  20943. units = normalizeUnits(units);
  20944. if (units === 'year' || units === 'month') {
  20945. // average number of days in the months in the given dates
  20946. diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2
  20947. // difference in months
  20948. output = ((this.year() - that.year()) * 12) + (this.month() - that.month());
  20949. // adjust by taking difference in days, average number of days
  20950. // and dst in the given months.
  20951. output += ((this - moment(this).startOf('month')) -
  20952. (that - moment(that).startOf('month'))) / diff;
  20953. // same as above but with zones, to negate all dst
  20954. output -= ((this.zone() - moment(this).startOf('month').zone()) -
  20955. (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff;
  20956. if (units === 'year') {
  20957. output = output / 12;
  20958. }
  20959. } else {
  20960. diff = (this - that);
  20961. output = units === 'second' ? diff / 1e3 : // 1000
  20962. units === 'minute' ? diff / 6e4 : // 1000 * 60
  20963. units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
  20964. units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
  20965. units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
  20966. diff;
  20967. }
  20968. return asFloat ? output : absRound(output);
  20969. },
  20970. from : function (time, withoutSuffix) {
  20971. return moment.duration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
  20972. },
  20973. fromNow : function (withoutSuffix) {
  20974. return this.from(moment(), withoutSuffix);
  20975. },
  20976. calendar : function (time) {
  20977. // We want to compare the start of today, vs this.
  20978. // Getting start-of-today depends on whether we're zone'd or not.
  20979. var now = time || moment(),
  20980. sod = makeAs(now, this).startOf('day'),
  20981. diff = this.diff(sod, 'days', true),
  20982. format = diff < -6 ? 'sameElse' :
  20983. diff < -1 ? 'lastWeek' :
  20984. diff < 0 ? 'lastDay' :
  20985. diff < 1 ? 'sameDay' :
  20986. diff < 2 ? 'nextDay' :
  20987. diff < 7 ? 'nextWeek' : 'sameElse';
  20988. return this.format(this.localeData().calendar(format, this));
  20989. },
  20990. isLeapYear : function () {
  20991. return isLeapYear(this.year());
  20992. },
  20993. isDST : function () {
  20994. return (this.zone() < this.clone().month(0).zone() ||
  20995. this.zone() < this.clone().month(5).zone());
  20996. },
  20997. day : function (input) {
  20998. var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
  20999. if (input != null) {
  21000. input = parseWeekday(input, this.localeData());
  21001. return this.add(input - day, 'd');
  21002. } else {
  21003. return day;
  21004. }
  21005. },
  21006. month : makeAccessor('Month', true),
  21007. startOf : function (units) {
  21008. units = normalizeUnits(units);
  21009. // the following switch intentionally omits break keywords
  21010. // to utilize falling through the cases.
  21011. switch (units) {
  21012. case 'year':
  21013. this.month(0);
  21014. /* falls through */
  21015. case 'quarter':
  21016. case 'month':
  21017. this.date(1);
  21018. /* falls through */
  21019. case 'week':
  21020. case 'isoWeek':
  21021. case 'day':
  21022. this.hours(0);
  21023. /* falls through */
  21024. case 'hour':
  21025. this.minutes(0);
  21026. /* falls through */
  21027. case 'minute':
  21028. this.seconds(0);
  21029. /* falls through */
  21030. case 'second':
  21031. this.milliseconds(0);
  21032. /* falls through */
  21033. }
  21034. // weeks are a special case
  21035. if (units === 'week') {
  21036. this.weekday(0);
  21037. } else if (units === 'isoWeek') {
  21038. this.isoWeekday(1);
  21039. }
  21040. // quarters are also special
  21041. if (units === 'quarter') {
  21042. this.month(Math.floor(this.month() / 3) * 3);
  21043. }
  21044. return this;
  21045. },
  21046. endOf: function (units) {
  21047. units = normalizeUnits(units);
  21048. return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
  21049. },
  21050. isAfter: function (input, units) {
  21051. units = typeof units !== 'undefined' ? units : 'millisecond';
  21052. return +this.clone().startOf(units) > +moment(input).startOf(units);
  21053. },
  21054. isBefore: function (input, units) {
  21055. units = typeof units !== 'undefined' ? units : 'millisecond';
  21056. return +this.clone().startOf(units) < +moment(input).startOf(units);
  21057. },
  21058. isSame: function (input, units) {
  21059. units = units || 'ms';
  21060. return +this.clone().startOf(units) === +makeAs(input, this).startOf(units);
  21061. },
  21062. min: deprecate(
  21063. 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548',
  21064. function (other) {
  21065. other = moment.apply(null, arguments);
  21066. return other < this ? this : other;
  21067. }
  21068. ),
  21069. max: deprecate(
  21070. 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548',
  21071. function (other) {
  21072. other = moment.apply(null, arguments);
  21073. return other > this ? this : other;
  21074. }
  21075. ),
  21076. // keepLocalTime = true means only change the timezone, without
  21077. // affecting the local hour. So 5:31:26 +0300 --[zone(2, true)]-->
  21078. // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist int zone
  21079. // +0200, so we adjust the time as needed, to be valid.
  21080. //
  21081. // Keeping the time actually adds/subtracts (one hour)
  21082. // from the actual represented time. That is why we call updateOffset
  21083. // a second time. In case it wants us to change the offset again
  21084. // _changeInProgress == true case, then we have to adjust, because
  21085. // there is no such time in the given timezone.
  21086. zone : function (input, keepLocalTime) {
  21087. var offset = this._offset || 0,
  21088. localAdjust;
  21089. if (input != null) {
  21090. if (typeof input === 'string') {
  21091. input = timezoneMinutesFromString(input);
  21092. }
  21093. if (Math.abs(input) < 16) {
  21094. input = input * 60;
  21095. }
  21096. if (!this._isUTC && keepLocalTime) {
  21097. localAdjust = this._d.getTimezoneOffset();
  21098. }
  21099. this._offset = input;
  21100. this._isUTC = true;
  21101. if (localAdjust != null) {
  21102. this.subtract(localAdjust, 'm');
  21103. }
  21104. if (offset !== input) {
  21105. if (!keepLocalTime || this._changeInProgress) {
  21106. addOrSubtractDurationFromMoment(this,
  21107. moment.duration(offset - input, 'm'), 1, false);
  21108. } else if (!this._changeInProgress) {
  21109. this._changeInProgress = true;
  21110. moment.updateOffset(this, true);
  21111. this._changeInProgress = null;
  21112. }
  21113. }
  21114. } else {
  21115. return this._isUTC ? offset : this._d.getTimezoneOffset();
  21116. }
  21117. return this;
  21118. },
  21119. zoneAbbr : function () {
  21120. return this._isUTC ? 'UTC' : '';
  21121. },
  21122. zoneName : function () {
  21123. return this._isUTC ? 'Coordinated Universal Time' : '';
  21124. },
  21125. parseZone : function () {
  21126. if (this._tzm) {
  21127. this.zone(this._tzm);
  21128. } else if (typeof this._i === 'string') {
  21129. this.zone(this._i);
  21130. }
  21131. return this;
  21132. },
  21133. hasAlignedHourOffset : function (input) {
  21134. if (!input) {
  21135. input = 0;
  21136. }
  21137. else {
  21138. input = moment(input).zone();
  21139. }
  21140. return (this.zone() - input) % 60 === 0;
  21141. },
  21142. daysInMonth : function () {
  21143. return daysInMonth(this.year(), this.month());
  21144. },
  21145. dayOfYear : function (input) {
  21146. var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1;
  21147. return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
  21148. },
  21149. quarter : function (input) {
  21150. return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
  21151. },
  21152. weekYear : function (input) {
  21153. var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year;
  21154. return input == null ? year : this.add((input - year), 'y');
  21155. },
  21156. isoWeekYear : function (input) {
  21157. var year = weekOfYear(this, 1, 4).year;
  21158. return input == null ? year : this.add((input - year), 'y');
  21159. },
  21160. week : function (input) {
  21161. var week = this.localeData().week(this);
  21162. return input == null ? week : this.add((input - week) * 7, 'd');
  21163. },
  21164. isoWeek : function (input) {
  21165. var week = weekOfYear(this, 1, 4).week;
  21166. return input == null ? week : this.add((input - week) * 7, 'd');
  21167. },
  21168. weekday : function (input) {
  21169. var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
  21170. return input == null ? weekday : this.add(input - weekday, 'd');
  21171. },
  21172. isoWeekday : function (input) {
  21173. // behaves the same as moment#day except
  21174. // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
  21175. // as a setter, sunday should belong to the previous week.
  21176. return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
  21177. },
  21178. isoWeeksInYear : function () {
  21179. return weeksInYear(this.year(), 1, 4);
  21180. },
  21181. weeksInYear : function () {
  21182. var weekInfo = this.localeData()._week;
  21183. return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
  21184. },
  21185. get : function (units) {
  21186. units = normalizeUnits(units);
  21187. return this[units]();
  21188. },
  21189. set : function (units, value) {
  21190. units = normalizeUnits(units);
  21191. if (typeof this[units] === 'function') {
  21192. this[units](value);
  21193. }
  21194. return this;
  21195. },
  21196. // If passed a locale key, it will set the locale for this
  21197. // instance. Otherwise, it will return the locale configuration
  21198. // variables for this instance.
  21199. locale : function (key) {
  21200. if (key === undefined) {
  21201. return this._locale._abbr;
  21202. } else {
  21203. this._locale = moment.localeData(key);
  21204. return this;
  21205. }
  21206. },
  21207. lang : deprecate(
  21208. 'moment().lang() is deprecated. Use moment().localeData() instead.',
  21209. function (key) {
  21210. if (key === undefined) {
  21211. return this.localeData();
  21212. } else {
  21213. this._locale = moment.localeData(key);
  21214. return this;
  21215. }
  21216. }
  21217. ),
  21218. localeData : function () {
  21219. return this._locale;
  21220. }
  21221. });
  21222. function rawMonthSetter(mom, value) {
  21223. var dayOfMonth;
  21224. // TODO: Move this out of here!
  21225. if (typeof value === 'string') {
  21226. value = mom.localeData().monthsParse(value);
  21227. // TODO: Another silent failure?
  21228. if (typeof value !== 'number') {
  21229. return mom;
  21230. }
  21231. }
  21232. dayOfMonth = Math.min(mom.date(),
  21233. daysInMonth(mom.year(), value));
  21234. mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
  21235. return mom;
  21236. }
  21237. function rawGetter(mom, unit) {
  21238. return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]();
  21239. }
  21240. function rawSetter(mom, unit, value) {
  21241. if (unit === 'Month') {
  21242. return rawMonthSetter(mom, value);
  21243. } else {
  21244. return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
  21245. }
  21246. }
  21247. function makeAccessor(unit, keepTime) {
  21248. return function (value) {
  21249. if (value != null) {
  21250. rawSetter(this, unit, value);
  21251. moment.updateOffset(this, keepTime);
  21252. return this;
  21253. } else {
  21254. return rawGetter(this, unit);
  21255. }
  21256. };
  21257. }
  21258. moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false);
  21259. moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false);
  21260. moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false);
  21261. // Setting the hour should keep the time, because the user explicitly
  21262. // specified which hour he wants. So trying to maintain the same hour (in
  21263. // a new timezone) makes sense. Adding/subtracting hours does not follow
  21264. // this rule.
  21265. moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true);
  21266. // moment.fn.month is defined separately
  21267. moment.fn.date = makeAccessor('Date', true);
  21268. moment.fn.dates = deprecate('dates accessor is deprecated. Use date instead.', makeAccessor('Date', true));
  21269. moment.fn.year = makeAccessor('FullYear', true);
  21270. moment.fn.years = deprecate('years accessor is deprecated. Use year instead.', makeAccessor('FullYear', true));
  21271. // add plural methods
  21272. moment.fn.days = moment.fn.day;
  21273. moment.fn.months = moment.fn.month;
  21274. moment.fn.weeks = moment.fn.week;
  21275. moment.fn.isoWeeks = moment.fn.isoWeek;
  21276. moment.fn.quarters = moment.fn.quarter;
  21277. // add aliased format methods
  21278. moment.fn.toJSON = moment.fn.toISOString;
  21279. /************************************
  21280. Duration Prototype
  21281. ************************************/
  21282. function daysToYears (days) {
  21283. // 400 years have 146097 days (taking into account leap year rules)
  21284. return days * 400 / 146097;
  21285. }
  21286. function yearsToDays (years) {
  21287. // years * 365 + absRound(years / 4) -
  21288. // absRound(years / 100) + absRound(years / 400);
  21289. return years * 146097 / 400;
  21290. }
  21291. extend(moment.duration.fn = Duration.prototype, {
  21292. _bubble : function () {
  21293. var milliseconds = this._milliseconds,
  21294. days = this._days,
  21295. months = this._months,
  21296. data = this._data,
  21297. seconds, minutes, hours, years = 0;
  21298. // The following code bubbles up values, see the tests for
  21299. // examples of what that means.
  21300. data.milliseconds = milliseconds % 1000;
  21301. seconds = absRound(milliseconds / 1000);
  21302. data.seconds = seconds % 60;
  21303. minutes = absRound(seconds / 60);
  21304. data.minutes = minutes % 60;
  21305. hours = absRound(minutes / 60);
  21306. data.hours = hours % 24;
  21307. days += absRound(hours / 24);
  21308. // Accurately convert days to years, assume start from year 0.
  21309. years = absRound(daysToYears(days));
  21310. days -= absRound(yearsToDays(years));
  21311. // 30 days to a month
  21312. // TODO (iskren): Use anchor date (like 1st Jan) to compute this.
  21313. months += absRound(days / 30);
  21314. days %= 30;
  21315. // 12 months -> 1 year
  21316. years += absRound(months / 12);
  21317. months %= 12;
  21318. data.days = days;
  21319. data.months = months;
  21320. data.years = years;
  21321. },
  21322. abs : function () {
  21323. this._milliseconds = Math.abs(this._milliseconds);
  21324. this._days = Math.abs(this._days);
  21325. this._months = Math.abs(this._months);
  21326. this._data.milliseconds = Math.abs(this._data.milliseconds);
  21327. this._data.seconds = Math.abs(this._data.seconds);
  21328. this._data.minutes = Math.abs(this._data.minutes);
  21329. this._data.hours = Math.abs(this._data.hours);
  21330. this._data.months = Math.abs(this._data.months);
  21331. this._data.years = Math.abs(this._data.years);
  21332. return this;
  21333. },
  21334. weeks : function () {
  21335. return absRound(this.days() / 7);
  21336. },
  21337. valueOf : function () {
  21338. return this._milliseconds +
  21339. this._days * 864e5 +
  21340. (this._months % 12) * 2592e6 +
  21341. toInt(this._months / 12) * 31536e6;
  21342. },
  21343. humanize : function (withSuffix) {
  21344. var output = relativeTime(this, !withSuffix, this.localeData());
  21345. if (withSuffix) {
  21346. output = this.localeData().pastFuture(+this, output);
  21347. }
  21348. return this.localeData().postformat(output);
  21349. },
  21350. add : function (input, val) {
  21351. // supports only 2.0-style add(1, 's') or add(moment)
  21352. var dur = moment.duration(input, val);
  21353. this._milliseconds += dur._milliseconds;
  21354. this._days += dur._days;
  21355. this._months += dur._months;
  21356. this._bubble();
  21357. return this;
  21358. },
  21359. subtract : function (input, val) {
  21360. var dur = moment.duration(input, val);
  21361. this._milliseconds -= dur._milliseconds;
  21362. this._days -= dur._days;
  21363. this._months -= dur._months;
  21364. this._bubble();
  21365. return this;
  21366. },
  21367. get : function (units) {
  21368. units = normalizeUnits(units);
  21369. return this[units.toLowerCase() + 's']();
  21370. },
  21371. as : function (units) {
  21372. var days, months;
  21373. units = normalizeUnits(units);
  21374. days = this._days + this._milliseconds / 864e5;
  21375. if (units === 'month' || units === 'year') {
  21376. months = this._months + daysToYears(days) * 12;
  21377. return units === 'month' ? months : months / 12;
  21378. } else {
  21379. days += yearsToDays(this._months / 12);
  21380. switch (units) {
  21381. case 'week': return days / 7;
  21382. case 'day': return days;
  21383. case 'hour': return days * 24;
  21384. case 'minute': return days * 24 * 60;
  21385. case 'second': return days * 24 * 60 * 60;
  21386. case 'millisecond': return days * 24 * 60 * 60 * 1000;
  21387. default: throw new Error('Unknown unit ' + units);
  21388. }
  21389. }
  21390. },
  21391. lang : moment.fn.lang,
  21392. locale : moment.fn.locale,
  21393. toIsoString : deprecate(
  21394. 'toIsoString() is deprecated. Please use toISOString() instead ' +
  21395. '(notice the capitals)',
  21396. function () {
  21397. return this.toISOString();
  21398. }
  21399. ),
  21400. toISOString : function () {
  21401. // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
  21402. var years = Math.abs(this.years()),
  21403. months = Math.abs(this.months()),
  21404. days = Math.abs(this.days()),
  21405. hours = Math.abs(this.hours()),
  21406. minutes = Math.abs(this.minutes()),
  21407. seconds = Math.abs(this.seconds() + this.milliseconds() / 1000);
  21408. if (!this.asSeconds()) {
  21409. // this is the same as C#'s (Noda) and python (isodate)...
  21410. // but not other JS (goog.date)
  21411. return 'P0D';
  21412. }
  21413. return (this.asSeconds() < 0 ? '-' : '') +
  21414. 'P' +
  21415. (years ? years + 'Y' : '') +
  21416. (months ? months + 'M' : '') +
  21417. (days ? days + 'D' : '') +
  21418. ((hours || minutes || seconds) ? 'T' : '') +
  21419. (hours ? hours + 'H' : '') +
  21420. (minutes ? minutes + 'M' : '') +
  21421. (seconds ? seconds + 'S' : '');
  21422. },
  21423. localeData : function () {
  21424. return this._locale;
  21425. }
  21426. });
  21427. moment.duration.fn.toString = moment.duration.fn.toISOString;
  21428. function makeDurationGetter(name) {
  21429. moment.duration.fn[name] = function () {
  21430. return this._data[name];
  21431. };
  21432. }
  21433. for (i in unitMillisecondFactors) {
  21434. if (hasOwnProp(unitMillisecondFactors, i)) {
  21435. makeDurationGetter(i.toLowerCase());
  21436. }
  21437. }
  21438. moment.duration.fn.asMilliseconds = function () {
  21439. return this.as('ms');
  21440. };
  21441. moment.duration.fn.asSeconds = function () {
  21442. return this.as('s');
  21443. };
  21444. moment.duration.fn.asMinutes = function () {
  21445. return this.as('m');
  21446. };
  21447. moment.duration.fn.asHours = function () {
  21448. return this.as('h');
  21449. };
  21450. moment.duration.fn.asDays = function () {
  21451. return this.as('d');
  21452. };
  21453. moment.duration.fn.asWeeks = function () {
  21454. return this.as('weeks');
  21455. };
  21456. moment.duration.fn.asMonths = function () {
  21457. return this.as('M');
  21458. };
  21459. moment.duration.fn.asYears = function () {
  21460. return this.as('y');
  21461. };
  21462. /************************************
  21463. Default Locale
  21464. ************************************/
  21465. // Set default locale, other locale will inherit from English.
  21466. moment.locale('en', {
  21467. ordinal : function (number) {
  21468. var b = number % 10,
  21469. output = (toInt(number % 100 / 10) === 1) ? 'th' :
  21470. (b === 1) ? 'st' :
  21471. (b === 2) ? 'nd' :
  21472. (b === 3) ? 'rd' : 'th';
  21473. return number + output;
  21474. }
  21475. });
  21476. /* EMBED_LOCALES */
  21477. /************************************
  21478. Exposing Moment
  21479. ************************************/
  21480. function makeGlobal(shouldDeprecate) {
  21481. /*global ender:false */
  21482. if (typeof ender !== 'undefined') {
  21483. return;
  21484. }
  21485. oldGlobalMoment = globalScope.moment;
  21486. if (shouldDeprecate) {
  21487. globalScope.moment = deprecate(
  21488. 'Accessing Moment through the global scope is ' +
  21489. 'deprecated, and will be removed in an upcoming ' +
  21490. 'release.',
  21491. moment);
  21492. } else {
  21493. globalScope.moment = moment;
  21494. }
  21495. }
  21496. // CommonJS module is defined
  21497. if (hasModule) {
  21498. module.exports = moment;
  21499. } else if (true) {
  21500. !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
  21501. if (module.config && module.config() && module.config().noGlobal === true) {
  21502. // release the global variable
  21503. globalScope.moment = oldGlobalMoment;
  21504. }
  21505. return moment;
  21506. }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  21507. makeGlobal(true);
  21508. } else {
  21509. makeGlobal();
  21510. }
  21511. }).call(this);
  21512. /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()), __webpack_require__(64)(module)))
  21513. /***/ },
  21514. /* 52 */
  21515. /***/ function(module, exports, __webpack_require__) {
  21516. var __WEBPACK_AMD_DEFINE_RESULT__;/*! Hammer.JS - v1.1.3 - 2014-05-20
  21517. * http://eightmedia.github.io/hammer.js
  21518. *
  21519. * Copyright (c) 2014 Jorik Tangelder <j.tangelder@gmail.com>;
  21520. * Licensed under the MIT license */
  21521. (function(window, undefined) {
  21522. 'use strict';
  21523. /**
  21524. * @main
  21525. * @module hammer
  21526. *
  21527. * @class Hammer
  21528. * @static
  21529. */
  21530. /**
  21531. * Hammer, use this to create instances
  21532. * ````
  21533. * var hammertime = new Hammer(myElement);
  21534. * ````
  21535. *
  21536. * @method Hammer
  21537. * @param {HTMLElement} element
  21538. * @param {Object} [options={}]
  21539. * @return {Hammer.Instance}
  21540. */
  21541. var Hammer = function Hammer(element, options) {
  21542. return new Hammer.Instance(element, options || {});
  21543. };
  21544. /**
  21545. * version, as defined in package.json
  21546. * the value will be set at each build
  21547. * @property VERSION
  21548. * @final
  21549. * @type {String}
  21550. */
  21551. Hammer.VERSION = '1.1.3';
  21552. /**
  21553. * default settings.
  21554. * more settings are defined per gesture at `/gestures`. Each gesture can be disabled/enabled
  21555. * by setting it's name (like `swipe`) to false.
  21556. * You can set the defaults for all instances by changing this object before creating an instance.
  21557. * @example
  21558. * ````
  21559. * Hammer.defaults.drag = false;
  21560. * Hammer.defaults.behavior.touchAction = 'pan-y';
  21561. * delete Hammer.defaults.behavior.userSelect;
  21562. * ````
  21563. * @property defaults
  21564. * @type {Object}
  21565. */
  21566. Hammer.defaults = {
  21567. /**
  21568. * this setting object adds styles and attributes to the element to prevent the browser from doing
  21569. * its native behavior. The css properties are auto prefixed for the browsers when needed.
  21570. * @property defaults.behavior
  21571. * @type {Object}
  21572. */
  21573. behavior: {
  21574. /**
  21575. * Disables text selection to improve the dragging gesture. When the value is `none` it also sets
  21576. * `onselectstart=false` for IE on the element. Mainly for desktop browsers.
  21577. * @property defaults.behavior.userSelect
  21578. * @type {String}
  21579. * @default 'none'
  21580. */
  21581. userSelect: 'none',
  21582. /**
  21583. * Specifies whether and how a given region can be manipulated by the user (for instance, by panning or zooming).
  21584. * Used by Chrome 35> and IE10>. By default this makes the element blocking any touch event.
  21585. * @property defaults.behavior.touchAction
  21586. * @type {String}
  21587. * @default: 'pan-y'
  21588. */
  21589. touchAction: 'pan-y',
  21590. /**
  21591. * Disables the default callout shown when you touch and hold a touch target.
  21592. * On iOS, when you touch and hold a touch target such as a link, Safari displays
  21593. * a callout containing information about the link. This property allows you to disable that callout.
  21594. * @property defaults.behavior.touchCallout
  21595. * @type {String}
  21596. * @default 'none'
  21597. */
  21598. touchCallout: 'none',
  21599. /**
  21600. * Specifies whether zooming is enabled. Used by IE10>
  21601. * @property defaults.behavior.contentZooming
  21602. * @type {String}
  21603. * @default 'none'
  21604. */
  21605. contentZooming: 'none',
  21606. /**
  21607. * Specifies that an entire element should be draggable instead of its contents.
  21608. * Mainly for desktop browsers.
  21609. * @property defaults.behavior.userDrag
  21610. * @type {String}
  21611. * @default 'none'
  21612. */
  21613. userDrag: 'none',
  21614. /**
  21615. * Overrides the highlight color shown when the user taps a link or a JavaScript
  21616. * clickable element in Safari on iPhone. This property obeys the alpha value, if specified.
  21617. *
  21618. * If you don't specify an alpha value, Safari on iPhone applies a default alpha value
  21619. * to the color. To disable tap highlighting, set the alpha value to 0 (invisible).
  21620. * If you set the alpha value to 1.0 (opaque), the element is not visible when tapped.
  21621. * @property defaults.behavior.tapHighlightColor
  21622. * @type {String}
  21623. * @default 'rgba(0,0,0,0)'
  21624. */
  21625. tapHighlightColor: 'rgba(0,0,0,0)'
  21626. }
  21627. };
  21628. /**
  21629. * hammer document where the base events are added at
  21630. * @property DOCUMENT
  21631. * @type {HTMLElement}
  21632. * @default window.document
  21633. */
  21634. Hammer.DOCUMENT = document;
  21635. /**
  21636. * detect support for pointer events
  21637. * @property HAS_POINTEREVENTS
  21638. * @type {Boolean}
  21639. */
  21640. Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;
  21641. /**
  21642. * detect support for touch events
  21643. * @property HAS_TOUCHEVENTS
  21644. * @type {Boolean}
  21645. */
  21646. Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);
  21647. /**
  21648. * detect mobile browsers
  21649. * @property IS_MOBILE
  21650. * @type {Boolean}
  21651. */
  21652. Hammer.IS_MOBILE = /mobile|tablet|ip(ad|hone|od)|android|silk/i.test(navigator.userAgent);
  21653. /**
  21654. * detect if we want to support mouseevents at all
  21655. * @property NO_MOUSEEVENTS
  21656. * @type {Boolean}
  21657. */
  21658. Hammer.NO_MOUSEEVENTS = (Hammer.HAS_TOUCHEVENTS && Hammer.IS_MOBILE) || Hammer.HAS_POINTEREVENTS;
  21659. /**
  21660. * interval in which Hammer recalculates current velocity/direction/angle in ms
  21661. * @property CALCULATE_INTERVAL
  21662. * @type {Number}
  21663. * @default 25
  21664. */
  21665. Hammer.CALCULATE_INTERVAL = 25;
  21666. /**
  21667. * eventtypes per touchevent (start, move, end) are filled by `Event.determineEventTypes` on `setup`
  21668. * the object contains the DOM event names per type (`EVENT_START`, `EVENT_MOVE`, `EVENT_END`)
  21669. * @property EVENT_TYPES
  21670. * @private
  21671. * @writeOnce
  21672. * @type {Object}
  21673. */
  21674. var EVENT_TYPES = {};
  21675. /**
  21676. * direction strings, for safe comparisons
  21677. * @property DIRECTION_DOWN|LEFT|UP|RIGHT
  21678. * @final
  21679. * @type {String}
  21680. * @default 'down' 'left' 'up' 'right'
  21681. */
  21682. var DIRECTION_DOWN = Hammer.DIRECTION_DOWN = 'down';
  21683. var DIRECTION_LEFT = Hammer.DIRECTION_LEFT = 'left';
  21684. var DIRECTION_UP = Hammer.DIRECTION_UP = 'up';
  21685. var DIRECTION_RIGHT = Hammer.DIRECTION_RIGHT = 'right';
  21686. /**
  21687. * pointertype strings, for safe comparisons
  21688. * @property POINTER_MOUSE|TOUCH|PEN
  21689. * @final
  21690. * @type {String}
  21691. * @default 'mouse' 'touch' 'pen'
  21692. */
  21693. var POINTER_MOUSE = Hammer.POINTER_MOUSE = 'mouse';
  21694. var POINTER_TOUCH = Hammer.POINTER_TOUCH = 'touch';
  21695. var POINTER_PEN = Hammer.POINTER_PEN = 'pen';
  21696. /**
  21697. * eventtypes
  21698. * @property EVENT_START|MOVE|END|RELEASE|TOUCH
  21699. * @final
  21700. * @type {String}
  21701. * @default 'start' 'change' 'move' 'end' 'release' 'touch'
  21702. */
  21703. var EVENT_START = Hammer.EVENT_START = 'start';
  21704. var EVENT_MOVE = Hammer.EVENT_MOVE = 'move';
  21705. var EVENT_END = Hammer.EVENT_END = 'end';
  21706. var EVENT_RELEASE = Hammer.EVENT_RELEASE = 'release';
  21707. var EVENT_TOUCH = Hammer.EVENT_TOUCH = 'touch';
  21708. /**
  21709. * if the window events are set...
  21710. * @property READY
  21711. * @writeOnce
  21712. * @type {Boolean}
  21713. * @default false
  21714. */
  21715. Hammer.READY = false;
  21716. /**
  21717. * plugins namespace
  21718. * @property plugins
  21719. * @type {Object}
  21720. */
  21721. Hammer.plugins = Hammer.plugins || {};
  21722. /**
  21723. * gestures namespace
  21724. * see `/gestures` for the definitions
  21725. * @property gestures
  21726. * @type {Object}
  21727. */
  21728. Hammer.gestures = Hammer.gestures || {};
  21729. /**
  21730. * setup events to detect gestures on the document
  21731. * this function is called when creating an new instance
  21732. * @private
  21733. */
  21734. function setup() {
  21735. if(Hammer.READY) {
  21736. return;
  21737. }
  21738. // find what eventtypes we add listeners to
  21739. Event.determineEventTypes();
  21740. // Register all gestures inside Hammer.gestures
  21741. Utils.each(Hammer.gestures, function(gesture) {
  21742. Detection.register(gesture);
  21743. });
  21744. // Add touch events on the document
  21745. Event.onTouch(Hammer.DOCUMENT, EVENT_MOVE, Detection.detect);
  21746. Event.onTouch(Hammer.DOCUMENT, EVENT_END, Detection.detect);
  21747. // Hammer is ready...!
  21748. Hammer.READY = true;
  21749. }
  21750. /**
  21751. * @module hammer
  21752. *
  21753. * @class Utils
  21754. * @static
  21755. */
  21756. var Utils = Hammer.utils = {
  21757. /**
  21758. * extend method, could also be used for cloning when `dest` is an empty object.
  21759. * changes the dest object
  21760. * @method extend
  21761. * @param {Object} dest
  21762. * @param {Object} src
  21763. * @param {Boolean} [merge=false] do a merge
  21764. * @return {Object} dest
  21765. */
  21766. extend: function extend(dest, src, merge) {
  21767. for(var key in src) {
  21768. if(!src.hasOwnProperty(key) || (dest[key] !== undefined && merge)) {
  21769. continue;
  21770. }
  21771. dest[key] = src[key];
  21772. }
  21773. return dest;
  21774. },
  21775. /**
  21776. * simple addEventListener wrapper
  21777. * @method on
  21778. * @param {HTMLElement} element
  21779. * @param {String} type
  21780. * @param {Function} handler
  21781. */
  21782. on: function on(element, type, handler) {
  21783. element.addEventListener(type, handler, false);
  21784. },
  21785. /**
  21786. * simple removeEventListener wrapper
  21787. * @method off
  21788. * @param {HTMLElement} element
  21789. * @param {String} type
  21790. * @param {Function} handler
  21791. */
  21792. off: function off(element, type, handler) {
  21793. element.removeEventListener(type, handler, false);
  21794. },
  21795. /**
  21796. * forEach over arrays and objects
  21797. * @method each
  21798. * @param {Object|Array} obj
  21799. * @param {Function} iterator
  21800. * @param {any} iterator.item
  21801. * @param {Number} iterator.index
  21802. * @param {Object|Array} iterator.obj the source object
  21803. * @param {Object} context value to use as `this` in the iterator
  21804. */
  21805. each: function each(obj, iterator, context) {
  21806. var i, len;
  21807. // native forEach on arrays
  21808. if('forEach' in obj) {
  21809. obj.forEach(iterator, context);
  21810. // arrays
  21811. } else if(obj.length !== undefined) {
  21812. for(i = 0, len = obj.length; i < len; i++) {
  21813. if(iterator.call(context, obj[i], i, obj) === false) {
  21814. return;
  21815. }
  21816. }
  21817. // objects
  21818. } else {
  21819. for(i in obj) {
  21820. if(obj.hasOwnProperty(i) &&
  21821. iterator.call(context, obj[i], i, obj) === false) {
  21822. return;
  21823. }
  21824. }
  21825. }
  21826. },
  21827. /**
  21828. * find if a string contains the string using indexOf
  21829. * @method inStr
  21830. * @param {String} src
  21831. * @param {String} find
  21832. * @return {Boolean} found
  21833. */
  21834. inStr: function inStr(src, find) {
  21835. return src.indexOf(find) > -1;
  21836. },
  21837. /**
  21838. * find if a array contains the object using indexOf or a simple polyfill
  21839. * @method inArray
  21840. * @param {String} src
  21841. * @param {String} find
  21842. * @return {Boolean|Number} false when not found, or the index
  21843. */
  21844. inArray: function inArray(src, find) {
  21845. if(src.indexOf) {
  21846. var index = src.indexOf(find);
  21847. return (index === -1) ? false : index;
  21848. } else {
  21849. for(var i = 0, len = src.length; i < len; i++) {
  21850. if(src[i] === find) {
  21851. return i;
  21852. }
  21853. }
  21854. return false;
  21855. }
  21856. },
  21857. /**
  21858. * convert an array-like object (`arguments`, `touchlist`) to an array
  21859. * @method toArray
  21860. * @param {Object} obj
  21861. * @return {Array}
  21862. */
  21863. toArray: function toArray(obj) {
  21864. return Array.prototype.slice.call(obj, 0);
  21865. },
  21866. /**
  21867. * find if a node is in the given parent
  21868. * @method hasParent
  21869. * @param {HTMLElement} node
  21870. * @param {HTMLElement} parent
  21871. * @return {Boolean} found
  21872. */
  21873. hasParent: function hasParent(node, parent) {
  21874. while(node) {
  21875. if(node == parent) {
  21876. return true;
  21877. }
  21878. node = node.parentNode;
  21879. }
  21880. return false;
  21881. },
  21882. /**
  21883. * get the center of all the touches
  21884. * @method getCenter
  21885. * @param {Array} touches
  21886. * @return {Object} center contains `pageX`, `pageY`, `clientX` and `clientY` properties
  21887. */
  21888. getCenter: function getCenter(touches) {
  21889. var pageX = [],
  21890. pageY = [],
  21891. clientX = [],
  21892. clientY = [],
  21893. min = Math.min,
  21894. max = Math.max;
  21895. // no need to loop when only one touch
  21896. if(touches.length === 1) {
  21897. return {
  21898. pageX: touches[0].pageX,
  21899. pageY: touches[0].pageY,
  21900. clientX: touches[0].clientX,
  21901. clientY: touches[0].clientY
  21902. };
  21903. }
  21904. Utils.each(touches, function(touch) {
  21905. pageX.push(touch.pageX);
  21906. pageY.push(touch.pageY);
  21907. clientX.push(touch.clientX);
  21908. clientY.push(touch.clientY);
  21909. });
  21910. return {
  21911. pageX: (min.apply(Math, pageX) + max.apply(Math, pageX)) / 2,
  21912. pageY: (min.apply(Math, pageY) + max.apply(Math, pageY)) / 2,
  21913. clientX: (min.apply(Math, clientX) + max.apply(Math, clientX)) / 2,
  21914. clientY: (min.apply(Math, clientY) + max.apply(Math, clientY)) / 2
  21915. };
  21916. },
  21917. /**
  21918. * calculate the velocity between two points. unit is in px per ms.
  21919. * @method getVelocity
  21920. * @param {Number} deltaTime
  21921. * @param {Number} deltaX
  21922. * @param {Number} deltaY
  21923. * @return {Object} velocity `x` and `y`
  21924. */
  21925. getVelocity: function getVelocity(deltaTime, deltaX, deltaY) {
  21926. return {
  21927. x: Math.abs(deltaX / deltaTime) || 0,
  21928. y: Math.abs(deltaY / deltaTime) || 0
  21929. };
  21930. },
  21931. /**
  21932. * calculate the angle between two coordinates
  21933. * @method getAngle
  21934. * @param {Touch} touch1
  21935. * @param {Touch} touch2
  21936. * @return {Number} angle
  21937. */
  21938. getAngle: function getAngle(touch1, touch2) {
  21939. var x = touch2.clientX - touch1.clientX,
  21940. y = touch2.clientY - touch1.clientY;
  21941. return Math.atan2(y, x) * 180 / Math.PI;
  21942. },
  21943. /**
  21944. * do a small comparision to get the direction between two touches.
  21945. * @method getDirection
  21946. * @param {Touch} touch1
  21947. * @param {Touch} touch2
  21948. * @return {String} direction matches `DIRECTION_LEFT|RIGHT|UP|DOWN`
  21949. */
  21950. getDirection: function getDirection(touch1, touch2) {
  21951. var x = Math.abs(touch1.clientX - touch2.clientX),
  21952. y = Math.abs(touch1.clientY - touch2.clientY);
  21953. if(x >= y) {
  21954. return touch1.clientX - touch2.clientX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
  21955. }
  21956. return touch1.clientY - touch2.clientY > 0 ? DIRECTION_UP : DIRECTION_DOWN;
  21957. },
  21958. /**
  21959. * calculate the distance between two touches
  21960. * @method getDistance
  21961. * @param {Touch}touch1
  21962. * @param {Touch} touch2
  21963. * @return {Number} distance
  21964. */
  21965. getDistance: function getDistance(touch1, touch2) {
  21966. var x = touch2.clientX - touch1.clientX,
  21967. y = touch2.clientY - touch1.clientY;
  21968. return Math.sqrt((x * x) + (y * y));
  21969. },
  21970. /**
  21971. * calculate the scale factor between two touchLists
  21972. * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
  21973. * @method getScale
  21974. * @param {Array} start array of touches
  21975. * @param {Array} end array of touches
  21976. * @return {Number} scale
  21977. */
  21978. getScale: function getScale(start, end) {
  21979. // need two fingers...
  21980. if(start.length >= 2 && end.length >= 2) {
  21981. return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]);
  21982. }
  21983. return 1;
  21984. },
  21985. /**
  21986. * calculate the rotation degrees between two touchLists
  21987. * @method getRotation
  21988. * @param {Array} start array of touches
  21989. * @param {Array} end array of touches
  21990. * @return {Number} rotation
  21991. */
  21992. getRotation: function getRotation(start, end) {
  21993. // need two fingers
  21994. if(start.length >= 2 && end.length >= 2) {
  21995. return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]);
  21996. }
  21997. return 0;
  21998. },
  21999. /**
  22000. * find out if the direction is vertical *
  22001. * @method isVertical
  22002. * @param {String} direction matches `DIRECTION_UP|DOWN`
  22003. * @return {Boolean} is_vertical
  22004. */
  22005. isVertical: function isVertical(direction) {
  22006. return direction == DIRECTION_UP || direction == DIRECTION_DOWN;
  22007. },
  22008. /**
  22009. * set css properties with their prefixes
  22010. * @param {HTMLElement} element
  22011. * @param {String} prop
  22012. * @param {String} value
  22013. * @param {Boolean} [toggle=true]
  22014. * @return {Boolean}
  22015. */
  22016. setPrefixedCss: function setPrefixedCss(element, prop, value, toggle) {
  22017. var prefixes = ['', 'Webkit', 'Moz', 'O', 'ms'];
  22018. prop = Utils.toCamelCase(prop);
  22019. for(var i = 0; i < prefixes.length; i++) {
  22020. var p = prop;
  22021. // prefixes
  22022. if(prefixes[i]) {
  22023. p = prefixes[i] + p.slice(0, 1).toUpperCase() + p.slice(1);
  22024. }
  22025. // test the style
  22026. if(p in element.style) {
  22027. element.style[p] = (toggle == null || toggle) && value || '';
  22028. break;
  22029. }
  22030. }
  22031. },
  22032. /**
  22033. * toggle browser default behavior by setting css properties.
  22034. * `userSelect='none'` also sets `element.onselectstart` to false
  22035. * `userDrag='none'` also sets `element.ondragstart` to false
  22036. *
  22037. * @method toggleBehavior
  22038. * @param {HtmlElement} element
  22039. * @param {Object} props
  22040. * @param {Boolean} [toggle=true]
  22041. */
  22042. toggleBehavior: function toggleBehavior(element, props, toggle) {
  22043. if(!props || !element || !element.style) {
  22044. return;
  22045. }
  22046. // set the css properties
  22047. Utils.each(props, function(value, prop) {
  22048. Utils.setPrefixedCss(element, prop, value, toggle);
  22049. });
  22050. var falseFn = toggle && function() {
  22051. return false;
  22052. };
  22053. // also the disable onselectstart
  22054. if(props.userSelect == 'none') {
  22055. element.onselectstart = falseFn;
  22056. }
  22057. // and disable ondragstart
  22058. if(props.userDrag == 'none') {
  22059. element.ondragstart = falseFn;
  22060. }
  22061. },
  22062. /**
  22063. * convert a string with underscores to camelCase
  22064. * so prevent_default becomes preventDefault
  22065. * @param {String} str
  22066. * @return {String} camelCaseStr
  22067. */
  22068. toCamelCase: function toCamelCase(str) {
  22069. return str.replace(/[_-]([a-z])/g, function(s) {
  22070. return s[1].toUpperCase();
  22071. });
  22072. }
  22073. };
  22074. /**
  22075. * @module hammer
  22076. */
  22077. /**
  22078. * @class Event
  22079. * @static
  22080. */
  22081. var Event = Hammer.event = {
  22082. /**
  22083. * when touch events have been fired, this is true
  22084. * this is used to stop mouse events
  22085. * @property prevent_mouseevents
  22086. * @private
  22087. * @type {Boolean}
  22088. */
  22089. preventMouseEvents: false,
  22090. /**
  22091. * if EVENT_START has been fired
  22092. * @property started
  22093. * @private
  22094. * @type {Boolean}
  22095. */
  22096. started: false,
  22097. /**
  22098. * when the mouse is hold down, this is true
  22099. * @property should_detect
  22100. * @private
  22101. * @type {Boolean}
  22102. */
  22103. shouldDetect: false,
  22104. /**
  22105. * simple event binder with a hook and support for multiple types
  22106. * @method on
  22107. * @param {HTMLElement} element
  22108. * @param {String} type
  22109. * @param {Function} handler
  22110. * @param {Function} [hook]
  22111. * @param {Object} hook.type
  22112. */
  22113. on: function on(element, type, handler, hook) {
  22114. var types = type.split(' ');
  22115. Utils.each(types, function(type) {
  22116. Utils.on(element, type, handler);
  22117. hook && hook(type);
  22118. });
  22119. },
  22120. /**
  22121. * simple event unbinder with a hook and support for multiple types
  22122. * @method off
  22123. * @param {HTMLElement} element
  22124. * @param {String} type
  22125. * @param {Function} handler
  22126. * @param {Function} [hook]
  22127. * @param {Object} hook.type
  22128. */
  22129. off: function off(element, type, handler, hook) {
  22130. var types = type.split(' ');
  22131. Utils.each(types, function(type) {
  22132. Utils.off(element, type, handler);
  22133. hook && hook(type);
  22134. });
  22135. },
  22136. /**
  22137. * the core touch event handler.
  22138. * this finds out if we should to detect gestures
  22139. * @method onTouch
  22140. * @param {HTMLElement} element
  22141. * @param {String} eventType matches `EVENT_START|MOVE|END`
  22142. * @param {Function} handler
  22143. * @return onTouchHandler {Function} the core event handler
  22144. */
  22145. onTouch: function onTouch(element, eventType, handler) {
  22146. var self = this;
  22147. var onTouchHandler = function onTouchHandler(ev) {
  22148. var srcType = ev.type.toLowerCase(),
  22149. isPointer = Hammer.HAS_POINTEREVENTS,
  22150. isMouse = Utils.inStr(srcType, 'mouse'),
  22151. triggerType;
  22152. // if we are in a mouseevent, but there has been a touchevent triggered in this session
  22153. // we want to do nothing. simply break out of the event.
  22154. if(isMouse && self.preventMouseEvents) {
  22155. return;
  22156. // mousebutton must be down
  22157. } else if(isMouse && eventType == EVENT_START && ev.button === 0) {
  22158. self.preventMouseEvents = false;
  22159. self.shouldDetect = true;
  22160. } else if(isPointer && eventType == EVENT_START) {
  22161. self.shouldDetect = (ev.buttons === 1 || PointerEvent.matchType(POINTER_TOUCH, ev));
  22162. // just a valid start event, but no mouse
  22163. } else if(!isMouse && eventType == EVENT_START) {
  22164. self.preventMouseEvents = true;
  22165. self.shouldDetect = true;
  22166. }
  22167. // update the pointer event before entering the detection
  22168. if(isPointer && eventType != EVENT_END) {
  22169. PointerEvent.updatePointer(eventType, ev);
  22170. }
  22171. // we are in a touch/down state, so allowed detection of gestures
  22172. if(self.shouldDetect) {
  22173. triggerType = self.doDetect.call(self, ev, eventType, element, handler);
  22174. }
  22175. // ...and we are done with the detection
  22176. // so reset everything to start each detection totally fresh
  22177. if(triggerType == EVENT_END) {
  22178. self.preventMouseEvents = false;
  22179. self.shouldDetect = false;
  22180. PointerEvent.reset();
  22181. // update the pointerevent object after the detection
  22182. }
  22183. if(isPointer && eventType == EVENT_END) {
  22184. PointerEvent.updatePointer(eventType, ev);
  22185. }
  22186. };
  22187. this.on(element, EVENT_TYPES[eventType], onTouchHandler);
  22188. return onTouchHandler;
  22189. },
  22190. /**
  22191. * the core detection method
  22192. * this finds out what hammer-touch-events to trigger
  22193. * @method doDetect
  22194. * @param {Object} ev
  22195. * @param {String} eventType matches `EVENT_START|MOVE|END`
  22196. * @param {HTMLElement} element
  22197. * @param {Function} handler
  22198. * @return {String} triggerType matches `EVENT_START|MOVE|END`
  22199. */
  22200. doDetect: function doDetect(ev, eventType, element, handler) {
  22201. var touchList = this.getTouchList(ev, eventType);
  22202. var touchListLength = touchList.length;
  22203. var triggerType = eventType;
  22204. var triggerChange = touchList.trigger; // used by fakeMultitouch plugin
  22205. var changedLength = touchListLength;
  22206. // at each touchstart-like event we want also want to trigger a TOUCH event...
  22207. if(eventType == EVENT_START) {
  22208. triggerChange = EVENT_TOUCH;
  22209. // ...the same for a touchend-like event
  22210. } else if(eventType == EVENT_END) {
  22211. triggerChange = EVENT_RELEASE;
  22212. // keep track of how many touches have been removed
  22213. changedLength = touchList.length - ((ev.changedTouches) ? ev.changedTouches.length : 1);
  22214. }
  22215. // after there are still touches on the screen,
  22216. // we just want to trigger a MOVE event. so change the START or END to a MOVE
  22217. // but only after detection has been started, the first time we actualy want a START
  22218. if(changedLength > 0 && this.started) {
  22219. triggerType = EVENT_MOVE;
  22220. }
  22221. // detection has been started, we keep track of this, see above
  22222. this.started = true;
  22223. // generate some event data, some basic information
  22224. var evData = this.collectEventData(element, triggerType, touchList, ev);
  22225. // trigger the triggerType event before the change (TOUCH, RELEASE) events
  22226. // but the END event should be at last
  22227. if(eventType != EVENT_END) {
  22228. handler.call(Detection, evData);
  22229. }
  22230. // trigger a change (TOUCH, RELEASE) event, this means the length of the touches changed
  22231. if(triggerChange) {
  22232. evData.changedLength = changedLength;
  22233. evData.eventType = triggerChange;
  22234. handler.call(Detection, evData);
  22235. evData.eventType = triggerType;
  22236. delete evData.changedLength;
  22237. }
  22238. // trigger the END event
  22239. if(triggerType == EVENT_END) {
  22240. handler.call(Detection, evData);
  22241. // ...and we are done with the detection
  22242. // so reset everything to start each detection totally fresh
  22243. this.started = false;
  22244. }
  22245. return triggerType;
  22246. },
  22247. /**
  22248. * we have different events for each device/browser
  22249. * determine what we need and set them in the EVENT_TYPES constant
  22250. * the `onTouch` method is bind to these properties.
  22251. * @method determineEventTypes
  22252. * @return {Object} events
  22253. */
  22254. determineEventTypes: function determineEventTypes() {
  22255. var types;
  22256. if(Hammer.HAS_POINTEREVENTS) {
  22257. if(window.PointerEvent) {
  22258. types = [
  22259. 'pointerdown',
  22260. 'pointermove',
  22261. 'pointerup pointercancel lostpointercapture'
  22262. ];
  22263. } else {
  22264. types = [
  22265. 'MSPointerDown',
  22266. 'MSPointerMove',
  22267. 'MSPointerUp MSPointerCancel MSLostPointerCapture'
  22268. ];
  22269. }
  22270. } else if(Hammer.NO_MOUSEEVENTS) {
  22271. types = [
  22272. 'touchstart',
  22273. 'touchmove',
  22274. 'touchend touchcancel'
  22275. ];
  22276. } else {
  22277. types = [
  22278. 'touchstart mousedown',
  22279. 'touchmove mousemove',
  22280. 'touchend touchcancel mouseup'
  22281. ];
  22282. }
  22283. EVENT_TYPES[EVENT_START] = types[0];
  22284. EVENT_TYPES[EVENT_MOVE] = types[1];
  22285. EVENT_TYPES[EVENT_END] = types[2];
  22286. return EVENT_TYPES;
  22287. },
  22288. /**
  22289. * create touchList depending on the event
  22290. * @method getTouchList
  22291. * @param {Object} ev
  22292. * @param {String} eventType
  22293. * @return {Array} touches
  22294. */
  22295. getTouchList: function getTouchList(ev, eventType) {
  22296. // get the fake pointerEvent touchlist
  22297. if(Hammer.HAS_POINTEREVENTS) {
  22298. return PointerEvent.getTouchList();
  22299. }
  22300. // get the touchlist
  22301. if(ev.touches) {
  22302. if(eventType == EVENT_MOVE) {
  22303. return ev.touches;
  22304. }
  22305. var identifiers = [];
  22306. var concat = [].concat(Utils.toArray(ev.touches), Utils.toArray(ev.changedTouches));
  22307. var touchList = [];
  22308. Utils.each(concat, function(touch) {
  22309. if(Utils.inArray(identifiers, touch.identifier) === false) {
  22310. touchList.push(touch);
  22311. }
  22312. identifiers.push(touch.identifier);
  22313. });
  22314. return touchList;
  22315. }
  22316. // make fake touchList from mouse position
  22317. ev.identifier = 1;
  22318. return [ev];
  22319. },
  22320. /**
  22321. * collect basic event data
  22322. * @method collectEventData
  22323. * @param {HTMLElement} element
  22324. * @param {String} eventType matches `EVENT_START|MOVE|END`
  22325. * @param {Array} touches
  22326. * @param {Object} ev
  22327. * @return {Object} ev
  22328. */
  22329. collectEventData: function collectEventData(element, eventType, touches, ev) {
  22330. // find out pointerType
  22331. var pointerType = POINTER_TOUCH;
  22332. if(Utils.inStr(ev.type, 'mouse') || PointerEvent.matchType(POINTER_MOUSE, ev)) {
  22333. pointerType = POINTER_MOUSE;
  22334. } else if(PointerEvent.matchType(POINTER_PEN, ev)) {
  22335. pointerType = POINTER_PEN;
  22336. }
  22337. return {
  22338. center: Utils.getCenter(touches),
  22339. timeStamp: Date.now(),
  22340. target: ev.target,
  22341. touches: touches,
  22342. eventType: eventType,
  22343. pointerType: pointerType,
  22344. srcEvent: ev,
  22345. /**
  22346. * prevent the browser default actions
  22347. * mostly used to disable scrolling of the browser
  22348. */
  22349. preventDefault: function() {
  22350. var srcEvent = this.srcEvent;
  22351. srcEvent.preventManipulation && srcEvent.preventManipulation();
  22352. srcEvent.preventDefault && srcEvent.preventDefault();
  22353. },
  22354. /**
  22355. * stop bubbling the event up to its parents
  22356. */
  22357. stopPropagation: function() {
  22358. this.srcEvent.stopPropagation();
  22359. },
  22360. /**
  22361. * immediately stop gesture detection
  22362. * might be useful after a swipe was detected
  22363. * @return {*}
  22364. */
  22365. stopDetect: function() {
  22366. return Detection.stopDetect();
  22367. }
  22368. };
  22369. }
  22370. };
  22371. /**
  22372. * @module hammer
  22373. *
  22374. * @class PointerEvent
  22375. * @static
  22376. */
  22377. var PointerEvent = Hammer.PointerEvent = {
  22378. /**
  22379. * holds all pointers, by `identifier`
  22380. * @property pointers
  22381. * @type {Object}
  22382. */
  22383. pointers: {},
  22384. /**
  22385. * get the pointers as an array
  22386. * @method getTouchList
  22387. * @return {Array} touchlist
  22388. */
  22389. getTouchList: function getTouchList() {
  22390. var touchlist = [];
  22391. // we can use forEach since pointerEvents only is in IE10
  22392. Utils.each(this.pointers, function(pointer) {
  22393. touchlist.push(pointer);
  22394. });
  22395. return touchlist;
  22396. },
  22397. /**
  22398. * update the position of a pointer
  22399. * @method updatePointer
  22400. * @param {String} eventType matches `EVENT_START|MOVE|END`
  22401. * @param {Object} pointerEvent
  22402. */
  22403. updatePointer: function updatePointer(eventType, pointerEvent) {
  22404. if(eventType == EVENT_END || (eventType != EVENT_END && pointerEvent.buttons !== 1)) {
  22405. delete this.pointers[pointerEvent.pointerId];
  22406. } else {
  22407. pointerEvent.identifier = pointerEvent.pointerId;
  22408. this.pointers[pointerEvent.pointerId] = pointerEvent;
  22409. }
  22410. },
  22411. /**
  22412. * check if ev matches pointertype
  22413. * @method matchType
  22414. * @param {String} pointerType matches `POINTER_MOUSE|TOUCH|PEN`
  22415. * @param {PointerEvent} ev
  22416. */
  22417. matchType: function matchType(pointerType, ev) {
  22418. if(!ev.pointerType) {
  22419. return false;
  22420. }
  22421. var pt = ev.pointerType,
  22422. types = {};
  22423. types[POINTER_MOUSE] = (pt === (ev.MSPOINTER_TYPE_MOUSE || POINTER_MOUSE));
  22424. types[POINTER_TOUCH] = (pt === (ev.MSPOINTER_TYPE_TOUCH || POINTER_TOUCH));
  22425. types[POINTER_PEN] = (pt === (ev.MSPOINTER_TYPE_PEN || POINTER_PEN));
  22426. return types[pointerType];
  22427. },
  22428. /**
  22429. * reset the stored pointers
  22430. * @method reset
  22431. */
  22432. reset: function resetList() {
  22433. this.pointers = {};
  22434. }
  22435. };
  22436. /**
  22437. * @module hammer
  22438. *
  22439. * @class Detection
  22440. * @static
  22441. */
  22442. var Detection = Hammer.detection = {
  22443. // contains all registred Hammer.gestures in the correct order
  22444. gestures: [],
  22445. // data of the current Hammer.gesture detection session
  22446. current: null,
  22447. // the previous Hammer.gesture session data
  22448. // is a full clone of the previous gesture.current object
  22449. previous: null,
  22450. // when this becomes true, no gestures are fired
  22451. stopped: false,
  22452. /**
  22453. * start Hammer.gesture detection
  22454. * @method startDetect
  22455. * @param {Hammer.Instance} inst
  22456. * @param {Object} eventData
  22457. */
  22458. startDetect: function startDetect(inst, eventData) {
  22459. // already busy with a Hammer.gesture detection on an element
  22460. if(this.current) {
  22461. return;
  22462. }
  22463. this.stopped = false;
  22464. // holds current session
  22465. this.current = {
  22466. inst: inst, // reference to HammerInstance we're working for
  22467. startEvent: Utils.extend({}, eventData), // start eventData for distances, timing etc
  22468. lastEvent: false, // last eventData
  22469. lastCalcEvent: false, // last eventData for calculations.
  22470. futureCalcEvent: false, // last eventData for calculations.
  22471. lastCalcData: {}, // last lastCalcData
  22472. name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
  22473. };
  22474. this.detect(eventData);
  22475. },
  22476. /**
  22477. * Hammer.gesture detection
  22478. * @method detect
  22479. * @param {Object} eventData
  22480. * @return {any}
  22481. */
  22482. detect: function detect(eventData) {
  22483. if(!this.current || this.stopped) {
  22484. return;
  22485. }
  22486. // extend event data with calculations about scale, distance etc
  22487. eventData = this.extendEventData(eventData);
  22488. // hammer instance and instance options
  22489. var inst = this.current.inst,
  22490. instOptions = inst.options;
  22491. // call Hammer.gesture handlers
  22492. Utils.each(this.gestures, function triggerGesture(gesture) {
  22493. // only when the instance options have enabled this gesture
  22494. if(!this.stopped && inst.enabled && instOptions[gesture.name]) {
  22495. gesture.handler.call(gesture, eventData, inst);
  22496. }
  22497. }, this);
  22498. // store as previous event event
  22499. if(this.current) {
  22500. this.current.lastEvent = eventData;
  22501. }
  22502. if(eventData.eventType == EVENT_END) {
  22503. this.stopDetect();
  22504. }
  22505. return eventData;
  22506. },
  22507. /**
  22508. * clear the Hammer.gesture vars
  22509. * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
  22510. * to stop other Hammer.gestures from being fired
  22511. * @method stopDetect
  22512. */
  22513. stopDetect: function stopDetect() {
  22514. // clone current data to the store as the previous gesture
  22515. // used for the double tap gesture, since this is an other gesture detect session
  22516. this.previous = Utils.extend({}, this.current);
  22517. // reset the current
  22518. this.current = null;
  22519. this.stopped = true;
  22520. },
  22521. /**
  22522. * calculate velocity, angle and direction
  22523. * @method getVelocityData
  22524. * @param {Object} ev
  22525. * @param {Object} center
  22526. * @param {Number} deltaTime
  22527. * @param {Number} deltaX
  22528. * @param {Number} deltaY
  22529. */
  22530. getCalculatedData: function getCalculatedData(ev, center, deltaTime, deltaX, deltaY) {
  22531. var cur = this.current,
  22532. recalc = false,
  22533. calcEv = cur.lastCalcEvent,
  22534. calcData = cur.lastCalcData;
  22535. if(calcEv && ev.timeStamp - calcEv.timeStamp > Hammer.CALCULATE_INTERVAL) {
  22536. center = calcEv.center;
  22537. deltaTime = ev.timeStamp - calcEv.timeStamp;
  22538. deltaX = ev.center.clientX - calcEv.center.clientX;
  22539. deltaY = ev.center.clientY - calcEv.center.clientY;
  22540. recalc = true;
  22541. }
  22542. if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
  22543. cur.futureCalcEvent = ev;
  22544. }
  22545. if(!cur.lastCalcEvent || recalc) {
  22546. calcData.velocity = Utils.getVelocity(deltaTime, deltaX, deltaY);
  22547. calcData.angle = Utils.getAngle(center, ev.center);
  22548. calcData.direction = Utils.getDirection(center, ev.center);
  22549. cur.lastCalcEvent = cur.futureCalcEvent || ev;
  22550. cur.futureCalcEvent = ev;
  22551. }
  22552. ev.velocityX = calcData.velocity.x;
  22553. ev.velocityY = calcData.velocity.y;
  22554. ev.interimAngle = calcData.angle;
  22555. ev.interimDirection = calcData.direction;
  22556. },
  22557. /**
  22558. * extend eventData for Hammer.gestures
  22559. * @method extendEventData
  22560. * @param {Object} ev
  22561. * @return {Object} ev
  22562. */
  22563. extendEventData: function extendEventData(ev) {
  22564. var cur = this.current,
  22565. startEv = cur.startEvent,
  22566. lastEv = cur.lastEvent || startEv;
  22567. // update the start touchlist to calculate the scale/rotation
  22568. if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
  22569. startEv.touches = [];
  22570. Utils.each(ev.touches, function(touch) {
  22571. startEv.touches.push({
  22572. clientX: touch.clientX,
  22573. clientY: touch.clientY
  22574. });
  22575. });
  22576. }
  22577. var deltaTime = ev.timeStamp - startEv.timeStamp,
  22578. deltaX = ev.center.clientX - startEv.center.clientX,
  22579. deltaY = ev.center.clientY - startEv.center.clientY;
  22580. this.getCalculatedData(ev, lastEv.center, deltaTime, deltaX, deltaY);
  22581. Utils.extend(ev, {
  22582. startEvent: startEv,
  22583. deltaTime: deltaTime,
  22584. deltaX: deltaX,
  22585. deltaY: deltaY,
  22586. distance: Utils.getDistance(startEv.center, ev.center),
  22587. angle: Utils.getAngle(startEv.center, ev.center),
  22588. direction: Utils.getDirection(startEv.center, ev.center),
  22589. scale: Utils.getScale(startEv.touches, ev.touches),
  22590. rotation: Utils.getRotation(startEv.touches, ev.touches)
  22591. });
  22592. return ev;
  22593. },
  22594. /**
  22595. * register new gesture
  22596. * @method register
  22597. * @param {Object} gesture object, see `gestures/` for documentation
  22598. * @return {Array} gestures
  22599. */
  22600. register: function register(gesture) {
  22601. // add an enable gesture options if there is no given
  22602. var options = gesture.defaults || {};
  22603. if(options[gesture.name] === undefined) {
  22604. options[gesture.name] = true;
  22605. }
  22606. // extend Hammer default options with the Hammer.gesture options
  22607. Utils.extend(Hammer.defaults, options, true);
  22608. // set its index
  22609. gesture.index = gesture.index || 1000;
  22610. // add Hammer.gesture to the list
  22611. this.gestures.push(gesture);
  22612. // sort the list by index
  22613. this.gestures.sort(function(a, b) {
  22614. if(a.index < b.index) {
  22615. return -1;
  22616. }
  22617. if(a.index > b.index) {
  22618. return 1;
  22619. }
  22620. return 0;
  22621. });
  22622. return this.gestures;
  22623. }
  22624. };
  22625. /**
  22626. * @module hammer
  22627. */
  22628. /**
  22629. * create new hammer instance
  22630. * all methods should return the instance itself, so it is chainable.
  22631. *
  22632. * @class Instance
  22633. * @constructor
  22634. * @param {HTMLElement} element
  22635. * @param {Object} [options={}] options are merged with `Hammer.defaults`
  22636. * @return {Hammer.Instance}
  22637. */
  22638. Hammer.Instance = function(element, options) {
  22639. var self = this;
  22640. // setup HammerJS window events and register all gestures
  22641. // this also sets up the default options
  22642. setup();
  22643. /**
  22644. * @property element
  22645. * @type {HTMLElement}
  22646. */
  22647. this.element = element;
  22648. /**
  22649. * @property enabled
  22650. * @type {Boolean}
  22651. * @protected
  22652. */
  22653. this.enabled = true;
  22654. /**
  22655. * options, merged with the defaults
  22656. * options with an _ are converted to camelCase
  22657. * @property options
  22658. * @type {Object}
  22659. */
  22660. Utils.each(options, function(value, name) {
  22661. delete options[name];
  22662. options[Utils.toCamelCase(name)] = value;
  22663. });
  22664. this.options = Utils.extend(Utils.extend({}, Hammer.defaults), options || {});
  22665. // add some css to the element to prevent the browser from doing its native behavoir
  22666. if(this.options.behavior) {
  22667. Utils.toggleBehavior(this.element, this.options.behavior, true);
  22668. }
  22669. /**
  22670. * event start handler on the element to start the detection
  22671. * @property eventStartHandler
  22672. * @type {Object}
  22673. */
  22674. this.eventStartHandler = Event.onTouch(element, EVENT_START, function(ev) {
  22675. if(self.enabled && ev.eventType == EVENT_START) {
  22676. Detection.startDetect(self, ev);
  22677. } else if(ev.eventType == EVENT_TOUCH) {
  22678. Detection.detect(ev);
  22679. }
  22680. });
  22681. /**
  22682. * keep a list of user event handlers which needs to be removed when calling 'dispose'
  22683. * @property eventHandlers
  22684. * @type {Array}
  22685. */
  22686. this.eventHandlers = [];
  22687. };
  22688. Hammer.Instance.prototype = {
  22689. /**
  22690. * bind events to the instance
  22691. * @method on
  22692. * @chainable
  22693. * @param {String} gestures multiple gestures by splitting with a space
  22694. * @param {Function} handler
  22695. * @param {Object} handler.ev event object
  22696. */
  22697. on: function onEvent(gestures, handler) {
  22698. var self = this;
  22699. Event.on(self.element, gestures, handler, function(type) {
  22700. self.eventHandlers.push({ gesture: type, handler: handler });
  22701. });
  22702. return self;
  22703. },
  22704. /**
  22705. * unbind events to the instance
  22706. * @method off
  22707. * @chainable
  22708. * @param {String} gestures
  22709. * @param {Function} handler
  22710. */
  22711. off: function offEvent(gestures, handler) {
  22712. var self = this;
  22713. Event.off(self.element, gestures, handler, function(type) {
  22714. var index = Utils.inArray({ gesture: type, handler: handler });
  22715. if(index !== false) {
  22716. self.eventHandlers.splice(index, 1);
  22717. }
  22718. });
  22719. return self;
  22720. },
  22721. /**
  22722. * trigger gesture event
  22723. * @method trigger
  22724. * @chainable
  22725. * @param {String} gesture
  22726. * @param {Object} [eventData]
  22727. */
  22728. trigger: function triggerEvent(gesture, eventData) {
  22729. // optional
  22730. if(!eventData) {
  22731. eventData = {};
  22732. }
  22733. // create DOM event
  22734. var event = Hammer.DOCUMENT.createEvent('Event');
  22735. event.initEvent(gesture, true, true);
  22736. event.gesture = eventData;
  22737. // trigger on the target if it is in the instance element,
  22738. // this is for event delegation tricks
  22739. var element = this.element;
  22740. if(Utils.hasParent(eventData.target, element)) {
  22741. element = eventData.target;
  22742. }
  22743. element.dispatchEvent(event);
  22744. return this;
  22745. },
  22746. /**
  22747. * enable of disable hammer.js detection
  22748. * @method enable
  22749. * @chainable
  22750. * @param {Boolean} state
  22751. */
  22752. enable: function enable(state) {
  22753. this.enabled = state;
  22754. return this;
  22755. },
  22756. /**
  22757. * dispose this hammer instance
  22758. * @method dispose
  22759. * @return {Null}
  22760. */
  22761. dispose: function dispose() {
  22762. var i, eh;
  22763. // undo all changes made by stop_browser_behavior
  22764. Utils.toggleBehavior(this.element, this.options.behavior, false);
  22765. // unbind all custom event handlers
  22766. for(i = -1; (eh = this.eventHandlers[++i]);) {
  22767. Utils.off(this.element, eh.gesture, eh.handler);
  22768. }
  22769. this.eventHandlers = [];
  22770. // unbind the start event listener
  22771. Event.off(this.element, EVENT_TYPES[EVENT_START], this.eventStartHandler);
  22772. return null;
  22773. }
  22774. };
  22775. /**
  22776. * @module gestures
  22777. */
  22778. /**
  22779. * Move with x fingers (default 1) around on the page.
  22780. * Preventing the default browser behavior is a good way to improve feel and working.
  22781. * ````
  22782. * hammertime.on("drag", function(ev) {
  22783. * console.log(ev);
  22784. * ev.gesture.preventDefault();
  22785. * });
  22786. * ````
  22787. *
  22788. * @class Drag
  22789. * @static
  22790. */
  22791. /**
  22792. * @event drag
  22793. * @param {Object} ev
  22794. */
  22795. /**
  22796. * @event dragstart
  22797. * @param {Object} ev
  22798. */
  22799. /**
  22800. * @event dragend
  22801. * @param {Object} ev
  22802. */
  22803. /**
  22804. * @event drapleft
  22805. * @param {Object} ev
  22806. */
  22807. /**
  22808. * @event dragright
  22809. * @param {Object} ev
  22810. */
  22811. /**
  22812. * @event dragup
  22813. * @param {Object} ev
  22814. */
  22815. /**
  22816. * @event dragdown
  22817. * @param {Object} ev
  22818. */
  22819. /**
  22820. * @param {String} name
  22821. */
  22822. (function(name) {
  22823. var triggered = false;
  22824. function dragGesture(ev, inst) {
  22825. var cur = Detection.current;
  22826. // max touches
  22827. if(inst.options.dragMaxTouches > 0 &&
  22828. ev.touches.length > inst.options.dragMaxTouches) {
  22829. return;
  22830. }
  22831. switch(ev.eventType) {
  22832. case EVENT_START:
  22833. triggered = false;
  22834. break;
  22835. case EVENT_MOVE:
  22836. // when the distance we moved is too small we skip this gesture
  22837. // or we can be already in dragging
  22838. if(ev.distance < inst.options.dragMinDistance &&
  22839. cur.name != name) {
  22840. return;
  22841. }
  22842. var startCenter = cur.startEvent.center;
  22843. // we are dragging!
  22844. if(cur.name != name) {
  22845. cur.name = name;
  22846. if(inst.options.dragDistanceCorrection && ev.distance > 0) {
  22847. // When a drag is triggered, set the event center to dragMinDistance pixels from the original event center.
  22848. // Without this correction, the dragged distance would jumpstart at dragMinDistance pixels instead of at 0.
  22849. // It might be useful to save the original start point somewhere
  22850. var factor = Math.abs(inst.options.dragMinDistance / ev.distance);
  22851. startCenter.pageX += ev.deltaX * factor;
  22852. startCenter.pageY += ev.deltaY * factor;
  22853. startCenter.clientX += ev.deltaX * factor;
  22854. startCenter.clientY += ev.deltaY * factor;
  22855. // recalculate event data using new start point
  22856. ev = Detection.extendEventData(ev);
  22857. }
  22858. }
  22859. // lock drag to axis?
  22860. if(cur.lastEvent.dragLockToAxis ||
  22861. ( inst.options.dragLockToAxis &&
  22862. inst.options.dragLockMinDistance <= ev.distance
  22863. )) {
  22864. ev.dragLockToAxis = true;
  22865. }
  22866. // keep direction on the axis that the drag gesture started on
  22867. var lastDirection = cur.lastEvent.direction;
  22868. if(ev.dragLockToAxis && lastDirection !== ev.direction) {
  22869. if(Utils.isVertical(lastDirection)) {
  22870. ev.direction = (ev.deltaY < 0) ? DIRECTION_UP : DIRECTION_DOWN;
  22871. } else {
  22872. ev.direction = (ev.deltaX < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
  22873. }
  22874. }
  22875. // first time, trigger dragstart event
  22876. if(!triggered) {
  22877. inst.trigger(name + 'start', ev);
  22878. triggered = true;
  22879. }
  22880. // trigger events
  22881. inst.trigger(name, ev);
  22882. inst.trigger(name + ev.direction, ev);
  22883. var isVertical = Utils.isVertical(ev.direction);
  22884. // block the browser events
  22885. if((inst.options.dragBlockVertical && isVertical) ||
  22886. (inst.options.dragBlockHorizontal && !isVertical)) {
  22887. ev.preventDefault();
  22888. }
  22889. break;
  22890. case EVENT_RELEASE:
  22891. if(triggered && ev.changedLength <= inst.options.dragMaxTouches) {
  22892. inst.trigger(name + 'end', ev);
  22893. triggered = false;
  22894. }
  22895. break;
  22896. case EVENT_END:
  22897. triggered = false;
  22898. break;
  22899. }
  22900. }
  22901. Hammer.gestures.Drag = {
  22902. name: name,
  22903. index: 50,
  22904. handler: dragGesture,
  22905. defaults: {
  22906. /**
  22907. * minimal movement that have to be made before the drag event gets triggered
  22908. * @property dragMinDistance
  22909. * @type {Number}
  22910. * @default 10
  22911. */
  22912. dragMinDistance: 10,
  22913. /**
  22914. * Set dragDistanceCorrection to true to make the starting point of the drag
  22915. * be calculated from where the drag was triggered, not from where the touch started.
  22916. * Useful to avoid a jerk-starting drag, which can make fine-adjustments
  22917. * through dragging difficult, and be visually unappealing.
  22918. * @property dragDistanceCorrection
  22919. * @type {Boolean}
  22920. * @default true
  22921. */
  22922. dragDistanceCorrection: true,
  22923. /**
  22924. * set 0 for unlimited, but this can conflict with transform
  22925. * @property dragMaxTouches
  22926. * @type {Number}
  22927. * @default 1
  22928. */
  22929. dragMaxTouches: 1,
  22930. /**
  22931. * prevent default browser behavior when dragging occurs
  22932. * be careful with it, it makes the element a blocking element
  22933. * when you are using the drag gesture, it is a good practice to set this true
  22934. * @property dragBlockHorizontal
  22935. * @type {Boolean}
  22936. * @default false
  22937. */
  22938. dragBlockHorizontal: false,
  22939. /**
  22940. * same as `dragBlockHorizontal`, but for vertical movement
  22941. * @property dragBlockVertical
  22942. * @type {Boolean}
  22943. * @default false
  22944. */
  22945. dragBlockVertical: false,
  22946. /**
  22947. * dragLockToAxis keeps the drag gesture on the axis that it started on,
  22948. * It disallows vertical directions if the initial direction was horizontal, and vice versa.
  22949. * @property dragLockToAxis
  22950. * @type {Boolean}
  22951. * @default false
  22952. */
  22953. dragLockToAxis: false,
  22954. /**
  22955. * drag lock only kicks in when distance > dragLockMinDistance
  22956. * This way, locking occurs only when the distance has become large enough to reliably determine the direction
  22957. * @property dragLockMinDistance
  22958. * @type {Number}
  22959. * @default 25
  22960. */
  22961. dragLockMinDistance: 25
  22962. }
  22963. };
  22964. })('drag');
  22965. /**
  22966. * @module gestures
  22967. */
  22968. /**
  22969. * trigger a simple gesture event, so you can do anything in your handler.
  22970. * only usable if you know what your doing...
  22971. *
  22972. * @class Gesture
  22973. * @static
  22974. */
  22975. /**
  22976. * @event gesture
  22977. * @param {Object} ev
  22978. */
  22979. Hammer.gestures.Gesture = {
  22980. name: 'gesture',
  22981. index: 1337,
  22982. handler: function releaseGesture(ev, inst) {
  22983. inst.trigger(this.name, ev);
  22984. }
  22985. };
  22986. /**
  22987. * @module gestures
  22988. */
  22989. /**
  22990. * Touch stays at the same place for x time
  22991. *
  22992. * @class Hold
  22993. * @static
  22994. */
  22995. /**
  22996. * @event hold
  22997. * @param {Object} ev
  22998. */
  22999. /**
  23000. * @param {String} name
  23001. */
  23002. (function(name) {
  23003. var timer;
  23004. function holdGesture(ev, inst) {
  23005. var options = inst.options,
  23006. current = Detection.current;
  23007. switch(ev.eventType) {
  23008. case EVENT_START:
  23009. clearTimeout(timer);
  23010. // set the gesture so we can check in the timeout if it still is
  23011. current.name = name;
  23012. // set timer and if after the timeout it still is hold,
  23013. // we trigger the hold event
  23014. timer = setTimeout(function() {
  23015. if(current && current.name == name) {
  23016. inst.trigger(name, ev);
  23017. }
  23018. }, options.holdTimeout);
  23019. break;
  23020. case EVENT_MOVE:
  23021. if(ev.distance > options.holdThreshold) {
  23022. clearTimeout(timer);
  23023. }
  23024. break;
  23025. case EVENT_RELEASE:
  23026. clearTimeout(timer);
  23027. break;
  23028. }
  23029. }
  23030. Hammer.gestures.Hold = {
  23031. name: name,
  23032. index: 10,
  23033. defaults: {
  23034. /**
  23035. * @property holdTimeout
  23036. * @type {Number}
  23037. * @default 500
  23038. */
  23039. holdTimeout: 500,
  23040. /**
  23041. * movement allowed while holding
  23042. * @property holdThreshold
  23043. * @type {Number}
  23044. * @default 2
  23045. */
  23046. holdThreshold: 2
  23047. },
  23048. handler: holdGesture
  23049. };
  23050. })('hold');
  23051. /**
  23052. * @module gestures
  23053. */
  23054. /**
  23055. * when a touch is being released from the page
  23056. *
  23057. * @class Release
  23058. * @static
  23059. */
  23060. /**
  23061. * @event release
  23062. * @param {Object} ev
  23063. */
  23064. Hammer.gestures.Release = {
  23065. name: 'release',
  23066. index: Infinity,
  23067. handler: function releaseGesture(ev, inst) {
  23068. if(ev.eventType == EVENT_RELEASE) {
  23069. inst.trigger(this.name, ev);
  23070. }
  23071. }
  23072. };
  23073. /**
  23074. * @module gestures
  23075. */
  23076. /**
  23077. * triggers swipe events when the end velocity is above the threshold
  23078. * for best usage, set `preventDefault` (on the drag gesture) to `true`
  23079. * ````
  23080. * hammertime.on("dragleft swipeleft", function(ev) {
  23081. * console.log(ev);
  23082. * ev.gesture.preventDefault();
  23083. * });
  23084. * ````
  23085. *
  23086. * @class Swipe
  23087. * @static
  23088. */
  23089. /**
  23090. * @event swipe
  23091. * @param {Object} ev
  23092. */
  23093. /**
  23094. * @event swipeleft
  23095. * @param {Object} ev
  23096. */
  23097. /**
  23098. * @event swiperight
  23099. * @param {Object} ev
  23100. */
  23101. /**
  23102. * @event swipeup
  23103. * @param {Object} ev
  23104. */
  23105. /**
  23106. * @event swipedown
  23107. * @param {Object} ev
  23108. */
  23109. Hammer.gestures.Swipe = {
  23110. name: 'swipe',
  23111. index: 40,
  23112. defaults: {
  23113. /**
  23114. * @property swipeMinTouches
  23115. * @type {Number}
  23116. * @default 1
  23117. */
  23118. swipeMinTouches: 1,
  23119. /**
  23120. * @property swipeMaxTouches
  23121. * @type {Number}
  23122. * @default 1
  23123. */
  23124. swipeMaxTouches: 1,
  23125. /**
  23126. * horizontal swipe velocity
  23127. * @property swipeVelocityX
  23128. * @type {Number}
  23129. * @default 0.6
  23130. */
  23131. swipeVelocityX: 0.6,
  23132. /**
  23133. * vertical swipe velocity
  23134. * @property swipeVelocityY
  23135. * @type {Number}
  23136. * @default 0.6
  23137. */
  23138. swipeVelocityY: 0.6
  23139. },
  23140. handler: function swipeGesture(ev, inst) {
  23141. if(ev.eventType == EVENT_RELEASE) {
  23142. var touches = ev.touches.length,
  23143. options = inst.options;
  23144. // max touches
  23145. if(touches < options.swipeMinTouches ||
  23146. touches > options.swipeMaxTouches) {
  23147. return;
  23148. }
  23149. // when the distance we moved is too small we skip this gesture
  23150. // or we can be already in dragging
  23151. if(ev.velocityX > options.swipeVelocityX ||
  23152. ev.velocityY > options.swipeVelocityY) {
  23153. // trigger swipe events
  23154. inst.trigger(this.name, ev);
  23155. inst.trigger(this.name + ev.direction, ev);
  23156. }
  23157. }
  23158. }
  23159. };
  23160. /**
  23161. * @module gestures
  23162. */
  23163. /**
  23164. * Single tap and a double tap on a place
  23165. *
  23166. * @class Tap
  23167. * @static
  23168. */
  23169. /**
  23170. * @event tap
  23171. * @param {Object} ev
  23172. */
  23173. /**
  23174. * @event doubletap
  23175. * @param {Object} ev
  23176. */
  23177. /**
  23178. * @param {String} name
  23179. */
  23180. (function(name) {
  23181. var hasMoved = false;
  23182. function tapGesture(ev, inst) {
  23183. var options = inst.options,
  23184. current = Detection.current,
  23185. prev = Detection.previous,
  23186. sincePrev,
  23187. didDoubleTap;
  23188. switch(ev.eventType) {
  23189. case EVENT_START:
  23190. hasMoved = false;
  23191. break;
  23192. case EVENT_MOVE:
  23193. hasMoved = hasMoved || (ev.distance > options.tapMaxDistance);
  23194. break;
  23195. case EVENT_END:
  23196. if(!Utils.inStr(ev.srcEvent.type, 'cancel') && ev.deltaTime < options.tapMaxTime && !hasMoved) {
  23197. // previous gesture, for the double tap since these are two different gesture detections
  23198. sincePrev = prev && prev.lastEvent && ev.timeStamp - prev.lastEvent.timeStamp;
  23199. didDoubleTap = false;
  23200. // check if double tap
  23201. if(prev && prev.name == name &&
  23202. (sincePrev && sincePrev < options.doubleTapInterval) &&
  23203. ev.distance < options.doubleTapDistance) {
  23204. inst.trigger('doubletap', ev);
  23205. didDoubleTap = true;
  23206. }
  23207. // do a single tap
  23208. if(!didDoubleTap || options.tapAlways) {
  23209. current.name = name;
  23210. inst.trigger(current.name, ev);
  23211. }
  23212. }
  23213. break;
  23214. }
  23215. }
  23216. Hammer.gestures.Tap = {
  23217. name: name,
  23218. index: 100,
  23219. handler: tapGesture,
  23220. defaults: {
  23221. /**
  23222. * max time of a tap, this is for the slow tappers
  23223. * @property tapMaxTime
  23224. * @type {Number}
  23225. * @default 250
  23226. */
  23227. tapMaxTime: 250,
  23228. /**
  23229. * max distance of movement of a tap, this is for the slow tappers
  23230. * @property tapMaxDistance
  23231. * @type {Number}
  23232. * @default 10
  23233. */
  23234. tapMaxDistance: 10,
  23235. /**
  23236. * always trigger the `tap` event, even while double-tapping
  23237. * @property tapAlways
  23238. * @type {Boolean}
  23239. * @default true
  23240. */
  23241. tapAlways: true,
  23242. /**
  23243. * max distance between two taps
  23244. * @property doubleTapDistance
  23245. * @type {Number}
  23246. * @default 20
  23247. */
  23248. doubleTapDistance: 20,
  23249. /**
  23250. * max time between two taps
  23251. * @property doubleTapInterval
  23252. * @type {Number}
  23253. * @default 300
  23254. */
  23255. doubleTapInterval: 300
  23256. }
  23257. };
  23258. })('tap');
  23259. /**
  23260. * @module gestures
  23261. */
  23262. /**
  23263. * when a touch is being touched at the page
  23264. *
  23265. * @class Touch
  23266. * @static
  23267. */
  23268. /**
  23269. * @event touch
  23270. * @param {Object} ev
  23271. */
  23272. Hammer.gestures.Touch = {
  23273. name: 'touch',
  23274. index: -Infinity,
  23275. defaults: {
  23276. /**
  23277. * call preventDefault at touchstart, and makes the element blocking by disabling the scrolling of the page,
  23278. * but it improves gestures like transforming and dragging.
  23279. * be careful with using this, it can be very annoying for users to be stuck on the page
  23280. * @property preventDefault
  23281. * @type {Boolean}
  23282. * @default false
  23283. */
  23284. preventDefault: false,
  23285. /**
  23286. * disable mouse events, so only touch (or pen!) input triggers events
  23287. * @property preventMouse
  23288. * @type {Boolean}
  23289. * @default false
  23290. */
  23291. preventMouse: false
  23292. },
  23293. handler: function touchGesture(ev, inst) {
  23294. if(inst.options.preventMouse && ev.pointerType == POINTER_MOUSE) {
  23295. ev.stopDetect();
  23296. return;
  23297. }
  23298. if(inst.options.preventDefault) {
  23299. ev.preventDefault();
  23300. }
  23301. if(ev.eventType == EVENT_TOUCH) {
  23302. inst.trigger('touch', ev);
  23303. }
  23304. }
  23305. };
  23306. /**
  23307. * @module gestures
  23308. */
  23309. /**
  23310. * User want to scale or rotate with 2 fingers
  23311. * Preventing the default browser behavior is a good way to improve feel and working. This can be done with the
  23312. * `preventDefault` option.
  23313. *
  23314. * @class Transform
  23315. * @static
  23316. */
  23317. /**
  23318. * @event transform
  23319. * @param {Object} ev
  23320. */
  23321. /**
  23322. * @event transformstart
  23323. * @param {Object} ev
  23324. */
  23325. /**
  23326. * @event transformend
  23327. * @param {Object} ev
  23328. */
  23329. /**
  23330. * @event pinchin
  23331. * @param {Object} ev
  23332. */
  23333. /**
  23334. * @event pinchout
  23335. * @param {Object} ev
  23336. */
  23337. /**
  23338. * @event rotate
  23339. * @param {Object} ev
  23340. */
  23341. /**
  23342. * @param {String} name
  23343. */
  23344. (function(name) {
  23345. var triggered = false;
  23346. function transformGesture(ev, inst) {
  23347. switch(ev.eventType) {
  23348. case EVENT_START:
  23349. triggered = false;
  23350. break;
  23351. case EVENT_MOVE:
  23352. // at least multitouch
  23353. if(ev.touches.length < 2) {
  23354. return;
  23355. }
  23356. var scaleThreshold = Math.abs(1 - ev.scale);
  23357. var rotationThreshold = Math.abs(ev.rotation);
  23358. // when the distance we moved is too small we skip this gesture
  23359. // or we can be already in dragging
  23360. if(scaleThreshold < inst.options.transformMinScale &&
  23361. rotationThreshold < inst.options.transformMinRotation) {
  23362. return;
  23363. }
  23364. // we are transforming!
  23365. Detection.current.name = name;
  23366. // first time, trigger dragstart event
  23367. if(!triggered) {
  23368. inst.trigger(name + 'start', ev);
  23369. triggered = true;
  23370. }
  23371. inst.trigger(name, ev); // basic transform event
  23372. // trigger rotate event
  23373. if(rotationThreshold > inst.options.transformMinRotation) {
  23374. inst.trigger('rotate', ev);
  23375. }
  23376. // trigger pinch event
  23377. if(scaleThreshold > inst.options.transformMinScale) {
  23378. inst.trigger('pinch', ev);
  23379. inst.trigger('pinch' + (ev.scale < 1 ? 'in' : 'out'), ev);
  23380. }
  23381. break;
  23382. case EVENT_RELEASE:
  23383. if(triggered && ev.changedLength < 2) {
  23384. inst.trigger(name + 'end', ev);
  23385. triggered = false;
  23386. }
  23387. break;
  23388. }
  23389. }
  23390. Hammer.gestures.Transform = {
  23391. name: name,
  23392. index: 45,
  23393. defaults: {
  23394. /**
  23395. * minimal scale factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
  23396. * @property transformMinScale
  23397. * @type {Number}
  23398. * @default 0.01
  23399. */
  23400. transformMinScale: 0.01,
  23401. /**
  23402. * rotation in degrees
  23403. * @property transformMinRotation
  23404. * @type {Number}
  23405. * @default 1
  23406. */
  23407. transformMinRotation: 1
  23408. },
  23409. handler: transformGesture
  23410. };
  23411. })('transform');
  23412. /**
  23413. * @module hammer
  23414. */
  23415. // AMD export
  23416. if(true) {
  23417. !(__WEBPACK_AMD_DEFINE_RESULT__ = function() {
  23418. return Hammer;
  23419. }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  23420. // commonjs export
  23421. } else if(typeof module !== 'undefined' && module.exports) {
  23422. module.exports = Hammer;
  23423. // browser export
  23424. } else {
  23425. window.Hammer = Hammer;
  23426. }
  23427. })(window);
  23428. /***/ },
  23429. /* 53 */
  23430. /***/ function(module, exports, __webpack_require__) {
  23431. /**
  23432. * Creation of the ClusterMixin var.
  23433. *
  23434. * This contains all the functions the Network object can use to employ clustering
  23435. */
  23436. /**
  23437. * This is only called in the constructor of the network object
  23438. *
  23439. */
  23440. exports.startWithClustering = function() {
  23441. // cluster if the data set is big
  23442. this.clusterToFit(this.constants.clustering.initialMaxNodes, true);
  23443. // updates the lables after clustering
  23444. this.updateLabels();
  23445. // this is called here because if clusterin is disabled, the start and stabilize are called in
  23446. // the setData function.
  23447. if (this.stabilize) {
  23448. this._stabilize();
  23449. }
  23450. this.start();
  23451. };
  23452. /**
  23453. * This function clusters until the initialMaxNodes has been reached
  23454. *
  23455. * @param {Number} maxNumberOfNodes
  23456. * @param {Boolean} reposition
  23457. */
  23458. exports.clusterToFit = function(maxNumberOfNodes, reposition) {
  23459. var numberOfNodes = this.nodeIndices.length;
  23460. var maxLevels = 50;
  23461. var level = 0;
  23462. // we first cluster the hubs, then we pull in the outliers, repeat
  23463. while (numberOfNodes > maxNumberOfNodes && level < maxLevels) {
  23464. if (level % 3 == 0) {
  23465. this.forceAggregateHubs(true);
  23466. this.normalizeClusterLevels();
  23467. }
  23468. else {
  23469. this.increaseClusterLevel(); // this also includes a cluster normalization
  23470. }
  23471. numberOfNodes = this.nodeIndices.length;
  23472. level += 1;
  23473. }
  23474. // after the clustering we reposition the nodes to reduce the initial chaos
  23475. if (level > 0 && reposition == true) {
  23476. this.repositionNodes();
  23477. }
  23478. this._updateCalculationNodes();
  23479. };
  23480. /**
  23481. * This function can be called to open up a specific cluster. It is only called by
  23482. * It will unpack the cluster back one level.
  23483. *
  23484. * @param node | Node object: cluster to open.
  23485. */
  23486. exports.openCluster = function(node) {
  23487. var isMovingBeforeClustering = this.moving;
  23488. if (node.clusterSize > this.constants.clustering.sectorThreshold && this._nodeInActiveArea(node) &&
  23489. !(this._sector() == "default" && this.nodeIndices.length == 1)) {
  23490. // this loads a new sector, loads the nodes and edges and nodeIndices of it.
  23491. this._addSector(node);
  23492. var level = 0;
  23493. // we decluster until we reach a decent number of nodes
  23494. while ((this.nodeIndices.length < this.constants.clustering.initialMaxNodes) && (level < 10)) {
  23495. this.decreaseClusterLevel();
  23496. level += 1;
  23497. }
  23498. }
  23499. else {
  23500. this._expandClusterNode(node,false,true);
  23501. // update the index list, dynamic edges and labels
  23502. this._updateNodeIndexList();
  23503. this._updateDynamicEdges();
  23504. this._updateCalculationNodes();
  23505. this.updateLabels();
  23506. }
  23507. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  23508. if (this.moving != isMovingBeforeClustering) {
  23509. this.start();
  23510. }
  23511. };
  23512. /**
  23513. * This calls the updateClustes with default arguments
  23514. */
  23515. exports.updateClustersDefault = function() {
  23516. if (this.constants.clustering.enabled == true) {
  23517. this.updateClusters(0,false,false);
  23518. }
  23519. };
  23520. /**
  23521. * This function can be called to increase the cluster level. This means that the nodes with only one edge connection will
  23522. * be clustered with their connected node. This can be repeated as many times as needed.
  23523. * This can be called externally (by a keybind for instance) to reduce the complexity of big datasets.
  23524. */
  23525. exports.increaseClusterLevel = function() {
  23526. this.updateClusters(-1,false,true);
  23527. };
  23528. /**
  23529. * This function can be called to decrease the cluster level. This means that the nodes with only one edge connection will
  23530. * be unpacked if they are a cluster. This can be repeated as many times as needed.
  23531. * This can be called externally (by a key-bind for instance) to look into clusters without zooming.
  23532. */
  23533. exports.decreaseClusterLevel = function() {
  23534. this.updateClusters(1,false,true);
  23535. };
  23536. /**
  23537. * This is the main clustering function. It clusters and declusters on zoom or forced
  23538. * This function clusters on zoom, it can be called with a predefined zoom direction
  23539. * If out, check if we can form clusters, if in, check if we can open clusters.
  23540. * This function is only called from _zoom()
  23541. *
  23542. * @param {Number} zoomDirection | -1 / 0 / +1 for zoomOut / determineByZoom / zoomIn
  23543. * @param {Boolean} recursive | enabled or disable recursive calling of the opening of clusters
  23544. * @param {Boolean} force | enabled or disable forcing
  23545. * @param {Boolean} doNotStart | if true do not call start
  23546. *
  23547. */
  23548. exports.updateClusters = function(zoomDirection,recursive,force,doNotStart) {
  23549. var isMovingBeforeClustering = this.moving;
  23550. var amountOfNodes = this.nodeIndices.length;
  23551. // on zoom out collapse the sector if the scale is at the level the sector was made
  23552. if (this.previousScale > this.scale && zoomDirection == 0) {
  23553. this._collapseSector();
  23554. }
  23555. // check if we zoom in or out
  23556. if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
  23557. // forming clusters when forced pulls outliers in. When not forced, the edge length of the
  23558. // outer nodes determines if it is being clustered
  23559. this._formClusters(force);
  23560. }
  23561. else if (this.previousScale < this.scale || zoomDirection == 1) { // zoom in
  23562. if (force == true) {
  23563. // _openClusters checks for each node if the formationScale of the cluster is smaller than
  23564. // the current scale and if so, declusters. When forced, all clusters are reduced by one step
  23565. this._openClusters(recursive,force);
  23566. }
  23567. else {
  23568. // if a cluster takes up a set percentage of the active window
  23569. this._openClustersBySize();
  23570. }
  23571. }
  23572. this._updateNodeIndexList();
  23573. // if a cluster was NOT formed and the user zoomed out, we try clustering by hubs
  23574. if (this.nodeIndices.length == amountOfNodes && (this.previousScale > this.scale || zoomDirection == -1)) {
  23575. this._aggregateHubs(force);
  23576. this._updateNodeIndexList();
  23577. }
  23578. // we now reduce chains.
  23579. if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
  23580. this.handleChains();
  23581. this._updateNodeIndexList();
  23582. }
  23583. this.previousScale = this.scale;
  23584. // rest of the update the index list, dynamic edges and labels
  23585. this._updateDynamicEdges();
  23586. this.updateLabels();
  23587. // if a cluster was formed, we increase the clusterSession
  23588. if (this.nodeIndices.length < amountOfNodes) { // this means a clustering operation has taken place
  23589. this.clusterSession += 1;
  23590. // if clusters have been made, we normalize the cluster level
  23591. this.normalizeClusterLevels();
  23592. }
  23593. if (doNotStart == false || doNotStart === undefined) {
  23594. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  23595. if (this.moving != isMovingBeforeClustering) {
  23596. this.start();
  23597. }
  23598. }
  23599. this._updateCalculationNodes();
  23600. };
  23601. /**
  23602. * This function handles the chains. It is called on every updateClusters().
  23603. */
  23604. exports.handleChains = function() {
  23605. // after clustering we check how many chains there are
  23606. var chainPercentage = this._getChainFraction();
  23607. if (chainPercentage > this.constants.clustering.chainThreshold) {
  23608. this._reduceAmountOfChains(1 - this.constants.clustering.chainThreshold / chainPercentage)
  23609. }
  23610. };
  23611. /**
  23612. * this functions starts clustering by hubs
  23613. * The minimum hub threshold is set globally
  23614. *
  23615. * @private
  23616. */
  23617. exports._aggregateHubs = function(force) {
  23618. this._getHubSize();
  23619. this._formClustersByHub(force,false);
  23620. };
  23621. /**
  23622. * This function is fired by keypress. It forces hubs to form.
  23623. *
  23624. */
  23625. exports.forceAggregateHubs = function(doNotStart) {
  23626. var isMovingBeforeClustering = this.moving;
  23627. var amountOfNodes = this.nodeIndices.length;
  23628. this._aggregateHubs(true);
  23629. // update the index list, dynamic edges and labels
  23630. this._updateNodeIndexList();
  23631. this._updateDynamicEdges();
  23632. this.updateLabels();
  23633. // if a cluster was formed, we increase the clusterSession
  23634. if (this.nodeIndices.length != amountOfNodes) {
  23635. this.clusterSession += 1;
  23636. }
  23637. if (doNotStart == false || doNotStart === undefined) {
  23638. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  23639. if (this.moving != isMovingBeforeClustering) {
  23640. this.start();
  23641. }
  23642. }
  23643. };
  23644. /**
  23645. * If a cluster takes up more than a set percentage of the screen, open the cluster
  23646. *
  23647. * @private
  23648. */
  23649. exports._openClustersBySize = function() {
  23650. for (var nodeId in this.nodes) {
  23651. if (this.nodes.hasOwnProperty(nodeId)) {
  23652. var node = this.nodes[nodeId];
  23653. if (node.inView() == true) {
  23654. if ((node.width*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  23655. (node.height*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  23656. this.openCluster(node);
  23657. }
  23658. }
  23659. }
  23660. }
  23661. };
  23662. /**
  23663. * This function loops over all nodes in the nodeIndices list. For each node it checks if it is a cluster and if it
  23664. * has to be opened based on the current zoom level.
  23665. *
  23666. * @private
  23667. */
  23668. exports._openClusters = function(recursive,force) {
  23669. for (var i = 0; i < this.nodeIndices.length; i++) {
  23670. var node = this.nodes[this.nodeIndices[i]];
  23671. this._expandClusterNode(node,recursive,force);
  23672. this._updateCalculationNodes();
  23673. }
  23674. };
  23675. /**
  23676. * This function checks if a node has to be opened. This is done by checking the zoom level.
  23677. * If the node contains child nodes, this function is recursively called on the child nodes as well.
  23678. * This recursive behaviour is optional and can be set by the recursive argument.
  23679. *
  23680. * @param {Node} parentNode | to check for cluster and expand
  23681. * @param {Boolean} recursive | enabled or disable recursive calling
  23682. * @param {Boolean} force | enabled or disable forcing
  23683. * @param {Boolean} [openAll] | This will recursively force all nodes in the parent to be released
  23684. * @private
  23685. */
  23686. exports._expandClusterNode = function(parentNode, recursive, force, openAll) {
  23687. // first check if node is a cluster
  23688. if (parentNode.clusterSize > 1) {
  23689. // this means that on a double tap event or a zoom event, the cluster fully unpacks if it is smaller than 20
  23690. if (parentNode.clusterSize < this.constants.clustering.sectorThreshold) {
  23691. openAll = true;
  23692. }
  23693. recursive = openAll ? true : recursive;
  23694. // if the last child has been added on a smaller scale than current scale decluster
  23695. if (parentNode.formationScale < this.scale || force == true) {
  23696. // we will check if any of the contained child nodes should be removed from the cluster
  23697. for (var containedNodeId in parentNode.containedNodes) {
  23698. if (parentNode.containedNodes.hasOwnProperty(containedNodeId)) {
  23699. var childNode = parentNode.containedNodes[containedNodeId];
  23700. // force expand will expand the largest cluster size clusters. Since we cluster from outside in, we assume that
  23701. // the largest cluster is the one that comes from outside
  23702. if (force == true) {
  23703. if (childNode.clusterSession == parentNode.clusterSessions[parentNode.clusterSessions.length-1]
  23704. || openAll) {
  23705. this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
  23706. }
  23707. }
  23708. else {
  23709. if (this._nodeInActiveArea(parentNode)) {
  23710. this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
  23711. }
  23712. }
  23713. }
  23714. }
  23715. }
  23716. }
  23717. };
  23718. /**
  23719. * ONLY CALLED FROM _expandClusterNode
  23720. *
  23721. * This function will expel a child_node from a parent_node. This is to de-cluster the node. This function will remove
  23722. * the child node from the parent contained_node object and put it back into the global nodes object.
  23723. * The same holds for the edge that was connected to the child node. It is moved back into the global edges object.
  23724. *
  23725. * @param {Node} parentNode | the parent node
  23726. * @param {String} containedNodeId | child_node id as it is contained in the containedNodes object of the parent node
  23727. * @param {Boolean} recursive | This will also check if the child needs to be expanded.
  23728. * With force and recursive both true, the entire cluster is unpacked
  23729. * @param {Boolean} force | This will disregard the zoom level and will expel this child from the parent
  23730. * @param {Boolean} openAll | This will recursively force all nodes in the parent to be released
  23731. * @private
  23732. */
  23733. exports._expelChildFromParent = function(parentNode, containedNodeId, recursive, force, openAll) {
  23734. var childNode = parentNode.containedNodes[containedNodeId];
  23735. // if child node has been added on smaller scale than current, kick out
  23736. if (childNode.formationScale < this.scale || force == true) {
  23737. // unselect all selected items
  23738. this._unselectAll();
  23739. // put the child node back in the global nodes object
  23740. this.nodes[containedNodeId] = childNode;
  23741. // release the contained edges from this childNode back into the global edges
  23742. this._releaseContainedEdges(parentNode,childNode);
  23743. // reconnect rerouted edges to the childNode
  23744. this._connectEdgeBackToChild(parentNode,childNode);
  23745. // validate all edges in dynamicEdges
  23746. this._validateEdges(parentNode);
  23747. // undo the changes from the clustering operation on the parent node
  23748. parentNode.options.mass -= childNode.options.mass;
  23749. parentNode.clusterSize -= childNode.clusterSize;
  23750. parentNode.options.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
  23751. parentNode.dynamicEdgesLength = parentNode.dynamicEdges.length;
  23752. // place the child node near the parent, not at the exact same location to avoid chaos in the system
  23753. childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random());
  23754. childNode.y = parentNode.y + parentNode.growthIndicator * (0.5 - Math.random());
  23755. // remove node from the list
  23756. delete parentNode.containedNodes[containedNodeId];
  23757. // check if there are other childs with this clusterSession in the parent.
  23758. var othersPresent = false;
  23759. for (var childNodeId in parentNode.containedNodes) {
  23760. if (parentNode.containedNodes.hasOwnProperty(childNodeId)) {
  23761. if (parentNode.containedNodes[childNodeId].clusterSession == childNode.clusterSession) {
  23762. othersPresent = true;
  23763. break;
  23764. }
  23765. }
  23766. }
  23767. // if there are no others, remove the cluster session from the list
  23768. if (othersPresent == false) {
  23769. parentNode.clusterSessions.pop();
  23770. }
  23771. this._repositionBezierNodes(childNode);
  23772. // this._repositionBezierNodes(parentNode);
  23773. // remove the clusterSession from the child node
  23774. childNode.clusterSession = 0;
  23775. // recalculate the size of the node on the next time the node is rendered
  23776. parentNode.clearSizeCache();
  23777. // restart the simulation to reorganise all nodes
  23778. this.moving = true;
  23779. }
  23780. // check if a further expansion step is possible if recursivity is enabled
  23781. if (recursive == true) {
  23782. this._expandClusterNode(childNode,recursive,force,openAll);
  23783. }
  23784. };
  23785. /**
  23786. * position the bezier nodes at the center of the edges
  23787. *
  23788. * @param node
  23789. * @private
  23790. */
  23791. exports._repositionBezierNodes = function(node) {
  23792. for (var i = 0; i < node.dynamicEdges.length; i++) {
  23793. node.dynamicEdges[i].positionBezierNode();
  23794. }
  23795. };
  23796. /**
  23797. * This function checks if any nodes at the end of their trees have edges below a threshold length
  23798. * This function is called only from updateClusters()
  23799. * forceLevelCollapse ignores the length of the edge and collapses one level
  23800. * This means that a node with only one edge will be clustered with its connected node
  23801. *
  23802. * @private
  23803. * @param {Boolean} force
  23804. */
  23805. exports._formClusters = function(force) {
  23806. if (force == false) {
  23807. this._formClustersByZoom();
  23808. }
  23809. else {
  23810. this._forceClustersByZoom();
  23811. }
  23812. };
  23813. /**
  23814. * This function handles the clustering by zooming out, this is based on a minimum edge distance
  23815. *
  23816. * @private
  23817. */
  23818. exports._formClustersByZoom = function() {
  23819. var dx,dy,length,
  23820. minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
  23821. // check if any edges are shorter than minLength and start the clustering
  23822. // the clustering favours the node with the larger mass
  23823. for (var edgeId in this.edges) {
  23824. if (this.edges.hasOwnProperty(edgeId)) {
  23825. var edge = this.edges[edgeId];
  23826. if (edge.connected) {
  23827. if (edge.toId != edge.fromId) {
  23828. dx = (edge.to.x - edge.from.x);
  23829. dy = (edge.to.y - edge.from.y);
  23830. length = Math.sqrt(dx * dx + dy * dy);
  23831. if (length < minLength) {
  23832. // first check which node is larger
  23833. var parentNode = edge.from;
  23834. var childNode = edge.to;
  23835. if (edge.to.options.mass > edge.from.options.mass) {
  23836. parentNode = edge.to;
  23837. childNode = edge.from;
  23838. }
  23839. if (childNode.dynamicEdgesLength == 1) {
  23840. this._addToCluster(parentNode,childNode,false);
  23841. }
  23842. else if (parentNode.dynamicEdgesLength == 1) {
  23843. this._addToCluster(childNode,parentNode,false);
  23844. }
  23845. }
  23846. }
  23847. }
  23848. }
  23849. }
  23850. };
  23851. /**
  23852. * This function forces the network to cluster all nodes with only one connecting edge to their
  23853. * connected node.
  23854. *
  23855. * @private
  23856. */
  23857. exports._forceClustersByZoom = function() {
  23858. for (var nodeId in this.nodes) {
  23859. // another node could have absorbed this child.
  23860. if (this.nodes.hasOwnProperty(nodeId)) {
  23861. var childNode = this.nodes[nodeId];
  23862. // the edges can be swallowed by another decrease
  23863. if (childNode.dynamicEdgesLength == 1 && childNode.dynamicEdges.length != 0) {
  23864. var edge = childNode.dynamicEdges[0];
  23865. var parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId];
  23866. // group to the largest node
  23867. if (childNode.id != parentNode.id) {
  23868. if (parentNode.options.mass > childNode.options.mass) {
  23869. this._addToCluster(parentNode,childNode,true);
  23870. }
  23871. else {
  23872. this._addToCluster(childNode,parentNode,true);
  23873. }
  23874. }
  23875. }
  23876. }
  23877. }
  23878. };
  23879. /**
  23880. * To keep the nodes of roughly equal size we normalize the cluster levels.
  23881. * This function clusters a node to its smallest connected neighbour.
  23882. *
  23883. * @param node
  23884. * @private
  23885. */
  23886. exports._clusterToSmallestNeighbour = function(node) {
  23887. var smallestNeighbour = -1;
  23888. var smallestNeighbourNode = null;
  23889. for (var i = 0; i < node.dynamicEdges.length; i++) {
  23890. if (node.dynamicEdges[i] !== undefined) {
  23891. var neighbour = null;
  23892. if (node.dynamicEdges[i].fromId != node.id) {
  23893. neighbour = node.dynamicEdges[i].from;
  23894. }
  23895. else if (node.dynamicEdges[i].toId != node.id) {
  23896. neighbour = node.dynamicEdges[i].to;
  23897. }
  23898. if (neighbour != null && smallestNeighbour > neighbour.clusterSessions.length) {
  23899. smallestNeighbour = neighbour.clusterSessions.length;
  23900. smallestNeighbourNode = neighbour;
  23901. }
  23902. }
  23903. }
  23904. if (neighbour != null && this.nodes[neighbour.id] !== undefined) {
  23905. this._addToCluster(neighbour, node, true);
  23906. }
  23907. };
  23908. /**
  23909. * This function forms clusters from hubs, it loops over all nodes
  23910. *
  23911. * @param {Boolean} force | Disregard zoom level
  23912. * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
  23913. * @private
  23914. */
  23915. exports._formClustersByHub = function(force, onlyEqual) {
  23916. // we loop over all nodes in the list
  23917. for (var nodeId in this.nodes) {
  23918. // we check if it is still available since it can be used by the clustering in this loop
  23919. if (this.nodes.hasOwnProperty(nodeId)) {
  23920. this._formClusterFromHub(this.nodes[nodeId],force,onlyEqual);
  23921. }
  23922. }
  23923. };
  23924. /**
  23925. * This function forms a cluster from a specific preselected hub node
  23926. *
  23927. * @param {Node} hubNode | the node we will cluster as a hub
  23928. * @param {Boolean} force | Disregard zoom level
  23929. * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
  23930. * @param {Number} [absorptionSizeOffset] |
  23931. * @private
  23932. */
  23933. exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSizeOffset) {
  23934. if (absorptionSizeOffset === undefined) {
  23935. absorptionSizeOffset = 0;
  23936. }
  23937. // we decide if the node is a hub
  23938. if ((hubNode.dynamicEdgesLength >= this.hubThreshold && onlyEqual == false) ||
  23939. (hubNode.dynamicEdgesLength == this.hubThreshold && onlyEqual == true)) {
  23940. // initialize variables
  23941. var dx,dy,length;
  23942. var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
  23943. var allowCluster = false;
  23944. // we create a list of edges because the dynamicEdges change over the course of this loop
  23945. var edgesIdarray = [];
  23946. var amountOfInitialEdges = hubNode.dynamicEdges.length;
  23947. for (var j = 0; j < amountOfInitialEdges; j++) {
  23948. edgesIdarray.push(hubNode.dynamicEdges[j].id);
  23949. }
  23950. // if the hub clustering is not forces, we check if one of the edges connected
  23951. // to a cluster is small enough based on the constants.clustering.clusterEdgeThreshold
  23952. if (force == false) {
  23953. allowCluster = false;
  23954. for (j = 0; j < amountOfInitialEdges; j++) {
  23955. var edge = this.edges[edgesIdarray[j]];
  23956. if (edge !== undefined) {
  23957. if (edge.connected) {
  23958. if (edge.toId != edge.fromId) {
  23959. dx = (edge.to.x - edge.from.x);
  23960. dy = (edge.to.y - edge.from.y);
  23961. length = Math.sqrt(dx * dx + dy * dy);
  23962. if (length < minLength) {
  23963. allowCluster = true;
  23964. break;
  23965. }
  23966. }
  23967. }
  23968. }
  23969. }
  23970. }
  23971. // start the clustering if allowed
  23972. if ((!force && allowCluster) || force) {
  23973. // we loop over all edges INITIALLY connected to this hub
  23974. for (j = 0; j < amountOfInitialEdges; j++) {
  23975. edge = this.edges[edgesIdarray[j]];
  23976. // the edge can be clustered by this function in a previous loop
  23977. if (edge !== undefined) {
  23978. var childNode = this.nodes[(edge.fromId == hubNode.id) ? edge.toId : edge.fromId];
  23979. // we do not want hubs to merge with other hubs nor do we want to cluster itself.
  23980. if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) &&
  23981. (childNode.id != hubNode.id)) {
  23982. this._addToCluster(hubNode,childNode,force);
  23983. }
  23984. }
  23985. }
  23986. }
  23987. }
  23988. };
  23989. /**
  23990. * This function adds the child node to the parent node, creating a cluster if it is not already.
  23991. *
  23992. * @param {Node} parentNode | this is the node that will house the child node
  23993. * @param {Node} childNode | this node will be deleted from the global this.nodes and stored in the parent node
  23994. * @param {Boolean} force | true will only update the remainingEdges at the very end of the clustering, ensuring single level collapse
  23995. * @private
  23996. */
  23997. exports._addToCluster = function(parentNode, childNode, force) {
  23998. // join child node in the parent node
  23999. parentNode.containedNodes[childNode.id] = childNode;
  24000. // manage all the edges connected to the child and parent nodes
  24001. for (var i = 0; i < childNode.dynamicEdges.length; i++) {
  24002. var edge = childNode.dynamicEdges[i];
  24003. if (edge.toId == parentNode.id || edge.fromId == parentNode.id) { // edge connected to parentNode
  24004. this._addToContainedEdges(parentNode,childNode,edge);
  24005. }
  24006. else {
  24007. this._connectEdgeToCluster(parentNode,childNode,edge);
  24008. }
  24009. }
  24010. // a contained node has no dynamic edges.
  24011. childNode.dynamicEdges = [];
  24012. // remove circular edges from clusters
  24013. this._containCircularEdgesFromNode(parentNode,childNode);
  24014. // remove the childNode from the global nodes object
  24015. delete this.nodes[childNode.id];
  24016. // update the properties of the child and parent
  24017. var massBefore = parentNode.options.mass;
  24018. childNode.clusterSession = this.clusterSession;
  24019. parentNode.options.mass += childNode.options.mass;
  24020. parentNode.clusterSize += childNode.clusterSize;
  24021. parentNode.options.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
  24022. // keep track of the clustersessions so we can open the cluster up as it has been formed.
  24023. if (parentNode.clusterSessions[parentNode.clusterSessions.length - 1] != this.clusterSession) {
  24024. parentNode.clusterSessions.push(this.clusterSession);
  24025. }
  24026. // forced clusters only open from screen size and double tap
  24027. if (force == true) {
  24028. // parentNode.formationScale = Math.pow(1 - (1.0/11.0),this.clusterSession+3);
  24029. parentNode.formationScale = 0;
  24030. }
  24031. else {
  24032. parentNode.formationScale = this.scale; // The latest child has been added on this scale
  24033. }
  24034. // recalculate the size of the node on the next time the node is rendered
  24035. parentNode.clearSizeCache();
  24036. // set the pop-out scale for the childnode
  24037. parentNode.containedNodes[childNode.id].formationScale = parentNode.formationScale;
  24038. // nullify the movement velocity of the child, this is to avoid hectic behaviour
  24039. childNode.clearVelocity();
  24040. // the mass has altered, preservation of energy dictates the velocity to be updated
  24041. parentNode.updateVelocity(massBefore);
  24042. // restart the simulation to reorganise all nodes
  24043. this.moving = true;
  24044. };
  24045. /**
  24046. * This function will apply the changes made to the remainingEdges during the formation of the clusters.
  24047. * This is a seperate function to allow for level-wise collapsing of the node barnesHutTree.
  24048. * It has to be called if a level is collapsed. It is called by _formClusters().
  24049. * @private
  24050. */
  24051. exports._updateDynamicEdges = function() {
  24052. for (var i = 0; i < this.nodeIndices.length; i++) {
  24053. var node = this.nodes[this.nodeIndices[i]];
  24054. node.dynamicEdgesLength = node.dynamicEdges.length;
  24055. // this corrects for multiple edges pointing at the same other node
  24056. var correction = 0;
  24057. if (node.dynamicEdgesLength > 1) {
  24058. for (var j = 0; j < node.dynamicEdgesLength - 1; j++) {
  24059. var edgeToId = node.dynamicEdges[j].toId;
  24060. var edgeFromId = node.dynamicEdges[j].fromId;
  24061. for (var k = j+1; k < node.dynamicEdgesLength; k++) {
  24062. if ((node.dynamicEdges[k].toId == edgeToId && node.dynamicEdges[k].fromId == edgeFromId) ||
  24063. (node.dynamicEdges[k].fromId == edgeToId && node.dynamicEdges[k].toId == edgeFromId)) {
  24064. correction += 1;
  24065. }
  24066. }
  24067. }
  24068. }
  24069. node.dynamicEdgesLength -= correction;
  24070. }
  24071. };
  24072. /**
  24073. * This adds an edge from the childNode to the contained edges of the parent node
  24074. *
  24075. * @param parentNode | Node object
  24076. * @param childNode | Node object
  24077. * @param edge | Edge object
  24078. * @private
  24079. */
  24080. exports._addToContainedEdges = function(parentNode, childNode, edge) {
  24081. // create an array object if it does not yet exist for this childNode
  24082. if (!(parentNode.containedEdges.hasOwnProperty(childNode.id))) {
  24083. parentNode.containedEdges[childNode.id] = []
  24084. }
  24085. // add this edge to the list
  24086. parentNode.containedEdges[childNode.id].push(edge);
  24087. // remove the edge from the global edges object
  24088. delete this.edges[edge.id];
  24089. // remove the edge from the parent object
  24090. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  24091. if (parentNode.dynamicEdges[i].id == edge.id) {
  24092. parentNode.dynamicEdges.splice(i,1);
  24093. break;
  24094. }
  24095. }
  24096. };
  24097. /**
  24098. * This function connects an edge that was connected to a child node to the parent node.
  24099. * It keeps track of which nodes it has been connected to with the originalId array.
  24100. *
  24101. * @param {Node} parentNode | Node object
  24102. * @param {Node} childNode | Node object
  24103. * @param {Edge} edge | Edge object
  24104. * @private
  24105. */
  24106. exports._connectEdgeToCluster = function(parentNode, childNode, edge) {
  24107. // handle circular edges
  24108. if (edge.toId == edge.fromId) {
  24109. this._addToContainedEdges(parentNode, childNode, edge);
  24110. }
  24111. else {
  24112. if (edge.toId == childNode.id) { // edge connected to other node on the "to" side
  24113. edge.originalToId.push(childNode.id);
  24114. edge.to = parentNode;
  24115. edge.toId = parentNode.id;
  24116. }
  24117. else { // edge connected to other node with the "from" side
  24118. edge.originalFromId.push(childNode.id);
  24119. edge.from = parentNode;
  24120. edge.fromId = parentNode.id;
  24121. }
  24122. this._addToReroutedEdges(parentNode,childNode,edge);
  24123. }
  24124. };
  24125. /**
  24126. * If a node is connected to itself, a circular edge is drawn. When clustering we want to contain
  24127. * these edges inside of the cluster.
  24128. *
  24129. * @param parentNode
  24130. * @param childNode
  24131. * @private
  24132. */
  24133. exports._containCircularEdgesFromNode = function(parentNode, childNode) {
  24134. // manage all the edges connected to the child and parent nodes
  24135. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  24136. var edge = parentNode.dynamicEdges[i];
  24137. // handle circular edges
  24138. if (edge.toId == edge.fromId) {
  24139. this._addToContainedEdges(parentNode, childNode, edge);
  24140. }
  24141. }
  24142. };
  24143. /**
  24144. * This adds an edge from the childNode to the rerouted edges of the parent node
  24145. *
  24146. * @param parentNode | Node object
  24147. * @param childNode | Node object
  24148. * @param edge | Edge object
  24149. * @private
  24150. */
  24151. exports._addToReroutedEdges = function(parentNode, childNode, edge) {
  24152. // create an array object if it does not yet exist for this childNode
  24153. // we store the edge in the rerouted edges so we can restore it when the cluster pops open
  24154. if (!(parentNode.reroutedEdges.hasOwnProperty(childNode.id))) {
  24155. parentNode.reroutedEdges[childNode.id] = [];
  24156. }
  24157. parentNode.reroutedEdges[childNode.id].push(edge);
  24158. // this edge becomes part of the dynamicEdges of the cluster node
  24159. parentNode.dynamicEdges.push(edge);
  24160. };
  24161. /**
  24162. * This function connects an edge that was connected to a cluster node back to the child node.
  24163. *
  24164. * @param parentNode | Node object
  24165. * @param childNode | Node object
  24166. * @private
  24167. */
  24168. exports._connectEdgeBackToChild = function(parentNode, childNode) {
  24169. if (parentNode.reroutedEdges.hasOwnProperty(childNode.id)) {
  24170. for (var i = 0; i < parentNode.reroutedEdges[childNode.id].length; i++) {
  24171. var edge = parentNode.reroutedEdges[childNode.id][i];
  24172. if (edge.originalFromId[edge.originalFromId.length-1] == childNode.id) {
  24173. edge.originalFromId.pop();
  24174. edge.fromId = childNode.id;
  24175. edge.from = childNode;
  24176. }
  24177. else {
  24178. edge.originalToId.pop();
  24179. edge.toId = childNode.id;
  24180. edge.to = childNode;
  24181. }
  24182. // append this edge to the list of edges connecting to the childnode
  24183. childNode.dynamicEdges.push(edge);
  24184. // remove the edge from the parent object
  24185. for (var j = 0; j < parentNode.dynamicEdges.length; j++) {
  24186. if (parentNode.dynamicEdges[j].id == edge.id) {
  24187. parentNode.dynamicEdges.splice(j,1);
  24188. break;
  24189. }
  24190. }
  24191. }
  24192. // remove the entry from the rerouted edges
  24193. delete parentNode.reroutedEdges[childNode.id];
  24194. }
  24195. };
  24196. /**
  24197. * When loops are clustered, an edge can be both in the rerouted array and the contained array.
  24198. * This function is called last to verify that all edges in dynamicEdges are in fact connected to the
  24199. * parentNode
  24200. *
  24201. * @param parentNode | Node object
  24202. * @private
  24203. */
  24204. exports._validateEdges = function(parentNode) {
  24205. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  24206. var edge = parentNode.dynamicEdges[i];
  24207. if (parentNode.id != edge.toId && parentNode.id != edge.fromId) {
  24208. parentNode.dynamicEdges.splice(i,1);
  24209. }
  24210. }
  24211. };
  24212. /**
  24213. * This function released the contained edges back into the global domain and puts them back into the
  24214. * dynamic edges of both parent and child.
  24215. *
  24216. * @param {Node} parentNode |
  24217. * @param {Node} childNode |
  24218. * @private
  24219. */
  24220. exports._releaseContainedEdges = function(parentNode, childNode) {
  24221. for (var i = 0; i < parentNode.containedEdges[childNode.id].length; i++) {
  24222. var edge = parentNode.containedEdges[childNode.id][i];
  24223. // put the edge back in the global edges object
  24224. this.edges[edge.id] = edge;
  24225. // put the edge back in the dynamic edges of the child and parent
  24226. childNode.dynamicEdges.push(edge);
  24227. parentNode.dynamicEdges.push(edge);
  24228. }
  24229. // remove the entry from the contained edges
  24230. delete parentNode.containedEdges[childNode.id];
  24231. };
  24232. // ------------------- UTILITY FUNCTIONS ---------------------------- //
  24233. /**
  24234. * This updates the node labels for all nodes (for debugging purposes)
  24235. */
  24236. exports.updateLabels = function() {
  24237. var nodeId;
  24238. // update node labels
  24239. for (nodeId in this.nodes) {
  24240. if (this.nodes.hasOwnProperty(nodeId)) {
  24241. var node = this.nodes[nodeId];
  24242. if (node.clusterSize > 1) {
  24243. node.label = "[".concat(String(node.clusterSize),"]");
  24244. }
  24245. }
  24246. }
  24247. // update node labels
  24248. for (nodeId in this.nodes) {
  24249. if (this.nodes.hasOwnProperty(nodeId)) {
  24250. node = this.nodes[nodeId];
  24251. if (node.clusterSize == 1) {
  24252. if (node.originalLabel !== undefined) {
  24253. node.label = node.originalLabel;
  24254. }
  24255. else {
  24256. node.label = String(node.id);
  24257. }
  24258. }
  24259. }
  24260. }
  24261. // /* Debug Override */
  24262. // for (nodeId in this.nodes) {
  24263. // if (this.nodes.hasOwnProperty(nodeId)) {
  24264. // node = this.nodes[nodeId];
  24265. // node.label = String(node.level);
  24266. // }
  24267. // }
  24268. };
  24269. /**
  24270. * We want to keep the cluster level distribution rather small. This means we do not want unclustered nodes
  24271. * if the rest of the nodes are already a few cluster levels in.
  24272. * To fix this we use this function. It determines the min and max cluster level and sends nodes that have not
  24273. * clustered enough to the clusterToSmallestNeighbours function.
  24274. */
  24275. exports.normalizeClusterLevels = function() {
  24276. var maxLevel = 0;
  24277. var minLevel = 1e9;
  24278. var clusterLevel = 0;
  24279. var nodeId;
  24280. // we loop over all nodes in the list
  24281. for (nodeId in this.nodes) {
  24282. if (this.nodes.hasOwnProperty(nodeId)) {
  24283. clusterLevel = this.nodes[nodeId].clusterSessions.length;
  24284. if (maxLevel < clusterLevel) {maxLevel = clusterLevel;}
  24285. if (minLevel > clusterLevel) {minLevel = clusterLevel;}
  24286. }
  24287. }
  24288. if (maxLevel - minLevel > this.constants.clustering.clusterLevelDifference) {
  24289. var amountOfNodes = this.nodeIndices.length;
  24290. var targetLevel = maxLevel - this.constants.clustering.clusterLevelDifference;
  24291. // we loop over all nodes in the list
  24292. for (nodeId in this.nodes) {
  24293. if (this.nodes.hasOwnProperty(nodeId)) {
  24294. if (this.nodes[nodeId].clusterSessions.length < targetLevel) {
  24295. this._clusterToSmallestNeighbour(this.nodes[nodeId]);
  24296. }
  24297. }
  24298. }
  24299. this._updateNodeIndexList();
  24300. this._updateDynamicEdges();
  24301. // if a cluster was formed, we increase the clusterSession
  24302. if (this.nodeIndices.length != amountOfNodes) {
  24303. this.clusterSession += 1;
  24304. }
  24305. }
  24306. };
  24307. /**
  24308. * This function determines if the cluster we want to decluster is in the active area
  24309. * this means around the zoom center
  24310. *
  24311. * @param {Node} node
  24312. * @returns {boolean}
  24313. * @private
  24314. */
  24315. exports._nodeInActiveArea = function(node) {
  24316. return (
  24317. Math.abs(node.x - this.areaCenter.x) <= this.constants.clustering.activeAreaBoxSize/this.scale
  24318. &&
  24319. Math.abs(node.y - this.areaCenter.y) <= this.constants.clustering.activeAreaBoxSize/this.scale
  24320. )
  24321. };
  24322. /**
  24323. * This is an adaptation of the original repositioning function. This is called if the system is clustered initially
  24324. * It puts large clusters away from the center and randomizes the order.
  24325. *
  24326. */
  24327. exports.repositionNodes = function() {
  24328. for (var i = 0; i < this.nodeIndices.length; i++) {
  24329. var node = this.nodes[this.nodeIndices[i]];
  24330. if ((node.xFixed == false || node.yFixed == false)) {
  24331. var radius = 10 * 0.1*this.nodeIndices.length * Math.min(100,node.options.mass);
  24332. var angle = 2 * Math.PI * Math.random();
  24333. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  24334. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  24335. this._repositionBezierNodes(node);
  24336. }
  24337. }
  24338. };
  24339. /**
  24340. * We determine how many connections denote an important hub.
  24341. * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
  24342. *
  24343. * @private
  24344. */
  24345. exports._getHubSize = function() {
  24346. var average = 0;
  24347. var averageSquared = 0;
  24348. var hubCounter = 0;
  24349. var largestHub = 0;
  24350. for (var i = 0; i < this.nodeIndices.length; i++) {
  24351. var node = this.nodes[this.nodeIndices[i]];
  24352. if (node.dynamicEdgesLength > largestHub) {
  24353. largestHub = node.dynamicEdgesLength;
  24354. }
  24355. average += node.dynamicEdgesLength;
  24356. averageSquared += Math.pow(node.dynamicEdgesLength,2);
  24357. hubCounter += 1;
  24358. }
  24359. average = average / hubCounter;
  24360. averageSquared = averageSquared / hubCounter;
  24361. var variance = averageSquared - Math.pow(average,2);
  24362. var standardDeviation = Math.sqrt(variance);
  24363. this.hubThreshold = Math.floor(average + 2*standardDeviation);
  24364. // always have at least one to cluster
  24365. if (this.hubThreshold > largestHub) {
  24366. this.hubThreshold = largestHub;
  24367. }
  24368. // console.log("average",average,"averageSQ",averageSquared,"var",variance,"std",standardDeviation);
  24369. // console.log("hubThreshold:",this.hubThreshold);
  24370. };
  24371. /**
  24372. * We reduce the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
  24373. * with this amount we can cluster specifically on these chains.
  24374. *
  24375. * @param {Number} fraction | between 0 and 1, the percentage of chains to reduce
  24376. * @private
  24377. */
  24378. exports._reduceAmountOfChains = function(fraction) {
  24379. this.hubThreshold = 2;
  24380. var reduceAmount = Math.floor(this.nodeIndices.length * fraction);
  24381. for (var nodeId in this.nodes) {
  24382. if (this.nodes.hasOwnProperty(nodeId)) {
  24383. if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) {
  24384. if (reduceAmount > 0) {
  24385. this._formClusterFromHub(this.nodes[nodeId],true,true,1);
  24386. reduceAmount -= 1;
  24387. }
  24388. }
  24389. }
  24390. }
  24391. };
  24392. /**
  24393. * We get the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
  24394. * with this amount we can cluster specifically on these chains.
  24395. *
  24396. * @private
  24397. */
  24398. exports._getChainFraction = function() {
  24399. var chains = 0;
  24400. var total = 0;
  24401. for (var nodeId in this.nodes) {
  24402. if (this.nodes.hasOwnProperty(nodeId)) {
  24403. if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) {
  24404. chains += 1;
  24405. }
  24406. total += 1;
  24407. }
  24408. }
  24409. return chains/total;
  24410. };
  24411. /***/ },
  24412. /* 54 */
  24413. /***/ function(module, exports, __webpack_require__) {
  24414. var util = __webpack_require__(1);
  24415. /**
  24416. * Creation of the SectorMixin var.
  24417. *
  24418. * This contains all the functions the Network object can use to employ the sector system.
  24419. * The sector system is always used by Network, though the benefits only apply to the use of clustering.
  24420. * If clustering is not used, there is no overhead except for a duplicate object with references to nodes and edges.
  24421. */
  24422. /**
  24423. * This function is only called by the setData function of the Network object.
  24424. * This loads the global references into the active sector. This initializes the sector.
  24425. *
  24426. * @private
  24427. */
  24428. exports._putDataInSector = function() {
  24429. this.sectors["active"][this._sector()].nodes = this.nodes;
  24430. this.sectors["active"][this._sector()].edges = this.edges;
  24431. this.sectors["active"][this._sector()].nodeIndices = this.nodeIndices;
  24432. };
  24433. /**
  24434. * /**
  24435. * This function sets the global references to nodes, edges and nodeIndices back to
  24436. * those of the supplied (active) sector. If a type is defined, do the specific type
  24437. *
  24438. * @param {String} sectorId
  24439. * @param {String} [sectorType] | "active" or "frozen"
  24440. * @private
  24441. */
  24442. exports._switchToSector = function(sectorId, sectorType) {
  24443. if (sectorType === undefined || sectorType == "active") {
  24444. this._switchToActiveSector(sectorId);
  24445. }
  24446. else {
  24447. this._switchToFrozenSector(sectorId);
  24448. }
  24449. };
  24450. /**
  24451. * This function sets the global references to nodes, edges and nodeIndices back to
  24452. * those of the supplied active sector.
  24453. *
  24454. * @param sectorId
  24455. * @private
  24456. */
  24457. exports._switchToActiveSector = function(sectorId) {
  24458. this.nodeIndices = this.sectors["active"][sectorId]["nodeIndices"];
  24459. this.nodes = this.sectors["active"][sectorId]["nodes"];
  24460. this.edges = this.sectors["active"][sectorId]["edges"];
  24461. };
  24462. /**
  24463. * This function sets the global references to nodes, edges and nodeIndices back to
  24464. * those of the supplied active sector.
  24465. *
  24466. * @private
  24467. */
  24468. exports._switchToSupportSector = function() {
  24469. this.nodeIndices = this.sectors["support"]["nodeIndices"];
  24470. this.nodes = this.sectors["support"]["nodes"];
  24471. this.edges = this.sectors["support"]["edges"];
  24472. };
  24473. /**
  24474. * This function sets the global references to nodes, edges and nodeIndices back to
  24475. * those of the supplied frozen sector.
  24476. *
  24477. * @param sectorId
  24478. * @private
  24479. */
  24480. exports._switchToFrozenSector = function(sectorId) {
  24481. this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"];
  24482. this.nodes = this.sectors["frozen"][sectorId]["nodes"];
  24483. this.edges = this.sectors["frozen"][sectorId]["edges"];
  24484. };
  24485. /**
  24486. * This function sets the global references to nodes, edges and nodeIndices back to
  24487. * those of the currently active sector.
  24488. *
  24489. * @private
  24490. */
  24491. exports._loadLatestSector = function() {
  24492. this._switchToSector(this._sector());
  24493. };
  24494. /**
  24495. * This function returns the currently active sector Id
  24496. *
  24497. * @returns {String}
  24498. * @private
  24499. */
  24500. exports._sector = function() {
  24501. return this.activeSector[this.activeSector.length-1];
  24502. };
  24503. /**
  24504. * This function returns the previously active sector Id
  24505. *
  24506. * @returns {String}
  24507. * @private
  24508. */
  24509. exports._previousSector = function() {
  24510. if (this.activeSector.length > 1) {
  24511. return this.activeSector[this.activeSector.length-2];
  24512. }
  24513. else {
  24514. throw new TypeError('there are not enough sectors in the this.activeSector array.');
  24515. }
  24516. };
  24517. /**
  24518. * We add the active sector at the end of the this.activeSector array
  24519. * This ensures it is the currently active sector returned by _sector() and it reaches the top
  24520. * of the activeSector stack. When we reverse our steps we move from the end to the beginning of this stack.
  24521. *
  24522. * @param newId
  24523. * @private
  24524. */
  24525. exports._setActiveSector = function(newId) {
  24526. this.activeSector.push(newId);
  24527. };
  24528. /**
  24529. * We remove the currently active sector id from the active sector stack. This happens when
  24530. * we reactivate the previously active sector
  24531. *
  24532. * @private
  24533. */
  24534. exports._forgetLastSector = function() {
  24535. this.activeSector.pop();
  24536. };
  24537. /**
  24538. * This function creates a new active sector with the supplied newId. This newId
  24539. * is the expanding node id.
  24540. *
  24541. * @param {String} newId | Id of the new active sector
  24542. * @private
  24543. */
  24544. exports._createNewSector = function(newId) {
  24545. // create the new sector
  24546. this.sectors["active"][newId] = {"nodes":{},
  24547. "edges":{},
  24548. "nodeIndices":[],
  24549. "formationScale": this.scale,
  24550. "drawingNode": undefined};
  24551. // create the new sector render node. This gives visual feedback that you are in a new sector.
  24552. this.sectors["active"][newId]['drawingNode'] = new Node(
  24553. {id:newId,
  24554. color: {
  24555. background: "#eaefef",
  24556. border: "495c5e"
  24557. }
  24558. },{},{},this.constants);
  24559. this.sectors["active"][newId]['drawingNode'].clusterSize = 2;
  24560. };
  24561. /**
  24562. * This function removes the currently active sector. This is called when we create a new
  24563. * active sector.
  24564. *
  24565. * @param {String} sectorId | Id of the active sector that will be removed
  24566. * @private
  24567. */
  24568. exports._deleteActiveSector = function(sectorId) {
  24569. delete this.sectors["active"][sectorId];
  24570. };
  24571. /**
  24572. * This function removes the currently active sector. This is called when we reactivate
  24573. * the previously active sector.
  24574. *
  24575. * @param {String} sectorId | Id of the active sector that will be removed
  24576. * @private
  24577. */
  24578. exports._deleteFrozenSector = function(sectorId) {
  24579. delete this.sectors["frozen"][sectorId];
  24580. };
  24581. /**
  24582. * Freezing an active sector means moving it from the "active" object to the "frozen" object.
  24583. * We copy the references, then delete the active entree.
  24584. *
  24585. * @param sectorId
  24586. * @private
  24587. */
  24588. exports._freezeSector = function(sectorId) {
  24589. // we move the set references from the active to the frozen stack.
  24590. this.sectors["frozen"][sectorId] = this.sectors["active"][sectorId];
  24591. // we have moved the sector data into the frozen set, we now remove it from the active set
  24592. this._deleteActiveSector(sectorId);
  24593. };
  24594. /**
  24595. * This is the reverse operation of _freezeSector. Activating means moving the sector from the "frozen"
  24596. * object to the "active" object.
  24597. *
  24598. * @param sectorId
  24599. * @private
  24600. */
  24601. exports._activateSector = function(sectorId) {
  24602. // we move the set references from the frozen to the active stack.
  24603. this.sectors["active"][sectorId] = this.sectors["frozen"][sectorId];
  24604. // we have moved the sector data into the active set, we now remove it from the frozen stack
  24605. this._deleteFrozenSector(sectorId);
  24606. };
  24607. /**
  24608. * This function merges the data from the currently active sector with a frozen sector. This is used
  24609. * in the process of reverting back to the previously active sector.
  24610. * The data that is placed in the frozen (the previously active) sector is the node that has been removed from it
  24611. * upon the creation of a new active sector.
  24612. *
  24613. * @param sectorId
  24614. * @private
  24615. */
  24616. exports._mergeThisWithFrozen = function(sectorId) {
  24617. // copy all nodes
  24618. for (var nodeId in this.nodes) {
  24619. if (this.nodes.hasOwnProperty(nodeId)) {
  24620. this.sectors["frozen"][sectorId]["nodes"][nodeId] = this.nodes[nodeId];
  24621. }
  24622. }
  24623. // copy all edges (if not fully clustered, else there are no edges)
  24624. for (var edgeId in this.edges) {
  24625. if (this.edges.hasOwnProperty(edgeId)) {
  24626. this.sectors["frozen"][sectorId]["edges"][edgeId] = this.edges[edgeId];
  24627. }
  24628. }
  24629. // merge the nodeIndices
  24630. for (var i = 0; i < this.nodeIndices.length; i++) {
  24631. this.sectors["frozen"][sectorId]["nodeIndices"].push(this.nodeIndices[i]);
  24632. }
  24633. };
  24634. /**
  24635. * This clusters the sector to one cluster. It was a single cluster before this process started so
  24636. * we revert to that state. The clusterToFit function with a maximum size of 1 node does this.
  24637. *
  24638. * @private
  24639. */
  24640. exports._collapseThisToSingleCluster = function() {
  24641. this.clusterToFit(1,false);
  24642. };
  24643. /**
  24644. * We create a new active sector from the node that we want to open.
  24645. *
  24646. * @param node
  24647. * @private
  24648. */
  24649. exports._addSector = function(node) {
  24650. // this is the currently active sector
  24651. var sector = this._sector();
  24652. // // this should allow me to select nodes from a frozen set.
  24653. // if (this.sectors['active'][sector]["nodes"].hasOwnProperty(node.id)) {
  24654. // console.log("the node is part of the active sector");
  24655. // }
  24656. // else {
  24657. // console.log("I dont know what the fuck happened!!");
  24658. // }
  24659. // when we switch to a new sector, we remove the node that will be expanded from the current nodes list.
  24660. delete this.nodes[node.id];
  24661. var unqiueIdentifier = util.randomUUID();
  24662. // we fully freeze the currently active sector
  24663. this._freezeSector(sector);
  24664. // we create a new active sector. This sector has the Id of the node to ensure uniqueness
  24665. this._createNewSector(unqiueIdentifier);
  24666. // we add the active sector to the sectors array to be able to revert these steps later on
  24667. this._setActiveSector(unqiueIdentifier);
  24668. // we redirect the global references to the new sector's references. this._sector() now returns unqiueIdentifier
  24669. this._switchToSector(this._sector());
  24670. // finally we add the node we removed from our previous active sector to the new active sector
  24671. this.nodes[node.id] = node;
  24672. };
  24673. /**
  24674. * We close the sector that is currently open and revert back to the one before.
  24675. * If the active sector is the "default" sector, nothing happens.
  24676. *
  24677. * @private
  24678. */
  24679. exports._collapseSector = function() {
  24680. // the currently active sector
  24681. var sector = this._sector();
  24682. // we cannot collapse the default sector
  24683. if (sector != "default") {
  24684. if ((this.nodeIndices.length == 1) ||
  24685. (this.sectors["active"][sector]["drawingNode"].width*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  24686. (this.sectors["active"][sector]["drawingNode"].height*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  24687. var previousSector = this._previousSector();
  24688. // we collapse the sector back to a single cluster
  24689. this._collapseThisToSingleCluster();
  24690. // we move the remaining nodes, edges and nodeIndices to the previous sector.
  24691. // This previous sector is the one we will reactivate
  24692. this._mergeThisWithFrozen(previousSector);
  24693. // the previously active (frozen) sector now has all the data from the currently active sector.
  24694. // we can now delete the active sector.
  24695. this._deleteActiveSector(sector);
  24696. // we activate the previously active (and currently frozen) sector.
  24697. this._activateSector(previousSector);
  24698. // we load the references from the newly active sector into the global references
  24699. this._switchToSector(previousSector);
  24700. // we forget the previously active sector because we reverted to the one before
  24701. this._forgetLastSector();
  24702. // finally, we update the node index list.
  24703. this._updateNodeIndexList();
  24704. // we refresh the list with calulation nodes and calculation node indices.
  24705. this._updateCalculationNodes();
  24706. }
  24707. }
  24708. };
  24709. /**
  24710. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  24711. *
  24712. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  24713. * | we dont pass the function itself because then the "this" is the window object
  24714. * | instead of the Network object
  24715. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  24716. * @private
  24717. */
  24718. exports._doInAllActiveSectors = function(runFunction,argument) {
  24719. var returnValues = [];
  24720. if (argument === undefined) {
  24721. for (var sector in this.sectors["active"]) {
  24722. if (this.sectors["active"].hasOwnProperty(sector)) {
  24723. // switch the global references to those of this sector
  24724. this._switchToActiveSector(sector);
  24725. returnValues.push( this[runFunction]() );
  24726. }
  24727. }
  24728. }
  24729. else {
  24730. for (var sector in this.sectors["active"]) {
  24731. if (this.sectors["active"].hasOwnProperty(sector)) {
  24732. // switch the global references to those of this sector
  24733. this._switchToActiveSector(sector);
  24734. var args = Array.prototype.splice.call(arguments, 1);
  24735. if (args.length > 1) {
  24736. returnValues.push( this[runFunction](args[0],args[1]) );
  24737. }
  24738. else {
  24739. returnValues.push( this[runFunction](argument) );
  24740. }
  24741. }
  24742. }
  24743. }
  24744. // we revert the global references back to our active sector
  24745. this._loadLatestSector();
  24746. return returnValues;
  24747. };
  24748. /**
  24749. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  24750. *
  24751. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  24752. * | we dont pass the function itself because then the "this" is the window object
  24753. * | instead of the Network object
  24754. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  24755. * @private
  24756. */
  24757. exports._doInSupportSector = function(runFunction,argument) {
  24758. var returnValues = false;
  24759. if (argument === undefined) {
  24760. this._switchToSupportSector();
  24761. returnValues = this[runFunction]();
  24762. }
  24763. else {
  24764. this._switchToSupportSector();
  24765. var args = Array.prototype.splice.call(arguments, 1);
  24766. if (args.length > 1) {
  24767. returnValues = this[runFunction](args[0],args[1]);
  24768. }
  24769. else {
  24770. returnValues = this[runFunction](argument);
  24771. }
  24772. }
  24773. // we revert the global references back to our active sector
  24774. this._loadLatestSector();
  24775. return returnValues;
  24776. };
  24777. /**
  24778. * This runs a function in all frozen sectors. This is used in the _redraw().
  24779. *
  24780. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  24781. * | we don't pass the function itself because then the "this" is the window object
  24782. * | instead of the Network object
  24783. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  24784. * @private
  24785. */
  24786. exports._doInAllFrozenSectors = function(runFunction,argument) {
  24787. if (argument === undefined) {
  24788. for (var sector in this.sectors["frozen"]) {
  24789. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  24790. // switch the global references to those of this sector
  24791. this._switchToFrozenSector(sector);
  24792. this[runFunction]();
  24793. }
  24794. }
  24795. }
  24796. else {
  24797. for (var sector in this.sectors["frozen"]) {
  24798. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  24799. // switch the global references to those of this sector
  24800. this._switchToFrozenSector(sector);
  24801. var args = Array.prototype.splice.call(arguments, 1);
  24802. if (args.length > 1) {
  24803. this[runFunction](args[0],args[1]);
  24804. }
  24805. else {
  24806. this[runFunction](argument);
  24807. }
  24808. }
  24809. }
  24810. }
  24811. this._loadLatestSector();
  24812. };
  24813. /**
  24814. * This runs a function in all sectors. This is used in the _redraw().
  24815. *
  24816. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  24817. * | we don't pass the function itself because then the "this" is the window object
  24818. * | instead of the Network object
  24819. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  24820. * @private
  24821. */
  24822. exports._doInAllSectors = function(runFunction,argument) {
  24823. var args = Array.prototype.splice.call(arguments, 1);
  24824. if (argument === undefined) {
  24825. this._doInAllActiveSectors(runFunction);
  24826. this._doInAllFrozenSectors(runFunction);
  24827. }
  24828. else {
  24829. if (args.length > 1) {
  24830. this._doInAllActiveSectors(runFunction,args[0],args[1]);
  24831. this._doInAllFrozenSectors(runFunction,args[0],args[1]);
  24832. }
  24833. else {
  24834. this._doInAllActiveSectors(runFunction,argument);
  24835. this._doInAllFrozenSectors(runFunction,argument);
  24836. }
  24837. }
  24838. };
  24839. /**
  24840. * This clears the nodeIndices list. We cannot use this.nodeIndices = [] because we would break the link with the
  24841. * active sector. Thus we clear the nodeIndices in the active sector, then reconnect the this.nodeIndices to it.
  24842. *
  24843. * @private
  24844. */
  24845. exports._clearNodeIndexList = function() {
  24846. var sector = this._sector();
  24847. this.sectors["active"][sector]["nodeIndices"] = [];
  24848. this.nodeIndices = this.sectors["active"][sector]["nodeIndices"];
  24849. };
  24850. /**
  24851. * Draw the encompassing sector node
  24852. *
  24853. * @param ctx
  24854. * @param sectorType
  24855. * @private
  24856. */
  24857. exports._drawSectorNodes = function(ctx,sectorType) {
  24858. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  24859. for (var sector in this.sectors[sectorType]) {
  24860. if (this.sectors[sectorType].hasOwnProperty(sector)) {
  24861. if (this.sectors[sectorType][sector]["drawingNode"] !== undefined) {
  24862. this._switchToSector(sector,sectorType);
  24863. minY = 1e9; maxY = -1e9; minX = 1e9; maxX = -1e9;
  24864. for (var nodeId in this.nodes) {
  24865. if (this.nodes.hasOwnProperty(nodeId)) {
  24866. node = this.nodes[nodeId];
  24867. node.resize(ctx);
  24868. if (minX > node.x - 0.5 * node.width) {minX = node.x - 0.5 * node.width;}
  24869. if (maxX < node.x + 0.5 * node.width) {maxX = node.x + 0.5 * node.width;}
  24870. if (minY > node.y - 0.5 * node.height) {minY = node.y - 0.5 * node.height;}
  24871. if (maxY < node.y + 0.5 * node.height) {maxY = node.y + 0.5 * node.height;}
  24872. }
  24873. }
  24874. node = this.sectors[sectorType][sector]["drawingNode"];
  24875. node.x = 0.5 * (maxX + minX);
  24876. node.y = 0.5 * (maxY + minY);
  24877. node.width = 2 * (node.x - minX);
  24878. node.height = 2 * (node.y - minY);
  24879. node.radius = Math.sqrt(Math.pow(0.5*node.width,2) + Math.pow(0.5*node.height,2));
  24880. node.setScale(this.scale);
  24881. node._drawCircle(ctx);
  24882. }
  24883. }
  24884. }
  24885. };
  24886. exports._drawAllSectorNodes = function(ctx) {
  24887. this._drawSectorNodes(ctx,"frozen");
  24888. this._drawSectorNodes(ctx,"active");
  24889. this._loadLatestSector();
  24890. };
  24891. /***/ },
  24892. /* 55 */
  24893. /***/ function(module, exports, __webpack_require__) {
  24894. var Node = __webpack_require__(36);
  24895. /**
  24896. * This function can be called from the _doInAllSectors function
  24897. *
  24898. * @param object
  24899. * @param overlappingNodes
  24900. * @private
  24901. */
  24902. exports._getNodesOverlappingWith = function(object, overlappingNodes) {
  24903. var nodes = this.nodes;
  24904. for (var nodeId in nodes) {
  24905. if (nodes.hasOwnProperty(nodeId)) {
  24906. if (nodes[nodeId].isOverlappingWith(object)) {
  24907. overlappingNodes.push(nodeId);
  24908. }
  24909. }
  24910. }
  24911. };
  24912. /**
  24913. * retrieve all nodes overlapping with given object
  24914. * @param {Object} object An object with parameters left, top, right, bottom
  24915. * @return {Number[]} An array with id's of the overlapping nodes
  24916. * @private
  24917. */
  24918. exports._getAllNodesOverlappingWith = function (object) {
  24919. var overlappingNodes = [];
  24920. this._doInAllActiveSectors("_getNodesOverlappingWith",object,overlappingNodes);
  24921. return overlappingNodes;
  24922. };
  24923. /**
  24924. * Return a position object in canvasspace from a single point in screenspace
  24925. *
  24926. * @param pointer
  24927. * @returns {{left: number, top: number, right: number, bottom: number}}
  24928. * @private
  24929. */
  24930. exports._pointerToPositionObject = function(pointer) {
  24931. var x = this._XconvertDOMtoCanvas(pointer.x);
  24932. var y = this._YconvertDOMtoCanvas(pointer.y);
  24933. return {
  24934. left: x,
  24935. top: y,
  24936. right: x,
  24937. bottom: y
  24938. };
  24939. };
  24940. /**
  24941. * Get the top node at the a specific point (like a click)
  24942. *
  24943. * @param {{x: Number, y: Number}} pointer
  24944. * @return {Node | null} node
  24945. * @private
  24946. */
  24947. exports._getNodeAt = function (pointer) {
  24948. // we first check if this is an navigation controls element
  24949. var positionObject = this._pointerToPositionObject(pointer);
  24950. var overlappingNodes = this._getAllNodesOverlappingWith(positionObject);
  24951. // if there are overlapping nodes, select the last one, this is the
  24952. // one which is drawn on top of the others
  24953. if (overlappingNodes.length > 0) {
  24954. return this.nodes[overlappingNodes[overlappingNodes.length - 1]];
  24955. }
  24956. else {
  24957. return null;
  24958. }
  24959. };
  24960. /**
  24961. * retrieve all edges overlapping with given object, selector is around center
  24962. * @param {Object} object An object with parameters left, top, right, bottom
  24963. * @return {Number[]} An array with id's of the overlapping nodes
  24964. * @private
  24965. */
  24966. exports._getEdgesOverlappingWith = function (object, overlappingEdges) {
  24967. var edges = this.edges;
  24968. for (var edgeId in edges) {
  24969. if (edges.hasOwnProperty(edgeId)) {
  24970. if (edges[edgeId].isOverlappingWith(object)) {
  24971. overlappingEdges.push(edgeId);
  24972. }
  24973. }
  24974. }
  24975. };
  24976. /**
  24977. * retrieve all nodes overlapping with given object
  24978. * @param {Object} object An object with parameters left, top, right, bottom
  24979. * @return {Number[]} An array with id's of the overlapping nodes
  24980. * @private
  24981. */
  24982. exports._getAllEdgesOverlappingWith = function (object) {
  24983. var overlappingEdges = [];
  24984. this._doInAllActiveSectors("_getEdgesOverlappingWith",object,overlappingEdges);
  24985. return overlappingEdges;
  24986. };
  24987. /**
  24988. * Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call
  24989. * _getNodeAt and _getEdgesAt, then priortize the selection to user preferences.
  24990. *
  24991. * @param pointer
  24992. * @returns {null}
  24993. * @private
  24994. */
  24995. exports._getEdgeAt = function(pointer) {
  24996. var positionObject = this._pointerToPositionObject(pointer);
  24997. var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject);
  24998. if (overlappingEdges.length > 0) {
  24999. return this.edges[overlappingEdges[overlappingEdges.length - 1]];
  25000. }
  25001. else {
  25002. return null;
  25003. }
  25004. };
  25005. /**
  25006. * Add object to the selection array.
  25007. *
  25008. * @param obj
  25009. * @private
  25010. */
  25011. exports._addToSelection = function(obj) {
  25012. if (obj instanceof Node) {
  25013. this.selectionObj.nodes[obj.id] = obj;
  25014. }
  25015. else {
  25016. this.selectionObj.edges[obj.id] = obj;
  25017. }
  25018. };
  25019. /**
  25020. * Add object to the selection array.
  25021. *
  25022. * @param obj
  25023. * @private
  25024. */
  25025. exports._addToHover = function(obj) {
  25026. if (obj instanceof Node) {
  25027. this.hoverObj.nodes[obj.id] = obj;
  25028. }
  25029. else {
  25030. this.hoverObj.edges[obj.id] = obj;
  25031. }
  25032. };
  25033. /**
  25034. * Remove a single option from selection.
  25035. *
  25036. * @param {Object} obj
  25037. * @private
  25038. */
  25039. exports._removeFromSelection = function(obj) {
  25040. if (obj instanceof Node) {
  25041. delete this.selectionObj.nodes[obj.id];
  25042. }
  25043. else {
  25044. delete this.selectionObj.edges[obj.id];
  25045. }
  25046. };
  25047. /**
  25048. * Unselect all. The selectionObj is useful for this.
  25049. *
  25050. * @param {Boolean} [doNotTrigger] | ignore trigger
  25051. * @private
  25052. */
  25053. exports._unselectAll = function(doNotTrigger) {
  25054. if (doNotTrigger === undefined) {
  25055. doNotTrigger = false;
  25056. }
  25057. for(var nodeId in this.selectionObj.nodes) {
  25058. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25059. this.selectionObj.nodes[nodeId].unselect();
  25060. }
  25061. }
  25062. for(var edgeId in this.selectionObj.edges) {
  25063. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25064. this.selectionObj.edges[edgeId].unselect();
  25065. }
  25066. }
  25067. this.selectionObj = {nodes:{},edges:{}};
  25068. if (doNotTrigger == false) {
  25069. this.emit('select', this.getSelection());
  25070. }
  25071. };
  25072. /**
  25073. * Unselect all clusters. The selectionObj is useful for this.
  25074. *
  25075. * @param {Boolean} [doNotTrigger] | ignore trigger
  25076. * @private
  25077. */
  25078. exports._unselectClusters = function(doNotTrigger) {
  25079. if (doNotTrigger === undefined) {
  25080. doNotTrigger = false;
  25081. }
  25082. for (var nodeId in this.selectionObj.nodes) {
  25083. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25084. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  25085. this.selectionObj.nodes[nodeId].unselect();
  25086. this._removeFromSelection(this.selectionObj.nodes[nodeId]);
  25087. }
  25088. }
  25089. }
  25090. if (doNotTrigger == false) {
  25091. this.emit('select', this.getSelection());
  25092. }
  25093. };
  25094. /**
  25095. * return the number of selected nodes
  25096. *
  25097. * @returns {number}
  25098. * @private
  25099. */
  25100. exports._getSelectedNodeCount = function() {
  25101. var count = 0;
  25102. for (var nodeId in this.selectionObj.nodes) {
  25103. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25104. count += 1;
  25105. }
  25106. }
  25107. return count;
  25108. };
  25109. /**
  25110. * return the selected node
  25111. *
  25112. * @returns {number}
  25113. * @private
  25114. */
  25115. exports._getSelectedNode = function() {
  25116. for (var nodeId in this.selectionObj.nodes) {
  25117. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25118. return this.selectionObj.nodes[nodeId];
  25119. }
  25120. }
  25121. return null;
  25122. };
  25123. /**
  25124. * return the selected edge
  25125. *
  25126. * @returns {number}
  25127. * @private
  25128. */
  25129. exports._getSelectedEdge = function() {
  25130. for (var edgeId in this.selectionObj.edges) {
  25131. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25132. return this.selectionObj.edges[edgeId];
  25133. }
  25134. }
  25135. return null;
  25136. };
  25137. /**
  25138. * return the number of selected edges
  25139. *
  25140. * @returns {number}
  25141. * @private
  25142. */
  25143. exports._getSelectedEdgeCount = function() {
  25144. var count = 0;
  25145. for (var edgeId in this.selectionObj.edges) {
  25146. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25147. count += 1;
  25148. }
  25149. }
  25150. return count;
  25151. };
  25152. /**
  25153. * return the number of selected objects.
  25154. *
  25155. * @returns {number}
  25156. * @private
  25157. */
  25158. exports._getSelectedObjectCount = function() {
  25159. var count = 0;
  25160. for(var nodeId in this.selectionObj.nodes) {
  25161. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25162. count += 1;
  25163. }
  25164. }
  25165. for(var edgeId in this.selectionObj.edges) {
  25166. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25167. count += 1;
  25168. }
  25169. }
  25170. return count;
  25171. };
  25172. /**
  25173. * Check if anything is selected
  25174. *
  25175. * @returns {boolean}
  25176. * @private
  25177. */
  25178. exports._selectionIsEmpty = function() {
  25179. for(var nodeId in this.selectionObj.nodes) {
  25180. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25181. return false;
  25182. }
  25183. }
  25184. for(var edgeId in this.selectionObj.edges) {
  25185. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25186. return false;
  25187. }
  25188. }
  25189. return true;
  25190. };
  25191. /**
  25192. * check if one of the selected nodes is a cluster.
  25193. *
  25194. * @returns {boolean}
  25195. * @private
  25196. */
  25197. exports._clusterInSelection = function() {
  25198. for(var nodeId in this.selectionObj.nodes) {
  25199. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25200. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  25201. return true;
  25202. }
  25203. }
  25204. }
  25205. return false;
  25206. };
  25207. /**
  25208. * select the edges connected to the node that is being selected
  25209. *
  25210. * @param {Node} node
  25211. * @private
  25212. */
  25213. exports._selectConnectedEdges = function(node) {
  25214. for (var i = 0; i < node.dynamicEdges.length; i++) {
  25215. var edge = node.dynamicEdges[i];
  25216. edge.select();
  25217. this._addToSelection(edge);
  25218. }
  25219. };
  25220. /**
  25221. * select the edges connected to the node that is being selected
  25222. *
  25223. * @param {Node} node
  25224. * @private
  25225. */
  25226. exports._hoverConnectedEdges = function(node) {
  25227. for (var i = 0; i < node.dynamicEdges.length; i++) {
  25228. var edge = node.dynamicEdges[i];
  25229. edge.hover = true;
  25230. this._addToHover(edge);
  25231. }
  25232. };
  25233. /**
  25234. * unselect the edges connected to the node that is being selected
  25235. *
  25236. * @param {Node} node
  25237. * @private
  25238. */
  25239. exports._unselectConnectedEdges = function(node) {
  25240. for (var i = 0; i < node.dynamicEdges.length; i++) {
  25241. var edge = node.dynamicEdges[i];
  25242. edge.unselect();
  25243. this._removeFromSelection(edge);
  25244. }
  25245. };
  25246. /**
  25247. * This is called when someone clicks on a node. either select or deselect it.
  25248. * If there is an existing selection and we don't want to append to it, clear the existing selection
  25249. *
  25250. * @param {Node || Edge} object
  25251. * @param {Boolean} append
  25252. * @param {Boolean} [doNotTrigger] | ignore trigger
  25253. * @private
  25254. */
  25255. exports._selectObject = function(object, append, doNotTrigger, highlightEdges) {
  25256. if (doNotTrigger === undefined) {
  25257. doNotTrigger = false;
  25258. }
  25259. if (highlightEdges === undefined) {
  25260. highlightEdges = true;
  25261. }
  25262. if (this._selectionIsEmpty() == false && append == false && this.forceAppendSelection == false) {
  25263. this._unselectAll(true);
  25264. }
  25265. if (object.selected == false) {
  25266. object.select();
  25267. this._addToSelection(object);
  25268. if (object instanceof Node && this.blockConnectingEdgeSelection == false && highlightEdges == true) {
  25269. this._selectConnectedEdges(object);
  25270. }
  25271. }
  25272. else {
  25273. object.unselect();
  25274. this._removeFromSelection(object);
  25275. }
  25276. if (doNotTrigger == false) {
  25277. this.emit('select', this.getSelection());
  25278. }
  25279. };
  25280. /**
  25281. * This is called when someone clicks on a node. either select or deselect it.
  25282. * If there is an existing selection and we don't want to append to it, clear the existing selection
  25283. *
  25284. * @param {Node || Edge} object
  25285. * @private
  25286. */
  25287. exports._blurObject = function(object) {
  25288. if (object.hover == true) {
  25289. object.hover = false;
  25290. this.emit("blurNode",{node:object.id});
  25291. }
  25292. };
  25293. /**
  25294. * This is called when someone clicks on a node. either select or deselect it.
  25295. * If there is an existing selection and we don't want to append to it, clear the existing selection
  25296. *
  25297. * @param {Node || Edge} object
  25298. * @private
  25299. */
  25300. exports._hoverObject = function(object) {
  25301. if (object.hover == false) {
  25302. object.hover = true;
  25303. this._addToHover(object);
  25304. if (object instanceof Node) {
  25305. this.emit("hoverNode",{node:object.id});
  25306. }
  25307. }
  25308. if (object instanceof Node) {
  25309. this._hoverConnectedEdges(object);
  25310. }
  25311. };
  25312. /**
  25313. * handles the selection part of the touch, only for navigation controls elements;
  25314. * Touch is triggered before tap, also before hold. Hold triggers after a while.
  25315. * This is the most responsive solution
  25316. *
  25317. * @param {Object} pointer
  25318. * @private
  25319. */
  25320. exports._handleTouch = function(pointer) {
  25321. };
  25322. /**
  25323. * handles the selection part of the tap;
  25324. *
  25325. * @param {Object} pointer
  25326. * @private
  25327. */
  25328. exports._handleTap = function(pointer) {
  25329. var node = this._getNodeAt(pointer);
  25330. if (node != null) {
  25331. this._selectObject(node,false);
  25332. }
  25333. else {
  25334. var edge = this._getEdgeAt(pointer);
  25335. if (edge != null) {
  25336. this._selectObject(edge,false);
  25337. }
  25338. else {
  25339. this._unselectAll();
  25340. }
  25341. }
  25342. this.emit("click", this.getSelection());
  25343. this._redraw();
  25344. };
  25345. /**
  25346. * handles the selection part of the double tap and opens a cluster if needed
  25347. *
  25348. * @param {Object} pointer
  25349. * @private
  25350. */
  25351. exports._handleDoubleTap = function(pointer) {
  25352. var node = this._getNodeAt(pointer);
  25353. if (node != null && node !== undefined) {
  25354. // we reset the areaCenter here so the opening of the node will occur
  25355. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  25356. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  25357. this.openCluster(node);
  25358. }
  25359. this.emit("doubleClick", this.getSelection());
  25360. };
  25361. /**
  25362. * Handle the onHold selection part
  25363. *
  25364. * @param pointer
  25365. * @private
  25366. */
  25367. exports._handleOnHold = function(pointer) {
  25368. var node = this._getNodeAt(pointer);
  25369. if (node != null) {
  25370. this._selectObject(node,true);
  25371. }
  25372. else {
  25373. var edge = this._getEdgeAt(pointer);
  25374. if (edge != null) {
  25375. this._selectObject(edge,true);
  25376. }
  25377. }
  25378. this._redraw();
  25379. };
  25380. /**
  25381. * handle the onRelease event. These functions are here for the navigation controls module.
  25382. *
  25383. * @private
  25384. */
  25385. exports._handleOnRelease = function(pointer) {
  25386. };
  25387. /**
  25388. *
  25389. * retrieve the currently selected objects
  25390. * @return {{nodes: Array.<String>, edges: Array.<String>}} selection
  25391. */
  25392. exports.getSelection = function() {
  25393. var nodeIds = this.getSelectedNodes();
  25394. var edgeIds = this.getSelectedEdges();
  25395. return {nodes:nodeIds, edges:edgeIds};
  25396. };
  25397. /**
  25398. *
  25399. * retrieve the currently selected nodes
  25400. * @return {String[]} selection An array with the ids of the
  25401. * selected nodes.
  25402. */
  25403. exports.getSelectedNodes = function() {
  25404. var idArray = [];
  25405. for(var nodeId in this.selectionObj.nodes) {
  25406. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25407. idArray.push(nodeId);
  25408. }
  25409. }
  25410. return idArray
  25411. };
  25412. /**
  25413. *
  25414. * retrieve the currently selected edges
  25415. * @return {Array} selection An array with the ids of the
  25416. * selected nodes.
  25417. */
  25418. exports.getSelectedEdges = function() {
  25419. var idArray = [];
  25420. for(var edgeId in this.selectionObj.edges) {
  25421. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25422. idArray.push(edgeId);
  25423. }
  25424. }
  25425. return idArray;
  25426. };
  25427. /**
  25428. * select zero or more nodes
  25429. * @param {Number[] | String[]} selection An array with the ids of the
  25430. * selected nodes.
  25431. */
  25432. exports.setSelection = function(selection) {
  25433. var i, iMax, id;
  25434. if (!selection || (selection.length == undefined))
  25435. throw 'Selection must be an array with ids';
  25436. // first unselect any selected node
  25437. this._unselectAll(true);
  25438. for (i = 0, iMax = selection.length; i < iMax; i++) {
  25439. id = selection[i];
  25440. var node = this.nodes[id];
  25441. if (!node) {
  25442. throw new RangeError('Node with id "' + id + '" not found');
  25443. }
  25444. this._selectObject(node,true,true);
  25445. }
  25446. console.log("setSelection is deprecated. Please use selectNodes instead.")
  25447. this.redraw();
  25448. };
  25449. /**
  25450. * select zero or more nodes with the option to highlight edges
  25451. * @param {Number[] | String[]} selection An array with the ids of the
  25452. * selected nodes.
  25453. * @param {boolean} [highlightEdges]
  25454. */
  25455. exports.selectNodes = function(selection, highlightEdges) {
  25456. var i, iMax, id;
  25457. if (!selection || (selection.length == undefined))
  25458. throw 'Selection must be an array with ids';
  25459. // first unselect any selected node
  25460. this._unselectAll(true);
  25461. for (i = 0, iMax = selection.length; i < iMax; i++) {
  25462. id = selection[i];
  25463. var node = this.nodes[id];
  25464. if (!node) {
  25465. throw new RangeError('Node with id "' + id + '" not found');
  25466. }
  25467. this._selectObject(node,true,true,highlightEdges);
  25468. }
  25469. this.redraw();
  25470. };
  25471. /**
  25472. * select zero or more edges
  25473. * @param {Number[] | String[]} selection An array with the ids of the
  25474. * selected nodes.
  25475. */
  25476. exports.selectEdges = function(selection) {
  25477. var i, iMax, id;
  25478. if (!selection || (selection.length == undefined))
  25479. throw 'Selection must be an array with ids';
  25480. // first unselect any selected node
  25481. this._unselectAll(true);
  25482. for (i = 0, iMax = selection.length; i < iMax; i++) {
  25483. id = selection[i];
  25484. var edge = this.edges[id];
  25485. if (!edge) {
  25486. throw new RangeError('Edge with id "' + id + '" not found');
  25487. }
  25488. this._selectObject(edge,true,true,highlightEdges);
  25489. }
  25490. this.redraw();
  25491. };
  25492. /**
  25493. * Validate the selection: remove ids of nodes which no longer exist
  25494. * @private
  25495. */
  25496. exports._updateSelection = function () {
  25497. for(var nodeId in this.selectionObj.nodes) {
  25498. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25499. if (!this.nodes.hasOwnProperty(nodeId)) {
  25500. delete this.selectionObj.nodes[nodeId];
  25501. }
  25502. }
  25503. }
  25504. for(var edgeId in this.selectionObj.edges) {
  25505. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25506. if (!this.edges.hasOwnProperty(edgeId)) {
  25507. delete this.selectionObj.edges[edgeId];
  25508. }
  25509. }
  25510. }
  25511. };
  25512. /***/ },
  25513. /* 56 */
  25514. /***/ function(module, exports, __webpack_require__) {
  25515. var util = __webpack_require__(1);
  25516. var Node = __webpack_require__(36);
  25517. var Edge = __webpack_require__(33);
  25518. /**
  25519. * clears the toolbar div element of children
  25520. *
  25521. * @private
  25522. */
  25523. exports._clearManipulatorBar = function() {
  25524. while (this.manipulationDiv.hasChildNodes()) {
  25525. this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
  25526. }
  25527. };
  25528. /**
  25529. * Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore
  25530. * these functions to their original functionality, we saved them in this.cachedFunctions.
  25531. * This function restores these functions to their original function.
  25532. *
  25533. * @private
  25534. */
  25535. exports._restoreOverloadedFunctions = function() {
  25536. for (var functionName in this.cachedFunctions) {
  25537. if (this.cachedFunctions.hasOwnProperty(functionName)) {
  25538. this[functionName] = this.cachedFunctions[functionName];
  25539. }
  25540. }
  25541. };
  25542. /**
  25543. * Enable or disable edit-mode.
  25544. *
  25545. * @private
  25546. */
  25547. exports._toggleEditMode = function() {
  25548. this.editMode = !this.editMode;
  25549. var toolbar = document.getElementById("network-manipulationDiv");
  25550. var closeDiv = document.getElementById("network-manipulation-closeDiv");
  25551. var editModeDiv = document.getElementById("network-manipulation-editMode");
  25552. if (this.editMode == true) {
  25553. toolbar.style.display="block";
  25554. closeDiv.style.display="block";
  25555. editModeDiv.style.display="none";
  25556. closeDiv.onclick = this._toggleEditMode.bind(this);
  25557. }
  25558. else {
  25559. toolbar.style.display="none";
  25560. closeDiv.style.display="none";
  25561. editModeDiv.style.display="block";
  25562. closeDiv.onclick = null;
  25563. }
  25564. this._createManipulatorBar()
  25565. };
  25566. /**
  25567. * main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
  25568. *
  25569. * @private
  25570. */
  25571. exports._createManipulatorBar = function() {
  25572. // remove bound functions
  25573. if (this.boundFunction) {
  25574. this.off('select', this.boundFunction);
  25575. }
  25576. var locale = this.constants.locales[this.constants.locale];
  25577. if (this.edgeBeingEdited !== undefined) {
  25578. this.edgeBeingEdited._disableControlNodes();
  25579. this.edgeBeingEdited = undefined;
  25580. this.selectedControlNode = null;
  25581. this.controlNodesActive = false;
  25582. }
  25583. // restore overloaded functions
  25584. this._restoreOverloadedFunctions();
  25585. // resume calculation
  25586. this.freezeSimulation = false;
  25587. // reset global variables
  25588. this.blockConnectingEdgeSelection = false;
  25589. this.forceAppendSelection = false;
  25590. if (this.editMode == true) {
  25591. while (this.manipulationDiv.hasChildNodes()) {
  25592. this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
  25593. }
  25594. // add the icons to the manipulator div
  25595. this.manipulationDiv.innerHTML = "" +
  25596. "<span class='network-manipulationUI add' id='network-manipulate-addNode'>" +
  25597. "<span class='network-manipulationLabel'>"+locale['addNode'] +"</span></span>" +
  25598. "<div class='network-seperatorLine'></div>" +
  25599. "<span class='network-manipulationUI connect' id='network-manipulate-connectNode'>" +
  25600. "<span class='network-manipulationLabel'>"+locale['addEdge'] +"</span></span>";
  25601. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  25602. this.manipulationDiv.innerHTML += "" +
  25603. "<div class='network-seperatorLine'></div>" +
  25604. "<span class='network-manipulationUI edit' id='network-manipulate-editNode'>" +
  25605. "<span class='network-manipulationLabel'>"+locale['editNode'] +"</span></span>";
  25606. }
  25607. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  25608. this.manipulationDiv.innerHTML += "" +
  25609. "<div class='network-seperatorLine'></div>" +
  25610. "<span class='network-manipulationUI edit' id='network-manipulate-editEdge'>" +
  25611. "<span class='network-manipulationLabel'>"+locale['editEdge'] +"</span></span>";
  25612. }
  25613. if (this._selectionIsEmpty() == false) {
  25614. this.manipulationDiv.innerHTML += "" +
  25615. "<div class='network-seperatorLine'></div>" +
  25616. "<span class='network-manipulationUI delete' id='network-manipulate-delete'>" +
  25617. "<span class='network-manipulationLabel'>"+locale['del'] +"</span></span>";
  25618. }
  25619. // bind the icons
  25620. var addNodeButton = document.getElementById("network-manipulate-addNode");
  25621. addNodeButton.onclick = this._createAddNodeToolbar.bind(this);
  25622. var addEdgeButton = document.getElementById("network-manipulate-connectNode");
  25623. addEdgeButton.onclick = this._createAddEdgeToolbar.bind(this);
  25624. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  25625. var editButton = document.getElementById("network-manipulate-editNode");
  25626. editButton.onclick = this._editNode.bind(this);
  25627. }
  25628. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  25629. var editButton = document.getElementById("network-manipulate-editEdge");
  25630. editButton.onclick = this._createEditEdgeToolbar.bind(this);
  25631. }
  25632. if (this._selectionIsEmpty() == false) {
  25633. var deleteButton = document.getElementById("network-manipulate-delete");
  25634. deleteButton.onclick = this._deleteSelected.bind(this);
  25635. }
  25636. var closeDiv = document.getElementById("network-manipulation-closeDiv");
  25637. closeDiv.onclick = this._toggleEditMode.bind(this);
  25638. this.boundFunction = this._createManipulatorBar.bind(this);
  25639. this.on('select', this.boundFunction);
  25640. }
  25641. else {
  25642. this.editModeDiv.innerHTML = "" +
  25643. "<span class='network-manipulationUI edit editmode' id='network-manipulate-editModeButton'>" +
  25644. "<span class='network-manipulationLabel'>" + locale['edit'] + "</span></span>";
  25645. var editModeButton = document.getElementById("network-manipulate-editModeButton");
  25646. editModeButton.onclick = this._toggleEditMode.bind(this);
  25647. }
  25648. };
  25649. /**
  25650. * Create the toolbar for adding Nodes
  25651. *
  25652. * @private
  25653. */
  25654. exports._createAddNodeToolbar = function() {
  25655. // clear the toolbar
  25656. this._clearManipulatorBar();
  25657. if (this.boundFunction) {
  25658. this.off('select', this.boundFunction);
  25659. }
  25660. var locale = this.constants.locales[this.constants.locale];
  25661. // create the toolbar contents
  25662. this.manipulationDiv.innerHTML = "" +
  25663. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  25664. "<span class='network-manipulationLabel'>" + locale['back'] + " </span></span>" +
  25665. "<div class='network-seperatorLine'></div>" +
  25666. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  25667. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + locale['addDescription'] + "</span></span>";
  25668. // bind the icon
  25669. var backButton = document.getElementById("network-manipulate-back");
  25670. backButton.onclick = this._createManipulatorBar.bind(this);
  25671. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  25672. this.boundFunction = this._addNode.bind(this);
  25673. this.on('select', this.boundFunction);
  25674. };
  25675. /**
  25676. * create the toolbar to connect nodes
  25677. *
  25678. * @private
  25679. */
  25680. exports._createAddEdgeToolbar = function() {
  25681. // clear the toolbar
  25682. this._clearManipulatorBar();
  25683. this._unselectAll(true);
  25684. this.freezeSimulation = true;
  25685. var locale = this.constants.locales[this.constants.locale];
  25686. if (this.boundFunction) {
  25687. this.off('select', this.boundFunction);
  25688. }
  25689. this._unselectAll();
  25690. this.forceAppendSelection = false;
  25691. this.blockConnectingEdgeSelection = true;
  25692. this.manipulationDiv.innerHTML = "" +
  25693. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  25694. "<span class='network-manipulationLabel'>" + locale['back'] + " </span></span>" +
  25695. "<div class='network-seperatorLine'></div>" +
  25696. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  25697. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + locale['edgeDescription'] + "</span></span>";
  25698. // bind the icon
  25699. var backButton = document.getElementById("network-manipulate-back");
  25700. backButton.onclick = this._createManipulatorBar.bind(this);
  25701. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  25702. this.boundFunction = this._handleConnect.bind(this);
  25703. this.on('select', this.boundFunction);
  25704. // temporarily overload functions
  25705. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  25706. this.cachedFunctions["_handleOnRelease"] = this._handleOnRelease;
  25707. this._handleTouch = this._handleConnect;
  25708. this._handleOnRelease = this._finishConnect;
  25709. // redraw to show the unselect
  25710. this._redraw();
  25711. };
  25712. /**
  25713. * create the toolbar to edit edges
  25714. *
  25715. * @private
  25716. */
  25717. exports._createEditEdgeToolbar = function() {
  25718. // clear the toolbar
  25719. this._clearManipulatorBar();
  25720. this.controlNodesActive = true;
  25721. if (this.boundFunction) {
  25722. this.off('select', this.boundFunction);
  25723. }
  25724. this.edgeBeingEdited = this._getSelectedEdge();
  25725. this.edgeBeingEdited._enableControlNodes();
  25726. var locale = this.constants.locales[this.constants.locale];
  25727. this.manipulationDiv.innerHTML = "" +
  25728. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  25729. "<span class='network-manipulationLabel'>" + locale['back'] + " </span></span>" +
  25730. "<div class='network-seperatorLine'></div>" +
  25731. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  25732. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + locale['editEdgeDescription'] + "</span></span>";
  25733. // bind the icon
  25734. var backButton = document.getElementById("network-manipulate-back");
  25735. backButton.onclick = this._createManipulatorBar.bind(this);
  25736. // temporarily overload functions
  25737. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  25738. this.cachedFunctions["_handleOnRelease"] = this._handleOnRelease;
  25739. this.cachedFunctions["_handleTap"] = this._handleTap;
  25740. this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
  25741. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  25742. this._handleTouch = this._selectControlNode;
  25743. this._handleTap = function () {};
  25744. this._handleOnDrag = this._controlNodeDrag;
  25745. this._handleDragStart = function () {}
  25746. this._handleOnRelease = this._releaseControlNode;
  25747. // redraw to show the unselect
  25748. this._redraw();
  25749. };
  25750. /**
  25751. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  25752. * to walk the user through the process.
  25753. *
  25754. * @private
  25755. */
  25756. exports._selectControlNode = function(pointer) {
  25757. this.edgeBeingEdited.controlNodes.from.unselect();
  25758. this.edgeBeingEdited.controlNodes.to.unselect();
  25759. this.selectedControlNode = this.edgeBeingEdited._getSelectedControlNode(this._XconvertDOMtoCanvas(pointer.x),this._YconvertDOMtoCanvas(pointer.y));
  25760. if (this.selectedControlNode !== null) {
  25761. this.selectedControlNode.select();
  25762. this.freezeSimulation = true;
  25763. }
  25764. this._redraw();
  25765. };
  25766. /**
  25767. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  25768. * to walk the user through the process.
  25769. *
  25770. * @private
  25771. */
  25772. exports._controlNodeDrag = function(event) {
  25773. var pointer = this._getPointer(event.gesture.center);
  25774. if (this.selectedControlNode !== null && this.selectedControlNode !== undefined) {
  25775. this.selectedControlNode.x = this._XconvertDOMtoCanvas(pointer.x);
  25776. this.selectedControlNode.y = this._YconvertDOMtoCanvas(pointer.y);
  25777. }
  25778. this._redraw();
  25779. };
  25780. exports._releaseControlNode = function(pointer) {
  25781. var newNode = this._getNodeAt(pointer);
  25782. if (newNode != null) {
  25783. if (this.edgeBeingEdited.controlNodes.from.selected == true) {
  25784. this._editEdge(newNode.id, this.edgeBeingEdited.to.id);
  25785. this.edgeBeingEdited.controlNodes.from.unselect();
  25786. }
  25787. if (this.edgeBeingEdited.controlNodes.to.selected == true) {
  25788. this._editEdge(this.edgeBeingEdited.from.id, newNode.id);
  25789. this.edgeBeingEdited.controlNodes.to.unselect();
  25790. }
  25791. }
  25792. else {
  25793. this.edgeBeingEdited._restoreControlNodes();
  25794. }
  25795. this.freezeSimulation = false;
  25796. this._redraw();
  25797. };
  25798. /**
  25799. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  25800. * to walk the user through the process.
  25801. *
  25802. * @private
  25803. */
  25804. exports._handleConnect = function(pointer) {
  25805. if (this._getSelectedNodeCount() == 0) {
  25806. var node = this._getNodeAt(pointer);
  25807. if (node != null) {
  25808. if (node.clusterSize > 1) {
  25809. alert(this.constants.locales[this.constants.locale]['createEdgeError'])
  25810. }
  25811. else {
  25812. this._selectObject(node,false);
  25813. // create a node the temporary line can look at
  25814. this.sectors['support']['nodes']['targetNode'] = new Node({id:'targetNode'},{},{},this.constants);
  25815. this.sectors['support']['nodes']['targetNode'].x = node.x;
  25816. this.sectors['support']['nodes']['targetNode'].y = node.y;
  25817. this.sectors['support']['nodes']['targetViaNode'] = new Node({id:'targetViaNode'},{},{},this.constants);
  25818. this.sectors['support']['nodes']['targetViaNode'].x = node.x;
  25819. this.sectors['support']['nodes']['targetViaNode'].y = node.y;
  25820. this.sectors['support']['nodes']['targetViaNode'].parentEdgeId = "connectionEdge";
  25821. // create a temporary edge
  25822. this.edges['connectionEdge'] = new Edge({id:"connectionEdge",from:node.id,to:this.sectors['support']['nodes']['targetNode'].id}, this, this.constants);
  25823. this.edges['connectionEdge'].from = node;
  25824. this.edges['connectionEdge'].connected = true;
  25825. this.edges['connectionEdge'].smooth = true;
  25826. this.edges['connectionEdge'].selected = true;
  25827. this.edges['connectionEdge'].to = this.sectors['support']['nodes']['targetNode'];
  25828. this.edges['connectionEdge'].via = this.sectors['support']['nodes']['targetViaNode'];
  25829. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  25830. this._handleOnDrag = function(event) {
  25831. var pointer = this._getPointer(event.gesture.center);
  25832. this.sectors['support']['nodes']['targetNode'].x = this._XconvertDOMtoCanvas(pointer.x);
  25833. this.sectors['support']['nodes']['targetNode'].y = this._YconvertDOMtoCanvas(pointer.y);
  25834. this.sectors['support']['nodes']['targetViaNode'].x = 0.5 * (this._XconvertDOMtoCanvas(pointer.x) + this.edges['connectionEdge'].from.x);
  25835. this.sectors['support']['nodes']['targetViaNode'].y = this._YconvertDOMtoCanvas(pointer.y);
  25836. };
  25837. this.moving = true;
  25838. this.start();
  25839. }
  25840. }
  25841. }
  25842. };
  25843. exports._finishConnect = function(pointer) {
  25844. if (this._getSelectedNodeCount() == 1) {
  25845. // restore the drag function
  25846. this._handleOnDrag = this.cachedFunctions["_handleOnDrag"];
  25847. delete this.cachedFunctions["_handleOnDrag"];
  25848. // remember the edge id
  25849. var connectFromId = this.edges['connectionEdge'].fromId;
  25850. // remove the temporary nodes and edge
  25851. delete this.edges['connectionEdge'];
  25852. delete this.sectors['support']['nodes']['targetNode'];
  25853. delete this.sectors['support']['nodes']['targetViaNode'];
  25854. var node = this._getNodeAt(pointer);
  25855. if (node != null) {
  25856. if (node.clusterSize > 1) {
  25857. alert(this.constants.locales[this.constants.locale]["createEdgeError"])
  25858. }
  25859. else {
  25860. this._createEdge(connectFromId,node.id);
  25861. this._createManipulatorBar();
  25862. }
  25863. }
  25864. this._unselectAll();
  25865. }
  25866. };
  25867. /**
  25868. * Adds a node on the specified location
  25869. */
  25870. exports._addNode = function() {
  25871. if (this._selectionIsEmpty() && this.editMode == true) {
  25872. var positionObject = this._pointerToPositionObject(this.pointerPosition);
  25873. var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",allowedToMoveX:true,allowedToMoveY:true};
  25874. if (this.triggerFunctions.add) {
  25875. if (this.triggerFunctions.add.length == 2) {
  25876. var me = this;
  25877. this.triggerFunctions.add(defaultData, function(finalizedData) {
  25878. me.nodesData.add(finalizedData);
  25879. me._createManipulatorBar();
  25880. me.moving = true;
  25881. me.start();
  25882. });
  25883. }
  25884. else {
  25885. throw new Error('The function for add does not support two arguments (data,callback)');
  25886. this._createManipulatorBar();
  25887. this.moving = true;
  25888. this.start();
  25889. }
  25890. }
  25891. else {
  25892. this.nodesData.add(defaultData);
  25893. this._createManipulatorBar();
  25894. this.moving = true;
  25895. this.start();
  25896. }
  25897. }
  25898. };
  25899. /**
  25900. * connect two nodes with a new edge.
  25901. *
  25902. * @private
  25903. */
  25904. exports._createEdge = function(sourceNodeId,targetNodeId) {
  25905. if (this.editMode == true) {
  25906. var defaultData = {from:sourceNodeId, to:targetNodeId};
  25907. if (this.triggerFunctions.connect) {
  25908. if (this.triggerFunctions.connect.length == 2) {
  25909. var me = this;
  25910. this.triggerFunctions.connect(defaultData, function(finalizedData) {
  25911. me.edgesData.add(finalizedData);
  25912. me.moving = true;
  25913. me.start();
  25914. });
  25915. }
  25916. else {
  25917. throw new Error('The function for connect does not support two arguments (data,callback)');
  25918. this.moving = true;
  25919. this.start();
  25920. }
  25921. }
  25922. else {
  25923. this.edgesData.add(defaultData);
  25924. this.moving = true;
  25925. this.start();
  25926. }
  25927. }
  25928. };
  25929. /**
  25930. * connect two nodes with a new edge.
  25931. *
  25932. * @private
  25933. */
  25934. exports._editEdge = function(sourceNodeId,targetNodeId) {
  25935. if (this.editMode == true) {
  25936. var defaultData = {id: this.edgeBeingEdited.id, from:sourceNodeId, to:targetNodeId};
  25937. if (this.triggerFunctions.editEdge) {
  25938. if (this.triggerFunctions.editEdge.length == 2) {
  25939. var me = this;
  25940. this.triggerFunctions.editEdge(defaultData, function(finalizedData) {
  25941. me.edgesData.update(finalizedData);
  25942. me.moving = true;
  25943. me.start();
  25944. });
  25945. }
  25946. else {
  25947. throw new Error('The function for edit does not support two arguments (data, callback)');
  25948. this.moving = true;
  25949. this.start();
  25950. }
  25951. }
  25952. else {
  25953. this.edgesData.update(defaultData);
  25954. this.moving = true;
  25955. this.start();
  25956. }
  25957. }
  25958. };
  25959. /**
  25960. * Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color.
  25961. *
  25962. * @private
  25963. */
  25964. exports._editNode = function() {
  25965. if (this.triggerFunctions.edit && this.editMode == true) {
  25966. var node = this._getSelectedNode();
  25967. var data = {id:node.id,
  25968. label: node.label,
  25969. group: node.options.group,
  25970. shape: node.options.shape,
  25971. color: {
  25972. background:node.options.color.background,
  25973. border:node.options.color.border,
  25974. highlight: {
  25975. background:node.options.color.highlight.background,
  25976. border:node.options.color.highlight.border
  25977. }
  25978. }};
  25979. if (this.triggerFunctions.edit.length == 2) {
  25980. var me = this;
  25981. this.triggerFunctions.edit(data, function (finalizedData) {
  25982. me.nodesData.update(finalizedData);
  25983. me._createManipulatorBar();
  25984. me.moving = true;
  25985. me.start();
  25986. });
  25987. }
  25988. else {
  25989. throw new Error('The function for edit does not support two arguments (data, callback)');
  25990. }
  25991. }
  25992. else {
  25993. throw new Error('No edit function has been bound to this button');
  25994. }
  25995. };
  25996. /**
  25997. * delete everything in the selection
  25998. *
  25999. * @private
  26000. */
  26001. exports._deleteSelected = function() {
  26002. if (!this._selectionIsEmpty() && this.editMode == true) {
  26003. if (!this._clusterInSelection()) {
  26004. var selectedNodes = this.getSelectedNodes();
  26005. var selectedEdges = this.getSelectedEdges();
  26006. if (this.triggerFunctions.del) {
  26007. var me = this;
  26008. var data = {nodes: selectedNodes, edges: selectedEdges};
  26009. if (this.triggerFunctions.del.length = 2) {
  26010. this.triggerFunctions.del(data, function (finalizedData) {
  26011. me.edgesData.remove(finalizedData.edges);
  26012. me.nodesData.remove(finalizedData.nodes);
  26013. me._unselectAll();
  26014. me.moving = true;
  26015. me.start();
  26016. });
  26017. }
  26018. else {
  26019. throw new Error('The function for delete does not support two arguments (data, callback)')
  26020. }
  26021. }
  26022. else {
  26023. this.edgesData.remove(selectedEdges);
  26024. this.nodesData.remove(selectedNodes);
  26025. this._unselectAll();
  26026. this.moving = true;
  26027. this.start();
  26028. }
  26029. }
  26030. else {
  26031. alert(this.constants.locales[this.constants.locale]["deleteClusterError"]);
  26032. }
  26033. }
  26034. };
  26035. /***/ },
  26036. /* 57 */
  26037. /***/ function(module, exports, __webpack_require__) {
  26038. var util = __webpack_require__(1);
  26039. var Hammer = __webpack_require__(41);
  26040. exports._cleanNavigation = function() {
  26041. // clean up previous navigation items
  26042. var wrapper = document.getElementById('network-navigation_wrapper');
  26043. if (wrapper && wrapper.parentNode) {
  26044. wrapper.parentNode.removeChild(wrapper);
  26045. }
  26046. document.onmouseup = null;
  26047. };
  26048. /**
  26049. * Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation
  26050. * they have a triggerFunction which is called on click. If the position of the navigation controls is dependent
  26051. * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false.
  26052. * This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas.
  26053. *
  26054. * @private
  26055. */
  26056. exports._loadNavigationElements = function() {
  26057. this._cleanNavigation();
  26058. this.navigationDivs = {};
  26059. var navigationDivs = ['up','down','left','right','zoomIn','zoomOut','zoomExtends'];
  26060. var navigationDivActions = ['_moveUp','_moveDown','_moveLeft','_moveRight','_zoomIn','_zoomOut','zoomExtent'];
  26061. this.navigationDivs['wrapper'] = document.createElement('div');
  26062. this.navigationDivs['wrapper'].id = 'network-navigation_wrapper';
  26063. this.frame.appendChild(this.navigationDivs['wrapper']);
  26064. var me = this;
  26065. for (var i = 0; i < navigationDivs.length; i++) {
  26066. this.navigationDivs[navigationDivs[i]] = document.createElement('div');
  26067. this.navigationDivs[navigationDivs[i]].id = 'network-navigation_' + navigationDivs[i];
  26068. this.navigationDivs[navigationDivs[i]].className = 'network-navigation ' + navigationDivs[i];
  26069. this.navigationDivs['wrapper'].appendChild(this.navigationDivs[navigationDivs[i]]);
  26070. var hammer = Hammer(this.navigationDivs[navigationDivs[i]], {prevent_default: true});
  26071. hammer.on('touch', me[navigationDivActions[i]].bind(me));
  26072. }
  26073. var hammer = Hammer(document, {prevent_default: false});
  26074. hammer.on('release', me._stopMovement.bind(me));
  26075. };
  26076. /**
  26077. * this stops all movement induced by the navigation buttons
  26078. *
  26079. * @private
  26080. */
  26081. exports._stopMovement = function() {
  26082. this._xStopMoving();
  26083. this._yStopMoving();
  26084. this._stopZoom();
  26085. };
  26086. /**
  26087. * move the screen up
  26088. * By using the increments, instead of adding a fixed number to the translation, we keep fluent and
  26089. * instant movement. The onKeypress event triggers immediately, then pauses, then triggers frequently
  26090. * To avoid this behaviour, we do the translation in the start loop.
  26091. *
  26092. * @private
  26093. */
  26094. exports._moveUp = function(event) {
  26095. this.yIncrement = this.constants.keyboard.speed.y;
  26096. this.start(); // if there is no node movement, the calculation wont be done
  26097. event.preventDefault();
  26098. };
  26099. /**
  26100. * move the screen down
  26101. * @private
  26102. */
  26103. exports._moveDown = function(event) {
  26104. this.yIncrement = -this.constants.keyboard.speed.y;
  26105. this.start(); // if there is no node movement, the calculation wont be done
  26106. event.preventDefault();
  26107. };
  26108. /**
  26109. * move the screen left
  26110. * @private
  26111. */
  26112. exports._moveLeft = function(event) {
  26113. this.xIncrement = this.constants.keyboard.speed.x;
  26114. this.start(); // if there is no node movement, the calculation wont be done
  26115. event.preventDefault();
  26116. };
  26117. /**
  26118. * move the screen right
  26119. * @private
  26120. */
  26121. exports._moveRight = function(event) {
  26122. this.xIncrement = -this.constants.keyboard.speed.y;
  26123. this.start(); // if there is no node movement, the calculation wont be done
  26124. event.preventDefault();
  26125. };
  26126. /**
  26127. * Zoom in, using the same method as the movement.
  26128. * @private
  26129. */
  26130. exports._zoomIn = function(event) {
  26131. this.zoomIncrement = this.constants.keyboard.speed.zoom;
  26132. this.start(); // if there is no node movement, the calculation wont be done
  26133. event.preventDefault();
  26134. };
  26135. /**
  26136. * Zoom out
  26137. * @private
  26138. */
  26139. exports._zoomOut = function(event) {
  26140. this.zoomIncrement = -this.constants.keyboard.speed.zoom;
  26141. this.start(); // if there is no node movement, the calculation wont be done
  26142. event.preventDefault();
  26143. };
  26144. /**
  26145. * Stop zooming and unhighlight the zoom controls
  26146. * @private
  26147. */
  26148. exports._stopZoom = function(event) {
  26149. this.zoomIncrement = 0;
  26150. event && event.preventDefault();
  26151. };
  26152. /**
  26153. * Stop moving in the Y direction and unHighlight the up and down
  26154. * @private
  26155. */
  26156. exports._yStopMoving = function(event) {
  26157. this.yIncrement = 0;
  26158. event && event.preventDefault();
  26159. };
  26160. /**
  26161. * Stop moving in the X direction and unHighlight left and right.
  26162. * @private
  26163. */
  26164. exports._xStopMoving = function(event) {
  26165. this.xIncrement = 0;
  26166. event && event.preventDefault();
  26167. };
  26168. /***/ },
  26169. /* 58 */
  26170. /***/ function(module, exports, __webpack_require__) {
  26171. exports._resetLevels = function() {
  26172. for (var nodeId in this.nodes) {
  26173. if (this.nodes.hasOwnProperty(nodeId)) {
  26174. var node = this.nodes[nodeId];
  26175. if (node.preassignedLevel == false) {
  26176. node.level = -1;
  26177. node.hierarchyEnumerated = false;
  26178. }
  26179. }
  26180. }
  26181. };
  26182. /**
  26183. * This is the main function to layout the nodes in a hierarchical way.
  26184. * It checks if the node details are supplied correctly
  26185. *
  26186. * @private
  26187. */
  26188. exports._setupHierarchicalLayout = function() {
  26189. if (this.constants.hierarchicalLayout.enabled == true && this.nodeIndices.length > 0) {
  26190. if (this.constants.hierarchicalLayout.direction == "RL" || this.constants.hierarchicalLayout.direction == "DU") {
  26191. this.constants.hierarchicalLayout.levelSeparation *= -1;
  26192. }
  26193. else {
  26194. this.constants.hierarchicalLayout.levelSeparation = Math.abs(this.constants.hierarchicalLayout.levelSeparation);
  26195. }
  26196. if (this.constants.hierarchicalLayout.direction == "RL" || this.constants.hierarchicalLayout.direction == "LR") {
  26197. if (this.constants.smoothCurves.enabled == true) {
  26198. this.constants.smoothCurves.type = "vertical";
  26199. }
  26200. }
  26201. else {
  26202. if (this.constants.smoothCurves.enabled == true) {
  26203. this.constants.smoothCurves.type = "horizontal";
  26204. }
  26205. }
  26206. // get the size of the largest hubs and check if the user has defined a level for a node.
  26207. var hubsize = 0;
  26208. var node, nodeId;
  26209. var definedLevel = false;
  26210. var undefinedLevel = false;
  26211. for (nodeId in this.nodes) {
  26212. if (this.nodes.hasOwnProperty(nodeId)) {
  26213. node = this.nodes[nodeId];
  26214. if (node.level != -1) {
  26215. definedLevel = true;
  26216. }
  26217. else {
  26218. undefinedLevel = true;
  26219. }
  26220. if (hubsize < node.edges.length) {
  26221. hubsize = node.edges.length;
  26222. }
  26223. }
  26224. }
  26225. // if the user defined some levels but not all, alert and run without hierarchical layout
  26226. if (undefinedLevel == true && definedLevel == true) {
  26227. throw new Error("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.");
  26228. this.zoomExtent(true,this.constants.clustering.enabled);
  26229. if (!this.constants.clustering.enabled) {
  26230. this.start();
  26231. }
  26232. }
  26233. else {
  26234. // setup the system to use hierarchical method.
  26235. this._changeConstants();
  26236. // define levels if undefined by the users. Based on hubsize
  26237. if (undefinedLevel == true) {
  26238. if (this.constants.hierarchicalLayout.layout == "hubsize") {
  26239. this._determineLevels(hubsize);
  26240. }
  26241. else {
  26242. this._determineLevelsDirected();
  26243. }
  26244. }
  26245. // check the distribution of the nodes per level.
  26246. var distribution = this._getDistribution();
  26247. // place the nodes on the canvas. This also stablilizes the system.
  26248. this._placeNodesByHierarchy(distribution);
  26249. // start the simulation.
  26250. this.start();
  26251. }
  26252. }
  26253. };
  26254. /**
  26255. * This function places the nodes on the canvas based on the hierarchial distribution.
  26256. *
  26257. * @param {Object} distribution | obtained by the function this._getDistribution()
  26258. * @private
  26259. */
  26260. exports._placeNodesByHierarchy = function(distribution) {
  26261. var nodeId, node;
  26262. // start placing all the level 0 nodes first. Then recursively position their branches.
  26263. for (var level in distribution) {
  26264. if (distribution.hasOwnProperty(level)) {
  26265. for (nodeId in distribution[level].nodes) {
  26266. if (distribution[level].nodes.hasOwnProperty(nodeId)) {
  26267. node = distribution[level].nodes[nodeId];
  26268. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  26269. if (node.xFixed) {
  26270. node.x = distribution[level].minPos;
  26271. node.xFixed = false;
  26272. distribution[level].minPos += distribution[level].nodeSpacing;
  26273. }
  26274. }
  26275. else {
  26276. if (node.yFixed) {
  26277. node.y = distribution[level].minPos;
  26278. node.yFixed = false;
  26279. distribution[level].minPos += distribution[level].nodeSpacing;
  26280. }
  26281. }
  26282. this._placeBranchNodes(node.edges,node.id,distribution,node.level);
  26283. }
  26284. }
  26285. }
  26286. }
  26287. // stabilize the system after positioning. This function calls zoomExtent.
  26288. this._stabilize();
  26289. };
  26290. /**
  26291. * This function get the distribution of levels based on hubsize
  26292. *
  26293. * @returns {Object}
  26294. * @private
  26295. */
  26296. exports._getDistribution = function() {
  26297. var distribution = {};
  26298. var nodeId, node, level;
  26299. // 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.
  26300. // the fix of X is removed after the x value has been set.
  26301. for (nodeId in this.nodes) {
  26302. if (this.nodes.hasOwnProperty(nodeId)) {
  26303. node = this.nodes[nodeId];
  26304. node.xFixed = true;
  26305. node.yFixed = true;
  26306. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  26307. node.y = this.constants.hierarchicalLayout.levelSeparation*node.level;
  26308. }
  26309. else {
  26310. node.x = this.constants.hierarchicalLayout.levelSeparation*node.level;
  26311. }
  26312. if (distribution[node.level] === undefined) {
  26313. distribution[node.level] = {amount: 0, nodes: {}, minPos:0, nodeSpacing:0};
  26314. }
  26315. distribution[node.level].amount += 1;
  26316. distribution[node.level].nodes[nodeId] = node;
  26317. }
  26318. }
  26319. // determine the largest amount of nodes of all levels
  26320. var maxCount = 0;
  26321. for (level in distribution) {
  26322. if (distribution.hasOwnProperty(level)) {
  26323. if (maxCount < distribution[level].amount) {
  26324. maxCount = distribution[level].amount;
  26325. }
  26326. }
  26327. }
  26328. // set the initial position and spacing of each nodes accordingly
  26329. for (level in distribution) {
  26330. if (distribution.hasOwnProperty(level)) {
  26331. distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing;
  26332. distribution[level].nodeSpacing /= (distribution[level].amount + 1);
  26333. distribution[level].minPos = distribution[level].nodeSpacing - (0.5 * (distribution[level].amount + 1) * distribution[level].nodeSpacing);
  26334. }
  26335. }
  26336. return distribution;
  26337. };
  26338. /**
  26339. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  26340. *
  26341. * @param hubsize
  26342. * @private
  26343. */
  26344. exports._determineLevels = function(hubsize) {
  26345. var nodeId, node;
  26346. // determine hubs
  26347. for (nodeId in this.nodes) {
  26348. if (this.nodes.hasOwnProperty(nodeId)) {
  26349. node = this.nodes[nodeId];
  26350. if (node.edges.length == hubsize) {
  26351. node.level = 0;
  26352. }
  26353. }
  26354. }
  26355. // branch from hubs
  26356. for (nodeId in this.nodes) {
  26357. if (this.nodes.hasOwnProperty(nodeId)) {
  26358. node = this.nodes[nodeId];
  26359. if (node.level == 0) {
  26360. this._setLevel(1,node.edges,node.id);
  26361. }
  26362. }
  26363. }
  26364. };
  26365. /**
  26366. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  26367. *
  26368. * @param hubsize
  26369. * @private
  26370. */
  26371. exports._determineLevelsDirected = function() {
  26372. var nodeId, node;
  26373. // set first node to source
  26374. for (nodeId in this.nodes) {
  26375. if (this.nodes.hasOwnProperty(nodeId)) {
  26376. this.nodes[nodeId].level = 10000;
  26377. break;
  26378. }
  26379. }
  26380. // branch from hubs
  26381. for (nodeId in this.nodes) {
  26382. if (this.nodes.hasOwnProperty(nodeId)) {
  26383. node = this.nodes[nodeId];
  26384. if (node.level == 10000) {
  26385. this._setLevelDirected(10000,node.edges,node.id);
  26386. }
  26387. }
  26388. }
  26389. // branch from hubs
  26390. var minLevel = 10000;
  26391. for (nodeId in this.nodes) {
  26392. if (this.nodes.hasOwnProperty(nodeId)) {
  26393. node = this.nodes[nodeId];
  26394. minLevel = node.level < minLevel ? node.level : minLevel;
  26395. }
  26396. }
  26397. // branch from hubs
  26398. for (nodeId in this.nodes) {
  26399. if (this.nodes.hasOwnProperty(nodeId)) {
  26400. node = this.nodes[nodeId];
  26401. node.level -= minLevel;
  26402. }
  26403. }
  26404. };
  26405. /**
  26406. * Since hierarchical layout does not support:
  26407. * - smooth curves (based on the physics),
  26408. * - clustering (based on dynamic node counts)
  26409. *
  26410. * We disable both features so there will be no problems.
  26411. *
  26412. * @private
  26413. */
  26414. exports._changeConstants = function() {
  26415. this.constants.clustering.enabled = false;
  26416. this.constants.physics.barnesHut.enabled = false;
  26417. this.constants.physics.hierarchicalRepulsion.enabled = true;
  26418. this._loadSelectedForceSolver();
  26419. if (this.constants.smoothCurves.enabled == true) {
  26420. this.constants.smoothCurves.dynamic = false;
  26421. }
  26422. this._configureSmoothCurves();
  26423. };
  26424. /**
  26425. * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
  26426. * on a X position that ensures there will be no overlap.
  26427. *
  26428. * @param edges
  26429. * @param parentId
  26430. * @param distribution
  26431. * @param parentLevel
  26432. * @private
  26433. */
  26434. exports._placeBranchNodes = function(edges, parentId, distribution, parentLevel) {
  26435. for (var i = 0; i < edges.length; i++) {
  26436. var childNode = null;
  26437. if (edges[i].toId == parentId) {
  26438. childNode = edges[i].from;
  26439. }
  26440. else {
  26441. childNode = edges[i].to;
  26442. }
  26443. // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
  26444. var nodeMoved = false;
  26445. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  26446. if (childNode.xFixed && childNode.level > parentLevel) {
  26447. childNode.xFixed = false;
  26448. childNode.x = distribution[childNode.level].minPos;
  26449. nodeMoved = true;
  26450. }
  26451. }
  26452. else {
  26453. if (childNode.yFixed && childNode.level > parentLevel) {
  26454. childNode.yFixed = false;
  26455. childNode.y = distribution[childNode.level].minPos;
  26456. nodeMoved = true;
  26457. }
  26458. }
  26459. if (nodeMoved == true) {
  26460. distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing;
  26461. if (childNode.edges.length > 1) {
  26462. this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level);
  26463. }
  26464. }
  26465. }
  26466. };
  26467. /**
  26468. * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
  26469. *
  26470. * @param level
  26471. * @param edges
  26472. * @param parentId
  26473. * @private
  26474. */
  26475. exports._setLevel = function(level, edges, parentId) {
  26476. for (var i = 0; i < edges.length; i++) {
  26477. var childNode = null;
  26478. if (edges[i].toId == parentId) {
  26479. childNode = edges[i].from;
  26480. }
  26481. else {
  26482. childNode = edges[i].to;
  26483. }
  26484. if (childNode.level == -1 || childNode.level > level) {
  26485. childNode.level = level;
  26486. if (childNode.edges.length > 1) {
  26487. this._setLevel(level+1, childNode.edges, childNode.id);
  26488. }
  26489. }
  26490. }
  26491. };
  26492. /**
  26493. * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
  26494. *
  26495. * @param level
  26496. * @param edges
  26497. * @param parentId
  26498. * @private
  26499. */
  26500. exports._setLevelDirected = function(level, edges, parentId) {
  26501. this.nodes[parentId].hierarchyEnumerated = true;
  26502. for (var i = 0; i < edges.length; i++) {
  26503. var childNode = null;
  26504. var direction = 1;
  26505. if (edges[i].toId == parentId) {
  26506. childNode = edges[i].from;
  26507. direction = -1;
  26508. }
  26509. else {
  26510. childNode = edges[i].to;
  26511. }
  26512. if (childNode.level == -1) {
  26513. childNode.level = level + direction;
  26514. }
  26515. }
  26516. for (var i = 0; i < edges.length; i++) {
  26517. var childNode = null;
  26518. if (edges[i].toId == parentId) {childNode = edges[i].from;}
  26519. else {childNode = edges[i].to;}
  26520. if (childNode.edges.length > 1 && childNode.hierarchyEnumerated === false) {
  26521. this._setLevelDirected(childNode.level, childNode.edges, childNode.id);
  26522. }
  26523. }
  26524. };
  26525. /**
  26526. * Unfix nodes
  26527. *
  26528. * @private
  26529. */
  26530. exports._restoreNodes = function() {
  26531. for (var nodeId in this.nodes) {
  26532. if (this.nodes.hasOwnProperty(nodeId)) {
  26533. this.nodes[nodeId].xFixed = false;
  26534. this.nodes[nodeId].yFixed = false;
  26535. }
  26536. }
  26537. };
  26538. /***/ },
  26539. /* 59 */
  26540. /***/ function(module, exports, __webpack_require__) {
  26541. var util = __webpack_require__(1);
  26542. var RepulsionMixin = __webpack_require__(61);
  26543. var HierarchialRepulsionMixin = __webpack_require__(62);
  26544. var BarnesHutMixin = __webpack_require__(63);
  26545. /**
  26546. * Toggling barnes Hut calculation on and off.
  26547. *
  26548. * @private
  26549. */
  26550. exports._toggleBarnesHut = function () {
  26551. this.constants.physics.barnesHut.enabled = !this.constants.physics.barnesHut.enabled;
  26552. this._loadSelectedForceSolver();
  26553. this.moving = true;
  26554. this.start();
  26555. };
  26556. /**
  26557. * This loads the node force solver based on the barnes hut or repulsion algorithm
  26558. *
  26559. * @private
  26560. */
  26561. exports._loadSelectedForceSolver = function () {
  26562. // this overloads the this._calculateNodeForces
  26563. if (this.constants.physics.barnesHut.enabled == true) {
  26564. this._clearMixin(RepulsionMixin);
  26565. this._clearMixin(HierarchialRepulsionMixin);
  26566. this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity;
  26567. this.constants.physics.springLength = this.constants.physics.barnesHut.springLength;
  26568. this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant;
  26569. this.constants.physics.damping = this.constants.physics.barnesHut.damping;
  26570. this._loadMixin(BarnesHutMixin);
  26571. }
  26572. else if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  26573. this._clearMixin(BarnesHutMixin);
  26574. this._clearMixin(RepulsionMixin);
  26575. this.constants.physics.centralGravity = this.constants.physics.hierarchicalRepulsion.centralGravity;
  26576. this.constants.physics.springLength = this.constants.physics.hierarchicalRepulsion.springLength;
  26577. this.constants.physics.springConstant = this.constants.physics.hierarchicalRepulsion.springConstant;
  26578. this.constants.physics.damping = this.constants.physics.hierarchicalRepulsion.damping;
  26579. this._loadMixin(HierarchialRepulsionMixin);
  26580. }
  26581. else {
  26582. this._clearMixin(BarnesHutMixin);
  26583. this._clearMixin(HierarchialRepulsionMixin);
  26584. this.barnesHutTree = undefined;
  26585. this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity;
  26586. this.constants.physics.springLength = this.constants.physics.repulsion.springLength;
  26587. this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant;
  26588. this.constants.physics.damping = this.constants.physics.repulsion.damping;
  26589. this._loadMixin(RepulsionMixin);
  26590. }
  26591. };
  26592. /**
  26593. * Before calculating the forces, we check if we need to cluster to keep up performance and we check
  26594. * if there is more than one node. If it is just one node, we dont calculate anything.
  26595. *
  26596. * @private
  26597. */
  26598. exports._initializeForceCalculation = function () {
  26599. // stop calculation if there is only one node
  26600. if (this.nodeIndices.length == 1) {
  26601. this.nodes[this.nodeIndices[0]]._setForce(0, 0);
  26602. }
  26603. else {
  26604. // if there are too many nodes on screen, we cluster without repositioning
  26605. if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
  26606. this.clusterToFit(this.constants.clustering.reduceToNodes, false);
  26607. }
  26608. // we now start the force calculation
  26609. this._calculateForces();
  26610. }
  26611. };
  26612. /**
  26613. * Calculate the external forces acting on the nodes
  26614. * Forces are caused by: edges, repulsing forces between nodes, gravity
  26615. * @private
  26616. */
  26617. exports._calculateForces = function () {
  26618. // Gravity is required to keep separated groups from floating off
  26619. // the forces are reset to zero in this loop by using _setForce instead
  26620. // of _addForce
  26621. this._calculateGravitationalForces();
  26622. this._calculateNodeForces();
  26623. if (this.constants.physics.springConstant > 0) {
  26624. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  26625. this._calculateSpringForcesWithSupport();
  26626. }
  26627. else {
  26628. if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  26629. this._calculateHierarchicalSpringForces();
  26630. }
  26631. else {
  26632. this._calculateSpringForces();
  26633. }
  26634. }
  26635. }
  26636. };
  26637. /**
  26638. * Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also
  26639. * handled in the calculateForces function. We then use a quadratic curve with the center node as control.
  26640. * This function joins the datanodes and invisible (called support) nodes into one object.
  26641. * We do this so we do not contaminate this.nodes with the support nodes.
  26642. *
  26643. * @private
  26644. */
  26645. exports._updateCalculationNodes = function () {
  26646. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  26647. this.calculationNodes = {};
  26648. this.calculationNodeIndices = [];
  26649. for (var nodeId in this.nodes) {
  26650. if (this.nodes.hasOwnProperty(nodeId)) {
  26651. this.calculationNodes[nodeId] = this.nodes[nodeId];
  26652. }
  26653. }
  26654. var supportNodes = this.sectors['support']['nodes'];
  26655. for (var supportNodeId in supportNodes) {
  26656. if (supportNodes.hasOwnProperty(supportNodeId)) {
  26657. if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) {
  26658. this.calculationNodes[supportNodeId] = supportNodes[supportNodeId];
  26659. }
  26660. else {
  26661. supportNodes[supportNodeId]._setForce(0, 0);
  26662. }
  26663. }
  26664. }
  26665. for (var idx in this.calculationNodes) {
  26666. if (this.calculationNodes.hasOwnProperty(idx)) {
  26667. this.calculationNodeIndices.push(idx);
  26668. }
  26669. }
  26670. }
  26671. else {
  26672. this.calculationNodes = this.nodes;
  26673. this.calculationNodeIndices = this.nodeIndices;
  26674. }
  26675. };
  26676. /**
  26677. * this function applies the central gravity effect to keep groups from floating off
  26678. *
  26679. * @private
  26680. */
  26681. exports._calculateGravitationalForces = function () {
  26682. var dx, dy, distance, node, i;
  26683. var nodes = this.calculationNodes;
  26684. var gravity = this.constants.physics.centralGravity;
  26685. var gravityForce = 0;
  26686. for (i = 0; i < this.calculationNodeIndices.length; i++) {
  26687. node = nodes[this.calculationNodeIndices[i]];
  26688. node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters.
  26689. // gravity does not apply when we are in a pocket sector
  26690. if (this._sector() == "default" && gravity != 0) {
  26691. dx = -node.x;
  26692. dy = -node.y;
  26693. distance = Math.sqrt(dx * dx + dy * dy);
  26694. gravityForce = (distance == 0) ? 0 : (gravity / distance);
  26695. node.fx = dx * gravityForce;
  26696. node.fy = dy * gravityForce;
  26697. }
  26698. else {
  26699. node.fx = 0;
  26700. node.fy = 0;
  26701. }
  26702. }
  26703. };
  26704. /**
  26705. * this function calculates the effects of the springs in the case of unsmooth curves.
  26706. *
  26707. * @private
  26708. */
  26709. exports._calculateSpringForces = function () {
  26710. var edgeLength, edge, edgeId;
  26711. var dx, dy, fx, fy, springForce, distance;
  26712. var edges = this.edges;
  26713. // forces caused by the edges, modelled as springs
  26714. for (edgeId in edges) {
  26715. if (edges.hasOwnProperty(edgeId)) {
  26716. edge = edges[edgeId];
  26717. if (edge.connected) {
  26718. // only calculate forces if nodes are in the same sector
  26719. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  26720. edgeLength = edge.physics.springLength;
  26721. // this implies that the edges between big clusters are longer
  26722. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  26723. dx = (edge.from.x - edge.to.x);
  26724. dy = (edge.from.y - edge.to.y);
  26725. distance = Math.sqrt(dx * dx + dy * dy);
  26726. if (distance == 0) {
  26727. distance = 0.01;
  26728. }
  26729. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  26730. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  26731. fx = dx * springForce;
  26732. fy = dy * springForce;
  26733. edge.from.fx += fx;
  26734. edge.from.fy += fy;
  26735. edge.to.fx -= fx;
  26736. edge.to.fy -= fy;
  26737. }
  26738. }
  26739. }
  26740. }
  26741. };
  26742. /**
  26743. * This function calculates the springforces on the nodes, accounting for the support nodes.
  26744. *
  26745. * @private
  26746. */
  26747. exports._calculateSpringForcesWithSupport = function () {
  26748. var edgeLength, edge, edgeId, combinedClusterSize;
  26749. var edges = this.edges;
  26750. // forces caused by the edges, modelled as springs
  26751. for (edgeId in edges) {
  26752. if (edges.hasOwnProperty(edgeId)) {
  26753. edge = edges[edgeId];
  26754. if (edge.connected) {
  26755. // only calculate forces if nodes are in the same sector
  26756. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  26757. if (edge.via != null) {
  26758. var node1 = edge.to;
  26759. var node2 = edge.via;
  26760. var node3 = edge.from;
  26761. edgeLength = edge.physics.springLength;
  26762. combinedClusterSize = node1.clusterSize + node3.clusterSize - 2;
  26763. // this implies that the edges between big clusters are longer
  26764. edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth;
  26765. this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
  26766. this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
  26767. }
  26768. }
  26769. }
  26770. }
  26771. }
  26772. };
  26773. /**
  26774. * This is the code actually performing the calculation for the function above. It is split out to avoid repetition.
  26775. *
  26776. * @param node1
  26777. * @param node2
  26778. * @param edgeLength
  26779. * @private
  26780. */
  26781. exports._calculateSpringForce = function (node1, node2, edgeLength) {
  26782. var dx, dy, fx, fy, springForce, distance;
  26783. dx = (node1.x - node2.x);
  26784. dy = (node1.y - node2.y);
  26785. distance = Math.sqrt(dx * dx + dy * dy);
  26786. if (distance == 0) {
  26787. distance = 0.01;
  26788. }
  26789. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  26790. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  26791. fx = dx * springForce;
  26792. fy = dy * springForce;
  26793. node1.fx += fx;
  26794. node1.fy += fy;
  26795. node2.fx -= fx;
  26796. node2.fy -= fy;
  26797. };
  26798. /**
  26799. * Load the HTML for the physics config and bind it
  26800. * @private
  26801. */
  26802. exports._loadPhysicsConfiguration = function () {
  26803. if (this.physicsConfiguration === undefined) {
  26804. this.backupConstants = {};
  26805. util.deepExtend(this.backupConstants,this.constants);
  26806. var hierarchicalLayoutDirections = ["LR", "RL", "UD", "DU"];
  26807. this.physicsConfiguration = document.createElement('div');
  26808. this.physicsConfiguration.className = "PhysicsConfiguration";
  26809. this.physicsConfiguration.innerHTML = '' +
  26810. '<table><tr><td><b>Simulation Mode:</b></td></tr>' +
  26811. '<tr>' +
  26812. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod1" value="BH" checked="checked">Barnes Hut</td>' +
  26813. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod2" value="R">Repulsion</td>' +
  26814. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod3" value="H">Hierarchical</td>' +
  26815. '</tr>' +
  26816. '</table>' +
  26817. '<table id="graph_BH_table" style="display:none">' +
  26818. '<tr><td><b>Barnes Hut</b></td></tr>' +
  26819. '<tr>' +
  26820. '<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>' +
  26821. '</tr>' +
  26822. '<tr>' +
  26823. '<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>' +
  26824. '</tr>' +
  26825. '<tr>' +
  26826. '<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>' +
  26827. '</tr>' +
  26828. '<tr>' +
  26829. '<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>' +
  26830. '</tr>' +
  26831. '<tr>' +
  26832. '<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>' +
  26833. '</tr>' +
  26834. '</table>' +
  26835. '<table id="graph_R_table" style="display:none">' +
  26836. '<tr><td><b>Repulsion</b></td></tr>' +
  26837. '<tr>' +
  26838. '<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>' +
  26839. '</tr>' +
  26840. '<tr>' +
  26841. '<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>' +
  26842. '</tr>' +
  26843. '<tr>' +
  26844. '<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>' +
  26845. '</tr>' +
  26846. '<tr>' +
  26847. '<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>' +
  26848. '</tr>' +
  26849. '<tr>' +
  26850. '<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>' +
  26851. '</tr>' +
  26852. '</table>' +
  26853. '<table id="graph_H_table" style="display:none">' +
  26854. '<tr><td width="150"><b>Hierarchical</b></td></tr>' +
  26855. '<tr>' +
  26856. '<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>' +
  26857. '</tr>' +
  26858. '<tr>' +
  26859. '<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>' +
  26860. '</tr>' +
  26861. '<tr>' +
  26862. '<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>' +
  26863. '</tr>' +
  26864. '<tr>' +
  26865. '<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>' +
  26866. '</tr>' +
  26867. '<tr>' +
  26868. '<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>' +
  26869. '</tr>' +
  26870. '<tr>' +
  26871. '<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>' +
  26872. '</tr>' +
  26873. '<tr>' +
  26874. '<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>' +
  26875. '</tr>' +
  26876. '<tr>' +
  26877. '<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>' +
  26878. '</tr>' +
  26879. '</table>' +
  26880. '<table><tr><td><b>Options:</b></td></tr>' +
  26881. '<tr>' +
  26882. '<td width="180px"><input type="button" id="graph_toggleSmooth" value="Toggle smoothCurves" style="width:150px"></td>' +
  26883. '<td width="180px"><input type="button" id="graph_repositionNodes" value="Reinitialize" style="width:150px"></td>' +
  26884. '<td width="180px"><input type="button" id="graph_generateOptions" value="Generate Options" style="width:150px"></td>' +
  26885. '</tr>' +
  26886. '</table>'
  26887. this.containerElement.parentElement.insertBefore(this.physicsConfiguration, this.containerElement);
  26888. this.optionsDiv = document.createElement("div");
  26889. this.optionsDiv.style.fontSize = "14px";
  26890. this.optionsDiv.style.fontFamily = "verdana";
  26891. this.containerElement.parentElement.insertBefore(this.optionsDiv, this.containerElement);
  26892. var rangeElement;
  26893. rangeElement = document.getElementById('graph_BH_gc');
  26894. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_gc', -1, "physics_barnesHut_gravitationalConstant");
  26895. rangeElement = document.getElementById('graph_BH_cg');
  26896. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_cg', 1, "physics_centralGravity");
  26897. rangeElement = document.getElementById('graph_BH_sc');
  26898. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sc', 1, "physics_springConstant");
  26899. rangeElement = document.getElementById('graph_BH_sl');
  26900. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sl', 1, "physics_springLength");
  26901. rangeElement = document.getElementById('graph_BH_damp');
  26902. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_damp', 1, "physics_damping");
  26903. rangeElement = document.getElementById('graph_R_nd');
  26904. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_nd', 1, "physics_repulsion_nodeDistance");
  26905. rangeElement = document.getElementById('graph_R_cg');
  26906. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_cg', 1, "physics_centralGravity");
  26907. rangeElement = document.getElementById('graph_R_sc');
  26908. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sc', 1, "physics_springConstant");
  26909. rangeElement = document.getElementById('graph_R_sl');
  26910. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sl', 1, "physics_springLength");
  26911. rangeElement = document.getElementById('graph_R_damp');
  26912. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_damp', 1, "physics_damping");
  26913. rangeElement = document.getElementById('graph_H_nd');
  26914. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  26915. rangeElement = document.getElementById('graph_H_cg');
  26916. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_cg', 1, "physics_centralGravity");
  26917. rangeElement = document.getElementById('graph_H_sc');
  26918. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sc', 1, "physics_springConstant");
  26919. rangeElement = document.getElementById('graph_H_sl');
  26920. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sl', 1, "physics_springLength");
  26921. rangeElement = document.getElementById('graph_H_damp');
  26922. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_damp', 1, "physics_damping");
  26923. rangeElement = document.getElementById('graph_H_direction');
  26924. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_direction', hierarchicalLayoutDirections, "hierarchicalLayout_direction");
  26925. rangeElement = document.getElementById('graph_H_levsep');
  26926. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_levsep', 1, "hierarchicalLayout_levelSeparation");
  26927. rangeElement = document.getElementById('graph_H_nspac');
  26928. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nspac', 1, "hierarchicalLayout_nodeSpacing");
  26929. var radioButton1 = document.getElementById("graph_physicsMethod1");
  26930. var radioButton2 = document.getElementById("graph_physicsMethod2");
  26931. var radioButton3 = document.getElementById("graph_physicsMethod3");
  26932. radioButton2.checked = true;
  26933. if (this.constants.physics.barnesHut.enabled) {
  26934. radioButton1.checked = true;
  26935. }
  26936. if (this.constants.hierarchicalLayout.enabled) {
  26937. radioButton3.checked = true;
  26938. }
  26939. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  26940. var graph_repositionNodes = document.getElementById("graph_repositionNodes");
  26941. var graph_generateOptions = document.getElementById("graph_generateOptions");
  26942. graph_toggleSmooth.onclick = graphToggleSmoothCurves.bind(this);
  26943. graph_repositionNodes.onclick = graphRepositionNodes.bind(this);
  26944. graph_generateOptions.onclick = graphGenerateOptions.bind(this);
  26945. if (this.constants.smoothCurves == true && this.constants.dynamicSmoothCurves == false) {
  26946. graph_toggleSmooth.style.background = "#A4FF56";
  26947. }
  26948. else {
  26949. graph_toggleSmooth.style.background = "#FF8532";
  26950. }
  26951. switchConfigurations.apply(this);
  26952. radioButton1.onchange = switchConfigurations.bind(this);
  26953. radioButton2.onchange = switchConfigurations.bind(this);
  26954. radioButton3.onchange = switchConfigurations.bind(this);
  26955. }
  26956. };
  26957. /**
  26958. * This overwrites the this.constants.
  26959. *
  26960. * @param constantsVariableName
  26961. * @param value
  26962. * @private
  26963. */
  26964. exports._overWriteGraphConstants = function (constantsVariableName, value) {
  26965. var nameArray = constantsVariableName.split("_");
  26966. if (nameArray.length == 1) {
  26967. this.constants[nameArray[0]] = value;
  26968. }
  26969. else if (nameArray.length == 2) {
  26970. this.constants[nameArray[0]][nameArray[1]] = value;
  26971. }
  26972. else if (nameArray.length == 3) {
  26973. this.constants[nameArray[0]][nameArray[1]][nameArray[2]] = value;
  26974. }
  26975. };
  26976. /**
  26977. * this function is bound to the toggle smooth curves button. That is also why it is not in the prototype.
  26978. */
  26979. function graphToggleSmoothCurves () {
  26980. this.constants.smoothCurves.enabled = !this.constants.smoothCurves.enabled;
  26981. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  26982. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  26983. else {graph_toggleSmooth.style.background = "#FF8532";}
  26984. this._configureSmoothCurves(false);
  26985. }
  26986. /**
  26987. * this function is used to scramble the nodes
  26988. *
  26989. */
  26990. function graphRepositionNodes () {
  26991. for (var nodeId in this.calculationNodes) {
  26992. if (this.calculationNodes.hasOwnProperty(nodeId)) {
  26993. this.calculationNodes[nodeId].vx = 0; this.calculationNodes[nodeId].vy = 0;
  26994. this.calculationNodes[nodeId].fx = 0; this.calculationNodes[nodeId].fy = 0;
  26995. }
  26996. }
  26997. if (this.constants.hierarchicalLayout.enabled == true) {
  26998. this._setupHierarchicalLayout();
  26999. showValueOfRange.call(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  27000. showValueOfRange.call(this, 'graph_H_cg', 1, "physics_centralGravity");
  27001. showValueOfRange.call(this, 'graph_H_sc', 1, "physics_springConstant");
  27002. showValueOfRange.call(this, 'graph_H_sl', 1, "physics_springLength");
  27003. showValueOfRange.call(this, 'graph_H_damp', 1, "physics_damping");
  27004. }
  27005. else {
  27006. this.repositionNodes();
  27007. }
  27008. this.moving = true;
  27009. this.start();
  27010. }
  27011. /**
  27012. * this is used to generate an options file from the playing with physics system.
  27013. */
  27014. function graphGenerateOptions () {
  27015. var options = "No options are required, default values used.";
  27016. var optionsSpecific = [];
  27017. var radioButton1 = document.getElementById("graph_physicsMethod1");
  27018. var radioButton2 = document.getElementById("graph_physicsMethod2");
  27019. if (radioButton1.checked == true) {
  27020. if (this.constants.physics.barnesHut.gravitationalConstant != this.backupConstants.physics.barnesHut.gravitationalConstant) {optionsSpecific.push("gravitationalConstant: " + this.constants.physics.barnesHut.gravitationalConstant);}
  27021. if (this.constants.physics.centralGravity != this.backupConstants.physics.barnesHut.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  27022. if (this.constants.physics.springLength != this.backupConstants.physics.barnesHut.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  27023. if (this.constants.physics.springConstant != this.backupConstants.physics.barnesHut.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  27024. if (this.constants.physics.damping != this.backupConstants.physics.barnesHut.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  27025. if (optionsSpecific.length != 0) {
  27026. options = "var options = {";
  27027. options += "physics: {barnesHut: {";
  27028. for (var i = 0; i < optionsSpecific.length; i++) {
  27029. options += optionsSpecific[i];
  27030. if (i < optionsSpecific.length - 1) {
  27031. options += ", "
  27032. }
  27033. }
  27034. options += '}}'
  27035. }
  27036. if (this.constants.smoothCurves.enabled != this.backupConstants.smoothCurves.enabled) {
  27037. if (optionsSpecific.length == 0) {options = "var options = {";}
  27038. else {options += ", "}
  27039. options += "smoothCurves: " + this.constants.smoothCurves.enabled;
  27040. }
  27041. if (options != "No options are required, default values used.") {
  27042. options += '};'
  27043. }
  27044. }
  27045. else if (radioButton2.checked == true) {
  27046. options = "var options = {";
  27047. options += "physics: {barnesHut: {enabled: false}";
  27048. if (this.constants.physics.repulsion.nodeDistance != this.backupConstants.physics.repulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.repulsion.nodeDistance);}
  27049. if (this.constants.physics.centralGravity != this.backupConstants.physics.repulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  27050. if (this.constants.physics.springLength != this.backupConstants.physics.repulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  27051. if (this.constants.physics.springConstant != this.backupConstants.physics.repulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  27052. if (this.constants.physics.damping != this.backupConstants.physics.repulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  27053. if (optionsSpecific.length != 0) {
  27054. options += ", repulsion: {";
  27055. for (var i = 0; i < optionsSpecific.length; i++) {
  27056. options += optionsSpecific[i];
  27057. if (i < optionsSpecific.length - 1) {
  27058. options += ", "
  27059. }
  27060. }
  27061. options += '}}'
  27062. }
  27063. if (optionsSpecific.length == 0) {options += "}"}
  27064. if (this.constants.smoothCurves != this.backupConstants.smoothCurves) {
  27065. options += ", smoothCurves: " + this.constants.smoothCurves;
  27066. }
  27067. options += '};'
  27068. }
  27069. else {
  27070. options = "var options = {";
  27071. if (this.constants.physics.hierarchicalRepulsion.nodeDistance != this.backupConstants.physics.hierarchicalRepulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.hierarchicalRepulsion.nodeDistance);}
  27072. if (this.constants.physics.centralGravity != this.backupConstants.physics.hierarchicalRepulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  27073. if (this.constants.physics.springLength != this.backupConstants.physics.hierarchicalRepulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  27074. if (this.constants.physics.springConstant != this.backupConstants.physics.hierarchicalRepulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  27075. if (this.constants.physics.damping != this.backupConstants.physics.hierarchicalRepulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  27076. if (optionsSpecific.length != 0) {
  27077. options += "physics: {hierarchicalRepulsion: {";
  27078. for (var i = 0; i < optionsSpecific.length; i++) {
  27079. options += optionsSpecific[i];
  27080. if (i < optionsSpecific.length - 1) {
  27081. options += ", ";
  27082. }
  27083. }
  27084. options += '}},';
  27085. }
  27086. options += 'hierarchicalLayout: {';
  27087. optionsSpecific = [];
  27088. if (this.constants.hierarchicalLayout.direction != this.backupConstants.hierarchicalLayout.direction) {optionsSpecific.push("direction: " + this.constants.hierarchicalLayout.direction);}
  27089. if (Math.abs(this.constants.hierarchicalLayout.levelSeparation) != this.backupConstants.hierarchicalLayout.levelSeparation) {optionsSpecific.push("levelSeparation: " + this.constants.hierarchicalLayout.levelSeparation);}
  27090. if (this.constants.hierarchicalLayout.nodeSpacing != this.backupConstants.hierarchicalLayout.nodeSpacing) {optionsSpecific.push("nodeSpacing: " + this.constants.hierarchicalLayout.nodeSpacing);}
  27091. if (optionsSpecific.length != 0) {
  27092. for (var i = 0; i < optionsSpecific.length; i++) {
  27093. options += optionsSpecific[i];
  27094. if (i < optionsSpecific.length - 1) {
  27095. options += ", "
  27096. }
  27097. }
  27098. options += '}'
  27099. }
  27100. else {
  27101. options += "enabled:true}";
  27102. }
  27103. options += '};'
  27104. }
  27105. this.optionsDiv.innerHTML = options;
  27106. }
  27107. /**
  27108. * this is used to switch between barnesHut, repulsion and hierarchical.
  27109. *
  27110. */
  27111. function switchConfigurations () {
  27112. var ids = ["graph_BH_table", "graph_R_table", "graph_H_table"];
  27113. var radioButton = document.querySelector('input[name="graph_physicsMethod"]:checked').value;
  27114. var tableId = "graph_" + radioButton + "_table";
  27115. var table = document.getElementById(tableId);
  27116. table.style.display = "block";
  27117. for (var i = 0; i < ids.length; i++) {
  27118. if (ids[i] != tableId) {
  27119. table = document.getElementById(ids[i]);
  27120. table.style.display = "none";
  27121. }
  27122. }
  27123. this._restoreNodes();
  27124. if (radioButton == "R") {
  27125. this.constants.hierarchicalLayout.enabled = false;
  27126. this.constants.physics.hierarchicalRepulsion.enabled = false;
  27127. this.constants.physics.barnesHut.enabled = false;
  27128. }
  27129. else if (radioButton == "H") {
  27130. if (this.constants.hierarchicalLayout.enabled == false) {
  27131. this.constants.hierarchicalLayout.enabled = true;
  27132. this.constants.physics.hierarchicalRepulsion.enabled = true;
  27133. this.constants.physics.barnesHut.enabled = false;
  27134. this.constants.smoothCurves.enabled = false;
  27135. this._setupHierarchicalLayout();
  27136. }
  27137. }
  27138. else {
  27139. this.constants.hierarchicalLayout.enabled = false;
  27140. this.constants.physics.hierarchicalRepulsion.enabled = false;
  27141. this.constants.physics.barnesHut.enabled = true;
  27142. }
  27143. this._loadSelectedForceSolver();
  27144. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  27145. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  27146. else {graph_toggleSmooth.style.background = "#FF8532";}
  27147. this.moving = true;
  27148. this.start();
  27149. }
  27150. /**
  27151. * this generates the ranges depending on the iniital values.
  27152. *
  27153. * @param id
  27154. * @param map
  27155. * @param constantsVariableName
  27156. */
  27157. function showValueOfRange (id,map,constantsVariableName) {
  27158. var valueId = id + "_value";
  27159. var rangeValue = document.getElementById(id).value;
  27160. if (map instanceof Array) {
  27161. document.getElementById(valueId).value = map[parseInt(rangeValue)];
  27162. this._overWriteGraphConstants(constantsVariableName,map[parseInt(rangeValue)]);
  27163. }
  27164. else {
  27165. document.getElementById(valueId).value = parseInt(map) * parseFloat(rangeValue);
  27166. this._overWriteGraphConstants(constantsVariableName, parseInt(map) * parseFloat(rangeValue));
  27167. }
  27168. if (constantsVariableName == "hierarchicalLayout_direction" ||
  27169. constantsVariableName == "hierarchicalLayout_levelSeparation" ||
  27170. constantsVariableName == "hierarchicalLayout_nodeSpacing") {
  27171. this._setupHierarchicalLayout();
  27172. }
  27173. this.moving = true;
  27174. this.start();
  27175. }
  27176. /***/ },
  27177. /* 60 */
  27178. /***/ function(module, exports, __webpack_require__) {
  27179. function webpackContext(req) {
  27180. throw new Error("Cannot find module '" + req + "'.");
  27181. }
  27182. webpackContext.resolve = webpackContext;
  27183. webpackContext.keys = function() { return []; };
  27184. module.exports = webpackContext;
  27185. /***/ },
  27186. /* 61 */
  27187. /***/ function(module, exports, __webpack_require__) {
  27188. /**
  27189. * Calculate the forces the nodes apply on each other based on a repulsion field.
  27190. * This field is linearly approximated.
  27191. *
  27192. * @private
  27193. */
  27194. exports._calculateNodeForces = function () {
  27195. var dx, dy, angle, distance, fx, fy, combinedClusterSize,
  27196. repulsingForce, node1, node2, i, j;
  27197. var nodes = this.calculationNodes;
  27198. var nodeIndices = this.calculationNodeIndices;
  27199. // approximation constants
  27200. var a_base = -2 / 3;
  27201. var b = 4 / 3;
  27202. // repulsing forces between nodes
  27203. var nodeDistance = this.constants.physics.repulsion.nodeDistance;
  27204. var minimumDistance = nodeDistance;
  27205. // we loop from i over all but the last entree in the array
  27206. // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  27207. for (i = 0; i < nodeIndices.length - 1; i++) {
  27208. node1 = nodes[nodeIndices[i]];
  27209. for (j = i + 1; j < nodeIndices.length; j++) {
  27210. node2 = nodes[nodeIndices[j]];
  27211. combinedClusterSize = node1.clusterSize + node2.clusterSize - 2;
  27212. dx = node2.x - node1.x;
  27213. dy = node2.y - node1.y;
  27214. distance = Math.sqrt(dx * dx + dy * dy);
  27215. minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification));
  27216. var a = a_base / minimumDistance;
  27217. if (distance < 2 * minimumDistance) {
  27218. if (distance < 0.5 * minimumDistance) {
  27219. repulsingForce = 1.0;
  27220. }
  27221. else {
  27222. repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness))
  27223. }
  27224. // amplify the repulsion for clusters.
  27225. repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification;
  27226. repulsingForce = repulsingForce / distance;
  27227. fx = dx * repulsingForce;
  27228. fy = dy * repulsingForce;
  27229. node1.fx -= fx;
  27230. node1.fy -= fy;
  27231. node2.fx += fx;
  27232. node2.fy += fy;
  27233. }
  27234. }
  27235. }
  27236. };
  27237. /***/ },
  27238. /* 62 */
  27239. /***/ function(module, exports, __webpack_require__) {
  27240. /**
  27241. * Calculate the forces the nodes apply on eachother based on a repulsion field.
  27242. * This field is linearly approximated.
  27243. *
  27244. * @private
  27245. */
  27246. exports._calculateNodeForces = function () {
  27247. var dx, dy, distance, fx, fy,
  27248. repulsingForce, node1, node2, i, j;
  27249. var nodes = this.calculationNodes;
  27250. var nodeIndices = this.calculationNodeIndices;
  27251. // repulsing forces between nodes
  27252. var nodeDistance = this.constants.physics.hierarchicalRepulsion.nodeDistance;
  27253. // we loop from i over all but the last entree in the array
  27254. // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  27255. for (i = 0; i < nodeIndices.length - 1; i++) {
  27256. node1 = nodes[nodeIndices[i]];
  27257. for (j = i + 1; j < nodeIndices.length; j++) {
  27258. node2 = nodes[nodeIndices[j]];
  27259. // nodes only affect nodes on their level
  27260. if (node1.level == node2.level) {
  27261. dx = node2.x - node1.x;
  27262. dy = node2.y - node1.y;
  27263. distance = Math.sqrt(dx * dx + dy * dy);
  27264. var steepness = 0.05;
  27265. if (distance < nodeDistance) {
  27266. repulsingForce = -Math.pow(steepness*distance,2) + Math.pow(steepness*nodeDistance,2);
  27267. }
  27268. else {
  27269. repulsingForce = 0;
  27270. }
  27271. // normalize force with
  27272. if (distance == 0) {
  27273. distance = 0.01;
  27274. }
  27275. else {
  27276. repulsingForce = repulsingForce / distance;
  27277. }
  27278. fx = dx * repulsingForce;
  27279. fy = dy * repulsingForce;
  27280. node1.fx -= fx;
  27281. node1.fy -= fy;
  27282. node2.fx += fx;
  27283. node2.fy += fy;
  27284. }
  27285. }
  27286. }
  27287. };
  27288. /**
  27289. * this function calculates the effects of the springs in the case of unsmooth curves.
  27290. *
  27291. * @private
  27292. */
  27293. exports._calculateHierarchicalSpringForces = function () {
  27294. var edgeLength, edge, edgeId;
  27295. var dx, dy, fx, fy, springForce, distance;
  27296. var edges = this.edges;
  27297. var nodes = this.calculationNodes;
  27298. var nodeIndices = this.calculationNodeIndices;
  27299. for (var i = 0; i < nodeIndices.length; i++) {
  27300. var node1 = nodes[nodeIndices[i]];
  27301. node1.springFx = 0;
  27302. node1.springFy = 0;
  27303. }
  27304. // forces caused by the edges, modelled as springs
  27305. for (edgeId in edges) {
  27306. if (edges.hasOwnProperty(edgeId)) {
  27307. edge = edges[edgeId];
  27308. if (edge.connected) {
  27309. // only calculate forces if nodes are in the same sector
  27310. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  27311. edgeLength = edge.physics.springLength;
  27312. // this implies that the edges between big clusters are longer
  27313. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  27314. dx = (edge.from.x - edge.to.x);
  27315. dy = (edge.from.y - edge.to.y);
  27316. distance = Math.sqrt(dx * dx + dy * dy);
  27317. if (distance == 0) {
  27318. distance = 0.01;
  27319. }
  27320. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  27321. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  27322. fx = dx * springForce;
  27323. fy = dy * springForce;
  27324. if (edge.to.level != edge.from.level) {
  27325. edge.to.springFx -= fx;
  27326. edge.to.springFy -= fy;
  27327. edge.from.springFx += fx;
  27328. edge.from.springFy += fy;
  27329. }
  27330. else {
  27331. var factor = 0.5;
  27332. edge.to.fx -= factor*fx;
  27333. edge.to.fy -= factor*fy;
  27334. edge.from.fx += factor*fx;
  27335. edge.from.fy += factor*fy;
  27336. }
  27337. }
  27338. }
  27339. }
  27340. }
  27341. // normalize spring forces
  27342. var springForce = 1;
  27343. var springFx, springFy;
  27344. for (i = 0; i < nodeIndices.length; i++) {
  27345. var node = nodes[nodeIndices[i]];
  27346. springFx = Math.min(springForce,Math.max(-springForce,node.springFx));
  27347. springFy = Math.min(springForce,Math.max(-springForce,node.springFy));
  27348. node.fx += springFx;
  27349. node.fy += springFy;
  27350. }
  27351. // retain energy balance
  27352. var totalFx = 0;
  27353. var totalFy = 0;
  27354. for (i = 0; i < nodeIndices.length; i++) {
  27355. var node = nodes[nodeIndices[i]];
  27356. totalFx += node.fx;
  27357. totalFy += node.fy;
  27358. }
  27359. var correctionFx = totalFx / nodeIndices.length;
  27360. var correctionFy = totalFy / nodeIndices.length;
  27361. for (i = 0; i < nodeIndices.length; i++) {
  27362. var node = nodes[nodeIndices[i]];
  27363. node.fx -= correctionFx;
  27364. node.fy -= correctionFy;
  27365. }
  27366. };
  27367. /***/ },
  27368. /* 63 */
  27369. /***/ function(module, exports, __webpack_require__) {
  27370. /**
  27371. * This function calculates the forces the nodes apply on eachother based on a gravitational model.
  27372. * The Barnes Hut method is used to speed up this N-body simulation.
  27373. *
  27374. * @private
  27375. */
  27376. exports._calculateNodeForces = function() {
  27377. if (this.constants.physics.barnesHut.gravitationalConstant != 0) {
  27378. var node;
  27379. var nodes = this.calculationNodes;
  27380. var nodeIndices = this.calculationNodeIndices;
  27381. var nodeCount = nodeIndices.length;
  27382. this._formBarnesHutTree(nodes,nodeIndices);
  27383. var barnesHutTree = this.barnesHutTree;
  27384. // place the nodes one by one recursively
  27385. for (var i = 0; i < nodeCount; i++) {
  27386. node = nodes[nodeIndices[i]];
  27387. if (node.options.mass > 0) {
  27388. // starting with root is irrelevant, it never passes the BarnesHut condition
  27389. this._getForceContribution(barnesHutTree.root.children.NW,node);
  27390. this._getForceContribution(barnesHutTree.root.children.NE,node);
  27391. this._getForceContribution(barnesHutTree.root.children.SW,node);
  27392. this._getForceContribution(barnesHutTree.root.children.SE,node);
  27393. }
  27394. }
  27395. }
  27396. };
  27397. /**
  27398. * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass.
  27399. * If a region contains a single node, we check if it is not itself, then we apply the force.
  27400. *
  27401. * @param parentBranch
  27402. * @param node
  27403. * @private
  27404. */
  27405. exports._getForceContribution = function(parentBranch,node) {
  27406. // we get no force contribution from an empty region
  27407. if (parentBranch.childrenCount > 0) {
  27408. var dx,dy,distance;
  27409. // get the distance from the center of mass to the node.
  27410. dx = parentBranch.centerOfMass.x - node.x;
  27411. dy = parentBranch.centerOfMass.y - node.y;
  27412. distance = Math.sqrt(dx * dx + dy * dy);
  27413. // BarnesHut condition
  27414. // original condition : s/d < theta = passed === d/s > 1/theta = passed
  27415. // calcSize = 1/s --> d * 1/s > 1/theta = passed
  27416. if (distance * parentBranch.calcSize > this.constants.physics.barnesHut.theta) {
  27417. // duplicate code to reduce function calls to speed up program
  27418. if (distance == 0) {
  27419. distance = 0.1*Math.random();
  27420. dx = distance;
  27421. }
  27422. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
  27423. var fx = dx * gravityForce;
  27424. var fy = dy * gravityForce;
  27425. node.fx += fx;
  27426. node.fy += fy;
  27427. }
  27428. else {
  27429. // Did not pass the condition, go into children if available
  27430. if (parentBranch.childrenCount == 4) {
  27431. this._getForceContribution(parentBranch.children.NW,node);
  27432. this._getForceContribution(parentBranch.children.NE,node);
  27433. this._getForceContribution(parentBranch.children.SW,node);
  27434. this._getForceContribution(parentBranch.children.SE,node);
  27435. }
  27436. else { // parentBranch must have only one node, if it was empty we wouldnt be here
  27437. if (parentBranch.children.data.id != node.id) { // if it is not self
  27438. // duplicate code to reduce function calls to speed up program
  27439. if (distance == 0) {
  27440. distance = 0.5*Math.random();
  27441. dx = distance;
  27442. }
  27443. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
  27444. var fx = dx * gravityForce;
  27445. var fy = dy * gravityForce;
  27446. node.fx += fx;
  27447. node.fy += fy;
  27448. }
  27449. }
  27450. }
  27451. }
  27452. };
  27453. /**
  27454. * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes.
  27455. *
  27456. * @param nodes
  27457. * @param nodeIndices
  27458. * @private
  27459. */
  27460. exports._formBarnesHutTree = function(nodes,nodeIndices) {
  27461. var node;
  27462. var nodeCount = nodeIndices.length;
  27463. var minX = Number.MAX_VALUE,
  27464. minY = Number.MAX_VALUE,
  27465. maxX =-Number.MAX_VALUE,
  27466. maxY =-Number.MAX_VALUE;
  27467. // get the range of the nodes
  27468. for (var i = 0; i < nodeCount; i++) {
  27469. var x = nodes[nodeIndices[i]].x;
  27470. var y = nodes[nodeIndices[i]].y;
  27471. if (nodes[nodeIndices[i]].options.mass > 0) {
  27472. if (x < minX) { minX = x; }
  27473. if (x > maxX) { maxX = x; }
  27474. if (y < minY) { minY = y; }
  27475. if (y > maxY) { maxY = y; }
  27476. }
  27477. }
  27478. // make the range a square
  27479. var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
  27480. if (sizeDiff > 0) {minY -= 0.5 * sizeDiff; maxY += 0.5 * sizeDiff;} // xSize > ySize
  27481. else {minX += 0.5 * sizeDiff; maxX -= 0.5 * sizeDiff;} // xSize < ySize
  27482. var minimumTreeSize = 1e-5;
  27483. var rootSize = Math.max(minimumTreeSize,Math.abs(maxX - minX));
  27484. var halfRootSize = 0.5 * rootSize;
  27485. var centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY);
  27486. // construct the barnesHutTree
  27487. var barnesHutTree = {
  27488. root:{
  27489. centerOfMass: {x:0, y:0},
  27490. mass:0,
  27491. range: {
  27492. minX: centerX-halfRootSize,maxX:centerX+halfRootSize,
  27493. minY: centerY-halfRootSize,maxY:centerY+halfRootSize
  27494. },
  27495. size: rootSize,
  27496. calcSize: 1 / rootSize,
  27497. children: { data:null},
  27498. maxWidth: 0,
  27499. level: 0,
  27500. childrenCount: 4
  27501. }
  27502. };
  27503. this._splitBranch(barnesHutTree.root);
  27504. // place the nodes one by one recursively
  27505. for (i = 0; i < nodeCount; i++) {
  27506. node = nodes[nodeIndices[i]];
  27507. if (node.options.mass > 0) {
  27508. this._placeInTree(barnesHutTree.root,node);
  27509. }
  27510. }
  27511. // make global
  27512. this.barnesHutTree = barnesHutTree
  27513. };
  27514. /**
  27515. * this updates the mass of a branch. this is increased by adding a node.
  27516. *
  27517. * @param parentBranch
  27518. * @param node
  27519. * @private
  27520. */
  27521. exports._updateBranchMass = function(parentBranch, node) {
  27522. var totalMass = parentBranch.mass + node.options.mass;
  27523. var totalMassInv = 1/totalMass;
  27524. parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass;
  27525. parentBranch.centerOfMass.x *= totalMassInv;
  27526. parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass;
  27527. parentBranch.centerOfMass.y *= totalMassInv;
  27528. parentBranch.mass = totalMass;
  27529. var biggestSize = Math.max(Math.max(node.height,node.radius),node.width);
  27530. parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth;
  27531. };
  27532. /**
  27533. * determine in which branch the node will be placed.
  27534. *
  27535. * @param parentBranch
  27536. * @param node
  27537. * @param skipMassUpdate
  27538. * @private
  27539. */
  27540. exports._placeInTree = function(parentBranch,node,skipMassUpdate) {
  27541. if (skipMassUpdate != true || skipMassUpdate === undefined) {
  27542. // update the mass of the branch.
  27543. this._updateBranchMass(parentBranch,node);
  27544. }
  27545. if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW
  27546. if (parentBranch.children.NW.range.maxY > node.y) { // in NW
  27547. this._placeInRegion(parentBranch,node,"NW");
  27548. }
  27549. else { // in SW
  27550. this._placeInRegion(parentBranch,node,"SW");
  27551. }
  27552. }
  27553. else { // in NE or SE
  27554. if (parentBranch.children.NW.range.maxY > node.y) { // in NE
  27555. this._placeInRegion(parentBranch,node,"NE");
  27556. }
  27557. else { // in SE
  27558. this._placeInRegion(parentBranch,node,"SE");
  27559. }
  27560. }
  27561. };
  27562. /**
  27563. * actually place the node in a region (or branch)
  27564. *
  27565. * @param parentBranch
  27566. * @param node
  27567. * @param region
  27568. * @private
  27569. */
  27570. exports._placeInRegion = function(parentBranch,node,region) {
  27571. switch (parentBranch.children[region].childrenCount) {
  27572. case 0: // place node here
  27573. parentBranch.children[region].children.data = node;
  27574. parentBranch.children[region].childrenCount = 1;
  27575. this._updateBranchMass(parentBranch.children[region],node);
  27576. break;
  27577. case 1: // convert into children
  27578. // if there are two nodes exactly overlapping (on init, on opening of cluster etc.)
  27579. // we move one node a pixel and we do not put it in the tree.
  27580. if (parentBranch.children[region].children.data.x == node.x &&
  27581. parentBranch.children[region].children.data.y == node.y) {
  27582. node.x += Math.random();
  27583. node.y += Math.random();
  27584. }
  27585. else {
  27586. this._splitBranch(parentBranch.children[region]);
  27587. this._placeInTree(parentBranch.children[region],node);
  27588. }
  27589. break;
  27590. case 4: // place in branch
  27591. this._placeInTree(parentBranch.children[region],node);
  27592. break;
  27593. }
  27594. };
  27595. /**
  27596. * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch
  27597. * after the split is complete.
  27598. *
  27599. * @param parentBranch
  27600. * @private
  27601. */
  27602. exports._splitBranch = function(parentBranch) {
  27603. // if the branch is shaded with a node, replace the node in the new subset.
  27604. var containedNode = null;
  27605. if (parentBranch.childrenCount == 1) {
  27606. containedNode = parentBranch.children.data;
  27607. parentBranch.mass = 0; parentBranch.centerOfMass.x = 0; parentBranch.centerOfMass.y = 0;
  27608. }
  27609. parentBranch.childrenCount = 4;
  27610. parentBranch.children.data = null;
  27611. this._insertRegion(parentBranch,"NW");
  27612. this._insertRegion(parentBranch,"NE");
  27613. this._insertRegion(parentBranch,"SW");
  27614. this._insertRegion(parentBranch,"SE");
  27615. if (containedNode != null) {
  27616. this._placeInTree(parentBranch,containedNode);
  27617. }
  27618. };
  27619. /**
  27620. * This function subdivides the region into four new segments.
  27621. * Specifically, this inserts a single new segment.
  27622. * It fills the children section of the parentBranch
  27623. *
  27624. * @param parentBranch
  27625. * @param region
  27626. * @param parentRange
  27627. * @private
  27628. */
  27629. exports._insertRegion = function(parentBranch, region) {
  27630. var minX,maxX,minY,maxY;
  27631. var childSize = 0.5 * parentBranch.size;
  27632. switch (region) {
  27633. case "NW":
  27634. minX = parentBranch.range.minX;
  27635. maxX = parentBranch.range.minX + childSize;
  27636. minY = parentBranch.range.minY;
  27637. maxY = parentBranch.range.minY + childSize;
  27638. break;
  27639. case "NE":
  27640. minX = parentBranch.range.minX + childSize;
  27641. maxX = parentBranch.range.maxX;
  27642. minY = parentBranch.range.minY;
  27643. maxY = parentBranch.range.minY + childSize;
  27644. break;
  27645. case "SW":
  27646. minX = parentBranch.range.minX;
  27647. maxX = parentBranch.range.minX + childSize;
  27648. minY = parentBranch.range.minY + childSize;
  27649. maxY = parentBranch.range.maxY;
  27650. break;
  27651. case "SE":
  27652. minX = parentBranch.range.minX + childSize;
  27653. maxX = parentBranch.range.maxX;
  27654. minY = parentBranch.range.minY + childSize;
  27655. maxY = parentBranch.range.maxY;
  27656. break;
  27657. }
  27658. parentBranch.children[region] = {
  27659. centerOfMass:{x:0,y:0},
  27660. mass:0,
  27661. range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY},
  27662. size: 0.5 * parentBranch.size,
  27663. calcSize: 2 * parentBranch.calcSize,
  27664. children: {data:null},
  27665. maxWidth: 0,
  27666. level: parentBranch.level+1,
  27667. childrenCount: 0
  27668. };
  27669. };
  27670. /**
  27671. * This function is for debugging purposed, it draws the tree.
  27672. *
  27673. * @param ctx
  27674. * @param color
  27675. * @private
  27676. */
  27677. exports._drawTree = function(ctx,color) {
  27678. if (this.barnesHutTree !== undefined) {
  27679. ctx.lineWidth = 1;
  27680. this._drawBranch(this.barnesHutTree.root,ctx,color);
  27681. }
  27682. };
  27683. /**
  27684. * This function is for debugging purposes. It draws the branches recursively.
  27685. *
  27686. * @param branch
  27687. * @param ctx
  27688. * @param color
  27689. * @private
  27690. */
  27691. exports._drawBranch = function(branch,ctx,color) {
  27692. if (color === undefined) {
  27693. color = "#FF0000";
  27694. }
  27695. if (branch.childrenCount == 4) {
  27696. this._drawBranch(branch.children.NW,ctx);
  27697. this._drawBranch(branch.children.NE,ctx);
  27698. this._drawBranch(branch.children.SE,ctx);
  27699. this._drawBranch(branch.children.SW,ctx);
  27700. }
  27701. ctx.strokeStyle = color;
  27702. ctx.beginPath();
  27703. ctx.moveTo(branch.range.minX,branch.range.minY);
  27704. ctx.lineTo(branch.range.maxX,branch.range.minY);
  27705. ctx.stroke();
  27706. ctx.beginPath();
  27707. ctx.moveTo(branch.range.maxX,branch.range.minY);
  27708. ctx.lineTo(branch.range.maxX,branch.range.maxY);
  27709. ctx.stroke();
  27710. ctx.beginPath();
  27711. ctx.moveTo(branch.range.maxX,branch.range.maxY);
  27712. ctx.lineTo(branch.range.minX,branch.range.maxY);
  27713. ctx.stroke();
  27714. ctx.beginPath();
  27715. ctx.moveTo(branch.range.minX,branch.range.maxY);
  27716. ctx.lineTo(branch.range.minX,branch.range.minY);
  27717. ctx.stroke();
  27718. /*
  27719. if (branch.mass > 0) {
  27720. ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass);
  27721. ctx.stroke();
  27722. }
  27723. */
  27724. };
  27725. /***/ },
  27726. /* 64 */
  27727. /***/ function(module, exports, __webpack_require__) {
  27728. module.exports = function(module) {
  27729. if(!module.webpackPolyfill) {
  27730. module.deprecate = function() {};
  27731. module.paths = [];
  27732. // module.parent = undefined by default
  27733. module.children = [];
  27734. module.webpackPolyfill = 1;
  27735. }
  27736. return module;
  27737. }
  27738. /***/ }
  27739. /******/ ])
  27740. });