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.

31469 lines
982 KiB

  1. /**
  2. * vis.js
  3. * https://github.com/almende/vis
  4. *
  5. * A dynamic, browser-based visualization library.
  6. *
  7. * @version 3.1.0
  8. * @date 2014-07-22
  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. * Deep extend an object a with the properties of object b
  296. * @param {Object} a
  297. * @param {Object} b
  298. * @returns {Object}
  299. */
  300. exports.deepExtend = function(a, b) {
  301. // TODO: add support for Arrays to deepExtend
  302. if (Array.isArray(b)) {
  303. throw new TypeError('Arrays are not supported by deepExtend');
  304. }
  305. for (var prop in b) {
  306. if (b.hasOwnProperty(prop)) {
  307. if (b[prop] && b[prop].constructor === Object) {
  308. if (a[prop] === undefined) {
  309. a[prop] = {};
  310. }
  311. if (a[prop].constructor === Object) {
  312. exports.deepExtend(a[prop], b[prop]);
  313. }
  314. else {
  315. a[prop] = b[prop];
  316. }
  317. } else if (Array.isArray(b[prop])) {
  318. throw new TypeError('Arrays are not supported by deepExtend');
  319. } else {
  320. a[prop] = b[prop];
  321. }
  322. }
  323. }
  324. return a;
  325. };
  326. /**
  327. * Test whether all elements in two arrays are equal.
  328. * @param {Array} a
  329. * @param {Array} b
  330. * @return {boolean} Returns true if both arrays have the same length and same
  331. * elements.
  332. */
  333. exports.equalArray = function (a, b) {
  334. if (a.length != b.length) return false;
  335. for (var i = 0, len = a.length; i < len; i++) {
  336. if (a[i] != b[i]) return false;
  337. }
  338. return true;
  339. };
  340. /**
  341. * Convert an object to another type
  342. * @param {Boolean | Number | String | Date | Moment | Null | undefined} object
  343. * @param {String | undefined} type Name of the type. Available types:
  344. * 'Boolean', 'Number', 'String',
  345. * 'Date', 'Moment', ISODate', 'ASPDate'.
  346. * @return {*} object
  347. * @throws Error
  348. */
  349. exports.convert = function(object, type) {
  350. var match;
  351. if (object === undefined) {
  352. return undefined;
  353. }
  354. if (object === null) {
  355. return null;
  356. }
  357. if (!type) {
  358. return object;
  359. }
  360. if (!(typeof type === 'string') && !(type instanceof String)) {
  361. throw new Error('Type must be a string');
  362. }
  363. //noinspection FallthroughInSwitchStatementJS
  364. switch (type) {
  365. case 'boolean':
  366. case 'Boolean':
  367. return Boolean(object);
  368. case 'number':
  369. case 'Number':
  370. return Number(object.valueOf());
  371. case 'string':
  372. case 'String':
  373. return String(object);
  374. case 'Date':
  375. if (exports.isNumber(object)) {
  376. return new Date(object);
  377. }
  378. if (object instanceof Date) {
  379. return new Date(object.valueOf());
  380. }
  381. else if (moment.isMoment(object)) {
  382. return new Date(object.valueOf());
  383. }
  384. if (exports.isString(object)) {
  385. match = ASPDateRegex.exec(object);
  386. if (match) {
  387. // object is an ASP date
  388. return new Date(Number(match[1])); // parse number
  389. }
  390. else {
  391. return moment(object).toDate(); // parse string
  392. }
  393. }
  394. else {
  395. throw new Error(
  396. 'Cannot convert object of type ' + exports.getType(object) +
  397. ' to type Date');
  398. }
  399. case 'Moment':
  400. if (exports.isNumber(object)) {
  401. return moment(object);
  402. }
  403. if (object instanceof Date) {
  404. return moment(object.valueOf());
  405. }
  406. else if (moment.isMoment(object)) {
  407. return moment(object);
  408. }
  409. if (exports.isString(object)) {
  410. match = ASPDateRegex.exec(object);
  411. if (match) {
  412. // object is an ASP date
  413. return moment(Number(match[1])); // parse number
  414. }
  415. else {
  416. return moment(object); // parse string
  417. }
  418. }
  419. else {
  420. throw new Error(
  421. 'Cannot convert object of type ' + exports.getType(object) +
  422. ' to type Date');
  423. }
  424. case 'ISODate':
  425. if (exports.isNumber(object)) {
  426. return new Date(object);
  427. }
  428. else if (object instanceof Date) {
  429. return object.toISOString();
  430. }
  431. else if (moment.isMoment(object)) {
  432. return object.toDate().toISOString();
  433. }
  434. else if (exports.isString(object)) {
  435. match = ASPDateRegex.exec(object);
  436. if (match) {
  437. // object is an ASP date
  438. return new Date(Number(match[1])).toISOString(); // parse number
  439. }
  440. else {
  441. return new Date(object).toISOString(); // parse string
  442. }
  443. }
  444. else {
  445. throw new Error(
  446. 'Cannot convert object of type ' + exports.getType(object) +
  447. ' to type ISODate');
  448. }
  449. case 'ASPDate':
  450. if (exports.isNumber(object)) {
  451. return '/Date(' + object + ')/';
  452. }
  453. else if (object instanceof Date) {
  454. return '/Date(' + object.valueOf() + ')/';
  455. }
  456. else if (exports.isString(object)) {
  457. match = ASPDateRegex.exec(object);
  458. var value;
  459. if (match) {
  460. // object is an ASP date
  461. value = new Date(Number(match[1])).valueOf(); // parse number
  462. }
  463. else {
  464. value = new Date(object).valueOf(); // parse string
  465. }
  466. return '/Date(' + value + ')/';
  467. }
  468. else {
  469. throw new Error(
  470. 'Cannot convert object of type ' + exports.getType(object) +
  471. ' to type ASPDate');
  472. }
  473. default:
  474. throw new Error('Unknown type "' + type + '"');
  475. }
  476. };
  477. // parse ASP.Net Date pattern,
  478. // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
  479. // code from http://momentjs.com/
  480. var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
  481. /**
  482. * Get the type of an object, for example exports.getType([]) returns 'Array'
  483. * @param {*} object
  484. * @return {String} type
  485. */
  486. exports.getType = function(object) {
  487. var type = typeof object;
  488. if (type == 'object') {
  489. if (object == null) {
  490. return 'null';
  491. }
  492. if (object instanceof Boolean) {
  493. return 'Boolean';
  494. }
  495. if (object instanceof Number) {
  496. return 'Number';
  497. }
  498. if (object instanceof String) {
  499. return 'String';
  500. }
  501. if (object instanceof Array) {
  502. return 'Array';
  503. }
  504. if (object instanceof Date) {
  505. return 'Date';
  506. }
  507. return 'Object';
  508. }
  509. else if (type == 'number') {
  510. return 'Number';
  511. }
  512. else if (type == 'boolean') {
  513. return 'Boolean';
  514. }
  515. else if (type == 'string') {
  516. return 'String';
  517. }
  518. return type;
  519. };
  520. /**
  521. * Retrieve the absolute left value of a DOM element
  522. * @param {Element} elem A dom element, for example a div
  523. * @return {number} left The absolute left position of this element
  524. * in the browser page.
  525. */
  526. exports.getAbsoluteLeft = function(elem) {
  527. return elem.getBoundingClientRect().left + window.pageXOffset;
  528. };
  529. /**
  530. * Retrieve the absolute top value of a DOM element
  531. * @param {Element} elem A dom element, for example a div
  532. * @return {number} top The absolute top position of this element
  533. * in the browser page.
  534. */
  535. exports.getAbsoluteTop = function(elem) {
  536. return elem.getBoundingClientRect().top + window.pageYOffset;
  537. };
  538. /**
  539. * add a className to the given elements style
  540. * @param {Element} elem
  541. * @param {String} className
  542. */
  543. exports.addClassName = function(elem, className) {
  544. var classes = elem.className.split(' ');
  545. if (classes.indexOf(className) == -1) {
  546. classes.push(className); // add the class to the array
  547. elem.className = classes.join(' ');
  548. }
  549. };
  550. /**
  551. * add a className to the given elements style
  552. * @param {Element} elem
  553. * @param {String} className
  554. */
  555. exports.removeClassName = function(elem, className) {
  556. var classes = elem.className.split(' ');
  557. var index = classes.indexOf(className);
  558. if (index != -1) {
  559. classes.splice(index, 1); // remove the class from the array
  560. elem.className = classes.join(' ');
  561. }
  562. };
  563. /**
  564. * For each method for both arrays and objects.
  565. * In case of an array, the built-in Array.forEach() is applied.
  566. * In case of an Object, the method loops over all properties of the object.
  567. * @param {Object | Array} object An Object or Array
  568. * @param {function} callback Callback method, called for each item in
  569. * the object or array with three parameters:
  570. * callback(value, index, object)
  571. */
  572. exports.forEach = function(object, callback) {
  573. var i,
  574. len;
  575. if (object instanceof Array) {
  576. // array
  577. for (i = 0, len = object.length; i < len; i++) {
  578. callback(object[i], i, object);
  579. }
  580. }
  581. else {
  582. // object
  583. for (i in object) {
  584. if (object.hasOwnProperty(i)) {
  585. callback(object[i], i, object);
  586. }
  587. }
  588. }
  589. };
  590. /**
  591. * Convert an object into an array: all objects properties are put into the
  592. * array. The resulting array is unordered.
  593. * @param {Object} object
  594. * @param {Array} array
  595. */
  596. exports.toArray = function(object) {
  597. var array = [];
  598. for (var prop in object) {
  599. if (object.hasOwnProperty(prop)) array.push(object[prop]);
  600. }
  601. return array;
  602. }
  603. /**
  604. * Update a property in an object
  605. * @param {Object} object
  606. * @param {String} key
  607. * @param {*} value
  608. * @return {Boolean} changed
  609. */
  610. exports.updateProperty = function(object, key, value) {
  611. if (object[key] !== value) {
  612. object[key] = value;
  613. return true;
  614. }
  615. else {
  616. return false;
  617. }
  618. };
  619. /**
  620. * Add and event listener. Works for all browsers
  621. * @param {Element} element An html element
  622. * @param {string} action The action, for example "click",
  623. * without the prefix "on"
  624. * @param {function} listener The callback function to be executed
  625. * @param {boolean} [useCapture]
  626. */
  627. exports.addEventListener = function(element, action, listener, useCapture) {
  628. if (element.addEventListener) {
  629. if (useCapture === undefined)
  630. useCapture = false;
  631. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  632. action = "DOMMouseScroll"; // For Firefox
  633. }
  634. element.addEventListener(action, listener, useCapture);
  635. } else {
  636. element.attachEvent("on" + action, listener); // IE browsers
  637. }
  638. };
  639. /**
  640. * Remove an event listener from an element
  641. * @param {Element} element An html dom element
  642. * @param {string} action The name of the event, for example "mousedown"
  643. * @param {function} listener The listener function
  644. * @param {boolean} [useCapture]
  645. */
  646. exports.removeEventListener = function(element, action, listener, useCapture) {
  647. if (element.removeEventListener) {
  648. // non-IE browsers
  649. if (useCapture === undefined)
  650. useCapture = false;
  651. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  652. action = "DOMMouseScroll"; // For Firefox
  653. }
  654. element.removeEventListener(action, listener, useCapture);
  655. } else {
  656. // IE browsers
  657. element.detachEvent("on" + action, listener);
  658. }
  659. };
  660. /**
  661. * Cancels the event if it is cancelable, without stopping further propagation of the event.
  662. */
  663. exports.preventDefault = function (event) {
  664. if (!event)
  665. event = window.event;
  666. if (event.preventDefault) {
  667. event.preventDefault(); // non-IE browsers
  668. }
  669. else {
  670. event.returnValue = false; // IE browsers
  671. }
  672. };
  673. /**
  674. * Get HTML element which is the target of the event
  675. * @param {Event} event
  676. * @return {Element} target element
  677. */
  678. exports.getTarget = function(event) {
  679. // code from http://www.quirksmode.org/js/events_properties.html
  680. if (!event) {
  681. event = window.event;
  682. }
  683. var target;
  684. if (event.target) {
  685. target = event.target;
  686. }
  687. else if (event.srcElement) {
  688. target = event.srcElement;
  689. }
  690. if (target.nodeType != undefined && target.nodeType == 3) {
  691. // defeat Safari bug
  692. target = target.parentNode;
  693. }
  694. return target;
  695. };
  696. exports.option = {};
  697. /**
  698. * Convert a value into a boolean
  699. * @param {Boolean | function | undefined} value
  700. * @param {Boolean} [defaultValue]
  701. * @returns {Boolean} bool
  702. */
  703. exports.option.asBoolean = function (value, defaultValue) {
  704. if (typeof value == 'function') {
  705. value = value();
  706. }
  707. if (value != null) {
  708. return (value != false);
  709. }
  710. return defaultValue || null;
  711. };
  712. /**
  713. * Convert a value into a number
  714. * @param {Boolean | function | undefined} value
  715. * @param {Number} [defaultValue]
  716. * @returns {Number} number
  717. */
  718. exports.option.asNumber = function (value, defaultValue) {
  719. if (typeof value == 'function') {
  720. value = value();
  721. }
  722. if (value != null) {
  723. return Number(value) || defaultValue || null;
  724. }
  725. return defaultValue || null;
  726. };
  727. /**
  728. * Convert a value into a string
  729. * @param {String | function | undefined} value
  730. * @param {String} [defaultValue]
  731. * @returns {String} str
  732. */
  733. exports.option.asString = function (value, defaultValue) {
  734. if (typeof value == 'function') {
  735. value = value();
  736. }
  737. if (value != null) {
  738. return String(value);
  739. }
  740. return defaultValue || null;
  741. };
  742. /**
  743. * Convert a size or location into a string with pixels or a percentage
  744. * @param {String | Number | function | undefined} value
  745. * @param {String} [defaultValue]
  746. * @returns {String} size
  747. */
  748. exports.option.asSize = function (value, defaultValue) {
  749. if (typeof value == 'function') {
  750. value = value();
  751. }
  752. if (exports.isString(value)) {
  753. return value;
  754. }
  755. else if (exports.isNumber(value)) {
  756. return value + 'px';
  757. }
  758. else {
  759. return defaultValue || null;
  760. }
  761. };
  762. /**
  763. * Convert a value into a DOM element
  764. * @param {HTMLElement | function | undefined} value
  765. * @param {HTMLElement} [defaultValue]
  766. * @returns {HTMLElement | null} dom
  767. */
  768. exports.option.asElement = function (value, defaultValue) {
  769. if (typeof value == 'function') {
  770. value = value();
  771. }
  772. return value || defaultValue || null;
  773. };
  774. exports.GiveDec = function(Hex) {
  775. var Value;
  776. if (Hex == "A")
  777. Value = 10;
  778. else if (Hex == "B")
  779. Value = 11;
  780. else if (Hex == "C")
  781. Value = 12;
  782. else if (Hex == "D")
  783. Value = 13;
  784. else if (Hex == "E")
  785. Value = 14;
  786. else if (Hex == "F")
  787. Value = 15;
  788. else
  789. Value = eval(Hex);
  790. return Value;
  791. };
  792. exports.GiveHex = function(Dec) {
  793. var Value;
  794. if(Dec == 10)
  795. Value = "A";
  796. else if (Dec == 11)
  797. Value = "B";
  798. else if (Dec == 12)
  799. Value = "C";
  800. else if (Dec == 13)
  801. Value = "D";
  802. else if (Dec == 14)
  803. Value = "E";
  804. else if (Dec == 15)
  805. Value = "F";
  806. else
  807. Value = "" + Dec;
  808. return Value;
  809. };
  810. /**
  811. * Parse a color property into an object with border, background, and
  812. * highlight colors
  813. * @param {Object | String} color
  814. * @return {Object} colorObject
  815. */
  816. exports.parseColor = function(color) {
  817. var c;
  818. if (exports.isString(color)) {
  819. if (exports.isValidRGB(color)) {
  820. var rgb = color.substr(4).substr(0,color.length-5).split(',');
  821. color = exports.RGBToHex(rgb[0],rgb[1],rgb[2]);
  822. }
  823. if (exports.isValidHex(color)) {
  824. var hsv = exports.hexToHSV(color);
  825. var lighterColorHSV = {h:hsv.h,s:hsv.s * 0.45,v:Math.min(1,hsv.v * 1.05)};
  826. var darkerColorHSV = {h:hsv.h,s:Math.min(1,hsv.v * 1.25),v:hsv.v*0.6};
  827. var darkerColorHex = exports.HSVToHex(darkerColorHSV.h ,darkerColorHSV.h ,darkerColorHSV.v);
  828. var lighterColorHex = exports.HSVToHex(lighterColorHSV.h,lighterColorHSV.s,lighterColorHSV.v);
  829. c = {
  830. background: color,
  831. border:darkerColorHex,
  832. highlight: {
  833. background:lighterColorHex,
  834. border:darkerColorHex
  835. },
  836. hover: {
  837. background:lighterColorHex,
  838. border:darkerColorHex
  839. }
  840. };
  841. }
  842. else {
  843. c = {
  844. background:color,
  845. border:color,
  846. highlight: {
  847. background:color,
  848. border:color
  849. },
  850. hover: {
  851. background:color,
  852. border:color
  853. }
  854. };
  855. }
  856. }
  857. else {
  858. c = {};
  859. c.background = color.background || 'white';
  860. c.border = color.border || c.background;
  861. if (exports.isString(color.highlight)) {
  862. c.highlight = {
  863. border: color.highlight,
  864. background: color.highlight
  865. }
  866. }
  867. else {
  868. c.highlight = {};
  869. c.highlight.background = color.highlight && color.highlight.background || c.background;
  870. c.highlight.border = color.highlight && color.highlight.border || c.border;
  871. }
  872. if (exports.isString(color.hover)) {
  873. c.hover = {
  874. border: color.hover,
  875. background: color.hover
  876. }
  877. }
  878. else {
  879. c.hover = {};
  880. c.hover.background = color.hover && color.hover.background || c.background;
  881. c.hover.border = color.hover && color.hover.border || c.border;
  882. }
  883. }
  884. return c;
  885. };
  886. /**
  887. * http://www.yellowpipe.com/yis/tools/hex-to-rgb/color-converter.php
  888. *
  889. * @param {String} hex
  890. * @returns {{r: *, g: *, b: *}}
  891. */
  892. exports.hexToRGB = function(hex) {
  893. hex = hex.replace("#","").toUpperCase();
  894. var a = exports.GiveDec(hex.substring(0, 1));
  895. var b = exports.GiveDec(hex.substring(1, 2));
  896. var c = exports.GiveDec(hex.substring(2, 3));
  897. var d = exports.GiveDec(hex.substring(3, 4));
  898. var e = exports.GiveDec(hex.substring(4, 5));
  899. var f = exports.GiveDec(hex.substring(5, 6));
  900. var r = (a * 16) + b;
  901. var g = (c * 16) + d;
  902. var b = (e * 16) + f;
  903. return {r:r,g:g,b:b};
  904. };
  905. exports.RGBToHex = function(red,green,blue) {
  906. var a = exports.GiveHex(Math.floor(red / 16));
  907. var b = exports.GiveHex(red % 16);
  908. var c = exports.GiveHex(Math.floor(green / 16));
  909. var d = exports.GiveHex(green % 16);
  910. var e = exports.GiveHex(Math.floor(blue / 16));
  911. var f = exports.GiveHex(blue % 16);
  912. var hex = a + b + c + d + e + f;
  913. return "#" + hex;
  914. };
  915. /**
  916. * http://www.javascripter.net/faq/rgb2hsv.htm
  917. *
  918. * @param red
  919. * @param green
  920. * @param blue
  921. * @returns {*}
  922. * @constructor
  923. */
  924. exports.RGBToHSV = function(red,green,blue) {
  925. red=red/255; green=green/255; blue=blue/255;
  926. var minRGB = Math.min(red,Math.min(green,blue));
  927. var maxRGB = Math.max(red,Math.max(green,blue));
  928. // Black-gray-white
  929. if (minRGB == maxRGB) {
  930. return {h:0,s:0,v:minRGB};
  931. }
  932. // Colors other than black-gray-white:
  933. var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
  934. var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
  935. var hue = 60*(h - d/(maxRGB - minRGB))/360;
  936. var saturation = (maxRGB - minRGB)/maxRGB;
  937. var value = maxRGB;
  938. return {h:hue,s:saturation,v:value};
  939. };
  940. /**
  941. * https://gist.github.com/mjijackson/5311256
  942. * @param h
  943. * @param s
  944. * @param v
  945. * @returns {{r: number, g: number, b: number}}
  946. * @constructor
  947. */
  948. exports.HSVToRGB = function(h, s, v) {
  949. var r, g, b;
  950. var i = Math.floor(h * 6);
  951. var f = h * 6 - i;
  952. var p = v * (1 - s);
  953. var q = v * (1 - f * s);
  954. var t = v * (1 - (1 - f) * s);
  955. switch (i % 6) {
  956. case 0: r = v, g = t, b = p; break;
  957. case 1: r = q, g = v, b = p; break;
  958. case 2: r = p, g = v, b = t; break;
  959. case 3: r = p, g = q, b = v; break;
  960. case 4: r = t, g = p, b = v; break;
  961. case 5: r = v, g = p, b = q; break;
  962. }
  963. return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
  964. };
  965. exports.HSVToHex = function(h, s, v) {
  966. var rgb = exports.HSVToRGB(h, s, v);
  967. return exports.RGBToHex(rgb.r, rgb.g, rgb.b);
  968. };
  969. exports.hexToHSV = function(hex) {
  970. var rgb = exports.hexToRGB(hex);
  971. return exports.RGBToHSV(rgb.r, rgb.g, rgb.b);
  972. };
  973. exports.isValidHex = function(hex) {
  974. var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
  975. return isOk;
  976. };
  977. exports.isValidRGB = function(rgb) {
  978. rgb = rgb.replace(" ","");
  979. var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb);
  980. return isOk;
  981. }
  982. /**
  983. * This recursively redirects the prototype of JSON objects to the referenceObject
  984. * This is used for default options.
  985. *
  986. * @param referenceObject
  987. * @returns {*}
  988. */
  989. exports.selectiveBridgeObject = function(fields, referenceObject) {
  990. if (typeof referenceObject == "object") {
  991. var objectTo = Object.create(referenceObject);
  992. for (var i = 0; i < fields.length; i++) {
  993. if (referenceObject.hasOwnProperty(fields[i])) {
  994. if (typeof referenceObject[fields[i]] == "object") {
  995. objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]);
  996. }
  997. }
  998. }
  999. return objectTo;
  1000. }
  1001. else {
  1002. return null;
  1003. }
  1004. };
  1005. /**
  1006. * This recursively redirects the prototype of JSON objects to the referenceObject
  1007. * This is used for default options.
  1008. *
  1009. * @param referenceObject
  1010. * @returns {*}
  1011. */
  1012. exports.bridgeObject = function(referenceObject) {
  1013. if (typeof referenceObject == "object") {
  1014. var objectTo = Object.create(referenceObject);
  1015. for (var i in referenceObject) {
  1016. if (referenceObject.hasOwnProperty(i)) {
  1017. if (typeof referenceObject[i] == "object") {
  1018. objectTo[i] = exports.bridgeObject(referenceObject[i]);
  1019. }
  1020. }
  1021. }
  1022. return objectTo;
  1023. }
  1024. else {
  1025. return null;
  1026. }
  1027. };
  1028. /**
  1029. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  1030. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  1031. *
  1032. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  1033. * @param [object] options | options
  1034. * @param [String] option | this is the option key in the options argument
  1035. * @private
  1036. */
  1037. exports.mergeOptions = function (mergeTarget, options, option) {
  1038. if (options[option] !== undefined) {
  1039. if (typeof options[option] == 'boolean') {
  1040. mergeTarget[option].enabled = options[option];
  1041. }
  1042. else {
  1043. mergeTarget[option].enabled = true;
  1044. for (prop in options[option]) {
  1045. if (options[option].hasOwnProperty(prop)) {
  1046. mergeTarget[option][prop] = options[option][prop];
  1047. }
  1048. }
  1049. }
  1050. }
  1051. }
  1052. /**
  1053. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  1054. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  1055. *
  1056. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  1057. * @param [object] options | options
  1058. * @param [String] option | this is the option key in the options argument
  1059. * @private
  1060. */
  1061. exports.mergeOptions = function (mergeTarget, options, option) {
  1062. if (options[option] !== undefined) {
  1063. if (typeof options[option] == 'boolean') {
  1064. mergeTarget[option].enabled = options[option];
  1065. }
  1066. else {
  1067. mergeTarget[option].enabled = true;
  1068. for (prop in options[option]) {
  1069. if (options[option].hasOwnProperty(prop)) {
  1070. mergeTarget[option][prop] = options[option][prop];
  1071. }
  1072. }
  1073. }
  1074. }
  1075. }
  1076. /**
  1077. * This function does a binary search for a visible item. The user can select either the this.orderedItems.byStart or .byEnd
  1078. * arrays. This is done by giving a boolean value true if you want to use the byEnd.
  1079. * 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
  1080. * if the time we selected (start or end) is within the current range).
  1081. *
  1082. * 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
  1083. * 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,
  1084. * either the start OR end time has to be in the range.
  1085. *
  1086. * @param {Item[]} orderedItems Items ordered by start
  1087. * @param {{start: number, end: number}} range
  1088. * @param {String} field
  1089. * @param {String} field2
  1090. * @returns {number}
  1091. * @private
  1092. */
  1093. exports.binarySearch = function(orderedItems, range, field, field2) {
  1094. var array = orderedItems;
  1095. var maxIterations = 10000;
  1096. var iteration = 0;
  1097. var found = false;
  1098. var low = 0;
  1099. var high = array.length;
  1100. var newLow = low;
  1101. var newHigh = high;
  1102. var guess = Math.floor(0.5*(high+low));
  1103. var value;
  1104. if (high == 0) {
  1105. guess = -1;
  1106. }
  1107. else if (high == 1) {
  1108. if (array[guess].isVisible(range)) {
  1109. guess = 0;
  1110. }
  1111. else {
  1112. guess = -1;
  1113. }
  1114. }
  1115. else {
  1116. high -= 1;
  1117. while (found == false && iteration < maxIterations) {
  1118. value = field2 === undefined ? array[guess][field] : array[guess][field][field2];
  1119. if (array[guess].isVisible(range)) {
  1120. found = true;
  1121. }
  1122. else {
  1123. if (value < range.start) { // it is too small --> increase low
  1124. newLow = Math.floor(0.5*(high+low));
  1125. }
  1126. else { // it is too big --> decrease high
  1127. newHigh = Math.floor(0.5*(high+low));
  1128. }
  1129. // not in list;
  1130. if (low == newLow && high == newHigh) {
  1131. guess = -1;
  1132. found = true;
  1133. }
  1134. else {
  1135. high = newHigh; low = newLow;
  1136. guess = Math.floor(0.5*(high+low));
  1137. }
  1138. }
  1139. iteration++;
  1140. }
  1141. if (iteration >= maxIterations) {
  1142. console.log("BinarySearch too many iterations. Aborting.");
  1143. }
  1144. }
  1145. return guess;
  1146. };
  1147. /**
  1148. * This function does a binary search for a visible item. The user can select either the this.orderedItems.byStart or .byEnd
  1149. * arrays. This is done by giving a boolean value true if you want to use the byEnd.
  1150. * 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
  1151. * if the time we selected (start or end) is within the current range).
  1152. *
  1153. * 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
  1154. * 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,
  1155. * either the start OR end time has to be in the range.
  1156. *
  1157. * @param {Array} orderedItems
  1158. * @param {{start: number, end: number}} target
  1159. * @param {String} field
  1160. * @param {String} sidePreference 'before' or 'after'
  1161. * @returns {number}
  1162. * @private
  1163. */
  1164. exports.binarySearchGeneric = function(orderedItems, target, field, sidePreference) {
  1165. var maxIterations = 10000;
  1166. var iteration = 0;
  1167. var array = orderedItems;
  1168. var found = false;
  1169. var low = 0;
  1170. var high = array.length;
  1171. var newLow = low;
  1172. var newHigh = high;
  1173. var guess = Math.floor(0.5*(high+low));
  1174. var newGuess;
  1175. var prevValue, value, nextValue;
  1176. if (high == 0) {guess = -1;}
  1177. else if (high == 1) {
  1178. value = array[guess][field];
  1179. if (value == target) {
  1180. guess = 0;
  1181. }
  1182. else {
  1183. guess = -1;
  1184. }
  1185. }
  1186. else {
  1187. high -= 1;
  1188. while (found == false && iteration < maxIterations) {
  1189. prevValue = array[Math.max(0,guess - 1)][field];
  1190. value = array[guess][field];
  1191. nextValue = array[Math.min(array.length-1,guess + 1)][field];
  1192. if (value == target || prevValue < target && value > target || value < target && nextValue > target) {
  1193. found = true;
  1194. if (value != target) {
  1195. if (sidePreference == 'before') {
  1196. if (prevValue < target && value > target) {
  1197. guess = Math.max(0,guess - 1);
  1198. }
  1199. }
  1200. else {
  1201. if (value < target && nextValue > target) {
  1202. guess = Math.min(array.length-1,guess + 1);
  1203. }
  1204. }
  1205. }
  1206. }
  1207. else {
  1208. if (value < target) { // it is too small --> increase low
  1209. newLow = Math.floor(0.5*(high+low));
  1210. }
  1211. else { // it is too big --> decrease high
  1212. newHigh = Math.floor(0.5*(high+low));
  1213. }
  1214. newGuess = Math.floor(0.5*(high+low));
  1215. // not in list;
  1216. if (low == newLow && high == newHigh) {
  1217. guess = -1;
  1218. found = true;
  1219. }
  1220. else {
  1221. high = newHigh; low = newLow;
  1222. guess = Math.floor(0.5*(high+low));
  1223. }
  1224. }
  1225. iteration++;
  1226. }
  1227. if (iteration >= maxIterations) {
  1228. console.log("BinarySearch too many iterations. Aborting.");
  1229. }
  1230. }
  1231. return guess;
  1232. };
  1233. /***/ },
  1234. /* 2 */
  1235. /***/ function(module, exports, __webpack_require__) {
  1236. // DOM utility methods
  1237. /**
  1238. * this prepares the JSON container for allocating SVG elements
  1239. * @param JSONcontainer
  1240. * @private
  1241. */
  1242. exports.prepareElements = function(JSONcontainer) {
  1243. // cleanup the redundant svgElements;
  1244. for (var elementType in JSONcontainer) {
  1245. if (JSONcontainer.hasOwnProperty(elementType)) {
  1246. JSONcontainer[elementType].redundant = JSONcontainer[elementType].used;
  1247. JSONcontainer[elementType].used = [];
  1248. }
  1249. }
  1250. };
  1251. /**
  1252. * this cleans up all the unused SVG elements. By asking for the parentNode, we only need to supply the JSON container from
  1253. * which to remove the redundant elements.
  1254. *
  1255. * @param JSONcontainer
  1256. * @private
  1257. */
  1258. exports.cleanupElements = function(JSONcontainer) {
  1259. // cleanup the redundant svgElements;
  1260. for (var elementType in JSONcontainer) {
  1261. if (JSONcontainer.hasOwnProperty(elementType)) {
  1262. if (JSONcontainer[elementType].redundant) {
  1263. for (var i = 0; i < JSONcontainer[elementType].redundant.length; i++) {
  1264. JSONcontainer[elementType].redundant[i].parentNode.removeChild(JSONcontainer[elementType].redundant[i]);
  1265. }
  1266. JSONcontainer[elementType].redundant = [];
  1267. }
  1268. }
  1269. }
  1270. };
  1271. /**
  1272. * Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer
  1273. * the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this.
  1274. *
  1275. * @param elementType
  1276. * @param JSONcontainer
  1277. * @param svgContainer
  1278. * @returns {*}
  1279. * @private
  1280. */
  1281. exports.getSVGElement = function (elementType, JSONcontainer, svgContainer) {
  1282. var element;
  1283. // allocate SVG element, if it doesnt yet exist, create one.
  1284. if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
  1285. // check if there is an redundant element
  1286. if (JSONcontainer[elementType].redundant.length > 0) {
  1287. element = JSONcontainer[elementType].redundant[0];
  1288. JSONcontainer[elementType].redundant.shift();
  1289. }
  1290. else {
  1291. // create a new element and add it to the SVG
  1292. element = document.createElementNS('http://www.w3.org/2000/svg', elementType);
  1293. svgContainer.appendChild(element);
  1294. }
  1295. }
  1296. else {
  1297. // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it.
  1298. element = document.createElementNS('http://www.w3.org/2000/svg', elementType);
  1299. JSONcontainer[elementType] = {used: [], redundant: []};
  1300. svgContainer.appendChild(element);
  1301. }
  1302. JSONcontainer[elementType].used.push(element);
  1303. return element;
  1304. };
  1305. /**
  1306. * Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer
  1307. * the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this.
  1308. *
  1309. * @param elementType
  1310. * @param JSONcontainer
  1311. * @param DOMContainer
  1312. * @returns {*}
  1313. * @private
  1314. */
  1315. exports.getDOMElement = function (elementType, JSONcontainer, DOMContainer) {
  1316. var element;
  1317. // allocate SVG element, if it doesnt yet exist, create one.
  1318. if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
  1319. // check if there is an redundant element
  1320. if (JSONcontainer[elementType].redundant.length > 0) {
  1321. element = JSONcontainer[elementType].redundant[0];
  1322. JSONcontainer[elementType].redundant.shift();
  1323. }
  1324. else {
  1325. // create a new element and add it to the SVG
  1326. element = document.createElement(elementType);
  1327. DOMContainer.appendChild(element);
  1328. }
  1329. }
  1330. else {
  1331. // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it.
  1332. element = document.createElement(elementType);
  1333. JSONcontainer[elementType] = {used: [], redundant: []};
  1334. DOMContainer.appendChild(element);
  1335. }
  1336. JSONcontainer[elementType].used.push(element);
  1337. return element;
  1338. };
  1339. /**
  1340. * draw a point object. this is a seperate function because it can also be called by the legend.
  1341. * The reason the JSONcontainer and the target SVG svgContainer have to be supplied is so the legend can use these functions
  1342. * as well.
  1343. *
  1344. * @param x
  1345. * @param y
  1346. * @param group
  1347. * @param JSONcontainer
  1348. * @param svgContainer
  1349. * @returns {*}
  1350. */
  1351. exports.drawPoint = function(x, y, group, JSONcontainer, svgContainer) {
  1352. var point;
  1353. if (group.options.drawPoints.style == 'circle') {
  1354. point = exports.getSVGElement('circle',JSONcontainer,svgContainer);
  1355. point.setAttributeNS(null, "cx", x);
  1356. point.setAttributeNS(null, "cy", y);
  1357. point.setAttributeNS(null, "r", 0.5 * group.options.drawPoints.size);
  1358. point.setAttributeNS(null, "class", group.className + " point");
  1359. }
  1360. else {
  1361. point = exports.getSVGElement('rect',JSONcontainer,svgContainer);
  1362. point.setAttributeNS(null, "x", x - 0.5*group.options.drawPoints.size);
  1363. point.setAttributeNS(null, "y", y - 0.5*group.options.drawPoints.size);
  1364. point.setAttributeNS(null, "width", group.options.drawPoints.size);
  1365. point.setAttributeNS(null, "height", group.options.drawPoints.size);
  1366. point.setAttributeNS(null, "class", group.className + " point");
  1367. }
  1368. return point;
  1369. };
  1370. /**
  1371. * draw a bar SVG element centered on the X coordinate
  1372. *
  1373. * @param x
  1374. * @param y
  1375. * @param className
  1376. */
  1377. exports.drawBar = function (x, y, width, height, className, JSONcontainer, svgContainer) {
  1378. var rect = exports.getSVGElement('rect',JSONcontainer, svgContainer);
  1379. rect.setAttributeNS(null, "x", x - 0.5 * width);
  1380. rect.setAttributeNS(null, "y", y);
  1381. rect.setAttributeNS(null, "width", width);
  1382. rect.setAttributeNS(null, "height", height);
  1383. rect.setAttributeNS(null, "class", className);
  1384. };
  1385. /***/ },
  1386. /* 3 */
  1387. /***/ function(module, exports, __webpack_require__) {
  1388. var util = __webpack_require__(1);
  1389. /**
  1390. * DataSet
  1391. *
  1392. * Usage:
  1393. * var dataSet = new DataSet({
  1394. * fieldId: '_id',
  1395. * type: {
  1396. * // ...
  1397. * }
  1398. * });
  1399. *
  1400. * dataSet.add(item);
  1401. * dataSet.add(data);
  1402. * dataSet.update(item);
  1403. * dataSet.update(data);
  1404. * dataSet.remove(id);
  1405. * dataSet.remove(ids);
  1406. * var data = dataSet.get();
  1407. * var data = dataSet.get(id);
  1408. * var data = dataSet.get(ids);
  1409. * var data = dataSet.get(ids, options, data);
  1410. * dataSet.clear();
  1411. *
  1412. * A data set can:
  1413. * - add/remove/update data
  1414. * - gives triggers upon changes in the data
  1415. * - can import/export data in various data formats
  1416. *
  1417. * @param {Array | DataTable} [data] Optional array with initial data
  1418. * @param {Object} [options] Available options:
  1419. * {String} fieldId Field name of the id in the
  1420. * items, 'id' by default.
  1421. * {Object.<String, String} type
  1422. * A map with field names as key,
  1423. * and the field type as value.
  1424. * @constructor DataSet
  1425. */
  1426. // TODO: add a DataSet constructor DataSet(data, options)
  1427. function DataSet (data, options) {
  1428. // correctly read optional arguments
  1429. if (data && !Array.isArray(data) && !util.isDataTable(data)) {
  1430. options = data;
  1431. data = null;
  1432. }
  1433. this._options = options || {};
  1434. this._data = {}; // map with data indexed by id
  1435. this._fieldId = this._options.fieldId || 'id'; // name of the field containing id
  1436. this._type = {}; // internal field types (NOTE: this can differ from this._options.type)
  1437. // all variants of a Date are internally stored as Date, so we can convert
  1438. // from everything to everything (also from ISODate to Number for example)
  1439. if (this._options.type) {
  1440. for (var field in this._options.type) {
  1441. if (this._options.type.hasOwnProperty(field)) {
  1442. var value = this._options.type[field];
  1443. if (value == 'Date' || value == 'ISODate' || value == 'ASPDate') {
  1444. this._type[field] = 'Date';
  1445. }
  1446. else {
  1447. this._type[field] = value;
  1448. }
  1449. }
  1450. }
  1451. }
  1452. // TODO: deprecated since version 1.1.1 (or 2.0.0?)
  1453. if (this._options.convert) {
  1454. throw new Error('Option "convert" is deprecated. Use "type" instead.');
  1455. }
  1456. this._subscribers = {}; // event subscribers
  1457. // add initial data when provided
  1458. if (data) {
  1459. this.add(data);
  1460. }
  1461. }
  1462. /**
  1463. * Subscribe to an event, add an event listener
  1464. * @param {String} event Event name. Available events: 'put', 'update',
  1465. * 'remove'
  1466. * @param {function} callback Callback method. Called with three parameters:
  1467. * {String} event
  1468. * {Object | null} params
  1469. * {String | Number} senderId
  1470. */
  1471. DataSet.prototype.on = function(event, callback) {
  1472. var subscribers = this._subscribers[event];
  1473. if (!subscribers) {
  1474. subscribers = [];
  1475. this._subscribers[event] = subscribers;
  1476. }
  1477. subscribers.push({
  1478. callback: callback
  1479. });
  1480. };
  1481. // TODO: make this function deprecated (replaced with `on` since version 0.5)
  1482. DataSet.prototype.subscribe = DataSet.prototype.on;
  1483. /**
  1484. * Unsubscribe from an event, remove an event listener
  1485. * @param {String} event
  1486. * @param {function} callback
  1487. */
  1488. DataSet.prototype.off = function(event, callback) {
  1489. var subscribers = this._subscribers[event];
  1490. if (subscribers) {
  1491. this._subscribers[event] = subscribers.filter(function (listener) {
  1492. return (listener.callback != callback);
  1493. });
  1494. }
  1495. };
  1496. // TODO: make this function deprecated (replaced with `on` since version 0.5)
  1497. DataSet.prototype.unsubscribe = DataSet.prototype.off;
  1498. /**
  1499. * Trigger an event
  1500. * @param {String} event
  1501. * @param {Object | null} params
  1502. * @param {String} [senderId] Optional id of the sender.
  1503. * @private
  1504. */
  1505. DataSet.prototype._trigger = function (event, params, senderId) {
  1506. if (event == '*') {
  1507. throw new Error('Cannot trigger event *');
  1508. }
  1509. var subscribers = [];
  1510. if (event in this._subscribers) {
  1511. subscribers = subscribers.concat(this._subscribers[event]);
  1512. }
  1513. if ('*' in this._subscribers) {
  1514. subscribers = subscribers.concat(this._subscribers['*']);
  1515. }
  1516. for (var i = 0; i < subscribers.length; i++) {
  1517. var subscriber = subscribers[i];
  1518. if (subscriber.callback) {
  1519. subscriber.callback(event, params, senderId || null);
  1520. }
  1521. }
  1522. };
  1523. /**
  1524. * Add data.
  1525. * Adding an item will fail when there already is an item with the same id.
  1526. * @param {Object | Array | DataTable} data
  1527. * @param {String} [senderId] Optional sender id
  1528. * @return {Array} addedIds Array with the ids of the added items
  1529. */
  1530. DataSet.prototype.add = function (data, senderId) {
  1531. var addedIds = [],
  1532. id,
  1533. me = this;
  1534. if (Array.isArray(data)) {
  1535. // Array
  1536. for (var i = 0, len = data.length; i < len; i++) {
  1537. id = me._addItem(data[i]);
  1538. addedIds.push(id);
  1539. }
  1540. }
  1541. else if (util.isDataTable(data)) {
  1542. // Google DataTable
  1543. var columns = this._getColumnNames(data);
  1544. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  1545. var item = {};
  1546. for (var col = 0, cols = columns.length; col < cols; col++) {
  1547. var field = columns[col];
  1548. item[field] = data.getValue(row, col);
  1549. }
  1550. id = me._addItem(item);
  1551. addedIds.push(id);
  1552. }
  1553. }
  1554. else if (data instanceof Object) {
  1555. // Single item
  1556. id = me._addItem(data);
  1557. addedIds.push(id);
  1558. }
  1559. else {
  1560. throw new Error('Unknown dataType');
  1561. }
  1562. if (addedIds.length) {
  1563. this._trigger('add', {items: addedIds}, senderId);
  1564. }
  1565. return addedIds;
  1566. };
  1567. /**
  1568. * Update existing items. When an item does not exist, it will be created
  1569. * @param {Object | Array | DataTable} data
  1570. * @param {String} [senderId] Optional sender id
  1571. * @return {Array} updatedIds The ids of the added or updated items
  1572. */
  1573. DataSet.prototype.update = function (data, senderId) {
  1574. var addedIds = [],
  1575. updatedIds = [],
  1576. me = this,
  1577. fieldId = me._fieldId;
  1578. var addOrUpdate = function (item) {
  1579. var id = item[fieldId];
  1580. if (me._data[id]) {
  1581. // update item
  1582. id = me._updateItem(item);
  1583. updatedIds.push(id);
  1584. }
  1585. else {
  1586. // add new item
  1587. id = me._addItem(item);
  1588. addedIds.push(id);
  1589. }
  1590. };
  1591. if (Array.isArray(data)) {
  1592. // Array
  1593. for (var i = 0, len = data.length; i < len; i++) {
  1594. addOrUpdate(data[i]);
  1595. }
  1596. }
  1597. else if (util.isDataTable(data)) {
  1598. // Google DataTable
  1599. var columns = this._getColumnNames(data);
  1600. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  1601. var item = {};
  1602. for (var col = 0, cols = columns.length; col < cols; col++) {
  1603. var field = columns[col];
  1604. item[field] = data.getValue(row, col);
  1605. }
  1606. addOrUpdate(item);
  1607. }
  1608. }
  1609. else if (data instanceof Object) {
  1610. // Single item
  1611. addOrUpdate(data);
  1612. }
  1613. else {
  1614. throw new Error('Unknown dataType');
  1615. }
  1616. if (addedIds.length) {
  1617. this._trigger('add', {items: addedIds}, senderId);
  1618. }
  1619. if (updatedIds.length) {
  1620. this._trigger('update', {items: updatedIds}, senderId);
  1621. }
  1622. return addedIds.concat(updatedIds);
  1623. };
  1624. /**
  1625. * Get a data item or multiple items.
  1626. *
  1627. * Usage:
  1628. *
  1629. * get()
  1630. * get(options: Object)
  1631. * get(options: Object, data: Array | DataTable)
  1632. *
  1633. * get(id: Number | String)
  1634. * get(id: Number | String, options: Object)
  1635. * get(id: Number | String, options: Object, data: Array | DataTable)
  1636. *
  1637. * get(ids: Number[] | String[])
  1638. * get(ids: Number[] | String[], options: Object)
  1639. * get(ids: Number[] | String[], options: Object, data: Array | DataTable)
  1640. *
  1641. * Where:
  1642. *
  1643. * {Number | String} id The id of an item
  1644. * {Number[] | String{}} ids An array with ids of items
  1645. * {Object} options An Object with options. Available options:
  1646. * {String} [returnType] Type of data to be
  1647. * returned. Can be 'DataTable' or 'Array' (default)
  1648. * {Object.<String, String>} [type]
  1649. * {String[]} [fields] field names to be returned
  1650. * {function} [filter] filter items
  1651. * {String | function} [order] Order the items by
  1652. * a field name or custom sort function.
  1653. * {Array | DataTable} [data] If provided, items will be appended to this
  1654. * array or table. Required in case of Google
  1655. * DataTable.
  1656. *
  1657. * @throws Error
  1658. */
  1659. DataSet.prototype.get = function (args) {
  1660. var me = this;
  1661. // parse the arguments
  1662. var id, ids, options, data;
  1663. var firstType = util.getType(arguments[0]);
  1664. if (firstType == 'String' || firstType == 'Number') {
  1665. // get(id [, options] [, data])
  1666. id = arguments[0];
  1667. options = arguments[1];
  1668. data = arguments[2];
  1669. }
  1670. else if (firstType == 'Array') {
  1671. // get(ids [, options] [, data])
  1672. ids = arguments[0];
  1673. options = arguments[1];
  1674. data = arguments[2];
  1675. }
  1676. else {
  1677. // get([, options] [, data])
  1678. options = arguments[0];
  1679. data = arguments[1];
  1680. }
  1681. // determine the return type
  1682. var returnType;
  1683. if (options && options.returnType) {
  1684. var allowedValues = ["DataTable", "Array", "Object"];
  1685. returnType = allowedValues.indexOf(options.returnType) == -1 ? "Array" : options.returnType;
  1686. if (data && (returnType != util.getType(data))) {
  1687. throw new Error('Type of parameter "data" (' + util.getType(data) + ') ' +
  1688. 'does not correspond with specified options.type (' + options.type + ')');
  1689. }
  1690. if (returnType == 'DataTable' && !util.isDataTable(data)) {
  1691. throw new Error('Parameter "data" must be a DataTable ' +
  1692. 'when options.type is "DataTable"');
  1693. }
  1694. }
  1695. else if (data) {
  1696. returnType = (util.getType(data) == 'DataTable') ? 'DataTable' : 'Array';
  1697. }
  1698. else {
  1699. returnType = 'Array';
  1700. }
  1701. // build options
  1702. var type = options && options.type || this._options.type;
  1703. var filter = options && options.filter;
  1704. var items = [], item, itemId, i, len;
  1705. // convert items
  1706. if (id != undefined) {
  1707. // return a single item
  1708. item = me._getItem(id, type);
  1709. if (filter && !filter(item)) {
  1710. item = null;
  1711. }
  1712. }
  1713. else if (ids != undefined) {
  1714. // return a subset of items
  1715. for (i = 0, len = ids.length; i < len; i++) {
  1716. item = me._getItem(ids[i], type);
  1717. if (!filter || filter(item)) {
  1718. items.push(item);
  1719. }
  1720. }
  1721. }
  1722. else {
  1723. // return all items
  1724. for (itemId in this._data) {
  1725. if (this._data.hasOwnProperty(itemId)) {
  1726. item = me._getItem(itemId, type);
  1727. if (!filter || filter(item)) {
  1728. items.push(item);
  1729. }
  1730. }
  1731. }
  1732. }
  1733. // order the results
  1734. if (options && options.order && id == undefined) {
  1735. this._sort(items, options.order);
  1736. }
  1737. // filter fields of the items
  1738. if (options && options.fields) {
  1739. var fields = options.fields;
  1740. if (id != undefined) {
  1741. item = this._filterFields(item, fields);
  1742. }
  1743. else {
  1744. for (i = 0, len = items.length; i < len; i++) {
  1745. items[i] = this._filterFields(items[i], fields);
  1746. }
  1747. }
  1748. }
  1749. // return the results
  1750. if (returnType == 'DataTable') {
  1751. var columns = this._getColumnNames(data);
  1752. if (id != undefined) {
  1753. // append a single item to the data table
  1754. me._appendRow(data, columns, item);
  1755. }
  1756. else {
  1757. // copy the items to the provided data table
  1758. for (i = 0; i < items.length; i++) {
  1759. me._appendRow(data, columns, items[i]);
  1760. }
  1761. }
  1762. return data;
  1763. }
  1764. else if (returnType == "Object") {
  1765. var result = {};
  1766. for (i = 0; i < items.length; i++) {
  1767. result[items[i].id] = items[i];
  1768. }
  1769. return result;
  1770. }
  1771. else {
  1772. // return an array
  1773. if (id != undefined) {
  1774. // a single item
  1775. return item;
  1776. }
  1777. else {
  1778. // multiple items
  1779. if (data) {
  1780. // copy the items to the provided array
  1781. for (i = 0, len = items.length; i < len; i++) {
  1782. data.push(items[i]);
  1783. }
  1784. return data;
  1785. }
  1786. else {
  1787. // just return our array
  1788. return items;
  1789. }
  1790. }
  1791. }
  1792. };
  1793. /**
  1794. * Get ids of all items or from a filtered set of items.
  1795. * @param {Object} [options] An Object with options. Available options:
  1796. * {function} [filter] filter items
  1797. * {String | function} [order] Order the items by
  1798. * a field name or custom sort function.
  1799. * @return {Array} ids
  1800. */
  1801. DataSet.prototype.getIds = function (options) {
  1802. var data = this._data,
  1803. filter = options && options.filter,
  1804. order = options && options.order,
  1805. type = options && options.type || this._options.type,
  1806. i,
  1807. len,
  1808. id,
  1809. item,
  1810. items,
  1811. ids = [];
  1812. if (filter) {
  1813. // get filtered items
  1814. if (order) {
  1815. // create ordered list
  1816. items = [];
  1817. for (id in data) {
  1818. if (data.hasOwnProperty(id)) {
  1819. item = this._getItem(id, type);
  1820. if (filter(item)) {
  1821. items.push(item);
  1822. }
  1823. }
  1824. }
  1825. this._sort(items, order);
  1826. for (i = 0, len = items.length; i < len; i++) {
  1827. ids[i] = items[i][this._fieldId];
  1828. }
  1829. }
  1830. else {
  1831. // create unordered list
  1832. for (id in data) {
  1833. if (data.hasOwnProperty(id)) {
  1834. item = this._getItem(id, type);
  1835. if (filter(item)) {
  1836. ids.push(item[this._fieldId]);
  1837. }
  1838. }
  1839. }
  1840. }
  1841. }
  1842. else {
  1843. // get all items
  1844. if (order) {
  1845. // create an ordered list
  1846. items = [];
  1847. for (id in data) {
  1848. if (data.hasOwnProperty(id)) {
  1849. items.push(data[id]);
  1850. }
  1851. }
  1852. this._sort(items, order);
  1853. for (i = 0, len = items.length; i < len; i++) {
  1854. ids[i] = items[i][this._fieldId];
  1855. }
  1856. }
  1857. else {
  1858. // create unordered list
  1859. for (id in data) {
  1860. if (data.hasOwnProperty(id)) {
  1861. item = data[id];
  1862. ids.push(item[this._fieldId]);
  1863. }
  1864. }
  1865. }
  1866. }
  1867. return ids;
  1868. };
  1869. /**
  1870. * Returns the DataSet itself. Is overwritten for example by the DataView,
  1871. * which returns the DataSet it is connected to instead.
  1872. */
  1873. DataSet.prototype.getDataSet = function () {
  1874. return this;
  1875. };
  1876. /**
  1877. * Execute a callback function for every item in the dataset.
  1878. * @param {function} callback
  1879. * @param {Object} [options] Available options:
  1880. * {Object.<String, String>} [type]
  1881. * {String[]} [fields] filter fields
  1882. * {function} [filter] filter items
  1883. * {String | function} [order] Order the items by
  1884. * a field name or custom sort function.
  1885. */
  1886. DataSet.prototype.forEach = function (callback, options) {
  1887. var filter = options && options.filter,
  1888. type = options && options.type || this._options.type,
  1889. data = this._data,
  1890. item,
  1891. id;
  1892. if (options && options.order) {
  1893. // execute forEach on ordered list
  1894. var items = this.get(options);
  1895. for (var i = 0, len = items.length; i < len; i++) {
  1896. item = items[i];
  1897. id = item[this._fieldId];
  1898. callback(item, id);
  1899. }
  1900. }
  1901. else {
  1902. // unordered
  1903. for (id in data) {
  1904. if (data.hasOwnProperty(id)) {
  1905. item = this._getItem(id, type);
  1906. if (!filter || filter(item)) {
  1907. callback(item, id);
  1908. }
  1909. }
  1910. }
  1911. }
  1912. };
  1913. /**
  1914. * Map every item in the dataset.
  1915. * @param {function} callback
  1916. * @param {Object} [options] Available options:
  1917. * {Object.<String, String>} [type]
  1918. * {String[]} [fields] filter fields
  1919. * {function} [filter] filter items
  1920. * {String | function} [order] Order the items by
  1921. * a field name or custom sort function.
  1922. * @return {Object[]} mappedItems
  1923. */
  1924. DataSet.prototype.map = function (callback, options) {
  1925. var filter = options && options.filter,
  1926. type = options && options.type || this._options.type,
  1927. mappedItems = [],
  1928. data = this._data,
  1929. item;
  1930. // convert and filter items
  1931. for (var id in data) {
  1932. if (data.hasOwnProperty(id)) {
  1933. item = this._getItem(id, type);
  1934. if (!filter || filter(item)) {
  1935. mappedItems.push(callback(item, id));
  1936. }
  1937. }
  1938. }
  1939. // order items
  1940. if (options && options.order) {
  1941. this._sort(mappedItems, options.order);
  1942. }
  1943. return mappedItems;
  1944. };
  1945. /**
  1946. * Filter the fields of an item
  1947. * @param {Object} item
  1948. * @param {String[]} fields Field names
  1949. * @return {Object} filteredItem
  1950. * @private
  1951. */
  1952. DataSet.prototype._filterFields = function (item, fields) {
  1953. var filteredItem = {};
  1954. for (var field in item) {
  1955. if (item.hasOwnProperty(field) && (fields.indexOf(field) != -1)) {
  1956. filteredItem[field] = item[field];
  1957. }
  1958. }
  1959. return filteredItem;
  1960. };
  1961. /**
  1962. * Sort the provided array with items
  1963. * @param {Object[]} items
  1964. * @param {String | function} order A field name or custom sort function.
  1965. * @private
  1966. */
  1967. DataSet.prototype._sort = function (items, order) {
  1968. if (util.isString(order)) {
  1969. // order by provided field name
  1970. var name = order; // field name
  1971. items.sort(function (a, b) {
  1972. var av = a[name];
  1973. var bv = b[name];
  1974. return (av > bv) ? 1 : ((av < bv) ? -1 : 0);
  1975. });
  1976. }
  1977. else if (typeof order === 'function') {
  1978. // order by sort function
  1979. items.sort(order);
  1980. }
  1981. // TODO: extend order by an Object {field:String, direction:String}
  1982. // where direction can be 'asc' or 'desc'
  1983. else {
  1984. throw new TypeError('Order must be a function or a string');
  1985. }
  1986. };
  1987. /**
  1988. * Remove an object by pointer or by id
  1989. * @param {String | Number | Object | Array} id Object or id, or an array with
  1990. * objects or ids to be removed
  1991. * @param {String} [senderId] Optional sender id
  1992. * @return {Array} removedIds
  1993. */
  1994. DataSet.prototype.remove = function (id, senderId) {
  1995. var removedIds = [],
  1996. i, len, removedId;
  1997. if (Array.isArray(id)) {
  1998. for (i = 0, len = id.length; i < len; i++) {
  1999. removedId = this._remove(id[i]);
  2000. if (removedId != null) {
  2001. removedIds.push(removedId);
  2002. }
  2003. }
  2004. }
  2005. else {
  2006. removedId = this._remove(id);
  2007. if (removedId != null) {
  2008. removedIds.push(removedId);
  2009. }
  2010. }
  2011. if (removedIds.length) {
  2012. this._trigger('remove', {items: removedIds}, senderId);
  2013. }
  2014. return removedIds;
  2015. };
  2016. /**
  2017. * Remove an item by its id
  2018. * @param {Number | String | Object} id id or item
  2019. * @returns {Number | String | null} id
  2020. * @private
  2021. */
  2022. DataSet.prototype._remove = function (id) {
  2023. if (util.isNumber(id) || util.isString(id)) {
  2024. if (this._data[id]) {
  2025. delete this._data[id];
  2026. return id;
  2027. }
  2028. }
  2029. else if (id instanceof Object) {
  2030. var itemId = id[this._fieldId];
  2031. if (itemId && this._data[itemId]) {
  2032. delete this._data[itemId];
  2033. return itemId;
  2034. }
  2035. }
  2036. return null;
  2037. };
  2038. /**
  2039. * Clear the data
  2040. * @param {String} [senderId] Optional sender id
  2041. * @return {Array} removedIds The ids of all removed items
  2042. */
  2043. DataSet.prototype.clear = function (senderId) {
  2044. var ids = Object.keys(this._data);
  2045. this._data = {};
  2046. this._trigger('remove', {items: ids}, senderId);
  2047. return ids;
  2048. };
  2049. /**
  2050. * Find the item with maximum value of a specified field
  2051. * @param {String} field
  2052. * @return {Object | null} item Item containing max value, or null if no items
  2053. */
  2054. DataSet.prototype.max = function (field) {
  2055. var data = this._data,
  2056. max = null,
  2057. maxField = null;
  2058. for (var id in data) {
  2059. if (data.hasOwnProperty(id)) {
  2060. var item = data[id];
  2061. var itemField = item[field];
  2062. if (itemField != null && (!max || itemField > maxField)) {
  2063. max = item;
  2064. maxField = itemField;
  2065. }
  2066. }
  2067. }
  2068. return max;
  2069. };
  2070. /**
  2071. * Find the item with minimum value of a specified field
  2072. * @param {String} field
  2073. * @return {Object | null} item Item containing max value, or null if no items
  2074. */
  2075. DataSet.prototype.min = function (field) {
  2076. var data = this._data,
  2077. min = null,
  2078. minField = null;
  2079. for (var id in data) {
  2080. if (data.hasOwnProperty(id)) {
  2081. var item = data[id];
  2082. var itemField = item[field];
  2083. if (itemField != null && (!min || itemField < minField)) {
  2084. min = item;
  2085. minField = itemField;
  2086. }
  2087. }
  2088. }
  2089. return min;
  2090. };
  2091. /**
  2092. * Find all distinct values of a specified field
  2093. * @param {String} field
  2094. * @return {Array} values Array containing all distinct values. If data items
  2095. * do not contain the specified field are ignored.
  2096. * The returned array is unordered.
  2097. */
  2098. DataSet.prototype.distinct = function (field) {
  2099. var data = this._data;
  2100. var values = [];
  2101. var fieldType = this._options.type && this._options.type[field] || null;
  2102. var count = 0;
  2103. var i;
  2104. for (var prop in data) {
  2105. if (data.hasOwnProperty(prop)) {
  2106. var item = data[prop];
  2107. var value = item[field];
  2108. var exists = false;
  2109. for (i = 0; i < count; i++) {
  2110. if (values[i] == value) {
  2111. exists = true;
  2112. break;
  2113. }
  2114. }
  2115. if (!exists && (value !== undefined)) {
  2116. values[count] = value;
  2117. count++;
  2118. }
  2119. }
  2120. }
  2121. if (fieldType) {
  2122. for (i = 0; i < values.length; i++) {
  2123. values[i] = util.convert(values[i], fieldType);
  2124. }
  2125. }
  2126. return values;
  2127. };
  2128. /**
  2129. * Add a single item. Will fail when an item with the same id already exists.
  2130. * @param {Object} item
  2131. * @return {String} id
  2132. * @private
  2133. */
  2134. DataSet.prototype._addItem = function (item) {
  2135. var id = item[this._fieldId];
  2136. if (id != undefined) {
  2137. // check whether this id is already taken
  2138. if (this._data[id]) {
  2139. // item already exists
  2140. throw new Error('Cannot add item: item with id ' + id + ' already exists');
  2141. }
  2142. }
  2143. else {
  2144. // generate an id
  2145. id = util.randomUUID();
  2146. item[this._fieldId] = id;
  2147. }
  2148. var d = {};
  2149. for (var field in item) {
  2150. if (item.hasOwnProperty(field)) {
  2151. var fieldType = this._type[field]; // type may be undefined
  2152. d[field] = util.convert(item[field], fieldType);
  2153. }
  2154. }
  2155. this._data[id] = d;
  2156. return id;
  2157. };
  2158. /**
  2159. * Get an item. Fields can be converted to a specific type
  2160. * @param {String} id
  2161. * @param {Object.<String, String>} [types] field types to convert
  2162. * @return {Object | null} item
  2163. * @private
  2164. */
  2165. DataSet.prototype._getItem = function (id, types) {
  2166. var field, value;
  2167. // get the item from the dataset
  2168. var raw = this._data[id];
  2169. if (!raw) {
  2170. return null;
  2171. }
  2172. // convert the items field types
  2173. var converted = {};
  2174. if (types) {
  2175. for (field in raw) {
  2176. if (raw.hasOwnProperty(field)) {
  2177. value = raw[field];
  2178. converted[field] = util.convert(value, types[field]);
  2179. }
  2180. }
  2181. }
  2182. else {
  2183. // no field types specified, no converting needed
  2184. for (field in raw) {
  2185. if (raw.hasOwnProperty(field)) {
  2186. value = raw[field];
  2187. converted[field] = value;
  2188. }
  2189. }
  2190. }
  2191. return converted;
  2192. };
  2193. /**
  2194. * Update a single item: merge with existing item.
  2195. * Will fail when the item has no id, or when there does not exist an item
  2196. * with the same id.
  2197. * @param {Object} item
  2198. * @return {String} id
  2199. * @private
  2200. */
  2201. DataSet.prototype._updateItem = function (item) {
  2202. var id = item[this._fieldId];
  2203. if (id == undefined) {
  2204. throw new Error('Cannot update item: item has no id (item: ' + JSON.stringify(item) + ')');
  2205. }
  2206. var d = this._data[id];
  2207. if (!d) {
  2208. // item doesn't exist
  2209. throw new Error('Cannot update item: no item with id ' + id + ' found');
  2210. }
  2211. // merge with current item
  2212. for (var field in item) {
  2213. if (item.hasOwnProperty(field)) {
  2214. var fieldType = this._type[field]; // type may be undefined
  2215. d[field] = util.convert(item[field], fieldType);
  2216. }
  2217. }
  2218. return id;
  2219. };
  2220. /**
  2221. * Get an array with the column names of a Google DataTable
  2222. * @param {DataTable} dataTable
  2223. * @return {String[]} columnNames
  2224. * @private
  2225. */
  2226. DataSet.prototype._getColumnNames = function (dataTable) {
  2227. var columns = [];
  2228. for (var col = 0, cols = dataTable.getNumberOfColumns(); col < cols; col++) {
  2229. columns[col] = dataTable.getColumnId(col) || dataTable.getColumnLabel(col);
  2230. }
  2231. return columns;
  2232. };
  2233. /**
  2234. * Append an item as a row to the dataTable
  2235. * @param dataTable
  2236. * @param columns
  2237. * @param item
  2238. * @private
  2239. */
  2240. DataSet.prototype._appendRow = function (dataTable, columns, item) {
  2241. var row = dataTable.addRow();
  2242. for (var col = 0, cols = columns.length; col < cols; col++) {
  2243. var field = columns[col];
  2244. dataTable.setValue(row, col, item[field]);
  2245. }
  2246. };
  2247. module.exports = DataSet;
  2248. /***/ },
  2249. /* 4 */
  2250. /***/ function(module, exports, __webpack_require__) {
  2251. var util = __webpack_require__(1);
  2252. var DataSet = __webpack_require__(3);
  2253. /**
  2254. * DataView
  2255. *
  2256. * a dataview offers a filtered view on a dataset or an other dataview.
  2257. *
  2258. * @param {DataSet | DataView} data
  2259. * @param {Object} [options] Available options: see method get
  2260. *
  2261. * @constructor DataView
  2262. */
  2263. function DataView (data, options) {
  2264. this._data = null;
  2265. this._ids = {}; // ids of the items currently in memory (just contains a boolean true)
  2266. this._options = options || {};
  2267. this._fieldId = 'id'; // name of the field containing id
  2268. this._subscribers = {}; // event subscribers
  2269. var me = this;
  2270. this.listener = function () {
  2271. me._onEvent.apply(me, arguments);
  2272. };
  2273. this.setData(data);
  2274. }
  2275. // TODO: implement a function .config() to dynamically update things like configured filter
  2276. // and trigger changes accordingly
  2277. /**
  2278. * Set a data source for the view
  2279. * @param {DataSet | DataView} data
  2280. */
  2281. DataView.prototype.setData = function (data) {
  2282. var ids, i, len;
  2283. if (this._data) {
  2284. // unsubscribe from current dataset
  2285. if (this._data.unsubscribe) {
  2286. this._data.unsubscribe('*', this.listener);
  2287. }
  2288. // trigger a remove of all items in memory
  2289. ids = [];
  2290. for (var id in this._ids) {
  2291. if (this._ids.hasOwnProperty(id)) {
  2292. ids.push(id);
  2293. }
  2294. }
  2295. this._ids = {};
  2296. this._trigger('remove', {items: ids});
  2297. }
  2298. this._data = data;
  2299. if (this._data) {
  2300. // update fieldId
  2301. this._fieldId = this._options.fieldId ||
  2302. (this._data && this._data.options && this._data.options.fieldId) ||
  2303. 'id';
  2304. // trigger an add of all added items
  2305. ids = this._data.getIds({filter: this._options && this._options.filter});
  2306. for (i = 0, len = ids.length; i < len; i++) {
  2307. id = ids[i];
  2308. this._ids[id] = true;
  2309. }
  2310. this._trigger('add', {items: ids});
  2311. // subscribe to new dataset
  2312. if (this._data.on) {
  2313. this._data.on('*', this.listener);
  2314. }
  2315. }
  2316. };
  2317. /**
  2318. * Get data from the data view
  2319. *
  2320. * Usage:
  2321. *
  2322. * get()
  2323. * get(options: Object)
  2324. * get(options: Object, data: Array | DataTable)
  2325. *
  2326. * get(id: Number)
  2327. * get(id: Number, options: Object)
  2328. * get(id: Number, options: Object, data: Array | DataTable)
  2329. *
  2330. * get(ids: Number[])
  2331. * get(ids: Number[], options: Object)
  2332. * get(ids: Number[], options: Object, data: Array | DataTable)
  2333. *
  2334. * Where:
  2335. *
  2336. * {Number | String} id The id of an item
  2337. * {Number[] | String{}} ids An array with ids of items
  2338. * {Object} options An Object with options. Available options:
  2339. * {String} [type] Type of data to be returned. Can
  2340. * be 'DataTable' or 'Array' (default)
  2341. * {Object.<String, String>} [convert]
  2342. * {String[]} [fields] field names to be returned
  2343. * {function} [filter] filter items
  2344. * {String | function} [order] Order the items by
  2345. * a field name or custom sort function.
  2346. * {Array | DataTable} [data] If provided, items will be appended to this
  2347. * array or table. Required in case of Google
  2348. * DataTable.
  2349. * @param args
  2350. */
  2351. DataView.prototype.get = function (args) {
  2352. var me = this;
  2353. // parse the arguments
  2354. var ids, options, data;
  2355. var firstType = util.getType(arguments[0]);
  2356. if (firstType == 'String' || firstType == 'Number' || firstType == 'Array') {
  2357. // get(id(s) [, options] [, data])
  2358. ids = arguments[0]; // can be a single id or an array with ids
  2359. options = arguments[1];
  2360. data = arguments[2];
  2361. }
  2362. else {
  2363. // get([, options] [, data])
  2364. options = arguments[0];
  2365. data = arguments[1];
  2366. }
  2367. // extend the options with the default options and provided options
  2368. var viewOptions = util.extend({}, this._options, options);
  2369. // create a combined filter method when needed
  2370. if (this._options.filter && options && options.filter) {
  2371. viewOptions.filter = function (item) {
  2372. return me._options.filter(item) && options.filter(item);
  2373. }
  2374. }
  2375. // build up the call to the linked data set
  2376. var getArguments = [];
  2377. if (ids != undefined) {
  2378. getArguments.push(ids);
  2379. }
  2380. getArguments.push(viewOptions);
  2381. getArguments.push(data);
  2382. return this._data && this._data.get.apply(this._data, getArguments);
  2383. };
  2384. /**
  2385. * Get ids of all items or from a filtered set of items.
  2386. * @param {Object} [options] An Object with options. Available options:
  2387. * {function} [filter] filter items
  2388. * {String | function} [order] Order the items by
  2389. * a field name or custom sort function.
  2390. * @return {Array} ids
  2391. */
  2392. DataView.prototype.getIds = function (options) {
  2393. var ids;
  2394. if (this._data) {
  2395. var defaultFilter = this._options.filter;
  2396. var filter;
  2397. if (options && options.filter) {
  2398. if (defaultFilter) {
  2399. filter = function (item) {
  2400. return defaultFilter(item) && options.filter(item);
  2401. }
  2402. }
  2403. else {
  2404. filter = options.filter;
  2405. }
  2406. }
  2407. else {
  2408. filter = defaultFilter;
  2409. }
  2410. ids = this._data.getIds({
  2411. filter: filter,
  2412. order: options && options.order
  2413. });
  2414. }
  2415. else {
  2416. ids = [];
  2417. }
  2418. return ids;
  2419. };
  2420. /**
  2421. * Get the DataSet to which this DataView is connected. In case there is a chain
  2422. * of multiple DataViews, the root DataSet of this chain is returned.
  2423. * @return {DataSet} dataSet
  2424. */
  2425. DataView.prototype.getDataSet = function () {
  2426. var dataSet = this;
  2427. while (dataSet instanceof DataView) {
  2428. dataSet = dataSet._data;
  2429. }
  2430. return dataSet || null;
  2431. };
  2432. /**
  2433. * Event listener. Will propagate all events from the connected data set to
  2434. * the subscribers of the DataView, but will filter the items and only trigger
  2435. * when there are changes in the filtered data set.
  2436. * @param {String} event
  2437. * @param {Object | null} params
  2438. * @param {String} senderId
  2439. * @private
  2440. */
  2441. DataView.prototype._onEvent = function (event, params, senderId) {
  2442. var i, len, id, item,
  2443. ids = params && params.items,
  2444. data = this._data,
  2445. added = [],
  2446. updated = [],
  2447. removed = [];
  2448. if (ids && data) {
  2449. switch (event) {
  2450. case 'add':
  2451. // filter the ids of the added items
  2452. for (i = 0, len = ids.length; i < len; i++) {
  2453. id = ids[i];
  2454. item = this.get(id);
  2455. if (item) {
  2456. this._ids[id] = true;
  2457. added.push(id);
  2458. }
  2459. }
  2460. break;
  2461. case 'update':
  2462. // determine the event from the views viewpoint: an updated
  2463. // item can be added, updated, or removed from this view.
  2464. for (i = 0, len = ids.length; i < len; i++) {
  2465. id = ids[i];
  2466. item = this.get(id);
  2467. if (item) {
  2468. if (this._ids[id]) {
  2469. updated.push(id);
  2470. }
  2471. else {
  2472. this._ids[id] = true;
  2473. added.push(id);
  2474. }
  2475. }
  2476. else {
  2477. if (this._ids[id]) {
  2478. delete this._ids[id];
  2479. removed.push(id);
  2480. }
  2481. else {
  2482. // nothing interesting for me :-(
  2483. }
  2484. }
  2485. }
  2486. break;
  2487. case 'remove':
  2488. // filter the ids of the removed items
  2489. for (i = 0, len = ids.length; i < len; i++) {
  2490. id = ids[i];
  2491. if (this._ids[id]) {
  2492. delete this._ids[id];
  2493. removed.push(id);
  2494. }
  2495. }
  2496. break;
  2497. }
  2498. if (added.length) {
  2499. this._trigger('add', {items: added}, senderId);
  2500. }
  2501. if (updated.length) {
  2502. this._trigger('update', {items: updated}, senderId);
  2503. }
  2504. if (removed.length) {
  2505. this._trigger('remove', {items: removed}, senderId);
  2506. }
  2507. }
  2508. };
  2509. // copy subscription functionality from DataSet
  2510. DataView.prototype.on = DataSet.prototype.on;
  2511. DataView.prototype.off = DataSet.prototype.off;
  2512. DataView.prototype._trigger = DataSet.prototype._trigger;
  2513. // TODO: make these functions deprecated (replaced with `on` and `off` since version 0.5)
  2514. DataView.prototype.subscribe = DataView.prototype.on;
  2515. DataView.prototype.unsubscribe = DataView.prototype.off;
  2516. module.exports = DataView;
  2517. /***/ },
  2518. /* 5 */
  2519. /***/ function(module, exports, __webpack_require__) {
  2520. var Emitter = __webpack_require__(45);
  2521. var DataSet = __webpack_require__(3);
  2522. var DataView = __webpack_require__(4);
  2523. var util = __webpack_require__(1);
  2524. var Point3d = __webpack_require__(9);
  2525. var Point2d = __webpack_require__(8);
  2526. var Camera = __webpack_require__(6);
  2527. var Filter = __webpack_require__(7);
  2528. var Slider = __webpack_require__(10);
  2529. var StepNumber = __webpack_require__(11);
  2530. /**
  2531. * @constructor Graph3d
  2532. * Graph3d displays data in 3d.
  2533. *
  2534. * Graph3d is developed in javascript as a Google Visualization Chart.
  2535. *
  2536. * @param {Element} container The DOM element in which the Graph3d will
  2537. * be created. Normally a div element.
  2538. * @param {DataSet | DataView | Array} [data]
  2539. * @param {Object} [options]
  2540. */
  2541. function Graph3d(container, data, options) {
  2542. if (!(this instanceof Graph3d)) {
  2543. throw new SyntaxError('Constructor must be called with the new operator');
  2544. }
  2545. // create variables and set default values
  2546. this.containerElement = container;
  2547. this.width = '400px';
  2548. this.height = '400px';
  2549. this.margin = 10; // px
  2550. this.defaultXCenter = '55%';
  2551. this.defaultYCenter = '50%';
  2552. this.xLabel = 'x';
  2553. this.yLabel = 'y';
  2554. this.zLabel = 'z';
  2555. this.filterLabel = 'time';
  2556. this.legendLabel = 'value';
  2557. this.style = Graph3d.STYLE.DOT;
  2558. this.showPerspective = true;
  2559. this.showGrid = true;
  2560. this.keepAspectRatio = true;
  2561. this.showShadow = false;
  2562. this.showGrayBottom = false; // TODO: this does not work correctly
  2563. this.showTooltip = false;
  2564. this.verticalRatio = 0.5; // 0.1 to 1.0, where 1.0 results in a 'cube'
  2565. this.animationInterval = 1000; // milliseconds
  2566. this.animationPreload = false;
  2567. this.camera = new Camera();
  2568. this.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?
  2569. this.dataTable = null; // The original data table
  2570. this.dataPoints = null; // The table with point objects
  2571. // the column indexes
  2572. this.colX = undefined;
  2573. this.colY = undefined;
  2574. this.colZ = undefined;
  2575. this.colValue = undefined;
  2576. this.colFilter = undefined;
  2577. this.xMin = 0;
  2578. this.xStep = undefined; // auto by default
  2579. this.xMax = 1;
  2580. this.yMin = 0;
  2581. this.yStep = undefined; // auto by default
  2582. this.yMax = 1;
  2583. this.zMin = 0;
  2584. this.zStep = undefined; // auto by default
  2585. this.zMax = 1;
  2586. this.valueMin = 0;
  2587. this.valueMax = 1;
  2588. this.xBarWidth = 1;
  2589. this.yBarWidth = 1;
  2590. // TODO: customize axis range
  2591. // constants
  2592. this.colorAxis = '#4D4D4D';
  2593. this.colorGrid = '#D3D3D3';
  2594. this.colorDot = '#7DC1FF';
  2595. this.colorDotBorder = '#3267D2';
  2596. // create a frame and canvas
  2597. this.create();
  2598. // apply options (also when undefined)
  2599. this.setOptions(options);
  2600. // apply data
  2601. if (data) {
  2602. this.setData(data);
  2603. }
  2604. }
  2605. // Extend Graph3d with an Emitter mixin
  2606. Emitter(Graph3d.prototype);
  2607. /**
  2608. * Calculate the scaling values, dependent on the range in x, y, and z direction
  2609. */
  2610. Graph3d.prototype._setScale = function() {
  2611. this.scale = new Point3d(1 / (this.xMax - this.xMin),
  2612. 1 / (this.yMax - this.yMin),
  2613. 1 / (this.zMax - this.zMin));
  2614. // keep aspect ration between x and y scale if desired
  2615. if (this.keepAspectRatio) {
  2616. if (this.scale.x < this.scale.y) {
  2617. //noinspection JSSuspiciousNameCombination
  2618. this.scale.y = this.scale.x;
  2619. }
  2620. else {
  2621. //noinspection JSSuspiciousNameCombination
  2622. this.scale.x = this.scale.y;
  2623. }
  2624. }
  2625. // scale the vertical axis
  2626. this.scale.z *= this.verticalRatio;
  2627. // TODO: can this be automated? verticalRatio?
  2628. // determine scale for (optional) value
  2629. this.scale.value = 1 / (this.valueMax - this.valueMin);
  2630. // position the camera arm
  2631. var xCenter = (this.xMax + this.xMin) / 2 * this.scale.x;
  2632. var yCenter = (this.yMax + this.yMin) / 2 * this.scale.y;
  2633. var zCenter = (this.zMax + this.zMin) / 2 * this.scale.z;
  2634. this.camera.setArmLocation(xCenter, yCenter, zCenter);
  2635. };
  2636. /**
  2637. * Convert a 3D location to a 2D location on screen
  2638. * http://en.wikipedia.org/wiki/3D_projection
  2639. * @param {Point3d} point3d A 3D point with parameters x, y, z
  2640. * @return {Point2d} point2d A 2D point with parameters x, y
  2641. */
  2642. Graph3d.prototype._convert3Dto2D = function(point3d) {
  2643. var translation = this._convertPointToTranslation(point3d);
  2644. return this._convertTranslationToScreen(translation);
  2645. };
  2646. /**
  2647. * Convert a 3D location its translation seen from the camera
  2648. * http://en.wikipedia.org/wiki/3D_projection
  2649. * @param {Point3d} point3d A 3D point with parameters x, y, z
  2650. * @return {Point3d} translation A 3D point with parameters x, y, z This is
  2651. * the translation of the point, seen from the
  2652. * camera
  2653. */
  2654. Graph3d.prototype._convertPointToTranslation = function(point3d) {
  2655. var ax = point3d.x * this.scale.x,
  2656. ay = point3d.y * this.scale.y,
  2657. az = point3d.z * this.scale.z,
  2658. cx = this.camera.getCameraLocation().x,
  2659. cy = this.camera.getCameraLocation().y,
  2660. cz = this.camera.getCameraLocation().z,
  2661. // calculate angles
  2662. sinTx = Math.sin(this.camera.getCameraRotation().x),
  2663. cosTx = Math.cos(this.camera.getCameraRotation().x),
  2664. sinTy = Math.sin(this.camera.getCameraRotation().y),
  2665. cosTy = Math.cos(this.camera.getCameraRotation().y),
  2666. sinTz = Math.sin(this.camera.getCameraRotation().z),
  2667. cosTz = Math.cos(this.camera.getCameraRotation().z),
  2668. // calculate translation
  2669. dx = cosTy * (sinTz * (ay - cy) + cosTz * (ax - cx)) - sinTy * (az - cz),
  2670. dy = sinTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) + cosTx * (cosTz * (ay - cy) - sinTz * (ax-cx)),
  2671. dz = cosTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) - sinTx * (cosTz * (ay - cy) - sinTz * (ax-cx));
  2672. return new Point3d(dx, dy, dz);
  2673. };
  2674. /**
  2675. * Convert a translation point to a point on the screen
  2676. * @param {Point3d} translation A 3D point with parameters x, y, z This is
  2677. * the translation of the point, seen from the
  2678. * camera
  2679. * @return {Point2d} point2d A 2D point with parameters x, y
  2680. */
  2681. Graph3d.prototype._convertTranslationToScreen = function(translation) {
  2682. var ex = this.eye.x,
  2683. ey = this.eye.y,
  2684. ez = this.eye.z,
  2685. dx = translation.x,
  2686. dy = translation.y,
  2687. dz = translation.z;
  2688. // calculate position on screen from translation
  2689. var bx;
  2690. var by;
  2691. if (this.showPerspective) {
  2692. bx = (dx - ex) * (ez / dz);
  2693. by = (dy - ey) * (ez / dz);
  2694. }
  2695. else {
  2696. bx = dx * -(ez / this.camera.getArmLength());
  2697. by = dy * -(ez / this.camera.getArmLength());
  2698. }
  2699. // shift and scale the point to the center of the screen
  2700. // use the width of the graph to scale both horizontally and vertically.
  2701. return new Point2d(
  2702. this.xcenter + bx * this.frame.canvas.clientWidth,
  2703. this.ycenter - by * this.frame.canvas.clientWidth);
  2704. };
  2705. /**
  2706. * Set the background styling for the graph
  2707. * @param {string | {fill: string, stroke: string, strokeWidth: string}} backgroundColor
  2708. */
  2709. Graph3d.prototype._setBackgroundColor = function(backgroundColor) {
  2710. var fill = 'white';
  2711. var stroke = 'gray';
  2712. var strokeWidth = 1;
  2713. if (typeof(backgroundColor) === 'string') {
  2714. fill = backgroundColor;
  2715. stroke = 'none';
  2716. strokeWidth = 0;
  2717. }
  2718. else if (typeof(backgroundColor) === 'object') {
  2719. if (backgroundColor.fill !== undefined) fill = backgroundColor.fill;
  2720. if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke;
  2721. if (backgroundColor.strokeWidth !== undefined) strokeWidth = backgroundColor.strokeWidth;
  2722. }
  2723. else if (backgroundColor === undefined) {
  2724. // use use defaults
  2725. }
  2726. else {
  2727. throw 'Unsupported type of backgroundColor';
  2728. }
  2729. this.frame.style.backgroundColor = fill;
  2730. this.frame.style.borderColor = stroke;
  2731. this.frame.style.borderWidth = strokeWidth + 'px';
  2732. this.frame.style.borderStyle = 'solid';
  2733. };
  2734. /// enumerate the available styles
  2735. Graph3d.STYLE = {
  2736. BAR: 0,
  2737. BARCOLOR: 1,
  2738. BARSIZE: 2,
  2739. DOT : 3,
  2740. DOTLINE : 4,
  2741. DOTCOLOR: 5,
  2742. DOTSIZE: 6,
  2743. GRID : 7,
  2744. LINE: 8,
  2745. SURFACE : 9
  2746. };
  2747. /**
  2748. * Retrieve the style index from given styleName
  2749. * @param {string} styleName Style name such as 'dot', 'grid', 'dot-line'
  2750. * @return {Number} styleNumber Enumeration value representing the style, or -1
  2751. * when not found
  2752. */
  2753. Graph3d.prototype._getStyleNumber = function(styleName) {
  2754. switch (styleName) {
  2755. case 'dot': return Graph3d.STYLE.DOT;
  2756. case 'dot-line': return Graph3d.STYLE.DOTLINE;
  2757. case 'dot-color': return Graph3d.STYLE.DOTCOLOR;
  2758. case 'dot-size': return Graph3d.STYLE.DOTSIZE;
  2759. case 'line': return Graph3d.STYLE.LINE;
  2760. case 'grid': return Graph3d.STYLE.GRID;
  2761. case 'surface': return Graph3d.STYLE.SURFACE;
  2762. case 'bar': return Graph3d.STYLE.BAR;
  2763. case 'bar-color': return Graph3d.STYLE.BARCOLOR;
  2764. case 'bar-size': return Graph3d.STYLE.BARSIZE;
  2765. }
  2766. return -1;
  2767. };
  2768. /**
  2769. * Determine the indexes of the data columns, based on the given style and data
  2770. * @param {DataSet} data
  2771. * @param {Number} style
  2772. */
  2773. Graph3d.prototype._determineColumnIndexes = function(data, style) {
  2774. if (this.style === Graph3d.STYLE.DOT ||
  2775. this.style === Graph3d.STYLE.DOTLINE ||
  2776. this.style === Graph3d.STYLE.LINE ||
  2777. this.style === Graph3d.STYLE.GRID ||
  2778. this.style === Graph3d.STYLE.SURFACE ||
  2779. this.style === Graph3d.STYLE.BAR) {
  2780. // 3 columns expected, and optionally a 4th with filter values
  2781. this.colX = 0;
  2782. this.colY = 1;
  2783. this.colZ = 2;
  2784. this.colValue = undefined;
  2785. if (data.getNumberOfColumns() > 3) {
  2786. this.colFilter = 3;
  2787. }
  2788. }
  2789. else if (this.style === Graph3d.STYLE.DOTCOLOR ||
  2790. this.style === Graph3d.STYLE.DOTSIZE ||
  2791. this.style === Graph3d.STYLE.BARCOLOR ||
  2792. this.style === Graph3d.STYLE.BARSIZE) {
  2793. // 4 columns expected, and optionally a 5th with filter values
  2794. this.colX = 0;
  2795. this.colY = 1;
  2796. this.colZ = 2;
  2797. this.colValue = 3;
  2798. if (data.getNumberOfColumns() > 4) {
  2799. this.colFilter = 4;
  2800. }
  2801. }
  2802. else {
  2803. throw 'Unknown style "' + this.style + '"';
  2804. }
  2805. };
  2806. Graph3d.prototype.getNumberOfRows = function(data) {
  2807. return data.length;
  2808. }
  2809. Graph3d.prototype.getNumberOfColumns = function(data) {
  2810. var counter = 0;
  2811. for (var column in data[0]) {
  2812. if (data[0].hasOwnProperty(column)) {
  2813. counter++;
  2814. }
  2815. }
  2816. return counter;
  2817. }
  2818. Graph3d.prototype.getDistinctValues = function(data, column) {
  2819. var distinctValues = [];
  2820. for (var i = 0; i < data.length; i++) {
  2821. if (distinctValues.indexOf(data[i][column]) == -1) {
  2822. distinctValues.push(data[i][column]);
  2823. }
  2824. }
  2825. return distinctValues;
  2826. }
  2827. Graph3d.prototype.getColumnRange = function(data,column) {
  2828. var minMax = {min:data[0][column],max:data[0][column]};
  2829. for (var i = 0; i < data.length; i++) {
  2830. if (minMax.min > data[i][column]) { minMax.min = data[i][column]; }
  2831. if (minMax.max < data[i][column]) { minMax.max = data[i][column]; }
  2832. }
  2833. return minMax;
  2834. };
  2835. /**
  2836. * Initialize the data from the data table. Calculate minimum and maximum values
  2837. * and column index values
  2838. * @param {Array | DataSet | DataView} rawData The data containing the items for the Graph.
  2839. * @param {Number} style Style Number
  2840. */
  2841. Graph3d.prototype._dataInitialize = function (rawData, style) {
  2842. var me = this;
  2843. // unsubscribe from the dataTable
  2844. if (this.dataSet) {
  2845. this.dataSet.off('*', this._onChange);
  2846. }
  2847. if (rawData === undefined)
  2848. return;
  2849. if (Array.isArray(rawData)) {
  2850. rawData = new DataSet(rawData);
  2851. }
  2852. var data;
  2853. if (rawData instanceof DataSet || rawData instanceof DataView) {
  2854. data = rawData.get();
  2855. }
  2856. else {
  2857. throw new Error('Array, DataSet, or DataView expected');
  2858. }
  2859. if (data.length == 0)
  2860. return;
  2861. this.dataSet = rawData;
  2862. this.dataTable = data;
  2863. // subscribe to changes in the dataset
  2864. this._onChange = function () {
  2865. me.setData(me.dataSet);
  2866. };
  2867. this.dataSet.on('*', this._onChange);
  2868. // _determineColumnIndexes
  2869. // getNumberOfRows (points)
  2870. // getNumberOfColumns (x,y,z,v,t,t1,t2...)
  2871. // getDistinctValues (unique values?)
  2872. // getColumnRange
  2873. // determine the location of x,y,z,value,filter columns
  2874. this.colX = 'x';
  2875. this.colY = 'y';
  2876. this.colZ = 'z';
  2877. this.colValue = 'style';
  2878. this.colFilter = 'filter';
  2879. // check if a filter column is provided
  2880. if (data[0].hasOwnProperty('filter')) {
  2881. if (this.dataFilter === undefined) {
  2882. this.dataFilter = new Filter(rawData, this.colFilter, this);
  2883. this.dataFilter.setOnLoadCallback(function() {me.redraw();});
  2884. }
  2885. }
  2886. var withBars = this.style == Graph3d.STYLE.BAR ||
  2887. this.style == Graph3d.STYLE.BARCOLOR ||
  2888. this.style == Graph3d.STYLE.BARSIZE;
  2889. // determine barWidth from data
  2890. if (withBars) {
  2891. if (this.defaultXBarWidth !== undefined) {
  2892. this.xBarWidth = this.defaultXBarWidth;
  2893. }
  2894. else {
  2895. var dataX = this.getDistinctValues(data,this.colX);
  2896. this.xBarWidth = (dataX[1] - dataX[0]) || 1;
  2897. }
  2898. if (this.defaultYBarWidth !== undefined) {
  2899. this.yBarWidth = this.defaultYBarWidth;
  2900. }
  2901. else {
  2902. var dataY = this.getDistinctValues(data,this.colY);
  2903. this.yBarWidth = (dataY[1] - dataY[0]) || 1;
  2904. }
  2905. }
  2906. // calculate minimums and maximums
  2907. var xRange = this.getColumnRange(data,this.colX);
  2908. if (withBars) {
  2909. xRange.min -= this.xBarWidth / 2;
  2910. xRange.max += this.xBarWidth / 2;
  2911. }
  2912. this.xMin = (this.defaultXMin !== undefined) ? this.defaultXMin : xRange.min;
  2913. this.xMax = (this.defaultXMax !== undefined) ? this.defaultXMax : xRange.max;
  2914. if (this.xMax <= this.xMin) this.xMax = this.xMin + 1;
  2915. this.xStep = (this.defaultXStep !== undefined) ? this.defaultXStep : (this.xMax-this.xMin)/5;
  2916. var yRange = this.getColumnRange(data,this.colY);
  2917. if (withBars) {
  2918. yRange.min -= this.yBarWidth / 2;
  2919. yRange.max += this.yBarWidth / 2;
  2920. }
  2921. this.yMin = (this.defaultYMin !== undefined) ? this.defaultYMin : yRange.min;
  2922. this.yMax = (this.defaultYMax !== undefined) ? this.defaultYMax : yRange.max;
  2923. if (this.yMax <= this.yMin) this.yMax = this.yMin + 1;
  2924. this.yStep = (this.defaultYStep !== undefined) ? this.defaultYStep : (this.yMax-this.yMin)/5;
  2925. var zRange = this.getColumnRange(data,this.colZ);
  2926. this.zMin = (this.defaultZMin !== undefined) ? this.defaultZMin : zRange.min;
  2927. this.zMax = (this.defaultZMax !== undefined) ? this.defaultZMax : zRange.max;
  2928. if (this.zMax <= this.zMin) this.zMax = this.zMin + 1;
  2929. this.zStep = (this.defaultZStep !== undefined) ? this.defaultZStep : (this.zMax-this.zMin)/5;
  2930. if (this.colValue !== undefined) {
  2931. var valueRange = this.getColumnRange(data,this.colValue);
  2932. this.valueMin = (this.defaultValueMin !== undefined) ? this.defaultValueMin : valueRange.min;
  2933. this.valueMax = (this.defaultValueMax !== undefined) ? this.defaultValueMax : valueRange.max;
  2934. if (this.valueMax <= this.valueMin) this.valueMax = this.valueMin + 1;
  2935. }
  2936. // set the scale dependent on the ranges.
  2937. this._setScale();
  2938. };
  2939. /**
  2940. * Filter the data based on the current filter
  2941. * @param {Array} data
  2942. * @return {Array} dataPoints Array with point objects which can be drawn on screen
  2943. */
  2944. Graph3d.prototype._getDataPoints = function (data) {
  2945. // TODO: store the created matrix dataPoints in the filters instead of reloading each time
  2946. var x, y, i, z, obj, point;
  2947. var dataPoints = [];
  2948. if (this.style === Graph3d.STYLE.GRID ||
  2949. this.style === Graph3d.STYLE.SURFACE) {
  2950. // copy all values from the google data table to a matrix
  2951. // the provided values are supposed to form a grid of (x,y) positions
  2952. // create two lists with all present x and y values
  2953. var dataX = [];
  2954. var dataY = [];
  2955. for (i = 0; i < this.getNumberOfRows(data); i++) {
  2956. x = data[i][this.colX] || 0;
  2957. y = data[i][this.colY] || 0;
  2958. if (dataX.indexOf(x) === -1) {
  2959. dataX.push(x);
  2960. }
  2961. if (dataY.indexOf(y) === -1) {
  2962. dataY.push(y);
  2963. }
  2964. }
  2965. function sortNumber(a, b) {
  2966. return a - b;
  2967. }
  2968. dataX.sort(sortNumber);
  2969. dataY.sort(sortNumber);
  2970. // create a grid, a 2d matrix, with all values.
  2971. var dataMatrix = []; // temporary data matrix
  2972. for (i = 0; i < data.length; i++) {
  2973. x = data[i][this.colX] || 0;
  2974. y = data[i][this.colY] || 0;
  2975. z = data[i][this.colZ] || 0;
  2976. var xIndex = dataX.indexOf(x); // TODO: implement Array().indexOf() for Internet Explorer
  2977. var yIndex = dataY.indexOf(y);
  2978. if (dataMatrix[xIndex] === undefined) {
  2979. dataMatrix[xIndex] = [];
  2980. }
  2981. var point3d = new Point3d();
  2982. point3d.x = x;
  2983. point3d.y = y;
  2984. point3d.z = z;
  2985. obj = {};
  2986. obj.point = point3d;
  2987. obj.trans = undefined;
  2988. obj.screen = undefined;
  2989. obj.bottom = new Point3d(x, y, this.zMin);
  2990. dataMatrix[xIndex][yIndex] = obj;
  2991. dataPoints.push(obj);
  2992. }
  2993. // fill in the pointers to the neighbors.
  2994. for (x = 0; x < dataMatrix.length; x++) {
  2995. for (y = 0; y < dataMatrix[x].length; y++) {
  2996. if (dataMatrix[x][y]) {
  2997. dataMatrix[x][y].pointRight = (x < dataMatrix.length-1) ? dataMatrix[x+1][y] : undefined;
  2998. dataMatrix[x][y].pointTop = (y < dataMatrix[x].length-1) ? dataMatrix[x][y+1] : undefined;
  2999. dataMatrix[x][y].pointCross =
  3000. (x < dataMatrix.length-1 && y < dataMatrix[x].length-1) ?
  3001. dataMatrix[x+1][y+1] :
  3002. undefined;
  3003. }
  3004. }
  3005. }
  3006. }
  3007. else { // 'dot', 'dot-line', etc.
  3008. // copy all values from the google data table to a list with Point3d objects
  3009. for (i = 0; i < data.length; i++) {
  3010. point = new Point3d();
  3011. point.x = data[i][this.colX] || 0;
  3012. point.y = data[i][this.colY] || 0;
  3013. point.z = data[i][this.colZ] || 0;
  3014. if (this.colValue !== undefined) {
  3015. point.value = data[i][this.colValue] || 0;
  3016. }
  3017. obj = {};
  3018. obj.point = point;
  3019. obj.bottom = new Point3d(point.x, point.y, this.zMin);
  3020. obj.trans = undefined;
  3021. obj.screen = undefined;
  3022. dataPoints.push(obj);
  3023. }
  3024. }
  3025. return dataPoints;
  3026. };
  3027. /**
  3028. * Create the main frame for the Graph3d.
  3029. * This function is executed once when a Graph3d object is created. The frame
  3030. * contains a canvas, and this canvas contains all objects like the axis and
  3031. * nodes.
  3032. */
  3033. Graph3d.prototype.create = function () {
  3034. // remove all elements from the container element.
  3035. while (this.containerElement.hasChildNodes()) {
  3036. this.containerElement.removeChild(this.containerElement.firstChild);
  3037. }
  3038. this.frame = document.createElement('div');
  3039. this.frame.style.position = 'relative';
  3040. this.frame.style.overflow = 'hidden';
  3041. // create the graph canvas (HTML canvas element)
  3042. this.frame.canvas = document.createElement( 'canvas' );
  3043. this.frame.canvas.style.position = 'relative';
  3044. this.frame.appendChild(this.frame.canvas);
  3045. //if (!this.frame.canvas.getContext) {
  3046. {
  3047. var noCanvas = document.createElement( 'DIV' );
  3048. noCanvas.style.color = 'red';
  3049. noCanvas.style.fontWeight = 'bold' ;
  3050. noCanvas.style.padding = '10px';
  3051. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  3052. this.frame.canvas.appendChild(noCanvas);
  3053. }
  3054. this.frame.filter = document.createElement( 'div' );
  3055. this.frame.filter.style.position = 'absolute';
  3056. this.frame.filter.style.bottom = '0px';
  3057. this.frame.filter.style.left = '0px';
  3058. this.frame.filter.style.width = '100%';
  3059. this.frame.appendChild(this.frame.filter);
  3060. // add event listeners to handle moving and zooming the contents
  3061. var me = this;
  3062. var onmousedown = function (event) {me._onMouseDown(event);};
  3063. var ontouchstart = function (event) {me._onTouchStart(event);};
  3064. var onmousewheel = function (event) {me._onWheel(event);};
  3065. var ontooltip = function (event) {me._onTooltip(event);};
  3066. // TODO: these events are never cleaned up... can give a 'memory leakage'
  3067. util.addEventListener(this.frame.canvas, 'keydown', onkeydown);
  3068. util.addEventListener(this.frame.canvas, 'mousedown', onmousedown);
  3069. util.addEventListener(this.frame.canvas, 'touchstart', ontouchstart);
  3070. util.addEventListener(this.frame.canvas, 'mousewheel', onmousewheel);
  3071. util.addEventListener(this.frame.canvas, 'mousemove', ontooltip);
  3072. // add the new graph to the container element
  3073. this.containerElement.appendChild(this.frame);
  3074. };
  3075. /**
  3076. * Set a new size for the graph
  3077. * @param {string} width Width in pixels or percentage (for example '800px'
  3078. * or '50%')
  3079. * @param {string} height Height in pixels or percentage (for example '400px'
  3080. * or '30%')
  3081. */
  3082. Graph3d.prototype.setSize = function(width, height) {
  3083. this.frame.style.width = width;
  3084. this.frame.style.height = height;
  3085. this._resizeCanvas();
  3086. };
  3087. /**
  3088. * Resize the canvas to the current size of the frame
  3089. */
  3090. Graph3d.prototype._resizeCanvas = function() {
  3091. this.frame.canvas.style.width = '100%';
  3092. this.frame.canvas.style.height = '100%';
  3093. this.frame.canvas.width = this.frame.canvas.clientWidth;
  3094. this.frame.canvas.height = this.frame.canvas.clientHeight;
  3095. // adjust with for margin
  3096. this.frame.filter.style.width = (this.frame.canvas.clientWidth - 2 * 10) + 'px';
  3097. };
  3098. /**
  3099. * Start animation
  3100. */
  3101. Graph3d.prototype.animationStart = function() {
  3102. if (!this.frame.filter || !this.frame.filter.slider)
  3103. throw 'No animation available';
  3104. this.frame.filter.slider.play();
  3105. };
  3106. /**
  3107. * Stop animation
  3108. */
  3109. Graph3d.prototype.animationStop = function() {
  3110. if (!this.frame.filter || !this.frame.filter.slider) return;
  3111. this.frame.filter.slider.stop();
  3112. };
  3113. /**
  3114. * Resize the center position based on the current values in this.defaultXCenter
  3115. * and this.defaultYCenter (which are strings with a percentage or a value
  3116. * in pixels). The center positions are the variables this.xCenter
  3117. * and this.yCenter
  3118. */
  3119. Graph3d.prototype._resizeCenter = function() {
  3120. // calculate the horizontal center position
  3121. if (this.defaultXCenter.charAt(this.defaultXCenter.length-1) === '%') {
  3122. this.xcenter =
  3123. parseFloat(this.defaultXCenter) / 100 *
  3124. this.frame.canvas.clientWidth;
  3125. }
  3126. else {
  3127. this.xcenter = parseFloat(this.defaultXCenter); // supposed to be in px
  3128. }
  3129. // calculate the vertical center position
  3130. if (this.defaultYCenter.charAt(this.defaultYCenter.length-1) === '%') {
  3131. this.ycenter =
  3132. parseFloat(this.defaultYCenter) / 100 *
  3133. (this.frame.canvas.clientHeight - this.frame.filter.clientHeight);
  3134. }
  3135. else {
  3136. this.ycenter = parseFloat(this.defaultYCenter); // supposed to be in px
  3137. }
  3138. };
  3139. /**
  3140. * Set the rotation and distance of the camera
  3141. * @param {Object} pos An object with the camera position. The object
  3142. * contains three parameters:
  3143. * - horizontal {Number}
  3144. * The horizontal rotation, between 0 and 2*PI.
  3145. * Optional, can be left undefined.
  3146. * - vertical {Number}
  3147. * The vertical rotation, between 0 and 0.5*PI
  3148. * if vertical=0.5*PI, the graph is shown from the
  3149. * top. Optional, can be left undefined.
  3150. * - distance {Number}
  3151. * The (normalized) distance of the camera to the
  3152. * center of the graph, a value between 0.71 and 5.0.
  3153. * Optional, can be left undefined.
  3154. */
  3155. Graph3d.prototype.setCameraPosition = function(pos) {
  3156. if (pos === undefined) {
  3157. return;
  3158. }
  3159. if (pos.horizontal !== undefined && pos.vertical !== undefined) {
  3160. this.camera.setArmRotation(pos.horizontal, pos.vertical);
  3161. }
  3162. if (pos.distance !== undefined) {
  3163. this.camera.setArmLength(pos.distance);
  3164. }
  3165. this.redraw();
  3166. };
  3167. /**
  3168. * Retrieve the current camera rotation
  3169. * @return {object} An object with parameters horizontal, vertical, and
  3170. * distance
  3171. */
  3172. Graph3d.prototype.getCameraPosition = function() {
  3173. var pos = this.camera.getArmRotation();
  3174. pos.distance = this.camera.getArmLength();
  3175. return pos;
  3176. };
  3177. /**
  3178. * Load data into the 3D Graph
  3179. */
  3180. Graph3d.prototype._readData = function(data) {
  3181. // read the data
  3182. this._dataInitialize(data, this.style);
  3183. if (this.dataFilter) {
  3184. // apply filtering
  3185. this.dataPoints = this.dataFilter._getDataPoints();
  3186. }
  3187. else {
  3188. // no filtering. load all data
  3189. this.dataPoints = this._getDataPoints(this.dataTable);
  3190. }
  3191. // draw the filter
  3192. this._redrawFilter();
  3193. };
  3194. /**
  3195. * Replace the dataset of the Graph3d
  3196. * @param {Array | DataSet | DataView} data
  3197. */
  3198. Graph3d.prototype.setData = function (data) {
  3199. this._readData(data);
  3200. this.redraw();
  3201. // start animation when option is true
  3202. if (this.animationAutoStart && this.dataFilter) {
  3203. this.animationStart();
  3204. }
  3205. };
  3206. /**
  3207. * Update the options. Options will be merged with current options
  3208. * @param {Object} options
  3209. */
  3210. Graph3d.prototype.setOptions = function (options) {
  3211. var cameraPosition = undefined;
  3212. this.animationStop();
  3213. if (options !== undefined) {
  3214. // retrieve parameter values
  3215. if (options.width !== undefined) this.width = options.width;
  3216. if (options.height !== undefined) this.height = options.height;
  3217. if (options.xCenter !== undefined) this.defaultXCenter = options.xCenter;
  3218. if (options.yCenter !== undefined) this.defaultYCenter = options.yCenter;
  3219. if (options.filterLabel !== undefined) this.filterLabel = options.filterLabel;
  3220. if (options.legendLabel !== undefined) this.legendLabel = options.legendLabel;
  3221. if (options.xLabel !== undefined) this.xLabel = options.xLabel;
  3222. if (options.yLabel !== undefined) this.yLabel = options.yLabel;
  3223. if (options.zLabel !== undefined) this.zLabel = options.zLabel;
  3224. if (options.style !== undefined) {
  3225. var styleNumber = this._getStyleNumber(options.style);
  3226. if (styleNumber !== -1) {
  3227. this.style = styleNumber;
  3228. }
  3229. }
  3230. if (options.showGrid !== undefined) this.showGrid = options.showGrid;
  3231. if (options.showPerspective !== undefined) this.showPerspective = options.showPerspective;
  3232. if (options.showShadow !== undefined) this.showShadow = options.showShadow;
  3233. if (options.tooltip !== undefined) this.showTooltip = options.tooltip;
  3234. if (options.showAnimationControls !== undefined) this.showAnimationControls = options.showAnimationControls;
  3235. if (options.keepAspectRatio !== undefined) this.keepAspectRatio = options.keepAspectRatio;
  3236. if (options.verticalRatio !== undefined) this.verticalRatio = options.verticalRatio;
  3237. if (options.animationInterval !== undefined) this.animationInterval = options.animationInterval;
  3238. if (options.animationPreload !== undefined) this.animationPreload = options.animationPreload;
  3239. if (options.animationAutoStart !== undefined)this.animationAutoStart = options.animationAutoStart;
  3240. if (options.xBarWidth !== undefined) this.defaultXBarWidth = options.xBarWidth;
  3241. if (options.yBarWidth !== undefined) this.defaultYBarWidth = options.yBarWidth;
  3242. if (options.xMin !== undefined) this.defaultXMin = options.xMin;
  3243. if (options.xStep !== undefined) this.defaultXStep = options.xStep;
  3244. if (options.xMax !== undefined) this.defaultXMax = options.xMax;
  3245. if (options.yMin !== undefined) this.defaultYMin = options.yMin;
  3246. if (options.yStep !== undefined) this.defaultYStep = options.yStep;
  3247. if (options.yMax !== undefined) this.defaultYMax = options.yMax;
  3248. if (options.zMin !== undefined) this.defaultZMin = options.zMin;
  3249. if (options.zStep !== undefined) this.defaultZStep = options.zStep;
  3250. if (options.zMax !== undefined) this.defaultZMax = options.zMax;
  3251. if (options.valueMin !== undefined) this.defaultValueMin = options.valueMin;
  3252. if (options.valueMax !== undefined) this.defaultValueMax = options.valueMax;
  3253. if (options.cameraPosition !== undefined) cameraPosition = options.cameraPosition;
  3254. if (cameraPosition !== undefined) {
  3255. this.camera.setArmRotation(cameraPosition.horizontal, cameraPosition.vertical);
  3256. this.camera.setArmLength(cameraPosition.distance);
  3257. }
  3258. else {
  3259. this.camera.setArmRotation(1.0, 0.5);
  3260. this.camera.setArmLength(1.7);
  3261. }
  3262. }
  3263. this._setBackgroundColor(options && options.backgroundColor);
  3264. this.setSize(this.width, this.height);
  3265. // re-load the data
  3266. if (this.dataTable) {
  3267. this.setData(this.dataTable);
  3268. }
  3269. // start animation when option is true
  3270. if (this.animationAutoStart && this.dataFilter) {
  3271. this.animationStart();
  3272. }
  3273. };
  3274. /**
  3275. * Redraw the Graph.
  3276. */
  3277. Graph3d.prototype.redraw = function() {
  3278. if (this.dataPoints === undefined) {
  3279. throw 'Error: graph data not initialized';
  3280. }
  3281. this._resizeCanvas();
  3282. this._resizeCenter();
  3283. this._redrawSlider();
  3284. this._redrawClear();
  3285. this._redrawAxis();
  3286. if (this.style === Graph3d.STYLE.GRID ||
  3287. this.style === Graph3d.STYLE.SURFACE) {
  3288. this._redrawDataGrid();
  3289. }
  3290. else if (this.style === Graph3d.STYLE.LINE) {
  3291. this._redrawDataLine();
  3292. }
  3293. else if (this.style === Graph3d.STYLE.BAR ||
  3294. this.style === Graph3d.STYLE.BARCOLOR ||
  3295. this.style === Graph3d.STYLE.BARSIZE) {
  3296. this._redrawDataBar();
  3297. }
  3298. else {
  3299. // style is DOT, DOTLINE, DOTCOLOR, DOTSIZE
  3300. this._redrawDataDot();
  3301. }
  3302. this._redrawInfo();
  3303. this._redrawLegend();
  3304. };
  3305. /**
  3306. * Clear the canvas before redrawing
  3307. */
  3308. Graph3d.prototype._redrawClear = function() {
  3309. var canvas = this.frame.canvas;
  3310. var ctx = canvas.getContext('2d');
  3311. ctx.clearRect(0, 0, canvas.width, canvas.height);
  3312. };
  3313. /**
  3314. * Redraw the legend showing the colors
  3315. */
  3316. Graph3d.prototype._redrawLegend = function() {
  3317. var y;
  3318. if (this.style === Graph3d.STYLE.DOTCOLOR ||
  3319. this.style === Graph3d.STYLE.DOTSIZE) {
  3320. var dotSize = this.frame.clientWidth * 0.02;
  3321. var widthMin, widthMax;
  3322. if (this.style === Graph3d.STYLE.DOTSIZE) {
  3323. widthMin = dotSize / 2; // px
  3324. widthMax = dotSize / 2 + dotSize * 2; // Todo: put this in one function
  3325. }
  3326. else {
  3327. widthMin = 20; // px
  3328. widthMax = 20; // px
  3329. }
  3330. var height = Math.max(this.frame.clientHeight * 0.25, 100);
  3331. var top = this.margin;
  3332. var right = this.frame.clientWidth - this.margin;
  3333. var left = right - widthMax;
  3334. var bottom = top + height;
  3335. }
  3336. var canvas = this.frame.canvas;
  3337. var ctx = canvas.getContext('2d');
  3338. ctx.lineWidth = 1;
  3339. ctx.font = '14px arial'; // TODO: put in options
  3340. if (this.style === Graph3d.STYLE.DOTCOLOR) {
  3341. // draw the color bar
  3342. var ymin = 0;
  3343. var ymax = height; // Todo: make height customizable
  3344. for (y = ymin; y < ymax; y++) {
  3345. var f = (y - ymin) / (ymax - ymin);
  3346. //var width = (dotSize / 2 + (1-f) * dotSize * 2); // Todo: put this in one function
  3347. var hue = f * 240;
  3348. var color = this._hsv2rgb(hue, 1, 1);
  3349. ctx.strokeStyle = color;
  3350. ctx.beginPath();
  3351. ctx.moveTo(left, top + y);
  3352. ctx.lineTo(right, top + y);
  3353. ctx.stroke();
  3354. }
  3355. ctx.strokeStyle = this.colorAxis;
  3356. ctx.strokeRect(left, top, widthMax, height);
  3357. }
  3358. if (this.style === Graph3d.STYLE.DOTSIZE) {
  3359. // draw border around color bar
  3360. ctx.strokeStyle = this.colorAxis;
  3361. ctx.fillStyle = this.colorDot;
  3362. ctx.beginPath();
  3363. ctx.moveTo(left, top);
  3364. ctx.lineTo(right, top);
  3365. ctx.lineTo(right - widthMax + widthMin, bottom);
  3366. ctx.lineTo(left, bottom);
  3367. ctx.closePath();
  3368. ctx.fill();
  3369. ctx.stroke();
  3370. }
  3371. if (this.style === Graph3d.STYLE.DOTCOLOR ||
  3372. this.style === Graph3d.STYLE.DOTSIZE) {
  3373. // print values along the color bar
  3374. var gridLineLen = 5; // px
  3375. var step = new StepNumber(this.valueMin, this.valueMax, (this.valueMax-this.valueMin)/5, true);
  3376. step.start();
  3377. if (step.getCurrent() < this.valueMin) {
  3378. step.next();
  3379. }
  3380. while (!step.end()) {
  3381. y = bottom - (step.getCurrent() - this.valueMin) / (this.valueMax - this.valueMin) * height;
  3382. ctx.beginPath();
  3383. ctx.moveTo(left - gridLineLen, y);
  3384. ctx.lineTo(left, y);
  3385. ctx.stroke();
  3386. ctx.textAlign = 'right';
  3387. ctx.textBaseline = 'middle';
  3388. ctx.fillStyle = this.colorAxis;
  3389. ctx.fillText(step.getCurrent(), left - 2 * gridLineLen, y);
  3390. step.next();
  3391. }
  3392. ctx.textAlign = 'right';
  3393. ctx.textBaseline = 'top';
  3394. var label = this.legendLabel;
  3395. ctx.fillText(label, right, bottom + this.margin);
  3396. }
  3397. };
  3398. /**
  3399. * Redraw the filter
  3400. */
  3401. Graph3d.prototype._redrawFilter = function() {
  3402. this.frame.filter.innerHTML = '';
  3403. if (this.dataFilter) {
  3404. var options = {
  3405. 'visible': this.showAnimationControls
  3406. };
  3407. var slider = new Slider(this.frame.filter, options);
  3408. this.frame.filter.slider = slider;
  3409. // TODO: css here is not nice here...
  3410. this.frame.filter.style.padding = '10px';
  3411. //this.frame.filter.style.backgroundColor = '#EFEFEF';
  3412. slider.setValues(this.dataFilter.values);
  3413. slider.setPlayInterval(this.animationInterval);
  3414. // create an event handler
  3415. var me = this;
  3416. var onchange = function () {
  3417. var index = slider.getIndex();
  3418. me.dataFilter.selectValue(index);
  3419. me.dataPoints = me.dataFilter._getDataPoints();
  3420. me.redraw();
  3421. };
  3422. slider.setOnChangeCallback(onchange);
  3423. }
  3424. else {
  3425. this.frame.filter.slider = undefined;
  3426. }
  3427. };
  3428. /**
  3429. * Redraw the slider
  3430. */
  3431. Graph3d.prototype._redrawSlider = function() {
  3432. if ( this.frame.filter.slider !== undefined) {
  3433. this.frame.filter.slider.redraw();
  3434. }
  3435. };
  3436. /**
  3437. * Redraw common information
  3438. */
  3439. Graph3d.prototype._redrawInfo = function() {
  3440. if (this.dataFilter) {
  3441. var canvas = this.frame.canvas;
  3442. var ctx = canvas.getContext('2d');
  3443. ctx.font = '14px arial'; // TODO: put in options
  3444. ctx.lineStyle = 'gray';
  3445. ctx.fillStyle = 'gray';
  3446. ctx.textAlign = 'left';
  3447. ctx.textBaseline = 'top';
  3448. var x = this.margin;
  3449. var y = this.margin;
  3450. ctx.fillText(this.dataFilter.getLabel() + ': ' + this.dataFilter.getSelectedValue(), x, y);
  3451. }
  3452. };
  3453. /**
  3454. * Redraw the axis
  3455. */
  3456. Graph3d.prototype._redrawAxis = function() {
  3457. var canvas = this.frame.canvas,
  3458. ctx = canvas.getContext('2d'),
  3459. from, to, step, prettyStep,
  3460. text, xText, yText, zText,
  3461. offset, xOffset, yOffset,
  3462. xMin2d, xMax2d;
  3463. // TODO: get the actual rendered style of the containerElement
  3464. //ctx.font = this.containerElement.style.font;
  3465. ctx.font = 24 / this.camera.getArmLength() + 'px arial';
  3466. // calculate the length for the short grid lines
  3467. var gridLenX = 0.025 / this.scale.x;
  3468. var gridLenY = 0.025 / this.scale.y;
  3469. var textMargin = 5 / this.camera.getArmLength(); // px
  3470. var armAngle = this.camera.getArmRotation().horizontal;
  3471. // draw x-grid lines
  3472. ctx.lineWidth = 1;
  3473. prettyStep = (this.defaultXStep === undefined);
  3474. step = new StepNumber(this.xMin, this.xMax, this.xStep, prettyStep);
  3475. step.start();
  3476. if (step.getCurrent() < this.xMin) {
  3477. step.next();
  3478. }
  3479. while (!step.end()) {
  3480. var x = step.getCurrent();
  3481. if (this.showGrid) {
  3482. from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin));
  3483. to = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin));
  3484. ctx.strokeStyle = this.colorGrid;
  3485. ctx.beginPath();
  3486. ctx.moveTo(from.x, from.y);
  3487. ctx.lineTo(to.x, to.y);
  3488. ctx.stroke();
  3489. }
  3490. else {
  3491. from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin));
  3492. to = this._convert3Dto2D(new Point3d(x, this.yMin+gridLenX, this.zMin));
  3493. ctx.strokeStyle = this.colorAxis;
  3494. ctx.beginPath();
  3495. ctx.moveTo(from.x, from.y);
  3496. ctx.lineTo(to.x, to.y);
  3497. ctx.stroke();
  3498. from = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin));
  3499. to = this._convert3Dto2D(new Point3d(x, this.yMax-gridLenX, this.zMin));
  3500. ctx.strokeStyle = this.colorAxis;
  3501. ctx.beginPath();
  3502. ctx.moveTo(from.x, from.y);
  3503. ctx.lineTo(to.x, to.y);
  3504. ctx.stroke();
  3505. }
  3506. yText = (Math.cos(armAngle) > 0) ? this.yMin : this.yMax;
  3507. text = this._convert3Dto2D(new Point3d(x, yText, this.zMin));
  3508. if (Math.cos(armAngle * 2) > 0) {
  3509. ctx.textAlign = 'center';
  3510. ctx.textBaseline = 'top';
  3511. text.y += textMargin;
  3512. }
  3513. else if (Math.sin(armAngle * 2) < 0){
  3514. ctx.textAlign = 'right';
  3515. ctx.textBaseline = 'middle';
  3516. }
  3517. else {
  3518. ctx.textAlign = 'left';
  3519. ctx.textBaseline = 'middle';
  3520. }
  3521. ctx.fillStyle = this.colorAxis;
  3522. ctx.fillText(' ' + step.getCurrent() + ' ', text.x, text.y);
  3523. step.next();
  3524. }
  3525. // draw y-grid lines
  3526. ctx.lineWidth = 1;
  3527. prettyStep = (this.defaultYStep === undefined);
  3528. step = new StepNumber(this.yMin, this.yMax, this.yStep, prettyStep);
  3529. step.start();
  3530. if (step.getCurrent() < this.yMin) {
  3531. step.next();
  3532. }
  3533. while (!step.end()) {
  3534. if (this.showGrid) {
  3535. from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin));
  3536. to = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin));
  3537. ctx.strokeStyle = this.colorGrid;
  3538. ctx.beginPath();
  3539. ctx.moveTo(from.x, from.y);
  3540. ctx.lineTo(to.x, to.y);
  3541. ctx.stroke();
  3542. }
  3543. else {
  3544. from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin));
  3545. to = this._convert3Dto2D(new Point3d(this.xMin+gridLenY, step.getCurrent(), this.zMin));
  3546. ctx.strokeStyle = this.colorAxis;
  3547. ctx.beginPath();
  3548. ctx.moveTo(from.x, from.y);
  3549. ctx.lineTo(to.x, to.y);
  3550. ctx.stroke();
  3551. from = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin));
  3552. to = this._convert3Dto2D(new Point3d(this.xMax-gridLenY, step.getCurrent(), this.zMin));
  3553. ctx.strokeStyle = this.colorAxis;
  3554. ctx.beginPath();
  3555. ctx.moveTo(from.x, from.y);
  3556. ctx.lineTo(to.x, to.y);
  3557. ctx.stroke();
  3558. }
  3559. xText = (Math.sin(armAngle ) > 0) ? this.xMin : this.xMax;
  3560. text = this._convert3Dto2D(new Point3d(xText, step.getCurrent(), this.zMin));
  3561. if (Math.cos(armAngle * 2) < 0) {
  3562. ctx.textAlign = 'center';
  3563. ctx.textBaseline = 'top';
  3564. text.y += textMargin;
  3565. }
  3566. else if (Math.sin(armAngle * 2) > 0){
  3567. ctx.textAlign = 'right';
  3568. ctx.textBaseline = 'middle';
  3569. }
  3570. else {
  3571. ctx.textAlign = 'left';
  3572. ctx.textBaseline = 'middle';
  3573. }
  3574. ctx.fillStyle = this.colorAxis;
  3575. ctx.fillText(' ' + step.getCurrent() + ' ', text.x, text.y);
  3576. step.next();
  3577. }
  3578. // draw z-grid lines and axis
  3579. ctx.lineWidth = 1;
  3580. prettyStep = (this.defaultZStep === undefined);
  3581. step = new StepNumber(this.zMin, this.zMax, this.zStep, prettyStep);
  3582. step.start();
  3583. if (step.getCurrent() < this.zMin) {
  3584. step.next();
  3585. }
  3586. xText = (Math.cos(armAngle ) > 0) ? this.xMin : this.xMax;
  3587. yText = (Math.sin(armAngle ) < 0) ? this.yMin : this.yMax;
  3588. while (!step.end()) {
  3589. // TODO: make z-grid lines really 3d?
  3590. from = this._convert3Dto2D(new Point3d(xText, yText, step.getCurrent()));
  3591. ctx.strokeStyle = this.colorAxis;
  3592. ctx.beginPath();
  3593. ctx.moveTo(from.x, from.y);
  3594. ctx.lineTo(from.x - textMargin, from.y);
  3595. ctx.stroke();
  3596. ctx.textAlign = 'right';
  3597. ctx.textBaseline = 'middle';
  3598. ctx.fillStyle = this.colorAxis;
  3599. ctx.fillText(step.getCurrent() + ' ', from.x - 5, from.y);
  3600. step.next();
  3601. }
  3602. ctx.lineWidth = 1;
  3603. from = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  3604. to = this._convert3Dto2D(new Point3d(xText, yText, this.zMax));
  3605. ctx.strokeStyle = this.colorAxis;
  3606. ctx.beginPath();
  3607. ctx.moveTo(from.x, from.y);
  3608. ctx.lineTo(to.x, to.y);
  3609. ctx.stroke();
  3610. // draw x-axis
  3611. ctx.lineWidth = 1;
  3612. // line at yMin
  3613. xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin));
  3614. xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin));
  3615. ctx.strokeStyle = this.colorAxis;
  3616. ctx.beginPath();
  3617. ctx.moveTo(xMin2d.x, xMin2d.y);
  3618. ctx.lineTo(xMax2d.x, xMax2d.y);
  3619. ctx.stroke();
  3620. // line at ymax
  3621. xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin));
  3622. xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin));
  3623. ctx.strokeStyle = this.colorAxis;
  3624. ctx.beginPath();
  3625. ctx.moveTo(xMin2d.x, xMin2d.y);
  3626. ctx.lineTo(xMax2d.x, xMax2d.y);
  3627. ctx.stroke();
  3628. // draw y-axis
  3629. ctx.lineWidth = 1;
  3630. // line at xMin
  3631. from = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin));
  3632. to = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin));
  3633. ctx.strokeStyle = this.colorAxis;
  3634. ctx.beginPath();
  3635. ctx.moveTo(from.x, from.y);
  3636. ctx.lineTo(to.x, to.y);
  3637. ctx.stroke();
  3638. // line at xMax
  3639. from = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin));
  3640. to = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin));
  3641. ctx.strokeStyle = this.colorAxis;
  3642. ctx.beginPath();
  3643. ctx.moveTo(from.x, from.y);
  3644. ctx.lineTo(to.x, to.y);
  3645. ctx.stroke();
  3646. // draw x-label
  3647. var xLabel = this.xLabel;
  3648. if (xLabel.length > 0) {
  3649. yOffset = 0.1 / this.scale.y;
  3650. xText = (this.xMin + this.xMax) / 2;
  3651. yText = (Math.cos(armAngle) > 0) ? this.yMin - yOffset: this.yMax + yOffset;
  3652. text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  3653. if (Math.cos(armAngle * 2) > 0) {
  3654. ctx.textAlign = 'center';
  3655. ctx.textBaseline = 'top';
  3656. }
  3657. else if (Math.sin(armAngle * 2) < 0){
  3658. ctx.textAlign = 'right';
  3659. ctx.textBaseline = 'middle';
  3660. }
  3661. else {
  3662. ctx.textAlign = 'left';
  3663. ctx.textBaseline = 'middle';
  3664. }
  3665. ctx.fillStyle = this.colorAxis;
  3666. ctx.fillText(xLabel, text.x, text.y);
  3667. }
  3668. // draw y-label
  3669. var yLabel = this.yLabel;
  3670. if (yLabel.length > 0) {
  3671. xOffset = 0.1 / this.scale.x;
  3672. xText = (Math.sin(armAngle ) > 0) ? this.xMin - xOffset : this.xMax + xOffset;
  3673. yText = (this.yMin + this.yMax) / 2;
  3674. text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  3675. if (Math.cos(armAngle * 2) < 0) {
  3676. ctx.textAlign = 'center';
  3677. ctx.textBaseline = 'top';
  3678. }
  3679. else if (Math.sin(armAngle * 2) > 0){
  3680. ctx.textAlign = 'right';
  3681. ctx.textBaseline = 'middle';
  3682. }
  3683. else {
  3684. ctx.textAlign = 'left';
  3685. ctx.textBaseline = 'middle';
  3686. }
  3687. ctx.fillStyle = this.colorAxis;
  3688. ctx.fillText(yLabel, text.x, text.y);
  3689. }
  3690. // draw z-label
  3691. var zLabel = this.zLabel;
  3692. if (zLabel.length > 0) {
  3693. offset = 30; // pixels. // TODO: relate to the max width of the values on the z axis?
  3694. xText = (Math.cos(armAngle ) > 0) ? this.xMin : this.xMax;
  3695. yText = (Math.sin(armAngle ) < 0) ? this.yMin : this.yMax;
  3696. zText = (this.zMin + this.zMax) / 2;
  3697. text = this._convert3Dto2D(new Point3d(xText, yText, zText));
  3698. ctx.textAlign = 'right';
  3699. ctx.textBaseline = 'middle';
  3700. ctx.fillStyle = this.colorAxis;
  3701. ctx.fillText(zLabel, text.x - offset, text.y);
  3702. }
  3703. };
  3704. /**
  3705. * Calculate the color based on the given value.
  3706. * @param {Number} H Hue, a value be between 0 and 360
  3707. * @param {Number} S Saturation, a value between 0 and 1
  3708. * @param {Number} V Value, a value between 0 and 1
  3709. */
  3710. Graph3d.prototype._hsv2rgb = function(H, S, V) {
  3711. var R, G, B, C, Hi, X;
  3712. C = V * S;
  3713. Hi = Math.floor(H/60); // hi = 0,1,2,3,4,5
  3714. X = C * (1 - Math.abs(((H/60) % 2) - 1));
  3715. switch (Hi) {
  3716. case 0: R = C; G = X; B = 0; break;
  3717. case 1: R = X; G = C; B = 0; break;
  3718. case 2: R = 0; G = C; B = X; break;
  3719. case 3: R = 0; G = X; B = C; break;
  3720. case 4: R = X; G = 0; B = C; break;
  3721. case 5: R = C; G = 0; B = X; break;
  3722. default: R = 0; G = 0; B = 0; break;
  3723. }
  3724. return 'RGB(' + parseInt(R*255) + ',' + parseInt(G*255) + ',' + parseInt(B*255) + ')';
  3725. };
  3726. /**
  3727. * Draw all datapoints as a grid
  3728. * This function can be used when the style is 'grid'
  3729. */
  3730. Graph3d.prototype._redrawDataGrid = function() {
  3731. var canvas = this.frame.canvas,
  3732. ctx = canvas.getContext('2d'),
  3733. point, right, top, cross,
  3734. i,
  3735. topSideVisible, fillStyle, strokeStyle, lineWidth,
  3736. h, s, v, zAvg;
  3737. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  3738. return; // TODO: throw exception?
  3739. // calculate the translations and screen position of all points
  3740. for (i = 0; i < this.dataPoints.length; i++) {
  3741. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  3742. var screen = this._convertTranslationToScreen(trans);
  3743. this.dataPoints[i].trans = trans;
  3744. this.dataPoints[i].screen = screen;
  3745. // calculate the translation of the point at the bottom (needed for sorting)
  3746. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  3747. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  3748. }
  3749. // sort the points on depth of their (x,y) position (not on z)
  3750. var sortDepth = function (a, b) {
  3751. return b.dist - a.dist;
  3752. };
  3753. this.dataPoints.sort(sortDepth);
  3754. if (this.style === Graph3d.STYLE.SURFACE) {
  3755. for (i = 0; i < this.dataPoints.length; i++) {
  3756. point = this.dataPoints[i];
  3757. right = this.dataPoints[i].pointRight;
  3758. top = this.dataPoints[i].pointTop;
  3759. cross = this.dataPoints[i].pointCross;
  3760. if (point !== undefined && right !== undefined && top !== undefined && cross !== undefined) {
  3761. if (this.showGrayBottom || this.showShadow) {
  3762. // calculate the cross product of the two vectors from center
  3763. // to left and right, in order to know whether we are looking at the
  3764. // bottom or at the top side. We can also use the cross product
  3765. // for calculating light intensity
  3766. var aDiff = Point3d.subtract(cross.trans, point.trans);
  3767. var bDiff = Point3d.subtract(top.trans, right.trans);
  3768. var crossproduct = Point3d.crossProduct(aDiff, bDiff);
  3769. var len = crossproduct.length();
  3770. // FIXME: there is a bug with determining the surface side (shadow or colored)
  3771. topSideVisible = (crossproduct.z > 0);
  3772. }
  3773. else {
  3774. topSideVisible = true;
  3775. }
  3776. if (topSideVisible) {
  3777. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  3778. zAvg = (point.point.z + right.point.z + top.point.z + cross.point.z) / 4;
  3779. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  3780. s = 1; // saturation
  3781. if (this.showShadow) {
  3782. v = Math.min(1 + (crossproduct.x / len) / 2, 1); // value. TODO: scale
  3783. fillStyle = this._hsv2rgb(h, s, v);
  3784. strokeStyle = fillStyle;
  3785. }
  3786. else {
  3787. v = 1;
  3788. fillStyle = this._hsv2rgb(h, s, v);
  3789. strokeStyle = this.colorAxis;
  3790. }
  3791. }
  3792. else {
  3793. fillStyle = 'gray';
  3794. strokeStyle = this.colorAxis;
  3795. }
  3796. lineWidth = 0.5;
  3797. ctx.lineWidth = lineWidth;
  3798. ctx.fillStyle = fillStyle;
  3799. ctx.strokeStyle = strokeStyle;
  3800. ctx.beginPath();
  3801. ctx.moveTo(point.screen.x, point.screen.y);
  3802. ctx.lineTo(right.screen.x, right.screen.y);
  3803. ctx.lineTo(cross.screen.x, cross.screen.y);
  3804. ctx.lineTo(top.screen.x, top.screen.y);
  3805. ctx.closePath();
  3806. ctx.fill();
  3807. ctx.stroke();
  3808. }
  3809. }
  3810. }
  3811. else { // grid style
  3812. for (i = 0; i < this.dataPoints.length; i++) {
  3813. point = this.dataPoints[i];
  3814. right = this.dataPoints[i].pointRight;
  3815. top = this.dataPoints[i].pointTop;
  3816. if (point !== undefined) {
  3817. if (this.showPerspective) {
  3818. lineWidth = 2 / -point.trans.z;
  3819. }
  3820. else {
  3821. lineWidth = 2 * -(this.eye.z / this.camera.getArmLength());
  3822. }
  3823. }
  3824. if (point !== undefined && right !== undefined) {
  3825. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  3826. zAvg = (point.point.z + right.point.z) / 2;
  3827. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  3828. ctx.lineWidth = lineWidth;
  3829. ctx.strokeStyle = this._hsv2rgb(h, 1, 1);
  3830. ctx.beginPath();
  3831. ctx.moveTo(point.screen.x, point.screen.y);
  3832. ctx.lineTo(right.screen.x, right.screen.y);
  3833. ctx.stroke();
  3834. }
  3835. if (point !== undefined && top !== undefined) {
  3836. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  3837. zAvg = (point.point.z + top.point.z) / 2;
  3838. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  3839. ctx.lineWidth = lineWidth;
  3840. ctx.strokeStyle = this._hsv2rgb(h, 1, 1);
  3841. ctx.beginPath();
  3842. ctx.moveTo(point.screen.x, point.screen.y);
  3843. ctx.lineTo(top.screen.x, top.screen.y);
  3844. ctx.stroke();
  3845. }
  3846. }
  3847. }
  3848. };
  3849. /**
  3850. * Draw all datapoints as dots.
  3851. * This function can be used when the style is 'dot' or 'dot-line'
  3852. */
  3853. Graph3d.prototype._redrawDataDot = function() {
  3854. var canvas = this.frame.canvas;
  3855. var ctx = canvas.getContext('2d');
  3856. var i;
  3857. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  3858. return; // TODO: throw exception?
  3859. // calculate the translations of all points
  3860. for (i = 0; i < this.dataPoints.length; i++) {
  3861. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  3862. var screen = this._convertTranslationToScreen(trans);
  3863. this.dataPoints[i].trans = trans;
  3864. this.dataPoints[i].screen = screen;
  3865. // calculate the distance from the point at the bottom to the camera
  3866. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  3867. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  3868. }
  3869. // order the translated points by depth
  3870. var sortDepth = function (a, b) {
  3871. return b.dist - a.dist;
  3872. };
  3873. this.dataPoints.sort(sortDepth);
  3874. // draw the datapoints as colored circles
  3875. var dotSize = this.frame.clientWidth * 0.02; // px
  3876. for (i = 0; i < this.dataPoints.length; i++) {
  3877. var point = this.dataPoints[i];
  3878. if (this.style === Graph3d.STYLE.DOTLINE) {
  3879. // draw a vertical line from the bottom to the graph value
  3880. //var from = this._convert3Dto2D(new Point3d(point.point.x, point.point.y, this.zMin));
  3881. var from = this._convert3Dto2D(point.bottom);
  3882. ctx.lineWidth = 1;
  3883. ctx.strokeStyle = this.colorGrid;
  3884. ctx.beginPath();
  3885. ctx.moveTo(from.x, from.y);
  3886. ctx.lineTo(point.screen.x, point.screen.y);
  3887. ctx.stroke();
  3888. }
  3889. // calculate radius for the circle
  3890. var size;
  3891. if (this.style === Graph3d.STYLE.DOTSIZE) {
  3892. size = dotSize/2 + 2*dotSize * (point.point.value - this.valueMin) / (this.valueMax - this.valueMin);
  3893. }
  3894. else {
  3895. size = dotSize;
  3896. }
  3897. var radius;
  3898. if (this.showPerspective) {
  3899. radius = size / -point.trans.z;
  3900. }
  3901. else {
  3902. radius = size * -(this.eye.z / this.camera.getArmLength());
  3903. }
  3904. if (radius < 0) {
  3905. radius = 0;
  3906. }
  3907. var hue, color, borderColor;
  3908. if (this.style === Graph3d.STYLE.DOTCOLOR ) {
  3909. // calculate the color based on the value
  3910. hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240;
  3911. color = this._hsv2rgb(hue, 1, 1);
  3912. borderColor = this._hsv2rgb(hue, 1, 0.8);
  3913. }
  3914. else if (this.style === Graph3d.STYLE.DOTSIZE) {
  3915. color = this.colorDot;
  3916. borderColor = this.colorDotBorder;
  3917. }
  3918. else {
  3919. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  3920. hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  3921. color = this._hsv2rgb(hue, 1, 1);
  3922. borderColor = this._hsv2rgb(hue, 1, 0.8);
  3923. }
  3924. // draw the circle
  3925. ctx.lineWidth = 1.0;
  3926. ctx.strokeStyle = borderColor;
  3927. ctx.fillStyle = color;
  3928. ctx.beginPath();
  3929. ctx.arc(point.screen.x, point.screen.y, radius, 0, Math.PI*2, true);
  3930. ctx.fill();
  3931. ctx.stroke();
  3932. }
  3933. };
  3934. /**
  3935. * Draw all datapoints as bars.
  3936. * This function can be used when the style is 'bar', 'bar-color', or 'bar-size'
  3937. */
  3938. Graph3d.prototype._redrawDataBar = function() {
  3939. var canvas = this.frame.canvas;
  3940. var ctx = canvas.getContext('2d');
  3941. var i, j, surface, corners;
  3942. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  3943. return; // TODO: throw exception?
  3944. // calculate the translations of all points
  3945. for (i = 0; i < this.dataPoints.length; i++) {
  3946. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  3947. var screen = this._convertTranslationToScreen(trans);
  3948. this.dataPoints[i].trans = trans;
  3949. this.dataPoints[i].screen = screen;
  3950. // calculate the distance from the point at the bottom to the camera
  3951. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  3952. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  3953. }
  3954. // order the translated points by depth
  3955. var sortDepth = function (a, b) {
  3956. return b.dist - a.dist;
  3957. };
  3958. this.dataPoints.sort(sortDepth);
  3959. // draw the datapoints as bars
  3960. var xWidth = this.xBarWidth / 2;
  3961. var yWidth = this.yBarWidth / 2;
  3962. for (i = 0; i < this.dataPoints.length; i++) {
  3963. var point = this.dataPoints[i];
  3964. // determine color
  3965. var hue, color, borderColor;
  3966. if (this.style === Graph3d.STYLE.BARCOLOR ) {
  3967. // calculate the color based on the value
  3968. hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240;
  3969. color = this._hsv2rgb(hue, 1, 1);
  3970. borderColor = this._hsv2rgb(hue, 1, 0.8);
  3971. }
  3972. else if (this.style === Graph3d.STYLE.BARSIZE) {
  3973. color = this.colorDot;
  3974. borderColor = this.colorDotBorder;
  3975. }
  3976. else {
  3977. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  3978. hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  3979. color = this._hsv2rgb(hue, 1, 1);
  3980. borderColor = this._hsv2rgb(hue, 1, 0.8);
  3981. }
  3982. // calculate size for the bar
  3983. if (this.style === Graph3d.STYLE.BARSIZE) {
  3984. xWidth = (this.xBarWidth / 2) * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2);
  3985. yWidth = (this.yBarWidth / 2) * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2);
  3986. }
  3987. // calculate all corner points
  3988. var me = this;
  3989. var point3d = point.point;
  3990. var top = [
  3991. {point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, point3d.z)},
  3992. {point: new Point3d(point3d.x + xWidth, point3d.y - yWidth, point3d.z)},
  3993. {point: new Point3d(point3d.x + xWidth, point3d.y + yWidth, point3d.z)},
  3994. {point: new Point3d(point3d.x - xWidth, point3d.y + yWidth, point3d.z)}
  3995. ];
  3996. var bottom = [
  3997. {point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, this.zMin)},
  3998. {point: new Point3d(point3d.x + xWidth, point3d.y - yWidth, this.zMin)},
  3999. {point: new Point3d(point3d.x + xWidth, point3d.y + yWidth, this.zMin)},
  4000. {point: new Point3d(point3d.x - xWidth, point3d.y + yWidth, this.zMin)}
  4001. ];
  4002. // calculate screen location of the points
  4003. top.forEach(function (obj) {
  4004. obj.screen = me._convert3Dto2D(obj.point);
  4005. });
  4006. bottom.forEach(function (obj) {
  4007. obj.screen = me._convert3Dto2D(obj.point);
  4008. });
  4009. // create five sides, calculate both corner points and center points
  4010. var surfaces = [
  4011. {corners: top, center: Point3d.avg(bottom[0].point, bottom[2].point)},
  4012. {corners: [top[0], top[1], bottom[1], bottom[0]], center: Point3d.avg(bottom[1].point, bottom[0].point)},
  4013. {corners: [top[1], top[2], bottom[2], bottom[1]], center: Point3d.avg(bottom[2].point, bottom[1].point)},
  4014. {corners: [top[2], top[3], bottom[3], bottom[2]], center: Point3d.avg(bottom[3].point, bottom[2].point)},
  4015. {corners: [top[3], top[0], bottom[0], bottom[3]], center: Point3d.avg(bottom[0].point, bottom[3].point)}
  4016. ];
  4017. point.surfaces = surfaces;
  4018. // calculate the distance of each of the surface centers to the camera
  4019. for (j = 0; j < surfaces.length; j++) {
  4020. surface = surfaces[j];
  4021. var transCenter = this._convertPointToTranslation(surface.center);
  4022. surface.dist = this.showPerspective ? transCenter.length() : -transCenter.z;
  4023. // TODO: this dept calculation doesn't work 100% of the cases due to perspective,
  4024. // but the current solution is fast/simple and works in 99.9% of all cases
  4025. // the issue is visible in example 14, with graph.setCameraPosition({horizontal: 2.97, vertical: 0.5, distance: 0.9})
  4026. }
  4027. // order the surfaces by their (translated) depth
  4028. surfaces.sort(function (a, b) {
  4029. var diff = b.dist - a.dist;
  4030. if (diff) return diff;
  4031. // if equal depth, sort the top surface last
  4032. if (a.corners === top) return 1;
  4033. if (b.corners === top) return -1;
  4034. // both are equal
  4035. return 0;
  4036. });
  4037. // draw the ordered surfaces
  4038. ctx.lineWidth = 1;
  4039. ctx.strokeStyle = borderColor;
  4040. ctx.fillStyle = color;
  4041. // NOTE: we start at j=2 instead of j=0 as we don't need to draw the two surfaces at the backside
  4042. for (j = 2; j < surfaces.length; j++) {
  4043. surface = surfaces[j];
  4044. corners = surface.corners;
  4045. ctx.beginPath();
  4046. ctx.moveTo(corners[3].screen.x, corners[3].screen.y);
  4047. ctx.lineTo(corners[0].screen.x, corners[0].screen.y);
  4048. ctx.lineTo(corners[1].screen.x, corners[1].screen.y);
  4049. ctx.lineTo(corners[2].screen.x, corners[2].screen.y);
  4050. ctx.lineTo(corners[3].screen.x, corners[3].screen.y);
  4051. ctx.fill();
  4052. ctx.stroke();
  4053. }
  4054. }
  4055. };
  4056. /**
  4057. * Draw a line through all datapoints.
  4058. * This function can be used when the style is 'line'
  4059. */
  4060. Graph3d.prototype._redrawDataLine = function() {
  4061. var canvas = this.frame.canvas,
  4062. ctx = canvas.getContext('2d'),
  4063. point, i;
  4064. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  4065. return; // TODO: throw exception?
  4066. // calculate the translations of all points
  4067. for (i = 0; i < this.dataPoints.length; i++) {
  4068. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  4069. var screen = this._convertTranslationToScreen(trans);
  4070. this.dataPoints[i].trans = trans;
  4071. this.dataPoints[i].screen = screen;
  4072. }
  4073. // start the line
  4074. if (this.dataPoints.length > 0) {
  4075. point = this.dataPoints[0];
  4076. ctx.lineWidth = 1; // TODO: make customizable
  4077. ctx.strokeStyle = 'blue'; // TODO: make customizable
  4078. ctx.beginPath();
  4079. ctx.moveTo(point.screen.x, point.screen.y);
  4080. }
  4081. // draw the datapoints as colored circles
  4082. for (i = 1; i < this.dataPoints.length; i++) {
  4083. point = this.dataPoints[i];
  4084. ctx.lineTo(point.screen.x, point.screen.y);
  4085. }
  4086. // finish the line
  4087. if (this.dataPoints.length > 0) {
  4088. ctx.stroke();
  4089. }
  4090. };
  4091. /**
  4092. * Start a moving operation inside the provided parent element
  4093. * @param {Event} event The event that occurred (required for
  4094. * retrieving the mouse position)
  4095. */
  4096. Graph3d.prototype._onMouseDown = function(event) {
  4097. event = event || window.event;
  4098. // check if mouse is still down (may be up when focus is lost for example
  4099. // in an iframe)
  4100. if (this.leftButtonDown) {
  4101. this._onMouseUp(event);
  4102. }
  4103. // only react on left mouse button down
  4104. this.leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
  4105. if (!this.leftButtonDown && !this.touchDown) return;
  4106. // get mouse position (different code for IE and all other browsers)
  4107. this.startMouseX = getMouseX(event);
  4108. this.startMouseY = getMouseY(event);
  4109. this.startStart = new Date(this.start);
  4110. this.startEnd = new Date(this.end);
  4111. this.startArmRotation = this.camera.getArmRotation();
  4112. this.frame.style.cursor = 'move';
  4113. // add event listeners to handle moving the contents
  4114. // we store the function onmousemove and onmouseup in the graph, so we can
  4115. // remove the eventlisteners lateron in the function mouseUp()
  4116. var me = this;
  4117. this.onmousemove = function (event) {me._onMouseMove(event);};
  4118. this.onmouseup = function (event) {me._onMouseUp(event);};
  4119. util.addEventListener(document, 'mousemove', me.onmousemove);
  4120. util.addEventListener(document, 'mouseup', me.onmouseup);
  4121. util.preventDefault(event);
  4122. };
  4123. /**
  4124. * Perform moving operating.
  4125. * This function activated from within the funcion Graph.mouseDown().
  4126. * @param {Event} event Well, eehh, the event
  4127. */
  4128. Graph3d.prototype._onMouseMove = function (event) {
  4129. event = event || window.event;
  4130. // calculate change in mouse position
  4131. var diffX = parseFloat(getMouseX(event)) - this.startMouseX;
  4132. var diffY = parseFloat(getMouseY(event)) - this.startMouseY;
  4133. var horizontalNew = this.startArmRotation.horizontal + diffX / 200;
  4134. var verticalNew = this.startArmRotation.vertical + diffY / 200;
  4135. var snapAngle = 4; // degrees
  4136. var snapValue = Math.sin(snapAngle / 360 * 2 * Math.PI);
  4137. // snap horizontally to nice angles at 0pi, 0.5pi, 1pi, 1.5pi, etc...
  4138. // the -0.001 is to take care that the vertical axis is always drawn at the left front corner
  4139. if (Math.abs(Math.sin(horizontalNew)) < snapValue) {
  4140. horizontalNew = Math.round((horizontalNew / Math.PI)) * Math.PI - 0.001;
  4141. }
  4142. if (Math.abs(Math.cos(horizontalNew)) < snapValue) {
  4143. horizontalNew = (Math.round((horizontalNew/ Math.PI - 0.5)) + 0.5) * Math.PI - 0.001;
  4144. }
  4145. // snap vertically to nice angles
  4146. if (Math.abs(Math.sin(verticalNew)) < snapValue) {
  4147. verticalNew = Math.round((verticalNew / Math.PI)) * Math.PI;
  4148. }
  4149. if (Math.abs(Math.cos(verticalNew)) < snapValue) {
  4150. verticalNew = (Math.round((verticalNew/ Math.PI - 0.5)) + 0.5) * Math.PI;
  4151. }
  4152. this.camera.setArmRotation(horizontalNew, verticalNew);
  4153. this.redraw();
  4154. // fire a cameraPositionChange event
  4155. var parameters = this.getCameraPosition();
  4156. this.emit('cameraPositionChange', parameters);
  4157. util.preventDefault(event);
  4158. };
  4159. /**
  4160. * Stop moving operating.
  4161. * This function activated from within the funcion Graph.mouseDown().
  4162. * @param {event} event The event
  4163. */
  4164. Graph3d.prototype._onMouseUp = function (event) {
  4165. this.frame.style.cursor = 'auto';
  4166. this.leftButtonDown = false;
  4167. // remove event listeners here
  4168. util.removeEventListener(document, 'mousemove', this.onmousemove);
  4169. util.removeEventListener(document, 'mouseup', this.onmouseup);
  4170. util.preventDefault(event);
  4171. };
  4172. /**
  4173. * After having moved the mouse, a tooltip should pop up when the mouse is resting on a data point
  4174. * @param {Event} event A mouse move event
  4175. */
  4176. Graph3d.prototype._onTooltip = function (event) {
  4177. var delay = 300; // ms
  4178. var mouseX = getMouseX(event) - util.getAbsoluteLeft(this.frame);
  4179. var mouseY = getMouseY(event) - util.getAbsoluteTop(this.frame);
  4180. if (!this.showTooltip) {
  4181. return;
  4182. }
  4183. if (this.tooltipTimeout) {
  4184. clearTimeout(this.tooltipTimeout);
  4185. }
  4186. // (delayed) display of a tooltip only if no mouse button is down
  4187. if (this.leftButtonDown) {
  4188. this._hideTooltip();
  4189. return;
  4190. }
  4191. if (this.tooltip && this.tooltip.dataPoint) {
  4192. // tooltip is currently visible
  4193. var dataPoint = this._dataPointFromXY(mouseX, mouseY);
  4194. if (dataPoint !== this.tooltip.dataPoint) {
  4195. // datapoint changed
  4196. if (dataPoint) {
  4197. this._showTooltip(dataPoint);
  4198. }
  4199. else {
  4200. this._hideTooltip();
  4201. }
  4202. }
  4203. }
  4204. else {
  4205. // tooltip is currently not visible
  4206. var me = this;
  4207. this.tooltipTimeout = setTimeout(function () {
  4208. me.tooltipTimeout = null;
  4209. // show a tooltip if we have a data point
  4210. var dataPoint = me._dataPointFromXY(mouseX, mouseY);
  4211. if (dataPoint) {
  4212. me._showTooltip(dataPoint);
  4213. }
  4214. }, delay);
  4215. }
  4216. };
  4217. /**
  4218. * Event handler for touchstart event on mobile devices
  4219. */
  4220. Graph3d.prototype._onTouchStart = function(event) {
  4221. this.touchDown = true;
  4222. var me = this;
  4223. this.ontouchmove = function (event) {me._onTouchMove(event);};
  4224. this.ontouchend = function (event) {me._onTouchEnd(event);};
  4225. util.addEventListener(document, 'touchmove', me.ontouchmove);
  4226. util.addEventListener(document, 'touchend', me.ontouchend);
  4227. this._onMouseDown(event);
  4228. };
  4229. /**
  4230. * Event handler for touchmove event on mobile devices
  4231. */
  4232. Graph3d.prototype._onTouchMove = function(event) {
  4233. this._onMouseMove(event);
  4234. };
  4235. /**
  4236. * Event handler for touchend event on mobile devices
  4237. */
  4238. Graph3d.prototype._onTouchEnd = function(event) {
  4239. this.touchDown = false;
  4240. util.removeEventListener(document, 'touchmove', this.ontouchmove);
  4241. util.removeEventListener(document, 'touchend', this.ontouchend);
  4242. this._onMouseUp(event);
  4243. };
  4244. /**
  4245. * Event handler for mouse wheel event, used to zoom the graph
  4246. * Code from http://adomas.org/javascript-mouse-wheel/
  4247. * @param {event} event The event
  4248. */
  4249. Graph3d.prototype._onWheel = function(event) {
  4250. if (!event) /* For IE. */
  4251. event = window.event;
  4252. // retrieve delta
  4253. var delta = 0;
  4254. if (event.wheelDelta) { /* IE/Opera. */
  4255. delta = event.wheelDelta/120;
  4256. } else if (event.detail) { /* Mozilla case. */
  4257. // In Mozilla, sign of delta is different than in IE.
  4258. // Also, delta is multiple of 3.
  4259. delta = -event.detail/3;
  4260. }
  4261. // If delta is nonzero, handle it.
  4262. // Basically, delta is now positive if wheel was scrolled up,
  4263. // and negative, if wheel was scrolled down.
  4264. if (delta) {
  4265. var oldLength = this.camera.getArmLength();
  4266. var newLength = oldLength * (1 - delta / 10);
  4267. this.camera.setArmLength(newLength);
  4268. this.redraw();
  4269. this._hideTooltip();
  4270. }
  4271. // fire a cameraPositionChange event
  4272. var parameters = this.getCameraPosition();
  4273. this.emit('cameraPositionChange', parameters);
  4274. // Prevent default actions caused by mouse wheel.
  4275. // That might be ugly, but we handle scrolls somehow
  4276. // anyway, so don't bother here..
  4277. util.preventDefault(event);
  4278. };
  4279. /**
  4280. * Test whether a point lies inside given 2D triangle
  4281. * @param {Point2d} point
  4282. * @param {Point2d[]} triangle
  4283. * @return {boolean} Returns true if given point lies inside or on the edge of the triangle
  4284. * @private
  4285. */
  4286. Graph3d.prototype._insideTriangle = function (point, triangle) {
  4287. var a = triangle[0],
  4288. b = triangle[1],
  4289. c = triangle[2];
  4290. function sign (x) {
  4291. return x > 0 ? 1 : x < 0 ? -1 : 0;
  4292. }
  4293. var as = sign((b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x));
  4294. var bs = sign((c.x - b.x) * (point.y - b.y) - (c.y - b.y) * (point.x - b.x));
  4295. var cs = sign((a.x - c.x) * (point.y - c.y) - (a.y - c.y) * (point.x - c.x));
  4296. // each of the three signs must be either equal to each other or zero
  4297. return (as == 0 || bs == 0 || as == bs) &&
  4298. (bs == 0 || cs == 0 || bs == cs) &&
  4299. (as == 0 || cs == 0 || as == cs);
  4300. };
  4301. /**
  4302. * Find a data point close to given screen position (x, y)
  4303. * @param {Number} x
  4304. * @param {Number} y
  4305. * @return {Object | null} The closest data point or null if not close to any data point
  4306. * @private
  4307. */
  4308. Graph3d.prototype._dataPointFromXY = function (x, y) {
  4309. var i,
  4310. distMax = 100, // px
  4311. dataPoint = null,
  4312. closestDataPoint = null,
  4313. closestDist = null,
  4314. center = new Point2d(x, y);
  4315. if (this.style === Graph3d.STYLE.BAR ||
  4316. this.style === Graph3d.STYLE.BARCOLOR ||
  4317. this.style === Graph3d.STYLE.BARSIZE) {
  4318. // the data points are ordered from far away to closest
  4319. for (i = this.dataPoints.length - 1; i >= 0; i--) {
  4320. dataPoint = this.dataPoints[i];
  4321. var surfaces = dataPoint.surfaces;
  4322. if (surfaces) {
  4323. for (var s = surfaces.length - 1; s >= 0; s--) {
  4324. // split each surface in two triangles, and see if the center point is inside one of these
  4325. var surface = surfaces[s];
  4326. var corners = surface.corners;
  4327. var triangle1 = [corners[0].screen, corners[1].screen, corners[2].screen];
  4328. var triangle2 = [corners[2].screen, corners[3].screen, corners[0].screen];
  4329. if (this._insideTriangle(center, triangle1) ||
  4330. this._insideTriangle(center, triangle2)) {
  4331. // return immediately at the first hit
  4332. return dataPoint;
  4333. }
  4334. }
  4335. }
  4336. }
  4337. }
  4338. else {
  4339. // find the closest data point, using distance to the center of the point on 2d screen
  4340. for (i = 0; i < this.dataPoints.length; i++) {
  4341. dataPoint = this.dataPoints[i];
  4342. var point = dataPoint.screen;
  4343. if (point) {
  4344. var distX = Math.abs(x - point.x);
  4345. var distY = Math.abs(y - point.y);
  4346. var dist = Math.sqrt(distX * distX + distY * distY);
  4347. if ((closestDist === null || dist < closestDist) && dist < distMax) {
  4348. closestDist = dist;
  4349. closestDataPoint = dataPoint;
  4350. }
  4351. }
  4352. }
  4353. }
  4354. return closestDataPoint;
  4355. };
  4356. /**
  4357. * Display a tooltip for given data point
  4358. * @param {Object} dataPoint
  4359. * @private
  4360. */
  4361. Graph3d.prototype._showTooltip = function (dataPoint) {
  4362. var content, line, dot;
  4363. if (!this.tooltip) {
  4364. content = document.createElement('div');
  4365. content.style.position = 'absolute';
  4366. content.style.padding = '10px';
  4367. content.style.border = '1px solid #4d4d4d';
  4368. content.style.color = '#1a1a1a';
  4369. content.style.background = 'rgba(255,255,255,0.7)';
  4370. content.style.borderRadius = '2px';
  4371. content.style.boxShadow = '5px 5px 10px rgba(128,128,128,0.5)';
  4372. line = document.createElement('div');
  4373. line.style.position = 'absolute';
  4374. line.style.height = '40px';
  4375. line.style.width = '0';
  4376. line.style.borderLeft = '1px solid #4d4d4d';
  4377. dot = document.createElement('div');
  4378. dot.style.position = 'absolute';
  4379. dot.style.height = '0';
  4380. dot.style.width = '0';
  4381. dot.style.border = '5px solid #4d4d4d';
  4382. dot.style.borderRadius = '5px';
  4383. this.tooltip = {
  4384. dataPoint: null,
  4385. dom: {
  4386. content: content,
  4387. line: line,
  4388. dot: dot
  4389. }
  4390. };
  4391. }
  4392. else {
  4393. content = this.tooltip.dom.content;
  4394. line = this.tooltip.dom.line;
  4395. dot = this.tooltip.dom.dot;
  4396. }
  4397. this._hideTooltip();
  4398. this.tooltip.dataPoint = dataPoint;
  4399. if (typeof this.showTooltip === 'function') {
  4400. content.innerHTML = this.showTooltip(dataPoint.point);
  4401. }
  4402. else {
  4403. content.innerHTML = '<table>' +
  4404. '<tr><td>x:</td><td>' + dataPoint.point.x + '</td></tr>' +
  4405. '<tr><td>y:</td><td>' + dataPoint.point.y + '</td></tr>' +
  4406. '<tr><td>z:</td><td>' + dataPoint.point.z + '</td></tr>' +
  4407. '</table>';
  4408. }
  4409. content.style.left = '0';
  4410. content.style.top = '0';
  4411. this.frame.appendChild(content);
  4412. this.frame.appendChild(line);
  4413. this.frame.appendChild(dot);
  4414. // calculate sizes
  4415. var contentWidth = content.offsetWidth;
  4416. var contentHeight = content.offsetHeight;
  4417. var lineHeight = line.offsetHeight;
  4418. var dotWidth = dot.offsetWidth;
  4419. var dotHeight = dot.offsetHeight;
  4420. var left = dataPoint.screen.x - contentWidth / 2;
  4421. left = Math.min(Math.max(left, 10), this.frame.clientWidth - 10 - contentWidth);
  4422. line.style.left = dataPoint.screen.x + 'px';
  4423. line.style.top = (dataPoint.screen.y - lineHeight) + 'px';
  4424. content.style.left = left + 'px';
  4425. content.style.top = (dataPoint.screen.y - lineHeight - contentHeight) + 'px';
  4426. dot.style.left = (dataPoint.screen.x - dotWidth / 2) + 'px';
  4427. dot.style.top = (dataPoint.screen.y - dotHeight / 2) + 'px';
  4428. };
  4429. /**
  4430. * Hide the tooltip when displayed
  4431. * @private
  4432. */
  4433. Graph3d.prototype._hideTooltip = function () {
  4434. if (this.tooltip) {
  4435. this.tooltip.dataPoint = null;
  4436. for (var prop in this.tooltip.dom) {
  4437. if (this.tooltip.dom.hasOwnProperty(prop)) {
  4438. var elem = this.tooltip.dom[prop];
  4439. if (elem && elem.parentNode) {
  4440. elem.parentNode.removeChild(elem);
  4441. }
  4442. }
  4443. }
  4444. }
  4445. };
  4446. /**--------------------------------------------------------------------------**/
  4447. /**
  4448. * Get the horizontal mouse position from a mouse event
  4449. * @param {Event} event
  4450. * @return {Number} mouse x
  4451. */
  4452. getMouseX = function(event) {
  4453. if ('clientX' in event) return event.clientX;
  4454. return event.targetTouches[0] && event.targetTouches[0].clientX || 0;
  4455. };
  4456. /**
  4457. * Get the vertical mouse position from a mouse event
  4458. * @param {Event} event
  4459. * @return {Number} mouse y
  4460. */
  4461. getMouseY = function(event) {
  4462. if ('clientY' in event) return event.clientY;
  4463. return event.targetTouches[0] && event.targetTouches[0].clientY || 0;
  4464. };
  4465. module.exports = Graph3d;
  4466. /***/ },
  4467. /* 6 */
  4468. /***/ function(module, exports, __webpack_require__) {
  4469. var Point3d = __webpack_require__(9);
  4470. /**
  4471. * @class Camera
  4472. * The camera is mounted on a (virtual) camera arm. The camera arm can rotate
  4473. * The camera is always looking in the direction of the origin of the arm.
  4474. * This way, the camera always rotates around one fixed point, the location
  4475. * of the camera arm.
  4476. *
  4477. * Documentation:
  4478. * http://en.wikipedia.org/wiki/3D_projection
  4479. */
  4480. Camera = function () {
  4481. this.armLocation = new Point3d();
  4482. this.armRotation = {};
  4483. this.armRotation.horizontal = 0;
  4484. this.armRotation.vertical = 0;
  4485. this.armLength = 1.7;
  4486. this.cameraLocation = new Point3d();
  4487. this.cameraRotation = new Point3d(0.5*Math.PI, 0, 0);
  4488. this.calculateCameraOrientation();
  4489. };
  4490. /**
  4491. * Set the location (origin) of the arm
  4492. * @param {Number} x Normalized value of x
  4493. * @param {Number} y Normalized value of y
  4494. * @param {Number} z Normalized value of z
  4495. */
  4496. Camera.prototype.setArmLocation = function(x, y, z) {
  4497. this.armLocation.x = x;
  4498. this.armLocation.y = y;
  4499. this.armLocation.z = z;
  4500. this.calculateCameraOrientation();
  4501. };
  4502. /**
  4503. * Set the rotation of the camera arm
  4504. * @param {Number} horizontal The horizontal rotation, between 0 and 2*PI.
  4505. * Optional, can be left undefined.
  4506. * @param {Number} vertical The vertical rotation, between 0 and 0.5*PI
  4507. * if vertical=0.5*PI, the graph is shown from the
  4508. * top. Optional, can be left undefined.
  4509. */
  4510. Camera.prototype.setArmRotation = function(horizontal, vertical) {
  4511. if (horizontal !== undefined) {
  4512. this.armRotation.horizontal = horizontal;
  4513. }
  4514. if (vertical !== undefined) {
  4515. this.armRotation.vertical = vertical;
  4516. if (this.armRotation.vertical < 0) this.armRotation.vertical = 0;
  4517. if (this.armRotation.vertical > 0.5*Math.PI) this.armRotation.vertical = 0.5*Math.PI;
  4518. }
  4519. if (horizontal !== undefined || vertical !== undefined) {
  4520. this.calculateCameraOrientation();
  4521. }
  4522. };
  4523. /**
  4524. * Retrieve the current arm rotation
  4525. * @return {object} An object with parameters horizontal and vertical
  4526. */
  4527. Camera.prototype.getArmRotation = function() {
  4528. var rot = {};
  4529. rot.horizontal = this.armRotation.horizontal;
  4530. rot.vertical = this.armRotation.vertical;
  4531. return rot;
  4532. };
  4533. /**
  4534. * Set the (normalized) length of the camera arm.
  4535. * @param {Number} length A length between 0.71 and 5.0
  4536. */
  4537. Camera.prototype.setArmLength = function(length) {
  4538. if (length === undefined)
  4539. return;
  4540. this.armLength = length;
  4541. // Radius must be larger than the corner of the graph,
  4542. // which has a distance of sqrt(0.5^2+0.5^2) = 0.71 from the center of the
  4543. // graph
  4544. if (this.armLength < 0.71) this.armLength = 0.71;
  4545. if (this.armLength > 5.0) this.armLength = 5.0;
  4546. this.calculateCameraOrientation();
  4547. };
  4548. /**
  4549. * Retrieve the arm length
  4550. * @return {Number} length
  4551. */
  4552. Camera.prototype.getArmLength = function() {
  4553. return this.armLength;
  4554. };
  4555. /**
  4556. * Retrieve the camera location
  4557. * @return {Point3d} cameraLocation
  4558. */
  4559. Camera.prototype.getCameraLocation = function() {
  4560. return this.cameraLocation;
  4561. };
  4562. /**
  4563. * Retrieve the camera rotation
  4564. * @return {Point3d} cameraRotation
  4565. */
  4566. Camera.prototype.getCameraRotation = function() {
  4567. return this.cameraRotation;
  4568. };
  4569. /**
  4570. * Calculate the location and rotation of the camera based on the
  4571. * position and orientation of the camera arm
  4572. */
  4573. Camera.prototype.calculateCameraOrientation = function() {
  4574. // calculate location of the camera
  4575. this.cameraLocation.x = this.armLocation.x - this.armLength * Math.sin(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical);
  4576. this.cameraLocation.y = this.armLocation.y - this.armLength * Math.cos(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical);
  4577. this.cameraLocation.z = this.armLocation.z + this.armLength * Math.sin(this.armRotation.vertical);
  4578. // calculate rotation of the camera
  4579. this.cameraRotation.x = Math.PI/2 - this.armRotation.vertical;
  4580. this.cameraRotation.y = 0;
  4581. this.cameraRotation.z = -this.armRotation.horizontal;
  4582. };
  4583. module.exports = Camera;
  4584. /***/ },
  4585. /* 7 */
  4586. /***/ function(module, exports, __webpack_require__) {
  4587. var DataView = __webpack_require__(4);
  4588. /**
  4589. * @class Filter
  4590. *
  4591. * @param {DataSet} data The google data table
  4592. * @param {Number} column The index of the column to be filtered
  4593. * @param {Graph} graph The graph
  4594. */
  4595. function Filter (data, column, graph) {
  4596. this.data = data;
  4597. this.column = column;
  4598. this.graph = graph; // the parent graph
  4599. this.index = undefined;
  4600. this.value = undefined;
  4601. // read all distinct values and select the first one
  4602. this.values = graph.getDistinctValues(data.get(), this.column);
  4603. // sort both numeric and string values correctly
  4604. this.values.sort(function (a, b) {
  4605. return a > b ? 1 : a < b ? -1 : 0;
  4606. });
  4607. if (this.values.length > 0) {
  4608. this.selectValue(0);
  4609. }
  4610. // create an array with the filtered datapoints. this will be loaded afterwards
  4611. this.dataPoints = [];
  4612. this.loaded = false;
  4613. this.onLoadCallback = undefined;
  4614. if (graph.animationPreload) {
  4615. this.loaded = false;
  4616. this.loadInBackground();
  4617. }
  4618. else {
  4619. this.loaded = true;
  4620. }
  4621. };
  4622. /**
  4623. * Return the label
  4624. * @return {string} label
  4625. */
  4626. Filter.prototype.isLoaded = function() {
  4627. return this.loaded;
  4628. };
  4629. /**
  4630. * Return the loaded progress
  4631. * @return {Number} percentage between 0 and 100
  4632. */
  4633. Filter.prototype.getLoadedProgress = function() {
  4634. var len = this.values.length;
  4635. var i = 0;
  4636. while (this.dataPoints[i]) {
  4637. i++;
  4638. }
  4639. return Math.round(i / len * 100);
  4640. };
  4641. /**
  4642. * Return the label
  4643. * @return {string} label
  4644. */
  4645. Filter.prototype.getLabel = function() {
  4646. return this.graph.filterLabel;
  4647. };
  4648. /**
  4649. * Return the columnIndex of the filter
  4650. * @return {Number} columnIndex
  4651. */
  4652. Filter.prototype.getColumn = function() {
  4653. return this.column;
  4654. };
  4655. /**
  4656. * Return the currently selected value. Returns undefined if there is no selection
  4657. * @return {*} value
  4658. */
  4659. Filter.prototype.getSelectedValue = function() {
  4660. if (this.index === undefined)
  4661. return undefined;
  4662. return this.values[this.index];
  4663. };
  4664. /**
  4665. * Retrieve all values of the filter
  4666. * @return {Array} values
  4667. */
  4668. Filter.prototype.getValues = function() {
  4669. return this.values;
  4670. };
  4671. /**
  4672. * Retrieve one value of the filter
  4673. * @param {Number} index
  4674. * @return {*} value
  4675. */
  4676. Filter.prototype.getValue = function(index) {
  4677. if (index >= this.values.length)
  4678. throw 'Error: index out of range';
  4679. return this.values[index];
  4680. };
  4681. /**
  4682. * Retrieve the (filtered) dataPoints for the currently selected filter index
  4683. * @param {Number} [index] (optional)
  4684. * @return {Array} dataPoints
  4685. */
  4686. Filter.prototype._getDataPoints = function(index) {
  4687. if (index === undefined)
  4688. index = this.index;
  4689. if (index === undefined)
  4690. return [];
  4691. var dataPoints;
  4692. if (this.dataPoints[index]) {
  4693. dataPoints = this.dataPoints[index];
  4694. }
  4695. else {
  4696. var f = {};
  4697. f.column = this.column;
  4698. f.value = this.values[index];
  4699. var dataView = new DataView(this.data,{filter: function (item) {return (item[f.column] == f.value);}}).get();
  4700. dataPoints = this.graph._getDataPoints(dataView);
  4701. this.dataPoints[index] = dataPoints;
  4702. }
  4703. return dataPoints;
  4704. };
  4705. /**
  4706. * Set a callback function when the filter is fully loaded.
  4707. */
  4708. Filter.prototype.setOnLoadCallback = function(callback) {
  4709. this.onLoadCallback = callback;
  4710. };
  4711. /**
  4712. * Add a value to the list with available values for this filter
  4713. * No double entries will be created.
  4714. * @param {Number} index
  4715. */
  4716. Filter.prototype.selectValue = function(index) {
  4717. if (index >= this.values.length)
  4718. throw 'Error: index out of range';
  4719. this.index = index;
  4720. this.value = this.values[index];
  4721. };
  4722. /**
  4723. * Load all filtered rows in the background one by one
  4724. * Start this method without providing an index!
  4725. */
  4726. Filter.prototype.loadInBackground = function(index) {
  4727. if (index === undefined)
  4728. index = 0;
  4729. var frame = this.graph.frame;
  4730. if (index < this.values.length) {
  4731. var dataPointsTemp = this._getDataPoints(index);
  4732. //this.graph.redrawInfo(); // TODO: not neat
  4733. // create a progress box
  4734. if (frame.progress === undefined) {
  4735. frame.progress = document.createElement('DIV');
  4736. frame.progress.style.position = 'absolute';
  4737. frame.progress.style.color = 'gray';
  4738. frame.appendChild(frame.progress);
  4739. }
  4740. var progress = this.getLoadedProgress();
  4741. frame.progress.innerHTML = 'Loading animation... ' + progress + '%';
  4742. // TODO: this is no nice solution...
  4743. frame.progress.style.bottom = 60 + 'px'; // TODO: use height of slider
  4744. frame.progress.style.left = 10 + 'px';
  4745. var me = this;
  4746. setTimeout(function() {me.loadInBackground(index+1);}, 10);
  4747. this.loaded = false;
  4748. }
  4749. else {
  4750. this.loaded = true;
  4751. // remove the progress box
  4752. if (frame.progress !== undefined) {
  4753. frame.removeChild(frame.progress);
  4754. frame.progress = undefined;
  4755. }
  4756. if (this.onLoadCallback)
  4757. this.onLoadCallback();
  4758. }
  4759. };
  4760. module.exports = Filter;
  4761. /***/ },
  4762. /* 8 */
  4763. /***/ function(module, exports, __webpack_require__) {
  4764. /**
  4765. * @prototype Point2d
  4766. * @param {Number} [x]
  4767. * @param {Number} [y]
  4768. */
  4769. Point2d = function (x, y) {
  4770. this.x = x !== undefined ? x : 0;
  4771. this.y = y !== undefined ? y : 0;
  4772. };
  4773. module.exports = Point2d;
  4774. /***/ },
  4775. /* 9 */
  4776. /***/ function(module, exports, __webpack_require__) {
  4777. /**
  4778. * @prototype Point3d
  4779. * @param {Number} [x]
  4780. * @param {Number} [y]
  4781. * @param {Number} [z]
  4782. */
  4783. function Point3d(x, y, z) {
  4784. this.x = x !== undefined ? x : 0;
  4785. this.y = y !== undefined ? y : 0;
  4786. this.z = z !== undefined ? z : 0;
  4787. };
  4788. /**
  4789. * Subtract the two provided points, returns a-b
  4790. * @param {Point3d} a
  4791. * @param {Point3d} b
  4792. * @return {Point3d} a-b
  4793. */
  4794. Point3d.subtract = function(a, b) {
  4795. var sub = new Point3d();
  4796. sub.x = a.x - b.x;
  4797. sub.y = a.y - b.y;
  4798. sub.z = a.z - b.z;
  4799. return sub;
  4800. };
  4801. /**
  4802. * Add the two provided points, returns a+b
  4803. * @param {Point3d} a
  4804. * @param {Point3d} b
  4805. * @return {Point3d} a+b
  4806. */
  4807. Point3d.add = function(a, b) {
  4808. var sum = new Point3d();
  4809. sum.x = a.x + b.x;
  4810. sum.y = a.y + b.y;
  4811. sum.z = a.z + b.z;
  4812. return sum;
  4813. };
  4814. /**
  4815. * Calculate the average of two 3d points
  4816. * @param {Point3d} a
  4817. * @param {Point3d} b
  4818. * @return {Point3d} The average, (a+b)/2
  4819. */
  4820. Point3d.avg = function(a, b) {
  4821. return new Point3d(
  4822. (a.x + b.x) / 2,
  4823. (a.y + b.y) / 2,
  4824. (a.z + b.z) / 2
  4825. );
  4826. };
  4827. /**
  4828. * Calculate the cross product of the two provided points, returns axb
  4829. * Documentation: http://en.wikipedia.org/wiki/Cross_product
  4830. * @param {Point3d} a
  4831. * @param {Point3d} b
  4832. * @return {Point3d} cross product axb
  4833. */
  4834. Point3d.crossProduct = function(a, b) {
  4835. var crossproduct = new Point3d();
  4836. crossproduct.x = a.y * b.z - a.z * b.y;
  4837. crossproduct.y = a.z * b.x - a.x * b.z;
  4838. crossproduct.z = a.x * b.y - a.y * b.x;
  4839. return crossproduct;
  4840. };
  4841. /**
  4842. * Rtrieve the length of the vector (or the distance from this point to the origin
  4843. * @return {Number} length
  4844. */
  4845. Point3d.prototype.length = function() {
  4846. return Math.sqrt(
  4847. this.x * this.x +
  4848. this.y * this.y +
  4849. this.z * this.z
  4850. );
  4851. };
  4852. module.exports = Point3d;
  4853. /***/ },
  4854. /* 10 */
  4855. /***/ function(module, exports, __webpack_require__) {
  4856. var util = __webpack_require__(1);
  4857. /**
  4858. * @constructor Slider
  4859. *
  4860. * An html slider control with start/stop/prev/next buttons
  4861. * @param {Element} container The element where the slider will be created
  4862. * @param {Object} options Available options:
  4863. * {boolean} visible If true (default) the
  4864. * slider is visible.
  4865. */
  4866. function Slider(container, options) {
  4867. if (container === undefined) {
  4868. throw 'Error: No container element defined';
  4869. }
  4870. this.container = container;
  4871. this.visible = (options && options.visible != undefined) ? options.visible : true;
  4872. if (this.visible) {
  4873. this.frame = document.createElement('DIV');
  4874. //this.frame.style.backgroundColor = '#E5E5E5';
  4875. this.frame.style.width = '100%';
  4876. this.frame.style.position = 'relative';
  4877. this.container.appendChild(this.frame);
  4878. this.frame.prev = document.createElement('INPUT');
  4879. this.frame.prev.type = 'BUTTON';
  4880. this.frame.prev.value = 'Prev';
  4881. this.frame.appendChild(this.frame.prev);
  4882. this.frame.play = document.createElement('INPUT');
  4883. this.frame.play.type = 'BUTTON';
  4884. this.frame.play.value = 'Play';
  4885. this.frame.appendChild(this.frame.play);
  4886. this.frame.next = document.createElement('INPUT');
  4887. this.frame.next.type = 'BUTTON';
  4888. this.frame.next.value = 'Next';
  4889. this.frame.appendChild(this.frame.next);
  4890. this.frame.bar = document.createElement('INPUT');
  4891. this.frame.bar.type = 'BUTTON';
  4892. this.frame.bar.style.position = 'absolute';
  4893. this.frame.bar.style.border = '1px solid red';
  4894. this.frame.bar.style.width = '100px';
  4895. this.frame.bar.style.height = '6px';
  4896. this.frame.bar.style.borderRadius = '2px';
  4897. this.frame.bar.style.MozBorderRadius = '2px';
  4898. this.frame.bar.style.border = '1px solid #7F7F7F';
  4899. this.frame.bar.style.backgroundColor = '#E5E5E5';
  4900. this.frame.appendChild(this.frame.bar);
  4901. this.frame.slide = document.createElement('INPUT');
  4902. this.frame.slide.type = 'BUTTON';
  4903. this.frame.slide.style.margin = '0px';
  4904. this.frame.slide.value = ' ';
  4905. this.frame.slide.style.position = 'relative';
  4906. this.frame.slide.style.left = '-100px';
  4907. this.frame.appendChild(this.frame.slide);
  4908. // create events
  4909. var me = this;
  4910. this.frame.slide.onmousedown = function (event) {me._onMouseDown(event);};
  4911. this.frame.prev.onclick = function (event) {me.prev(event);};
  4912. this.frame.play.onclick = function (event) {me.togglePlay(event);};
  4913. this.frame.next.onclick = function (event) {me.next(event);};
  4914. }
  4915. this.onChangeCallback = undefined;
  4916. this.values = [];
  4917. this.index = undefined;
  4918. this.playTimeout = undefined;
  4919. this.playInterval = 1000; // milliseconds
  4920. this.playLoop = true;
  4921. }
  4922. /**
  4923. * Select the previous index
  4924. */
  4925. Slider.prototype.prev = function() {
  4926. var index = this.getIndex();
  4927. if (index > 0) {
  4928. index--;
  4929. this.setIndex(index);
  4930. }
  4931. };
  4932. /**
  4933. * Select the next index
  4934. */
  4935. Slider.prototype.next = function() {
  4936. var index = this.getIndex();
  4937. if (index < this.values.length - 1) {
  4938. index++;
  4939. this.setIndex(index);
  4940. }
  4941. };
  4942. /**
  4943. * Select the next index
  4944. */
  4945. Slider.prototype.playNext = function() {
  4946. var start = new Date();
  4947. var index = this.getIndex();
  4948. if (index < this.values.length - 1) {
  4949. index++;
  4950. this.setIndex(index);
  4951. }
  4952. else if (this.playLoop) {
  4953. // jump to the start
  4954. index = 0;
  4955. this.setIndex(index);
  4956. }
  4957. var end = new Date();
  4958. var diff = (end - start);
  4959. // calculate how much time it to to set the index and to execute the callback
  4960. // function.
  4961. var interval = Math.max(this.playInterval - diff, 0);
  4962. // document.title = diff // TODO: cleanup
  4963. var me = this;
  4964. this.playTimeout = setTimeout(function() {me.playNext();}, interval);
  4965. };
  4966. /**
  4967. * Toggle start or stop playing
  4968. */
  4969. Slider.prototype.togglePlay = function() {
  4970. if (this.playTimeout === undefined) {
  4971. this.play();
  4972. } else {
  4973. this.stop();
  4974. }
  4975. };
  4976. /**
  4977. * Start playing
  4978. */
  4979. Slider.prototype.play = function() {
  4980. // Test whether already playing
  4981. if (this.playTimeout) return;
  4982. this.playNext();
  4983. if (this.frame) {
  4984. this.frame.play.value = 'Stop';
  4985. }
  4986. };
  4987. /**
  4988. * Stop playing
  4989. */
  4990. Slider.prototype.stop = function() {
  4991. clearInterval(this.playTimeout);
  4992. this.playTimeout = undefined;
  4993. if (this.frame) {
  4994. this.frame.play.value = 'Play';
  4995. }
  4996. };
  4997. /**
  4998. * Set a callback function which will be triggered when the value of the
  4999. * slider bar has changed.
  5000. */
  5001. Slider.prototype.setOnChangeCallback = function(callback) {
  5002. this.onChangeCallback = callback;
  5003. };
  5004. /**
  5005. * Set the interval for playing the list
  5006. * @param {Number} interval The interval in milliseconds
  5007. */
  5008. Slider.prototype.setPlayInterval = function(interval) {
  5009. this.playInterval = interval;
  5010. };
  5011. /**
  5012. * Retrieve the current play interval
  5013. * @return {Number} interval The interval in milliseconds
  5014. */
  5015. Slider.prototype.getPlayInterval = function(interval) {
  5016. return this.playInterval;
  5017. };
  5018. /**
  5019. * Set looping on or off
  5020. * @pararm {boolean} doLoop If true, the slider will jump to the start when
  5021. * the end is passed, and will jump to the end
  5022. * when the start is passed.
  5023. */
  5024. Slider.prototype.setPlayLoop = function(doLoop) {
  5025. this.playLoop = doLoop;
  5026. };
  5027. /**
  5028. * Execute the onchange callback function
  5029. */
  5030. Slider.prototype.onChange = function() {
  5031. if (this.onChangeCallback !== undefined) {
  5032. this.onChangeCallback();
  5033. }
  5034. };
  5035. /**
  5036. * redraw the slider on the correct place
  5037. */
  5038. Slider.prototype.redraw = function() {
  5039. if (this.frame) {
  5040. // resize the bar
  5041. this.frame.bar.style.top = (this.frame.clientHeight/2 -
  5042. this.frame.bar.offsetHeight/2) + 'px';
  5043. this.frame.bar.style.width = (this.frame.clientWidth -
  5044. this.frame.prev.clientWidth -
  5045. this.frame.play.clientWidth -
  5046. this.frame.next.clientWidth - 30) + 'px';
  5047. // position the slider button
  5048. var left = this.indexToLeft(this.index);
  5049. this.frame.slide.style.left = (left) + 'px';
  5050. }
  5051. };
  5052. /**
  5053. * Set the list with values for the slider
  5054. * @param {Array} values A javascript array with values (any type)
  5055. */
  5056. Slider.prototype.setValues = function(values) {
  5057. this.values = values;
  5058. if (this.values.length > 0)
  5059. this.setIndex(0);
  5060. else
  5061. this.index = undefined;
  5062. };
  5063. /**
  5064. * Select a value by its index
  5065. * @param {Number} index
  5066. */
  5067. Slider.prototype.setIndex = function(index) {
  5068. if (index < this.values.length) {
  5069. this.index = index;
  5070. this.redraw();
  5071. this.onChange();
  5072. }
  5073. else {
  5074. throw 'Error: index out of range';
  5075. }
  5076. };
  5077. /**
  5078. * retrieve the index of the currently selected vaue
  5079. * @return {Number} index
  5080. */
  5081. Slider.prototype.getIndex = function() {
  5082. return this.index;
  5083. };
  5084. /**
  5085. * retrieve the currently selected value
  5086. * @return {*} value
  5087. */
  5088. Slider.prototype.get = function() {
  5089. return this.values[this.index];
  5090. };
  5091. Slider.prototype._onMouseDown = function(event) {
  5092. // only react on left mouse button down
  5093. var leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
  5094. if (!leftButtonDown) return;
  5095. this.startClientX = event.clientX;
  5096. this.startSlideX = parseFloat(this.frame.slide.style.left);
  5097. this.frame.style.cursor = 'move';
  5098. // add event listeners to handle moving the contents
  5099. // we store the function onmousemove and onmouseup in the graph, so we can
  5100. // remove the eventlisteners lateron in the function mouseUp()
  5101. var me = this;
  5102. this.onmousemove = function (event) {me._onMouseMove(event);};
  5103. this.onmouseup = function (event) {me._onMouseUp(event);};
  5104. util.addEventListener(document, 'mousemove', this.onmousemove);
  5105. util.addEventListener(document, 'mouseup', this.onmouseup);
  5106. util.preventDefault(event);
  5107. };
  5108. Slider.prototype.leftToIndex = function (left) {
  5109. var width = parseFloat(this.frame.bar.style.width) -
  5110. this.frame.slide.clientWidth - 10;
  5111. var x = left - 3;
  5112. var index = Math.round(x / width * (this.values.length-1));
  5113. if (index < 0) index = 0;
  5114. if (index > this.values.length-1) index = this.values.length-1;
  5115. return index;
  5116. };
  5117. Slider.prototype.indexToLeft = function (index) {
  5118. var width = parseFloat(this.frame.bar.style.width) -
  5119. this.frame.slide.clientWidth - 10;
  5120. var x = index / (this.values.length-1) * width;
  5121. var left = x + 3;
  5122. return left;
  5123. };
  5124. Slider.prototype._onMouseMove = function (event) {
  5125. var diff = event.clientX - this.startClientX;
  5126. var x = this.startSlideX + diff;
  5127. var index = this.leftToIndex(x);
  5128. this.setIndex(index);
  5129. util.preventDefault();
  5130. };
  5131. Slider.prototype._onMouseUp = function (event) {
  5132. this.frame.style.cursor = 'auto';
  5133. // remove event listeners
  5134. util.removeEventListener(document, 'mousemove', this.onmousemove);
  5135. util.removeEventListener(document, 'mouseup', this.onmouseup);
  5136. util.preventDefault();
  5137. };
  5138. module.exports = Slider;
  5139. /***/ },
  5140. /* 11 */
  5141. /***/ function(module, exports, __webpack_require__) {
  5142. /**
  5143. * @prototype StepNumber
  5144. * The class StepNumber is an iterator for Numbers. You provide a start and end
  5145. * value, and a best step size. StepNumber itself rounds to fixed values and
  5146. * a finds the step that best fits the provided step.
  5147. *
  5148. * If prettyStep is true, the step size is chosen as close as possible to the
  5149. * provided step, but being a round value like 1, 2, 5, 10, 20, 50, ....
  5150. *
  5151. * Example usage:
  5152. * var step = new StepNumber(0, 10, 2.5, true);
  5153. * step.start();
  5154. * while (!step.end()) {
  5155. * alert(step.getCurrent());
  5156. * step.next();
  5157. * }
  5158. *
  5159. * Version: 1.0
  5160. *
  5161. * @param {Number} start The start value
  5162. * @param {Number} end The end value
  5163. * @param {Number} step Optional. Step size. Must be a positive value.
  5164. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  5165. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  5166. */
  5167. function StepNumber(start, end, step, prettyStep) {
  5168. // set default values
  5169. this._start = 0;
  5170. this._end = 0;
  5171. this._step = 1;
  5172. this.prettyStep = true;
  5173. this.precision = 5;
  5174. this._current = 0;
  5175. this.setRange(start, end, step, prettyStep);
  5176. };
  5177. /**
  5178. * Set a new range: start, end and step.
  5179. *
  5180. * @param {Number} start The start value
  5181. * @param {Number} end The end value
  5182. * @param {Number} step Optional. Step size. Must be a positive value.
  5183. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  5184. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  5185. */
  5186. StepNumber.prototype.setRange = function(start, end, step, prettyStep) {
  5187. this._start = start ? start : 0;
  5188. this._end = end ? end : 0;
  5189. this.setStep(step, prettyStep);
  5190. };
  5191. /**
  5192. * Set a new step size
  5193. * @param {Number} step New step size. Must be a positive value
  5194. * @param {boolean} prettyStep Optional. If true, the provided step is rounded
  5195. * to a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  5196. */
  5197. StepNumber.prototype.setStep = function(step, prettyStep) {
  5198. if (step === undefined || step <= 0)
  5199. return;
  5200. if (prettyStep !== undefined)
  5201. this.prettyStep = prettyStep;
  5202. if (this.prettyStep === true)
  5203. this._step = StepNumber.calculatePrettyStep(step);
  5204. else
  5205. this._step = step;
  5206. };
  5207. /**
  5208. * Calculate a nice step size, closest to the desired step size.
  5209. * Returns a value in one of the ranges 1*10^n, 2*10^n, or 5*10^n, where n is an
  5210. * integer Number. For example 1, 2, 5, 10, 20, 50, etc...
  5211. * @param {Number} step Desired step size
  5212. * @return {Number} Nice step size
  5213. */
  5214. StepNumber.calculatePrettyStep = function (step) {
  5215. var log10 = function (x) {return Math.log(x) / Math.LN10;};
  5216. // try three steps (multiple of 1, 2, or 5
  5217. var step1 = Math.pow(10, Math.round(log10(step))),
  5218. step2 = 2 * Math.pow(10, Math.round(log10(step / 2))),
  5219. step5 = 5 * Math.pow(10, Math.round(log10(step / 5)));
  5220. // choose the best step (closest to minimum step)
  5221. var prettyStep = step1;
  5222. if (Math.abs(step2 - step) <= Math.abs(prettyStep - step)) prettyStep = step2;
  5223. if (Math.abs(step5 - step) <= Math.abs(prettyStep - step)) prettyStep = step5;
  5224. // for safety
  5225. if (prettyStep <= 0) {
  5226. prettyStep = 1;
  5227. }
  5228. return prettyStep;
  5229. };
  5230. /**
  5231. * returns the current value of the step
  5232. * @return {Number} current value
  5233. */
  5234. StepNumber.prototype.getCurrent = function () {
  5235. return parseFloat(this._current.toPrecision(this.precision));
  5236. };
  5237. /**
  5238. * returns the current step size
  5239. * @return {Number} current step size
  5240. */
  5241. StepNumber.prototype.getStep = function () {
  5242. return this._step;
  5243. };
  5244. /**
  5245. * Set the current value to the largest value smaller than start, which
  5246. * is a multiple of the step size
  5247. */
  5248. StepNumber.prototype.start = function() {
  5249. this._current = this._start - this._start % this._step;
  5250. };
  5251. /**
  5252. * Do a step, add the step size to the current value
  5253. */
  5254. StepNumber.prototype.next = function () {
  5255. this._current += this._step;
  5256. };
  5257. /**
  5258. * Returns true whether the end is reached
  5259. * @return {boolean} True if the current value has passed the end value.
  5260. */
  5261. StepNumber.prototype.end = function () {
  5262. return (this._current > this._end);
  5263. };
  5264. module.exports = StepNumber;
  5265. /***/ },
  5266. /* 12 */
  5267. /***/ function(module, exports, __webpack_require__) {
  5268. var Emitter = __webpack_require__(45);
  5269. var Hammer = __webpack_require__(41);
  5270. var util = __webpack_require__(1);
  5271. var DataSet = __webpack_require__(3);
  5272. var DataView = __webpack_require__(4);
  5273. var Range = __webpack_require__(15);
  5274. var TimeAxis = __webpack_require__(27);
  5275. var CurrentTime = __webpack_require__(19);
  5276. var CustomTime = __webpack_require__(20);
  5277. var ItemSet = __webpack_require__(24);
  5278. /**
  5279. * Create a timeline visualization
  5280. * @param {HTMLElement} container
  5281. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  5282. * @param {Object} [options] See Timeline.setOptions for the available options.
  5283. * @constructor
  5284. */
  5285. function Timeline (container, items, options) {
  5286. if (!(this instanceof Timeline)) {
  5287. throw new SyntaxError('Constructor must be called with the new operator');
  5288. }
  5289. var me = this;
  5290. this.defaultOptions = {
  5291. start: null,
  5292. end: null,
  5293. autoResize: true,
  5294. orientation: 'bottom',
  5295. width: null,
  5296. height: null,
  5297. maxHeight: null,
  5298. minHeight: null
  5299. };
  5300. this.options = util.deepExtend({}, this.defaultOptions);
  5301. // Create the DOM, props, and emitter
  5302. this._create(container);
  5303. // all components listed here will be repainted automatically
  5304. this.components = [];
  5305. this.body = {
  5306. dom: this.dom,
  5307. domProps: this.props,
  5308. emitter: {
  5309. on: this.on.bind(this),
  5310. off: this.off.bind(this),
  5311. emit: this.emit.bind(this)
  5312. },
  5313. util: {
  5314. snap: null, // will be specified after TimeAxis is created
  5315. toScreen: me._toScreen.bind(me),
  5316. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  5317. toTime: me._toTime.bind(me),
  5318. toGlobalTime : me._toGlobalTime.bind(me)
  5319. }
  5320. };
  5321. // range
  5322. this.range = new Range(this.body);
  5323. this.components.push(this.range);
  5324. this.body.range = this.range;
  5325. // time axis
  5326. this.timeAxis = new TimeAxis(this.body);
  5327. this.components.push(this.timeAxis);
  5328. this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
  5329. // current time bar
  5330. this.currentTime = new CurrentTime(this.body);
  5331. this.components.push(this.currentTime);
  5332. // custom time bar
  5333. // Note: time bar will be attached in this.setOptions when selected
  5334. this.customTime = new CustomTime(this.body);
  5335. this.components.push(this.customTime);
  5336. // item set
  5337. this.itemSet = new ItemSet(this.body);
  5338. this.components.push(this.itemSet);
  5339. this.itemsData = null; // DataSet
  5340. this.groupsData = null; // DataSet
  5341. // apply options
  5342. if (options) {
  5343. this.setOptions(options);
  5344. }
  5345. // create itemset
  5346. if (items) {
  5347. this.setItems(items);
  5348. }
  5349. else {
  5350. this.redraw();
  5351. }
  5352. }
  5353. // turn Timeline into an event emitter
  5354. Emitter(Timeline.prototype);
  5355. /**
  5356. * Create the main DOM for the Timeline: a root panel containing left, right,
  5357. * top, bottom, content, and background panel.
  5358. * @param {Element} container The container element where the Timeline will
  5359. * be attached.
  5360. * @private
  5361. */
  5362. Timeline.prototype._create = function (container) {
  5363. this.dom = {};
  5364. this.dom.root = document.createElement('div');
  5365. this.dom.background = document.createElement('div');
  5366. this.dom.backgroundVertical = document.createElement('div');
  5367. this.dom.backgroundHorizontal = document.createElement('div');
  5368. this.dom.centerContainer = document.createElement('div');
  5369. this.dom.leftContainer = document.createElement('div');
  5370. this.dom.rightContainer = document.createElement('div');
  5371. this.dom.center = document.createElement('div');
  5372. this.dom.left = document.createElement('div');
  5373. this.dom.right = document.createElement('div');
  5374. this.dom.top = document.createElement('div');
  5375. this.dom.bottom = document.createElement('div');
  5376. this.dom.shadowTop = document.createElement('div');
  5377. this.dom.shadowBottom = document.createElement('div');
  5378. this.dom.shadowTopLeft = document.createElement('div');
  5379. this.dom.shadowBottomLeft = document.createElement('div');
  5380. this.dom.shadowTopRight = document.createElement('div');
  5381. this.dom.shadowBottomRight = document.createElement('div');
  5382. this.dom.background.className = 'vispanel background';
  5383. this.dom.backgroundVertical.className = 'vispanel background vertical';
  5384. this.dom.backgroundHorizontal.className = 'vispanel background horizontal';
  5385. this.dom.centerContainer.className = 'vispanel center';
  5386. this.dom.leftContainer.className = 'vispanel left';
  5387. this.dom.rightContainer.className = 'vispanel right';
  5388. this.dom.top.className = 'vispanel top';
  5389. this.dom.bottom.className = 'vispanel bottom';
  5390. this.dom.left.className = 'content';
  5391. this.dom.center.className = 'content';
  5392. this.dom.right.className = 'content';
  5393. this.dom.shadowTop.className = 'shadow top';
  5394. this.dom.shadowBottom.className = 'shadow bottom';
  5395. this.dom.shadowTopLeft.className = 'shadow top';
  5396. this.dom.shadowBottomLeft.className = 'shadow bottom';
  5397. this.dom.shadowTopRight.className = 'shadow top';
  5398. this.dom.shadowBottomRight.className = 'shadow bottom';
  5399. this.dom.root.appendChild(this.dom.background);
  5400. this.dom.root.appendChild(this.dom.backgroundVertical);
  5401. this.dom.root.appendChild(this.dom.backgroundHorizontal);
  5402. this.dom.root.appendChild(this.dom.centerContainer);
  5403. this.dom.root.appendChild(this.dom.leftContainer);
  5404. this.dom.root.appendChild(this.dom.rightContainer);
  5405. this.dom.root.appendChild(this.dom.top);
  5406. this.dom.root.appendChild(this.dom.bottom);
  5407. this.dom.centerContainer.appendChild(this.dom.center);
  5408. this.dom.leftContainer.appendChild(this.dom.left);
  5409. this.dom.rightContainer.appendChild(this.dom.right);
  5410. this.dom.centerContainer.appendChild(this.dom.shadowTop);
  5411. this.dom.centerContainer.appendChild(this.dom.shadowBottom);
  5412. this.dom.leftContainer.appendChild(this.dom.shadowTopLeft);
  5413. this.dom.leftContainer.appendChild(this.dom.shadowBottomLeft);
  5414. this.dom.rightContainer.appendChild(this.dom.shadowTopRight);
  5415. this.dom.rightContainer.appendChild(this.dom.shadowBottomRight);
  5416. this.on('rangechange', this.redraw.bind(this));
  5417. this.on('change', this.redraw.bind(this));
  5418. this.on('touch', this._onTouch.bind(this));
  5419. this.on('pinch', this._onPinch.bind(this));
  5420. this.on('dragstart', this._onDragStart.bind(this));
  5421. this.on('drag', this._onDrag.bind(this));
  5422. // create event listeners for all interesting events, these events will be
  5423. // emitted via emitter
  5424. this.hammer = Hammer(this.dom.root, {
  5425. prevent_default: true
  5426. });
  5427. this.listeners = {};
  5428. var me = this;
  5429. var events = [
  5430. 'touch', 'pinch',
  5431. 'tap', 'doubletap', 'hold',
  5432. 'dragstart', 'drag', 'dragend',
  5433. 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
  5434. ];
  5435. events.forEach(function (event) {
  5436. var listener = function () {
  5437. var args = [event].concat(Array.prototype.slice.call(arguments, 0));
  5438. me.emit.apply(me, args);
  5439. };
  5440. me.hammer.on(event, listener);
  5441. me.listeners[event] = listener;
  5442. });
  5443. // size properties of each of the panels
  5444. this.props = {
  5445. root: {},
  5446. background: {},
  5447. centerContainer: {},
  5448. leftContainer: {},
  5449. rightContainer: {},
  5450. center: {},
  5451. left: {},
  5452. right: {},
  5453. top: {},
  5454. bottom: {},
  5455. border: {},
  5456. scrollTop: 0,
  5457. scrollTopMin: 0
  5458. };
  5459. this.touch = {}; // store state information needed for touch events
  5460. // attach the root panel to the provided container
  5461. if (!container) throw new Error('No container provided');
  5462. container.appendChild(this.dom.root);
  5463. };
  5464. /**
  5465. * Destroy the Timeline, clean up all DOM elements and event listeners.
  5466. */
  5467. Timeline.prototype.destroy = function () {
  5468. // unbind datasets
  5469. this.clear();
  5470. // remove all event listeners
  5471. this.off();
  5472. // stop checking for changed size
  5473. this._stopAutoResize();
  5474. // remove from DOM
  5475. if (this.dom.root.parentNode) {
  5476. this.dom.root.parentNode.removeChild(this.dom.root);
  5477. }
  5478. this.dom = null;
  5479. // cleanup hammer touch events
  5480. for (var event in this.listeners) {
  5481. if (this.listeners.hasOwnProperty(event)) {
  5482. delete this.listeners[event];
  5483. }
  5484. }
  5485. this.listeners = null;
  5486. this.hammer = null;
  5487. // give all components the opportunity to cleanup
  5488. this.components.forEach(function (component) {
  5489. component.destroy();
  5490. });
  5491. this.body = null;
  5492. };
  5493. /**
  5494. * Set options. Options will be passed to all components loaded in the Timeline.
  5495. * @param {Object} [options]
  5496. * {String} orientation
  5497. * Vertical orientation for the Timeline,
  5498. * can be 'bottom' (default) or 'top'.
  5499. * {String | Number} width
  5500. * Width for the timeline, a number in pixels or
  5501. * a css string like '1000px' or '75%'. '100%' by default.
  5502. * {String | Number} height
  5503. * Fixed height for the Timeline, a number in pixels or
  5504. * a css string like '400px' or '75%'. If undefined,
  5505. * The Timeline will automatically size such that
  5506. * its contents fit.
  5507. * {String | Number} minHeight
  5508. * Minimum height for the Timeline, a number in pixels or
  5509. * a css string like '400px' or '75%'.
  5510. * {String | Number} maxHeight
  5511. * Maximum height for the Timeline, a number in pixels or
  5512. * a css string like '400px' or '75%'.
  5513. * {Number | Date | String} start
  5514. * Start date for the visible window
  5515. * {Number | Date | String} end
  5516. * End date for the visible window
  5517. */
  5518. Timeline.prototype.setOptions = function (options) {
  5519. if (options) {
  5520. // copy the known options
  5521. var fields = ['width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'start', 'end', 'orientation'];
  5522. util.selectiveExtend(fields, this.options, options);
  5523. // enable/disable autoResize
  5524. this._initAutoResize();
  5525. }
  5526. // propagate options to all components
  5527. this.components.forEach(function (component) {
  5528. component.setOptions(options);
  5529. });
  5530. // TODO: remove deprecation error one day (deprecated since version 0.8.0)
  5531. if (options && options.order) {
  5532. throw new Error('Option order is deprecated. There is no replacement for this feature.');
  5533. }
  5534. // redraw everything
  5535. this.redraw();
  5536. };
  5537. /**
  5538. * Set a custom time bar
  5539. * @param {Date} time
  5540. */
  5541. Timeline.prototype.setCustomTime = function (time) {
  5542. if (!this.customTime) {
  5543. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  5544. }
  5545. this.customTime.setCustomTime(time);
  5546. };
  5547. /**
  5548. * Retrieve the current custom time.
  5549. * @return {Date} customTime
  5550. */
  5551. Timeline.prototype.getCustomTime = function() {
  5552. if (!this.customTime) {
  5553. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  5554. }
  5555. return this.customTime.getCustomTime();
  5556. };
  5557. /**
  5558. * Set items
  5559. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  5560. */
  5561. Timeline.prototype.setItems = function(items) {
  5562. var initialLoad = (this.itemsData == null);
  5563. // convert to type DataSet when needed
  5564. var newDataSet;
  5565. if (!items) {
  5566. newDataSet = null;
  5567. }
  5568. else if (items instanceof DataSet || items instanceof DataView) {
  5569. newDataSet = items;
  5570. }
  5571. else {
  5572. // turn an array into a dataset
  5573. newDataSet = new DataSet(items, {
  5574. type: {
  5575. start: 'Date',
  5576. end: 'Date'
  5577. }
  5578. });
  5579. }
  5580. // set items
  5581. this.itemsData = newDataSet;
  5582. this.itemSet && this.itemSet.setItems(newDataSet);
  5583. if (initialLoad && ('start' in this.options || 'end' in this.options)) {
  5584. this.fit();
  5585. var start = ('start' in this.options) ? util.convert(this.options.start, 'Date') : null;
  5586. var end = ('end' in this.options) ? util.convert(this.options.end, 'Date') : null;
  5587. this.setWindow(start, end);
  5588. }
  5589. };
  5590. /**
  5591. * Get the id's of the currently visible items.
  5592. * @returns {Array} The ids of the visible items
  5593. */
  5594. Timeline.prototype.getVisibleItems = function() {
  5595. return this.itemSet && this.itemSet.getVisibleItems() || [];
  5596. };
  5597. /**
  5598. * Set groups
  5599. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  5600. */
  5601. Timeline.prototype.setGroups = function(groups) {
  5602. // convert to type DataSet when needed
  5603. var newDataSet;
  5604. if (!groups) {
  5605. newDataSet = null;
  5606. }
  5607. else if (groups instanceof DataSet || groups instanceof DataView) {
  5608. newDataSet = groups;
  5609. }
  5610. else {
  5611. // turn an array into a dataset
  5612. newDataSet = new DataSet(groups);
  5613. }
  5614. this.groupsData = newDataSet;
  5615. this.itemSet.setGroups(newDataSet);
  5616. };
  5617. /**
  5618. * Clear the Timeline. By Default, items, groups and options are cleared.
  5619. * Example usage:
  5620. *
  5621. * timeline.clear(); // clear items, groups, and options
  5622. * timeline.clear({options: true}); // clear options only
  5623. *
  5624. * @param {Object} [what] Optionally specify what to clear. By default:
  5625. * {items: true, groups: true, options: true}
  5626. */
  5627. Timeline.prototype.clear = function(what) {
  5628. // clear items
  5629. if (!what || what.items) {
  5630. this.setItems(null);
  5631. }
  5632. // clear groups
  5633. if (!what || what.groups) {
  5634. this.setGroups(null);
  5635. }
  5636. // clear options of timeline and of each of the components
  5637. if (!what || what.options) {
  5638. this.components.forEach(function (component) {
  5639. component.setOptions(component.defaultOptions);
  5640. });
  5641. this.setOptions(this.defaultOptions); // this will also do a redraw
  5642. }
  5643. };
  5644. /**
  5645. * Set Timeline window such that it fits all items
  5646. */
  5647. Timeline.prototype.fit = function() {
  5648. // apply the data range as range
  5649. var dataRange = this.getItemRange();
  5650. // add 5% space on both sides
  5651. var start = dataRange.min;
  5652. var end = dataRange.max;
  5653. if (start != null && end != null) {
  5654. var interval = (end.valueOf() - start.valueOf());
  5655. if (interval <= 0) {
  5656. // prevent an empty interval
  5657. interval = 24 * 60 * 60 * 1000; // 1 day
  5658. }
  5659. start = new Date(start.valueOf() - interval * 0.05);
  5660. end = new Date(end.valueOf() + interval * 0.05);
  5661. }
  5662. // skip range set if there is no start and end date
  5663. if (start === null && end === null) {
  5664. return;
  5665. }
  5666. this.range.setRange(start, end);
  5667. };
  5668. /**
  5669. * Get the data range of the item set.
  5670. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  5671. * When no minimum is found, min==null
  5672. * When no maximum is found, max==null
  5673. */
  5674. Timeline.prototype.getItemRange = function() {
  5675. // calculate min from start filed
  5676. var dataset = this.itemsData.getDataSet(),
  5677. min = null,
  5678. max = null;
  5679. if (dataset) {
  5680. // calculate the minimum value of the field 'start'
  5681. var minItem = dataset.min('start');
  5682. min = minItem ? util.convert(minItem.start, 'Date').valueOf() : null;
  5683. // Note: we convert first to Date and then to number because else
  5684. // a conversion from ISODate to Number will fail
  5685. // calculate maximum value of fields 'start' and 'end'
  5686. var maxStartItem = dataset.max('start');
  5687. if (maxStartItem) {
  5688. max = util.convert(maxStartItem.start, 'Date').valueOf();
  5689. }
  5690. var maxEndItem = dataset.max('end');
  5691. if (maxEndItem) {
  5692. if (max == null) {
  5693. max = util.convert(maxEndItem.end, 'Date').valueOf();
  5694. }
  5695. else {
  5696. max = Math.max(max, util.convert(maxEndItem.end, 'Date').valueOf());
  5697. }
  5698. }
  5699. }
  5700. return {
  5701. min: (min != null) ? new Date(min) : null,
  5702. max: (max != null) ? new Date(max) : null
  5703. };
  5704. };
  5705. /**
  5706. * Set selected items by their id. Replaces the current selection
  5707. * Unknown id's are silently ignored.
  5708. * @param {Array} [ids] An array with zero or more id's of the items to be
  5709. * selected. If ids is an empty array, all items will be
  5710. * unselected.
  5711. */
  5712. Timeline.prototype.setSelection = function(ids) {
  5713. this.itemSet && this.itemSet.setSelection(ids);
  5714. };
  5715. /**
  5716. * Get the selected items by their id
  5717. * @return {Array} ids The ids of the selected items
  5718. */
  5719. Timeline.prototype.getSelection = function() {
  5720. return this.itemSet && this.itemSet.getSelection() || [];
  5721. };
  5722. /**
  5723. * Set the visible window. Both parameters are optional, you can change only
  5724. * start or only end. Syntax:
  5725. *
  5726. * TimeLine.setWindow(start, end)
  5727. * TimeLine.setWindow(range)
  5728. *
  5729. * Where start and end can be a Date, number, or string, and range is an
  5730. * object with properties start and end.
  5731. *
  5732. * @param {Date | Number | String | Object} [start] Start date of visible window
  5733. * @param {Date | Number | String} [end] End date of visible window
  5734. */
  5735. Timeline.prototype.setWindow = function(start, end) {
  5736. if (arguments.length == 1) {
  5737. var range = arguments[0];
  5738. this.range.setRange(range.start, range.end);
  5739. }
  5740. else {
  5741. this.range.setRange(start, end);
  5742. }
  5743. };
  5744. /**
  5745. * Get the visible window
  5746. * @return {{start: Date, end: Date}} Visible range
  5747. */
  5748. Timeline.prototype.getWindow = function() {
  5749. var range = this.range.getRange();
  5750. return {
  5751. start: new Date(range.start),
  5752. end: new Date(range.end)
  5753. };
  5754. };
  5755. /**
  5756. * Force a redraw of the Timeline. Can be useful to manually redraw when
  5757. * option autoResize=false
  5758. */
  5759. Timeline.prototype.redraw = function() {
  5760. var resized = false,
  5761. options = this.options,
  5762. props = this.props,
  5763. dom = this.dom;
  5764. if (!dom) return; // when destroyed
  5765. // update class names
  5766. dom.root.className = 'vis timeline root ' + options.orientation;
  5767. // update root width and height options
  5768. dom.root.style.maxHeight = util.option.asSize(options.maxHeight, '');
  5769. dom.root.style.minHeight = util.option.asSize(options.minHeight, '');
  5770. dom.root.style.width = util.option.asSize(options.width, '');
  5771. // calculate border widths
  5772. props.border.left = (dom.centerContainer.offsetWidth - dom.centerContainer.clientWidth) / 2;
  5773. props.border.right = props.border.left;
  5774. props.border.top = (dom.centerContainer.offsetHeight - dom.centerContainer.clientHeight) / 2;
  5775. props.border.bottom = props.border.top;
  5776. var borderRootHeight= dom.root.offsetHeight - dom.root.clientHeight;
  5777. var borderRootWidth = dom.root.offsetWidth - dom.root.clientWidth;
  5778. // calculate the heights. If any of the side panels is empty, we set the height to
  5779. // minus the border width, such that the border will be invisible
  5780. props.center.height = dom.center.offsetHeight;
  5781. props.left.height = dom.left.offsetHeight;
  5782. props.right.height = dom.right.offsetHeight;
  5783. props.top.height = dom.top.clientHeight || -props.border.top;
  5784. props.bottom.height = dom.bottom.clientHeight || -props.border.bottom;
  5785. // TODO: compensate borders when any of the panels is empty.
  5786. // apply auto height
  5787. // TODO: only calculate autoHeight when needed (else we cause an extra reflow/repaint of the DOM)
  5788. var contentHeight = Math.max(props.left.height, props.center.height, props.right.height);
  5789. var autoHeight = props.top.height + contentHeight + props.bottom.height +
  5790. borderRootHeight + props.border.top + props.border.bottom;
  5791. dom.root.style.height = util.option.asSize(options.height, autoHeight + 'px');
  5792. // calculate heights of the content panels
  5793. props.root.height = dom.root.offsetHeight;
  5794. props.background.height = props.root.height - borderRootHeight;
  5795. var containerHeight = props.root.height - props.top.height - props.bottom.height -
  5796. borderRootHeight;
  5797. props.centerContainer.height = containerHeight;
  5798. props.leftContainer.height = containerHeight;
  5799. props.rightContainer.height = props.leftContainer.height;
  5800. // calculate the widths of the panels
  5801. props.root.width = dom.root.offsetWidth;
  5802. props.background.width = props.root.width - borderRootWidth;
  5803. props.left.width = dom.leftContainer.clientWidth || -props.border.left;
  5804. props.leftContainer.width = props.left.width;
  5805. props.right.width = dom.rightContainer.clientWidth || -props.border.right;
  5806. props.rightContainer.width = props.right.width;
  5807. var centerWidth = props.root.width - props.left.width - props.right.width - borderRootWidth;
  5808. props.center.width = centerWidth;
  5809. props.centerContainer.width = centerWidth;
  5810. props.top.width = centerWidth;
  5811. props.bottom.width = centerWidth;
  5812. // resize the panels
  5813. dom.background.style.height = props.background.height + 'px';
  5814. dom.backgroundVertical.style.height = props.background.height + 'px';
  5815. dom.backgroundHorizontal.style.height = props.centerContainer.height + 'px';
  5816. dom.centerContainer.style.height = props.centerContainer.height + 'px';
  5817. dom.leftContainer.style.height = props.leftContainer.height + 'px';
  5818. dom.rightContainer.style.height = props.rightContainer.height + 'px';
  5819. dom.background.style.width = props.background.width + 'px';
  5820. dom.backgroundVertical.style.width = props.centerContainer.width + 'px';
  5821. dom.backgroundHorizontal.style.width = props.background.width + 'px';
  5822. dom.centerContainer.style.width = props.center.width + 'px';
  5823. dom.top.style.width = props.top.width + 'px';
  5824. dom.bottom.style.width = props.bottom.width + 'px';
  5825. // reposition the panels
  5826. dom.background.style.left = '0';
  5827. dom.background.style.top = '0';
  5828. dom.backgroundVertical.style.left = props.left.width + 'px';
  5829. dom.backgroundVertical.style.top = '0';
  5830. dom.backgroundHorizontal.style.left = '0';
  5831. dom.backgroundHorizontal.style.top = props.top.height + 'px';
  5832. dom.centerContainer.style.left = props.left.width + 'px';
  5833. dom.centerContainer.style.top = props.top.height + 'px';
  5834. dom.leftContainer.style.left = '0';
  5835. dom.leftContainer.style.top = props.top.height + 'px';
  5836. dom.rightContainer.style.left = (props.left.width + props.center.width) + 'px';
  5837. dom.rightContainer.style.top = props.top.height + 'px';
  5838. dom.top.style.left = props.left.width + 'px';
  5839. dom.top.style.top = '0';
  5840. dom.bottom.style.left = props.left.width + 'px';
  5841. dom.bottom.style.top = (props.top.height + props.centerContainer.height) + 'px';
  5842. // update the scrollTop, feasible range for the offset can be changed
  5843. // when the height of the Timeline or of the contents of the center changed
  5844. this._updateScrollTop();
  5845. // reposition the scrollable contents
  5846. var offset = this.props.scrollTop;
  5847. if (options.orientation == 'bottom') {
  5848. offset += Math.max(this.props.centerContainer.height - this.props.center.height -
  5849. this.props.border.top - this.props.border.bottom, 0);
  5850. }
  5851. dom.center.style.left = '0';
  5852. dom.center.style.top = offset + 'px';
  5853. dom.left.style.left = '0';
  5854. dom.left.style.top = offset + 'px';
  5855. dom.right.style.left = '0';
  5856. dom.right.style.top = offset + 'px';
  5857. // show shadows when vertical scrolling is available
  5858. var visibilityTop = this.props.scrollTop == 0 ? 'hidden' : '';
  5859. var visibilityBottom = this.props.scrollTop == this.props.scrollTopMin ? 'hidden' : '';
  5860. dom.shadowTop.style.visibility = visibilityTop;
  5861. dom.shadowBottom.style.visibility = visibilityBottom;
  5862. dom.shadowTopLeft.style.visibility = visibilityTop;
  5863. dom.shadowBottomLeft.style.visibility = visibilityBottom;
  5864. dom.shadowTopRight.style.visibility = visibilityTop;
  5865. dom.shadowBottomRight.style.visibility = visibilityBottom;
  5866. // redraw all components
  5867. this.components.forEach(function (component) {
  5868. resized = component.redraw() || resized;
  5869. });
  5870. if (resized) {
  5871. // keep repainting until all sizes are settled
  5872. this.redraw();
  5873. }
  5874. };
  5875. // TODO: deprecated since version 1.1.0, remove some day
  5876. Timeline.prototype.repaint = function () {
  5877. throw new Error('Function repaint is deprecated. Use redraw instead.');
  5878. };
  5879. /**
  5880. * Convert a position on screen (pixels) to a datetime
  5881. * @param {int} x Position on the screen in pixels
  5882. * @return {Date} time The datetime the corresponds with given position x
  5883. * @private
  5884. */
  5885. // TODO: move this function to Range
  5886. Timeline.prototype._toTime = function(x) {
  5887. var conversion = this.range.conversion(this.props.center.width);
  5888. return new Date(x / conversion.scale + conversion.offset);
  5889. };
  5890. /**
  5891. * Convert a position on the global screen (pixels) to a datetime
  5892. * @param {int} x Position on the screen in pixels
  5893. * @return {Date} time The datetime the corresponds with given position x
  5894. * @private
  5895. */
  5896. // TODO: move this function to Range
  5897. Timeline.prototype._toGlobalTime = function(x) {
  5898. var conversion = this.range.conversion(this.props.root.width);
  5899. return new Date(x / conversion.scale + conversion.offset);
  5900. };
  5901. /**
  5902. * Convert a datetime (Date object) into a position on the screen
  5903. * @param {Date} time A date
  5904. * @return {int} x The position on the screen in pixels which corresponds
  5905. * with the given date.
  5906. * @private
  5907. */
  5908. // TODO: move this function to Range
  5909. Timeline.prototype._toScreen = function(time) {
  5910. var conversion = this.range.conversion(this.props.center.width);
  5911. return (time.valueOf() - conversion.offset) * conversion.scale;
  5912. };
  5913. /**
  5914. * Convert a datetime (Date object) into a position on the root
  5915. * This is used to get the pixel density estimate for the screen, not the center panel
  5916. * @param {Date} time A date
  5917. * @return {int} x The position on root in pixels which corresponds
  5918. * with the given date.
  5919. * @private
  5920. */
  5921. // TODO: move this function to Range
  5922. Timeline.prototype._toGlobalScreen = function(time) {
  5923. var conversion = this.range.conversion(this.props.root.width);
  5924. return (time.valueOf() - conversion.offset) * conversion.scale;
  5925. };
  5926. /**
  5927. * Initialize watching when option autoResize is true
  5928. * @private
  5929. */
  5930. Timeline.prototype._initAutoResize = function () {
  5931. if (this.options.autoResize == true) {
  5932. this._startAutoResize();
  5933. }
  5934. else {
  5935. this._stopAutoResize();
  5936. }
  5937. };
  5938. /**
  5939. * Watch for changes in the size of the container. On resize, the Panel will
  5940. * automatically redraw itself.
  5941. * @private
  5942. */
  5943. Timeline.prototype._startAutoResize = function () {
  5944. var me = this;
  5945. this._stopAutoResize();
  5946. this._onResize = function() {
  5947. if (me.options.autoResize != true) {
  5948. // stop watching when the option autoResize is changed to false
  5949. me._stopAutoResize();
  5950. return;
  5951. }
  5952. if (me.dom.root) {
  5953. // check whether the frame is resized
  5954. if ((me.dom.root.clientWidth != me.props.lastWidth) ||
  5955. (me.dom.root.clientHeight != me.props.lastHeight)) {
  5956. me.props.lastWidth = me.dom.root.clientWidth;
  5957. me.props.lastHeight = me.dom.root.clientHeight;
  5958. me.emit('change');
  5959. }
  5960. }
  5961. };
  5962. // add event listener to window resize
  5963. util.addEventListener(window, 'resize', this._onResize);
  5964. this.watchTimer = setInterval(this._onResize, 1000);
  5965. };
  5966. /**
  5967. * Stop watching for a resize of the frame.
  5968. * @private
  5969. */
  5970. Timeline.prototype._stopAutoResize = function () {
  5971. if (this.watchTimer) {
  5972. clearInterval(this.watchTimer);
  5973. this.watchTimer = undefined;
  5974. }
  5975. // remove event listener on window.resize
  5976. util.removeEventListener(window, 'resize', this._onResize);
  5977. this._onResize = null;
  5978. };
  5979. /**
  5980. * Start moving the timeline vertically
  5981. * @param {Event} event
  5982. * @private
  5983. */
  5984. Timeline.prototype._onTouch = function (event) {
  5985. this.touch.allowDragging = true;
  5986. };
  5987. /**
  5988. * Start moving the timeline vertically
  5989. * @param {Event} event
  5990. * @private
  5991. */
  5992. Timeline.prototype._onPinch = function (event) {
  5993. this.touch.allowDragging = false;
  5994. };
  5995. /**
  5996. * Start moving the timeline vertically
  5997. * @param {Event} event
  5998. * @private
  5999. */
  6000. Timeline.prototype._onDragStart = function (event) {
  6001. this.touch.initialScrollTop = this.props.scrollTop;
  6002. };
  6003. /**
  6004. * Move the timeline vertically
  6005. * @param {Event} event
  6006. * @private
  6007. */
  6008. Timeline.prototype._onDrag = function (event) {
  6009. // refuse to drag when we where pinching to prevent the timeline make a jump
  6010. // when releasing the fingers in opposite order from the touch screen
  6011. if (!this.touch.allowDragging) return;
  6012. var delta = event.gesture.deltaY;
  6013. var oldScrollTop = this._getScrollTop();
  6014. var newScrollTop = this._setScrollTop(this.touch.initialScrollTop + delta);
  6015. if (newScrollTop != oldScrollTop) {
  6016. this.redraw(); // TODO: this causes two redraws when dragging, the other is triggered by rangechange already
  6017. }
  6018. };
  6019. /**
  6020. * Apply a scrollTop
  6021. * @param {Number} scrollTop
  6022. * @returns {Number} scrollTop Returns the applied scrollTop
  6023. * @private
  6024. */
  6025. Timeline.prototype._setScrollTop = function (scrollTop) {
  6026. this.props.scrollTop = scrollTop;
  6027. this._updateScrollTop();
  6028. return this.props.scrollTop;
  6029. };
  6030. /**
  6031. * Update the current scrollTop when the height of the containers has been changed
  6032. * @returns {Number} scrollTop Returns the applied scrollTop
  6033. * @private
  6034. */
  6035. Timeline.prototype._updateScrollTop = function () {
  6036. // recalculate the scrollTopMin
  6037. var scrollTopMin = Math.min(this.props.centerContainer.height - this.props.center.height, 0); // is negative or zero
  6038. if (scrollTopMin != this.props.scrollTopMin) {
  6039. // in case of bottom orientation, change the scrollTop such that the contents
  6040. // do not move relative to the time axis at the bottom
  6041. if (this.options.orientation == 'bottom') {
  6042. this.props.scrollTop += (scrollTopMin - this.props.scrollTopMin);
  6043. }
  6044. this.props.scrollTopMin = scrollTopMin;
  6045. }
  6046. // limit the scrollTop to the feasible scroll range
  6047. if (this.props.scrollTop > 0) this.props.scrollTop = 0;
  6048. if (this.props.scrollTop < scrollTopMin) this.props.scrollTop = scrollTopMin;
  6049. return this.props.scrollTop;
  6050. };
  6051. /**
  6052. * Get the current scrollTop
  6053. * @returns {number} scrollTop
  6054. * @private
  6055. */
  6056. Timeline.prototype._getScrollTop = function () {
  6057. return this.props.scrollTop;
  6058. };
  6059. module.exports = Timeline;
  6060. /***/ },
  6061. /* 13 */
  6062. /***/ function(module, exports, __webpack_require__) {
  6063. var Emitter = __webpack_require__(45);
  6064. var Hammer = __webpack_require__(41);
  6065. var util = __webpack_require__(1);
  6066. var DataSet = __webpack_require__(3);
  6067. var DataView = __webpack_require__(4);
  6068. var Range = __webpack_require__(15);
  6069. var TimeAxis = __webpack_require__(27);
  6070. var CurrentTime = __webpack_require__(19);
  6071. var CustomTime = __webpack_require__(20);
  6072. var LineGraph = __webpack_require__(26);
  6073. /**
  6074. * Create a timeline visualization
  6075. * @param {HTMLElement} container
  6076. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  6077. * @param {Object} [options] See Graph2d.setOptions for the available options.
  6078. * @constructor
  6079. */
  6080. function Graph2d (container, items, options, groups) {
  6081. var me = this;
  6082. this.defaultOptions = {
  6083. start: null,
  6084. end: null,
  6085. autoResize: true,
  6086. orientation: 'bottom',
  6087. width: null,
  6088. height: null,
  6089. maxHeight: null,
  6090. minHeight: null
  6091. };
  6092. this.options = util.deepExtend({}, this.defaultOptions);
  6093. // Create the DOM, props, and emitter
  6094. this._create(container);
  6095. // all components listed here will be repainted automatically
  6096. this.components = [];
  6097. this.body = {
  6098. dom: this.dom,
  6099. domProps: this.props,
  6100. emitter: {
  6101. on: this.on.bind(this),
  6102. off: this.off.bind(this),
  6103. emit: this.emit.bind(this)
  6104. },
  6105. util: {
  6106. snap: null, // will be specified after TimeAxis is created
  6107. toScreen: me._toScreen.bind(me),
  6108. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  6109. toTime: me._toTime.bind(me),
  6110. toGlobalTime : me._toGlobalTime.bind(me)
  6111. }
  6112. };
  6113. // range
  6114. this.range = new Range(this.body);
  6115. this.components.push(this.range);
  6116. this.body.range = this.range;
  6117. // time axis
  6118. this.timeAxis = new TimeAxis(this.body);
  6119. this.components.push(this.timeAxis);
  6120. this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
  6121. // current time bar
  6122. this.currentTime = new CurrentTime(this.body);
  6123. this.components.push(this.currentTime);
  6124. // custom time bar
  6125. // Note: time bar will be attached in this.setOptions when selected
  6126. this.customTime = new CustomTime(this.body);
  6127. this.components.push(this.customTime);
  6128. // item set
  6129. this.linegraph = new LineGraph(this.body);
  6130. this.components.push(this.linegraph);
  6131. this.itemsData = null; // DataSet
  6132. this.groupsData = null; // DataSet
  6133. // apply options
  6134. if (options) {
  6135. this.setOptions(options);
  6136. }
  6137. // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
  6138. if (groups) {
  6139. this.setGroups(groups);
  6140. }
  6141. // create itemset
  6142. if (items) {
  6143. this.setItems(items);
  6144. }
  6145. else {
  6146. this.redraw();
  6147. }
  6148. }
  6149. // turn Graph2d into an event emitter
  6150. Emitter(Graph2d.prototype);
  6151. /**
  6152. * Create the main DOM for the Graph2d: a root panel containing left, right,
  6153. * top, bottom, content, and background panel.
  6154. * @param {Element} container The container element where the Graph2d will
  6155. * be attached.
  6156. * @private
  6157. */
  6158. Graph2d.prototype._create = function (container) {
  6159. this.dom = {};
  6160. this.dom.root = document.createElement('div');
  6161. this.dom.background = document.createElement('div');
  6162. this.dom.backgroundVertical = document.createElement('div');
  6163. this.dom.backgroundHorizontalContainer = document.createElement('div');
  6164. this.dom.centerContainer = document.createElement('div');
  6165. this.dom.leftContainer = document.createElement('div');
  6166. this.dom.rightContainer = document.createElement('div');
  6167. this.dom.backgroundHorizontal = document.createElement('div');
  6168. this.dom.center = document.createElement('div');
  6169. this.dom.left = document.createElement('div');
  6170. this.dom.right = document.createElement('div');
  6171. this.dom.top = document.createElement('div');
  6172. this.dom.bottom = document.createElement('div');
  6173. this.dom.shadowTop = document.createElement('div');
  6174. this.dom.shadowBottom = document.createElement('div');
  6175. this.dom.shadowTopLeft = document.createElement('div');
  6176. this.dom.shadowBottomLeft = document.createElement('div');
  6177. this.dom.shadowTopRight = document.createElement('div');
  6178. this.dom.shadowBottomRight = document.createElement('div');
  6179. this.dom.background.className = 'vispanel background';
  6180. this.dom.backgroundVertical.className = 'vispanel background vertical';
  6181. this.dom.backgroundHorizontalContainer.className = 'vispanel background horizontal';
  6182. this.dom.backgroundHorizontal.className = 'vispanel background horizontal';
  6183. this.dom.centerContainer.className = 'vispanel center';
  6184. this.dom.leftContainer.className = 'vispanel left';
  6185. this.dom.rightContainer.className = 'vispanel right';
  6186. this.dom.top.className = 'vispanel top';
  6187. this.dom.bottom.className = 'vispanel bottom';
  6188. this.dom.left.className = 'content';
  6189. this.dom.center.className = 'content';
  6190. this.dom.right.className = 'content';
  6191. this.dom.shadowTop.className = 'shadow top';
  6192. this.dom.shadowBottom.className = 'shadow bottom';
  6193. this.dom.shadowTopLeft.className = 'shadow top';
  6194. this.dom.shadowBottomLeft.className = 'shadow bottom';
  6195. this.dom.shadowTopRight.className = 'shadow top';
  6196. this.dom.shadowBottomRight.className = 'shadow bottom';
  6197. this.dom.root.appendChild(this.dom.background);
  6198. this.dom.root.appendChild(this.dom.backgroundVertical);
  6199. this.dom.root.appendChild(this.dom.backgroundHorizontalContainer);
  6200. this.dom.root.appendChild(this.dom.centerContainer);
  6201. this.dom.root.appendChild(this.dom.leftContainer);
  6202. this.dom.root.appendChild(this.dom.rightContainer);
  6203. this.dom.root.appendChild(this.dom.top);
  6204. this.dom.root.appendChild(this.dom.bottom);
  6205. this.dom.backgroundHorizontalContainer.appendChild(this.dom.backgroundHorizontal);
  6206. this.dom.centerContainer.appendChild(this.dom.center);
  6207. this.dom.leftContainer.appendChild(this.dom.left);
  6208. this.dom.rightContainer.appendChild(this.dom.right);
  6209. this.dom.centerContainer.appendChild(this.dom.shadowTop);
  6210. this.dom.centerContainer.appendChild(this.dom.shadowBottom);
  6211. this.dom.leftContainer.appendChild(this.dom.shadowTopLeft);
  6212. this.dom.leftContainer.appendChild(this.dom.shadowBottomLeft);
  6213. this.dom.rightContainer.appendChild(this.dom.shadowTopRight);
  6214. this.dom.rightContainer.appendChild(this.dom.shadowBottomRight);
  6215. this.on('rangechange', this.redraw.bind(this));
  6216. this.on('change', this.redraw.bind(this));
  6217. this.on('touch', this._onTouch.bind(this));
  6218. this.on('pinch', this._onPinch.bind(this));
  6219. this.on('dragstart', this._onDragStart.bind(this));
  6220. this.on('drag', this._onDrag.bind(this));
  6221. // create event listeners for all interesting events, these events will be
  6222. // emitted via emitter
  6223. this.hammer = Hammer(this.dom.root, {
  6224. prevent_default: true
  6225. });
  6226. this.listeners = {};
  6227. var me = this;
  6228. var events = [
  6229. 'touch', 'pinch',
  6230. 'tap', 'doubletap', 'hold',
  6231. 'dragstart', 'drag', 'dragend',
  6232. 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
  6233. ];
  6234. events.forEach(function (event) {
  6235. var listener = function () {
  6236. var args = [event].concat(Array.prototype.slice.call(arguments, 0));
  6237. me.emit.apply(me, args);
  6238. };
  6239. me.hammer.on(event, listener);
  6240. me.listeners[event] = listener;
  6241. });
  6242. // size properties of each of the panels
  6243. this.props = {
  6244. root: {},
  6245. background: {},
  6246. centerContainer: {},
  6247. leftContainer: {},
  6248. rightContainer: {},
  6249. center: {},
  6250. left: {},
  6251. right: {},
  6252. top: {},
  6253. bottom: {},
  6254. border: {},
  6255. scrollTop: 0,
  6256. scrollTopMin: 0
  6257. };
  6258. this.touch = {}; // store state information needed for touch events
  6259. // attach the root panel to the provided container
  6260. if (!container) throw new Error('No container provided');
  6261. container.appendChild(this.dom.root);
  6262. };
  6263. /**
  6264. * Destroy the Graph2d, clean up all DOM elements and event listeners.
  6265. */
  6266. Graph2d.prototype.destroy = function () {
  6267. // unbind datasets
  6268. this.clear();
  6269. // remove all event listeners
  6270. this.off();
  6271. // stop checking for changed size
  6272. this._stopAutoResize();
  6273. // remove from DOM
  6274. if (this.dom.root.parentNode) {
  6275. this.dom.root.parentNode.removeChild(this.dom.root);
  6276. }
  6277. this.dom = null;
  6278. // cleanup hammer touch events
  6279. for (var event in this.listeners) {
  6280. if (this.listeners.hasOwnProperty(event)) {
  6281. delete this.listeners[event];
  6282. }
  6283. }
  6284. this.listeners = null;
  6285. this.hammer = null;
  6286. // give all components the opportunity to cleanup
  6287. this.components.forEach(function (component) {
  6288. component.destroy();
  6289. });
  6290. this.body = null;
  6291. };
  6292. /**
  6293. * Set options. Options will be passed to all components loaded in the Graph2d.
  6294. * @param {Object} [options]
  6295. * {String} orientation
  6296. * Vertical orientation for the Graph2d,
  6297. * can be 'bottom' (default) or 'top'.
  6298. * {String | Number} width
  6299. * Width for the timeline, a number in pixels or
  6300. * a css string like '1000px' or '75%'. '100%' by default.
  6301. * {String | Number} height
  6302. * Fixed height for the Graph2d, a number in pixels or
  6303. * a css string like '400px' or '75%'. If undefined,
  6304. * The Graph2d will automatically size such that
  6305. * its contents fit.
  6306. * {String | Number} minHeight
  6307. * Minimum height for the Graph2d, a number in pixels or
  6308. * a css string like '400px' or '75%'.
  6309. * {String | Number} maxHeight
  6310. * Maximum height for the Graph2d, a number in pixels or
  6311. * a css string like '400px' or '75%'.
  6312. * {Number | Date | String} start
  6313. * Start date for the visible window
  6314. * {Number | Date | String} end
  6315. * End date for the visible window
  6316. */
  6317. Graph2d.prototype.setOptions = function (options) {
  6318. if (options) {
  6319. // copy the known options
  6320. var fields = ['width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'start', 'end', 'orientation'];
  6321. util.selectiveExtend(fields, this.options, options);
  6322. // enable/disable autoResize
  6323. this._initAutoResize();
  6324. }
  6325. // propagate options to all components
  6326. this.components.forEach(function (component) {
  6327. component.setOptions(options);
  6328. });
  6329. // TODO: remove deprecation error one day (deprecated since version 0.8.0)
  6330. if (options && options.order) {
  6331. throw new Error('Option order is deprecated. There is no replacement for this feature.');
  6332. }
  6333. // redraw everything
  6334. this.redraw();
  6335. };
  6336. /**
  6337. * Set a custom time bar
  6338. * @param {Date} time
  6339. */
  6340. Graph2d.prototype.setCustomTime = function (time) {
  6341. if (!this.customTime) {
  6342. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  6343. }
  6344. this.customTime.setCustomTime(time);
  6345. };
  6346. /**
  6347. * Retrieve the current custom time.
  6348. * @return {Date} customTime
  6349. */
  6350. Graph2d.prototype.getCustomTime = function() {
  6351. if (!this.customTime) {
  6352. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  6353. }
  6354. return this.customTime.getCustomTime();
  6355. };
  6356. /**
  6357. * Set items
  6358. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  6359. */
  6360. Graph2d.prototype.setItems = function(items) {
  6361. var initialLoad = (this.itemsData == null);
  6362. // convert to type DataSet when needed
  6363. var newDataSet;
  6364. if (!items) {
  6365. newDataSet = null;
  6366. }
  6367. else if (items instanceof DataSet || items instanceof DataView) {
  6368. newDataSet = items;
  6369. }
  6370. else {
  6371. // turn an array into a dataset
  6372. newDataSet = new DataSet(items, {
  6373. type: {
  6374. start: 'Date',
  6375. end: 'Date'
  6376. }
  6377. });
  6378. }
  6379. // set items
  6380. this.itemsData = newDataSet;
  6381. this.linegraph && this.linegraph.setItems(newDataSet);
  6382. if (initialLoad && ('start' in this.options || 'end' in this.options)) {
  6383. this.fit();
  6384. var start = ('start' in this.options) ? util.convert(this.options.start, 'Date') : null;
  6385. var end = ('end' in this.options) ? util.convert(this.options.end, 'Date') : null;
  6386. this.setWindow(start, end);
  6387. }
  6388. };
  6389. /**
  6390. * Set groups
  6391. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  6392. */
  6393. Graph2d.prototype.setGroups = function(groups) {
  6394. // convert to type DataSet when needed
  6395. var newDataSet;
  6396. if (!groups) {
  6397. newDataSet = null;
  6398. }
  6399. else if (groups instanceof DataSet || groups instanceof DataView) {
  6400. newDataSet = groups;
  6401. }
  6402. else {
  6403. // turn an array into a dataset
  6404. newDataSet = new DataSet(groups);
  6405. }
  6406. this.groupsData = newDataSet;
  6407. this.linegraph.setGroups(newDataSet);
  6408. };
  6409. /**
  6410. * Clear the Graph2d. By Default, items, groups and options are cleared.
  6411. * Example usage:
  6412. *
  6413. * timeline.clear(); // clear items, groups, and options
  6414. * timeline.clear({options: true}); // clear options only
  6415. *
  6416. * @param {Object} [what] Optionally specify what to clear. By default:
  6417. * {items: true, groups: true, options: true}
  6418. */
  6419. Graph2d.prototype.clear = function(what) {
  6420. // clear items
  6421. if (!what || what.items) {
  6422. this.setItems(null);
  6423. }
  6424. // clear groups
  6425. if (!what || what.groups) {
  6426. this.setGroups(null);
  6427. }
  6428. // clear options of timeline and of each of the components
  6429. if (!what || what.options) {
  6430. this.components.forEach(function (component) {
  6431. component.setOptions(component.defaultOptions);
  6432. });
  6433. this.setOptions(this.defaultOptions); // this will also do a redraw
  6434. }
  6435. };
  6436. /**
  6437. * Set Graph2d window such that it fits all items
  6438. */
  6439. Graph2d.prototype.fit = function() {
  6440. // apply the data range as range
  6441. var dataRange = this.getItemRange();
  6442. // add 5% space on both sides
  6443. var start = dataRange.min;
  6444. var end = dataRange.max;
  6445. if (start != null && end != null) {
  6446. var interval = (end.valueOf() - start.valueOf());
  6447. if (interval <= 0) {
  6448. // prevent an empty interval
  6449. interval = 24 * 60 * 60 * 1000; // 1 day
  6450. }
  6451. start = new Date(start.valueOf() - interval * 0.05);
  6452. end = new Date(end.valueOf() + interval * 0.05);
  6453. }
  6454. // skip range set if there is no start and end date
  6455. if (start === null && end === null) {
  6456. return;
  6457. }
  6458. this.range.setRange(start, end);
  6459. };
  6460. /**
  6461. * Get the data range of the item set.
  6462. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  6463. * When no minimum is found, min==null
  6464. * When no maximum is found, max==null
  6465. */
  6466. Graph2d.prototype.getItemRange = function() {
  6467. // calculate min from start filed
  6468. var itemsData = this.itemsData,
  6469. min = null,
  6470. max = null;
  6471. if (itemsData) {
  6472. // calculate the minimum value of the field 'start'
  6473. var minItem = itemsData.min('start');
  6474. min = minItem ? util.convert(minItem.start, 'Date').valueOf() : null;
  6475. // Note: we convert first to Date and then to number because else
  6476. // a conversion from ISODate to Number will fail
  6477. // calculate maximum value of fields 'start' and 'end'
  6478. var maxStartItem = itemsData.max('start');
  6479. if (maxStartItem) {
  6480. max = util.convert(maxStartItem.start, 'Date').valueOf();
  6481. }
  6482. var maxEndItem = itemsData.max('end');
  6483. if (maxEndItem) {
  6484. if (max == null) {
  6485. max = util.convert(maxEndItem.end, 'Date').valueOf();
  6486. }
  6487. else {
  6488. max = Math.max(max, util.convert(maxEndItem.end, 'Date').valueOf());
  6489. }
  6490. }
  6491. }
  6492. return {
  6493. min: (min != null) ? new Date(min) : null,
  6494. max: (max != null) ? new Date(max) : null
  6495. };
  6496. };
  6497. /**
  6498. * Set the visible window. Both parameters are optional, you can change only
  6499. * start or only end. Syntax:
  6500. *
  6501. * TimeLine.setWindow(start, end)
  6502. * TimeLine.setWindow(range)
  6503. *
  6504. * Where start and end can be a Date, number, or string, and range is an
  6505. * object with properties start and end.
  6506. *
  6507. * @param {Date | Number | String | Object} [start] Start date of visible window
  6508. * @param {Date | Number | String} [end] End date of visible window
  6509. */
  6510. Graph2d.prototype.setWindow = function(start, end) {
  6511. if (arguments.length == 1) {
  6512. var range = arguments[0];
  6513. this.range.setRange(range.start, range.end);
  6514. }
  6515. else {
  6516. this.range.setRange(start, end);
  6517. }
  6518. };
  6519. /**
  6520. * Get the visible window
  6521. * @return {{start: Date, end: Date}} Visible range
  6522. */
  6523. Graph2d.prototype.getWindow = function() {
  6524. var range = this.range.getRange();
  6525. return {
  6526. start: new Date(range.start),
  6527. end: new Date(range.end)
  6528. };
  6529. };
  6530. /**
  6531. * Force a redraw of the Graph2d. Can be useful to manually redraw when
  6532. * option autoResize=false
  6533. */
  6534. Graph2d.prototype.redraw = function() {
  6535. var resized = false,
  6536. options = this.options,
  6537. props = this.props,
  6538. dom = this.dom;
  6539. if (!dom) return; // when destroyed
  6540. // update class names
  6541. dom.root.className = 'vis timeline root ' + options.orientation;
  6542. // update root width and height options
  6543. dom.root.style.maxHeight = util.option.asSize(options.maxHeight, '');
  6544. dom.root.style.minHeight = util.option.asSize(options.minHeight, '');
  6545. dom.root.style.width = util.option.asSize(options.width, '');
  6546. // calculate border widths
  6547. props.border.left = (dom.centerContainer.offsetWidth - dom.centerContainer.clientWidth) / 2;
  6548. props.border.right = props.border.left;
  6549. props.border.top = (dom.centerContainer.offsetHeight - dom.centerContainer.clientHeight) / 2;
  6550. props.border.bottom = props.border.top;
  6551. var borderRootHeight= dom.root.offsetHeight - dom.root.clientHeight;
  6552. var borderRootWidth = dom.root.offsetWidth - dom.root.clientWidth;
  6553. // calculate the heights. If any of the side panels is empty, we set the height to
  6554. // minus the border width, such that the border will be invisible
  6555. props.center.height = dom.center.offsetHeight;
  6556. props.left.height = dom.left.offsetHeight;
  6557. props.right.height = dom.right.offsetHeight;
  6558. props.top.height = dom.top.clientHeight || -props.border.top;
  6559. props.bottom.height = dom.bottom.clientHeight || -props.border.bottom;
  6560. // TODO: compensate borders when any of the panels is empty.
  6561. // apply auto height
  6562. // TODO: only calculate autoHeight when needed (else we cause an extra reflow/repaint of the DOM)
  6563. var contentHeight = Math.max(props.left.height, props.center.height, props.right.height);
  6564. var autoHeight = props.top.height + contentHeight + props.bottom.height +
  6565. borderRootHeight + props.border.top + props.border.bottom;
  6566. dom.root.style.height = util.option.asSize(options.height, autoHeight + 'px');
  6567. // calculate heights of the content panels
  6568. props.root.height = dom.root.offsetHeight;
  6569. props.background.height = props.root.height - borderRootHeight;
  6570. var containerHeight = props.root.height - props.top.height - props.bottom.height -
  6571. borderRootHeight;
  6572. props.centerContainer.height = containerHeight;
  6573. props.leftContainer.height = containerHeight;
  6574. props.rightContainer.height = props.leftContainer.height;
  6575. // calculate the widths of the panels
  6576. props.root.width = dom.root.offsetWidth;
  6577. props.background.width = props.root.width - borderRootWidth;
  6578. props.left.width = dom.leftContainer.clientWidth || -props.border.left;
  6579. props.leftContainer.width = props.left.width;
  6580. props.right.width = dom.rightContainer.clientWidth || -props.border.right;
  6581. props.rightContainer.width = props.right.width;
  6582. var centerWidth = props.root.width - props.left.width - props.right.width - borderRootWidth;
  6583. props.center.width = centerWidth;
  6584. props.centerContainer.width = centerWidth;
  6585. props.top.width = centerWidth;
  6586. props.bottom.width = centerWidth;
  6587. // resize the panels
  6588. dom.background.style.height = props.background.height + 'px';
  6589. dom.backgroundVertical.style.height = props.background.height + 'px';
  6590. dom.backgroundHorizontalContainer.style.height = props.centerContainer.height + 'px';
  6591. dom.centerContainer.style.height = props.centerContainer.height + 'px';
  6592. dom.leftContainer.style.height = props.leftContainer.height + 'px';
  6593. dom.rightContainer.style.height = props.rightContainer.height + 'px';
  6594. dom.background.style.width = props.background.width + 'px';
  6595. dom.backgroundVertical.style.width = props.centerContainer.width + 'px';
  6596. dom.backgroundHorizontalContainer.style.width = props.background.width + 'px';
  6597. dom.backgroundHorizontal.style.width = props.background.width + 'px';
  6598. dom.centerContainer.style.width = props.center.width + 'px';
  6599. dom.top.style.width = props.top.width + 'px';
  6600. dom.bottom.style.width = props.bottom.width + 'px';
  6601. // reposition the panels
  6602. dom.background.style.left = '0';
  6603. dom.background.style.top = '0';
  6604. dom.backgroundVertical.style.left = props.left.width + 'px';
  6605. dom.backgroundVertical.style.top = '0';
  6606. dom.backgroundHorizontalContainer.style.left = '0';
  6607. dom.backgroundHorizontalContainer.style.top = props.top.height + 'px';
  6608. dom.centerContainer.style.left = props.left.width + 'px';
  6609. dom.centerContainer.style.top = props.top.height + 'px';
  6610. dom.leftContainer.style.left = '0';
  6611. dom.leftContainer.style.top = props.top.height + 'px';
  6612. dom.rightContainer.style.left = (props.left.width + props.center.width) + 'px';
  6613. dom.rightContainer.style.top = props.top.height + 'px';
  6614. dom.top.style.left = props.left.width + 'px';
  6615. dom.top.style.top = '0';
  6616. dom.bottom.style.left = props.left.width + 'px';
  6617. dom.bottom.style.top = (props.top.height + props.centerContainer.height) + 'px';
  6618. // update the scrollTop, feasible range for the offset can be changed
  6619. // when the height of the Graph2d or of the contents of the center changed
  6620. this._updateScrollTop();
  6621. // reposition the scrollable contents
  6622. var offset = this.props.scrollTop;
  6623. if (options.orientation == 'bottom') {
  6624. offset += Math.max(this.props.centerContainer.height - this.props.center.height -
  6625. this.props.border.top - this.props.border.bottom, 0);
  6626. }
  6627. dom.center.style.left = '0';
  6628. dom.center.style.top = offset + 'px';
  6629. dom.backgroundHorizontal.style.left = '0';
  6630. dom.backgroundHorizontal.style.top = offset + 'px';
  6631. dom.left.style.left = '0';
  6632. dom.left.style.top = offset + 'px';
  6633. dom.right.style.left = '0';
  6634. dom.right.style.top = offset + 'px';
  6635. // show shadows when vertical scrolling is available
  6636. var visibilityTop = this.props.scrollTop == 0 ? 'hidden' : '';
  6637. var visibilityBottom = this.props.scrollTop == this.props.scrollTopMin ? 'hidden' : '';
  6638. dom.shadowTop.style.visibility = visibilityTop;
  6639. dom.shadowBottom.style.visibility = visibilityBottom;
  6640. dom.shadowTopLeft.style.visibility = visibilityTop;
  6641. dom.shadowBottomLeft.style.visibility = visibilityBottom;
  6642. dom.shadowTopRight.style.visibility = visibilityTop;
  6643. dom.shadowBottomRight.style.visibility = visibilityBottom;
  6644. // redraw all components
  6645. this.components.forEach(function (component) {
  6646. resized = component.redraw() || resized;
  6647. });
  6648. if (resized) {
  6649. // keep redrawing until all sizes are settled
  6650. this.redraw();
  6651. }
  6652. };
  6653. /**
  6654. * Convert a position on screen (pixels) to a datetime
  6655. * @param {int} x Position on the screen in pixels
  6656. * @return {Date} time The datetime the corresponds with given position x
  6657. * @private
  6658. */
  6659. // TODO: move this function to Range
  6660. Graph2d.prototype._toTime = function(x) {
  6661. var conversion = this.range.conversion(this.props.center.width);
  6662. return new Date(x / conversion.scale + conversion.offset);
  6663. };
  6664. /**
  6665. * Convert a datetime (Date object) into a position on the root
  6666. * This is used to get the pixel density estimate for the screen, not the center panel
  6667. * @param {Date} time A date
  6668. * @return {int} x The position on root in pixels which corresponds
  6669. * with the given date.
  6670. * @private
  6671. */
  6672. // TODO: move this function to Range
  6673. Graph2d.prototype._toGlobalTime = function(x) {
  6674. var conversion = this.range.conversion(this.props.root.width);
  6675. return new Date(x / conversion.scale + conversion.offset);
  6676. };
  6677. /**
  6678. * Convert a datetime (Date object) into a position on the screen
  6679. * @param {Date} time A date
  6680. * @return {int} x The position on the screen in pixels which corresponds
  6681. * with the given date.
  6682. * @private
  6683. */
  6684. // TODO: move this function to Range
  6685. Graph2d.prototype._toScreen = function(time) {
  6686. var conversion = this.range.conversion(this.props.center.width);
  6687. return (time.valueOf() - conversion.offset) * conversion.scale;
  6688. };
  6689. /**
  6690. * Convert a datetime (Date object) into a position on the root
  6691. * This is used to get the pixel density estimate for the screen, not the center panel
  6692. * @param {Date} time A date
  6693. * @return {int} x The position on root in pixels which corresponds
  6694. * with the given date.
  6695. * @private
  6696. */
  6697. // TODO: move this function to Range
  6698. Graph2d.prototype._toGlobalScreen = function(time) {
  6699. var conversion = this.range.conversion(this.props.root.width);
  6700. return (time.valueOf() - conversion.offset) * conversion.scale;
  6701. };
  6702. /**
  6703. * Initialize watching when option autoResize is true
  6704. * @private
  6705. */
  6706. Graph2d.prototype._initAutoResize = function () {
  6707. if (this.options.autoResize == true) {
  6708. this._startAutoResize();
  6709. }
  6710. else {
  6711. this._stopAutoResize();
  6712. }
  6713. };
  6714. /**
  6715. * Watch for changes in the size of the container. On resize, the Panel will
  6716. * automatically redraw itself.
  6717. * @private
  6718. */
  6719. Graph2d.prototype._startAutoResize = function () {
  6720. var me = this;
  6721. this._stopAutoResize();
  6722. this._onResize = function() {
  6723. if (me.options.autoResize != true) {
  6724. // stop watching when the option autoResize is changed to false
  6725. me._stopAutoResize();
  6726. return;
  6727. }
  6728. if (me.dom.root) {
  6729. // check whether the frame is resized
  6730. if ((me.dom.root.clientWidth != me.props.lastWidth) ||
  6731. (me.dom.root.clientHeight != me.props.lastHeight)) {
  6732. me.props.lastWidth = me.dom.root.clientWidth;
  6733. me.props.lastHeight = me.dom.root.clientHeight;
  6734. me.emit('change');
  6735. }
  6736. }
  6737. };
  6738. // add event listener to window resize
  6739. util.addEventListener(window, 'resize', this._onResize);
  6740. this.watchTimer = setInterval(this._onResize, 1000);
  6741. };
  6742. /**
  6743. * Stop watching for a resize of the frame.
  6744. * @private
  6745. */
  6746. Graph2d.prototype._stopAutoResize = function () {
  6747. if (this.watchTimer) {
  6748. clearInterval(this.watchTimer);
  6749. this.watchTimer = undefined;
  6750. }
  6751. // remove event listener on window.resize
  6752. util.removeEventListener(window, 'resize', this._onResize);
  6753. this._onResize = null;
  6754. };
  6755. /**
  6756. * Start moving the timeline vertically
  6757. * @param {Event} event
  6758. * @private
  6759. */
  6760. Graph2d.prototype._onTouch = function (event) {
  6761. this.touch.allowDragging = true;
  6762. };
  6763. /**
  6764. * Start moving the timeline vertically
  6765. * @param {Event} event
  6766. * @private
  6767. */
  6768. Graph2d.prototype._onPinch = function (event) {
  6769. this.touch.allowDragging = false;
  6770. };
  6771. /**
  6772. * Start moving the timeline vertically
  6773. * @param {Event} event
  6774. * @private
  6775. */
  6776. Graph2d.prototype._onDragStart = function (event) {
  6777. this.touch.initialScrollTop = this.props.scrollTop;
  6778. };
  6779. /**
  6780. * Move the timeline vertically
  6781. * @param {Event} event
  6782. * @private
  6783. */
  6784. Graph2d.prototype._onDrag = function (event) {
  6785. // refuse to drag when we where pinching to prevent the timeline make a jump
  6786. // when releasing the fingers in opposite order from the touch screen
  6787. if (!this.touch.allowDragging) return;
  6788. var delta = event.gesture.deltaY;
  6789. var oldScrollTop = this._getScrollTop();
  6790. var newScrollTop = this._setScrollTop(this.touch.initialScrollTop + delta);
  6791. if (newScrollTop != oldScrollTop) {
  6792. this.redraw(); // TODO: this causes two redraws when dragging, the other is triggered by rangechange already
  6793. }
  6794. };
  6795. /**
  6796. * Apply a scrollTop
  6797. * @param {Number} scrollTop
  6798. * @returns {Number} scrollTop Returns the applied scrollTop
  6799. * @private
  6800. */
  6801. Graph2d.prototype._setScrollTop = function (scrollTop) {
  6802. this.props.scrollTop = scrollTop;
  6803. this._updateScrollTop();
  6804. return this.props.scrollTop;
  6805. };
  6806. /**
  6807. * Update the current scrollTop when the height of the containers has been changed
  6808. * @returns {Number} scrollTop Returns the applied scrollTop
  6809. * @private
  6810. */
  6811. Graph2d.prototype._updateScrollTop = function () {
  6812. // recalculate the scrollTopMin
  6813. var scrollTopMin = Math.min(this.props.centerContainer.height - this.props.center.height, 0); // is negative or zero
  6814. if (scrollTopMin != this.props.scrollTopMin) {
  6815. // in case of bottom orientation, change the scrollTop such that the contents
  6816. // do not move relative to the time axis at the bottom
  6817. if (this.options.orientation == 'bottom') {
  6818. this.props.scrollTop += (scrollTopMin - this.props.scrollTopMin);
  6819. }
  6820. this.props.scrollTopMin = scrollTopMin;
  6821. }
  6822. // limit the scrollTop to the feasible scroll range
  6823. if (this.props.scrollTop > 0) this.props.scrollTop = 0;
  6824. if (this.props.scrollTop < scrollTopMin) this.props.scrollTop = scrollTopMin;
  6825. return this.props.scrollTop;
  6826. };
  6827. /**
  6828. * Get the current scrollTop
  6829. * @returns {number} scrollTop
  6830. * @private
  6831. */
  6832. Graph2d.prototype._getScrollTop = function () {
  6833. return this.props.scrollTop;
  6834. };
  6835. module.exports = Graph2d;
  6836. /***/ },
  6837. /* 14 */
  6838. /***/ function(module, exports, __webpack_require__) {
  6839. /**
  6840. * @constructor DataStep
  6841. * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an
  6842. * end data point. The class itself determines the best scale (step size) based on the
  6843. * provided start Date, end Date, and minimumStep.
  6844. *
  6845. * If minimumStep is provided, the step size is chosen as close as possible
  6846. * to the minimumStep but larger than minimumStep. If minimumStep is not
  6847. * provided, the scale is set to 1 DAY.
  6848. * The minimumStep should correspond with the onscreen size of about 6 characters
  6849. *
  6850. * Alternatively, you can set a scale by hand.
  6851. * After creation, you can initialize the class by executing first(). Then you
  6852. * can iterate from the start date to the end date via next(). You can check if
  6853. * the end date is reached with the function hasNext(). After each step, you can
  6854. * retrieve the current date via getCurrent().
  6855. * The DataStep has scales ranging from milliseconds, seconds, minutes, hours,
  6856. * days, to years.
  6857. *
  6858. * Version: 1.2
  6859. *
  6860. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  6861. * or new Date(2010, 9, 21, 23, 45, 00)
  6862. * @param {Date} [end] The end date
  6863. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  6864. */
  6865. function DataStep(start, end, minimumStep, containerHeight, forcedStepSize) {
  6866. // variables
  6867. this.current = 0;
  6868. this.autoScale = true;
  6869. this.stepIndex = 0;
  6870. this.step = 1;
  6871. this.scale = 1;
  6872. this.marginStart;
  6873. this.marginEnd;
  6874. this.majorSteps = [1, 2, 5, 10];
  6875. this.minorSteps = [0.25, 0.5, 1, 2];
  6876. this.setRange(start, end, minimumStep, containerHeight, forcedStepSize);
  6877. }
  6878. /**
  6879. * Set a new range
  6880. * If minimumStep is provided, the step size is chosen as close as possible
  6881. * to the minimumStep but larger than minimumStep. If minimumStep is not
  6882. * provided, the scale is set to 1 DAY.
  6883. * The minimumStep should correspond with the onscreen size of about 6 characters
  6884. * @param {Number} [start] The start date and time.
  6885. * @param {Number} [end] The end date and time.
  6886. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  6887. */
  6888. DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, forcedStepSize) {
  6889. this._start = start;
  6890. this._end = end;
  6891. if (start == end) {
  6892. this._start = start - 0.75;
  6893. this._end = end + 1;
  6894. }
  6895. if (this.autoScale) {
  6896. this.setMinimumStep(minimumStep, containerHeight, forcedStepSize);
  6897. }
  6898. this.setFirst();
  6899. };
  6900. /**
  6901. * Automatically determine the scale that bests fits the provided minimum step
  6902. * @param {Number} [minimumStep] The minimum step size in milliseconds
  6903. */
  6904. DataStep.prototype.setMinimumStep = function(minimumStep, containerHeight) {
  6905. // round to floor
  6906. var size = this._end - this._start;
  6907. var safeSize = size * 1.1;
  6908. var minimumStepValue = minimumStep * (safeSize / containerHeight);
  6909. var orderOfMagnitude = Math.round(Math.log(safeSize)/Math.LN10);
  6910. var minorStepIdx = -1;
  6911. var magnitudefactor = Math.pow(10,orderOfMagnitude);
  6912. var start = 0;
  6913. if (orderOfMagnitude < 0) {
  6914. start = orderOfMagnitude;
  6915. }
  6916. var solutionFound = false;
  6917. for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) {
  6918. magnitudefactor = Math.pow(10,i);
  6919. for (var j = 0; j < this.minorSteps.length; j++) {
  6920. var stepSize = magnitudefactor * this.minorSteps[j];
  6921. if (stepSize >= minimumStepValue) {
  6922. solutionFound = true;
  6923. minorStepIdx = j;
  6924. break;
  6925. }
  6926. }
  6927. if (solutionFound == true) {
  6928. break;
  6929. }
  6930. }
  6931. this.stepIndex = minorStepIdx;
  6932. this.scale = magnitudefactor;
  6933. this.step = magnitudefactor * this.minorSteps[minorStepIdx];
  6934. };
  6935. /**
  6936. * Set the range iterator to the start date.
  6937. */
  6938. DataStep.prototype.first = function() {
  6939. this.setFirst();
  6940. };
  6941. /**
  6942. * Round the current date to the first minor date value
  6943. * This must be executed once when the current date is set to start Date
  6944. */
  6945. DataStep.prototype.setFirst = function() {
  6946. var niceStart = this._start - (this.scale * this.minorSteps[this.stepIndex]);
  6947. var niceEnd = this._end + (this.scale * this.minorSteps[this.stepIndex]);
  6948. this.marginEnd = this.roundToMinor(niceEnd);
  6949. this.marginStart = this.roundToMinor(niceStart);
  6950. this.marginRange = this.marginEnd - this.marginStart;
  6951. this.current = this.marginEnd;
  6952. };
  6953. DataStep.prototype.roundToMinor = function(value) {
  6954. var rounded = value - (value % (this.scale * this.minorSteps[this.stepIndex]));
  6955. if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) {
  6956. return rounded + (this.scale * this.minorSteps[this.stepIndex]);
  6957. }
  6958. else {
  6959. return rounded;
  6960. }
  6961. }
  6962. /**
  6963. * Check if the there is a next step
  6964. * @return {boolean} true if the current date has not passed the end date
  6965. */
  6966. DataStep.prototype.hasNext = function () {
  6967. return (this.current >= this.marginStart);
  6968. };
  6969. /**
  6970. * Do the next step
  6971. */
  6972. DataStep.prototype.next = function() {
  6973. var prev = this.current;
  6974. this.current -= this.step;
  6975. // safety mechanism: if current time is still unchanged, move to the end
  6976. if (this.current == prev) {
  6977. this.current = this._end;
  6978. }
  6979. };
  6980. /**
  6981. * Do the next step
  6982. */
  6983. DataStep.prototype.previous = function() {
  6984. this.current += this.step;
  6985. this.marginEnd += this.step;
  6986. this.marginRange = this.marginEnd - this.marginStart;
  6987. };
  6988. /**
  6989. * Get the current datetime
  6990. * @return {String} current The current date
  6991. */
  6992. DataStep.prototype.getCurrent = function() {
  6993. var toPrecision = '' + Number(this.current).toPrecision(5);
  6994. for (var i = toPrecision.length-1; i > 0; i--) {
  6995. if (toPrecision[i] == "0") {
  6996. toPrecision = toPrecision.slice(0,i);
  6997. }
  6998. else if (toPrecision[i] == "." || toPrecision[i] == ",") {
  6999. toPrecision = toPrecision.slice(0,i);
  7000. break;
  7001. }
  7002. else{
  7003. break;
  7004. }
  7005. }
  7006. return toPrecision;
  7007. };
  7008. /**
  7009. * Snap a date to a rounded value.
  7010. * The snap intervals are dependent on the current scale and step.
  7011. * @param {Date} date the date to be snapped.
  7012. * @return {Date} snappedDate
  7013. */
  7014. DataStep.prototype.snap = function(date) {
  7015. };
  7016. /**
  7017. * Check if the current value is a major value (for example when the step
  7018. * is DAY, a major value is each first day of the MONTH)
  7019. * @return {boolean} true if current date is major, else false.
  7020. */
  7021. DataStep.prototype.isMajor = function() {
  7022. return (this.current % (this.scale * this.majorSteps[this.stepIndex]) == 0);
  7023. };
  7024. module.exports = DataStep;
  7025. /***/ },
  7026. /* 15 */
  7027. /***/ function(module, exports, __webpack_require__) {
  7028. var util = __webpack_require__(1);
  7029. var hammerUtil = __webpack_require__(42);
  7030. var moment = __webpack_require__(40);
  7031. var Component = __webpack_require__(18);
  7032. /**
  7033. * @constructor Range
  7034. * A Range controls a numeric range with a start and end value.
  7035. * The Range adjusts the range based on mouse events or programmatic changes,
  7036. * and triggers events when the range is changing or has been changed.
  7037. * @param {{dom: Object, domProps: Object, emitter: Emitter}} body
  7038. * @param {Object} [options] See description at Range.setOptions
  7039. */
  7040. function Range(body, options) {
  7041. var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
  7042. this.start = now.clone().add('days', -3).valueOf(); // Number
  7043. this.end = now.clone().add('days', 4).valueOf(); // Number
  7044. this.body = body;
  7045. // default options
  7046. this.defaultOptions = {
  7047. start: null,
  7048. end: null,
  7049. direction: 'horizontal', // 'horizontal' or 'vertical'
  7050. moveable: true,
  7051. zoomable: true,
  7052. min: null,
  7053. max: null,
  7054. zoomMin: 10, // milliseconds
  7055. zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000 // milliseconds
  7056. };
  7057. this.options = util.extend({}, this.defaultOptions);
  7058. this.props = {
  7059. touch: {}
  7060. };
  7061. // drag listeners for dragging
  7062. this.body.emitter.on('dragstart', this._onDragStart.bind(this));
  7063. this.body.emitter.on('drag', this._onDrag.bind(this));
  7064. this.body.emitter.on('dragend', this._onDragEnd.bind(this));
  7065. // ignore dragging when holding
  7066. this.body.emitter.on('hold', this._onHold.bind(this));
  7067. // mouse wheel for zooming
  7068. this.body.emitter.on('mousewheel', this._onMouseWheel.bind(this));
  7069. this.body.emitter.on('DOMMouseScroll', this._onMouseWheel.bind(this)); // For FF
  7070. // pinch to zoom
  7071. this.body.emitter.on('touch', this._onTouch.bind(this));
  7072. this.body.emitter.on('pinch', this._onPinch.bind(this));
  7073. this.setOptions(options);
  7074. }
  7075. Range.prototype = new Component();
  7076. /**
  7077. * Set options for the range controller
  7078. * @param {Object} options Available options:
  7079. * {Number | Date | String} start Start date for the range
  7080. * {Number | Date | String} end End date for the range
  7081. * {Number} min Minimum value for start
  7082. * {Number} max Maximum value for end
  7083. * {Number} zoomMin Set a minimum value for
  7084. * (end - start).
  7085. * {Number} zoomMax Set a maximum value for
  7086. * (end - start).
  7087. * {Boolean} moveable Enable moving of the range
  7088. * by dragging. True by default
  7089. * {Boolean} zoomable Enable zooming of the range
  7090. * by pinching/scrolling. True by default
  7091. */
  7092. Range.prototype.setOptions = function (options) {
  7093. if (options) {
  7094. // copy the options that we know
  7095. var fields = ['direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable'];
  7096. util.selectiveExtend(fields, this.options, options);
  7097. if ('start' in options || 'end' in options) {
  7098. // apply a new range. both start and end are optional
  7099. this.setRange(options.start, options.end);
  7100. }
  7101. }
  7102. };
  7103. /**
  7104. * Test whether direction has a valid value
  7105. * @param {String} direction 'horizontal' or 'vertical'
  7106. */
  7107. function validateDirection (direction) {
  7108. if (direction != 'horizontal' && direction != 'vertical') {
  7109. throw new TypeError('Unknown direction "' + direction + '". ' +
  7110. 'Choose "horizontal" or "vertical".');
  7111. }
  7112. }
  7113. /**
  7114. * Set a new start and end range
  7115. * @param {Number} [start]
  7116. * @param {Number} [end]
  7117. */
  7118. Range.prototype.setRange = function(start, end) {
  7119. var changed = this._applyRange(start, end);
  7120. if (changed) {
  7121. var params = {
  7122. start: new Date(this.start),
  7123. end: new Date(this.end)
  7124. };
  7125. this.body.emitter.emit('rangechange', params);
  7126. this.body.emitter.emit('rangechanged', params);
  7127. }
  7128. };
  7129. /**
  7130. * Set a new start and end range. This method is the same as setRange, but
  7131. * does not trigger a range change and range changed event, and it returns
  7132. * true when the range is changed
  7133. * @param {Number} [start]
  7134. * @param {Number} [end]
  7135. * @return {Boolean} changed
  7136. * @private
  7137. */
  7138. Range.prototype._applyRange = function(start, end) {
  7139. var newStart = (start != null) ? util.convert(start, 'Date').valueOf() : this.start,
  7140. newEnd = (end != null) ? util.convert(end, 'Date').valueOf() : this.end,
  7141. max = (this.options.max != null) ? util.convert(this.options.max, 'Date').valueOf() : null,
  7142. min = (this.options.min != null) ? util.convert(this.options.min, 'Date').valueOf() : null,
  7143. diff;
  7144. // check for valid number
  7145. if (isNaN(newStart) || newStart === null) {
  7146. throw new Error('Invalid start "' + start + '"');
  7147. }
  7148. if (isNaN(newEnd) || newEnd === null) {
  7149. throw new Error('Invalid end "' + end + '"');
  7150. }
  7151. // prevent start < end
  7152. if (newEnd < newStart) {
  7153. newEnd = newStart;
  7154. }
  7155. // prevent start < min
  7156. if (min !== null) {
  7157. if (newStart < min) {
  7158. diff = (min - newStart);
  7159. newStart += diff;
  7160. newEnd += diff;
  7161. // prevent end > max
  7162. if (max != null) {
  7163. if (newEnd > max) {
  7164. newEnd = max;
  7165. }
  7166. }
  7167. }
  7168. }
  7169. // prevent end > max
  7170. if (max !== null) {
  7171. if (newEnd > max) {
  7172. diff = (newEnd - max);
  7173. newStart -= diff;
  7174. newEnd -= diff;
  7175. // prevent start < min
  7176. if (min != null) {
  7177. if (newStart < min) {
  7178. newStart = min;
  7179. }
  7180. }
  7181. }
  7182. }
  7183. // prevent (end-start) < zoomMin
  7184. if (this.options.zoomMin !== null) {
  7185. var zoomMin = parseFloat(this.options.zoomMin);
  7186. if (zoomMin < 0) {
  7187. zoomMin = 0;
  7188. }
  7189. if ((newEnd - newStart) < zoomMin) {
  7190. if ((this.end - this.start) === zoomMin) {
  7191. // ignore this action, we are already zoomed to the minimum
  7192. newStart = this.start;
  7193. newEnd = this.end;
  7194. }
  7195. else {
  7196. // zoom to the minimum
  7197. diff = (zoomMin - (newEnd - newStart));
  7198. newStart -= diff / 2;
  7199. newEnd += diff / 2;
  7200. }
  7201. }
  7202. }
  7203. // prevent (end-start) > zoomMax
  7204. if (this.options.zoomMax !== null) {
  7205. var zoomMax = parseFloat(this.options.zoomMax);
  7206. if (zoomMax < 0) {
  7207. zoomMax = 0;
  7208. }
  7209. if ((newEnd - newStart) > zoomMax) {
  7210. if ((this.end - this.start) === zoomMax) {
  7211. // ignore this action, we are already zoomed to the maximum
  7212. newStart = this.start;
  7213. newEnd = this.end;
  7214. }
  7215. else {
  7216. // zoom to the maximum
  7217. diff = ((newEnd - newStart) - zoomMax);
  7218. newStart += diff / 2;
  7219. newEnd -= diff / 2;
  7220. }
  7221. }
  7222. }
  7223. var changed = (this.start != newStart || this.end != newEnd);
  7224. this.start = newStart;
  7225. this.end = newEnd;
  7226. return changed;
  7227. };
  7228. /**
  7229. * Retrieve the current range.
  7230. * @return {Object} An object with start and end properties
  7231. */
  7232. Range.prototype.getRange = function() {
  7233. return {
  7234. start: this.start,
  7235. end: this.end
  7236. };
  7237. };
  7238. /**
  7239. * Calculate the conversion offset and scale for current range, based on
  7240. * the provided width
  7241. * @param {Number} width
  7242. * @returns {{offset: number, scale: number}} conversion
  7243. */
  7244. Range.prototype.conversion = function (width) {
  7245. return Range.conversion(this.start, this.end, width);
  7246. };
  7247. /**
  7248. * Static method to calculate the conversion offset and scale for a range,
  7249. * based on the provided start, end, and width
  7250. * @param {Number} start
  7251. * @param {Number} end
  7252. * @param {Number} width
  7253. * @returns {{offset: number, scale: number}} conversion
  7254. */
  7255. Range.conversion = function (start, end, width) {
  7256. if (width != 0 && (end - start != 0)) {
  7257. return {
  7258. offset: start,
  7259. scale: width / (end - start)
  7260. }
  7261. }
  7262. else {
  7263. return {
  7264. offset: 0,
  7265. scale: 1
  7266. };
  7267. }
  7268. };
  7269. /**
  7270. * Start dragging horizontally or vertically
  7271. * @param {Event} event
  7272. * @private
  7273. */
  7274. Range.prototype._onDragStart = function(event) {
  7275. // only allow dragging when configured as movable
  7276. if (!this.options.moveable) return;
  7277. // refuse to drag when we where pinching to prevent the timeline make a jump
  7278. // when releasing the fingers in opposite order from the touch screen
  7279. if (!this.props.touch.allowDragging) return;
  7280. this.props.touch.start = this.start;
  7281. this.props.touch.end = this.end;
  7282. if (this.body.dom.root) {
  7283. this.body.dom.root.style.cursor = 'move';
  7284. }
  7285. };
  7286. /**
  7287. * Perform dragging operation
  7288. * @param {Event} event
  7289. * @private
  7290. */
  7291. Range.prototype._onDrag = function (event) {
  7292. // only allow dragging when configured as movable
  7293. if (!this.options.moveable) return;
  7294. var direction = this.options.direction;
  7295. validateDirection(direction);
  7296. // refuse to drag when we where pinching to prevent the timeline make a jump
  7297. // when releasing the fingers in opposite order from the touch screen
  7298. if (!this.props.touch.allowDragging) return;
  7299. var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY,
  7300. interval = (this.props.touch.end - this.props.touch.start),
  7301. width = (direction == 'horizontal') ? this.body.domProps.center.width : this.body.domProps.center.height,
  7302. diffRange = -delta / width * interval;
  7303. this._applyRange(this.props.touch.start + diffRange, this.props.touch.end + diffRange);
  7304. this.body.emitter.emit('rangechange', {
  7305. start: new Date(this.start),
  7306. end: new Date(this.end)
  7307. });
  7308. };
  7309. /**
  7310. * Stop dragging operation
  7311. * @param {event} event
  7312. * @private
  7313. */
  7314. Range.prototype._onDragEnd = function (event) {
  7315. // only allow dragging when configured as movable
  7316. if (!this.options.moveable) return;
  7317. // refuse to drag when we where pinching to prevent the timeline make a jump
  7318. // when releasing the fingers in opposite order from the touch screen
  7319. if (!this.props.touch.allowDragging) return;
  7320. if (this.body.dom.root) {
  7321. this.body.dom.root.style.cursor = 'auto';
  7322. }
  7323. // fire a rangechanged event
  7324. this.body.emitter.emit('rangechanged', {
  7325. start: new Date(this.start),
  7326. end: new Date(this.end)
  7327. });
  7328. };
  7329. /**
  7330. * Event handler for mouse wheel event, used to zoom
  7331. * Code from http://adomas.org/javascript-mouse-wheel/
  7332. * @param {Event} event
  7333. * @private
  7334. */
  7335. Range.prototype._onMouseWheel = function(event) {
  7336. // only allow zooming when configured as zoomable and moveable
  7337. if (!(this.options.zoomable && this.options.moveable)) return;
  7338. // retrieve delta
  7339. var delta = 0;
  7340. if (event.wheelDelta) { /* IE/Opera. */
  7341. delta = event.wheelDelta / 120;
  7342. } else if (event.detail) { /* Mozilla case. */
  7343. // In Mozilla, sign of delta is different than in IE.
  7344. // Also, delta is multiple of 3.
  7345. delta = -event.detail / 3;
  7346. }
  7347. // If delta is nonzero, handle it.
  7348. // Basically, delta is now positive if wheel was scrolled up,
  7349. // and negative, if wheel was scrolled down.
  7350. if (delta) {
  7351. // perform the zoom action. Delta is normally 1 or -1
  7352. // adjust a negative delta such that zooming in with delta 0.1
  7353. // equals zooming out with a delta -0.1
  7354. var scale;
  7355. if (delta < 0) {
  7356. scale = 1 - (delta / 5);
  7357. }
  7358. else {
  7359. scale = 1 / (1 + (delta / 5)) ;
  7360. }
  7361. // calculate center, the date to zoom around
  7362. var gesture = hammerUtil.fakeGesture(this, event),
  7363. pointer = getPointer(gesture.center, this.body.dom.center),
  7364. pointerDate = this._pointerToDate(pointer);
  7365. this.zoom(scale, pointerDate);
  7366. }
  7367. // Prevent default actions caused by mouse wheel
  7368. // (else the page and timeline both zoom and scroll)
  7369. event.preventDefault();
  7370. };
  7371. /**
  7372. * Start of a touch gesture
  7373. * @private
  7374. */
  7375. Range.prototype._onTouch = function (event) {
  7376. this.props.touch.start = this.start;
  7377. this.props.touch.end = this.end;
  7378. this.props.touch.allowDragging = true;
  7379. this.props.touch.center = null;
  7380. };
  7381. /**
  7382. * On start of a hold gesture
  7383. * @private
  7384. */
  7385. Range.prototype._onHold = function () {
  7386. this.props.touch.allowDragging = false;
  7387. };
  7388. /**
  7389. * Handle pinch event
  7390. * @param {Event} event
  7391. * @private
  7392. */
  7393. Range.prototype._onPinch = function (event) {
  7394. // only allow zooming when configured as zoomable and moveable
  7395. if (!(this.options.zoomable && this.options.moveable)) return;
  7396. this.props.touch.allowDragging = false;
  7397. if (event.gesture.touches.length > 1) {
  7398. if (!this.props.touch.center) {
  7399. this.props.touch.center = getPointer(event.gesture.center, this.body.dom.center);
  7400. }
  7401. var scale = 1 / event.gesture.scale,
  7402. initDate = this._pointerToDate(this.props.touch.center);
  7403. // calculate new start and end
  7404. var newStart = parseInt(initDate + (this.props.touch.start - initDate) * scale);
  7405. var newEnd = parseInt(initDate + (this.props.touch.end - initDate) * scale);
  7406. // apply new range
  7407. this.setRange(newStart, newEnd);
  7408. }
  7409. };
  7410. /**
  7411. * Helper function to calculate the center date for zooming
  7412. * @param {{x: Number, y: Number}} pointer
  7413. * @return {number} date
  7414. * @private
  7415. */
  7416. Range.prototype._pointerToDate = function (pointer) {
  7417. var conversion;
  7418. var direction = this.options.direction;
  7419. validateDirection(direction);
  7420. if (direction == 'horizontal') {
  7421. var width = this.body.domProps.center.width;
  7422. conversion = this.conversion(width);
  7423. return pointer.x / conversion.scale + conversion.offset;
  7424. }
  7425. else {
  7426. var height = this.body.domProps.center.height;
  7427. conversion = this.conversion(height);
  7428. return pointer.y / conversion.scale + conversion.offset;
  7429. }
  7430. };
  7431. /**
  7432. * Get the pointer location relative to the location of the dom element
  7433. * @param {{pageX: Number, pageY: Number}} touch
  7434. * @param {Element} element HTML DOM element
  7435. * @return {{x: Number, y: Number}} pointer
  7436. * @private
  7437. */
  7438. function getPointer (touch, element) {
  7439. return {
  7440. x: touch.pageX - util.getAbsoluteLeft(element),
  7441. y: touch.pageY - util.getAbsoluteTop(element)
  7442. };
  7443. }
  7444. /**
  7445. * Zoom the range the given scale in or out. Start and end date will
  7446. * be adjusted, and the timeline will be redrawn. You can optionally give a
  7447. * date around which to zoom.
  7448. * For example, try scale = 0.9 or 1.1
  7449. * @param {Number} scale Scaling factor. Values above 1 will zoom out,
  7450. * values below 1 will zoom in.
  7451. * @param {Number} [center] Value representing a date around which will
  7452. * be zoomed.
  7453. */
  7454. Range.prototype.zoom = function(scale, center) {
  7455. // if centerDate is not provided, take it half between start Date and end Date
  7456. if (center == null) {
  7457. center = (this.start + this.end) / 2;
  7458. }
  7459. // calculate new start and end
  7460. var newStart = center + (this.start - center) * scale;
  7461. var newEnd = center + (this.end - center) * scale;
  7462. this.setRange(newStart, newEnd);
  7463. };
  7464. /**
  7465. * Move the range with a given delta to the left or right. Start and end
  7466. * value will be adjusted. For example, try delta = 0.1 or -0.1
  7467. * @param {Number} delta Moving amount. Positive value will move right,
  7468. * negative value will move left
  7469. */
  7470. Range.prototype.move = function(delta) {
  7471. // zoom start Date and end Date relative to the centerDate
  7472. var diff = (this.end - this.start);
  7473. // apply new values
  7474. var newStart = this.start + diff * delta;
  7475. var newEnd = this.end + diff * delta;
  7476. // TODO: reckon with min and max range
  7477. this.start = newStart;
  7478. this.end = newEnd;
  7479. };
  7480. /**
  7481. * Move the range to a new center point
  7482. * @param {Number} moveTo New center point of the range
  7483. */
  7484. Range.prototype.moveTo = function(moveTo) {
  7485. var center = (this.start + this.end) / 2;
  7486. var diff = center - moveTo;
  7487. // calculate new start and end
  7488. var newStart = this.start - diff;
  7489. var newEnd = this.end - diff;
  7490. this.setRange(newStart, newEnd);
  7491. };
  7492. module.exports = Range;
  7493. /***/ },
  7494. /* 16 */
  7495. /***/ function(module, exports, __webpack_require__) {
  7496. // Utility functions for ordering and stacking of items
  7497. var EPSILON = 0.001; // used when checking collisions, to prevent round-off errors
  7498. /**
  7499. * Order items by their start data
  7500. * @param {Item[]} items
  7501. */
  7502. exports.orderByStart = function(items) {
  7503. items.sort(function (a, b) {
  7504. return a.data.start - b.data.start;
  7505. });
  7506. };
  7507. /**
  7508. * Order items by their end date. If they have no end date, their start date
  7509. * is used.
  7510. * @param {Item[]} items
  7511. */
  7512. exports.orderByEnd = function(items) {
  7513. items.sort(function (a, b) {
  7514. var aTime = ('end' in a.data) ? a.data.end : a.data.start,
  7515. bTime = ('end' in b.data) ? b.data.end : b.data.start;
  7516. return aTime - bTime;
  7517. });
  7518. };
  7519. /**
  7520. * Adjust vertical positions of the items such that they don't overlap each
  7521. * other.
  7522. * @param {Item[]} items
  7523. * All visible items
  7524. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  7525. * Margins between items and between items and the axis.
  7526. * @param {boolean} [force=false]
  7527. * If true, all items will be repositioned. If false (default), only
  7528. * items having a top===null will be re-stacked
  7529. */
  7530. exports.stack = function(items, margin, force) {
  7531. var i, iMax;
  7532. if (force) {
  7533. // reset top position of all items
  7534. for (i = 0, iMax = items.length; i < iMax; i++) {
  7535. items[i].top = null;
  7536. }
  7537. }
  7538. // calculate new, non-overlapping positions
  7539. for (i = 0, iMax = items.length; i < iMax; i++) {
  7540. var item = items[i];
  7541. if (item.top === null) {
  7542. // initialize top position
  7543. item.top = margin.axis;
  7544. do {
  7545. // TODO: optimize checking for overlap. when there is a gap without items,
  7546. // you only need to check for items from the next item on, not from zero
  7547. var collidingItem = null;
  7548. for (var j = 0, jj = items.length; j < jj; j++) {
  7549. var other = items[j];
  7550. if (other.top !== null && other !== item && exports.collision(item, other, margin.item)) {
  7551. collidingItem = other;
  7552. break;
  7553. }
  7554. }
  7555. if (collidingItem != null) {
  7556. // There is a collision. Reposition the items above the colliding element
  7557. item.top = collidingItem.top + collidingItem.height + margin.item.vertical;
  7558. }
  7559. } while (collidingItem);
  7560. }
  7561. }
  7562. };
  7563. /**
  7564. * Adjust vertical positions of the items without stacking them
  7565. * @param {Item[]} items
  7566. * All visible items
  7567. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  7568. * Margins between items and between items and the axis.
  7569. */
  7570. exports.nostack = function(items, margin) {
  7571. var i, iMax;
  7572. // reset top position of all items
  7573. for (i = 0, iMax = items.length; i < iMax; i++) {
  7574. items[i].top = margin.axis;
  7575. }
  7576. };
  7577. /**
  7578. * Test if the two provided items collide
  7579. * The items must have parameters left, width, top, and height.
  7580. * @param {Item} a The first item
  7581. * @param {Item} b The second item
  7582. * @param {{horizontal: number, vertical: number}} margin
  7583. * An object containing a horizontal and vertical
  7584. * minimum required margin.
  7585. * @return {boolean} true if a and b collide, else false
  7586. */
  7587. exports.collision = function(a, b, margin) {
  7588. return ((a.left - margin.horizontal + EPSILON) < (b.left + b.width) &&
  7589. (a.left + a.width + margin.horizontal - EPSILON) > b.left &&
  7590. (a.top - margin.vertical + EPSILON) < (b.top + b.height) &&
  7591. (a.top + a.height + margin.vertical - EPSILON) > b.top);
  7592. };
  7593. /***/ },
  7594. /* 17 */
  7595. /***/ function(module, exports, __webpack_require__) {
  7596. var moment = __webpack_require__(40);
  7597. /**
  7598. * @constructor TimeStep
  7599. * The class TimeStep is an iterator for dates. You provide a start date and an
  7600. * end date. The class itself determines the best scale (step size) based on the
  7601. * provided start Date, end Date, and minimumStep.
  7602. *
  7603. * If minimumStep is provided, the step size is chosen as close as possible
  7604. * to the minimumStep but larger than minimumStep. If minimumStep is not
  7605. * provided, the scale is set to 1 DAY.
  7606. * The minimumStep should correspond with the onscreen size of about 6 characters
  7607. *
  7608. * Alternatively, you can set a scale by hand.
  7609. * After creation, you can initialize the class by executing first(). Then you
  7610. * can iterate from the start date to the end date via next(). You can check if
  7611. * the end date is reached with the function hasNext(). After each step, you can
  7612. * retrieve the current date via getCurrent().
  7613. * The TimeStep has scales ranging from milliseconds, seconds, minutes, hours,
  7614. * days, to years.
  7615. *
  7616. * Version: 1.2
  7617. *
  7618. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  7619. * or new Date(2010, 9, 21, 23, 45, 00)
  7620. * @param {Date} [end] The end date
  7621. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  7622. */
  7623. function TimeStep(start, end, minimumStep) {
  7624. // variables
  7625. this.current = new Date();
  7626. this._start = new Date();
  7627. this._end = new Date();
  7628. this.autoScale = true;
  7629. this.scale = TimeStep.SCALE.DAY;
  7630. this.step = 1;
  7631. // initialize the range
  7632. this.setRange(start, end, minimumStep);
  7633. }
  7634. /// enum scale
  7635. TimeStep.SCALE = {
  7636. MILLISECOND: 1,
  7637. SECOND: 2,
  7638. MINUTE: 3,
  7639. HOUR: 4,
  7640. DAY: 5,
  7641. WEEKDAY: 6,
  7642. MONTH: 7,
  7643. YEAR: 8
  7644. };
  7645. /**
  7646. * Set a new range
  7647. * If minimumStep is provided, the step size is chosen as close as possible
  7648. * to the minimumStep but larger than minimumStep. If minimumStep is not
  7649. * provided, the scale is set to 1 DAY.
  7650. * The minimumStep should correspond with the onscreen size of about 6 characters
  7651. * @param {Date} [start] The start date and time.
  7652. * @param {Date} [end] The end date and time.
  7653. * @param {int} [minimumStep] Optional. Minimum step size in milliseconds
  7654. */
  7655. TimeStep.prototype.setRange = function(start, end, minimumStep) {
  7656. if (!(start instanceof Date) || !(end instanceof Date)) {
  7657. throw "No legal start or end date in method setRange";
  7658. }
  7659. this._start = (start != undefined) ? new Date(start.valueOf()) : new Date();
  7660. this._end = (end != undefined) ? new Date(end.valueOf()) : new Date();
  7661. if (this.autoScale) {
  7662. this.setMinimumStep(minimumStep);
  7663. }
  7664. };
  7665. /**
  7666. * Set the range iterator to the start date.
  7667. */
  7668. TimeStep.prototype.first = function() {
  7669. this.current = new Date(this._start.valueOf());
  7670. this.roundToMinor();
  7671. };
  7672. /**
  7673. * Round the current date to the first minor date value
  7674. * This must be executed once when the current date is set to start Date
  7675. */
  7676. TimeStep.prototype.roundToMinor = function() {
  7677. // round to floor
  7678. // IMPORTANT: we have no breaks in this switch! (this is no bug)
  7679. //noinspection FallthroughInSwitchStatementJS
  7680. switch (this.scale) {
  7681. case TimeStep.SCALE.YEAR:
  7682. this.current.setFullYear(this.step * Math.floor(this.current.getFullYear() / this.step));
  7683. this.current.setMonth(0);
  7684. case TimeStep.SCALE.MONTH: this.current.setDate(1);
  7685. case TimeStep.SCALE.DAY: // intentional fall through
  7686. case TimeStep.SCALE.WEEKDAY: this.current.setHours(0);
  7687. case TimeStep.SCALE.HOUR: this.current.setMinutes(0);
  7688. case TimeStep.SCALE.MINUTE: this.current.setSeconds(0);
  7689. case TimeStep.SCALE.SECOND: this.current.setMilliseconds(0);
  7690. //case TimeStep.SCALE.MILLISECOND: // nothing to do for milliseconds
  7691. }
  7692. if (this.step != 1) {
  7693. // round down to the first minor value that is a multiple of the current step size
  7694. switch (this.scale) {
  7695. case TimeStep.SCALE.MILLISECOND: this.current.setMilliseconds(this.current.getMilliseconds() - this.current.getMilliseconds() % this.step); break;
  7696. case TimeStep.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() - this.current.getSeconds() % this.step); break;
  7697. case TimeStep.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() - this.current.getMinutes() % this.step); break;
  7698. case TimeStep.SCALE.HOUR: this.current.setHours(this.current.getHours() - this.current.getHours() % this.step); break;
  7699. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  7700. case TimeStep.SCALE.DAY: this.current.setDate((this.current.getDate()-1) - (this.current.getDate()-1) % this.step + 1); break;
  7701. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() - this.current.getMonth() % this.step); break;
  7702. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() - this.current.getFullYear() % this.step); break;
  7703. default: break;
  7704. }
  7705. }
  7706. };
  7707. /**
  7708. * Check if the there is a next step
  7709. * @return {boolean} true if the current date has not passed the end date
  7710. */
  7711. TimeStep.prototype.hasNext = function () {
  7712. return (this.current.valueOf() <= this._end.valueOf());
  7713. };
  7714. /**
  7715. * Do the next step
  7716. */
  7717. TimeStep.prototype.next = function() {
  7718. var prev = this.current.valueOf();
  7719. // Two cases, needed to prevent issues with switching daylight savings
  7720. // (end of March and end of October)
  7721. if (this.current.getMonth() < 6) {
  7722. switch (this.scale) {
  7723. case TimeStep.SCALE.MILLISECOND:
  7724. this.current = new Date(this.current.valueOf() + this.step); break;
  7725. case TimeStep.SCALE.SECOND: this.current = new Date(this.current.valueOf() + this.step * 1000); break;
  7726. case TimeStep.SCALE.MINUTE: this.current = new Date(this.current.valueOf() + this.step * 1000 * 60); break;
  7727. case TimeStep.SCALE.HOUR:
  7728. this.current = new Date(this.current.valueOf() + this.step * 1000 * 60 * 60);
  7729. // in case of skipping an hour for daylight savings, adjust the hour again (else you get: 0h 5h 9h ... instead of 0h 4h 8h ...)
  7730. var h = this.current.getHours();
  7731. this.current.setHours(h - (h % this.step));
  7732. break;
  7733. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  7734. case TimeStep.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break;
  7735. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break;
  7736. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break;
  7737. default: break;
  7738. }
  7739. }
  7740. else {
  7741. switch (this.scale) {
  7742. case TimeStep.SCALE.MILLISECOND: this.current = new Date(this.current.valueOf() + this.step); break;
  7743. case TimeStep.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() + this.step); break;
  7744. case TimeStep.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() + this.step); break;
  7745. case TimeStep.SCALE.HOUR: this.current.setHours(this.current.getHours() + this.step); break;
  7746. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  7747. case TimeStep.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break;
  7748. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break;
  7749. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break;
  7750. default: break;
  7751. }
  7752. }
  7753. if (this.step != 1) {
  7754. // round down to the correct major value
  7755. switch (this.scale) {
  7756. case TimeStep.SCALE.MILLISECOND: if(this.current.getMilliseconds() < this.step) this.current.setMilliseconds(0); break;
  7757. case TimeStep.SCALE.SECOND: if(this.current.getSeconds() < this.step) this.current.setSeconds(0); break;
  7758. case TimeStep.SCALE.MINUTE: if(this.current.getMinutes() < this.step) this.current.setMinutes(0); break;
  7759. case TimeStep.SCALE.HOUR: if(this.current.getHours() < this.step) this.current.setHours(0); break;
  7760. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  7761. case TimeStep.SCALE.DAY: if(this.current.getDate() < this.step+1) this.current.setDate(1); break;
  7762. case TimeStep.SCALE.MONTH: if(this.current.getMonth() < this.step) this.current.setMonth(0); break;
  7763. case TimeStep.SCALE.YEAR: break; // nothing to do for year
  7764. default: break;
  7765. }
  7766. }
  7767. // safety mechanism: if current time is still unchanged, move to the end
  7768. if (this.current.valueOf() == prev) {
  7769. this.current = new Date(this._end.valueOf());
  7770. }
  7771. };
  7772. /**
  7773. * Get the current datetime
  7774. * @return {Date} current The current date
  7775. */
  7776. TimeStep.prototype.getCurrent = function() {
  7777. return this.current;
  7778. };
  7779. /**
  7780. * Set a custom scale. Autoscaling will be disabled.
  7781. * For example setScale(SCALE.MINUTES, 5) will result
  7782. * in minor steps of 5 minutes, and major steps of an hour.
  7783. *
  7784. * @param {TimeStep.SCALE} newScale
  7785. * A scale. Choose from SCALE.MILLISECOND,
  7786. * SCALE.SECOND, SCALE.MINUTE, SCALE.HOUR,
  7787. * SCALE.WEEKDAY, SCALE.DAY, SCALE.MONTH,
  7788. * SCALE.YEAR.
  7789. * @param {Number} newStep A step size, by default 1. Choose for
  7790. * example 1, 2, 5, or 10.
  7791. */
  7792. TimeStep.prototype.setScale = function(newScale, newStep) {
  7793. this.scale = newScale;
  7794. if (newStep > 0) {
  7795. this.step = newStep;
  7796. }
  7797. this.autoScale = false;
  7798. };
  7799. /**
  7800. * Enable or disable autoscaling
  7801. * @param {boolean} enable If true, autoascaling is set true
  7802. */
  7803. TimeStep.prototype.setAutoScale = function (enable) {
  7804. this.autoScale = enable;
  7805. };
  7806. /**
  7807. * Automatically determine the scale that bests fits the provided minimum step
  7808. * @param {Number} [minimumStep] The minimum step size in milliseconds
  7809. */
  7810. TimeStep.prototype.setMinimumStep = function(minimumStep) {
  7811. if (minimumStep == undefined) {
  7812. return;
  7813. }
  7814. var stepYear = (1000 * 60 * 60 * 24 * 30 * 12);
  7815. var stepMonth = (1000 * 60 * 60 * 24 * 30);
  7816. var stepDay = (1000 * 60 * 60 * 24);
  7817. var stepHour = (1000 * 60 * 60);
  7818. var stepMinute = (1000 * 60);
  7819. var stepSecond = (1000);
  7820. var stepMillisecond= (1);
  7821. // find the smallest step that is larger than the provided minimumStep
  7822. if (stepYear*1000 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1000;}
  7823. if (stepYear*500 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 500;}
  7824. if (stepYear*100 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 100;}
  7825. if (stepYear*50 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 50;}
  7826. if (stepYear*10 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 10;}
  7827. if (stepYear*5 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 5;}
  7828. if (stepYear > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1;}
  7829. if (stepMonth*3 > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 3;}
  7830. if (stepMonth > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 1;}
  7831. if (stepDay*5 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 5;}
  7832. if (stepDay*2 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 2;}
  7833. if (stepDay > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 1;}
  7834. if (stepDay/2 > minimumStep) {this.scale = TimeStep.SCALE.WEEKDAY; this.step = 1;}
  7835. if (stepHour*4 > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 4;}
  7836. if (stepHour > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 1;}
  7837. if (stepMinute*15 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 15;}
  7838. if (stepMinute*10 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 10;}
  7839. if (stepMinute*5 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 5;}
  7840. if (stepMinute > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 1;}
  7841. if (stepSecond*15 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 15;}
  7842. if (stepSecond*10 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 10;}
  7843. if (stepSecond*5 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 5;}
  7844. if (stepSecond > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 1;}
  7845. if (stepMillisecond*200 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 200;}
  7846. if (stepMillisecond*100 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 100;}
  7847. if (stepMillisecond*50 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 50;}
  7848. if (stepMillisecond*10 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 10;}
  7849. if (stepMillisecond*5 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 5;}
  7850. if (stepMillisecond > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 1;}
  7851. };
  7852. /**
  7853. * Snap a date to a rounded value.
  7854. * The snap intervals are dependent on the current scale and step.
  7855. * @param {Date} date the date to be snapped.
  7856. * @return {Date} snappedDate
  7857. */
  7858. TimeStep.prototype.snap = function(date) {
  7859. var clone = new Date(date.valueOf());
  7860. if (this.scale == TimeStep.SCALE.YEAR) {
  7861. var year = clone.getFullYear() + Math.round(clone.getMonth() / 12);
  7862. clone.setFullYear(Math.round(year / this.step) * this.step);
  7863. clone.setMonth(0);
  7864. clone.setDate(0);
  7865. clone.setHours(0);
  7866. clone.setMinutes(0);
  7867. clone.setSeconds(0);
  7868. clone.setMilliseconds(0);
  7869. }
  7870. else if (this.scale == TimeStep.SCALE.MONTH) {
  7871. if (clone.getDate() > 15) {
  7872. clone.setDate(1);
  7873. clone.setMonth(clone.getMonth() + 1);
  7874. // important: first set Date to 1, after that change the month.
  7875. }
  7876. else {
  7877. clone.setDate(1);
  7878. }
  7879. clone.setHours(0);
  7880. clone.setMinutes(0);
  7881. clone.setSeconds(0);
  7882. clone.setMilliseconds(0);
  7883. }
  7884. else if (this.scale == TimeStep.SCALE.DAY) {
  7885. //noinspection FallthroughInSwitchStatementJS
  7886. switch (this.step) {
  7887. case 5:
  7888. case 2:
  7889. clone.setHours(Math.round(clone.getHours() / 24) * 24); break;
  7890. default:
  7891. clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
  7892. }
  7893. clone.setMinutes(0);
  7894. clone.setSeconds(0);
  7895. clone.setMilliseconds(0);
  7896. }
  7897. else if (this.scale == TimeStep.SCALE.WEEKDAY) {
  7898. //noinspection FallthroughInSwitchStatementJS
  7899. switch (this.step) {
  7900. case 5:
  7901. case 2:
  7902. clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
  7903. default:
  7904. clone.setHours(Math.round(clone.getHours() / 6) * 6); break;
  7905. }
  7906. clone.setMinutes(0);
  7907. clone.setSeconds(0);
  7908. clone.setMilliseconds(0);
  7909. }
  7910. else if (this.scale == TimeStep.SCALE.HOUR) {
  7911. switch (this.step) {
  7912. case 4:
  7913. clone.setMinutes(Math.round(clone.getMinutes() / 60) * 60); break;
  7914. default:
  7915. clone.setMinutes(Math.round(clone.getMinutes() / 30) * 30); break;
  7916. }
  7917. clone.setSeconds(0);
  7918. clone.setMilliseconds(0);
  7919. } else if (this.scale == TimeStep.SCALE.MINUTE) {
  7920. //noinspection FallthroughInSwitchStatementJS
  7921. switch (this.step) {
  7922. case 15:
  7923. case 10:
  7924. clone.setMinutes(Math.round(clone.getMinutes() / 5) * 5);
  7925. clone.setSeconds(0);
  7926. break;
  7927. case 5:
  7928. clone.setSeconds(Math.round(clone.getSeconds() / 60) * 60); break;
  7929. default:
  7930. clone.setSeconds(Math.round(clone.getSeconds() / 30) * 30); break;
  7931. }
  7932. clone.setMilliseconds(0);
  7933. }
  7934. else if (this.scale == TimeStep.SCALE.SECOND) {
  7935. //noinspection FallthroughInSwitchStatementJS
  7936. switch (this.step) {
  7937. case 15:
  7938. case 10:
  7939. clone.setSeconds(Math.round(clone.getSeconds() / 5) * 5);
  7940. clone.setMilliseconds(0);
  7941. break;
  7942. case 5:
  7943. clone.setMilliseconds(Math.round(clone.getMilliseconds() / 1000) * 1000); break;
  7944. default:
  7945. clone.setMilliseconds(Math.round(clone.getMilliseconds() / 500) * 500); break;
  7946. }
  7947. }
  7948. else if (this.scale == TimeStep.SCALE.MILLISECOND) {
  7949. var step = this.step > 5 ? this.step / 2 : 1;
  7950. clone.setMilliseconds(Math.round(clone.getMilliseconds() / step) * step);
  7951. }
  7952. return clone;
  7953. };
  7954. /**
  7955. * Check if the current value is a major value (for example when the step
  7956. * is DAY, a major value is each first day of the MONTH)
  7957. * @return {boolean} true if current date is major, else false.
  7958. */
  7959. TimeStep.prototype.isMajor = function() {
  7960. switch (this.scale) {
  7961. case TimeStep.SCALE.MILLISECOND:
  7962. return (this.current.getMilliseconds() == 0);
  7963. case TimeStep.SCALE.SECOND:
  7964. return (this.current.getSeconds() == 0);
  7965. case TimeStep.SCALE.MINUTE:
  7966. return (this.current.getHours() == 0) && (this.current.getMinutes() == 0);
  7967. // Note: this is no bug. Major label is equal for both minute and hour scale
  7968. case TimeStep.SCALE.HOUR:
  7969. return (this.current.getHours() == 0);
  7970. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  7971. case TimeStep.SCALE.DAY:
  7972. return (this.current.getDate() == 1);
  7973. case TimeStep.SCALE.MONTH:
  7974. return (this.current.getMonth() == 0);
  7975. case TimeStep.SCALE.YEAR:
  7976. return false;
  7977. default:
  7978. return false;
  7979. }
  7980. };
  7981. /**
  7982. * Returns formatted text for the minor axislabel, depending on the current
  7983. * date and the scale. For example when scale is MINUTE, the current time is
  7984. * formatted as "hh:mm".
  7985. * @param {Date} [date] custom date. if not provided, current date is taken
  7986. */
  7987. TimeStep.prototype.getLabelMinor = function(date) {
  7988. if (date == undefined) {
  7989. date = this.current;
  7990. }
  7991. switch (this.scale) {
  7992. case TimeStep.SCALE.MILLISECOND: return moment(date).format('SSS');
  7993. case TimeStep.SCALE.SECOND: return moment(date).format('s');
  7994. case TimeStep.SCALE.MINUTE: return moment(date).format('HH:mm');
  7995. case TimeStep.SCALE.HOUR: return moment(date).format('HH:mm');
  7996. case TimeStep.SCALE.WEEKDAY: return moment(date).format('ddd D');
  7997. case TimeStep.SCALE.DAY: return moment(date).format('D');
  7998. case TimeStep.SCALE.MONTH: return moment(date).format('MMM');
  7999. case TimeStep.SCALE.YEAR: return moment(date).format('YYYY');
  8000. default: return '';
  8001. }
  8002. };
  8003. /**
  8004. * Returns formatted text for the major axis label, depending on the current
  8005. * date and the scale. For example when scale is MINUTE, the major scale is
  8006. * hours, and the hour will be formatted as "hh".
  8007. * @param {Date} [date] custom date. if not provided, current date is taken
  8008. */
  8009. TimeStep.prototype.getLabelMajor = function(date) {
  8010. if (date == undefined) {
  8011. date = this.current;
  8012. }
  8013. //noinspection FallthroughInSwitchStatementJS
  8014. switch (this.scale) {
  8015. case TimeStep.SCALE.MILLISECOND:return moment(date).format('HH:mm:ss');
  8016. case TimeStep.SCALE.SECOND: return moment(date).format('D MMMM HH:mm');
  8017. case TimeStep.SCALE.MINUTE:
  8018. case TimeStep.SCALE.HOUR: return moment(date).format('ddd D MMMM');
  8019. case TimeStep.SCALE.WEEKDAY:
  8020. case TimeStep.SCALE.DAY: return moment(date).format('MMMM YYYY');
  8021. case TimeStep.SCALE.MONTH: return moment(date).format('YYYY');
  8022. case TimeStep.SCALE.YEAR: return '';
  8023. default: return '';
  8024. }
  8025. };
  8026. module.exports = TimeStep;
  8027. /***/ },
  8028. /* 18 */
  8029. /***/ function(module, exports, __webpack_require__) {
  8030. /**
  8031. * Prototype for visual components
  8032. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} [body]
  8033. * @param {Object} [options]
  8034. */
  8035. function Component (body, options) {
  8036. this.options = null;
  8037. this.props = null;
  8038. }
  8039. /**
  8040. * Set options for the component. The new options will be merged into the
  8041. * current options.
  8042. * @param {Object} options
  8043. */
  8044. Component.prototype.setOptions = function(options) {
  8045. if (options) {
  8046. util.extend(this.options, options);
  8047. }
  8048. };
  8049. /**
  8050. * Repaint the component
  8051. * @return {boolean} Returns true if the component is resized
  8052. */
  8053. Component.prototype.redraw = function() {
  8054. // should be implemented by the component
  8055. return false;
  8056. };
  8057. /**
  8058. * Destroy the component. Cleanup DOM and event listeners
  8059. */
  8060. Component.prototype.destroy = function() {
  8061. // should be implemented by the component
  8062. };
  8063. /**
  8064. * Test whether the component is resized since the last time _isResized() was
  8065. * called.
  8066. * @return {Boolean} Returns true if the component is resized
  8067. * @protected
  8068. */
  8069. Component.prototype._isResized = function() {
  8070. var resized = (this.props._previousWidth !== this.props.width ||
  8071. this.props._previousHeight !== this.props.height);
  8072. this.props._previousWidth = this.props.width;
  8073. this.props._previousHeight = this.props.height;
  8074. return resized;
  8075. };
  8076. module.exports = Component;
  8077. /***/ },
  8078. /* 19 */
  8079. /***/ function(module, exports, __webpack_require__) {
  8080. var util = __webpack_require__(1);
  8081. var Component = __webpack_require__(18);
  8082. /**
  8083. * A current time bar
  8084. * @param {{range: Range, dom: Object, domProps: Object}} body
  8085. * @param {Object} [options] Available parameters:
  8086. * {Boolean} [showCurrentTime]
  8087. * @constructor CurrentTime
  8088. * @extends Component
  8089. */
  8090. function CurrentTime (body, options) {
  8091. this.body = body;
  8092. // default options
  8093. this.defaultOptions = {
  8094. showCurrentTime: true
  8095. };
  8096. this.options = util.extend({}, this.defaultOptions);
  8097. this._create();
  8098. this.setOptions(options);
  8099. }
  8100. CurrentTime.prototype = new Component();
  8101. /**
  8102. * Create the HTML DOM for the current time bar
  8103. * @private
  8104. */
  8105. CurrentTime.prototype._create = function() {
  8106. var bar = document.createElement('div');
  8107. bar.className = 'currenttime';
  8108. bar.style.position = 'absolute';
  8109. bar.style.top = '0px';
  8110. bar.style.height = '100%';
  8111. this.bar = bar;
  8112. };
  8113. /**
  8114. * Destroy the CurrentTime bar
  8115. */
  8116. CurrentTime.prototype.destroy = function () {
  8117. this.options.showCurrentTime = false;
  8118. this.redraw(); // will remove the bar from the DOM and stop refreshing
  8119. this.body = null;
  8120. };
  8121. /**
  8122. * Set options for the component. Options will be merged in current options.
  8123. * @param {Object} options Available parameters:
  8124. * {boolean} [showCurrentTime]
  8125. */
  8126. CurrentTime.prototype.setOptions = function(options) {
  8127. if (options) {
  8128. // copy all options that we know
  8129. util.selectiveExtend(['showCurrentTime'], this.options, options);
  8130. }
  8131. };
  8132. /**
  8133. * Repaint the component
  8134. * @return {boolean} Returns true if the component is resized
  8135. */
  8136. CurrentTime.prototype.redraw = function() {
  8137. if (this.options.showCurrentTime) {
  8138. var parent = this.body.dom.backgroundVertical;
  8139. if (this.bar.parentNode != parent) {
  8140. // attach to the dom
  8141. if (this.bar.parentNode) {
  8142. this.bar.parentNode.removeChild(this.bar);
  8143. }
  8144. parent.appendChild(this.bar);
  8145. this.start();
  8146. }
  8147. var now = new Date();
  8148. var x = this.body.util.toScreen(now);
  8149. this.bar.style.left = x + 'px';
  8150. this.bar.title = 'Current time: ' + now;
  8151. }
  8152. else {
  8153. // remove the line from the DOM
  8154. if (this.bar.parentNode) {
  8155. this.bar.parentNode.removeChild(this.bar);
  8156. }
  8157. this.stop();
  8158. }
  8159. return false;
  8160. };
  8161. /**
  8162. * Start auto refreshing the current time bar
  8163. */
  8164. CurrentTime.prototype.start = function() {
  8165. var me = this;
  8166. function update () {
  8167. me.stop();
  8168. // determine interval to refresh
  8169. var scale = me.body.range.conversion(me.body.domProps.center.width).scale;
  8170. var interval = 1 / scale / 10;
  8171. if (interval < 30) interval = 30;
  8172. if (interval > 1000) interval = 1000;
  8173. me.redraw();
  8174. // start a timer to adjust for the new time
  8175. me.currentTimeTimer = setTimeout(update, interval);
  8176. }
  8177. update();
  8178. };
  8179. /**
  8180. * Stop auto refreshing the current time bar
  8181. */
  8182. CurrentTime.prototype.stop = function() {
  8183. if (this.currentTimeTimer !== undefined) {
  8184. clearTimeout(this.currentTimeTimer);
  8185. delete this.currentTimeTimer;
  8186. }
  8187. };
  8188. module.exports = CurrentTime;
  8189. /***/ },
  8190. /* 20 */
  8191. /***/ function(module, exports, __webpack_require__) {
  8192. var Hammer = __webpack_require__(41);
  8193. var util = __webpack_require__(1);
  8194. var Component = __webpack_require__(18);
  8195. /**
  8196. * A custom time bar
  8197. * @param {{range: Range, dom: Object}} body
  8198. * @param {Object} [options] Available parameters:
  8199. * {Boolean} [showCustomTime]
  8200. * @constructor CustomTime
  8201. * @extends Component
  8202. */
  8203. function CustomTime (body, options) {
  8204. this.body = body;
  8205. // default options
  8206. this.defaultOptions = {
  8207. showCustomTime: false
  8208. };
  8209. this.options = util.extend({}, this.defaultOptions);
  8210. this.customTime = new Date();
  8211. this.eventParams = {}; // stores state parameters while dragging the bar
  8212. // create the DOM
  8213. this._create();
  8214. this.setOptions(options);
  8215. }
  8216. CustomTime.prototype = new Component();
  8217. /**
  8218. * Set options for the component. Options will be merged in current options.
  8219. * @param {Object} options Available parameters:
  8220. * {boolean} [showCustomTime]
  8221. */
  8222. CustomTime.prototype.setOptions = function(options) {
  8223. if (options) {
  8224. // copy all options that we know
  8225. util.selectiveExtend(['showCustomTime'], this.options, options);
  8226. }
  8227. };
  8228. /**
  8229. * Create the DOM for the custom time
  8230. * @private
  8231. */
  8232. CustomTime.prototype._create = function() {
  8233. var bar = document.createElement('div');
  8234. bar.className = 'customtime';
  8235. bar.style.position = 'absolute';
  8236. bar.style.top = '0px';
  8237. bar.style.height = '100%';
  8238. this.bar = bar;
  8239. var drag = document.createElement('div');
  8240. drag.style.position = 'relative';
  8241. drag.style.top = '0px';
  8242. drag.style.left = '-10px';
  8243. drag.style.height = '100%';
  8244. drag.style.width = '20px';
  8245. bar.appendChild(drag);
  8246. // attach event listeners
  8247. this.hammer = Hammer(bar, {
  8248. prevent_default: true
  8249. });
  8250. this.hammer.on('dragstart', this._onDragStart.bind(this));
  8251. this.hammer.on('drag', this._onDrag.bind(this));
  8252. this.hammer.on('dragend', this._onDragEnd.bind(this));
  8253. };
  8254. /**
  8255. * Destroy the CustomTime bar
  8256. */
  8257. CustomTime.prototype.destroy = function () {
  8258. this.options.showCustomTime = false;
  8259. this.redraw(); // will remove the bar from the DOM
  8260. this.hammer.enable(false);
  8261. this.hammer = null;
  8262. this.body = null;
  8263. };
  8264. /**
  8265. * Repaint the component
  8266. * @return {boolean} Returns true if the component is resized
  8267. */
  8268. CustomTime.prototype.redraw = function () {
  8269. if (this.options.showCustomTime) {
  8270. var parent = this.body.dom.backgroundVertical;
  8271. if (this.bar.parentNode != parent) {
  8272. // attach to the dom
  8273. if (this.bar.parentNode) {
  8274. this.bar.parentNode.removeChild(this.bar);
  8275. }
  8276. parent.appendChild(this.bar);
  8277. }
  8278. var x = this.body.util.toScreen(this.customTime);
  8279. this.bar.style.left = x + 'px';
  8280. this.bar.title = 'Time: ' + this.customTime;
  8281. }
  8282. else {
  8283. // remove the line from the DOM
  8284. if (this.bar.parentNode) {
  8285. this.bar.parentNode.removeChild(this.bar);
  8286. }
  8287. }
  8288. return false;
  8289. };
  8290. /**
  8291. * Set custom time.
  8292. * @param {Date} time
  8293. */
  8294. CustomTime.prototype.setCustomTime = function(time) {
  8295. this.customTime = new Date(time.valueOf());
  8296. this.redraw();
  8297. };
  8298. /**
  8299. * Retrieve the current custom time.
  8300. * @return {Date} customTime
  8301. */
  8302. CustomTime.prototype.getCustomTime = function() {
  8303. return new Date(this.customTime.valueOf());
  8304. };
  8305. /**
  8306. * Start moving horizontally
  8307. * @param {Event} event
  8308. * @private
  8309. */
  8310. CustomTime.prototype._onDragStart = function(event) {
  8311. this.eventParams.dragging = true;
  8312. this.eventParams.customTime = this.customTime;
  8313. event.stopPropagation();
  8314. event.preventDefault();
  8315. };
  8316. /**
  8317. * Perform moving operating.
  8318. * @param {Event} event
  8319. * @private
  8320. */
  8321. CustomTime.prototype._onDrag = function (event) {
  8322. if (!this.eventParams.dragging) return;
  8323. var deltaX = event.gesture.deltaX,
  8324. x = this.body.util.toScreen(this.eventParams.customTime) + deltaX,
  8325. time = this.body.util.toTime(x);
  8326. this.setCustomTime(time);
  8327. // fire a timechange event
  8328. this.body.emitter.emit('timechange', {
  8329. time: new Date(this.customTime.valueOf())
  8330. });
  8331. event.stopPropagation();
  8332. event.preventDefault();
  8333. };
  8334. /**
  8335. * Stop moving operating.
  8336. * @param {event} event
  8337. * @private
  8338. */
  8339. CustomTime.prototype._onDragEnd = function (event) {
  8340. if (!this.eventParams.dragging) return;
  8341. // fire a timechanged event
  8342. this.body.emitter.emit('timechanged', {
  8343. time: new Date(this.customTime.valueOf())
  8344. });
  8345. event.stopPropagation();
  8346. event.preventDefault();
  8347. };
  8348. module.exports = CustomTime;
  8349. /***/ },
  8350. /* 21 */
  8351. /***/ function(module, exports, __webpack_require__) {
  8352. var util = __webpack_require__(1);
  8353. var DOMutil = __webpack_require__(2);
  8354. var Component = __webpack_require__(18);
  8355. var DataStep = __webpack_require__(14);
  8356. /**
  8357. * A horizontal time axis
  8358. * @param {Object} [options] See DataAxis.setOptions for the available
  8359. * options.
  8360. * @constructor DataAxis
  8361. * @extends Component
  8362. * @param body
  8363. */
  8364. function DataAxis (body, options, svg) {
  8365. this.id = util.randomUUID();
  8366. this.body = body;
  8367. this.defaultOptions = {
  8368. orientation: 'left', // supported: 'left', 'right'
  8369. showMinorLabels: true,
  8370. showMajorLabels: true,
  8371. icons: true,
  8372. majorLinesOffset: 7,
  8373. minorLinesOffset: 4,
  8374. labelOffsetX: 10,
  8375. labelOffsetY: 2,
  8376. iconWidth: 20,
  8377. width: '40px',
  8378. visible: true
  8379. };
  8380. this.linegraphSVG = svg;
  8381. this.props = {};
  8382. this.DOMelements = { // dynamic elements
  8383. lines: {},
  8384. labels: {}
  8385. };
  8386. this.dom = {};
  8387. this.range = {start:0, end:0};
  8388. this.options = util.extend({}, this.defaultOptions);
  8389. this.conversionFactor = 1;
  8390. this.setOptions(options);
  8391. this.width = Number(('' + this.options.width).replace("px",""));
  8392. this.minWidth = this.width;
  8393. this.height = this.linegraphSVG.offsetHeight;
  8394. this.stepPixels = 25;
  8395. this.stepPixelsForced = 25;
  8396. this.lineOffset = 0;
  8397. this.master = true;
  8398. this.svgElements = {};
  8399. this.groups = {};
  8400. this.amountOfGroups = 0;
  8401. // create the HTML DOM
  8402. this._create();
  8403. }
  8404. DataAxis.prototype = new Component();
  8405. DataAxis.prototype.addGroup = function(label, graphOptions) {
  8406. if (!this.groups.hasOwnProperty(label)) {
  8407. this.groups[label] = graphOptions;
  8408. }
  8409. this.amountOfGroups += 1;
  8410. };
  8411. DataAxis.prototype.updateGroup = function(label, graphOptions) {
  8412. this.groups[label] = graphOptions;
  8413. };
  8414. DataAxis.prototype.removeGroup = function(label) {
  8415. if (this.groups.hasOwnProperty(label)) {
  8416. delete this.groups[label];
  8417. this.amountOfGroups -= 1;
  8418. }
  8419. };
  8420. DataAxis.prototype.setOptions = function (options) {
  8421. if (options) {
  8422. var redraw = false;
  8423. if (this.options.orientation != options.orientation && options.orientation !== undefined) {
  8424. redraw = true;
  8425. }
  8426. var fields = [
  8427. 'orientation',
  8428. 'showMinorLabels',
  8429. 'showMajorLabels',
  8430. 'icons',
  8431. 'majorLinesOffset',
  8432. 'minorLinesOffset',
  8433. 'labelOffsetX',
  8434. 'labelOffsetY',
  8435. 'iconWidth',
  8436. 'width',
  8437. 'visible'];
  8438. util.selectiveExtend(fields, this.options, options);
  8439. this.minWidth = Number(('' + this.options.width).replace("px",""));
  8440. if (redraw == true && this.dom.frame) {
  8441. this.hide();
  8442. this.show();
  8443. }
  8444. }
  8445. };
  8446. /**
  8447. * Create the HTML DOM for the DataAxis
  8448. */
  8449. DataAxis.prototype._create = function() {
  8450. this.dom.frame = document.createElement('div');
  8451. this.dom.frame.style.width = this.options.width;
  8452. this.dom.frame.style.height = this.height;
  8453. this.dom.lineContainer = document.createElement('div');
  8454. this.dom.lineContainer.style.width = '100%';
  8455. this.dom.lineContainer.style.height = this.height;
  8456. // create svg element for graph drawing.
  8457. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  8458. this.svg.style.position = "absolute";
  8459. this.svg.style.top = '0px';
  8460. this.svg.style.height = '100%';
  8461. this.svg.style.width = '100%';
  8462. this.svg.style.display = "block";
  8463. this.dom.frame.appendChild(this.svg);
  8464. };
  8465. DataAxis.prototype._redrawGroupIcons = function () {
  8466. DOMutil.prepareElements(this.svgElements);
  8467. var x;
  8468. var iconWidth = this.options.iconWidth;
  8469. var iconHeight = 15;
  8470. var iconOffset = 4;
  8471. var y = iconOffset + 0.5 * iconHeight;
  8472. if (this.options.orientation == 'left') {
  8473. x = iconOffset;
  8474. }
  8475. else {
  8476. x = this.width - iconWidth - iconOffset;
  8477. }
  8478. for (var groupId in this.groups) {
  8479. if (this.groups.hasOwnProperty(groupId)) {
  8480. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  8481. y += iconHeight + iconOffset;
  8482. }
  8483. }
  8484. DOMutil.cleanupElements(this.svgElements);
  8485. };
  8486. /**
  8487. * Create the HTML DOM for the DataAxis
  8488. */
  8489. DataAxis.prototype.show = function() {
  8490. if (!this.dom.frame.parentNode) {
  8491. if (this.options.orientation == 'left') {
  8492. this.body.dom.left.appendChild(this.dom.frame);
  8493. }
  8494. else {
  8495. this.body.dom.right.appendChild(this.dom.frame);
  8496. }
  8497. }
  8498. if (!this.dom.lineContainer.parentNode) {
  8499. this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer);
  8500. }
  8501. };
  8502. /**
  8503. * Create the HTML DOM for the DataAxis
  8504. */
  8505. DataAxis.prototype.hide = function() {
  8506. if (this.dom.frame.parentNode) {
  8507. this.dom.frame.parentNode.removeChild(this.dom.frame);
  8508. }
  8509. if (this.dom.lineContainer.parentNode) {
  8510. this.dom.lineContainer.parentNode.removeChild(this.dom.lineContainer);
  8511. }
  8512. };
  8513. /**
  8514. * Set a range (start and end)
  8515. * @param end
  8516. * @param start
  8517. * @param end
  8518. */
  8519. DataAxis.prototype.setRange = function (start, end) {
  8520. this.range.start = start;
  8521. this.range.end = end;
  8522. };
  8523. /**
  8524. * Repaint the component
  8525. * @return {boolean} Returns true if the component is resized
  8526. */
  8527. DataAxis.prototype.redraw = function () {
  8528. var changeCalled = false;
  8529. if (this.amountOfGroups == 0) {
  8530. this.hide();
  8531. }
  8532. else {
  8533. this.show();
  8534. this.height = Number(this.linegraphSVG.style.height.replace("px",""));
  8535. // svg offsetheight did not work in firefox and explorer...
  8536. this.dom.lineContainer.style.height = this.height + 'px';
  8537. this.width = this.options.visible == true ? Number(('' + this.options.width).replace("px","")) : 0;
  8538. var props = this.props;
  8539. var frame = this.dom.frame;
  8540. // update classname
  8541. frame.className = 'dataaxis';
  8542. // calculate character width and height
  8543. this._calculateCharSize();
  8544. var orientation = this.options.orientation;
  8545. var showMinorLabels = this.options.showMinorLabels;
  8546. var showMajorLabels = this.options.showMajorLabels;
  8547. // determine the width and height of the elemens for the axis
  8548. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  8549. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  8550. props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset;
  8551. props.minorLineHeight = 1;
  8552. props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset;
  8553. props.majorLineHeight = 1;
  8554. // take frame offline while updating (is almost twice as fast)
  8555. if (orientation == 'left') {
  8556. frame.style.top = '0';
  8557. frame.style.left = '0';
  8558. frame.style.bottom = '';
  8559. frame.style.width = this.width + 'px';
  8560. frame.style.height = this.height + "px";
  8561. }
  8562. else { // right
  8563. frame.style.top = '';
  8564. frame.style.bottom = '0';
  8565. frame.style.left = '0';
  8566. frame.style.width = this.width + 'px';
  8567. frame.style.height = this.height + "px";
  8568. }
  8569. changeCalled = this._redrawLabels();
  8570. if (this.options.icons == true) {
  8571. this._redrawGroupIcons();
  8572. }
  8573. }
  8574. return changeCalled;
  8575. };
  8576. /**
  8577. * Repaint major and minor text labels and vertical grid lines
  8578. * @private
  8579. */
  8580. DataAxis.prototype._redrawLabels = function () {
  8581. DOMutil.prepareElements(this.DOMelements);
  8582. var orientation = this.options['orientation'];
  8583. // calculate range and step (step such that we have space for 7 characters per label)
  8584. var minimumStep = this.master ? this.props.majorCharHeight || 10 : this.stepPixelsForced;
  8585. var step = new DataStep(this.range.start, this.range.end, minimumStep, this.dom.frame.offsetHeight);
  8586. this.step = step;
  8587. step.first();
  8588. // get the distance in pixels for a step
  8589. var stepPixels = this.dom.frame.offsetHeight / ((step.marginRange / step.step) + 1);
  8590. this.stepPixels = stepPixels;
  8591. var amountOfSteps = this.height / stepPixels;
  8592. var stepDifference = 0;
  8593. if (this.master == false) {
  8594. stepPixels = this.stepPixelsForced;
  8595. stepDifference = Math.round((this.height / stepPixels) - amountOfSteps);
  8596. for (var i = 0; i < 0.5 * stepDifference; i++) {
  8597. step.previous();
  8598. }
  8599. amountOfSteps = this.height / stepPixels;
  8600. }
  8601. this.valueAtZero = step.marginEnd;
  8602. var marginStartPos = 0;
  8603. // do not draw the first label
  8604. var max = 1;
  8605. step.next();
  8606. this.maxLabelSize = 0;
  8607. var y = 0;
  8608. while (max < Math.round(amountOfSteps)) {
  8609. y = Math.round(max * stepPixels);
  8610. marginStartPos = max * stepPixels;
  8611. var isMajor = step.isMajor();
  8612. if (this.options['showMinorLabels'] && isMajor == false || this.master == false && this.options['showMinorLabels'] == true) {
  8613. this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis minor', this.props.minorCharHeight);
  8614. }
  8615. if (isMajor && this.options['showMajorLabels'] && this.master == true ||
  8616. this.options['showMinorLabels'] == false && this.master == false && isMajor == true) {
  8617. if (y >= 0) {
  8618. this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis major', this.props.majorCharHeight);
  8619. }
  8620. this._redrawLine(y, orientation, 'grid horizontal major', this.options.majorLinesOffset, this.props.majorLineWidth);
  8621. }
  8622. else {
  8623. this._redrawLine(y, orientation, 'grid horizontal minor', this.options.minorLinesOffset, this.props.minorLineWidth);
  8624. }
  8625. step.next();
  8626. max++;
  8627. }
  8628. this.conversionFactor = marginStartPos/((amountOfSteps-1) * step.step);
  8629. var offset = this.options.icons == true ? this.options.iconWidth + this.options.labelOffsetX + 15 : this.options.labelOffsetX + 15;
  8630. // this will resize the yAxis to accomodate the labels.
  8631. if (this.maxLabelSize > (this.width - offset) && this.options.visible == true) {
  8632. this.width = this.maxLabelSize + offset;
  8633. this.options.width = this.width + "px";
  8634. DOMutil.cleanupElements(this.DOMelements);
  8635. this.redraw();
  8636. return true;
  8637. }
  8638. // this will resize the yAxis if it is too big for the labels.
  8639. else if (this.maxLabelSize < (this.width - offset) && this.options.visible == true && this.width > this.minWidth) {
  8640. this.width = Math.max(this.minWidth,this.maxLabelSize + offset);
  8641. this.options.width = this.width + "px";
  8642. DOMutil.cleanupElements(this.DOMelements);
  8643. this.redraw();
  8644. return true;
  8645. }
  8646. else {
  8647. DOMutil.cleanupElements(this.DOMelements);
  8648. return false;
  8649. }
  8650. };
  8651. /**
  8652. * Create a label for the axis at position x
  8653. * @private
  8654. * @param y
  8655. * @param text
  8656. * @param orientation
  8657. * @param className
  8658. * @param characterHeight
  8659. */
  8660. DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) {
  8661. // reuse redundant label
  8662. var label = DOMutil.getDOMElement('div',this.DOMelements, this.dom.frame); //this.dom.redundant.labels.shift();
  8663. label.className = className;
  8664. label.innerHTML = text;
  8665. if (orientation == 'left') {
  8666. label.style.left = '-' + this.options.labelOffsetX + 'px';
  8667. label.style.textAlign = "right";
  8668. }
  8669. else {
  8670. label.style.right = '-' + this.options.labelOffsetX + 'px';
  8671. label.style.textAlign = "left";
  8672. }
  8673. label.style.top = y - 0.5 * characterHeight + this.options.labelOffsetY + 'px';
  8674. text += '';
  8675. var largestWidth = Math.max(this.props.majorCharWidth,this.props.minorCharWidth);
  8676. if (this.maxLabelSize < text.length * largestWidth) {
  8677. this.maxLabelSize = text.length * largestWidth;
  8678. }
  8679. };
  8680. /**
  8681. * Create a minor line for the axis at position y
  8682. * @param y
  8683. * @param orientation
  8684. * @param className
  8685. * @param offset
  8686. * @param width
  8687. */
  8688. DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) {
  8689. if (this.master == true) {
  8690. var line = DOMutil.getDOMElement('div',this.DOMelements, this.dom.lineContainer);//this.dom.redundant.lines.shift();
  8691. line.className = className;
  8692. line.innerHTML = '';
  8693. if (orientation == 'left') {
  8694. line.style.left = (this.width - offset) + 'px';
  8695. }
  8696. else {
  8697. line.style.right = (this.width - offset) + 'px';
  8698. }
  8699. line.style.width = width + 'px';
  8700. line.style.top = y + 'px';
  8701. }
  8702. };
  8703. DataAxis.prototype.convertValue = function (value) {
  8704. var invertedValue = this.valueAtZero - value;
  8705. var convertedValue = invertedValue * this.conversionFactor;
  8706. return convertedValue; // the -2 is to compensate for the borders
  8707. };
  8708. /**
  8709. * Determine the size of text on the axis (both major and minor axis).
  8710. * The size is calculated only once and then cached in this.props.
  8711. * @private
  8712. */
  8713. DataAxis.prototype._calculateCharSize = function () {
  8714. // determine the char width and height on the minor axis
  8715. if (!('minorCharHeight' in this.props)) {
  8716. var textMinor = document.createTextNode('0');
  8717. var measureCharMinor = document.createElement('DIV');
  8718. measureCharMinor.className = 'yAxis minor measure';
  8719. measureCharMinor.appendChild(textMinor);
  8720. this.dom.frame.appendChild(measureCharMinor);
  8721. this.props.minorCharHeight = measureCharMinor.clientHeight;
  8722. this.props.minorCharWidth = measureCharMinor.clientWidth;
  8723. this.dom.frame.removeChild(measureCharMinor);
  8724. }
  8725. if (!('majorCharHeight' in this.props)) {
  8726. var textMajor = document.createTextNode('0');
  8727. var measureCharMajor = document.createElement('DIV');
  8728. measureCharMajor.className = 'yAxis major measure';
  8729. measureCharMajor.appendChild(textMajor);
  8730. this.dom.frame.appendChild(measureCharMajor);
  8731. this.props.majorCharHeight = measureCharMajor.clientHeight;
  8732. this.props.majorCharWidth = measureCharMajor.clientWidth;
  8733. this.dom.frame.removeChild(measureCharMajor);
  8734. }
  8735. };
  8736. /**
  8737. * Snap a date to a rounded value.
  8738. * The snap intervals are dependent on the current scale and step.
  8739. * @param {Date} date the date to be snapped.
  8740. * @return {Date} snappedDate
  8741. */
  8742. DataAxis.prototype.snap = function(date) {
  8743. return this.step.snap(date);
  8744. };
  8745. module.exports = DataAxis;
  8746. /***/ },
  8747. /* 22 */
  8748. /***/ function(module, exports, __webpack_require__) {
  8749. var util = __webpack_require__(1);
  8750. var DOMutil = __webpack_require__(2);
  8751. /**
  8752. * @constructor Group
  8753. * @param {Number | String} groupId
  8754. * @param {Object} data
  8755. * @param {ItemSet} itemSet
  8756. */
  8757. function GraphGroup (group, groupId, options, groupsUsingDefaultStyles) {
  8758. this.id = groupId;
  8759. var fields = ['sampling','style','sort','yAxisOrientation','barChart','drawPoints','shaded','catmullRom']
  8760. this.options = util.selectiveBridgeObject(fields,options);
  8761. this.usingDefaultStyle = group.className === undefined;
  8762. this.groupsUsingDefaultStyles = groupsUsingDefaultStyles;
  8763. this.zeroPosition = 0;
  8764. this.update(group);
  8765. if (this.usingDefaultStyle == true) {
  8766. this.groupsUsingDefaultStyles[0] += 1;
  8767. }
  8768. this.itemsData = [];
  8769. }
  8770. GraphGroup.prototype.setItems = function(items) {
  8771. if (items != null) {
  8772. this.itemsData = items;
  8773. if (this.options.sort == true) {
  8774. this.itemsData.sort(function (a,b) {return a.x - b.x;})
  8775. }
  8776. }
  8777. else {
  8778. this.itemsData = [];
  8779. }
  8780. };
  8781. GraphGroup.prototype.setZeroPosition = function(pos) {
  8782. this.zeroPosition = pos;
  8783. };
  8784. GraphGroup.prototype.setOptions = function(options) {
  8785. if (options !== undefined) {
  8786. var fields = ['sampling','style','sort','yAxisOrientation','barChart'];
  8787. util.selectiveDeepExtend(fields, this.options, options);
  8788. util.mergeOptions(this.options, options,'catmullRom');
  8789. util.mergeOptions(this.options, options,'drawPoints');
  8790. util.mergeOptions(this.options, options,'shaded');
  8791. if (options.catmullRom) {
  8792. if (typeof options.catmullRom == 'object') {
  8793. if (options.catmullRom.parametrization) {
  8794. if (options.catmullRom.parametrization == 'uniform') {
  8795. this.options.catmullRom.alpha = 0;
  8796. }
  8797. else if (options.catmullRom.parametrization == 'chordal') {
  8798. this.options.catmullRom.alpha = 1.0;
  8799. }
  8800. else {
  8801. this.options.catmullRom.parametrization = 'centripetal';
  8802. this.options.catmullRom.alpha = 0.5;
  8803. }
  8804. }
  8805. }
  8806. }
  8807. }
  8808. };
  8809. GraphGroup.prototype.update = function(group) {
  8810. this.group = group;
  8811. this.content = group.content || 'graph';
  8812. this.className = group.className || this.className || "graphGroup" + this.groupsUsingDefaultStyles[0] % 10;
  8813. this.setOptions(group.options);
  8814. };
  8815. GraphGroup.prototype.drawIcon = function(x, y, JSONcontainer, SVGcontainer, iconWidth, iconHeight) {
  8816. var fillHeight = iconHeight * 0.5;
  8817. var path, fillPath;
  8818. var outline = DOMutil.getSVGElement("rect", JSONcontainer, SVGcontainer);
  8819. outline.setAttributeNS(null, "x", x);
  8820. outline.setAttributeNS(null, "y", y - fillHeight);
  8821. outline.setAttributeNS(null, "width", iconWidth);
  8822. outline.setAttributeNS(null, "height", 2*fillHeight);
  8823. outline.setAttributeNS(null, "class", "outline");
  8824. if (this.options.style == 'line') {
  8825. path = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
  8826. path.setAttributeNS(null, "class", this.className);
  8827. path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+"");
  8828. if (this.options.shaded.enabled == true) {
  8829. fillPath = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
  8830. if (this.options.shaded.orientation == 'top') {
  8831. fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) +
  8832. "L"+x+","+y+" L"+ (x + iconWidth) + ","+y+" L"+ (x + iconWidth) + "," + (y - fillHeight));
  8833. }
  8834. else {
  8835. fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " +
  8836. "L"+x+"," + (y + fillHeight) + " " +
  8837. "L"+ (x + iconWidth) + "," + (y + fillHeight) +
  8838. "L"+ (x + iconWidth) + ","+y);
  8839. }
  8840. fillPath.setAttributeNS(null, "class", this.className + " iconFill");
  8841. }
  8842. if (this.options.drawPoints.enabled == true) {
  8843. DOMutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer);
  8844. }
  8845. }
  8846. else {
  8847. var barWidth = Math.round(0.3 * iconWidth);
  8848. var bar1Height = Math.round(0.4 * iconHeight);
  8849. var bar2Height = Math.round(0.75 * iconHeight);
  8850. var offset = Math.round((iconWidth - (2 * barWidth))/3);
  8851. DOMutil.drawBar(x + 0.5*barWidth + offset , y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' bar', JSONcontainer, SVGcontainer);
  8852. DOMutil.drawBar(x + 1.5*barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' bar', JSONcontainer, SVGcontainer);
  8853. }
  8854. };
  8855. module.exports = GraphGroup;
  8856. /***/ },
  8857. /* 23 */
  8858. /***/ function(module, exports, __webpack_require__) {
  8859. var util = __webpack_require__(1);
  8860. var stack = __webpack_require__(16);
  8861. var ItemRange = __webpack_require__(31);
  8862. /**
  8863. * @constructor Group
  8864. * @param {Number | String} groupId
  8865. * @param {Object} data
  8866. * @param {ItemSet} itemSet
  8867. */
  8868. function Group (groupId, data, itemSet) {
  8869. this.groupId = groupId;
  8870. this.itemSet = itemSet;
  8871. this.dom = {};
  8872. this.props = {
  8873. label: {
  8874. width: 0,
  8875. height: 0
  8876. }
  8877. };
  8878. this.className = null;
  8879. this.items = {}; // items filtered by groupId of this group
  8880. this.visibleItems = []; // items currently visible in window
  8881. this.orderedItems = { // items sorted by start and by end
  8882. byStart: [],
  8883. byEnd: []
  8884. };
  8885. this._create();
  8886. this.setData(data);
  8887. }
  8888. /**
  8889. * Create DOM elements for the group
  8890. * @private
  8891. */
  8892. Group.prototype._create = function() {
  8893. var label = document.createElement('div');
  8894. label.className = 'vlabel';
  8895. this.dom.label = label;
  8896. var inner = document.createElement('div');
  8897. inner.className = 'inner';
  8898. label.appendChild(inner);
  8899. this.dom.inner = inner;
  8900. var foreground = document.createElement('div');
  8901. foreground.className = 'group';
  8902. foreground['timeline-group'] = this;
  8903. this.dom.foreground = foreground;
  8904. this.dom.background = document.createElement('div');
  8905. this.dom.background.className = 'group';
  8906. this.dom.axis = document.createElement('div');
  8907. this.dom.axis.className = 'group';
  8908. // create a hidden marker to detect when the Timelines container is attached
  8909. // to the DOM, or the style of a parent of the Timeline is changed from
  8910. // display:none is changed to visible.
  8911. this.dom.marker = document.createElement('div');
  8912. this.dom.marker.style.visibility = 'hidden';
  8913. this.dom.marker.innerHTML = '?';
  8914. this.dom.background.appendChild(this.dom.marker);
  8915. };
  8916. /**
  8917. * Set the group data for this group
  8918. * @param {Object} data Group data, can contain properties content and className
  8919. */
  8920. Group.prototype.setData = function(data) {
  8921. // update contents
  8922. var content = data && data.content;
  8923. if (content instanceof Element) {
  8924. this.dom.inner.appendChild(content);
  8925. }
  8926. else if (content != undefined) {
  8927. this.dom.inner.innerHTML = content;
  8928. }
  8929. else {
  8930. this.dom.inner.innerHTML = this.groupId;
  8931. }
  8932. // update title
  8933. this.dom.label.title = data && data.title || '';
  8934. if (!this.dom.inner.firstChild) {
  8935. util.addClassName(this.dom.inner, 'hidden');
  8936. }
  8937. else {
  8938. util.removeClassName(this.dom.inner, 'hidden');
  8939. }
  8940. // update className
  8941. var className = data && data.className || null;
  8942. if (className != this.className) {
  8943. if (this.className) {
  8944. util.removeClassName(this.dom.label, className);
  8945. util.removeClassName(this.dom.foreground, className);
  8946. util.removeClassName(this.dom.background, className);
  8947. util.removeClassName(this.dom.axis, className);
  8948. }
  8949. util.addClassName(this.dom.label, className);
  8950. util.addClassName(this.dom.foreground, className);
  8951. util.addClassName(this.dom.background, className);
  8952. util.addClassName(this.dom.axis, className);
  8953. }
  8954. };
  8955. /**
  8956. * Get the width of the group label
  8957. * @return {number} width
  8958. */
  8959. Group.prototype.getLabelWidth = function() {
  8960. return this.props.label.width;
  8961. };
  8962. /**
  8963. * Repaint this group
  8964. * @param {{start: number, end: number}} range
  8965. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  8966. * @param {boolean} [restack=false] Force restacking of all items
  8967. * @return {boolean} Returns true if the group is resized
  8968. */
  8969. Group.prototype.redraw = function(range, margin, restack) {
  8970. var resized = false;
  8971. this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
  8972. // force recalculation of the height of the items when the marker height changed
  8973. // (due to the Timeline being attached to the DOM or changed from display:none to visible)
  8974. var markerHeight = this.dom.marker.clientHeight;
  8975. if (markerHeight != this.lastMarkerHeight) {
  8976. this.lastMarkerHeight = markerHeight;
  8977. util.forEach(this.items, function (item) {
  8978. item.dirty = true;
  8979. if (item.displayed) item.redraw();
  8980. });
  8981. restack = true;
  8982. }
  8983. // reposition visible items vertically
  8984. if (this.itemSet.options.stack) { // TODO: ugly way to access options...
  8985. stack.stack(this.visibleItems, margin, restack);
  8986. }
  8987. else { // no stacking
  8988. stack.nostack(this.visibleItems, margin);
  8989. }
  8990. // recalculate the height of the group
  8991. var height;
  8992. var visibleItems = this.visibleItems;
  8993. if (visibleItems.length) {
  8994. var min = visibleItems[0].top;
  8995. var max = visibleItems[0].top + visibleItems[0].height;
  8996. util.forEach(visibleItems, function (item) {
  8997. min = Math.min(min, item.top);
  8998. max = Math.max(max, (item.top + item.height));
  8999. });
  9000. if (min > margin.axis) {
  9001. // there is an empty gap between the lowest item and the axis
  9002. var offset = min - margin.axis;
  9003. max -= offset;
  9004. util.forEach(visibleItems, function (item) {
  9005. item.top -= offset;
  9006. });
  9007. }
  9008. height = max + margin.item.vertical / 2;
  9009. }
  9010. else {
  9011. height = margin.axis + margin.item.vertical;
  9012. }
  9013. height = Math.max(height, this.props.label.height);
  9014. // calculate actual size and position
  9015. var foreground = this.dom.foreground;
  9016. this.top = foreground.offsetTop;
  9017. this.left = foreground.offsetLeft;
  9018. this.width = foreground.offsetWidth;
  9019. resized = util.updateProperty(this, 'height', height) || resized;
  9020. // recalculate size of label
  9021. resized = util.updateProperty(this.props.label, 'width', this.dom.inner.clientWidth) || resized;
  9022. resized = util.updateProperty(this.props.label, 'height', this.dom.inner.clientHeight) || resized;
  9023. // apply new height
  9024. this.dom.background.style.height = height + 'px';
  9025. this.dom.foreground.style.height = height + 'px';
  9026. this.dom.label.style.height = height + 'px';
  9027. // update vertical position of items after they are re-stacked and the height of the group is calculated
  9028. for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
  9029. var item = this.visibleItems[i];
  9030. item.repositionY();
  9031. }
  9032. return resized;
  9033. };
  9034. /**
  9035. * Show this group: attach to the DOM
  9036. */
  9037. Group.prototype.show = function() {
  9038. if (!this.dom.label.parentNode) {
  9039. this.itemSet.dom.labelSet.appendChild(this.dom.label);
  9040. }
  9041. if (!this.dom.foreground.parentNode) {
  9042. this.itemSet.dom.foreground.appendChild(this.dom.foreground);
  9043. }
  9044. if (!this.dom.background.parentNode) {
  9045. this.itemSet.dom.background.appendChild(this.dom.background);
  9046. }
  9047. if (!this.dom.axis.parentNode) {
  9048. this.itemSet.dom.axis.appendChild(this.dom.axis);
  9049. }
  9050. };
  9051. /**
  9052. * Hide this group: remove from the DOM
  9053. */
  9054. Group.prototype.hide = function() {
  9055. var label = this.dom.label;
  9056. if (label.parentNode) {
  9057. label.parentNode.removeChild(label);
  9058. }
  9059. var foreground = this.dom.foreground;
  9060. if (foreground.parentNode) {
  9061. foreground.parentNode.removeChild(foreground);
  9062. }
  9063. var background = this.dom.background;
  9064. if (background.parentNode) {
  9065. background.parentNode.removeChild(background);
  9066. }
  9067. var axis = this.dom.axis;
  9068. if (axis.parentNode) {
  9069. axis.parentNode.removeChild(axis);
  9070. }
  9071. };
  9072. /**
  9073. * Add an item to the group
  9074. * @param {Item} item
  9075. */
  9076. Group.prototype.add = function(item) {
  9077. this.items[item.id] = item;
  9078. item.setParent(this);
  9079. if (item instanceof ItemRange && this.visibleItems.indexOf(item) == -1) {
  9080. var range = this.itemSet.body.range; // TODO: not nice accessing the range like this
  9081. this._checkIfVisible(item, this.visibleItems, range);
  9082. }
  9083. };
  9084. /**
  9085. * Remove an item from the group
  9086. * @param {Item} item
  9087. */
  9088. Group.prototype.remove = function(item) {
  9089. delete this.items[item.id];
  9090. item.setParent(this.itemSet);
  9091. // remove from visible items
  9092. var index = this.visibleItems.indexOf(item);
  9093. if (index != -1) this.visibleItems.splice(index, 1);
  9094. // TODO: also remove from ordered items?
  9095. };
  9096. /**
  9097. * Remove an item from the corresponding DataSet
  9098. * @param {Item} item
  9099. */
  9100. Group.prototype.removeFromDataSet = function(item) {
  9101. this.itemSet.removeItem(item.id);
  9102. };
  9103. /**
  9104. * Reorder the items
  9105. */
  9106. Group.prototype.order = function() {
  9107. var array = util.toArray(this.items);
  9108. this.orderedItems.byStart = array;
  9109. this.orderedItems.byEnd = this._constructByEndArray(array);
  9110. stack.orderByStart(this.orderedItems.byStart);
  9111. stack.orderByEnd(this.orderedItems.byEnd);
  9112. };
  9113. /**
  9114. * Create an array containing all items being a range (having an end date)
  9115. * @param {Item[]} array
  9116. * @returns {ItemRange[]}
  9117. * @private
  9118. */
  9119. Group.prototype._constructByEndArray = function(array) {
  9120. var endArray = [];
  9121. for (var i = 0; i < array.length; i++) {
  9122. if (array[i] instanceof ItemRange) {
  9123. endArray.push(array[i]);
  9124. }
  9125. }
  9126. return endArray;
  9127. };
  9128. /**
  9129. * Update the visible items
  9130. * @param {{byStart: Item[], byEnd: Item[]}} orderedItems All items ordered by start date and by end date
  9131. * @param {Item[]} visibleItems The previously visible items.
  9132. * @param {{start: number, end: number}} range Visible range
  9133. * @return {Item[]} visibleItems The new visible items.
  9134. * @private
  9135. */
  9136. Group.prototype._updateVisibleItems = function(orderedItems, visibleItems, range) {
  9137. var initialPosByStart,
  9138. newVisibleItems = [],
  9139. i;
  9140. // first check if the items that were in view previously are still in view.
  9141. // this handles the case for the ItemRange that is both before and after the current one.
  9142. if (visibleItems.length > 0) {
  9143. for (i = 0; i < visibleItems.length; i++) {
  9144. this._checkIfVisible(visibleItems[i], newVisibleItems, range);
  9145. }
  9146. }
  9147. // If there were no visible items previously, use binarySearch to find a visible ItemPoint or ItemRange (based on startTime)
  9148. if (newVisibleItems.length == 0) {
  9149. initialPosByStart = util.binarySearch(orderedItems.byStart, range, 'data','start');
  9150. }
  9151. else {
  9152. initialPosByStart = orderedItems.byStart.indexOf(newVisibleItems[0]);
  9153. }
  9154. // use visible search to find a visible ItemRange (only based on endTime)
  9155. var initialPosByEnd = util.binarySearch(orderedItems.byEnd, range, 'data','end');
  9156. // if we found a initial ID to use, trace it up and down until we meet an invisible item.
  9157. if (initialPosByStart != -1) {
  9158. for (i = initialPosByStart; i >= 0; i--) {
  9159. if (this._checkIfInvisible(orderedItems.byStart[i], newVisibleItems, range)) {break;}
  9160. }
  9161. for (i = initialPosByStart + 1; i < orderedItems.byStart.length; i++) {
  9162. if (this._checkIfInvisible(orderedItems.byStart[i], newVisibleItems, range)) {break;}
  9163. }
  9164. }
  9165. // if we found a initial ID to use, trace it up and down until we meet an invisible item.
  9166. if (initialPosByEnd != -1) {
  9167. for (i = initialPosByEnd; i >= 0; i--) {
  9168. if (this._checkIfInvisible(orderedItems.byEnd[i], newVisibleItems, range)) {break;}
  9169. }
  9170. for (i = initialPosByEnd + 1; i < orderedItems.byEnd.length; i++) {
  9171. if (this._checkIfInvisible(orderedItems.byEnd[i], newVisibleItems, range)) {break;}
  9172. }
  9173. }
  9174. return newVisibleItems;
  9175. };
  9176. /**
  9177. * this function checks if an item is invisible. If it is NOT we make it visible
  9178. * and add it to the global visible items. If it is, return true.
  9179. *
  9180. * @param {Item} item
  9181. * @param {Item[]} visibleItems
  9182. * @param {{start:number, end:number}} range
  9183. * @returns {boolean}
  9184. * @private
  9185. */
  9186. Group.prototype._checkIfInvisible = function(item, visibleItems, range) {
  9187. if (item.isVisible(range)) {
  9188. if (!item.displayed) item.show();
  9189. item.repositionX();
  9190. if (visibleItems.indexOf(item) == -1) {
  9191. visibleItems.push(item);
  9192. }
  9193. return false;
  9194. }
  9195. else {
  9196. if (item.displayed) item.hide();
  9197. return true;
  9198. }
  9199. };
  9200. /**
  9201. * this function is very similar to the _checkIfInvisible() but it does not
  9202. * return booleans, hides the item if it should not be seen and always adds to
  9203. * the visibleItems.
  9204. * this one is for brute forcing and hiding.
  9205. *
  9206. * @param {Item} item
  9207. * @param {Array} visibleItems
  9208. * @param {{start:number, end:number}} range
  9209. * @private
  9210. */
  9211. Group.prototype._checkIfVisible = function(item, visibleItems, range) {
  9212. if (item.isVisible(range)) {
  9213. if (!item.displayed) item.show();
  9214. // reposition item horizontally
  9215. item.repositionX();
  9216. visibleItems.push(item);
  9217. }
  9218. else {
  9219. if (item.displayed) item.hide();
  9220. }
  9221. };
  9222. module.exports = Group;
  9223. /***/ },
  9224. /* 24 */
  9225. /***/ function(module, exports, __webpack_require__) {
  9226. var Hammer = __webpack_require__(41);
  9227. var util = __webpack_require__(1);
  9228. var DataSet = __webpack_require__(3);
  9229. var DataView = __webpack_require__(4);
  9230. var Component = __webpack_require__(18);
  9231. var Group = __webpack_require__(23);
  9232. var ItemBox = __webpack_require__(29);
  9233. var ItemPoint = __webpack_require__(30);
  9234. var ItemRange = __webpack_require__(31);
  9235. var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
  9236. /**
  9237. * An ItemSet holds a set of items and ranges which can be displayed in a
  9238. * range. The width is determined by the parent of the ItemSet, and the height
  9239. * is determined by the size of the items.
  9240. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
  9241. * @param {Object} [options] See ItemSet.setOptions for the available options.
  9242. * @constructor ItemSet
  9243. * @extends Component
  9244. */
  9245. function ItemSet(body, options) {
  9246. this.body = body;
  9247. this.defaultOptions = {
  9248. type: null, // 'box', 'point', 'range'
  9249. orientation: 'bottom', // 'top' or 'bottom'
  9250. align: 'center', // alignment of box items
  9251. stack: true,
  9252. groupOrder: null,
  9253. selectable: true,
  9254. editable: {
  9255. updateTime: false,
  9256. updateGroup: false,
  9257. add: false,
  9258. remove: false
  9259. },
  9260. onAdd: function (item, callback) {
  9261. callback(item);
  9262. },
  9263. onUpdate: function (item, callback) {
  9264. callback(item);
  9265. },
  9266. onMove: function (item, callback) {
  9267. callback(item);
  9268. },
  9269. onRemove: function (item, callback) {
  9270. callback(item);
  9271. },
  9272. margin: {
  9273. item: {
  9274. horizontal: 10,
  9275. vertical: 10
  9276. },
  9277. axis: 20
  9278. },
  9279. padding: 5
  9280. };
  9281. // options is shared by this ItemSet and all its items
  9282. this.options = util.extend({}, this.defaultOptions);
  9283. // options for getting items from the DataSet with the correct type
  9284. this.itemOptions = {
  9285. type: {start: 'Date', end: 'Date'}
  9286. };
  9287. this.conversion = {
  9288. toScreen: body.util.toScreen,
  9289. toTime: body.util.toTime
  9290. };
  9291. this.dom = {};
  9292. this.props = {};
  9293. this.hammer = null;
  9294. var me = this;
  9295. this.itemsData = null; // DataSet
  9296. this.groupsData = null; // DataSet
  9297. // listeners for the DataSet of the items
  9298. this.itemListeners = {
  9299. 'add': function (event, params, senderId) {
  9300. me._onAdd(params.items);
  9301. },
  9302. 'update': function (event, params, senderId) {
  9303. me._onUpdate(params.items);
  9304. },
  9305. 'remove': function (event, params, senderId) {
  9306. me._onRemove(params.items);
  9307. }
  9308. };
  9309. // listeners for the DataSet of the groups
  9310. this.groupListeners = {
  9311. 'add': function (event, params, senderId) {
  9312. me._onAddGroups(params.items);
  9313. },
  9314. 'update': function (event, params, senderId) {
  9315. me._onUpdateGroups(params.items);
  9316. },
  9317. 'remove': function (event, params, senderId) {
  9318. me._onRemoveGroups(params.items);
  9319. }
  9320. };
  9321. this.items = {}; // object with an Item for every data item
  9322. this.groups = {}; // Group object for every group
  9323. this.groupIds = [];
  9324. this.selection = []; // list with the ids of all selected nodes
  9325. this.stackDirty = true; // if true, all items will be restacked on next redraw
  9326. this.touchParams = {}; // stores properties while dragging
  9327. // create the HTML DOM
  9328. this._create();
  9329. this.setOptions(options);
  9330. }
  9331. ItemSet.prototype = new Component();
  9332. // available item types will be registered here
  9333. ItemSet.types = {
  9334. box: ItemBox,
  9335. range: ItemRange,
  9336. point: ItemPoint
  9337. };
  9338. /**
  9339. * Create the HTML DOM for the ItemSet
  9340. */
  9341. ItemSet.prototype._create = function(){
  9342. var frame = document.createElement('div');
  9343. frame.className = 'itemset';
  9344. frame['timeline-itemset'] = this;
  9345. this.dom.frame = frame;
  9346. // create background panel
  9347. var background = document.createElement('div');
  9348. background.className = 'background';
  9349. frame.appendChild(background);
  9350. this.dom.background = background;
  9351. // create foreground panel
  9352. var foreground = document.createElement('div');
  9353. foreground.className = 'foreground';
  9354. frame.appendChild(foreground);
  9355. this.dom.foreground = foreground;
  9356. // create axis panel
  9357. var axis = document.createElement('div');
  9358. axis.className = 'axis';
  9359. this.dom.axis = axis;
  9360. // create labelset
  9361. var labelSet = document.createElement('div');
  9362. labelSet.className = 'labelset';
  9363. this.dom.labelSet = labelSet;
  9364. // create ungrouped Group
  9365. this._updateUngrouped();
  9366. // attach event listeners
  9367. // Note: we bind to the centerContainer for the case where the height
  9368. // of the center container is larger than of the ItemSet, so we
  9369. // can click in the empty area to create a new item or deselect an item.
  9370. this.hammer = Hammer(this.body.dom.centerContainer, {
  9371. prevent_default: true
  9372. });
  9373. // drag items when selected
  9374. this.hammer.on('touch', this._onTouch.bind(this));
  9375. this.hammer.on('dragstart', this._onDragStart.bind(this));
  9376. this.hammer.on('drag', this._onDrag.bind(this));
  9377. this.hammer.on('dragend', this._onDragEnd.bind(this));
  9378. // single select (or unselect) when tapping an item
  9379. this.hammer.on('tap', this._onSelectItem.bind(this));
  9380. // multi select when holding mouse/touch, or on ctrl+click
  9381. this.hammer.on('hold', this._onMultiSelectItem.bind(this));
  9382. // add item on doubletap
  9383. this.hammer.on('doubletap', this._onAddItem.bind(this));
  9384. // attach to the DOM
  9385. this.show();
  9386. };
  9387. /**
  9388. * Set options for the ItemSet. Existing options will be extended/overwritten.
  9389. * @param {Object} [options] The following options are available:
  9390. * {String} type
  9391. * Default type for the items. Choose from 'box'
  9392. * (default), 'point', or 'range'. The default
  9393. * Style can be overwritten by individual items.
  9394. * {String} align
  9395. * Alignment for the items, only applicable for
  9396. * ItemBox. Choose 'center' (default), 'left', or
  9397. * 'right'.
  9398. * {String} orientation
  9399. * Orientation of the item set. Choose 'top' or
  9400. * 'bottom' (default).
  9401. * {Function} groupOrder
  9402. * A sorting function for ordering groups
  9403. * {Boolean} stack
  9404. * If true (deafult), items will be stacked on
  9405. * top of each other.
  9406. * {Number} margin.axis
  9407. * Margin between the axis and the items in pixels.
  9408. * Default is 20.
  9409. * {Number} margin.item.horizontal
  9410. * Horizontal margin between items in pixels.
  9411. * Default is 10.
  9412. * {Number} margin.item.vertical
  9413. * Vertical Margin between items in pixels.
  9414. * Default is 10.
  9415. * {Number} margin.item
  9416. * Margin between items in pixels in both horizontal
  9417. * and vertical direction. Default is 10.
  9418. * {Number} margin
  9419. * Set margin for both axis and items in pixels.
  9420. * {Number} padding
  9421. * Padding of the contents of an item in pixels.
  9422. * Must correspond with the items css. Default is 5.
  9423. * {Boolean} selectable
  9424. * If true (default), items can be selected.
  9425. * {Boolean} editable
  9426. * Set all editable options to true or false
  9427. * {Boolean} editable.updateTime
  9428. * Allow dragging an item to an other moment in time
  9429. * {Boolean} editable.updateGroup
  9430. * Allow dragging an item to an other group
  9431. * {Boolean} editable.add
  9432. * Allow creating new items on double tap
  9433. * {Boolean} editable.remove
  9434. * Allow removing items by clicking the delete button
  9435. * top right of a selected item.
  9436. * {Function(item: Item, callback: Function)} onAdd
  9437. * Callback function triggered when an item is about to be added:
  9438. * when the user double taps an empty space in the Timeline.
  9439. * {Function(item: Item, callback: Function)} onUpdate
  9440. * Callback function fired when an item is about to be updated.
  9441. * This function typically has to show a dialog where the user
  9442. * change the item. If not implemented, nothing happens.
  9443. * {Function(item: Item, callback: Function)} onMove
  9444. * Fired when an item has been moved. If not implemented,
  9445. * the move action will be accepted.
  9446. * {Function(item: Item, callback: Function)} onRemove
  9447. * Fired when an item is about to be deleted.
  9448. * If not implemented, the item will be always removed.
  9449. */
  9450. ItemSet.prototype.setOptions = function(options) {
  9451. if (options) {
  9452. // copy all options that we know
  9453. var fields = ['type', 'align', 'orientation', 'padding', 'stack', 'selectable', 'groupOrder'];
  9454. util.selectiveExtend(fields, this.options, options);
  9455. if ('margin' in options) {
  9456. if (typeof options.margin === 'number') {
  9457. this.options.margin.axis = options.margin;
  9458. this.options.margin.item.horizontal = options.margin;
  9459. this.options.margin.item.vertical = options.margin;
  9460. }
  9461. else if (typeof options.margin === 'object') {
  9462. util.selectiveExtend(['axis'], this.options.margin, options.margin);
  9463. if ('item' in options.margin) {
  9464. if (typeof options.margin.item === 'number') {
  9465. this.options.margin.item.horizontal = options.margin.item;
  9466. this.options.margin.item.vertical = options.margin.item;
  9467. }
  9468. else if (typeof options.margin.item === 'object') {
  9469. util.selectiveExtend(['horizontal', 'vertical'], this.options.margin.item, options.margin.item);
  9470. }
  9471. }
  9472. }
  9473. }
  9474. if ('editable' in options) {
  9475. if (typeof options.editable === 'boolean') {
  9476. this.options.editable.updateTime = options.editable;
  9477. this.options.editable.updateGroup = options.editable;
  9478. this.options.editable.add = options.editable;
  9479. this.options.editable.remove = options.editable;
  9480. }
  9481. else if (typeof options.editable === 'object') {
  9482. util.selectiveExtend(['updateTime', 'updateGroup', 'add', 'remove'], this.options.editable, options.editable);
  9483. }
  9484. }
  9485. // callback functions
  9486. var addCallback = (function (name) {
  9487. if (name in options) {
  9488. var fn = options[name];
  9489. if (!(fn instanceof Function)) {
  9490. throw new Error('option ' + name + ' must be a function ' + name + '(item, callback)');
  9491. }
  9492. this.options[name] = fn;
  9493. }
  9494. }).bind(this);
  9495. ['onAdd', 'onUpdate', 'onRemove', 'onMove'].forEach(addCallback);
  9496. // force the itemSet to refresh: options like orientation and margins may be changed
  9497. this.markDirty();
  9498. }
  9499. };
  9500. /**
  9501. * Mark the ItemSet dirty so it will refresh everything with next redraw
  9502. */
  9503. ItemSet.prototype.markDirty = function() {
  9504. this.groupIds = [];
  9505. this.stackDirty = true;
  9506. };
  9507. /**
  9508. * Destroy the ItemSet
  9509. */
  9510. ItemSet.prototype.destroy = function() {
  9511. this.hide();
  9512. this.setItems(null);
  9513. this.setGroups(null);
  9514. this.hammer = null;
  9515. this.body = null;
  9516. this.conversion = null;
  9517. };
  9518. /**
  9519. * Hide the component from the DOM
  9520. */
  9521. ItemSet.prototype.hide = function() {
  9522. // remove the frame containing the items
  9523. if (this.dom.frame.parentNode) {
  9524. this.dom.frame.parentNode.removeChild(this.dom.frame);
  9525. }
  9526. // remove the axis with dots
  9527. if (this.dom.axis.parentNode) {
  9528. this.dom.axis.parentNode.removeChild(this.dom.axis);
  9529. }
  9530. // remove the labelset containing all group labels
  9531. if (this.dom.labelSet.parentNode) {
  9532. this.dom.labelSet.parentNode.removeChild(this.dom.labelSet);
  9533. }
  9534. };
  9535. /**
  9536. * Show the component in the DOM (when not already visible).
  9537. * @return {Boolean} changed
  9538. */
  9539. ItemSet.prototype.show = function() {
  9540. // show frame containing the items
  9541. if (!this.dom.frame.parentNode) {
  9542. this.body.dom.center.appendChild(this.dom.frame);
  9543. }
  9544. // show axis with dots
  9545. if (!this.dom.axis.parentNode) {
  9546. this.body.dom.backgroundVertical.appendChild(this.dom.axis);
  9547. }
  9548. // show labelset containing labels
  9549. if (!this.dom.labelSet.parentNode) {
  9550. this.body.dom.left.appendChild(this.dom.labelSet);
  9551. }
  9552. };
  9553. /**
  9554. * Set selected items by their id. Replaces the current selection
  9555. * Unknown id's are silently ignored.
  9556. * @param {Array} [ids] An array with zero or more id's of the items to be
  9557. * selected. If ids is an empty array, all items will be
  9558. * unselected.
  9559. */
  9560. ItemSet.prototype.setSelection = function(ids) {
  9561. var i, ii, id, item;
  9562. if (ids) {
  9563. if (!Array.isArray(ids)) {
  9564. throw new TypeError('Array expected');
  9565. }
  9566. // unselect currently selected items
  9567. for (i = 0, ii = this.selection.length; i < ii; i++) {
  9568. id = this.selection[i];
  9569. item = this.items[id];
  9570. if (item) item.unselect();
  9571. }
  9572. // select items
  9573. this.selection = [];
  9574. for (i = 0, ii = ids.length; i < ii; i++) {
  9575. id = ids[i];
  9576. item = this.items[id];
  9577. if (item) {
  9578. this.selection.push(id);
  9579. item.select();
  9580. }
  9581. }
  9582. }
  9583. };
  9584. /**
  9585. * Get the selected items by their id
  9586. * @return {Array} ids The ids of the selected items
  9587. */
  9588. ItemSet.prototype.getSelection = function() {
  9589. return this.selection.concat([]);
  9590. };
  9591. /**
  9592. * Get the id's of the currently visible items.
  9593. * @returns {Array} The ids of the visible items
  9594. */
  9595. ItemSet.prototype.getVisibleItems = function() {
  9596. var range = this.body.range.getRange();
  9597. var left = this.body.util.toScreen(range.start);
  9598. var right = this.body.util.toScreen(range.end);
  9599. var ids = [];
  9600. for (var groupId in this.groups) {
  9601. if (this.groups.hasOwnProperty(groupId)) {
  9602. var group = this.groups[groupId];
  9603. var rawVisibleItems = group.visibleItems;
  9604. // filter the "raw" set with visibleItems into a set which is really
  9605. // visible by pixels
  9606. for (var i = 0; i < rawVisibleItems.length; i++) {
  9607. var item = rawVisibleItems[i];
  9608. // TODO: also check whether visible vertically
  9609. if ((item.left < right) && (item.left + item.width > left)) {
  9610. ids.push(item.id);
  9611. }
  9612. }
  9613. }
  9614. }
  9615. return ids;
  9616. };
  9617. /**
  9618. * Deselect a selected item
  9619. * @param {String | Number} id
  9620. * @private
  9621. */
  9622. ItemSet.prototype._deselect = function(id) {
  9623. var selection = this.selection;
  9624. for (var i = 0, ii = selection.length; i < ii; i++) {
  9625. if (selection[i] == id) { // non-strict comparison!
  9626. selection.splice(i, 1);
  9627. break;
  9628. }
  9629. }
  9630. };
  9631. /**
  9632. * Repaint the component
  9633. * @return {boolean} Returns true if the component is resized
  9634. */
  9635. ItemSet.prototype.redraw = function() {
  9636. var margin = this.options.margin,
  9637. range = this.body.range,
  9638. asSize = util.option.asSize,
  9639. options = this.options,
  9640. orientation = options.orientation,
  9641. resized = false,
  9642. frame = this.dom.frame,
  9643. editable = options.editable.updateTime || options.editable.updateGroup;
  9644. // update class name
  9645. frame.className = 'itemset' + (editable ? ' editable' : '');
  9646. // reorder the groups (if needed)
  9647. resized = this._orderGroups() || resized;
  9648. // check whether zoomed (in that case we need to re-stack everything)
  9649. // TODO: would be nicer to get this as a trigger from Range
  9650. var visibleInterval = range.end - range.start;
  9651. var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.props.width != this.props.lastWidth);
  9652. if (zoomed) this.stackDirty = true;
  9653. this.lastVisibleInterval = visibleInterval;
  9654. this.props.lastWidth = this.props.width;
  9655. // redraw all groups
  9656. var restack = this.stackDirty,
  9657. firstGroup = this._firstGroup(),
  9658. firstMargin = {
  9659. item: margin.item,
  9660. axis: margin.axis
  9661. },
  9662. nonFirstMargin = {
  9663. item: margin.item,
  9664. axis: margin.item.vertical / 2
  9665. },
  9666. height = 0,
  9667. minHeight = margin.axis + margin.item.vertical;
  9668. util.forEach(this.groups, function (group) {
  9669. var groupMargin = (group == firstGroup) ? firstMargin : nonFirstMargin;
  9670. var groupResized = group.redraw(range, groupMargin, restack);
  9671. resized = groupResized || resized;
  9672. height += group.height;
  9673. });
  9674. height = Math.max(height, minHeight);
  9675. this.stackDirty = false;
  9676. // update frame height
  9677. frame.style.height = asSize(height);
  9678. // calculate actual size and position
  9679. this.props.top = frame.offsetTop;
  9680. this.props.left = frame.offsetLeft;
  9681. this.props.width = frame.offsetWidth;
  9682. this.props.height = height;
  9683. // reposition axis
  9684. this.dom.axis.style.top = asSize((orientation == 'top') ?
  9685. (this.body.domProps.top.height + this.body.domProps.border.top) :
  9686. (this.body.domProps.top.height + this.body.domProps.centerContainer.height));
  9687. this.dom.axis.style.left = this.body.domProps.border.left + 'px';
  9688. // check if this component is resized
  9689. resized = this._isResized() || resized;
  9690. return resized;
  9691. };
  9692. /**
  9693. * Get the first group, aligned with the axis
  9694. * @return {Group | null} firstGroup
  9695. * @private
  9696. */
  9697. ItemSet.prototype._firstGroup = function() {
  9698. var firstGroupIndex = (this.options.orientation == 'top') ? 0 : (this.groupIds.length - 1);
  9699. var firstGroupId = this.groupIds[firstGroupIndex];
  9700. var firstGroup = this.groups[firstGroupId] || this.groups[UNGROUPED];
  9701. return firstGroup || null;
  9702. };
  9703. /**
  9704. * Create or delete the group holding all ungrouped items. This group is used when
  9705. * there are no groups specified.
  9706. * @protected
  9707. */
  9708. ItemSet.prototype._updateUngrouped = function() {
  9709. var ungrouped = this.groups[UNGROUPED];
  9710. if (this.groupsData) {
  9711. // remove the group holding all ungrouped items
  9712. if (ungrouped) {
  9713. ungrouped.hide();
  9714. delete this.groups[UNGROUPED];
  9715. }
  9716. }
  9717. else {
  9718. // create a group holding all (unfiltered) items
  9719. if (!ungrouped) {
  9720. var id = null;
  9721. var data = null;
  9722. ungrouped = new Group(id, data, this);
  9723. this.groups[UNGROUPED] = ungrouped;
  9724. for (var itemId in this.items) {
  9725. if (this.items.hasOwnProperty(itemId)) {
  9726. ungrouped.add(this.items[itemId]);
  9727. }
  9728. }
  9729. ungrouped.show();
  9730. }
  9731. }
  9732. };
  9733. /**
  9734. * Get the element for the labelset
  9735. * @return {HTMLElement} labelSet
  9736. */
  9737. ItemSet.prototype.getLabelSet = function() {
  9738. return this.dom.labelSet;
  9739. };
  9740. /**
  9741. * Set items
  9742. * @param {vis.DataSet | null} items
  9743. */
  9744. ItemSet.prototype.setItems = function(items) {
  9745. var me = this,
  9746. ids,
  9747. oldItemsData = this.itemsData;
  9748. // replace the dataset
  9749. if (!items) {
  9750. this.itemsData = null;
  9751. }
  9752. else if (items instanceof DataSet || items instanceof DataView) {
  9753. this.itemsData = items;
  9754. }
  9755. else {
  9756. throw new TypeError('Data must be an instance of DataSet or DataView');
  9757. }
  9758. if (oldItemsData) {
  9759. // unsubscribe from old dataset
  9760. util.forEach(this.itemListeners, function (callback, event) {
  9761. oldItemsData.off(event, callback);
  9762. });
  9763. // remove all drawn items
  9764. ids = oldItemsData.getIds();
  9765. this._onRemove(ids);
  9766. }
  9767. if (this.itemsData) {
  9768. // subscribe to new dataset
  9769. var id = this.id;
  9770. util.forEach(this.itemListeners, function (callback, event) {
  9771. me.itemsData.on(event, callback, id);
  9772. });
  9773. // add all new items
  9774. ids = this.itemsData.getIds();
  9775. this._onAdd(ids);
  9776. // update the group holding all ungrouped items
  9777. this._updateUngrouped();
  9778. }
  9779. };
  9780. /**
  9781. * Get the current items
  9782. * @returns {vis.DataSet | null}
  9783. */
  9784. ItemSet.prototype.getItems = function() {
  9785. return this.itemsData;
  9786. };
  9787. /**
  9788. * Set groups
  9789. * @param {vis.DataSet} groups
  9790. */
  9791. ItemSet.prototype.setGroups = function(groups) {
  9792. var me = this,
  9793. ids;
  9794. // unsubscribe from current dataset
  9795. if (this.groupsData) {
  9796. util.forEach(this.groupListeners, function (callback, event) {
  9797. me.groupsData.unsubscribe(event, callback);
  9798. });
  9799. // remove all drawn groups
  9800. ids = this.groupsData.getIds();
  9801. this.groupsData = null;
  9802. this._onRemoveGroups(ids); // note: this will cause a redraw
  9803. }
  9804. // replace the dataset
  9805. if (!groups) {
  9806. this.groupsData = null;
  9807. }
  9808. else if (groups instanceof DataSet || groups instanceof DataView) {
  9809. this.groupsData = groups;
  9810. }
  9811. else {
  9812. throw new TypeError('Data must be an instance of DataSet or DataView');
  9813. }
  9814. if (this.groupsData) {
  9815. // subscribe to new dataset
  9816. var id = this.id;
  9817. util.forEach(this.groupListeners, function (callback, event) {
  9818. me.groupsData.on(event, callback, id);
  9819. });
  9820. // draw all ms
  9821. ids = this.groupsData.getIds();
  9822. this._onAddGroups(ids);
  9823. }
  9824. // update the group holding all ungrouped items
  9825. this._updateUngrouped();
  9826. // update the order of all items in each group
  9827. this._order();
  9828. this.body.emitter.emit('change');
  9829. };
  9830. /**
  9831. * Get the current groups
  9832. * @returns {vis.DataSet | null} groups
  9833. */
  9834. ItemSet.prototype.getGroups = function() {
  9835. return this.groupsData;
  9836. };
  9837. /**
  9838. * Remove an item by its id
  9839. * @param {String | Number} id
  9840. */
  9841. ItemSet.prototype.removeItem = function(id) {
  9842. var item = this.itemsData.get(id),
  9843. dataset = this.itemsData.getDataSet();
  9844. if (item) {
  9845. // confirm deletion
  9846. this.options.onRemove(item, function (item) {
  9847. if (item) {
  9848. // remove by id here, it is possible that an item has no id defined
  9849. // itself, so better not delete by the item itself
  9850. dataset.remove(id);
  9851. }
  9852. });
  9853. }
  9854. };
  9855. /**
  9856. * Handle updated items
  9857. * @param {Number[]} ids
  9858. * @protected
  9859. */
  9860. ItemSet.prototype._onUpdate = function(ids) {
  9861. var me = this;
  9862. ids.forEach(function (id) {
  9863. var itemData = me.itemsData.get(id, me.itemOptions),
  9864. item = me.items[id],
  9865. type = itemData.type || me.options.type || (itemData.end ? 'range' : 'box');
  9866. var constructor = ItemSet.types[type];
  9867. if (item) {
  9868. // update item
  9869. if (!constructor || !(item instanceof constructor)) {
  9870. // item type has changed, delete the item and recreate it
  9871. me._removeItem(item);
  9872. item = null;
  9873. }
  9874. else {
  9875. me._updateItem(item, itemData);
  9876. }
  9877. }
  9878. if (!item) {
  9879. // create item
  9880. if (constructor) {
  9881. item = new constructor(itemData, me.conversion, me.options);
  9882. item.id = id; // TODO: not so nice setting id afterwards
  9883. me._addItem(item);
  9884. }
  9885. else if (type == 'rangeoverflow') {
  9886. // TODO: deprecated since version 2.1.0 (or 3.0.0?). cleanup some day
  9887. throw new TypeError('Item type "rangeoverflow" is deprecated. Use css styling instead: ' +
  9888. '.vis.timeline .item.range .content {overflow: visible;}');
  9889. }
  9890. else {
  9891. throw new TypeError('Unknown item type "' + type + '"');
  9892. }
  9893. }
  9894. });
  9895. this._order();
  9896. this.stackDirty = true; // force re-stacking of all items next redraw
  9897. this.body.emitter.emit('change');
  9898. };
  9899. /**
  9900. * Handle added items
  9901. * @param {Number[]} ids
  9902. * @protected
  9903. */
  9904. ItemSet.prototype._onAdd = ItemSet.prototype._onUpdate;
  9905. /**
  9906. * Handle removed items
  9907. * @param {Number[]} ids
  9908. * @protected
  9909. */
  9910. ItemSet.prototype._onRemove = function(ids) {
  9911. var count = 0;
  9912. var me = this;
  9913. ids.forEach(function (id) {
  9914. var item = me.items[id];
  9915. if (item) {
  9916. count++;
  9917. me._removeItem(item);
  9918. }
  9919. });
  9920. if (count) {
  9921. // update order
  9922. this._order();
  9923. this.stackDirty = true; // force re-stacking of all items next redraw
  9924. this.body.emitter.emit('change');
  9925. }
  9926. };
  9927. /**
  9928. * Update the order of item in all groups
  9929. * @private
  9930. */
  9931. ItemSet.prototype._order = function() {
  9932. // reorder the items in all groups
  9933. // TODO: optimization: only reorder groups affected by the changed items
  9934. util.forEach(this.groups, function (group) {
  9935. group.order();
  9936. });
  9937. };
  9938. /**
  9939. * Handle updated groups
  9940. * @param {Number[]} ids
  9941. * @private
  9942. */
  9943. ItemSet.prototype._onUpdateGroups = function(ids) {
  9944. this._onAddGroups(ids);
  9945. };
  9946. /**
  9947. * Handle changed groups
  9948. * @param {Number[]} ids
  9949. * @private
  9950. */
  9951. ItemSet.prototype._onAddGroups = function(ids) {
  9952. var me = this;
  9953. ids.forEach(function (id) {
  9954. var groupData = me.groupsData.get(id);
  9955. var group = me.groups[id];
  9956. if (!group) {
  9957. // check for reserved ids
  9958. if (id == UNGROUPED) {
  9959. throw new Error('Illegal group id. ' + id + ' is a reserved id.');
  9960. }
  9961. var groupOptions = Object.create(me.options);
  9962. util.extend(groupOptions, {
  9963. height: null
  9964. });
  9965. group = new Group(id, groupData, me);
  9966. me.groups[id] = group;
  9967. // add items with this groupId to the new group
  9968. for (var itemId in me.items) {
  9969. if (me.items.hasOwnProperty(itemId)) {
  9970. var item = me.items[itemId];
  9971. if (item.data.group == id) {
  9972. group.add(item);
  9973. }
  9974. }
  9975. }
  9976. group.order();
  9977. group.show();
  9978. }
  9979. else {
  9980. // update group
  9981. group.setData(groupData);
  9982. }
  9983. });
  9984. this.body.emitter.emit('change');
  9985. };
  9986. /**
  9987. * Handle removed groups
  9988. * @param {Number[]} ids
  9989. * @private
  9990. */
  9991. ItemSet.prototype._onRemoveGroups = function(ids) {
  9992. var groups = this.groups;
  9993. ids.forEach(function (id) {
  9994. var group = groups[id];
  9995. if (group) {
  9996. group.hide();
  9997. delete groups[id];
  9998. }
  9999. });
  10000. this.markDirty();
  10001. this.body.emitter.emit('change');
  10002. };
  10003. /**
  10004. * Reorder the groups if needed
  10005. * @return {boolean} changed
  10006. * @private
  10007. */
  10008. ItemSet.prototype._orderGroups = function () {
  10009. if (this.groupsData) {
  10010. // reorder the groups
  10011. var groupIds = this.groupsData.getIds({
  10012. order: this.options.groupOrder
  10013. });
  10014. var changed = !util.equalArray(groupIds, this.groupIds);
  10015. if (changed) {
  10016. // hide all groups, removes them from the DOM
  10017. var groups = this.groups;
  10018. groupIds.forEach(function (groupId) {
  10019. groups[groupId].hide();
  10020. });
  10021. // show the groups again, attach them to the DOM in correct order
  10022. groupIds.forEach(function (groupId) {
  10023. groups[groupId].show();
  10024. });
  10025. this.groupIds = groupIds;
  10026. }
  10027. return changed;
  10028. }
  10029. else {
  10030. return false;
  10031. }
  10032. };
  10033. /**
  10034. * Add a new item
  10035. * @param {Item} item
  10036. * @private
  10037. */
  10038. ItemSet.prototype._addItem = function(item) {
  10039. this.items[item.id] = item;
  10040. // add to group
  10041. var groupId = this.groupsData ? item.data.group : UNGROUPED;
  10042. var group = this.groups[groupId];
  10043. if (group) group.add(item);
  10044. };
  10045. /**
  10046. * Update an existing item
  10047. * @param {Item} item
  10048. * @param {Object} itemData
  10049. * @private
  10050. */
  10051. ItemSet.prototype._updateItem = function(item, itemData) {
  10052. var oldGroupId = item.data.group;
  10053. item.data = itemData;
  10054. if (item.displayed) {
  10055. item.redraw();
  10056. }
  10057. // update group
  10058. if (oldGroupId != item.data.group) {
  10059. var oldGroup = this.groups[oldGroupId];
  10060. if (oldGroup) oldGroup.remove(item);
  10061. var groupId = this.groupsData ? item.data.group : UNGROUPED;
  10062. var group = this.groups[groupId];
  10063. if (group) group.add(item);
  10064. }
  10065. };
  10066. /**
  10067. * Delete an item from the ItemSet: remove it from the DOM, from the map
  10068. * with items, and from the map with visible items, and from the selection
  10069. * @param {Item} item
  10070. * @private
  10071. */
  10072. ItemSet.prototype._removeItem = function(item) {
  10073. // remove from DOM
  10074. item.hide();
  10075. // remove from items
  10076. delete this.items[item.id];
  10077. // remove from selection
  10078. var index = this.selection.indexOf(item.id);
  10079. if (index != -1) this.selection.splice(index, 1);
  10080. // remove from group
  10081. var groupId = this.groupsData ? item.data.group : UNGROUPED;
  10082. var group = this.groups[groupId];
  10083. if (group) group.remove(item);
  10084. };
  10085. /**
  10086. * Create an array containing all items being a range (having an end date)
  10087. * @param array
  10088. * @returns {Array}
  10089. * @private
  10090. */
  10091. ItemSet.prototype._constructByEndArray = function(array) {
  10092. var endArray = [];
  10093. for (var i = 0; i < array.length; i++) {
  10094. if (array[i] instanceof ItemRange) {
  10095. endArray.push(array[i]);
  10096. }
  10097. }
  10098. return endArray;
  10099. };
  10100. /**
  10101. * Register the clicked item on touch, before dragStart is initiated.
  10102. *
  10103. * dragStart is initiated from a mousemove event, which can have left the item
  10104. * already resulting in an item == null
  10105. *
  10106. * @param {Event} event
  10107. * @private
  10108. */
  10109. ItemSet.prototype._onTouch = function (event) {
  10110. // store the touched item, used in _onDragStart
  10111. this.touchParams.item = ItemSet.itemFromTarget(event);
  10112. };
  10113. /**
  10114. * Start dragging the selected events
  10115. * @param {Event} event
  10116. * @private
  10117. */
  10118. ItemSet.prototype._onDragStart = function (event) {
  10119. if (!this.options.editable.updateTime && !this.options.editable.updateGroup) {
  10120. return;
  10121. }
  10122. var item = this.touchParams.item || null,
  10123. me = this,
  10124. props;
  10125. if (item && item.selected) {
  10126. var dragLeftItem = event.target.dragLeftItem;
  10127. var dragRightItem = event.target.dragRightItem;
  10128. if (dragLeftItem) {
  10129. props = {
  10130. item: dragLeftItem
  10131. };
  10132. if (me.options.editable.updateTime) {
  10133. props.start = item.data.start.valueOf();
  10134. }
  10135. if (me.options.editable.updateGroup) {
  10136. if ('group' in item.data) props.group = item.data.group;
  10137. }
  10138. this.touchParams.itemProps = [props];
  10139. }
  10140. else if (dragRightItem) {
  10141. props = {
  10142. item: dragRightItem
  10143. };
  10144. if (me.options.editable.updateTime) {
  10145. props.end = item.data.end.valueOf();
  10146. }
  10147. if (me.options.editable.updateGroup) {
  10148. if ('group' in item.data) props.group = item.data.group;
  10149. }
  10150. this.touchParams.itemProps = [props];
  10151. }
  10152. else {
  10153. this.touchParams.itemProps = this.getSelection().map(function (id) {
  10154. var item = me.items[id];
  10155. var props = {
  10156. item: item
  10157. };
  10158. if (me.options.editable.updateTime) {
  10159. if ('start' in item.data) props.start = item.data.start.valueOf();
  10160. if ('end' in item.data) props.end = item.data.end.valueOf();
  10161. }
  10162. if (me.options.editable.updateGroup) {
  10163. if ('group' in item.data) props.group = item.data.group;
  10164. }
  10165. return props;
  10166. });
  10167. }
  10168. event.stopPropagation();
  10169. }
  10170. };
  10171. /**
  10172. * Drag selected items
  10173. * @param {Event} event
  10174. * @private
  10175. */
  10176. ItemSet.prototype._onDrag = function (event) {
  10177. if (this.touchParams.itemProps) {
  10178. var range = this.body.range,
  10179. snap = this.body.util.snap || null,
  10180. deltaX = event.gesture.deltaX,
  10181. scale = (this.props.width / (range.end - range.start)),
  10182. offset = deltaX / scale;
  10183. // move
  10184. this.touchParams.itemProps.forEach(function (props) {
  10185. if ('start' in props) {
  10186. var start = new Date(props.start + offset);
  10187. props.item.data.start = snap ? snap(start) : start;
  10188. }
  10189. if ('end' in props) {
  10190. var end = new Date(props.end + offset);
  10191. props.item.data.end = snap ? snap(end) : end;
  10192. }
  10193. if ('group' in props) {
  10194. // drag from one group to another
  10195. var group = ItemSet.groupFromTarget(event);
  10196. if (group && group.groupId != props.item.data.group) {
  10197. var oldGroup = props.item.parent;
  10198. oldGroup.remove(props.item);
  10199. oldGroup.order();
  10200. group.add(props.item);
  10201. group.order();
  10202. props.item.data.group = group.groupId;
  10203. }
  10204. }
  10205. });
  10206. // TODO: implement onMoving handler
  10207. this.stackDirty = true; // force re-stacking of all items next redraw
  10208. this.body.emitter.emit('change');
  10209. event.stopPropagation();
  10210. }
  10211. };
  10212. /**
  10213. * End of dragging selected items
  10214. * @param {Event} event
  10215. * @private
  10216. */
  10217. ItemSet.prototype._onDragEnd = function (event) {
  10218. if (this.touchParams.itemProps) {
  10219. // prepare a change set for the changed items
  10220. var changes = [],
  10221. me = this,
  10222. dataset = this.itemsData.getDataSet();
  10223. this.touchParams.itemProps.forEach(function (props) {
  10224. var id = props.item.id,
  10225. itemData = me.itemsData.get(id, me.itemOptions);
  10226. var changed = false;
  10227. if ('start' in props.item.data) {
  10228. changed = (props.start != props.item.data.start.valueOf());
  10229. itemData.start = util.convert(props.item.data.start,
  10230. dataset._options.type && dataset._options.type.start || 'Date');
  10231. }
  10232. if ('end' in props.item.data) {
  10233. changed = changed || (props.end != props.item.data.end.valueOf());
  10234. itemData.end = util.convert(props.item.data.end,
  10235. dataset._options.type && dataset._options.type.end || 'Date');
  10236. }
  10237. if ('group' in props.item.data) {
  10238. changed = changed || (props.group != props.item.data.group);
  10239. itemData.group = props.item.data.group;
  10240. }
  10241. // only apply changes when start or end is actually changed
  10242. if (changed) {
  10243. me.options.onMove(itemData, function (itemData) {
  10244. if (itemData) {
  10245. // apply changes
  10246. itemData[dataset._fieldId] = id; // ensure the item contains its id (can be undefined)
  10247. changes.push(itemData);
  10248. }
  10249. else {
  10250. // restore original values
  10251. if ('start' in props) props.item.data.start = props.start;
  10252. if ('end' in props) props.item.data.end = props.end;
  10253. me.stackDirty = true; // force re-stacking of all items next redraw
  10254. me.body.emitter.emit('change');
  10255. }
  10256. });
  10257. }
  10258. });
  10259. this.touchParams.itemProps = null;
  10260. // apply the changes to the data (if there are changes)
  10261. if (changes.length) {
  10262. dataset.update(changes);
  10263. }
  10264. event.stopPropagation();
  10265. }
  10266. };
  10267. /**
  10268. * Handle selecting/deselecting an item when tapping it
  10269. * @param {Event} event
  10270. * @private
  10271. */
  10272. ItemSet.prototype._onSelectItem = function (event) {
  10273. if (!this.options.selectable) return;
  10274. var ctrlKey = event.gesture.srcEvent && event.gesture.srcEvent.ctrlKey;
  10275. var shiftKey = event.gesture.srcEvent && event.gesture.srcEvent.shiftKey;
  10276. if (ctrlKey || shiftKey) {
  10277. this._onMultiSelectItem(event);
  10278. return;
  10279. }
  10280. var oldSelection = this.getSelection();
  10281. var item = ItemSet.itemFromTarget(event);
  10282. var selection = item ? [item.id] : [];
  10283. this.setSelection(selection);
  10284. var newSelection = this.getSelection();
  10285. // emit a select event,
  10286. // except when old selection is empty and new selection is still empty
  10287. if (newSelection.length > 0 || oldSelection.length > 0) {
  10288. this.body.emitter.emit('select', {
  10289. items: this.getSelection()
  10290. });
  10291. }
  10292. event.stopPropagation();
  10293. };
  10294. /**
  10295. * Handle creation and updates of an item on double tap
  10296. * @param event
  10297. * @private
  10298. */
  10299. ItemSet.prototype._onAddItem = function (event) {
  10300. if (!this.options.selectable) return;
  10301. if (!this.options.editable.add) return;
  10302. var me = this,
  10303. snap = this.body.util.snap || null,
  10304. item = ItemSet.itemFromTarget(event);
  10305. if (item) {
  10306. // update item
  10307. // execute async handler to update the item (or cancel it)
  10308. var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset
  10309. this.options.onUpdate(itemData, function (itemData) {
  10310. if (itemData) {
  10311. me.itemsData.update(itemData);
  10312. }
  10313. });
  10314. }
  10315. else {
  10316. // add item
  10317. var xAbs = util.getAbsoluteLeft(this.dom.frame);
  10318. var x = event.gesture.center.pageX - xAbs;
  10319. var start = this.body.util.toTime(x);
  10320. var newItem = {
  10321. start: snap ? snap(start) : start,
  10322. content: 'new item'
  10323. };
  10324. // when default type is a range, add a default end date to the new item
  10325. if (this.options.type === 'range') {
  10326. var end = this.body.util.toTime(x + this.props.width / 5);
  10327. newItem.end = snap ? snap(end) : end;
  10328. }
  10329. newItem[this.itemsData.fieldId] = util.randomUUID();
  10330. var group = ItemSet.groupFromTarget(event);
  10331. if (group) {
  10332. newItem.group = group.groupId;
  10333. }
  10334. // execute async handler to customize (or cancel) adding an item
  10335. this.options.onAdd(newItem, function (item) {
  10336. if (item) {
  10337. me.itemsData.add(newItem);
  10338. // TODO: need to trigger a redraw?
  10339. }
  10340. });
  10341. }
  10342. };
  10343. /**
  10344. * Handle selecting/deselecting multiple items when holding an item
  10345. * @param {Event} event
  10346. * @private
  10347. */
  10348. ItemSet.prototype._onMultiSelectItem = function (event) {
  10349. if (!this.options.selectable) return;
  10350. var selection,
  10351. item = ItemSet.itemFromTarget(event);
  10352. if (item) {
  10353. // multi select items
  10354. selection = this.getSelection(); // current selection
  10355. var index = selection.indexOf(item.id);
  10356. if (index == -1) {
  10357. // item is not yet selected -> select it
  10358. selection.push(item.id);
  10359. }
  10360. else {
  10361. // item is already selected -> deselect it
  10362. selection.splice(index, 1);
  10363. }
  10364. this.setSelection(selection);
  10365. this.body.emitter.emit('select', {
  10366. items: this.getSelection()
  10367. });
  10368. event.stopPropagation();
  10369. }
  10370. };
  10371. /**
  10372. * Find an item from an event target:
  10373. * searches for the attribute 'timeline-item' in the event target's element tree
  10374. * @param {Event} event
  10375. * @return {Item | null} item
  10376. */
  10377. ItemSet.itemFromTarget = function(event) {
  10378. var target = event.target;
  10379. while (target) {
  10380. if (target.hasOwnProperty('timeline-item')) {
  10381. return target['timeline-item'];
  10382. }
  10383. target = target.parentNode;
  10384. }
  10385. return null;
  10386. };
  10387. /**
  10388. * Find the Group from an event target:
  10389. * searches for the attribute 'timeline-group' in the event target's element tree
  10390. * @param {Event} event
  10391. * @return {Group | null} group
  10392. */
  10393. ItemSet.groupFromTarget = function(event) {
  10394. var target = event.target;
  10395. while (target) {
  10396. if (target.hasOwnProperty('timeline-group')) {
  10397. return target['timeline-group'];
  10398. }
  10399. target = target.parentNode;
  10400. }
  10401. return null;
  10402. };
  10403. /**
  10404. * Find the ItemSet from an event target:
  10405. * searches for the attribute 'timeline-itemset' in the event target's element tree
  10406. * @param {Event} event
  10407. * @return {ItemSet | null} item
  10408. */
  10409. ItemSet.itemSetFromTarget = function(event) {
  10410. var target = event.target;
  10411. while (target) {
  10412. if (target.hasOwnProperty('timeline-itemset')) {
  10413. return target['timeline-itemset'];
  10414. }
  10415. target = target.parentNode;
  10416. }
  10417. return null;
  10418. };
  10419. module.exports = ItemSet;
  10420. /***/ },
  10421. /* 25 */
  10422. /***/ function(module, exports, __webpack_require__) {
  10423. var util = __webpack_require__(1);
  10424. var DOMutil = __webpack_require__(2);
  10425. var Component = __webpack_require__(18);
  10426. /**
  10427. * Legend for Graph2d
  10428. */
  10429. function Legend(body, options, side) {
  10430. this.body = body;
  10431. this.defaultOptions = {
  10432. enabled: true,
  10433. icons: true,
  10434. iconSize: 20,
  10435. iconSpacing: 6,
  10436. left: {
  10437. visible: true,
  10438. position: 'top-left' // top/bottom - left,center,right
  10439. },
  10440. right: {
  10441. visible: true,
  10442. position: 'top-left' // top/bottom - left,center,right
  10443. }
  10444. }
  10445. this.side = side;
  10446. this.options = util.extend({},this.defaultOptions);
  10447. this.svgElements = {};
  10448. this.dom = {};
  10449. this.groups = {};
  10450. this.amountOfGroups = 0;
  10451. this._create();
  10452. this.setOptions(options);
  10453. }
  10454. Legend.prototype = new Component();
  10455. Legend.prototype.addGroup = function(label, graphOptions) {
  10456. if (!this.groups.hasOwnProperty(label)) {
  10457. this.groups[label] = graphOptions;
  10458. }
  10459. this.amountOfGroups += 1;
  10460. };
  10461. Legend.prototype.updateGroup = function(label, graphOptions) {
  10462. this.groups[label] = graphOptions;
  10463. };
  10464. Legend.prototype.removeGroup = function(label) {
  10465. if (this.groups.hasOwnProperty(label)) {
  10466. delete this.groups[label];
  10467. this.amountOfGroups -= 1;
  10468. }
  10469. };
  10470. Legend.prototype._create = function() {
  10471. this.dom.frame = document.createElement('div');
  10472. this.dom.frame.className = 'legend';
  10473. this.dom.frame.style.position = "absolute";
  10474. this.dom.frame.style.top = "10px";
  10475. this.dom.frame.style.display = "block";
  10476. this.dom.textArea = document.createElement('div');
  10477. this.dom.textArea.className = 'legendText';
  10478. this.dom.textArea.style.position = "relative";
  10479. this.dom.textArea.style.top = "0px";
  10480. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  10481. this.svg.style.position = 'absolute';
  10482. this.svg.style.top = 0 +'px';
  10483. this.svg.style.width = this.options.iconSize + 5 + 'px';
  10484. this.dom.frame.appendChild(this.svg);
  10485. this.dom.frame.appendChild(this.dom.textArea);
  10486. };
  10487. /**
  10488. * Hide the component from the DOM
  10489. */
  10490. Legend.prototype.hide = function() {
  10491. // remove the frame containing the items
  10492. if (this.dom.frame.parentNode) {
  10493. this.dom.frame.parentNode.removeChild(this.dom.frame);
  10494. }
  10495. };
  10496. /**
  10497. * Show the component in the DOM (when not already visible).
  10498. * @return {Boolean} changed
  10499. */
  10500. Legend.prototype.show = function() {
  10501. // show frame containing the items
  10502. if (!this.dom.frame.parentNode) {
  10503. this.body.dom.center.appendChild(this.dom.frame);
  10504. }
  10505. };
  10506. Legend.prototype.setOptions = function(options) {
  10507. var fields = ['enabled','orientation','icons','left','right'];
  10508. util.selectiveDeepExtend(fields, this.options, options);
  10509. };
  10510. Legend.prototype.redraw = function() {
  10511. if (this.options[this.side].visible == false || this.amountOfGroups == 0 || this.options.enabled == false) {
  10512. this.hide();
  10513. }
  10514. else {
  10515. this.show();
  10516. if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'bottom-left') {
  10517. this.dom.frame.style.left = '4px';
  10518. this.dom.frame.style.textAlign = "left";
  10519. this.dom.textArea.style.textAlign = "left";
  10520. this.dom.textArea.style.left = (this.options.iconSize + 15) + 'px';
  10521. this.dom.textArea.style.right = '';
  10522. this.svg.style.left = 0 +'px';
  10523. this.svg.style.right = '';
  10524. }
  10525. else {
  10526. this.dom.frame.style.right = '4px';
  10527. this.dom.frame.style.textAlign = "right";
  10528. this.dom.textArea.style.textAlign = "right";
  10529. this.dom.textArea.style.right = (this.options.iconSize + 15) + 'px';
  10530. this.dom.textArea.style.left = '';
  10531. this.svg.style.right = 0 +'px';
  10532. this.svg.style.left = '';
  10533. }
  10534. if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'top-right') {
  10535. this.dom.frame.style.top = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
  10536. this.dom.frame.style.bottom = '';
  10537. }
  10538. else {
  10539. this.dom.frame.style.bottom = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
  10540. this.dom.frame.style.top = '';
  10541. }
  10542. if (this.options.icons == false) {
  10543. this.dom.frame.style.width = this.dom.textArea.offsetWidth + 10 + 'px';
  10544. this.dom.textArea.style.right = '';
  10545. this.dom.textArea.style.left = '';
  10546. this.svg.style.width = '0px';
  10547. }
  10548. else {
  10549. this.dom.frame.style.width = this.options.iconSize + 15 + this.dom.textArea.offsetWidth + 10 + 'px'
  10550. this.drawLegendIcons();
  10551. }
  10552. var content = '';
  10553. for (var groupId in this.groups) {
  10554. if (this.groups.hasOwnProperty(groupId)) {
  10555. content += this.groups[groupId].content + '<br />';
  10556. }
  10557. }
  10558. this.dom.textArea.innerHTML = content;
  10559. this.dom.textArea.style.lineHeight = ((0.75 * this.options.iconSize) + this.options.iconSpacing) + 'px';
  10560. }
  10561. };
  10562. Legend.prototype.drawLegendIcons = function() {
  10563. if (this.dom.frame.parentNode) {
  10564. DOMutil.prepareElements(this.svgElements);
  10565. var padding = window.getComputedStyle(this.dom.frame).paddingTop;
  10566. var iconOffset = Number(padding.replace('px',''));
  10567. var x = iconOffset;
  10568. var iconWidth = this.options.iconSize;
  10569. var iconHeight = 0.75 * this.options.iconSize;
  10570. var y = iconOffset + 0.5 * iconHeight + 3;
  10571. this.svg.style.width = iconWidth + 5 + iconOffset + 'px';
  10572. for (var groupId in this.groups) {
  10573. if (this.groups.hasOwnProperty(groupId)) {
  10574. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  10575. y += iconHeight + this.options.iconSpacing;
  10576. }
  10577. }
  10578. DOMutil.cleanupElements(this.svgElements);
  10579. }
  10580. };
  10581. module.exports = Legend;
  10582. /***/ },
  10583. /* 26 */
  10584. /***/ function(module, exports, __webpack_require__) {
  10585. var util = __webpack_require__(1);
  10586. var DOMutil = __webpack_require__(2);
  10587. var DataSet = __webpack_require__(3);
  10588. var DataView = __webpack_require__(4);
  10589. var Component = __webpack_require__(18);
  10590. var DataAxis = __webpack_require__(21);
  10591. var GraphGroup = __webpack_require__(22);
  10592. var Legend = __webpack_require__(25);
  10593. var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
  10594. /**
  10595. * This is the constructor of the LineGraph. It requires a Timeline body and options.
  10596. *
  10597. * @param body
  10598. * @param options
  10599. * @constructor
  10600. */
  10601. function LineGraph(body, options) {
  10602. this.id = util.randomUUID();
  10603. this.body = body;
  10604. this.defaultOptions = {
  10605. yAxisOrientation: 'left',
  10606. defaultGroup: 'default',
  10607. sort: true,
  10608. sampling: true,
  10609. graphHeight: '400px',
  10610. shaded: {
  10611. enabled: false,
  10612. orientation: 'bottom' // top, bottom
  10613. },
  10614. style: 'line', // line, bar
  10615. barChart: {
  10616. width: 50,
  10617. align: 'center' // left, center, right
  10618. },
  10619. catmullRom: {
  10620. enabled: true,
  10621. parametrization: 'centripetal', // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5)
  10622. alpha: 0.5
  10623. },
  10624. drawPoints: {
  10625. enabled: true,
  10626. size: 6,
  10627. style: 'square' // square, circle
  10628. },
  10629. dataAxis: {
  10630. showMinorLabels: true,
  10631. showMajorLabels: true,
  10632. icons: false,
  10633. width: '40px',
  10634. visible: true
  10635. },
  10636. legend: {
  10637. enabled: false,
  10638. icons: true,
  10639. left: {
  10640. visible: true,
  10641. position: 'top-left' // top/bottom - left,right
  10642. },
  10643. right: {
  10644. visible: true,
  10645. position: 'top-right' // top/bottom - left,right
  10646. }
  10647. }
  10648. };
  10649. // options is shared by this ItemSet and all its items
  10650. this.options = util.extend({}, this.defaultOptions);
  10651. this.dom = {};
  10652. this.props = {};
  10653. this.hammer = null;
  10654. this.groups = {};
  10655. var me = this;
  10656. this.itemsData = null; // DataSet
  10657. this.groupsData = null; // DataSet
  10658. // listeners for the DataSet of the items
  10659. this.itemListeners = {
  10660. 'add': function (event, params, senderId) {
  10661. me._onAdd(params.items);
  10662. },
  10663. 'update': function (event, params, senderId) {
  10664. me._onUpdate(params.items);
  10665. },
  10666. 'remove': function (event, params, senderId) {
  10667. me._onRemove(params.items);
  10668. }
  10669. };
  10670. // listeners for the DataSet of the groups
  10671. this.groupListeners = {
  10672. 'add': function (event, params, senderId) {
  10673. me._onAddGroups(params.items);
  10674. },
  10675. 'update': function (event, params, senderId) {
  10676. me._onUpdateGroups(params.items);
  10677. },
  10678. 'remove': function (event, params, senderId) {
  10679. me._onRemoveGroups(params.items);
  10680. }
  10681. };
  10682. this.items = {}; // object with an Item for every data item
  10683. this.selection = []; // list with the ids of all selected nodes
  10684. this.lastStart = this.body.range.start;
  10685. this.touchParams = {}; // stores properties while dragging
  10686. this.svgElements = {};
  10687. this.setOptions(options);
  10688. this.groupsUsingDefaultStyles = [0];
  10689. this.body.emitter.on("rangechange",function() {
  10690. if (me.lastStart != 0) {
  10691. var offset = me.body.range.start - me.lastStart;
  10692. var range = me.body.range.end - me.body.range.start;
  10693. if (me.width != 0) {
  10694. var rangePerPixelInv = me.width/range;
  10695. var xOffset = offset * rangePerPixelInv;
  10696. me.svg.style.left = (-me.width - xOffset) + "px";
  10697. }
  10698. }
  10699. });
  10700. this.body.emitter.on("rangechanged", function() {
  10701. me.lastStart = me.body.range.start;
  10702. me.svg.style.left = util.option.asSize(-me.width);
  10703. me._updateGraph.apply(me);
  10704. });
  10705. // create the HTML DOM
  10706. this._create();
  10707. this.body.emitter.emit("change");
  10708. }
  10709. LineGraph.prototype = new Component();
  10710. /**
  10711. * Create the HTML DOM for the ItemSet
  10712. */
  10713. LineGraph.prototype._create = function(){
  10714. var frame = document.createElement('div');
  10715. frame.className = 'LineGraph';
  10716. this.dom.frame = frame;
  10717. // create svg element for graph drawing.
  10718. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  10719. this.svg.style.position = "relative";
  10720. this.svg.style.height = ('' + this.options.graphHeight).replace("px",'') + 'px';
  10721. this.svg.style.display = "block";
  10722. frame.appendChild(this.svg);
  10723. // data axis
  10724. this.options.dataAxis.orientation = 'left';
  10725. this.yAxisLeft = new DataAxis(this.body, this.options.dataAxis, this.svg);
  10726. this.options.dataAxis.orientation = 'right';
  10727. this.yAxisRight = new DataAxis(this.body, this.options.dataAxis, this.svg);
  10728. delete this.options.dataAxis.orientation;
  10729. // legends
  10730. this.legendLeft = new Legend(this.body, this.options.legend, 'left');
  10731. this.legendRight = new Legend(this.body, this.options.legend, 'right');
  10732. this.show();
  10733. };
  10734. /**
  10735. * set the options of the LineGraph. the mergeOptions is used for subObjects that have an enabled element.
  10736. * @param options
  10737. */
  10738. LineGraph.prototype.setOptions = function(options) {
  10739. if (options) {
  10740. var fields = ['sampling','defaultGroup','graphHeight','yAxisOrientation','style','barChart','dataAxis','sort'];
  10741. util.selectiveDeepExtend(fields, this.options, options);
  10742. util.mergeOptions(this.options, options,'catmullRom');
  10743. util.mergeOptions(this.options, options,'drawPoints');
  10744. util.mergeOptions(this.options, options,'shaded');
  10745. util.mergeOptions(this.options, options,'legend');
  10746. if (options.catmullRom) {
  10747. if (typeof options.catmullRom == 'object') {
  10748. if (options.catmullRom.parametrization) {
  10749. if (options.catmullRom.parametrization == 'uniform') {
  10750. this.options.catmullRom.alpha = 0;
  10751. }
  10752. else if (options.catmullRom.parametrization == 'chordal') {
  10753. this.options.catmullRom.alpha = 1.0;
  10754. }
  10755. else {
  10756. this.options.catmullRom.parametrization = 'centripetal';
  10757. this.options.catmullRom.alpha = 0.5;
  10758. }
  10759. }
  10760. }
  10761. }
  10762. if (this.yAxisLeft) {
  10763. if (options.dataAxis !== undefined) {
  10764. this.yAxisLeft.setOptions(this.options.dataAxis);
  10765. this.yAxisRight.setOptions(this.options.dataAxis);
  10766. }
  10767. }
  10768. if (this.legendLeft) {
  10769. if (options.legend !== undefined) {
  10770. this.legendLeft.setOptions(this.options.legend);
  10771. this.legendRight.setOptions(this.options.legend);
  10772. }
  10773. }
  10774. if (this.groups.hasOwnProperty(UNGROUPED)) {
  10775. this.groups[UNGROUPED].setOptions(options);
  10776. }
  10777. }
  10778. if (this.dom.frame) {
  10779. this._updateGraph();
  10780. }
  10781. };
  10782. /**
  10783. * Hide the component from the DOM
  10784. */
  10785. LineGraph.prototype.hide = function() {
  10786. // remove the frame containing the items
  10787. if (this.dom.frame.parentNode) {
  10788. this.dom.frame.parentNode.removeChild(this.dom.frame);
  10789. }
  10790. };
  10791. /**
  10792. * Show the component in the DOM (when not already visible).
  10793. * @return {Boolean} changed
  10794. */
  10795. LineGraph.prototype.show = function() {
  10796. // show frame containing the items
  10797. if (!this.dom.frame.parentNode) {
  10798. this.body.dom.center.appendChild(this.dom.frame);
  10799. }
  10800. };
  10801. /**
  10802. * Set items
  10803. * @param {vis.DataSet | null} items
  10804. */
  10805. LineGraph.prototype.setItems = function(items) {
  10806. var me = this,
  10807. ids,
  10808. oldItemsData = this.itemsData;
  10809. // replace the dataset
  10810. if (!items) {
  10811. this.itemsData = null;
  10812. }
  10813. else if (items instanceof DataSet || items instanceof DataView) {
  10814. this.itemsData = items;
  10815. }
  10816. else {
  10817. throw new TypeError('Data must be an instance of DataSet or DataView');
  10818. }
  10819. if (oldItemsData) {
  10820. // unsubscribe from old dataset
  10821. util.forEach(this.itemListeners, function (callback, event) {
  10822. oldItemsData.off(event, callback);
  10823. });
  10824. // remove all drawn items
  10825. ids = oldItemsData.getIds();
  10826. this._onRemove(ids);
  10827. }
  10828. if (this.itemsData) {
  10829. // subscribe to new dataset
  10830. var id = this.id;
  10831. util.forEach(this.itemListeners, function (callback, event) {
  10832. me.itemsData.on(event, callback, id);
  10833. });
  10834. // add all new items
  10835. ids = this.itemsData.getIds();
  10836. this._onAdd(ids);
  10837. }
  10838. this._updateUngrouped();
  10839. this._updateGraph();
  10840. this.redraw();
  10841. };
  10842. /**
  10843. * Set groups
  10844. * @param {vis.DataSet} groups
  10845. */
  10846. LineGraph.prototype.setGroups = function(groups) {
  10847. var me = this,
  10848. ids;
  10849. // unsubscribe from current dataset
  10850. if (this.groupsData) {
  10851. util.forEach(this.groupListeners, function (callback, event) {
  10852. me.groupsData.unsubscribe(event, callback);
  10853. });
  10854. // remove all drawn groups
  10855. ids = this.groupsData.getIds();
  10856. this.groupsData = null;
  10857. this._onRemoveGroups(ids); // note: this will cause a redraw
  10858. }
  10859. // replace the dataset
  10860. if (!groups) {
  10861. this.groupsData = null;
  10862. }
  10863. else if (groups instanceof DataSet || groups instanceof DataView) {
  10864. this.groupsData = groups;
  10865. }
  10866. else {
  10867. throw new TypeError('Data must be an instance of DataSet or DataView');
  10868. }
  10869. if (this.groupsData) {
  10870. // subscribe to new dataset
  10871. var id = this.id;
  10872. util.forEach(this.groupListeners, function (callback, event) {
  10873. me.groupsData.on(event, callback, id);
  10874. });
  10875. // draw all ms
  10876. ids = this.groupsData.getIds();
  10877. this._onAddGroups(ids);
  10878. }
  10879. this._onUpdate();
  10880. };
  10881. LineGraph.prototype._onUpdate = function(ids) {
  10882. this._updateUngrouped();
  10883. this._updateAllGroupData();
  10884. this._updateGraph();
  10885. this.redraw();
  10886. };
  10887. LineGraph.prototype._onAdd = function (ids) {this._onUpdate(ids);};
  10888. LineGraph.prototype._onRemove = function (ids) {this._onUpdate(ids);};
  10889. LineGraph.prototype._onUpdateGroups = function (groupIds) {
  10890. for (var i = 0; i < groupIds.length; i++) {
  10891. var group = this.groupsData.get(groupIds[i]);
  10892. this._updateGroup(group, groupIds[i]);
  10893. }
  10894. this._updateGraph();
  10895. this.redraw();
  10896. };
  10897. LineGraph.prototype._onAddGroups = function (groupIds) {this._onUpdateGroups(groupIds);};
  10898. LineGraph.prototype._onRemoveGroups = function (groupIds) {
  10899. for (var i = 0; i < groupIds.length; i++) {
  10900. if (!this.groups.hasOwnProperty(groupIds[i])) {
  10901. if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') {
  10902. this.yAxisRight.removeGroup(groupIds[i]);
  10903. this.legendRight.removeGroup(groupIds[i]);
  10904. this.legendRight.redraw();
  10905. }
  10906. else {
  10907. this.yAxisLeft.removeGroup(groupIds[i]);
  10908. this.legendLeft.removeGroup(groupIds[i]);
  10909. this.legendLeft.redraw();
  10910. }
  10911. delete this.groups[groupIds[i]];
  10912. }
  10913. }
  10914. this._updateUngrouped();
  10915. this._updateGraph();
  10916. this.redraw();
  10917. };
  10918. /**
  10919. * update a group object
  10920. *
  10921. * @param group
  10922. * @param groupId
  10923. * @private
  10924. */
  10925. LineGraph.prototype._updateGroup = function (group, groupId) {
  10926. if (!this.groups.hasOwnProperty(groupId)) {
  10927. this.groups[groupId] = new GraphGroup(group, groupId, this.options, this.groupsUsingDefaultStyles);
  10928. if (this.groups[groupId].options.yAxisOrientation == 'right') {
  10929. this.yAxisRight.addGroup(groupId, this.groups[groupId]);
  10930. this.legendRight.addGroup(groupId, this.groups[groupId]);
  10931. }
  10932. else {
  10933. this.yAxisLeft.addGroup(groupId, this.groups[groupId]);
  10934. this.legendLeft.addGroup(groupId, this.groups[groupId]);
  10935. }
  10936. }
  10937. else {
  10938. this.groups[groupId].update(group);
  10939. if (this.groups[groupId].options.yAxisOrientation == 'right') {
  10940. this.yAxisRight.updateGroup(groupId, this.groups[groupId]);
  10941. this.legendRight.updateGroup(groupId, this.groups[groupId]);
  10942. }
  10943. else {
  10944. this.yAxisLeft.updateGroup(groupId, this.groups[groupId]);
  10945. this.legendLeft.updateGroup(groupId, this.groups[groupId]);
  10946. }
  10947. }
  10948. this.legendLeft.redraw();
  10949. this.legendRight.redraw();
  10950. };
  10951. LineGraph.prototype._updateAllGroupData = function () {
  10952. if (this.itemsData != null) {
  10953. // ~450 ms @ 500k
  10954. var groupsContent = {};
  10955. for (var groupId in this.groups) {
  10956. if (this.groups.hasOwnProperty(groupId)) {
  10957. groupsContent[groupId] = [];
  10958. }
  10959. }
  10960. for (var itemId in this.itemsData._data) {
  10961. if (this.itemsData._data.hasOwnProperty(itemId)) {
  10962. var item = this.itemsData._data[itemId];
  10963. item.x = util.convert(item.x,"Date");
  10964. groupsContent[item.group].push(item);
  10965. }
  10966. }
  10967. for (var groupId in this.groups) {
  10968. if (this.groups.hasOwnProperty(groupId)) {
  10969. this.groups[groupId].setItems(groupsContent[groupId]);
  10970. }
  10971. }
  10972. // // ~4500ms @ 500k
  10973. // for (var groupId in this.groups) {
  10974. // if (this.groups.hasOwnProperty(groupId)) {
  10975. // this.groups[groupId].setItems(this.itemsData.get({filter:
  10976. // function (item) {
  10977. // return (item.group == groupId);
  10978. // }, type:{x:"Date"}}
  10979. // ));
  10980. // }
  10981. // }
  10982. }
  10983. };
  10984. /**
  10985. * Create or delete the group holding all ungrouped items. This group is used when
  10986. * there are no groups specified. This anonymous group is called 'graph'.
  10987. * @protected
  10988. */
  10989. LineGraph.prototype._updateUngrouped = function() {
  10990. if (this.itemsData != null) {
  10991. // var t0 = new Date();
  10992. var group = {id: UNGROUPED, content: this.options.defaultGroup};
  10993. this._updateGroup(group, UNGROUPED);
  10994. var ungroupedCounter = 0;
  10995. if (this.itemsData) {
  10996. for (var itemId in this.itemsData._data) {
  10997. if (this.itemsData._data.hasOwnProperty(itemId)) {
  10998. var item = this.itemsData._data[itemId];
  10999. if (item != undefined) {
  11000. if (item.hasOwnProperty('group')) {
  11001. if (item.group === undefined) {
  11002. item.group = UNGROUPED;
  11003. }
  11004. }
  11005. else {
  11006. item.group = UNGROUPED;
  11007. }
  11008. ungroupedCounter = item.group == UNGROUPED ? ungroupedCounter + 1 : ungroupedCounter;
  11009. }
  11010. }
  11011. }
  11012. }
  11013. // much much slower
  11014. // var datapoints = this.itemsData.get({
  11015. // filter: function (item) {return item.group === undefined;},
  11016. // showInternalIds:true
  11017. // });
  11018. // if (datapoints.length > 0) {
  11019. // var updateQuery = [];
  11020. // for (var i = 0; i < datapoints.length; i++) {
  11021. // updateQuery.push({id:datapoints[i].id, group: UNGROUPED});
  11022. // }
  11023. // this.itemsData.update(updateQuery, true);
  11024. // }
  11025. // var t1 = new Date();
  11026. // var pointInUNGROUPED = this.itemsData.get({filter: function (item) {return item.group == UNGROUPED;}});
  11027. if (ungroupedCounter == 0) {
  11028. delete this.groups[UNGROUPED];
  11029. this.legendLeft.removeGroup(UNGROUPED);
  11030. this.legendRight.removeGroup(UNGROUPED);
  11031. this.yAxisLeft.removeGroup(UNGROUPED);
  11032. this.yAxisRight.removeGroup(UNGROUPED);
  11033. }
  11034. // console.log("getting amount ungrouped",new Date() - t1);
  11035. // console.log("putting in ungrouped",new Date() - t0);
  11036. }
  11037. else {
  11038. delete this.groups[UNGROUPED];
  11039. this.legendLeft.removeGroup(UNGROUPED);
  11040. this.legendRight.removeGroup(UNGROUPED);
  11041. this.yAxisLeft.removeGroup(UNGROUPED);
  11042. this.yAxisRight.removeGroup(UNGROUPED);
  11043. }
  11044. this.legendLeft.redraw();
  11045. this.legendRight.redraw();
  11046. };
  11047. /**
  11048. * Redraw the component, mandatory function
  11049. * @return {boolean} Returns true if the component is resized
  11050. */
  11051. LineGraph.prototype.redraw = function() {
  11052. var resized = false;
  11053. this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px';
  11054. if (this.lastWidth === undefined && this.width || this.lastWidth != this.width) {
  11055. resized = true;
  11056. }
  11057. // check if this component is resized
  11058. resized = this._isResized() || resized;
  11059. // check whether zoomed (in that case we need to re-stack everything)
  11060. var visibleInterval = this.body.range.end - this.body.range.start;
  11061. var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.width != this.lastWidth);
  11062. this.lastVisibleInterval = visibleInterval;
  11063. this.lastWidth = this.width;
  11064. // calculate actual size and position
  11065. this.width = this.dom.frame.offsetWidth;
  11066. // the svg element is three times as big as the width, this allows for fully dragging left and right
  11067. // without reloading the graph. the controls for this are bound to events in the constructor
  11068. if (resized == true) {
  11069. this.svg.style.width = util.option.asSize(3*this.width);
  11070. this.svg.style.left = util.option.asSize(-this.width);
  11071. }
  11072. if (zoomed == true) {
  11073. this._updateGraph();
  11074. }
  11075. this.legendLeft.redraw();
  11076. this.legendRight.redraw();
  11077. return resized;
  11078. };
  11079. /**
  11080. * Update and redraw the graph.
  11081. *
  11082. */
  11083. LineGraph.prototype._updateGraph = function () {
  11084. // reset the svg elements
  11085. DOMutil.prepareElements(this.svgElements);
  11086. // // very slow...
  11087. // groupData = group.itemsData.get({filter:
  11088. // function (item) {
  11089. // return (item.x > minDate && item.x < maxDate);
  11090. // }}
  11091. // );
  11092. if (this.width != 0 && this.itemsData != null) {
  11093. var group, groupData, preprocessedGroup, i;
  11094. var preprocessedGroupData = [];
  11095. var processedGroupData = [];
  11096. var groupRanges = [];
  11097. var changeCalled = false;
  11098. // getting group Ids
  11099. var groupIds = [];
  11100. for (var groupId in this.groups) {
  11101. if (this.groups.hasOwnProperty(groupId)) {
  11102. groupIds.push(groupId);
  11103. }
  11104. }
  11105. // this is the range of the SVG canvas
  11106. var minDate = this.body.util.toGlobalTime(- this.body.domProps.root.width);
  11107. var maxDate = this.body.util.toGlobalTime(2 * this.body.domProps.root.width);
  11108. // first select and preprocess the data from the datasets.
  11109. // the groups have their preselection of data, we now loop over this data to see
  11110. // what data we need to draw. Sorted data is much faster.
  11111. // more optimization is possible by doing the sampling before and using the binary search
  11112. // to find the end date to determine the increment.
  11113. if (groupIds.length > 0) {
  11114. for (i = 0; i < groupIds.length; i++) {
  11115. group = this.groups[groupIds[i]];
  11116. groupData = [];
  11117. // optimization for sorted data
  11118. if (group.options.sort == true) {
  11119. var guess = Math.max(0,util.binarySearchGeneric(group.itemsData, minDate, 'x', 'before'));
  11120. for (var j = guess; j < group.itemsData.length; j++) {
  11121. var item = group.itemsData[j];
  11122. if (item !== undefined) {
  11123. if (item.x > maxDate) {
  11124. groupData.push(item);
  11125. break;
  11126. }
  11127. else {
  11128. groupData.push(item);
  11129. }
  11130. }
  11131. }
  11132. }
  11133. else {
  11134. for (var j = 0; j < group.itemsData.length; j++) {
  11135. var item = group.itemsData[j];
  11136. if (item !== undefined) {
  11137. if (item.x > minDate && item.x < maxDate) {
  11138. groupData.push(item);
  11139. }
  11140. }
  11141. }
  11142. }
  11143. // preprocess, split into ranges and data
  11144. preprocessedGroup = this._preprocessData(groupData, group);
  11145. groupRanges.push({min: preprocessedGroup.min, max: preprocessedGroup.max});
  11146. preprocessedGroupData.push(preprocessedGroup.data);
  11147. }
  11148. // update the Y axis first, we use this data to draw at the correct Y points
  11149. // changeCalled is required to clean the SVG on a change emit.
  11150. changeCalled = this._updateYAxis(groupIds, groupRanges);
  11151. if (changeCalled == true) {
  11152. DOMutil.cleanupElements(this.svgElements);
  11153. this.body.emitter.emit("change");
  11154. return;
  11155. }
  11156. // with the yAxis scaled correctly, use this to get the Y values of the points.
  11157. for (i = 0; i < groupIds.length; i++) {
  11158. group = this.groups[groupIds[i]];
  11159. processedGroupData.push(this._convertYvalues(preprocessedGroupData[i],group))
  11160. }
  11161. // draw the groups
  11162. for (i = 0; i < groupIds.length; i++) {
  11163. group = this.groups[groupIds[i]];
  11164. if (group.options.style == 'line') {
  11165. this._drawLineGraph(processedGroupData[i], group);
  11166. }
  11167. else {
  11168. this._drawBarGraph (processedGroupData[i], group);
  11169. }
  11170. }
  11171. }
  11172. }
  11173. // cleanup unused svg elements
  11174. DOMutil.cleanupElements(this.svgElements);
  11175. };
  11176. /**
  11177. * this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden.
  11178. * @param {array} groupIds
  11179. * @private
  11180. */
  11181. LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
  11182. var changeCalled = false;
  11183. var yAxisLeftUsed = false;
  11184. var yAxisRightUsed = false;
  11185. var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal;
  11186. var orientation = 'left';
  11187. // if groups are present
  11188. if (groupIds.length > 0) {
  11189. for (var i = 0; i < groupIds.length; i++) {
  11190. orientation = 'left';
  11191. var group = this.groups[groupIds[i]];
  11192. if (group.options.yAxisOrientation == 'right') {
  11193. orientation = 'right';
  11194. }
  11195. minVal = groupRanges[i].min;
  11196. maxVal = groupRanges[i].max;
  11197. if (orientation == 'left') {
  11198. yAxisLeftUsed = true;
  11199. minLeft = minLeft > minVal ? minVal : minLeft;
  11200. maxLeft = maxLeft < maxVal ? maxVal : maxLeft;
  11201. }
  11202. else {
  11203. yAxisRightUsed = true;
  11204. minRight = minRight > minVal ? minVal : minRight;
  11205. maxRight = maxRight < maxVal ? maxVal : maxRight;
  11206. }
  11207. }
  11208. if (yAxisLeftUsed == true) {
  11209. this.yAxisLeft.setRange(minLeft, maxLeft);
  11210. }
  11211. if (yAxisRightUsed == true) {
  11212. this.yAxisRight.setRange(minRight, maxRight);
  11213. }
  11214. }
  11215. changeCalled = this._toggleAxisVisiblity(yAxisLeftUsed , this.yAxisLeft) || changeCalled;
  11216. changeCalled = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || changeCalled;
  11217. if (yAxisRightUsed == true && yAxisLeftUsed == true) {
  11218. this.yAxisLeft.drawIcons = true;
  11219. this.yAxisRight.drawIcons = true;
  11220. }
  11221. else {
  11222. this.yAxisLeft.drawIcons = false;
  11223. this.yAxisRight.drawIcons = false;
  11224. }
  11225. this.yAxisRight.master = !yAxisLeftUsed;
  11226. if (this.yAxisRight.master == false) {
  11227. if (yAxisRightUsed == true) {
  11228. this.yAxisLeft.lineOffset = this.yAxisRight.width;
  11229. }
  11230. changeCalled = this.yAxisLeft.redraw() || changeCalled;
  11231. this.yAxisRight.stepPixelsForced = this.yAxisLeft.stepPixels;
  11232. changeCalled = this.yAxisRight.redraw() || changeCalled;
  11233. }
  11234. else {
  11235. changeCalled = this.yAxisRight.redraw() || changeCalled;
  11236. }
  11237. return changeCalled;
  11238. };
  11239. /**
  11240. * This shows or hides the Y axis if needed. If there is a change, the changed event is emitted by the updateYAxis function
  11241. *
  11242. * @param {boolean} axisUsed
  11243. * @returns {boolean}
  11244. * @private
  11245. * @param axis
  11246. */
  11247. LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) {
  11248. var changed = false;
  11249. if (axisUsed == false) {
  11250. if (axis.dom.frame.parentNode) {
  11251. axis.hide();
  11252. changed = true;
  11253. }
  11254. }
  11255. else {
  11256. if (!axis.dom.frame.parentNode) {
  11257. axis.show();
  11258. changed = true;
  11259. }
  11260. }
  11261. return changed;
  11262. };
  11263. /**
  11264. * draw a bar graph
  11265. * @param datapoints
  11266. * @param group
  11267. */
  11268. LineGraph.prototype._drawBarGraph = function (dataset, group) {
  11269. if (dataset != null) {
  11270. if (dataset.length > 0) {
  11271. var coreDistance;
  11272. var minWidth = 0.1 * group.options.barChart.width;
  11273. var offset = 0;
  11274. var width = group.options.barChart.width;
  11275. if (group.options.barChart.align == 'left') {offset -= 0.5*width;}
  11276. else if (group.options.barChart.align == 'right') {offset += 0.5*width;}
  11277. for (var i = 0; i < dataset.length; i++) {
  11278. // dynammically downscale the width so there is no overlap up to 1/10th the original width
  11279. if (i+1 < dataset.length) {coreDistance = Math.abs(dataset[i+1].x - dataset[i].x);}
  11280. if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(dataset[i-1].x - dataset[i].x));}
  11281. if (coreDistance < width) {width = coreDistance < minWidth ? minWidth : coreDistance;}
  11282. DOMutil.drawBar(dataset[i].x + offset, dataset[i].y, width, group.zeroPosition - dataset[i].y, group.className + ' bar', this.svgElements, this.svg);
  11283. }
  11284. // draw points
  11285. if (group.options.drawPoints.enabled == true) {
  11286. this._drawPoints(dataset, group, this.svgElements, this.svg, offset);
  11287. }
  11288. }
  11289. }
  11290. };
  11291. /**
  11292. * draw a line graph
  11293. *
  11294. * @param datapoints
  11295. * @param group
  11296. */
  11297. LineGraph.prototype._drawLineGraph = function (dataset, group) {
  11298. if (dataset != null) {
  11299. if (dataset.length > 0) {
  11300. var path, d;
  11301. var svgHeight = Number(this.svg.style.height.replace("px",""));
  11302. path = DOMutil.getSVGElement('path', this.svgElements, this.svg);
  11303. path.setAttributeNS(null, "class", group.className);
  11304. // construct path from dataset
  11305. if (group.options.catmullRom.enabled == true) {
  11306. d = this._catmullRom(dataset, group);
  11307. }
  11308. else {
  11309. d = this._linear(dataset);
  11310. }
  11311. // append with points for fill and finalize the path
  11312. if (group.options.shaded.enabled == true) {
  11313. var fillPath = DOMutil.getSVGElement('path',this.svgElements, this.svg);
  11314. var dFill;
  11315. if (group.options.shaded.orientation == 'top') {
  11316. dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0;
  11317. }
  11318. else {
  11319. dFill = "M" + dataset[0].x + "," + svgHeight + " " + d + "L" + dataset[dataset.length - 1].x + "," + svgHeight;
  11320. }
  11321. fillPath.setAttributeNS(null, "class", group.className + " fill");
  11322. fillPath.setAttributeNS(null, "d", dFill);
  11323. }
  11324. // copy properties to path for drawing.
  11325. path.setAttributeNS(null, "d", "M" + d);
  11326. // draw points
  11327. if (group.options.drawPoints.enabled == true) {
  11328. this._drawPoints(dataset, group, this.svgElements, this.svg);
  11329. }
  11330. }
  11331. }
  11332. };
  11333. /**
  11334. * draw the data points
  11335. *
  11336. * @param dataset
  11337. * @param JSONcontainer
  11338. * @param svg
  11339. * @param group
  11340. */
  11341. LineGraph.prototype._drawPoints = function (dataset, group, JSONcontainer, svg, offset) {
  11342. if (offset === undefined) {offset = 0;}
  11343. for (var i = 0; i < dataset.length; i++) {
  11344. DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, group, JSONcontainer, svg);
  11345. }
  11346. };
  11347. /**
  11348. * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
  11349. * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
  11350. * the yAxis.
  11351. *
  11352. * @param datapoints
  11353. * @returns {Array}
  11354. * @private
  11355. */
  11356. LineGraph.prototype._preprocessData = function (datapoints, group) {
  11357. var extractedData = [];
  11358. var xValue, yValue;
  11359. var toScreen = this.body.util.toScreen;
  11360. var increment = 1;
  11361. var amountOfPoints = datapoints.length;
  11362. var yMin = datapoints[0].y;
  11363. var yMax = datapoints[0].y;
  11364. // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop
  11365. // of width changing of the yAxis.
  11366. if (group.options.sampling == true) {
  11367. var xDistance = this.body.util.toGlobalScreen(datapoints[datapoints.length-1].x) - this.body.util.toGlobalScreen(datapoints[0].x);
  11368. var pointsPerPixel = amountOfPoints/xDistance;
  11369. increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1,Math.round(pointsPerPixel)));
  11370. }
  11371. for (var i = 0; i < amountOfPoints; i += increment) {
  11372. xValue = toScreen(datapoints[i].x) + this.width - 1;
  11373. yValue = datapoints[i].y;
  11374. extractedData.push({x: xValue, y: yValue});
  11375. yMin = yMin > yValue ? yValue : yMin;
  11376. yMax = yMax < yValue ? yValue : yMax;
  11377. }
  11378. // extractedData.sort(function (a,b) {return a.x - b.x;});
  11379. return {min: yMin, max: yMax, data: extractedData};
  11380. };
  11381. /**
  11382. * This uses the DataAxis object to generate the correct Y coordinate on the SVG window. It uses the
  11383. * util function toScreen to get the x coordinate from the timestamp.
  11384. *
  11385. * @param datapoints
  11386. * @param options
  11387. * @returns {Array}
  11388. * @private
  11389. */
  11390. LineGraph.prototype._convertYvalues = function (datapoints, group) {
  11391. var extractedData = [];
  11392. var xValue, yValue;
  11393. var axis = this.yAxisLeft;
  11394. var svgHeight = Number(this.svg.style.height.replace("px",""));
  11395. if (group.options.yAxisOrientation == 'right') {
  11396. axis = this.yAxisRight;
  11397. }
  11398. for (var i = 0; i < datapoints.length; i++) {
  11399. xValue = datapoints[i].x;
  11400. yValue = Math.round(axis.convertValue(datapoints[i].y));
  11401. extractedData.push({x: xValue, y: yValue});
  11402. }
  11403. group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0)));
  11404. // extractedData.sort(function (a,b) {return a.x - b.x;});
  11405. return extractedData;
  11406. };
  11407. /**
  11408. * This uses an uniform parametrization of the CatmullRom algorithm:
  11409. * "On the Parameterization of Catmull-Rom Curves" by Cem Yuksel et al.
  11410. * @param data
  11411. * @returns {string}
  11412. * @private
  11413. */
  11414. LineGraph.prototype._catmullRomUniform = function(data) {
  11415. // catmull rom
  11416. var p0, p1, p2, p3, bp1, bp2;
  11417. var d = Math.round(data[0].x) + "," + Math.round(data[0].y) + " ";
  11418. var normalization = 1/6;
  11419. var length = data.length;
  11420. for (var i = 0; i < length - 1; i++) {
  11421. p0 = (i == 0) ? data[0] : data[i-1];
  11422. p1 = data[i];
  11423. p2 = data[i+1];
  11424. p3 = (i + 2 < length) ? data[i+2] : p2;
  11425. // Catmull-Rom to Cubic Bezier conversion matrix
  11426. // 0 1 0 0
  11427. // -1/6 1 1/6 0
  11428. // 0 1/6 1 -1/6
  11429. // 0 0 1 0
  11430. // bp0 = { x: p1.x, y: p1.y };
  11431. bp1 = { x: ((-p0.x + 6*p1.x + p2.x) *normalization), y: ((-p0.y + 6*p1.y + p2.y) *normalization)};
  11432. bp2 = { x: (( p1.x + 6*p2.x - p3.x) *normalization), y: (( p1.y + 6*p2.y - p3.y) *normalization)};
  11433. // bp0 = { x: p2.x, y: p2.y };
  11434. d += "C" +
  11435. bp1.x + "," +
  11436. bp1.y + " " +
  11437. bp2.x + "," +
  11438. bp2.y + " " +
  11439. p2.x + "," +
  11440. p2.y + " ";
  11441. }
  11442. return d;
  11443. };
  11444. /**
  11445. * This uses either the chordal or centripetal parameterization of the catmull-rom algorithm.
  11446. * By default, the centripetal parameterization is used because this gives the nicest results.
  11447. * These parameterizations are relatively heavy because the distance between 4 points have to be calculated.
  11448. *
  11449. * One optimization can be used to reuse distances since this is a sliding window approach.
  11450. * @param data
  11451. * @returns {string}
  11452. * @private
  11453. */
  11454. LineGraph.prototype._catmullRom = function(data, group) {
  11455. var alpha = group.options.catmullRom.alpha;
  11456. if (alpha == 0 || alpha === undefined) {
  11457. return this._catmullRomUniform(data);
  11458. }
  11459. else {
  11460. var p0, p1, p2, p3, bp1, bp2, d1,d2,d3, A, B, N, M;
  11461. var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA;
  11462. var d = Math.round(data[0].x) + "," + Math.round(data[0].y) + " ";
  11463. var length = data.length;
  11464. for (var i = 0; i < length - 1; i++) {
  11465. p0 = (i == 0) ? data[0] : data[i-1];
  11466. p1 = data[i];
  11467. p2 = data[i+1];
  11468. p3 = (i + 2 < length) ? data[i+2] : p2;
  11469. d1 = Math.sqrt(Math.pow(p0.x - p1.x,2) + Math.pow(p0.y - p1.y,2));
  11470. d2 = Math.sqrt(Math.pow(p1.x - p2.x,2) + Math.pow(p1.y - p2.y,2));
  11471. d3 = Math.sqrt(Math.pow(p2.x - p3.x,2) + Math.pow(p2.y - p3.y,2));
  11472. // Catmull-Rom to Cubic Bezier conversion matrix
  11473. //
  11474. // A = 2d1^2a + 3d1^a * d2^a + d3^2a
  11475. // B = 2d3^2a + 3d3^a * d2^a + d2^2a
  11476. //
  11477. // [ 0 1 0 0 ]
  11478. // [ -d2^2a/N A/N d1^2a/N 0 ]
  11479. // [ 0 d3^2a/M B/M -d2^2a/M ]
  11480. // [ 0 0 1 0 ]
  11481. // [ 0 1 0 0 ]
  11482. // [ -d2pow2a/N A/N d1pow2a/N 0 ]
  11483. // [ 0 d3pow2a/M B/M -d2pow2a/M ]
  11484. // [ 0 0 1 0 ]
  11485. d3powA = Math.pow(d3, alpha);
  11486. d3pow2A = Math.pow(d3,2*alpha);
  11487. d2powA = Math.pow(d2, alpha);
  11488. d2pow2A = Math.pow(d2,2*alpha);
  11489. d1powA = Math.pow(d1, alpha);
  11490. d1pow2A = Math.pow(d1,2*alpha);
  11491. A = 2*d1pow2A + 3*d1powA * d2powA + d2pow2A;
  11492. B = 2*d3pow2A + 3*d3powA * d2powA + d2pow2A;
  11493. N = 3*d1powA * (d1powA + d2powA);
  11494. if (N > 0) {N = 1 / N;}
  11495. M = 3*d3powA * (d3powA + d2powA);
  11496. if (M > 0) {M = 1 / M;}
  11497. bp1 = { x: ((-d2pow2A * p0.x + A*p1.x + d1pow2A * p2.x) * N),
  11498. y: ((-d2pow2A * p0.y + A*p1.y + d1pow2A * p2.y) * N)};
  11499. bp2 = { x: (( d3pow2A * p1.x + B*p2.x - d2pow2A * p3.x) * M),
  11500. y: (( d3pow2A * p1.y + B*p2.y - d2pow2A * p3.y) * M)};
  11501. if (bp1.x == 0 && bp1.y == 0) {bp1 = p1;}
  11502. if (bp2.x == 0 && bp2.y == 0) {bp2 = p2;}
  11503. d += "C" +
  11504. bp1.x + "," +
  11505. bp1.y + " " +
  11506. bp2.x + "," +
  11507. bp2.y + " " +
  11508. p2.x + "," +
  11509. p2.y + " ";
  11510. }
  11511. return d;
  11512. }
  11513. };
  11514. /**
  11515. * this generates the SVG path for a linear drawing between datapoints.
  11516. * @param data
  11517. * @returns {string}
  11518. * @private
  11519. */
  11520. LineGraph.prototype._linear = function(data) {
  11521. // linear
  11522. var d = "";
  11523. for (var i = 0; i < data.length; i++) {
  11524. if (i == 0) {
  11525. d += data[i].x + "," + data[i].y;
  11526. }
  11527. else {
  11528. d += " " + data[i].x + "," + data[i].y;
  11529. }
  11530. }
  11531. return d;
  11532. };
  11533. module.exports = LineGraph;
  11534. /***/ },
  11535. /* 27 */
  11536. /***/ function(module, exports, __webpack_require__) {
  11537. var util = __webpack_require__(1);
  11538. var Component = __webpack_require__(18);
  11539. var TimeStep = __webpack_require__(17);
  11540. /**
  11541. * A horizontal time axis
  11542. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
  11543. * @param {Object} [options] See TimeAxis.setOptions for the available
  11544. * options.
  11545. * @constructor TimeAxis
  11546. * @extends Component
  11547. */
  11548. function TimeAxis (body, options) {
  11549. this.dom = {
  11550. foreground: null,
  11551. majorLines: [],
  11552. majorTexts: [],
  11553. minorLines: [],
  11554. minorTexts: [],
  11555. redundant: {
  11556. majorLines: [],
  11557. majorTexts: [],
  11558. minorLines: [],
  11559. minorTexts: []
  11560. }
  11561. };
  11562. this.props = {
  11563. range: {
  11564. start: 0,
  11565. end: 0,
  11566. minimumStep: 0
  11567. },
  11568. lineTop: 0
  11569. };
  11570. this.defaultOptions = {
  11571. orientation: 'bottom', // supported: 'top', 'bottom'
  11572. // TODO: implement timeaxis orientations 'left' and 'right'
  11573. showMinorLabels: true,
  11574. showMajorLabels: true
  11575. };
  11576. this.options = util.extend({}, this.defaultOptions);
  11577. this.body = body;
  11578. // create the HTML DOM
  11579. this._create();
  11580. this.setOptions(options);
  11581. }
  11582. TimeAxis.prototype = new Component();
  11583. /**
  11584. * Set options for the TimeAxis.
  11585. * Parameters will be merged in current options.
  11586. * @param {Object} options Available options:
  11587. * {string} [orientation]
  11588. * {boolean} [showMinorLabels]
  11589. * {boolean} [showMajorLabels]
  11590. */
  11591. TimeAxis.prototype.setOptions = function(options) {
  11592. if (options) {
  11593. // copy all options that we know
  11594. util.selectiveExtend(['orientation', 'showMinorLabels', 'showMajorLabels'], this.options, options);
  11595. }
  11596. };
  11597. /**
  11598. * Create the HTML DOM for the TimeAxis
  11599. */
  11600. TimeAxis.prototype._create = function() {
  11601. this.dom.foreground = document.createElement('div');
  11602. this.dom.background = document.createElement('div');
  11603. this.dom.foreground.className = 'timeaxis foreground';
  11604. this.dom.background.className = 'timeaxis background';
  11605. };
  11606. /**
  11607. * Destroy the TimeAxis
  11608. */
  11609. TimeAxis.prototype.destroy = function() {
  11610. // remove from DOM
  11611. if (this.dom.foreground.parentNode) {
  11612. this.dom.foreground.parentNode.removeChild(this.dom.foreground);
  11613. }
  11614. if (this.dom.background.parentNode) {
  11615. this.dom.background.parentNode.removeChild(this.dom.background);
  11616. }
  11617. this.body = null;
  11618. };
  11619. /**
  11620. * Repaint the component
  11621. * @return {boolean} Returns true if the component is resized
  11622. */
  11623. TimeAxis.prototype.redraw = function () {
  11624. var options = this.options,
  11625. props = this.props,
  11626. foreground = this.dom.foreground,
  11627. background = this.dom.background;
  11628. // determine the correct parent DOM element (depending on option orientation)
  11629. var parent = (options.orientation == 'top') ? this.body.dom.top : this.body.dom.bottom;
  11630. var parentChanged = (foreground.parentNode !== parent);
  11631. // calculate character width and height
  11632. this._calculateCharSize();
  11633. // TODO: recalculate sizes only needed when parent is resized or options is changed
  11634. var orientation = this.options.orientation,
  11635. showMinorLabels = this.options.showMinorLabels,
  11636. showMajorLabels = this.options.showMajorLabels;
  11637. // determine the width and height of the elemens for the axis
  11638. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  11639. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  11640. props.height = props.minorLabelHeight + props.majorLabelHeight;
  11641. props.width = foreground.offsetWidth;
  11642. props.minorLineHeight = this.body.domProps.root.height - props.majorLabelHeight -
  11643. (options.orientation == 'top' ? this.body.domProps.bottom.height : this.body.domProps.top.height);
  11644. props.minorLineWidth = 1; // TODO: really calculate width
  11645. props.majorLineHeight = props.minorLineHeight + props.majorLabelHeight;
  11646. props.majorLineWidth = 1; // TODO: really calculate width
  11647. // take foreground and background offline while updating (is almost twice as fast)
  11648. var foregroundNextSibling = foreground.nextSibling;
  11649. var backgroundNextSibling = background.nextSibling;
  11650. foreground.parentNode && foreground.parentNode.removeChild(foreground);
  11651. background.parentNode && background.parentNode.removeChild(background);
  11652. foreground.style.height = this.props.height + 'px';
  11653. this._repaintLabels();
  11654. // put DOM online again (at the same place)
  11655. if (foregroundNextSibling) {
  11656. parent.insertBefore(foreground, foregroundNextSibling);
  11657. }
  11658. else {
  11659. parent.appendChild(foreground)
  11660. }
  11661. if (backgroundNextSibling) {
  11662. this.body.dom.backgroundVertical.insertBefore(background, backgroundNextSibling);
  11663. }
  11664. else {
  11665. this.body.dom.backgroundVertical.appendChild(background)
  11666. }
  11667. return this._isResized() || parentChanged;
  11668. };
  11669. /**
  11670. * Repaint major and minor text labels and vertical grid lines
  11671. * @private
  11672. */
  11673. TimeAxis.prototype._repaintLabels = function () {
  11674. var orientation = this.options.orientation;
  11675. // calculate range and step (step such that we have space for 7 characters per label)
  11676. var start = util.convert(this.body.range.start, 'Number'),
  11677. end = util.convert(this.body.range.end, 'Number'),
  11678. minimumStep = this.body.util.toTime((this.props.minorCharWidth || 10) * 7).valueOf()
  11679. -this.body.util.toTime(0).valueOf();
  11680. var step = new TimeStep(new Date(start), new Date(end), minimumStep);
  11681. this.step = step;
  11682. // Move all DOM elements to a "redundant" list, where they
  11683. // can be picked for re-use, and clear the lists with lines and texts.
  11684. // At the end of the function _repaintLabels, left over elements will be cleaned up
  11685. var dom = this.dom;
  11686. dom.redundant.majorLines = dom.majorLines;
  11687. dom.redundant.majorTexts = dom.majorTexts;
  11688. dom.redundant.minorLines = dom.minorLines;
  11689. dom.redundant.minorTexts = dom.minorTexts;
  11690. dom.majorLines = [];
  11691. dom.majorTexts = [];
  11692. dom.minorLines = [];
  11693. dom.minorTexts = [];
  11694. step.first();
  11695. var xFirstMajorLabel = undefined;
  11696. var max = 0;
  11697. while (step.hasNext() && max < 1000) {
  11698. max++;
  11699. var cur = step.getCurrent(),
  11700. x = this.body.util.toScreen(cur),
  11701. isMajor = step.isMajor();
  11702. // TODO: lines must have a width, such that we can create css backgrounds
  11703. if (this.options.showMinorLabels) {
  11704. this._repaintMinorText(x, step.getLabelMinor(), orientation);
  11705. }
  11706. if (isMajor && this.options.showMajorLabels) {
  11707. if (x > 0) {
  11708. if (xFirstMajorLabel == undefined) {
  11709. xFirstMajorLabel = x;
  11710. }
  11711. this._repaintMajorText(x, step.getLabelMajor(), orientation);
  11712. }
  11713. this._repaintMajorLine(x, orientation);
  11714. }
  11715. else {
  11716. this._repaintMinorLine(x, orientation);
  11717. }
  11718. step.next();
  11719. }
  11720. // create a major label on the left when needed
  11721. if (this.options.showMajorLabels) {
  11722. var leftTime = this.body.util.toTime(0),
  11723. leftText = step.getLabelMajor(leftTime),
  11724. widthText = leftText.length * (this.props.majorCharWidth || 10) + 10; // upper bound estimation
  11725. if (xFirstMajorLabel == undefined || widthText < xFirstMajorLabel) {
  11726. this._repaintMajorText(0, leftText, orientation);
  11727. }
  11728. }
  11729. // Cleanup leftover DOM elements from the redundant list
  11730. util.forEach(this.dom.redundant, function (arr) {
  11731. while (arr.length) {
  11732. var elem = arr.pop();
  11733. if (elem && elem.parentNode) {
  11734. elem.parentNode.removeChild(elem);
  11735. }
  11736. }
  11737. });
  11738. };
  11739. /**
  11740. * Create a minor label for the axis at position x
  11741. * @param {Number} x
  11742. * @param {String} text
  11743. * @param {String} orientation "top" or "bottom" (default)
  11744. * @private
  11745. */
  11746. TimeAxis.prototype._repaintMinorText = function (x, text, orientation) {
  11747. // reuse redundant label
  11748. var label = this.dom.redundant.minorTexts.shift();
  11749. if (!label) {
  11750. // create new label
  11751. var content = document.createTextNode('');
  11752. label = document.createElement('div');
  11753. label.appendChild(content);
  11754. label.className = 'text minor';
  11755. this.dom.foreground.appendChild(label);
  11756. }
  11757. this.dom.minorTexts.push(label);
  11758. label.childNodes[0].nodeValue = text;
  11759. label.style.top = (orientation == 'top') ? (this.props.majorLabelHeight + 'px') : '0';
  11760. label.style.left = x + 'px';
  11761. //label.title = title; // TODO: this is a heavy operation
  11762. };
  11763. /**
  11764. * Create a Major label for the axis at position x
  11765. * @param {Number} x
  11766. * @param {String} text
  11767. * @param {String} orientation "top" or "bottom" (default)
  11768. * @private
  11769. */
  11770. TimeAxis.prototype._repaintMajorText = function (x, text, orientation) {
  11771. // reuse redundant label
  11772. var label = this.dom.redundant.majorTexts.shift();
  11773. if (!label) {
  11774. // create label
  11775. var content = document.createTextNode(text);
  11776. label = document.createElement('div');
  11777. label.className = 'text major';
  11778. label.appendChild(content);
  11779. this.dom.foreground.appendChild(label);
  11780. }
  11781. this.dom.majorTexts.push(label);
  11782. label.childNodes[0].nodeValue = text;
  11783. //label.title = title; // TODO: this is a heavy operation
  11784. label.style.top = (orientation == 'top') ? '0' : (this.props.minorLabelHeight + 'px');
  11785. label.style.left = x + 'px';
  11786. };
  11787. /**
  11788. * Create a minor line for the axis at position x
  11789. * @param {Number} x
  11790. * @param {String} orientation "top" or "bottom" (default)
  11791. * @private
  11792. */
  11793. TimeAxis.prototype._repaintMinorLine = function (x, orientation) {
  11794. // reuse redundant line
  11795. var line = this.dom.redundant.minorLines.shift();
  11796. if (!line) {
  11797. // create vertical line
  11798. line = document.createElement('div');
  11799. line.className = 'grid vertical minor';
  11800. this.dom.background.appendChild(line);
  11801. }
  11802. this.dom.minorLines.push(line);
  11803. var props = this.props;
  11804. if (orientation == 'top') {
  11805. line.style.top = props.majorLabelHeight + 'px';
  11806. }
  11807. else {
  11808. line.style.top = this.body.domProps.top.height + 'px';
  11809. }
  11810. line.style.height = props.minorLineHeight + 'px';
  11811. line.style.left = (x - props.minorLineWidth / 2) + 'px';
  11812. };
  11813. /**
  11814. * Create a Major line for the axis at position x
  11815. * @param {Number} x
  11816. * @param {String} orientation "top" or "bottom" (default)
  11817. * @private
  11818. */
  11819. TimeAxis.prototype._repaintMajorLine = function (x, orientation) {
  11820. // reuse redundant line
  11821. var line = this.dom.redundant.majorLines.shift();
  11822. if (!line) {
  11823. // create vertical line
  11824. line = document.createElement('DIV');
  11825. line.className = 'grid vertical major';
  11826. this.dom.background.appendChild(line);
  11827. }
  11828. this.dom.majorLines.push(line);
  11829. var props = this.props;
  11830. if (orientation == 'top') {
  11831. line.style.top = '0';
  11832. }
  11833. else {
  11834. line.style.top = this.body.domProps.top.height + 'px';
  11835. }
  11836. line.style.left = (x - props.majorLineWidth / 2) + 'px';
  11837. line.style.height = props.majorLineHeight + 'px';
  11838. };
  11839. /**
  11840. * Determine the size of text on the axis (both major and minor axis).
  11841. * The size is calculated only once and then cached in this.props.
  11842. * @private
  11843. */
  11844. TimeAxis.prototype._calculateCharSize = function () {
  11845. // Note: We calculate char size with every redraw. Size may change, for
  11846. // example when any of the timelines parents had display:none for example.
  11847. // determine the char width and height on the minor axis
  11848. if (!this.dom.measureCharMinor) {
  11849. this.dom.measureCharMinor = document.createElement('DIV');
  11850. this.dom.measureCharMinor.className = 'text minor measure';
  11851. this.dom.measureCharMinor.style.position = 'absolute';
  11852. this.dom.measureCharMinor.appendChild(document.createTextNode('0'));
  11853. this.dom.foreground.appendChild(this.dom.measureCharMinor);
  11854. }
  11855. this.props.minorCharHeight = this.dom.measureCharMinor.clientHeight;
  11856. this.props.minorCharWidth = this.dom.measureCharMinor.clientWidth;
  11857. // determine the char width and height on the major axis
  11858. if (!this.dom.measureCharMajor) {
  11859. this.dom.measureCharMajor = document.createElement('DIV');
  11860. this.dom.measureCharMajor.className = 'text minor measure';
  11861. this.dom.measureCharMajor.style.position = 'absolute';
  11862. this.dom.measureCharMajor.appendChild(document.createTextNode('0'));
  11863. this.dom.foreground.appendChild(this.dom.measureCharMajor);
  11864. }
  11865. this.props.majorCharHeight = this.dom.measureCharMajor.clientHeight;
  11866. this.props.majorCharWidth = this.dom.measureCharMajor.clientWidth;
  11867. };
  11868. /**
  11869. * Snap a date to a rounded value.
  11870. * The snap intervals are dependent on the current scale and step.
  11871. * @param {Date} date the date to be snapped.
  11872. * @return {Date} snappedDate
  11873. */
  11874. TimeAxis.prototype.snap = function(date) {
  11875. return this.step.snap(date);
  11876. };
  11877. module.exports = TimeAxis;
  11878. /***/ },
  11879. /* 28 */
  11880. /***/ function(module, exports, __webpack_require__) {
  11881. var Hammer = __webpack_require__(41);
  11882. /**
  11883. * @constructor Item
  11884. * @param {Object} data Object containing (optional) parameters type,
  11885. * start, end, content, group, className.
  11886. * @param {{toScreen: function, toTime: function}} conversion
  11887. * Conversion functions from time to screen and vice versa
  11888. * @param {Object} options Configuration options
  11889. * // TODO: describe available options
  11890. */
  11891. function Item (data, conversion, options) {
  11892. this.id = null;
  11893. this.parent = null;
  11894. this.data = data;
  11895. this.dom = null;
  11896. this.conversion = conversion || {};
  11897. this.options = options || {};
  11898. this.selected = false;
  11899. this.displayed = false;
  11900. this.dirty = true;
  11901. this.top = null;
  11902. this.left = null;
  11903. this.width = null;
  11904. this.height = null;
  11905. }
  11906. /**
  11907. * Select current item
  11908. */
  11909. Item.prototype.select = function() {
  11910. this.selected = true;
  11911. if (this.displayed) this.redraw();
  11912. };
  11913. /**
  11914. * Unselect current item
  11915. */
  11916. Item.prototype.unselect = function() {
  11917. this.selected = false;
  11918. if (this.displayed) this.redraw();
  11919. };
  11920. /**
  11921. * Set a parent for the item
  11922. * @param {ItemSet | Group} parent
  11923. */
  11924. Item.prototype.setParent = function(parent) {
  11925. if (this.displayed) {
  11926. this.hide();
  11927. this.parent = parent;
  11928. if (this.parent) {
  11929. this.show();
  11930. }
  11931. }
  11932. else {
  11933. this.parent = parent;
  11934. }
  11935. };
  11936. /**
  11937. * Check whether this item is visible inside given range
  11938. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  11939. * @returns {boolean} True if visible
  11940. */
  11941. Item.prototype.isVisible = function(range) {
  11942. // Should be implemented by Item implementations
  11943. return false;
  11944. };
  11945. /**
  11946. * Show the Item in the DOM (when not already visible)
  11947. * @return {Boolean} changed
  11948. */
  11949. Item.prototype.show = function() {
  11950. return false;
  11951. };
  11952. /**
  11953. * Hide the Item from the DOM (when visible)
  11954. * @return {Boolean} changed
  11955. */
  11956. Item.prototype.hide = function() {
  11957. return false;
  11958. };
  11959. /**
  11960. * Repaint the item
  11961. */
  11962. Item.prototype.redraw = function() {
  11963. // should be implemented by the item
  11964. };
  11965. /**
  11966. * Reposition the Item horizontally
  11967. */
  11968. Item.prototype.repositionX = function() {
  11969. // should be implemented by the item
  11970. };
  11971. /**
  11972. * Reposition the Item vertically
  11973. */
  11974. Item.prototype.repositionY = function() {
  11975. // should be implemented by the item
  11976. };
  11977. /**
  11978. * Repaint a delete button on the top right of the item when the item is selected
  11979. * @param {HTMLElement} anchor
  11980. * @protected
  11981. */
  11982. Item.prototype._repaintDeleteButton = function (anchor) {
  11983. if (this.selected && this.options.editable.remove && !this.dom.deleteButton) {
  11984. // create and show button
  11985. var me = this;
  11986. var deleteButton = document.createElement('div');
  11987. deleteButton.className = 'delete';
  11988. deleteButton.title = 'Delete this item';
  11989. Hammer(deleteButton, {
  11990. preventDefault: true
  11991. }).on('tap', function (event) {
  11992. me.parent.removeFromDataSet(me);
  11993. event.stopPropagation();
  11994. });
  11995. anchor.appendChild(deleteButton);
  11996. this.dom.deleteButton = deleteButton;
  11997. }
  11998. else if (!this.selected && this.dom.deleteButton) {
  11999. // remove button
  12000. if (this.dom.deleteButton.parentNode) {
  12001. this.dom.deleteButton.parentNode.removeChild(this.dom.deleteButton);
  12002. }
  12003. this.dom.deleteButton = null;
  12004. }
  12005. };
  12006. module.exports = Item;
  12007. /***/ },
  12008. /* 29 */
  12009. /***/ function(module, exports, __webpack_require__) {
  12010. var Item = __webpack_require__(28);
  12011. /**
  12012. * @constructor ItemBox
  12013. * @extends Item
  12014. * @param {Object} data Object containing parameters start
  12015. * content, className.
  12016. * @param {{toScreen: function, toTime: function}} conversion
  12017. * Conversion functions from time to screen and vice versa
  12018. * @param {Object} [options] Configuration options
  12019. * // TODO: describe available options
  12020. */
  12021. function ItemBox (data, conversion, options) {
  12022. this.props = {
  12023. dot: {
  12024. width: 0,
  12025. height: 0
  12026. },
  12027. line: {
  12028. width: 0,
  12029. height: 0
  12030. }
  12031. };
  12032. // validate data
  12033. if (data) {
  12034. if (data.start == undefined) {
  12035. throw new Error('Property "start" missing in item ' + data);
  12036. }
  12037. }
  12038. Item.call(this, data, conversion, options);
  12039. }
  12040. ItemBox.prototype = new Item (null, null, null);
  12041. /**
  12042. * Check whether this item is visible inside given range
  12043. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  12044. * @returns {boolean} True if visible
  12045. */
  12046. ItemBox.prototype.isVisible = function(range) {
  12047. // determine visibility
  12048. // TODO: account for the real width of the item. Right now we just add 1/4 to the window
  12049. var interval = (range.end - range.start) / 4;
  12050. return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
  12051. };
  12052. /**
  12053. * Repaint the item
  12054. */
  12055. ItemBox.prototype.redraw = function() {
  12056. var dom = this.dom;
  12057. if (!dom) {
  12058. // create DOM
  12059. this.dom = {};
  12060. dom = this.dom;
  12061. // create main box
  12062. dom.box = document.createElement('DIV');
  12063. // contents box (inside the background box). used for making margins
  12064. dom.content = document.createElement('DIV');
  12065. dom.content.className = 'content';
  12066. dom.box.appendChild(dom.content);
  12067. // line to axis
  12068. dom.line = document.createElement('DIV');
  12069. dom.line.className = 'line';
  12070. // dot on axis
  12071. dom.dot = document.createElement('DIV');
  12072. dom.dot.className = 'dot';
  12073. // attach this item as attribute
  12074. dom.box['timeline-item'] = this;
  12075. }
  12076. // append DOM to parent DOM
  12077. if (!this.parent) {
  12078. throw new Error('Cannot redraw item: no parent attached');
  12079. }
  12080. if (!dom.box.parentNode) {
  12081. var foreground = this.parent.dom.foreground;
  12082. if (!foreground) throw new Error('Cannot redraw time axis: parent has no foreground container element');
  12083. foreground.appendChild(dom.box);
  12084. }
  12085. if (!dom.line.parentNode) {
  12086. var background = this.parent.dom.background;
  12087. if (!background) throw new Error('Cannot redraw time axis: parent has no background container element');
  12088. background.appendChild(dom.line);
  12089. }
  12090. if (!dom.dot.parentNode) {
  12091. var axis = this.parent.dom.axis;
  12092. if (!background) throw new Error('Cannot redraw time axis: parent has no axis container element');
  12093. axis.appendChild(dom.dot);
  12094. }
  12095. this.displayed = true;
  12096. // update contents
  12097. if (this.data.content != this.content) {
  12098. this.content = this.data.content;
  12099. if (this.content instanceof Element) {
  12100. dom.content.innerHTML = '';
  12101. dom.content.appendChild(this.content);
  12102. }
  12103. else if (this.data.content != undefined) {
  12104. dom.content.innerHTML = this.content;
  12105. }
  12106. else {
  12107. throw new Error('Property "content" missing in item ' + this.data.id);
  12108. }
  12109. this.dirty = true;
  12110. }
  12111. // update title
  12112. if (this.data.title != this.title) {
  12113. dom.box.title = this.data.title;
  12114. this.title = this.data.title;
  12115. }
  12116. // update class
  12117. var className = (this.data.className? ' ' + this.data.className : '') +
  12118. (this.selected ? ' selected' : '');
  12119. if (this.className != className) {
  12120. this.className = className;
  12121. dom.box.className = 'item box' + className;
  12122. dom.line.className = 'item line' + className;
  12123. dom.dot.className = 'item dot' + className;
  12124. this.dirty = true;
  12125. }
  12126. // recalculate size
  12127. if (this.dirty) {
  12128. this.props.dot.height = dom.dot.offsetHeight;
  12129. this.props.dot.width = dom.dot.offsetWidth;
  12130. this.props.line.width = dom.line.offsetWidth;
  12131. this.width = dom.box.offsetWidth;
  12132. this.height = dom.box.offsetHeight;
  12133. this.dirty = false;
  12134. }
  12135. this._repaintDeleteButton(dom.box);
  12136. };
  12137. /**
  12138. * Show the item in the DOM (when not already displayed). The items DOM will
  12139. * be created when needed.
  12140. */
  12141. ItemBox.prototype.show = function() {
  12142. if (!this.displayed) {
  12143. this.redraw();
  12144. }
  12145. };
  12146. /**
  12147. * Hide the item from the DOM (when visible)
  12148. */
  12149. ItemBox.prototype.hide = function() {
  12150. if (this.displayed) {
  12151. var dom = this.dom;
  12152. if (dom.box.parentNode) dom.box.parentNode.removeChild(dom.box);
  12153. if (dom.line.parentNode) dom.line.parentNode.removeChild(dom.line);
  12154. if (dom.dot.parentNode) dom.dot.parentNode.removeChild(dom.dot);
  12155. this.top = null;
  12156. this.left = null;
  12157. this.displayed = false;
  12158. }
  12159. };
  12160. /**
  12161. * Reposition the item horizontally
  12162. * @Override
  12163. */
  12164. ItemBox.prototype.repositionX = function() {
  12165. var start = this.conversion.toScreen(this.data.start),
  12166. align = this.options.align,
  12167. left,
  12168. box = this.dom.box,
  12169. line = this.dom.line,
  12170. dot = this.dom.dot;
  12171. // calculate left position of the box
  12172. if (align == 'right') {
  12173. this.left = start - this.width;
  12174. }
  12175. else if (align == 'left') {
  12176. this.left = start;
  12177. }
  12178. else {
  12179. // default or 'center'
  12180. this.left = start - this.width / 2;
  12181. }
  12182. // reposition box
  12183. box.style.left = this.left + 'px';
  12184. // reposition line
  12185. line.style.left = (start - this.props.line.width / 2) + 'px';
  12186. // reposition dot
  12187. dot.style.left = (start - this.props.dot.width / 2) + 'px';
  12188. };
  12189. /**
  12190. * Reposition the item vertically
  12191. * @Override
  12192. */
  12193. ItemBox.prototype.repositionY = function() {
  12194. var orientation = this.options.orientation,
  12195. box = this.dom.box,
  12196. line = this.dom.line,
  12197. dot = this.dom.dot;
  12198. if (orientation == 'top') {
  12199. box.style.top = (this.top || 0) + 'px';
  12200. line.style.top = '0';
  12201. line.style.height = (this.parent.top + this.top + 1) + 'px';
  12202. line.style.bottom = '';
  12203. }
  12204. else { // orientation 'bottom'
  12205. var itemSetHeight = this.parent.itemSet.props.height; // TODO: this is nasty
  12206. var lineHeight = itemSetHeight - this.parent.top - this.parent.height + this.top;
  12207. box.style.top = (this.parent.height - this.top - this.height || 0) + 'px';
  12208. line.style.top = (itemSetHeight - lineHeight) + 'px';
  12209. line.style.bottom = '0';
  12210. }
  12211. dot.style.top = (-this.props.dot.height / 2) + 'px';
  12212. };
  12213. module.exports = ItemBox;
  12214. /***/ },
  12215. /* 30 */
  12216. /***/ function(module, exports, __webpack_require__) {
  12217. var Item = __webpack_require__(28);
  12218. /**
  12219. * @constructor ItemPoint
  12220. * @extends Item
  12221. * @param {Object} data Object containing parameters start
  12222. * content, className.
  12223. * @param {{toScreen: function, toTime: function}} conversion
  12224. * Conversion functions from time to screen and vice versa
  12225. * @param {Object} [options] Configuration options
  12226. * // TODO: describe available options
  12227. */
  12228. function ItemPoint (data, conversion, options) {
  12229. this.props = {
  12230. dot: {
  12231. top: 0,
  12232. width: 0,
  12233. height: 0
  12234. },
  12235. content: {
  12236. height: 0,
  12237. marginLeft: 0
  12238. }
  12239. };
  12240. // validate data
  12241. if (data) {
  12242. if (data.start == undefined) {
  12243. throw new Error('Property "start" missing in item ' + data);
  12244. }
  12245. }
  12246. Item.call(this, data, conversion, options);
  12247. }
  12248. ItemPoint.prototype = new Item (null, null, null);
  12249. /**
  12250. * Check whether this item is visible inside given range
  12251. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  12252. * @returns {boolean} True if visible
  12253. */
  12254. ItemPoint.prototype.isVisible = function(range) {
  12255. // determine visibility
  12256. // TODO: account for the real width of the item. Right now we just add 1/4 to the window
  12257. var interval = (range.end - range.start) / 4;
  12258. return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
  12259. };
  12260. /**
  12261. * Repaint the item
  12262. */
  12263. ItemPoint.prototype.redraw = function() {
  12264. var dom = this.dom;
  12265. if (!dom) {
  12266. // create DOM
  12267. this.dom = {};
  12268. dom = this.dom;
  12269. // background box
  12270. dom.point = document.createElement('div');
  12271. // className is updated in redraw()
  12272. // contents box, right from the dot
  12273. dom.content = document.createElement('div');
  12274. dom.content.className = 'content';
  12275. dom.point.appendChild(dom.content);
  12276. // dot at start
  12277. dom.dot = document.createElement('div');
  12278. dom.point.appendChild(dom.dot);
  12279. // attach this item as attribute
  12280. dom.point['timeline-item'] = this;
  12281. }
  12282. // append DOM to parent DOM
  12283. if (!this.parent) {
  12284. throw new Error('Cannot redraw item: no parent attached');
  12285. }
  12286. if (!dom.point.parentNode) {
  12287. var foreground = this.parent.dom.foreground;
  12288. if (!foreground) {
  12289. throw new Error('Cannot redraw time axis: parent has no foreground container element');
  12290. }
  12291. foreground.appendChild(dom.point);
  12292. }
  12293. this.displayed = true;
  12294. // update contents
  12295. if (this.data.content != this.content) {
  12296. this.content = this.data.content;
  12297. if (this.content instanceof Element) {
  12298. dom.content.innerHTML = '';
  12299. dom.content.appendChild(this.content);
  12300. }
  12301. else if (this.data.content != undefined) {
  12302. dom.content.innerHTML = this.content;
  12303. }
  12304. else {
  12305. throw new Error('Property "content" missing in item ' + this.data.id);
  12306. }
  12307. this.dirty = true;
  12308. }
  12309. // update title
  12310. if (this.data.title != this.title) {
  12311. dom.point.title = this.data.title;
  12312. this.title = this.data.title;
  12313. }
  12314. // update class
  12315. var className = (this.data.className? ' ' + this.data.className : '') +
  12316. (this.selected ? ' selected' : '');
  12317. if (this.className != className) {
  12318. this.className = className;
  12319. dom.point.className = 'item point' + className;
  12320. dom.dot.className = 'item dot' + className;
  12321. this.dirty = true;
  12322. }
  12323. // recalculate size
  12324. if (this.dirty) {
  12325. this.width = dom.point.offsetWidth;
  12326. this.height = dom.point.offsetHeight;
  12327. this.props.dot.width = dom.dot.offsetWidth;
  12328. this.props.dot.height = dom.dot.offsetHeight;
  12329. this.props.content.height = dom.content.offsetHeight;
  12330. // resize contents
  12331. dom.content.style.marginLeft = 2 * this.props.dot.width + 'px';
  12332. //dom.content.style.marginRight = ... + 'px'; // TODO: margin right
  12333. dom.dot.style.top = ((this.height - this.props.dot.height) / 2) + 'px';
  12334. dom.dot.style.left = (this.props.dot.width / 2) + 'px';
  12335. this.dirty = false;
  12336. }
  12337. this._repaintDeleteButton(dom.point);
  12338. };
  12339. /**
  12340. * Show the item in the DOM (when not already visible). The items DOM will
  12341. * be created when needed.
  12342. */
  12343. ItemPoint.prototype.show = function() {
  12344. if (!this.displayed) {
  12345. this.redraw();
  12346. }
  12347. };
  12348. /**
  12349. * Hide the item from the DOM (when visible)
  12350. */
  12351. ItemPoint.prototype.hide = function() {
  12352. if (this.displayed) {
  12353. if (this.dom.point.parentNode) {
  12354. this.dom.point.parentNode.removeChild(this.dom.point);
  12355. }
  12356. this.top = null;
  12357. this.left = null;
  12358. this.displayed = false;
  12359. }
  12360. };
  12361. /**
  12362. * Reposition the item horizontally
  12363. * @Override
  12364. */
  12365. ItemPoint.prototype.repositionX = function() {
  12366. var start = this.conversion.toScreen(this.data.start);
  12367. this.left = start - this.props.dot.width;
  12368. // reposition point
  12369. this.dom.point.style.left = this.left + 'px';
  12370. };
  12371. /**
  12372. * Reposition the item vertically
  12373. * @Override
  12374. */
  12375. ItemPoint.prototype.repositionY = function() {
  12376. var orientation = this.options.orientation,
  12377. point = this.dom.point;
  12378. if (orientation == 'top') {
  12379. point.style.top = this.top + 'px';
  12380. }
  12381. else {
  12382. point.style.top = (this.parent.height - this.top - this.height) + 'px';
  12383. }
  12384. };
  12385. module.exports = ItemPoint;
  12386. /***/ },
  12387. /* 31 */
  12388. /***/ function(module, exports, __webpack_require__) {
  12389. var Hammer = __webpack_require__(41);
  12390. var Item = __webpack_require__(28);
  12391. /**
  12392. * @constructor ItemRange
  12393. * @extends Item
  12394. * @param {Object} data Object containing parameters start, end
  12395. * content, className.
  12396. * @param {{toScreen: function, toTime: function}} conversion
  12397. * Conversion functions from time to screen and vice versa
  12398. * @param {Object} [options] Configuration options
  12399. * // TODO: describe options
  12400. */
  12401. function ItemRange (data, conversion, options) {
  12402. this.props = {
  12403. content: {
  12404. width: 0
  12405. }
  12406. };
  12407. this.overflow = false; // if contents can overflow (css styling), this flag is set to true
  12408. // validate data
  12409. if (data) {
  12410. if (data.start == undefined) {
  12411. throw new Error('Property "start" missing in item ' + data.id);
  12412. }
  12413. if (data.end == undefined) {
  12414. throw new Error('Property "end" missing in item ' + data.id);
  12415. }
  12416. }
  12417. Item.call(this, data, conversion, options);
  12418. }
  12419. ItemRange.prototype = new Item (null, null, null);
  12420. ItemRange.prototype.baseClassName = 'item range';
  12421. /**
  12422. * Check whether this item is visible inside given range
  12423. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  12424. * @returns {boolean} True if visible
  12425. */
  12426. ItemRange.prototype.isVisible = function(range) {
  12427. // determine visibility
  12428. return (this.data.start < range.end) && (this.data.end > range.start);
  12429. };
  12430. /**
  12431. * Repaint the item
  12432. */
  12433. ItemRange.prototype.redraw = function() {
  12434. var dom = this.dom;
  12435. if (!dom) {
  12436. // create DOM
  12437. this.dom = {};
  12438. dom = this.dom;
  12439. // background box
  12440. dom.box = document.createElement('div');
  12441. // className is updated in redraw()
  12442. // contents box
  12443. dom.content = document.createElement('div');
  12444. dom.content.className = 'content';
  12445. dom.box.appendChild(dom.content);
  12446. // attach this item as attribute
  12447. dom.box['timeline-item'] = this;
  12448. }
  12449. // append DOM to parent DOM
  12450. if (!this.parent) {
  12451. throw new Error('Cannot redraw item: no parent attached');
  12452. }
  12453. if (!dom.box.parentNode) {
  12454. var foreground = this.parent.dom.foreground;
  12455. if (!foreground) {
  12456. throw new Error('Cannot redraw time axis: parent has no foreground container element');
  12457. }
  12458. foreground.appendChild(dom.box);
  12459. }
  12460. this.displayed = true;
  12461. // update contents
  12462. if (this.data.content != this.content) {
  12463. this.content = this.data.content;
  12464. if (this.content instanceof Element) {
  12465. dom.content.innerHTML = '';
  12466. dom.content.appendChild(this.content);
  12467. }
  12468. else if (this.data.content != undefined) {
  12469. dom.content.innerHTML = this.content;
  12470. }
  12471. else {
  12472. throw new Error('Property "content" missing in item ' + this.data.id);
  12473. }
  12474. this.dirty = true;
  12475. }
  12476. // update title
  12477. if (this.data.title != this.title) {
  12478. dom.box.title = this.data.title;
  12479. this.title = this.data.title;
  12480. }
  12481. // update class
  12482. var className = (this.data.className ? (' ' + this.data.className) : '') +
  12483. (this.selected ? ' selected' : '');
  12484. if (this.className != className) {
  12485. this.className = className;
  12486. dom.box.className = this.baseClassName + className;
  12487. this.dirty = true;
  12488. }
  12489. // recalculate size
  12490. if (this.dirty) {
  12491. // determine from css whether this box has overflow
  12492. this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden';
  12493. this.props.content.width = this.dom.content.offsetWidth;
  12494. this.height = this.dom.box.offsetHeight;
  12495. this.dirty = false;
  12496. }
  12497. this._repaintDeleteButton(dom.box);
  12498. this._repaintDragLeft();
  12499. this._repaintDragRight();
  12500. };
  12501. /**
  12502. * Show the item in the DOM (when not already visible). The items DOM will
  12503. * be created when needed.
  12504. */
  12505. ItemRange.prototype.show = function() {
  12506. if (!this.displayed) {
  12507. this.redraw();
  12508. }
  12509. };
  12510. /**
  12511. * Hide the item from the DOM (when visible)
  12512. * @return {Boolean} changed
  12513. */
  12514. ItemRange.prototype.hide = function() {
  12515. if (this.displayed) {
  12516. var box = this.dom.box;
  12517. if (box.parentNode) {
  12518. box.parentNode.removeChild(box);
  12519. }
  12520. this.top = null;
  12521. this.left = null;
  12522. this.displayed = false;
  12523. }
  12524. };
  12525. /**
  12526. * Reposition the item horizontally
  12527. * @Override
  12528. */
  12529. // TODO: delete the old function
  12530. ItemRange.prototype.repositionX = function() {
  12531. var props = this.props,
  12532. parentWidth = this.parent.width,
  12533. start = this.conversion.toScreen(this.data.start),
  12534. end = this.conversion.toScreen(this.data.end),
  12535. padding = this.options.padding,
  12536. contentLeft;
  12537. // limit the width of the this, as browsers cannot draw very wide divs
  12538. if (start < -parentWidth) {
  12539. start = -parentWidth;
  12540. }
  12541. if (end > 2 * parentWidth) {
  12542. end = 2 * parentWidth;
  12543. }
  12544. var boxWidth = Math.max(end - start, 1);
  12545. if (this.overflow) {
  12546. // when range exceeds left of the window, position the contents at the left of the visible area
  12547. contentLeft = Math.max(-start, 0);
  12548. this.left = start;
  12549. this.width = boxWidth + this.props.content.width;
  12550. // Note: The calculation of width is an optimistic calculation, giving
  12551. // a width which will not change when moving the Timeline
  12552. // So no restacking needed, which is nicer for the eye;
  12553. }
  12554. else { // no overflow
  12555. // when range exceeds left of the window, position the contents at the left of the visible area
  12556. if (start < 0) {
  12557. contentLeft = Math.min(-start,
  12558. (end - start - props.content.width - 2 * padding));
  12559. // TODO: remove the need for options.padding. it's terrible.
  12560. }
  12561. else {
  12562. contentLeft = 0;
  12563. }
  12564. this.left = start;
  12565. this.width = boxWidth;
  12566. }
  12567. this.dom.box.style.left = this.left + 'px';
  12568. this.dom.box.style.width = boxWidth + 'px';
  12569. this.dom.content.style.left = contentLeft + 'px';
  12570. };
  12571. /**
  12572. * Reposition the item vertically
  12573. * @Override
  12574. */
  12575. ItemRange.prototype.repositionY = function() {
  12576. var orientation = this.options.orientation,
  12577. box = this.dom.box;
  12578. if (orientation == 'top') {
  12579. box.style.top = this.top + 'px';
  12580. }
  12581. else {
  12582. box.style.top = (this.parent.height - this.top - this.height) + 'px';
  12583. }
  12584. };
  12585. /**
  12586. * Repaint a drag area on the left side of the range when the range is selected
  12587. * @protected
  12588. */
  12589. ItemRange.prototype._repaintDragLeft = function () {
  12590. if (this.selected && this.options.editable.updateTime && !this.dom.dragLeft) {
  12591. // create and show drag area
  12592. var dragLeft = document.createElement('div');
  12593. dragLeft.className = 'drag-left';
  12594. dragLeft.dragLeftItem = this;
  12595. // TODO: this should be redundant?
  12596. Hammer(dragLeft, {
  12597. preventDefault: true
  12598. }).on('drag', function () {
  12599. //console.log('drag left')
  12600. });
  12601. this.dom.box.appendChild(dragLeft);
  12602. this.dom.dragLeft = dragLeft;
  12603. }
  12604. else if (!this.selected && this.dom.dragLeft) {
  12605. // delete drag area
  12606. if (this.dom.dragLeft.parentNode) {
  12607. this.dom.dragLeft.parentNode.removeChild(this.dom.dragLeft);
  12608. }
  12609. this.dom.dragLeft = null;
  12610. }
  12611. };
  12612. /**
  12613. * Repaint a drag area on the right side of the range when the range is selected
  12614. * @protected
  12615. */
  12616. ItemRange.prototype._repaintDragRight = function () {
  12617. if (this.selected && this.options.editable.updateTime && !this.dom.dragRight) {
  12618. // create and show drag area
  12619. var dragRight = document.createElement('div');
  12620. dragRight.className = 'drag-right';
  12621. dragRight.dragRightItem = this;
  12622. // TODO: this should be redundant?
  12623. Hammer(dragRight, {
  12624. preventDefault: true
  12625. }).on('drag', function () {
  12626. //console.log('drag right')
  12627. });
  12628. this.dom.box.appendChild(dragRight);
  12629. this.dom.dragRight = dragRight;
  12630. }
  12631. else if (!this.selected && this.dom.dragRight) {
  12632. // delete drag area
  12633. if (this.dom.dragRight.parentNode) {
  12634. this.dom.dragRight.parentNode.removeChild(this.dom.dragRight);
  12635. }
  12636. this.dom.dragRight = null;
  12637. }
  12638. };
  12639. module.exports = ItemRange;
  12640. /***/ },
  12641. /* 32 */
  12642. /***/ function(module, exports, __webpack_require__) {
  12643. var Emitter = __webpack_require__(45);
  12644. var Hammer = __webpack_require__(41);
  12645. var mousetrap = __webpack_require__(47);
  12646. var util = __webpack_require__(1);
  12647. var hammerUtil = __webpack_require__(42);
  12648. var DataSet = __webpack_require__(3);
  12649. var DataView = __webpack_require__(4);
  12650. var dotparser = __webpack_require__(38);
  12651. var gephiParser = __webpack_require__(39);
  12652. var Groups = __webpack_require__(34);
  12653. var Images = __webpack_require__(35);
  12654. var Node = __webpack_require__(36);
  12655. var Edge = __webpack_require__(33);
  12656. var Popup = __webpack_require__(37);
  12657. var MixinLoader = __webpack_require__(44);
  12658. // Load custom shapes into CanvasRenderingContext2D
  12659. __webpack_require__(43);
  12660. /**
  12661. * @constructor Network
  12662. * Create a network visualization, displaying nodes and edges.
  12663. *
  12664. * @param {Element} container The DOM element in which the Network will
  12665. * be created. Normally a div element.
  12666. * @param {Object} data An object containing parameters
  12667. * {Array} nodes
  12668. * {Array} edges
  12669. * @param {Object} options Options
  12670. */
  12671. function Network (container, data, options) {
  12672. if (!(this instanceof Network)) {
  12673. throw new SyntaxError('Constructor must be called with the new operator');
  12674. }
  12675. this._initializeMixinLoaders();
  12676. // create variables and set default values
  12677. this.containerElement = container;
  12678. this.width = '100%';
  12679. this.height = '100%';
  12680. // render and calculation settings
  12681. this.renderRefreshRate = 60; // hz (fps)
  12682. this.renderTimestep = 1000 / this.renderRefreshRate; // ms -- saves calculation later on
  12683. this.renderTime = 0.5 * this.renderTimestep; // measured time it takes to render a frame
  12684. this.maxPhysicsTicksPerRender = 3; // max amount of physics ticks per render step.
  12685. this.physicsDiscreteStepsize = 0.50; // discrete stepsize of the simulation
  12686. this.stabilize = true; // stabilize before displaying the network
  12687. this.selectable = true;
  12688. this.initializing = true;
  12689. // these functions are triggered when the dataset is edited
  12690. this.triggerFunctions = {add:null,edit:null,editEdge:null,connect:null,del:null};
  12691. // set constant values
  12692. this.constants = {
  12693. nodes: {
  12694. radiusMin: 10,
  12695. radiusMax: 30,
  12696. radius: 10,
  12697. shape: 'ellipse',
  12698. image: undefined,
  12699. widthMin: 16, // px
  12700. widthMax: 64, // px
  12701. fixed: false,
  12702. fontColor: 'black',
  12703. fontSize: 14, // px
  12704. fontFace: 'verdana',
  12705. level: -1,
  12706. color: {
  12707. border: '#2B7CE9',
  12708. background: '#97C2FC',
  12709. highlight: {
  12710. border: '#2B7CE9',
  12711. background: '#D2E5FF'
  12712. },
  12713. hover: {
  12714. border: '#2B7CE9',
  12715. background: '#D2E5FF'
  12716. }
  12717. },
  12718. borderColor: '#2B7CE9',
  12719. backgroundColor: '#97C2FC',
  12720. highlightColor: '#D2E5FF',
  12721. group: undefined,
  12722. borderWidth: 1
  12723. },
  12724. edges: {
  12725. widthMin: 1,
  12726. widthMax: 15,
  12727. width: 1,
  12728. widthSelectionMultiplier: 2,
  12729. hoverWidth: 1.5,
  12730. style: 'line',
  12731. color: {
  12732. color:'#848484',
  12733. highlight:'#848484',
  12734. hover: '#848484'
  12735. },
  12736. fontColor: '#343434',
  12737. fontSize: 14, // px
  12738. fontFace: 'arial',
  12739. fontFill: 'white',
  12740. arrowScaleFactor: 1,
  12741. dash: {
  12742. length: 10,
  12743. gap: 5,
  12744. altLength: undefined
  12745. },
  12746. inheritColor: "from" // to, from, false, true (== from)
  12747. },
  12748. configurePhysics:false,
  12749. physics: {
  12750. barnesHut: {
  12751. enabled: true,
  12752. theta: 1 / 0.6, // inverted to save time during calculation
  12753. gravitationalConstant: -2000,
  12754. centralGravity: 0.3,
  12755. springLength: 95,
  12756. springConstant: 0.04,
  12757. damping: 0.09
  12758. },
  12759. repulsion: {
  12760. centralGravity: 0.0,
  12761. springLength: 200,
  12762. springConstant: 0.05,
  12763. nodeDistance: 100,
  12764. damping: 0.09
  12765. },
  12766. hierarchicalRepulsion: {
  12767. enabled: false,
  12768. centralGravity: 0.0,
  12769. springLength: 100,
  12770. springConstant: 0.01,
  12771. nodeDistance: 150,
  12772. damping: 0.09
  12773. },
  12774. damping: null,
  12775. centralGravity: null,
  12776. springLength: null,
  12777. springConstant: null
  12778. },
  12779. clustering: { // Per Node in Cluster = PNiC
  12780. enabled: false, // (Boolean) | global on/off switch for clustering.
  12781. initialMaxNodes: 100, // (# nodes) | if the initial amount of nodes is larger than this, we cluster until the total number is less than this threshold.
  12782. 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
  12783. 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
  12784. chainThreshold: 0.4, // (% of all drawn nodes)| maximum percentage of allowed chainnodes (long strings of connected nodes) within all nodes. (lower means less chains).
  12785. clusterEdgeThreshold: 20, // (px) | edge length threshold. if smaller, this node is clustered.
  12786. sectorThreshold: 100, // (# nodes in cluster) | cluster size threshold. If larger, expanding in own sector.
  12787. 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.
  12788. fontSizeMultiplier: 4.0, // (px PNiC) | how much the cluster font size grows per node in cluster (in px).
  12789. maxFontSize: 1000,
  12790. forceAmplification: 0.1, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster).
  12791. distanceAmplification: 0.1, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster).
  12792. edgeGrowth: 20, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
  12793. nodeScaling: {width: 1, // (px PNiC) | growth of the width per node in cluster.
  12794. height: 1, // (px PNiC) | growth of the height per node in cluster.
  12795. radius: 1}, // (px PNiC) | growth of the radius per node in cluster.
  12796. maxNodeSizeIncrements: 600, // (# increments) | max growth of the width per node in cluster.
  12797. activeAreaBoxSize: 80, // (px) | box area around the curser where clusters are popped open.
  12798. clusterLevelDifference: 2
  12799. },
  12800. navigation: {
  12801. enabled: false
  12802. },
  12803. keyboard: {
  12804. enabled: false,
  12805. speed: {x: 10, y: 10, zoom: 0.02}
  12806. },
  12807. dataManipulation: {
  12808. enabled: false,
  12809. initiallyVisible: false
  12810. },
  12811. hierarchicalLayout: {
  12812. enabled:false,
  12813. levelSeparation: 150,
  12814. nodeSpacing: 100,
  12815. direction: "UD" // UD, DU, LR, RL
  12816. },
  12817. freezeForStabilization: false,
  12818. smoothCurves: {
  12819. enabled: true,
  12820. dynamic: true,
  12821. type: "continuous",
  12822. roundness: 0.5
  12823. },
  12824. dynamicSmoothCurves: true,
  12825. maxVelocity: 30,
  12826. minVelocity: 0.1, // px/s
  12827. stabilizationIterations: 1000, // maximum number of iteration to stabilize
  12828. labels:{
  12829. add:"Add Node",
  12830. edit:"Edit",
  12831. link:"Add Link",
  12832. del:"Delete selected",
  12833. editNode:"Edit Node",
  12834. editEdge:"Edit Edge",
  12835. back:"Back",
  12836. addDescription:"Click in an empty space to place a new node.",
  12837. linkDescription:"Click on a node and drag the edge to another node to connect them.",
  12838. editEdgeDescription:"Click on the control points and drag them to a node to connect to it.",
  12839. addError:"The function for add does not support two arguments (data,callback).",
  12840. linkError:"The function for connect does not support two arguments (data,callback).",
  12841. editError:"The function for edit does not support two arguments (data, callback).",
  12842. editBoundError:"No edit function has been bound to this button.",
  12843. deleteError:"The function for delete does not support two arguments (data, callback).",
  12844. deleteClusterError:"Clusters cannot be deleted."
  12845. },
  12846. tooltip: {
  12847. delay: 300,
  12848. fontColor: 'black',
  12849. fontSize: 14, // px
  12850. fontFace: 'verdana',
  12851. color: {
  12852. border: '#666',
  12853. background: '#FFFFC6'
  12854. }
  12855. },
  12856. dragNetwork: true,
  12857. dragNodes: true,
  12858. zoomable: true,
  12859. hover: false,
  12860. hideEdgesOnDrag: false,
  12861. hideNodesOnDrag: false
  12862. };
  12863. this.hoverObj = {nodes:{},edges:{}};
  12864. this.controlNodesActive = false;
  12865. // Node variables
  12866. var network = this;
  12867. this.groups = new Groups(); // object with groups
  12868. this.images = new Images(); // object with images
  12869. this.images.setOnloadCallback(function () {
  12870. network._redraw();
  12871. });
  12872. // keyboard navigation variables
  12873. this.xIncrement = 0;
  12874. this.yIncrement = 0;
  12875. this.zoomIncrement = 0;
  12876. // loading all the mixins:
  12877. // load the force calculation functions, grouped under the physics system.
  12878. this._loadPhysicsSystem();
  12879. // create a frame and canvas
  12880. this._create();
  12881. // load the sector system. (mandatory, fully integrated with Network)
  12882. this._loadSectorSystem();
  12883. // load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it)
  12884. this._loadClusterSystem();
  12885. // load the selection system. (mandatory, required by Network)
  12886. this._loadSelectionSystem();
  12887. // load the selection system. (mandatory, required by Network)
  12888. this._loadHierarchySystem();
  12889. // apply options
  12890. this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
  12891. this._setScale(1);
  12892. this.setOptions(options);
  12893. // other vars
  12894. this.freezeSimulation = false;// freeze the simulation
  12895. this.cachedFunctions = {};
  12896. // containers for nodes and edges
  12897. this.calculationNodes = {};
  12898. this.calculationNodeIndices = [];
  12899. this.nodeIndices = []; // array with all the indices of the nodes. Used to speed up forces calculation
  12900. this.nodes = {}; // object with Node objects
  12901. this.edges = {}; // object with Edge objects
  12902. // position and scale variables and objects
  12903. this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw.
  12904. this.canvasBottomRight = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  12905. this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  12906. this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action
  12907. this.scale = 1; // defining the global scale variable in the constructor
  12908. this.previousScale = this.scale; // this is used to check if the zoom operation is zooming in or out
  12909. // datasets or dataviews
  12910. this.nodesData = null; // A DataSet or DataView
  12911. this.edgesData = null; // A DataSet or DataView
  12912. // create event listeners used to subscribe on the DataSets of the nodes and edges
  12913. this.nodesListeners = {
  12914. 'add': function (event, params) {
  12915. network._addNodes(params.items);
  12916. network.start();
  12917. },
  12918. 'update': function (event, params) {
  12919. network._updateNodes(params.items);
  12920. network.start();
  12921. },
  12922. 'remove': function (event, params) {
  12923. network._removeNodes(params.items);
  12924. network.start();
  12925. }
  12926. };
  12927. this.edgesListeners = {
  12928. 'add': function (event, params) {
  12929. network._addEdges(params.items);
  12930. network.start();
  12931. },
  12932. 'update': function (event, params) {
  12933. network._updateEdges(params.items);
  12934. network.start();
  12935. },
  12936. 'remove': function (event, params) {
  12937. network._removeEdges(params.items);
  12938. network.start();
  12939. }
  12940. };
  12941. // properties for the animation
  12942. this.moving = true;
  12943. this.timer = undefined; // Scheduling function. Is definded in this.start();
  12944. // load data (the disable start variable will be the same as the enabled clustering)
  12945. this.setData(data,this.constants.clustering.enabled || this.constants.hierarchicalLayout.enabled);
  12946. // hierarchical layout
  12947. this.initializing = false;
  12948. if (this.constants.hierarchicalLayout.enabled == true) {
  12949. this._setupHierarchicalLayout();
  12950. }
  12951. else {
  12952. // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
  12953. if (this.stabilize == false) {
  12954. this.zoomExtent(true,this.constants.clustering.enabled);
  12955. }
  12956. }
  12957. // if clustering is disabled, the simulation will have started in the setData function
  12958. if (this.constants.clustering.enabled) {
  12959. this.startWithClustering();
  12960. }
  12961. }
  12962. // Extend Network with an Emitter mixin
  12963. Emitter(Network.prototype);
  12964. /**
  12965. * Get the script path where the vis.js library is located
  12966. *
  12967. * @returns {string | null} path Path or null when not found. Path does not
  12968. * end with a slash.
  12969. * @private
  12970. */
  12971. Network.prototype._getScriptPath = function() {
  12972. var scripts = document.getElementsByTagName( 'script' );
  12973. // find script named vis.js or vis.min.js
  12974. for (var i = 0; i < scripts.length; i++) {
  12975. var src = scripts[i].src;
  12976. var match = src && /\/?vis(.min)?\.js$/.exec(src);
  12977. if (match) {
  12978. // return path without the script name
  12979. return src.substring(0, src.length - match[0].length);
  12980. }
  12981. }
  12982. return null;
  12983. };
  12984. /**
  12985. * Find the center position of the network
  12986. * @private
  12987. */
  12988. Network.prototype._getRange = function() {
  12989. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  12990. for (var nodeId in this.nodes) {
  12991. if (this.nodes.hasOwnProperty(nodeId)) {
  12992. node = this.nodes[nodeId];
  12993. if (minX > (node.x)) {minX = node.x;}
  12994. if (maxX < (node.x)) {maxX = node.x;}
  12995. if (minY > (node.y)) {minY = node.y;}
  12996. if (maxY < (node.y)) {maxY = node.y;}
  12997. }
  12998. }
  12999. if (minX == 1e9 && maxX == -1e9 && minY == 1e9 && maxY == -1e9) {
  13000. minY = 0, maxY = 0, minX = 0, maxX = 0;
  13001. }
  13002. return {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  13003. };
  13004. /**
  13005. * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  13006. * @returns {{x: number, y: number}}
  13007. * @private
  13008. */
  13009. Network.prototype._findCenter = function(range) {
  13010. return {x: (0.5 * (range.maxX + range.minX)),
  13011. y: (0.5 * (range.maxY + range.minY))};
  13012. };
  13013. /**
  13014. * center the network
  13015. *
  13016. * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  13017. */
  13018. Network.prototype._centerNetwork = function(range) {
  13019. var center = this._findCenter(range);
  13020. center.x *= this.scale;
  13021. center.y *= this.scale;
  13022. center.x -= 0.5 * this.frame.canvas.clientWidth;
  13023. center.y -= 0.5 * this.frame.canvas.clientHeight;
  13024. this._setTranslation(-center.x,-center.y); // set at 0,0
  13025. };
  13026. /**
  13027. * This function zooms out to fit all data on screen based on amount of nodes
  13028. *
  13029. * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
  13030. * @param {Boolean} [disableStart] | If true, start is not called.
  13031. */
  13032. Network.prototype.zoomExtent = function(initialZoom, disableStart) {
  13033. if (initialZoom === undefined) {
  13034. initialZoom = false;
  13035. }
  13036. if (disableStart === undefined) {
  13037. disableStart = false;
  13038. }
  13039. var range = this._getRange();
  13040. var zoomLevel;
  13041. if (initialZoom == true) {
  13042. var numberOfNodes = this.nodeIndices.length;
  13043. if (this.constants.smoothCurves == true) {
  13044. if (this.constants.clustering.enabled == true &&
  13045. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  13046. 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.
  13047. }
  13048. else {
  13049. zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  13050. }
  13051. }
  13052. else {
  13053. if (this.constants.clustering.enabled == true &&
  13054. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  13055. 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.
  13056. }
  13057. else {
  13058. zoomLevel = 30.5062972 / (numberOfNodes + 19.93597763) + 0.08413486; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  13059. }
  13060. }
  13061. // correct for larger canvasses.
  13062. var factor = Math.min(this.frame.canvas.clientWidth / 600, this.frame.canvas.clientHeight / 600);
  13063. zoomLevel *= factor;
  13064. }
  13065. else {
  13066. var xDistance = (Math.abs(range.minX) + Math.abs(range.maxX)) * 1.1;
  13067. var yDistance = (Math.abs(range.minY) + Math.abs(range.maxY)) * 1.1;
  13068. var xZoomLevel = this.frame.canvas.clientWidth / xDistance;
  13069. var yZoomLevel = this.frame.canvas.clientHeight / yDistance;
  13070. zoomLevel = (xZoomLevel <= yZoomLevel) ? xZoomLevel : yZoomLevel;
  13071. }
  13072. if (zoomLevel > 1.0) {
  13073. zoomLevel = 1.0;
  13074. }
  13075. this._setScale(zoomLevel);
  13076. this._centerNetwork(range);
  13077. if (disableStart == false) {
  13078. this.moving = true;
  13079. this.start();
  13080. }
  13081. };
  13082. /**
  13083. * Update the this.nodeIndices with the most recent node index list
  13084. * @private
  13085. */
  13086. Network.prototype._updateNodeIndexList = function() {
  13087. this._clearNodeIndexList();
  13088. for (var idx in this.nodes) {
  13089. if (this.nodes.hasOwnProperty(idx)) {
  13090. this.nodeIndices.push(idx);
  13091. }
  13092. }
  13093. };
  13094. /**
  13095. * Set nodes and edges, and optionally options as well.
  13096. *
  13097. * @param {Object} data Object containing parameters:
  13098. * {Array | DataSet | DataView} [nodes] Array with nodes
  13099. * {Array | DataSet | DataView} [edges] Array with edges
  13100. * {String} [dot] String containing data in DOT format
  13101. * {String} [gephi] String containing data in gephi JSON format
  13102. * {Options} [options] Object with options
  13103. * @param {Boolean} [disableStart] | optional: disable the calling of the start function.
  13104. */
  13105. Network.prototype.setData = function(data, disableStart) {
  13106. if (disableStart === undefined) {
  13107. disableStart = false;
  13108. }
  13109. if (data && data.dot && (data.nodes || data.edges)) {
  13110. throw new SyntaxError('Data must contain either parameter "dot" or ' +
  13111. ' parameter pair "nodes" and "edges", but not both.');
  13112. }
  13113. // set options
  13114. this.setOptions(data && data.options);
  13115. // set all data
  13116. if (data && data.dot) {
  13117. // parse DOT file
  13118. if(data && data.dot) {
  13119. var dotData = dotparser.DOTToGraph(data.dot);
  13120. this.setData(dotData);
  13121. return;
  13122. }
  13123. }
  13124. else if (data && data.gephi) {
  13125. // parse DOT file
  13126. if(data && data.gephi) {
  13127. var gephiData = gephiParser.parseGephi(data.gephi);
  13128. this.setData(gephiData);
  13129. return;
  13130. }
  13131. }
  13132. else {
  13133. this._setNodes(data && data.nodes);
  13134. this._setEdges(data && data.edges);
  13135. }
  13136. this._putDataInSector();
  13137. if (!disableStart) {
  13138. // find a stable position or start animating to a stable position
  13139. if (this.stabilize) {
  13140. var me = this;
  13141. setTimeout(function() {me._stabilize(); me.start();},0)
  13142. }
  13143. else {
  13144. this.start();
  13145. }
  13146. }
  13147. };
  13148. /**
  13149. * Set options
  13150. * @param {Object} options
  13151. * @param {Boolean} [initializeView] | set zoom and translation to default.
  13152. */
  13153. Network.prototype.setOptions = function (options) {
  13154. if (options) {
  13155. var prop;
  13156. // retrieve parameter values
  13157. if (options.width !== undefined) {this.width = options.width;}
  13158. if (options.height !== undefined) {this.height = options.height;}
  13159. if (options.stabilize !== undefined) {this.stabilize = options.stabilize;}
  13160. if (options.selectable !== undefined) {this.selectable = options.selectable;}
  13161. if (options.freezeForStabilization !== undefined) {this.constants.freezeForStabilization = options.freezeForStabilization;}
  13162. if (options.configurePhysics !== undefined){this.constants.configurePhysics = options.configurePhysics;}
  13163. if (options.stabilizationIterations !== undefined) {this.constants.stabilizationIterations = options.stabilizationIterations;}
  13164. if (options.dragNetwork !== undefined) {this.constants.dragNetwork = options.dragNetwork;}
  13165. if (options.dragNodes !== undefined) {this.constants.dragNodes = options.dragNodes;}
  13166. if (options.zoomable !== undefined) {this.constants.zoomable = options.zoomable;}
  13167. if (options.hover !== undefined) {this.constants.hover = options.hover;}
  13168. if (options.hideEdgesOnDrag !== undefined) {this.constants.hideEdgesOnDrag = options.hideEdgesOnDrag;}
  13169. if (options.hideNodesOnDrag !== undefined) {this.constants.hideNodesOnDrag = options.hideNodesOnDrag;}
  13170. // TODO: deprecated since version 3.0.0. Cleanup some day
  13171. if (options.dragGraph !== undefined) {
  13172. throw new Error('Option dragGraph is renamed to dragNetwork');
  13173. }
  13174. if (options.labels !== undefined) {
  13175. for (prop in options.labels) {
  13176. if (options.labels.hasOwnProperty(prop)) {
  13177. this.constants.labels[prop] = options.labels[prop];
  13178. }
  13179. }
  13180. }
  13181. if (options.onAdd) {
  13182. this.triggerFunctions.add = options.onAdd;
  13183. }
  13184. if (options.onEdit) {
  13185. this.triggerFunctions.edit = options.onEdit;
  13186. }
  13187. if (options.onEditEdge) {
  13188. this.triggerFunctions.editEdge = options.onEditEdge;
  13189. }
  13190. if (options.onConnect) {
  13191. this.triggerFunctions.connect = options.onConnect;
  13192. }
  13193. if (options.onDelete) {
  13194. this.triggerFunctions.del = options.onDelete;
  13195. }
  13196. if (options.physics) {
  13197. if (options.physics.barnesHut) {
  13198. this.constants.physics.barnesHut.enabled = true;
  13199. for (prop in options.physics.barnesHut) {
  13200. if (options.physics.barnesHut.hasOwnProperty(prop)) {
  13201. this.constants.physics.barnesHut[prop] = options.physics.barnesHut[prop];
  13202. }
  13203. }
  13204. }
  13205. if (options.physics.repulsion) {
  13206. this.constants.physics.barnesHut.enabled = false;
  13207. for (prop in options.physics.repulsion) {
  13208. if (options.physics.repulsion.hasOwnProperty(prop)) {
  13209. this.constants.physics.repulsion[prop] = options.physics.repulsion[prop];
  13210. }
  13211. }
  13212. }
  13213. if (options.physics.hierarchicalRepulsion) {
  13214. this.constants.hierarchicalLayout.enabled = true;
  13215. this.constants.physics.hierarchicalRepulsion.enabled = true;
  13216. this.constants.physics.barnesHut.enabled = false;
  13217. for (prop in options.physics.hierarchicalRepulsion) {
  13218. if (options.physics.hierarchicalRepulsion.hasOwnProperty(prop)) {
  13219. this.constants.physics.hierarchicalRepulsion[prop] = options.physics.hierarchicalRepulsion[prop];
  13220. }
  13221. }
  13222. }
  13223. }
  13224. if (options.smoothCurves !== undefined) {
  13225. if (typeof options.smoothCurves == 'boolean') {
  13226. this.constants.smoothCurves.enabled = options.smoothCurves;
  13227. }
  13228. else {
  13229. this.constants.smoothCurves.enabled = true;
  13230. for (prop in options.smoothCurves) {
  13231. if (options.smoothCurves.hasOwnProperty(prop)) {
  13232. this.constants.smoothCurves[prop] = options.smoothCurves[prop];
  13233. }
  13234. }
  13235. }
  13236. }
  13237. if (options.hierarchicalLayout) {
  13238. this.constants.hierarchicalLayout.enabled = true;
  13239. for (prop in options.hierarchicalLayout) {
  13240. if (options.hierarchicalLayout.hasOwnProperty(prop)) {
  13241. this.constants.hierarchicalLayout[prop] = options.hierarchicalLayout[prop];
  13242. }
  13243. }
  13244. }
  13245. else if (options.hierarchicalLayout !== undefined) {
  13246. this.constants.hierarchicalLayout.enabled = false;
  13247. }
  13248. if (options.clustering) {
  13249. this.constants.clustering.enabled = true;
  13250. for (prop in options.clustering) {
  13251. if (options.clustering.hasOwnProperty(prop)) {
  13252. this.constants.clustering[prop] = options.clustering[prop];
  13253. }
  13254. }
  13255. }
  13256. else if (options.clustering !== undefined) {
  13257. this.constants.clustering.enabled = false;
  13258. }
  13259. if (options.navigation) {
  13260. this.constants.navigation.enabled = true;
  13261. for (prop in options.navigation) {
  13262. if (options.navigation.hasOwnProperty(prop)) {
  13263. this.constants.navigation[prop] = options.navigation[prop];
  13264. }
  13265. }
  13266. }
  13267. else if (options.navigation !== undefined) {
  13268. this.constants.navigation.enabled = false;
  13269. }
  13270. if (options.keyboard) {
  13271. this.constants.keyboard.enabled = true;
  13272. for (prop in options.keyboard) {
  13273. if (options.keyboard.hasOwnProperty(prop)) {
  13274. this.constants.keyboard[prop] = options.keyboard[prop];
  13275. }
  13276. }
  13277. }
  13278. else if (options.keyboard !== undefined) {
  13279. this.constants.keyboard.enabled = false;
  13280. }
  13281. if (options.dataManipulation) {
  13282. this.constants.dataManipulation.enabled = true;
  13283. for (prop in options.dataManipulation) {
  13284. if (options.dataManipulation.hasOwnProperty(prop)) {
  13285. this.constants.dataManipulation[prop] = options.dataManipulation[prop];
  13286. }
  13287. }
  13288. this.editMode = this.constants.dataManipulation.initiallyVisible;
  13289. }
  13290. else if (options.dataManipulation !== undefined) {
  13291. this.constants.dataManipulation.enabled = false;
  13292. }
  13293. // TODO: work out these options and document them
  13294. if (options.edges) {
  13295. for (prop in options.edges) {
  13296. if (options.edges.hasOwnProperty(prop)) {
  13297. if (typeof options.edges[prop] != "object") {
  13298. this.constants.edges[prop] = options.edges[prop];
  13299. }
  13300. }
  13301. }
  13302. if (options.edges.color !== undefined) {
  13303. if (util.isString(options.edges.color)) {
  13304. this.constants.edges.color = {};
  13305. this.constants.edges.color.color = options.edges.color;
  13306. this.constants.edges.color.highlight = options.edges.color;
  13307. this.constants.edges.color.hover = options.edges.color;
  13308. }
  13309. else {
  13310. if (options.edges.color.color !== undefined) {this.constants.edges.color.color = options.edges.color.color;}
  13311. if (options.edges.color.highlight !== undefined) {this.constants.edges.color.highlight = options.edges.color.highlight;}
  13312. if (options.edges.color.hover !== undefined) {this.constants.edges.color.hover = options.edges.color.hover;}
  13313. }
  13314. }
  13315. if (!options.edges.fontColor) {
  13316. if (options.edges.color !== undefined) {
  13317. if (util.isString(options.edges.color)) {this.constants.edges.fontColor = options.edges.color;}
  13318. else if (options.edges.color.color !== undefined) {this.constants.edges.fontColor = options.edges.color.color;}
  13319. }
  13320. }
  13321. // Added to support dashed lines
  13322. // David Jordan
  13323. // 2012-08-08
  13324. if (options.edges.dash) {
  13325. if (options.edges.dash.length !== undefined) {
  13326. this.constants.edges.dash.length = options.edges.dash.length;
  13327. }
  13328. if (options.edges.dash.gap !== undefined) {
  13329. this.constants.edges.dash.gap = options.edges.dash.gap;
  13330. }
  13331. if (options.edges.dash.altLength !== undefined) {
  13332. this.constants.edges.dash.altLength = options.edges.dash.altLength;
  13333. }
  13334. }
  13335. }
  13336. if (options.nodes) {
  13337. for (prop in options.nodes) {
  13338. if (options.nodes.hasOwnProperty(prop)) {
  13339. this.constants.nodes[prop] = options.nodes[prop];
  13340. }
  13341. }
  13342. if (options.nodes.color) {
  13343. this.constants.nodes.color = util.parseColor(options.nodes.color);
  13344. }
  13345. /*
  13346. if (options.nodes.widthMin) this.constants.nodes.radiusMin = options.nodes.widthMin;
  13347. if (options.nodes.widthMax) this.constants.nodes.radiusMax = options.nodes.widthMax;
  13348. */
  13349. }
  13350. if (options.groups) {
  13351. for (var groupname in options.groups) {
  13352. if (options.groups.hasOwnProperty(groupname)) {
  13353. var group = options.groups[groupname];
  13354. this.groups.add(groupname, group);
  13355. }
  13356. }
  13357. }
  13358. if (options.tooltip) {
  13359. for (prop in options.tooltip) {
  13360. if (options.tooltip.hasOwnProperty(prop)) {
  13361. this.constants.tooltip[prop] = options.tooltip[prop];
  13362. }
  13363. }
  13364. if (options.tooltip.color) {
  13365. this.constants.tooltip.color = util.parseColor(options.tooltip.color);
  13366. }
  13367. }
  13368. }
  13369. // (Re)loading the mixins that can be enabled or disabled in the options.
  13370. // load the force calculation functions, grouped under the physics system.
  13371. this._loadPhysicsSystem();
  13372. // load the navigation system.
  13373. this._loadNavigationControls();
  13374. // load the data manipulation system
  13375. this._loadManipulationSystem();
  13376. // configure the smooth curves
  13377. this._configureSmoothCurves();
  13378. // bind keys. If disabled, this will not do anything;
  13379. this._createKeyBinds();
  13380. this.setSize(this.width, this.height);
  13381. this.moving = true;
  13382. this.start();
  13383. };
  13384. /**
  13385. * Create the main frame for the Network.
  13386. * This function is executed once when a Network object is created. The frame
  13387. * contains a canvas, and this canvas contains all objects like the axis and
  13388. * nodes.
  13389. * @private
  13390. */
  13391. Network.prototype._create = function () {
  13392. // remove all elements from the container element.
  13393. while (this.containerElement.hasChildNodes()) {
  13394. this.containerElement.removeChild(this.containerElement.firstChild);
  13395. }
  13396. this.frame = document.createElement('div');
  13397. this.frame.className = 'network-frame';
  13398. this.frame.style.position = 'relative';
  13399. this.frame.style.overflow = 'hidden';
  13400. // create the network canvas (HTML canvas element)
  13401. this.frame.canvas = document.createElement( 'canvas' );
  13402. this.frame.canvas.style.position = 'relative';
  13403. this.frame.appendChild(this.frame.canvas);
  13404. if (!this.frame.canvas.getContext) {
  13405. var noCanvas = document.createElement( 'DIV' );
  13406. noCanvas.style.color = 'red';
  13407. noCanvas.style.fontWeight = 'bold' ;
  13408. noCanvas.style.padding = '10px';
  13409. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  13410. this.frame.canvas.appendChild(noCanvas);
  13411. }
  13412. var me = this;
  13413. this.drag = {};
  13414. this.pinch = {};
  13415. this.hammer = Hammer(this.frame.canvas, {
  13416. prevent_default: true
  13417. });
  13418. this.hammer.on('tap', me._onTap.bind(me) );
  13419. this.hammer.on('doubletap', me._onDoubleTap.bind(me) );
  13420. this.hammer.on('hold', me._onHold.bind(me) );
  13421. this.hammer.on('pinch', me._onPinch.bind(me) );
  13422. this.hammer.on('touch', me._onTouch.bind(me) );
  13423. this.hammer.on('dragstart', me._onDragStart.bind(me) );
  13424. this.hammer.on('drag', me._onDrag.bind(me) );
  13425. this.hammer.on('dragend', me._onDragEnd.bind(me) );
  13426. this.hammer.on('release', me._onRelease.bind(me) );
  13427. this.hammer.on('mousewheel',me._onMouseWheel.bind(me) );
  13428. this.hammer.on('DOMMouseScroll',me._onMouseWheel.bind(me) ); // for FF
  13429. this.hammer.on('mousemove', me._onMouseMoveTitle.bind(me) );
  13430. // add the frame to the container element
  13431. this.containerElement.appendChild(this.frame);
  13432. };
  13433. /**
  13434. * Binding the keys for keyboard navigation. These functions are defined in the NavigationMixin
  13435. * @private
  13436. */
  13437. Network.prototype._createKeyBinds = function() {
  13438. var me = this;
  13439. this.mousetrap = mousetrap;
  13440. this.mousetrap.reset();
  13441. if (this.constants.keyboard.enabled == true) {
  13442. this.mousetrap.bind("up", this._moveUp.bind(me) , "keydown");
  13443. this.mousetrap.bind("up", this._yStopMoving.bind(me), "keyup");
  13444. this.mousetrap.bind("down", this._moveDown.bind(me) , "keydown");
  13445. this.mousetrap.bind("down", this._yStopMoving.bind(me), "keyup");
  13446. this.mousetrap.bind("left", this._moveLeft.bind(me) , "keydown");
  13447. this.mousetrap.bind("left", this._xStopMoving.bind(me), "keyup");
  13448. this.mousetrap.bind("right",this._moveRight.bind(me), "keydown");
  13449. this.mousetrap.bind("right",this._xStopMoving.bind(me), "keyup");
  13450. this.mousetrap.bind("=", this._zoomIn.bind(me), "keydown");
  13451. this.mousetrap.bind("=", this._stopZoom.bind(me), "keyup");
  13452. this.mousetrap.bind("-", this._zoomOut.bind(me), "keydown");
  13453. this.mousetrap.bind("-", this._stopZoom.bind(me), "keyup");
  13454. this.mousetrap.bind("[", this._zoomIn.bind(me), "keydown");
  13455. this.mousetrap.bind("[", this._stopZoom.bind(me), "keyup");
  13456. this.mousetrap.bind("]", this._zoomOut.bind(me), "keydown");
  13457. this.mousetrap.bind("]", this._stopZoom.bind(me), "keyup");
  13458. this.mousetrap.bind("pageup",this._zoomIn.bind(me), "keydown");
  13459. this.mousetrap.bind("pageup",this._stopZoom.bind(me), "keyup");
  13460. this.mousetrap.bind("pagedown",this._zoomOut.bind(me),"keydown");
  13461. this.mousetrap.bind("pagedown",this._stopZoom.bind(me), "keyup");
  13462. }
  13463. if (this.constants.dataManipulation.enabled == true) {
  13464. this.mousetrap.bind("escape",this._createManipulatorBar.bind(me));
  13465. this.mousetrap.bind("del",this._deleteSelected.bind(me));
  13466. }
  13467. };
  13468. /**
  13469. * Get the pointer location from a touch location
  13470. * @param {{pageX: Number, pageY: Number}} touch
  13471. * @return {{x: Number, y: Number}} pointer
  13472. * @private
  13473. */
  13474. Network.prototype._getPointer = function (touch) {
  13475. return {
  13476. x: touch.pageX - util.getAbsoluteLeft(this.frame.canvas),
  13477. y: touch.pageY - util.getAbsoluteTop(this.frame.canvas)
  13478. };
  13479. };
  13480. /**
  13481. * On start of a touch gesture, store the pointer
  13482. * @param event
  13483. * @private
  13484. */
  13485. Network.prototype._onTouch = function (event) {
  13486. this.drag.pointer = this._getPointer(event.gesture.center);
  13487. this.drag.pinched = false;
  13488. this.pinch.scale = this._getScale();
  13489. this._handleTouch(this.drag.pointer);
  13490. };
  13491. /**
  13492. * handle drag start event
  13493. * @private
  13494. */
  13495. Network.prototype._onDragStart = function () {
  13496. this._handleDragStart();
  13497. };
  13498. /**
  13499. * This function is called by _onDragStart.
  13500. * It is separated out because we can then overload it for the datamanipulation system.
  13501. *
  13502. * @private
  13503. */
  13504. Network.prototype._handleDragStart = function() {
  13505. var drag = this.drag;
  13506. var node = this._getNodeAt(drag.pointer);
  13507. // note: drag.pointer is set in _onTouch to get the initial touch location
  13508. drag.dragging = true;
  13509. drag.selection = [];
  13510. drag.translation = this._getTranslation();
  13511. drag.nodeId = null;
  13512. if (node != null) {
  13513. drag.nodeId = node.id;
  13514. // select the clicked node if not yet selected
  13515. if (!node.isSelected()) {
  13516. this._selectObject(node,false);
  13517. }
  13518. // create an array with the selected nodes and their original location and status
  13519. for (var objectId in this.selectionObj.nodes) {
  13520. if (this.selectionObj.nodes.hasOwnProperty(objectId)) {
  13521. var object = this.selectionObj.nodes[objectId];
  13522. var s = {
  13523. id: object.id,
  13524. node: object,
  13525. // store original x, y, xFixed and yFixed, make the node temporarily Fixed
  13526. x: object.x,
  13527. y: object.y,
  13528. xFixed: object.xFixed,
  13529. yFixed: object.yFixed
  13530. };
  13531. object.xFixed = true;
  13532. object.yFixed = true;
  13533. drag.selection.push(s);
  13534. }
  13535. }
  13536. }
  13537. };
  13538. /**
  13539. * handle drag event
  13540. * @private
  13541. */
  13542. Network.prototype._onDrag = function (event) {
  13543. this._handleOnDrag(event)
  13544. };
  13545. /**
  13546. * This function is called by _onDrag.
  13547. * It is separated out because we can then overload it for the datamanipulation system.
  13548. *
  13549. * @private
  13550. */
  13551. Network.prototype._handleOnDrag = function(event) {
  13552. if (this.drag.pinched) {
  13553. return;
  13554. }
  13555. var pointer = this._getPointer(event.gesture.center);
  13556. var me = this;
  13557. var drag = this.drag;
  13558. var selection = drag.selection;
  13559. if (selection && selection.length && this.constants.dragNodes == true) {
  13560. // calculate delta's and new location
  13561. var deltaX = pointer.x - drag.pointer.x;
  13562. var deltaY = pointer.y - drag.pointer.y;
  13563. // update position of all selected nodes
  13564. selection.forEach(function (s) {
  13565. var node = s.node;
  13566. if (!s.xFixed) {
  13567. node.x = me._XconvertDOMtoCanvas(me._XconvertCanvasToDOM(s.x) + deltaX);
  13568. }
  13569. if (!s.yFixed) {
  13570. node.y = me._YconvertDOMtoCanvas(me._YconvertCanvasToDOM(s.y) + deltaY);
  13571. }
  13572. });
  13573. // start _animationStep if not yet running
  13574. if (!this.moving) {
  13575. this.moving = true;
  13576. this.start();
  13577. }
  13578. }
  13579. else {
  13580. if (this.constants.dragNetwork == true) {
  13581. // move the network
  13582. var diffX = pointer.x - this.drag.pointer.x;
  13583. var diffY = pointer.y - this.drag.pointer.y;
  13584. this._setTranslation(
  13585. this.drag.translation.x + diffX,
  13586. this.drag.translation.y + diffY
  13587. );
  13588. this._redraw();
  13589. // this.moving = true;
  13590. // this.start();
  13591. }
  13592. }
  13593. };
  13594. /**
  13595. * handle drag start event
  13596. * @private
  13597. */
  13598. Network.prototype._onDragEnd = function () {
  13599. this.drag.dragging = false;
  13600. var selection = this.drag.selection;
  13601. if (selection && selection.length) {
  13602. selection.forEach(function (s) {
  13603. // restore original xFixed and yFixed
  13604. s.node.xFixed = s.xFixed;
  13605. s.node.yFixed = s.yFixed;
  13606. });
  13607. this.moving = true;
  13608. this.start();
  13609. }
  13610. else {
  13611. this._redraw();
  13612. }
  13613. };
  13614. /**
  13615. * handle tap/click event: select/unselect a node
  13616. * @private
  13617. */
  13618. Network.prototype._onTap = function (event) {
  13619. var pointer = this._getPointer(event.gesture.center);
  13620. this.pointerPosition = pointer;
  13621. this._handleTap(pointer);
  13622. };
  13623. /**
  13624. * handle doubletap event
  13625. * @private
  13626. */
  13627. Network.prototype._onDoubleTap = function (event) {
  13628. var pointer = this._getPointer(event.gesture.center);
  13629. this._handleDoubleTap(pointer);
  13630. };
  13631. /**
  13632. * handle long tap event: multi select nodes
  13633. * @private
  13634. */
  13635. Network.prototype._onHold = function (event) {
  13636. var pointer = this._getPointer(event.gesture.center);
  13637. this.pointerPosition = pointer;
  13638. this._handleOnHold(pointer);
  13639. };
  13640. /**
  13641. * handle the release of the screen
  13642. *
  13643. * @private
  13644. */
  13645. Network.prototype._onRelease = function (event) {
  13646. var pointer = this._getPointer(event.gesture.center);
  13647. this._handleOnRelease(pointer);
  13648. };
  13649. /**
  13650. * Handle pinch event
  13651. * @param event
  13652. * @private
  13653. */
  13654. Network.prototype._onPinch = function (event) {
  13655. var pointer = this._getPointer(event.gesture.center);
  13656. this.drag.pinched = true;
  13657. if (!('scale' in this.pinch)) {
  13658. this.pinch.scale = 1;
  13659. }
  13660. // TODO: enabled moving while pinching?
  13661. var scale = this.pinch.scale * event.gesture.scale;
  13662. this._zoom(scale, pointer)
  13663. };
  13664. /**
  13665. * Zoom the network in or out
  13666. * @param {Number} scale a number around 1, and between 0.01 and 10
  13667. * @param {{x: Number, y: Number}} pointer Position on screen
  13668. * @return {Number} appliedScale scale is limited within the boundaries
  13669. * @private
  13670. */
  13671. Network.prototype._zoom = function(scale, pointer) {
  13672. if (this.constants.zoomable == true) {
  13673. var scaleOld = this._getScale();
  13674. if (scale < 0.00001) {
  13675. scale = 0.00001;
  13676. }
  13677. if (scale > 10) {
  13678. scale = 10;
  13679. }
  13680. var preScaleDragPointer = null;
  13681. if (this.drag !== undefined) {
  13682. if (this.drag.dragging == true) {
  13683. preScaleDragPointer = this.DOMtoCanvas(this.drag.pointer);
  13684. }
  13685. }
  13686. // + this.frame.canvas.clientHeight / 2
  13687. var translation = this._getTranslation();
  13688. var scaleFrac = scale / scaleOld;
  13689. var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
  13690. var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
  13691. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  13692. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  13693. this._setScale(scale);
  13694. this._setTranslation(tx, ty);
  13695. this.updateClustersDefault();
  13696. if (preScaleDragPointer != null) {
  13697. var postScaleDragPointer = this.canvasToDOM(preScaleDragPointer);
  13698. this.drag.pointer.x = postScaleDragPointer.x;
  13699. this.drag.pointer.y = postScaleDragPointer.y;
  13700. }
  13701. this._redraw();
  13702. if (scaleOld < scale) {
  13703. this.emit("zoom", {direction:"+"});
  13704. }
  13705. else {
  13706. this.emit("zoom", {direction:"-"});
  13707. }
  13708. return scale;
  13709. }
  13710. };
  13711. /**
  13712. * Event handler for mouse wheel event, used to zoom the timeline
  13713. * See http://adomas.org/javascript-mouse-wheel/
  13714. * https://github.com/EightMedia/hammer.js/issues/256
  13715. * @param {MouseEvent} event
  13716. * @private
  13717. */
  13718. Network.prototype._onMouseWheel = function(event) {
  13719. // retrieve delta
  13720. var delta = 0;
  13721. if (event.wheelDelta) { /* IE/Opera. */
  13722. delta = event.wheelDelta/120;
  13723. } else if (event.detail) { /* Mozilla case. */
  13724. // In Mozilla, sign of delta is different than in IE.
  13725. // Also, delta is multiple of 3.
  13726. delta = -event.detail/3;
  13727. }
  13728. // If delta is nonzero, handle it.
  13729. // Basically, delta is now positive if wheel was scrolled up,
  13730. // and negative, if wheel was scrolled down.
  13731. if (delta) {
  13732. // calculate the new scale
  13733. var scale = this._getScale();
  13734. var zoom = delta / 10;
  13735. if (delta < 0) {
  13736. zoom = zoom / (1 - zoom);
  13737. }
  13738. scale *= (1 + zoom);
  13739. // calculate the pointer location
  13740. var gesture = hammerUtil.fakeGesture(this, event);
  13741. var pointer = this._getPointer(gesture.center);
  13742. // apply the new scale
  13743. this._zoom(scale, pointer);
  13744. }
  13745. // Prevent default actions caused by mouse wheel.
  13746. event.preventDefault();
  13747. };
  13748. /**
  13749. * Mouse move handler for checking whether the title moves over a node with a title.
  13750. * @param {Event} event
  13751. * @private
  13752. */
  13753. Network.prototype._onMouseMoveTitle = function (event) {
  13754. var gesture = hammerUtil.fakeGesture(this, event);
  13755. var pointer = this._getPointer(gesture.center);
  13756. // check if the previously selected node is still selected
  13757. if (this.popupObj) {
  13758. this._checkHidePopup(pointer);
  13759. }
  13760. // start a timeout that will check if the mouse is positioned above
  13761. // an element
  13762. var me = this;
  13763. var checkShow = function() {
  13764. me._checkShowPopup(pointer);
  13765. };
  13766. if (this.popupTimer) {
  13767. clearInterval(this.popupTimer); // stop any running calculationTimer
  13768. }
  13769. if (!this.drag.dragging) {
  13770. this.popupTimer = setTimeout(checkShow, this.constants.tooltip.delay);
  13771. }
  13772. /**
  13773. * Adding hover highlights
  13774. */
  13775. if (this.constants.hover == true) {
  13776. // removing all hover highlights
  13777. for (var edgeId in this.hoverObj.edges) {
  13778. if (this.hoverObj.edges.hasOwnProperty(edgeId)) {
  13779. this.hoverObj.edges[edgeId].hover = false;
  13780. delete this.hoverObj.edges[edgeId];
  13781. }
  13782. }
  13783. // adding hover highlights
  13784. var obj = this._getNodeAt(pointer);
  13785. if (obj == null) {
  13786. obj = this._getEdgeAt(pointer);
  13787. }
  13788. if (obj != null) {
  13789. this._hoverObject(obj);
  13790. }
  13791. // removing all node hover highlights except for the selected one.
  13792. for (var nodeId in this.hoverObj.nodes) {
  13793. if (this.hoverObj.nodes.hasOwnProperty(nodeId)) {
  13794. if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj == null) {
  13795. this._blurObject(this.hoverObj.nodes[nodeId]);
  13796. delete this.hoverObj.nodes[nodeId];
  13797. }
  13798. }
  13799. }
  13800. this.redraw();
  13801. }
  13802. };
  13803. /**
  13804. * Check if there is an element on the given position in the network
  13805. * (a node or edge). If so, and if this element has a title,
  13806. * show a popup window with its title.
  13807. *
  13808. * @param {{x:Number, y:Number}} pointer
  13809. * @private
  13810. */
  13811. Network.prototype._checkShowPopup = function (pointer) {
  13812. var obj = {
  13813. left: this._XconvertDOMtoCanvas(pointer.x),
  13814. top: this._YconvertDOMtoCanvas(pointer.y),
  13815. right: this._XconvertDOMtoCanvas(pointer.x),
  13816. bottom: this._YconvertDOMtoCanvas(pointer.y)
  13817. };
  13818. var id;
  13819. var lastPopupNode = this.popupObj;
  13820. if (this.popupObj == undefined) {
  13821. // search the nodes for overlap, select the top one in case of multiple nodes
  13822. var nodes = this.nodes;
  13823. for (id in nodes) {
  13824. if (nodes.hasOwnProperty(id)) {
  13825. var node = nodes[id];
  13826. if (node.getTitle() !== undefined && node.isOverlappingWith(obj)) {
  13827. this.popupObj = node;
  13828. break;
  13829. }
  13830. }
  13831. }
  13832. }
  13833. if (this.popupObj === undefined) {
  13834. // search the edges for overlap
  13835. var edges = this.edges;
  13836. for (id in edges) {
  13837. if (edges.hasOwnProperty(id)) {
  13838. var edge = edges[id];
  13839. if (edge.connected && (edge.getTitle() !== undefined) &&
  13840. edge.isOverlappingWith(obj)) {
  13841. this.popupObj = edge;
  13842. break;
  13843. }
  13844. }
  13845. }
  13846. }
  13847. if (this.popupObj) {
  13848. // show popup message window
  13849. if (this.popupObj != lastPopupNode) {
  13850. var me = this;
  13851. if (!me.popup) {
  13852. me.popup = new Popup(me.frame, me.constants.tooltip);
  13853. }
  13854. // adjust a small offset such that the mouse cursor is located in the
  13855. // bottom left location of the popup, and you can easily move over the
  13856. // popup area
  13857. me.popup.setPosition(pointer.x - 3, pointer.y - 3);
  13858. me.popup.setText(me.popupObj.getTitle());
  13859. me.popup.show();
  13860. }
  13861. }
  13862. else {
  13863. if (this.popup) {
  13864. this.popup.hide();
  13865. }
  13866. }
  13867. };
  13868. /**
  13869. * Check if the popup must be hided, which is the case when the mouse is no
  13870. * longer hovering on the object
  13871. * @param {{x:Number, y:Number}} pointer
  13872. * @private
  13873. */
  13874. Network.prototype._checkHidePopup = function (pointer) {
  13875. if (!this.popupObj || !this._getNodeAt(pointer) ) {
  13876. this.popupObj = undefined;
  13877. if (this.popup) {
  13878. this.popup.hide();
  13879. }
  13880. }
  13881. };
  13882. /**
  13883. * Set a new size for the network
  13884. * @param {string} width Width in pixels or percentage (for example '800px'
  13885. * or '50%')
  13886. * @param {string} height Height in pixels or percentage (for example '400px'
  13887. * or '30%')
  13888. */
  13889. Network.prototype.setSize = function(width, height) {
  13890. this.frame.style.width = width;
  13891. this.frame.style.height = height;
  13892. this.frame.canvas.style.width = '100%';
  13893. this.frame.canvas.style.height = '100%';
  13894. this.frame.canvas.width = this.frame.canvas.clientWidth;
  13895. this.frame.canvas.height = this.frame.canvas.clientHeight;
  13896. if (this.manipulationDiv !== undefined) {
  13897. this.manipulationDiv.style.width = this.frame.canvas.clientWidth + "px";
  13898. }
  13899. if (this.navigationDivs !== undefined) {
  13900. if (this.navigationDivs['wrapper'] !== undefined) {
  13901. this.navigationDivs['wrapper'].style.width = this.frame.canvas.clientWidth + "px";
  13902. this.navigationDivs['wrapper'].style.height = this.frame.canvas.clientHeight + "px";
  13903. }
  13904. }
  13905. this.emit('resize', {width:this.frame.canvas.width,height:this.frame.canvas.height});
  13906. };
  13907. /**
  13908. * Set a data set with nodes for the network
  13909. * @param {Array | DataSet | DataView} nodes The data containing the nodes.
  13910. * @private
  13911. */
  13912. Network.prototype._setNodes = function(nodes) {
  13913. var oldNodesData = this.nodesData;
  13914. if (nodes instanceof DataSet || nodes instanceof DataView) {
  13915. this.nodesData = nodes;
  13916. }
  13917. else if (nodes instanceof Array) {
  13918. this.nodesData = new DataSet();
  13919. this.nodesData.add(nodes);
  13920. }
  13921. else if (!nodes) {
  13922. this.nodesData = new DataSet();
  13923. }
  13924. else {
  13925. throw new TypeError('Array or DataSet expected');
  13926. }
  13927. if (oldNodesData) {
  13928. // unsubscribe from old dataset
  13929. util.forEach(this.nodesListeners, function (callback, event) {
  13930. oldNodesData.off(event, callback);
  13931. });
  13932. }
  13933. // remove drawn nodes
  13934. this.nodes = {};
  13935. if (this.nodesData) {
  13936. // subscribe to new dataset
  13937. var me = this;
  13938. util.forEach(this.nodesListeners, function (callback, event) {
  13939. me.nodesData.on(event, callback);
  13940. });
  13941. // draw all new nodes
  13942. var ids = this.nodesData.getIds();
  13943. this._addNodes(ids);
  13944. }
  13945. this._updateSelection();
  13946. };
  13947. /**
  13948. * Add nodes
  13949. * @param {Number[] | String[]} ids
  13950. * @private
  13951. */
  13952. Network.prototype._addNodes = function(ids) {
  13953. var id;
  13954. for (var i = 0, len = ids.length; i < len; i++) {
  13955. id = ids[i];
  13956. var data = this.nodesData.get(id);
  13957. var node = new Node(data, this.images, this.groups, this.constants);
  13958. this.nodes[id] = node; // note: this may replace an existing node
  13959. if ((node.xFixed == false || node.yFixed == false) && (node.x === null || node.y === null)) {
  13960. var radius = 10 * 0.1*ids.length;
  13961. var angle = 2 * Math.PI * Math.random();
  13962. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  13963. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  13964. }
  13965. this.moving = true;
  13966. }
  13967. this._updateNodeIndexList();
  13968. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  13969. this._resetLevels();
  13970. this._setupHierarchicalLayout();
  13971. }
  13972. this._updateCalculationNodes();
  13973. this._reconnectEdges();
  13974. this._updateValueRange(this.nodes);
  13975. this.updateLabels();
  13976. };
  13977. /**
  13978. * Update existing nodes, or create them when not yet existing
  13979. * @param {Number[] | String[]} ids
  13980. * @private
  13981. */
  13982. Network.prototype._updateNodes = function(ids) {
  13983. var nodes = this.nodes,
  13984. nodesData = this.nodesData;
  13985. for (var i = 0, len = ids.length; i < len; i++) {
  13986. var id = ids[i];
  13987. var node = nodes[id];
  13988. var data = nodesData.get(id);
  13989. if (node) {
  13990. // update node
  13991. node.setProperties(data, this.constants);
  13992. }
  13993. else {
  13994. // create node
  13995. node = new Node(properties, this.images, this.groups, this.constants);
  13996. nodes[id] = node;
  13997. }
  13998. }
  13999. this.moving = true;
  14000. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  14001. this._resetLevels();
  14002. this._setupHierarchicalLayout();
  14003. }
  14004. this._updateNodeIndexList();
  14005. this._reconnectEdges();
  14006. this._updateValueRange(nodes);
  14007. };
  14008. /**
  14009. * Remove existing nodes. If nodes do not exist, the method will just ignore it.
  14010. * @param {Number[] | String[]} ids
  14011. * @private
  14012. */
  14013. Network.prototype._removeNodes = function(ids) {
  14014. var nodes = this.nodes;
  14015. for (var i = 0, len = ids.length; i < len; i++) {
  14016. var id = ids[i];
  14017. delete nodes[id];
  14018. }
  14019. this._updateNodeIndexList();
  14020. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  14021. this._resetLevels();
  14022. this._setupHierarchicalLayout();
  14023. }
  14024. this._updateCalculationNodes();
  14025. this._reconnectEdges();
  14026. this._updateSelection();
  14027. this._updateValueRange(nodes);
  14028. };
  14029. /**
  14030. * Load edges by reading the data table
  14031. * @param {Array | DataSet | DataView} edges The data containing the edges.
  14032. * @private
  14033. * @private
  14034. */
  14035. Network.prototype._setEdges = function(edges) {
  14036. var oldEdgesData = this.edgesData;
  14037. if (edges instanceof DataSet || edges instanceof DataView) {
  14038. this.edgesData = edges;
  14039. }
  14040. else if (edges instanceof Array) {
  14041. this.edgesData = new DataSet();
  14042. this.edgesData.add(edges);
  14043. }
  14044. else if (!edges) {
  14045. this.edgesData = new DataSet();
  14046. }
  14047. else {
  14048. throw new TypeError('Array or DataSet expected');
  14049. }
  14050. if (oldEdgesData) {
  14051. // unsubscribe from old dataset
  14052. util.forEach(this.edgesListeners, function (callback, event) {
  14053. oldEdgesData.off(event, callback);
  14054. });
  14055. }
  14056. // remove drawn edges
  14057. this.edges = {};
  14058. if (this.edgesData) {
  14059. // subscribe to new dataset
  14060. var me = this;
  14061. util.forEach(this.edgesListeners, function (callback, event) {
  14062. me.edgesData.on(event, callback);
  14063. });
  14064. // draw all new nodes
  14065. var ids = this.edgesData.getIds();
  14066. this._addEdges(ids);
  14067. }
  14068. this._reconnectEdges();
  14069. };
  14070. /**
  14071. * Add edges
  14072. * @param {Number[] | String[]} ids
  14073. * @private
  14074. */
  14075. Network.prototype._addEdges = function (ids) {
  14076. var edges = this.edges,
  14077. edgesData = this.edgesData;
  14078. for (var i = 0, len = ids.length; i < len; i++) {
  14079. var id = ids[i];
  14080. var oldEdge = edges[id];
  14081. if (oldEdge) {
  14082. oldEdge.disconnect();
  14083. }
  14084. var data = edgesData.get(id, {"showInternalIds" : true});
  14085. edges[id] = new Edge(data, this, this.constants);
  14086. }
  14087. this.moving = true;
  14088. this._updateValueRange(edges);
  14089. this._createBezierNodes();
  14090. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  14091. this._resetLevels();
  14092. this._setupHierarchicalLayout();
  14093. }
  14094. this._updateCalculationNodes();
  14095. };
  14096. /**
  14097. * Update existing edges, or create them when not yet existing
  14098. * @param {Number[] | String[]} ids
  14099. * @private
  14100. */
  14101. Network.prototype._updateEdges = function (ids) {
  14102. var edges = this.edges,
  14103. edgesData = this.edgesData;
  14104. for (var i = 0, len = ids.length; i < len; i++) {
  14105. var id = ids[i];
  14106. var data = edgesData.get(id);
  14107. var edge = edges[id];
  14108. if (edge) {
  14109. // update edge
  14110. edge.disconnect();
  14111. edge.setProperties(data, this.constants);
  14112. edge.connect();
  14113. }
  14114. else {
  14115. // create edge
  14116. edge = new Edge(data, this, this.constants);
  14117. this.edges[id] = edge;
  14118. }
  14119. }
  14120. this._createBezierNodes();
  14121. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  14122. this._resetLevels();
  14123. this._setupHierarchicalLayout();
  14124. }
  14125. this.moving = true;
  14126. this._updateValueRange(edges);
  14127. };
  14128. /**
  14129. * Remove existing edges. Non existing ids will be ignored
  14130. * @param {Number[] | String[]} ids
  14131. * @private
  14132. */
  14133. Network.prototype._removeEdges = function (ids) {
  14134. var edges = this.edges;
  14135. for (var i = 0, len = ids.length; i < len; i++) {
  14136. var id = ids[i];
  14137. var edge = edges[id];
  14138. if (edge) {
  14139. if (edge.via != null) {
  14140. delete this.sectors['support']['nodes'][edge.via.id];
  14141. }
  14142. edge.disconnect();
  14143. delete edges[id];
  14144. }
  14145. }
  14146. this.moving = true;
  14147. this._updateValueRange(edges);
  14148. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  14149. this._resetLevels();
  14150. this._setupHierarchicalLayout();
  14151. }
  14152. this._updateCalculationNodes();
  14153. };
  14154. /**
  14155. * Reconnect all edges
  14156. * @private
  14157. */
  14158. Network.prototype._reconnectEdges = function() {
  14159. var id,
  14160. nodes = this.nodes,
  14161. edges = this.edges;
  14162. for (id in nodes) {
  14163. if (nodes.hasOwnProperty(id)) {
  14164. nodes[id].edges = [];
  14165. }
  14166. }
  14167. for (id in edges) {
  14168. if (edges.hasOwnProperty(id)) {
  14169. var edge = edges[id];
  14170. edge.from = null;
  14171. edge.to = null;
  14172. edge.connect();
  14173. }
  14174. }
  14175. };
  14176. /**
  14177. * Update the values of all object in the given array according to the current
  14178. * value range of the objects in the array.
  14179. * @param {Object} obj An object containing a set of Edges or Nodes
  14180. * The objects must have a method getValue() and
  14181. * setValueRange(min, max).
  14182. * @private
  14183. */
  14184. Network.prototype._updateValueRange = function(obj) {
  14185. var id;
  14186. // determine the range of the objects
  14187. var valueMin = undefined;
  14188. var valueMax = undefined;
  14189. for (id in obj) {
  14190. if (obj.hasOwnProperty(id)) {
  14191. var value = obj[id].getValue();
  14192. if (value !== undefined) {
  14193. valueMin = (valueMin === undefined) ? value : Math.min(value, valueMin);
  14194. valueMax = (valueMax === undefined) ? value : Math.max(value, valueMax);
  14195. }
  14196. }
  14197. }
  14198. // adjust the range of all objects
  14199. if (valueMin !== undefined && valueMax !== undefined) {
  14200. for (id in obj) {
  14201. if (obj.hasOwnProperty(id)) {
  14202. obj[id].setValueRange(valueMin, valueMax);
  14203. }
  14204. }
  14205. }
  14206. };
  14207. /**
  14208. * Redraw the network with the current data
  14209. * chart will be resized too.
  14210. */
  14211. Network.prototype.redraw = function() {
  14212. this.setSize(this.width, this.height);
  14213. this._redraw();
  14214. };
  14215. /**
  14216. * Redraw the network with the current data
  14217. * @private
  14218. */
  14219. Network.prototype._redraw = function() {
  14220. var ctx = this.frame.canvas.getContext('2d');
  14221. // clear the canvas
  14222. var w = this.frame.canvas.width;
  14223. var h = this.frame.canvas.height;
  14224. ctx.clearRect(0, 0, w, h);
  14225. // set scaling and translation
  14226. ctx.save();
  14227. ctx.translate(this.translation.x, this.translation.y);
  14228. ctx.scale(this.scale, this.scale);
  14229. this.canvasTopLeft = {
  14230. "x": this._XconvertDOMtoCanvas(0),
  14231. "y": this._YconvertDOMtoCanvas(0)
  14232. };
  14233. this.canvasBottomRight = {
  14234. "x": this._XconvertDOMtoCanvas(this.frame.canvas.clientWidth),
  14235. "y": this._YconvertDOMtoCanvas(this.frame.canvas.clientHeight)
  14236. };
  14237. this._doInAllSectors("_drawAllSectorNodes",ctx);
  14238. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideEdgesOnDrag == false) {
  14239. this._doInAllSectors("_drawEdges",ctx);
  14240. }
  14241. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideNodesOnDrag == false) {
  14242. this._doInAllSectors("_drawNodes",ctx,false);
  14243. }
  14244. if (this.controlNodesActive == true) {
  14245. this._doInAllSectors("_drawControlNodes",ctx);
  14246. }
  14247. // this._doInSupportSector("_drawNodes",ctx,true);
  14248. // this._drawTree(ctx,"#F00F0F");
  14249. // restore original scaling and translation
  14250. ctx.restore();
  14251. };
  14252. /**
  14253. * Set the translation of the network
  14254. * @param {Number} offsetX Horizontal offset
  14255. * @param {Number} offsetY Vertical offset
  14256. * @private
  14257. */
  14258. Network.prototype._setTranslation = function(offsetX, offsetY) {
  14259. if (this.translation === undefined) {
  14260. this.translation = {
  14261. x: 0,
  14262. y: 0
  14263. };
  14264. }
  14265. if (offsetX !== undefined) {
  14266. this.translation.x = offsetX;
  14267. }
  14268. if (offsetY !== undefined) {
  14269. this.translation.y = offsetY;
  14270. }
  14271. this.emit('viewChanged');
  14272. };
  14273. /**
  14274. * Get the translation of the network
  14275. * @return {Object} translation An object with parameters x and y, both a number
  14276. * @private
  14277. */
  14278. Network.prototype._getTranslation = function() {
  14279. return {
  14280. x: this.translation.x,
  14281. y: this.translation.y
  14282. };
  14283. };
  14284. /**
  14285. * Scale the network
  14286. * @param {Number} scale Scaling factor 1.0 is unscaled
  14287. * @private
  14288. */
  14289. Network.prototype._setScale = function(scale) {
  14290. this.scale = scale;
  14291. };
  14292. /**
  14293. * Get the current scale of the network
  14294. * @return {Number} scale Scaling factor 1.0 is unscaled
  14295. * @private
  14296. */
  14297. Network.prototype._getScale = function() {
  14298. return this.scale;
  14299. };
  14300. /**
  14301. * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to
  14302. * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  14303. * @param {number} x
  14304. * @returns {number}
  14305. * @private
  14306. */
  14307. Network.prototype._XconvertDOMtoCanvas = function(x) {
  14308. return (x - this.translation.x) / this.scale;
  14309. };
  14310. /**
  14311. * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  14312. * the X coordinate in DOM-space (coordinate point in browser relative to the container div)
  14313. * @param {number} x
  14314. * @returns {number}
  14315. * @private
  14316. */
  14317. Network.prototype._XconvertCanvasToDOM = function(x) {
  14318. return x * this.scale + this.translation.x;
  14319. };
  14320. /**
  14321. * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to
  14322. * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  14323. * @param {number} y
  14324. * @returns {number}
  14325. * @private
  14326. */
  14327. Network.prototype._YconvertDOMtoCanvas = function(y) {
  14328. return (y - this.translation.y) / this.scale;
  14329. };
  14330. /**
  14331. * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  14332. * the Y coordinate in DOM-space (coordinate point in browser relative to the container div)
  14333. * @param {number} y
  14334. * @returns {number}
  14335. * @private
  14336. */
  14337. Network.prototype._YconvertCanvasToDOM = function(y) {
  14338. return y * this.scale + this.translation.y ;
  14339. };
  14340. /**
  14341. *
  14342. * @param {object} pos = {x: number, y: number}
  14343. * @returns {{x: number, y: number}}
  14344. * @constructor
  14345. */
  14346. Network.prototype.canvasToDOM = function(pos) {
  14347. return {x:this._XconvertCanvasToDOM(pos.x),y:this._YconvertCanvasToDOM(pos.y)};
  14348. }
  14349. /**
  14350. *
  14351. * @param {object} pos = {x: number, y: number}
  14352. * @returns {{x: number, y: number}}
  14353. * @constructor
  14354. */
  14355. Network.prototype.DOMtoCanvas = function(pos) {
  14356. return {x:this._XconvertDOMtoCanvas(pos.x),y:this._YconvertDOMtoCanvas(pos.y)};
  14357. }
  14358. /**
  14359. * Redraw all nodes
  14360. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  14361. * @param {CanvasRenderingContext2D} ctx
  14362. * @param {Boolean} [alwaysShow]
  14363. * @private
  14364. */
  14365. Network.prototype._drawNodes = function(ctx,alwaysShow) {
  14366. if (alwaysShow === undefined) {
  14367. alwaysShow = false;
  14368. }
  14369. // first draw the unselected nodes
  14370. var nodes = this.nodes;
  14371. var selected = [];
  14372. for (var id in nodes) {
  14373. if (nodes.hasOwnProperty(id)) {
  14374. nodes[id].setScaleAndPos(this.scale,this.canvasTopLeft,this.canvasBottomRight);
  14375. if (nodes[id].isSelected()) {
  14376. selected.push(id);
  14377. }
  14378. else {
  14379. if (nodes[id].inArea() || alwaysShow) {
  14380. nodes[id].draw(ctx);
  14381. }
  14382. }
  14383. }
  14384. }
  14385. // draw the selected nodes on top
  14386. for (var s = 0, sMax = selected.length; s < sMax; s++) {
  14387. if (nodes[selected[s]].inArea() || alwaysShow) {
  14388. nodes[selected[s]].draw(ctx);
  14389. }
  14390. }
  14391. };
  14392. /**
  14393. * Redraw all edges
  14394. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  14395. * @param {CanvasRenderingContext2D} ctx
  14396. * @private
  14397. */
  14398. Network.prototype._drawEdges = function(ctx) {
  14399. var edges = this.edges;
  14400. for (var id in edges) {
  14401. if (edges.hasOwnProperty(id)) {
  14402. var edge = edges[id];
  14403. edge.setScale(this.scale);
  14404. if (edge.connected) {
  14405. edges[id].draw(ctx);
  14406. }
  14407. }
  14408. }
  14409. };
  14410. /**
  14411. * Redraw all edges
  14412. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  14413. * @param {CanvasRenderingContext2D} ctx
  14414. * @private
  14415. */
  14416. Network.prototype._drawControlNodes = function(ctx) {
  14417. var edges = this.edges;
  14418. for (var id in edges) {
  14419. if (edges.hasOwnProperty(id)) {
  14420. edges[id]._drawControlNodes(ctx);
  14421. }
  14422. }
  14423. };
  14424. /**
  14425. * Find a stable position for all nodes
  14426. * @private
  14427. */
  14428. Network.prototype._stabilize = function() {
  14429. if (this.constants.freezeForStabilization == true) {
  14430. this._freezeDefinedNodes();
  14431. }
  14432. // find stable position
  14433. var count = 0;
  14434. while (this.moving && count < this.constants.stabilizationIterations) {
  14435. this._physicsTick();
  14436. count++;
  14437. }
  14438. this.zoomExtent(false,true);
  14439. if (this.constants.freezeForStabilization == true) {
  14440. this._restoreFrozenNodes();
  14441. }
  14442. this.emit("stabilized",{iterations:count});
  14443. };
  14444. /**
  14445. * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization
  14446. * because only the supportnodes for the smoothCurves have to settle.
  14447. *
  14448. * @private
  14449. */
  14450. Network.prototype._freezeDefinedNodes = function() {
  14451. var nodes = this.nodes;
  14452. for (var id in nodes) {
  14453. if (nodes.hasOwnProperty(id)) {
  14454. if (nodes[id].x != null && nodes[id].y != null) {
  14455. nodes[id].fixedData.x = nodes[id].xFixed;
  14456. nodes[id].fixedData.y = nodes[id].yFixed;
  14457. nodes[id].xFixed = true;
  14458. nodes[id].yFixed = true;
  14459. }
  14460. }
  14461. }
  14462. };
  14463. /**
  14464. * Unfreezes the nodes that have been frozen by _freezeDefinedNodes.
  14465. *
  14466. * @private
  14467. */
  14468. Network.prototype._restoreFrozenNodes = function() {
  14469. var nodes = this.nodes;
  14470. for (var id in nodes) {
  14471. if (nodes.hasOwnProperty(id)) {
  14472. if (nodes[id].fixedData.x != null) {
  14473. nodes[id].xFixed = nodes[id].fixedData.x;
  14474. nodes[id].yFixed = nodes[id].fixedData.y;
  14475. }
  14476. }
  14477. }
  14478. };
  14479. /**
  14480. * Check if any of the nodes is still moving
  14481. * @param {number} vmin the minimum velocity considered as 'moving'
  14482. * @return {boolean} true if moving, false if non of the nodes is moving
  14483. * @private
  14484. */
  14485. Network.prototype._isMoving = function(vmin) {
  14486. var nodes = this.nodes;
  14487. for (var id in nodes) {
  14488. if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vmin)) {
  14489. return true;
  14490. }
  14491. }
  14492. return false;
  14493. };
  14494. /**
  14495. * /**
  14496. * Perform one discrete step for all nodes
  14497. *
  14498. * @private
  14499. */
  14500. Network.prototype._discreteStepNodes = function() {
  14501. var interval = this.physicsDiscreteStepsize;
  14502. var nodes = this.nodes;
  14503. var nodeId;
  14504. var nodesPresent = false;
  14505. if (this.constants.maxVelocity > 0) {
  14506. for (nodeId in nodes) {
  14507. if (nodes.hasOwnProperty(nodeId)) {
  14508. nodes[nodeId].discreteStepLimited(interval, this.constants.maxVelocity);
  14509. nodesPresent = true;
  14510. }
  14511. }
  14512. }
  14513. else {
  14514. for (nodeId in nodes) {
  14515. if (nodes.hasOwnProperty(nodeId)) {
  14516. nodes[nodeId].discreteStep(interval);
  14517. nodesPresent = true;
  14518. }
  14519. }
  14520. }
  14521. if (nodesPresent == true) {
  14522. var vminCorrected = this.constants.minVelocity / Math.max(this.scale,0.05);
  14523. if (vminCorrected > 0.5*this.constants.maxVelocity) {
  14524. this.moving = true;
  14525. }
  14526. else {
  14527. this.moving = this._isMoving(vminCorrected);
  14528. if (this.moving == false) {
  14529. this.emit("stabilized",{iterations:null});
  14530. }
  14531. this.moving = this.moving || this.configurePhysics;
  14532. }
  14533. }
  14534. };
  14535. /**
  14536. * A single simulation step (or "tick") in the physics simulation
  14537. *
  14538. * @private
  14539. */
  14540. Network.prototype._physicsTick = function() {
  14541. if (!this.freezeSimulation) {
  14542. if (this.moving == true) {
  14543. this._doInAllActiveSectors("_initializeForceCalculation");
  14544. this._doInAllActiveSectors("_discreteStepNodes");
  14545. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  14546. this._doInSupportSector("_discreteStepNodes");
  14547. }
  14548. this._findCenter(this._getRange())
  14549. }
  14550. }
  14551. };
  14552. /**
  14553. * This function runs one step of the animation. It calls an x amount of physics ticks and one render tick.
  14554. * It reschedules itself at the beginning of the function
  14555. *
  14556. * @private
  14557. */
  14558. Network.prototype._animationStep = function() {
  14559. // reset the timer so a new scheduled animation step can be set
  14560. this.timer = undefined;
  14561. // handle the keyboad movement
  14562. this._handleNavigation();
  14563. // this schedules a new animation step
  14564. this.start();
  14565. // start the physics simulation
  14566. var calculationTime = Date.now();
  14567. var maxSteps = 1;
  14568. this._physicsTick();
  14569. var timeRequired = Date.now() - calculationTime;
  14570. while (timeRequired < 0.9*(this.renderTimestep - this.renderTime) && maxSteps < this.maxPhysicsTicksPerRender) {
  14571. this._physicsTick();
  14572. timeRequired = Date.now() - calculationTime;
  14573. maxSteps++;
  14574. }
  14575. // start the rendering process
  14576. var renderTime = Date.now();
  14577. this._redraw();
  14578. this.renderTime = Date.now() - renderTime;
  14579. };
  14580. if (typeof window !== 'undefined') {
  14581. window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
  14582. window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  14583. }
  14584. /**
  14585. * Schedule a animation step with the refreshrate interval.
  14586. */
  14587. Network.prototype.start = function() {
  14588. if (this.moving == true || this.xIncrement != 0 || this.yIncrement != 0 || this.zoomIncrement != 0) {
  14589. if (!this.timer) {
  14590. var ua = navigator.userAgent.toLowerCase();
  14591. var requiresTimeout = false;
  14592. if (ua.indexOf('msie 9.0') != -1) { // IE 9
  14593. requiresTimeout = true;
  14594. }
  14595. else if (ua.indexOf('safari') != -1) { // safari
  14596. if (ua.indexOf('chrome') <= -1) {
  14597. requiresTimeout = true;
  14598. }
  14599. }
  14600. if (requiresTimeout == true) {
  14601. this.timer = window.setTimeout(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
  14602. }
  14603. else{
  14604. this.timer = window.requestAnimationFrame(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
  14605. }
  14606. }
  14607. }
  14608. else {
  14609. this._redraw();
  14610. }
  14611. };
  14612. /**
  14613. * Move the network according to the keyboard presses.
  14614. *
  14615. * @private
  14616. */
  14617. Network.prototype._handleNavigation = function() {
  14618. if (this.xIncrement != 0 || this.yIncrement != 0) {
  14619. var translation = this._getTranslation();
  14620. this._setTranslation(translation.x+this.xIncrement, translation.y+this.yIncrement);
  14621. }
  14622. if (this.zoomIncrement != 0) {
  14623. var center = {
  14624. x: this.frame.canvas.clientWidth / 2,
  14625. y: this.frame.canvas.clientHeight / 2
  14626. };
  14627. this._zoom(this.scale*(1 + this.zoomIncrement), center);
  14628. }
  14629. };
  14630. /**
  14631. * Freeze the _animationStep
  14632. */
  14633. Network.prototype.toggleFreeze = function() {
  14634. if (this.freezeSimulation == false) {
  14635. this.freezeSimulation = true;
  14636. }
  14637. else {
  14638. this.freezeSimulation = false;
  14639. this.start();
  14640. }
  14641. };
  14642. /**
  14643. * This function cleans the support nodes if they are not needed and adds them when they are.
  14644. *
  14645. * @param {boolean} [disableStart]
  14646. * @private
  14647. */
  14648. Network.prototype._configureSmoothCurves = function(disableStart) {
  14649. if (disableStart === undefined) {
  14650. disableStart = true;
  14651. }
  14652. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  14653. this._createBezierNodes();
  14654. // cleanup unused support nodes
  14655. for (var nodeId in this.sectors['support']['nodes']) {
  14656. if (this.sectors['support']['nodes'].hasOwnProperty(nodeId)) {
  14657. if (this.edges[this.sectors['support']['nodes'][nodeId]] === undefined) {
  14658. delete this.sectors['support']['nodes'][nodeId];
  14659. }
  14660. }
  14661. }
  14662. }
  14663. else {
  14664. // delete the support nodes
  14665. this.sectors['support']['nodes'] = {};
  14666. for (var edgeId in this.edges) {
  14667. if (this.edges.hasOwnProperty(edgeId)) {
  14668. this.edges[edgeId].smooth = false;
  14669. this.edges[edgeId].via = null;
  14670. }
  14671. }
  14672. }
  14673. this._updateCalculationNodes();
  14674. if (!disableStart) {
  14675. this.moving = true;
  14676. this.start();
  14677. }
  14678. };
  14679. /**
  14680. * Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
  14681. * are used for the force calculation.
  14682. *
  14683. * @private
  14684. */
  14685. Network.prototype._createBezierNodes = function() {
  14686. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  14687. for (var edgeId in this.edges) {
  14688. if (this.edges.hasOwnProperty(edgeId)) {
  14689. var edge = this.edges[edgeId];
  14690. if (edge.via == null) {
  14691. edge.smooth = true;
  14692. var nodeId = "edgeId:".concat(edge.id);
  14693. this.sectors['support']['nodes'][nodeId] = new Node(
  14694. {id:nodeId,
  14695. mass:1,
  14696. shape:'circle',
  14697. image:"",
  14698. internalMultiplier:1
  14699. },{},{},this.constants);
  14700. edge.via = this.sectors['support']['nodes'][nodeId];
  14701. edge.via.parentEdgeId = edge.id;
  14702. edge.positionBezierNode();
  14703. }
  14704. }
  14705. }
  14706. }
  14707. };
  14708. /**
  14709. * load the functions that load the mixins into the prototype.
  14710. *
  14711. * @private
  14712. */
  14713. Network.prototype._initializeMixinLoaders = function () {
  14714. for (var mixin in MixinLoader) {
  14715. if (MixinLoader.hasOwnProperty(mixin)) {
  14716. Network.prototype[mixin] = MixinLoader[mixin];
  14717. }
  14718. }
  14719. };
  14720. /**
  14721. * Load the XY positions of the nodes into the dataset.
  14722. */
  14723. Network.prototype.storePosition = function() {
  14724. var dataArray = [];
  14725. for (var nodeId in this.nodes) {
  14726. if (this.nodes.hasOwnProperty(nodeId)) {
  14727. var node = this.nodes[nodeId];
  14728. var allowedToMoveX = !this.nodes.xFixed;
  14729. var allowedToMoveY = !this.nodes.yFixed;
  14730. if (this.nodesData._data[nodeId].x != Math.round(node.x) || this.nodesData._data[nodeId].y != Math.round(node.y)) {
  14731. dataArray.push({id:nodeId,x:Math.round(node.x),y:Math.round(node.y),allowedToMoveX:allowedToMoveX,allowedToMoveY:allowedToMoveY});
  14732. }
  14733. }
  14734. }
  14735. this.nodesData.update(dataArray);
  14736. };
  14737. /**
  14738. * Center a node in view.
  14739. *
  14740. * @param {Number} nodeId
  14741. * @param {Number} [zoomLevel]
  14742. */
  14743. Network.prototype.focusOnNode = function (nodeId, zoomLevel) {
  14744. if (this.nodes.hasOwnProperty(nodeId)) {
  14745. if (zoomLevel === undefined) {
  14746. zoomLevel = this._getScale();
  14747. }
  14748. var nodePosition= {x: this.nodes[nodeId].x, y: this.nodes[nodeId].y};
  14749. var requiredScale = zoomLevel;
  14750. this._setScale(requiredScale);
  14751. var canvasCenter = this.DOMtoCanvas({x:0.5 * this.frame.canvas.width,y:0.5 * this.frame.canvas.height});
  14752. var translation = this._getTranslation();
  14753. var distanceFromCenter = {x:canvasCenter.x - nodePosition.x,
  14754. y:canvasCenter.y - nodePosition.y};
  14755. this._setTranslation(translation.x + requiredScale * distanceFromCenter.x,
  14756. translation.y + requiredScale * distanceFromCenter.y);
  14757. this.redraw();
  14758. }
  14759. else {
  14760. console.log("This nodeId cannot be found.")
  14761. }
  14762. };
  14763. module.exports = Network;
  14764. /***/ },
  14765. /* 33 */
  14766. /***/ function(module, exports, __webpack_require__) {
  14767. var util = __webpack_require__(1);
  14768. var Node = __webpack_require__(36);
  14769. /**
  14770. * @class Edge
  14771. *
  14772. * A edge connects two nodes
  14773. * @param {Object} properties Object with properties. Must contain
  14774. * At least properties from and to.
  14775. * Available properties: from (number),
  14776. * to (number), label (string, color (string),
  14777. * width (number), style (string),
  14778. * length (number), title (string)
  14779. * @param {Network} network A Network object, used to find and edge to
  14780. * nodes.
  14781. * @param {Object} constants An object with default values for
  14782. * example for the color
  14783. */
  14784. function Edge (properties, network, constants) {
  14785. if (!network) {
  14786. throw "No network provided";
  14787. }
  14788. this.network = network;
  14789. // initialize constants
  14790. this.widthMin = constants.edges.widthMin;
  14791. this.widthMax = constants.edges.widthMax;
  14792. // initialize variables
  14793. this.id = undefined;
  14794. this.fromId = undefined;
  14795. this.toId = undefined;
  14796. this.style = constants.edges.style;
  14797. this.title = undefined;
  14798. this.width = constants.edges.width;
  14799. this.widthSelectionMultiplier = constants.edges.widthSelectionMultiplier;
  14800. this.widthSelected = this.width * this.widthSelectionMultiplier;
  14801. this.hoverWidth = constants.edges.hoverWidth;
  14802. this.value = undefined;
  14803. this.length = constants.physics.springLength;
  14804. this.customLength = false;
  14805. this.selected = false;
  14806. this.hover = false;
  14807. this.smoothCurves = constants.smoothCurves;
  14808. this.dynamicSmoothCurves = constants.dynamicSmoothCurves;
  14809. this.arrowScaleFactor = constants.edges.arrowScaleFactor;
  14810. this.inheritColor = constants.edges.inheritColor;
  14811. this.from = null; // a node
  14812. this.to = null; // a node
  14813. this.via = null; // a temp node
  14814. // we use this to be able to reconnect the edge to a cluster if its node is put into a cluster
  14815. // by storing the original information we can revert to the original connection when the cluser is opened.
  14816. this.originalFromId = [];
  14817. this.originalToId = [];
  14818. this.connected = false;
  14819. // Added to support dashed lines
  14820. // David Jordan
  14821. // 2012-08-08
  14822. this.dash = util.extend({}, constants.edges.dash); // contains properties length, gap, altLength
  14823. this.color = {color:constants.edges.color.color,
  14824. highlight:constants.edges.color.highlight,
  14825. hover:constants.edges.color.hover};
  14826. this.widthFixed = false;
  14827. this.lengthFixed = false;
  14828. this.setProperties(properties, constants);
  14829. this.controlNodesEnabled = false;
  14830. this.controlNodes = {from:null, to:null, positions:{}};
  14831. this.connectedNode = null;
  14832. }
  14833. /**
  14834. * Set or overwrite properties for the edge
  14835. * @param {Object} properties an object with properties
  14836. * @param {Object} constants and object with default, global properties
  14837. */
  14838. Edge.prototype.setProperties = function(properties, constants) {
  14839. if (!properties) {
  14840. return;
  14841. }
  14842. if (properties.from !== undefined) {this.fromId = properties.from;}
  14843. if (properties.to !== undefined) {this.toId = properties.to;}
  14844. if (properties.id !== undefined) {this.id = properties.id;}
  14845. if (properties.style !== undefined) {this.style = properties.style;}
  14846. if (properties.label !== undefined) {this.label = properties.label;}
  14847. if (this.label) {
  14848. this.fontSize = constants.edges.fontSize;
  14849. this.fontFace = constants.edges.fontFace;
  14850. this.fontColor = constants.edges.fontColor;
  14851. this.fontFill = constants.edges.fontFill;
  14852. if (properties.fontColor !== undefined) {this.fontColor = properties.fontColor;}
  14853. if (properties.fontSize !== undefined) {this.fontSize = properties.fontSize;}
  14854. if (properties.fontFace !== undefined) {this.fontFace = properties.fontFace;}
  14855. if (properties.fontFill !== undefined) {this.fontFill = properties.fontFill;}
  14856. }
  14857. if (properties.title !== undefined) {this.title = properties.title;}
  14858. if (properties.width !== undefined) {this.width = properties.width;}
  14859. if (properties.widthSelectionMultiplier !== undefined)
  14860. {this.widthSelectionMultiplier = properties.widthSelectionMultiplier;}
  14861. if (properties.hoverWidth !== undefined) {this.hoverWidth = properties.hoverWidth;}
  14862. if (properties.value !== undefined) {this.value = properties.value;}
  14863. if (properties.length !== undefined) {this.length = properties.length;
  14864. this.customLength = true;}
  14865. // scale the arrow
  14866. if (properties.arrowScaleFactor !== undefined) {this.arrowScaleFactor = properties.arrowScaleFactor;}
  14867. if (properties.inheritColor !== undefined) {this.inheritColor = properties.inheritColor;}
  14868. // Added to support dashed lines
  14869. // David Jordan
  14870. // 2012-08-08
  14871. if (properties.dash) {
  14872. if (properties.dash.length !== undefined) {this.dash.length = properties.dash.length;}
  14873. if (properties.dash.gap !== undefined) {this.dash.gap = properties.dash.gap;}
  14874. if (properties.dash.altLength !== undefined) {this.dash.altLength = properties.dash.altLength;}
  14875. }
  14876. if (properties.color !== undefined) {
  14877. if (util.isString(properties.color)) {
  14878. this.color.color = properties.color;
  14879. this.color.highlight = properties.color;
  14880. }
  14881. else {
  14882. if (properties.color.color !== undefined) {this.color.color = properties.color.color;}
  14883. if (properties.color.highlight !== undefined) {this.color.highlight = properties.color.highlight;}
  14884. if (properties.color.hover !== undefined) {this.color.hover = properties.color.hover;}
  14885. }
  14886. }
  14887. // A node is connected when it has a from and to node.
  14888. this.connect();
  14889. this.widthFixed = this.widthFixed || (properties.width !== undefined);
  14890. this.lengthFixed = this.lengthFixed || (properties.length !== undefined);
  14891. this.widthSelected = this.width * this.widthSelectionMultiplier;
  14892. // set draw method based on style
  14893. switch (this.style) {
  14894. case 'line': this.draw = this._drawLine; break;
  14895. case 'arrow': this.draw = this._drawArrow; break;
  14896. case 'arrow-center': this.draw = this._drawArrowCenter; break;
  14897. case 'dash-line': this.draw = this._drawDashLine; break;
  14898. default: this.draw = this._drawLine; break;
  14899. }
  14900. };
  14901. /**
  14902. * Connect an edge to its nodes
  14903. */
  14904. Edge.prototype.connect = function () {
  14905. this.disconnect();
  14906. this.from = this.network.nodes[this.fromId] || null;
  14907. this.to = this.network.nodes[this.toId] || null;
  14908. this.connected = (this.from && this.to);
  14909. if (this.connected) {
  14910. this.from.attachEdge(this);
  14911. this.to.attachEdge(this);
  14912. }
  14913. else {
  14914. if (this.from) {
  14915. this.from.detachEdge(this);
  14916. }
  14917. if (this.to) {
  14918. this.to.detachEdge(this);
  14919. }
  14920. }
  14921. };
  14922. /**
  14923. * Disconnect an edge from its nodes
  14924. */
  14925. Edge.prototype.disconnect = function () {
  14926. if (this.from) {
  14927. this.from.detachEdge(this);
  14928. this.from = null;
  14929. }
  14930. if (this.to) {
  14931. this.to.detachEdge(this);
  14932. this.to = null;
  14933. }
  14934. this.connected = false;
  14935. };
  14936. /**
  14937. * get the title of this edge.
  14938. * @return {string} title The title of the edge, or undefined when no title
  14939. * has been set.
  14940. */
  14941. Edge.prototype.getTitle = function() {
  14942. return typeof this.title === "function" ? this.title() : this.title;
  14943. };
  14944. /**
  14945. * Retrieve the value of the edge. Can be undefined
  14946. * @return {Number} value
  14947. */
  14948. Edge.prototype.getValue = function() {
  14949. return this.value;
  14950. };
  14951. /**
  14952. * Adjust the value range of the edge. The edge will adjust it's width
  14953. * based on its value.
  14954. * @param {Number} min
  14955. * @param {Number} max
  14956. */
  14957. Edge.prototype.setValueRange = function(min, max) {
  14958. if (!this.widthFixed && this.value !== undefined) {
  14959. var scale = (this.widthMax - this.widthMin) / (max - min);
  14960. this.width = (this.value - min) * scale + this.widthMin;
  14961. this.widthSelected = this.width * this.widthSelectionMultiplier;
  14962. }
  14963. };
  14964. /**
  14965. * Redraw a edge
  14966. * Draw this edge in the given canvas
  14967. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  14968. * @param {CanvasRenderingContext2D} ctx
  14969. */
  14970. Edge.prototype.draw = function(ctx) {
  14971. throw "Method draw not initialized in edge";
  14972. };
  14973. /**
  14974. * Check if this object is overlapping with the provided object
  14975. * @param {Object} obj an object with parameters left, top
  14976. * @return {boolean} True if location is located on the edge
  14977. */
  14978. Edge.prototype.isOverlappingWith = function(obj) {
  14979. if (this.connected) {
  14980. var distMax = 10;
  14981. var xFrom = this.from.x;
  14982. var yFrom = this.from.y;
  14983. var xTo = this.to.x;
  14984. var yTo = this.to.y;
  14985. var xObj = obj.left;
  14986. var yObj = obj.top;
  14987. var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  14988. return (dist < distMax);
  14989. }
  14990. else {
  14991. return false
  14992. }
  14993. };
  14994. Edge.prototype._getColor = function() {
  14995. var colorObj = this.color;
  14996. if (this.inheritColor == "to") {
  14997. colorObj = {
  14998. highlight: this.to.color.highlight.border,
  14999. hover: this.to.color.hover.border,
  15000. color: this.to.color.border
  15001. };
  15002. }
  15003. else if (this.inheritColor == "from" || this.inheritColor == true) {
  15004. colorObj = {
  15005. highlight: this.from.color.highlight.border,
  15006. hover: this.from.color.hover.border,
  15007. color: this.from.color.border
  15008. };
  15009. }
  15010. if (this.selected == true) {return colorObj.highlight;}
  15011. else if (this.hover == true) {return colorObj.hover;}
  15012. else {return colorObj.color;}
  15013. }
  15014. /**
  15015. * Redraw a edge as a line
  15016. * Draw this edge in the given canvas
  15017. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  15018. * @param {CanvasRenderingContext2D} ctx
  15019. * @private
  15020. */
  15021. Edge.prototype._drawLine = function(ctx) {
  15022. // set style
  15023. ctx.strokeStyle = this._getColor();
  15024. ctx.lineWidth = this._getLineWidth();
  15025. if (this.from != this.to) {
  15026. // draw line
  15027. var via = this._line(ctx);
  15028. // draw label
  15029. var point;
  15030. if (this.label) {
  15031. if (this.smoothCurves.enabled == true && via != null) {
  15032. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  15033. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  15034. point = {x:midpointX, y:midpointY};
  15035. }
  15036. else {
  15037. point = this._pointOnLine(0.5);
  15038. }
  15039. this._label(ctx, this.label, point.x, point.y);
  15040. }
  15041. }
  15042. else {
  15043. var x, y;
  15044. var radius = this.length / 4;
  15045. var node = this.from;
  15046. if (!node.width) {
  15047. node.resize(ctx);
  15048. }
  15049. if (node.width > node.height) {
  15050. x = node.x + node.width / 2;
  15051. y = node.y - radius;
  15052. }
  15053. else {
  15054. x = node.x + radius;
  15055. y = node.y - node.height / 2;
  15056. }
  15057. this._circle(ctx, x, y, radius);
  15058. point = this._pointOnCircle(x, y, radius, 0.5);
  15059. this._label(ctx, this.label, point.x, point.y);
  15060. }
  15061. };
  15062. /**
  15063. * Get the line width of the edge. Depends on width and whether one of the
  15064. * connected nodes is selected.
  15065. * @return {Number} width
  15066. * @private
  15067. */
  15068. Edge.prototype._getLineWidth = function() {
  15069. if (this.selected == true) {
  15070. return Math.min(this.widthSelected, this.widthMax)*this.networkScaleInv;
  15071. }
  15072. else {
  15073. if (this.hover == true) {
  15074. return Math.min(this.hoverWidth, this.widthMax)*this.networkScaleInv;
  15075. }
  15076. else {
  15077. return this.width*this.networkScaleInv;
  15078. }
  15079. }
  15080. };
  15081. Edge.prototype._getViaCoordinates = function () {
  15082. var xVia = null;
  15083. var yVia = null;
  15084. var factor = this.smoothCurves.roundness;
  15085. var type = this.smoothCurves.type;
  15086. var dx = Math.abs(this.from.x - this.to.x);
  15087. var dy = Math.abs(this.from.y - this.to.y);
  15088. if (type == 'discrete' || type == 'diagonalCross') {
  15089. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  15090. if (this.from.y > this.to.y) {
  15091. if (this.from.x < this.to.x) {
  15092. xVia = this.from.x + factor * dy;
  15093. yVia = this.from.y - factor * dy;
  15094. }
  15095. else if (this.from.x > this.to.x) {
  15096. xVia = this.from.x - factor * dy;
  15097. yVia = this.from.y - factor * dy;
  15098. }
  15099. }
  15100. else if (this.from.y < this.to.y) {
  15101. if (this.from.x < this.to.x) {
  15102. xVia = this.from.x + factor * dy;
  15103. yVia = this.from.y + factor * dy;
  15104. }
  15105. else if (this.from.x > this.to.x) {
  15106. xVia = this.from.x - factor * dy;
  15107. yVia = this.from.y + factor * dy;
  15108. }
  15109. }
  15110. if (type == "discrete") {
  15111. xVia = dx < factor * dy ? this.from.x : xVia;
  15112. }
  15113. }
  15114. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  15115. if (this.from.y > this.to.y) {
  15116. if (this.from.x < this.to.x) {
  15117. xVia = this.from.x + factor * dx;
  15118. yVia = this.from.y - factor * dx;
  15119. }
  15120. else if (this.from.x > this.to.x) {
  15121. xVia = this.from.x - factor * dx;
  15122. yVia = this.from.y - factor * dx;
  15123. }
  15124. }
  15125. else if (this.from.y < this.to.y) {
  15126. if (this.from.x < this.to.x) {
  15127. xVia = this.from.x + factor * dx;
  15128. yVia = this.from.y + factor * dx;
  15129. }
  15130. else if (this.from.x > this.to.x) {
  15131. xVia = this.from.x - factor * dx;
  15132. yVia = this.from.y + factor * dx;
  15133. }
  15134. }
  15135. if (type == "discrete") {
  15136. yVia = dy < factor * dx ? this.from.y : yVia;
  15137. }
  15138. }
  15139. }
  15140. else if (type == "straightCross") {
  15141. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { // up - down
  15142. xVia = this.from.x;
  15143. if (this.from.y < this.to.y) {
  15144. yVia = this.to.y - (1-factor) * dy;
  15145. }
  15146. else {
  15147. yVia = this.to.y + (1-factor) * dy;
  15148. }
  15149. }
  15150. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { // left - right
  15151. if (this.from.x < this.to.x) {
  15152. xVia = this.to.x - (1-factor) * dx;
  15153. }
  15154. else {
  15155. xVia = this.to.x + (1-factor) * dx;
  15156. }
  15157. yVia = this.from.y;
  15158. }
  15159. }
  15160. else if (type == 'horizontal') {
  15161. if (this.from.x < this.to.x) {
  15162. xVia = this.to.x - (1-factor) * dx;
  15163. }
  15164. else {
  15165. xVia = this.to.x + (1-factor) * dx;
  15166. }
  15167. yVia = this.from.y;
  15168. }
  15169. else if (type == 'vertical') {
  15170. xVia = this.from.x;
  15171. if (this.from.y < this.to.y) {
  15172. yVia = this.to.y - (1-factor) * dy;
  15173. }
  15174. else {
  15175. yVia = this.to.y + (1-factor) * dy;
  15176. }
  15177. }
  15178. else { // continuous
  15179. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  15180. if (this.from.y > this.to.y) {
  15181. if (this.from.x < this.to.x) {
  15182. // console.log(1)
  15183. xVia = this.from.x + factor * dy;
  15184. yVia = this.from.y - factor * dy;
  15185. xVia = this.to.x < xVia ? this.to.x : xVia;
  15186. }
  15187. else if (this.from.x > this.to.x) {
  15188. // console.log(2)
  15189. xVia = this.from.x - factor * dy;
  15190. yVia = this.from.y - factor * dy;
  15191. xVia = this.to.x > xVia ? this.to.x :xVia;
  15192. }
  15193. }
  15194. else if (this.from.y < this.to.y) {
  15195. if (this.from.x < this.to.x) {
  15196. // console.log(3)
  15197. xVia = this.from.x + factor * dy;
  15198. yVia = this.from.y + factor * dy;
  15199. xVia = this.to.x < xVia ? this.to.x : xVia;
  15200. }
  15201. else if (this.from.x > this.to.x) {
  15202. // console.log(4, this.from.x, this.to.x)
  15203. xVia = this.from.x - factor * dy;
  15204. yVia = this.from.y + factor * dy;
  15205. xVia = this.to.x > xVia ? this.to.x : xVia;
  15206. }
  15207. }
  15208. }
  15209. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  15210. if (this.from.y > this.to.y) {
  15211. if (this.from.x < this.to.x) {
  15212. // console.log(5)
  15213. xVia = this.from.x + factor * dx;
  15214. yVia = this.from.y - factor * dx;
  15215. yVia = this.to.y > yVia ? this.to.y : yVia;
  15216. }
  15217. else if (this.from.x > this.to.x) {
  15218. // console.log(6)
  15219. xVia = this.from.x - factor * dx;
  15220. yVia = this.from.y - factor * dx;
  15221. yVia = this.to.y > yVia ? this.to.y : yVia;
  15222. }
  15223. }
  15224. else if (this.from.y < this.to.y) {
  15225. if (this.from.x < this.to.x) {
  15226. // console.log(7)
  15227. xVia = this.from.x + factor * dx;
  15228. yVia = this.from.y + factor * dx;
  15229. yVia = this.to.y < yVia ? this.to.y : yVia;
  15230. }
  15231. else if (this.from.x > this.to.x) {
  15232. // console.log(8)
  15233. xVia = this.from.x - factor * dx;
  15234. yVia = this.from.y + factor * dx;
  15235. yVia = this.to.y < yVia ? this.to.y : yVia;
  15236. }
  15237. }
  15238. }
  15239. }
  15240. return {x:xVia, y:yVia};
  15241. }
  15242. /**
  15243. * Draw a line between two nodes
  15244. * @param {CanvasRenderingContext2D} ctx
  15245. * @private
  15246. */
  15247. Edge.prototype._line = function (ctx) {
  15248. // draw a straight line
  15249. ctx.beginPath();
  15250. ctx.moveTo(this.from.x, this.from.y);
  15251. if (this.smoothCurves.enabled == true) {
  15252. if (this.smoothCurves.dynamic == false) {
  15253. var via = this._getViaCoordinates();
  15254. if (via.x == null) {
  15255. ctx.lineTo(this.to.x, this.to.y);
  15256. ctx.stroke();
  15257. return null;
  15258. }
  15259. else {
  15260. // this.via.x = via.x;
  15261. // this.via.y = via.y;
  15262. ctx.quadraticCurveTo(via.x,via.y,this.to.x, this.to.y);
  15263. ctx.stroke();
  15264. return via;
  15265. }
  15266. }
  15267. else {
  15268. ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
  15269. ctx.stroke();
  15270. return this.via;
  15271. }
  15272. }
  15273. else {
  15274. ctx.lineTo(this.to.x, this.to.y);
  15275. ctx.stroke();
  15276. return null;
  15277. }
  15278. };
  15279. /**
  15280. * Draw a line from a node to itself, a circle
  15281. * @param {CanvasRenderingContext2D} ctx
  15282. * @param {Number} x
  15283. * @param {Number} y
  15284. * @param {Number} radius
  15285. * @private
  15286. */
  15287. Edge.prototype._circle = function (ctx, x, y, radius) {
  15288. // draw a circle
  15289. ctx.beginPath();
  15290. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  15291. ctx.stroke();
  15292. };
  15293. /**
  15294. * Draw label with white background and with the middle at (x, y)
  15295. * @param {CanvasRenderingContext2D} ctx
  15296. * @param {String} text
  15297. * @param {Number} x
  15298. * @param {Number} y
  15299. * @private
  15300. */
  15301. Edge.prototype._label = function (ctx, text, x, y) {
  15302. if (text) {
  15303. // TODO: cache the calculated size
  15304. ctx.font = ((this.from.selected || this.to.selected) ? "bold " : "") +
  15305. this.fontSize + "px " + this.fontFace;
  15306. ctx.fillStyle = this.fontFill;
  15307. var width = ctx.measureText(text).width;
  15308. var height = this.fontSize;
  15309. var left = x - width / 2;
  15310. var top = y - height / 2;
  15311. ctx.fillRect(left, top, width, height);
  15312. // draw text
  15313. ctx.fillStyle = this.fontColor || "black";
  15314. ctx.textAlign = "left";
  15315. ctx.textBaseline = "top";
  15316. ctx.fillText(text, left, top);
  15317. }
  15318. };
  15319. /**
  15320. * Redraw a edge as a dashed line
  15321. * Draw this edge in the given canvas
  15322. * @author David Jordan
  15323. * @date 2012-08-08
  15324. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  15325. * @param {CanvasRenderingContext2D} ctx
  15326. * @private
  15327. */
  15328. Edge.prototype._drawDashLine = function(ctx) {
  15329. // set style
  15330. if (this.selected == true) {ctx.strokeStyle = this.color.highlight;}
  15331. else if (this.hover == true) {ctx.strokeStyle = this.color.hover;}
  15332. else {ctx.strokeStyle = this.color.color;}
  15333. ctx.lineWidth = this._getLineWidth();
  15334. var via = null;
  15335. // only firefox and chrome support this method, else we use the legacy one.
  15336. if (ctx.mozDash !== undefined || ctx.setLineDash !== undefined) {
  15337. // configure the dash pattern
  15338. var pattern = [0];
  15339. if (this.dash.length !== undefined && this.dash.gap !== undefined) {
  15340. pattern = [this.dash.length,this.dash.gap];
  15341. }
  15342. else {
  15343. pattern = [5,5];
  15344. }
  15345. // set dash settings for chrome or firefox
  15346. if (typeof ctx.setLineDash !== 'undefined') { //Chrome
  15347. ctx.setLineDash(pattern);
  15348. ctx.lineDashOffset = 0;
  15349. } else { //Firefox
  15350. ctx.mozDash = pattern;
  15351. ctx.mozDashOffset = 0;
  15352. }
  15353. // draw the line
  15354. via = this._line(ctx);
  15355. // restore the dash settings.
  15356. if (typeof ctx.setLineDash !== 'undefined') { //Chrome
  15357. ctx.setLineDash([0]);
  15358. ctx.lineDashOffset = 0;
  15359. } else { //Firefox
  15360. ctx.mozDash = [0];
  15361. ctx.mozDashOffset = 0;
  15362. }
  15363. }
  15364. else { // unsupporting smooth lines
  15365. // draw dashed line
  15366. ctx.beginPath();
  15367. ctx.lineCap = 'round';
  15368. if (this.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
  15369. {
  15370. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  15371. [this.dash.length,this.dash.gap,this.dash.altLength,this.dash.gap]);
  15372. }
  15373. else if (this.dash.length !== undefined && this.dash.gap !== undefined) //If a dash and gap value has been set add to the array this value
  15374. {
  15375. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  15376. [this.dash.length,this.dash.gap]);
  15377. }
  15378. else //If all else fails draw a line
  15379. {
  15380. ctx.moveTo(this.from.x, this.from.y);
  15381. ctx.lineTo(this.to.x, this.to.y);
  15382. }
  15383. ctx.stroke();
  15384. }
  15385. // draw label
  15386. if (this.label) {
  15387. var point;
  15388. if (this.smoothCurves.enabled == true && via != null) {
  15389. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  15390. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  15391. point = {x:midpointX, y:midpointY};
  15392. }
  15393. else {
  15394. point = this._pointOnLine(0.5);
  15395. }
  15396. this._label(ctx, this.label, point.x, point.y);
  15397. }
  15398. };
  15399. /**
  15400. * Get a point on a line
  15401. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  15402. * @return {Object} point
  15403. * @private
  15404. */
  15405. Edge.prototype._pointOnLine = function (percentage) {
  15406. return {
  15407. x: (1 - percentage) * this.from.x + percentage * this.to.x,
  15408. y: (1 - percentage) * this.from.y + percentage * this.to.y
  15409. }
  15410. };
  15411. /**
  15412. * Get a point on a circle
  15413. * @param {Number} x
  15414. * @param {Number} y
  15415. * @param {Number} radius
  15416. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  15417. * @return {Object} point
  15418. * @private
  15419. */
  15420. Edge.prototype._pointOnCircle = function (x, y, radius, percentage) {
  15421. var angle = (percentage - 3/8) * 2 * Math.PI;
  15422. return {
  15423. x: x + radius * Math.cos(angle),
  15424. y: y - radius * Math.sin(angle)
  15425. }
  15426. };
  15427. /**
  15428. * Redraw a edge as a line with an arrow halfway the line
  15429. * Draw this edge in the given canvas
  15430. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  15431. * @param {CanvasRenderingContext2D} ctx
  15432. * @private
  15433. */
  15434. Edge.prototype._drawArrowCenter = function(ctx) {
  15435. var point;
  15436. // set style
  15437. if (this.selected == true) {ctx.strokeStyle = this.color.highlight; ctx.fillStyle = this.color.highlight;}
  15438. else if (this.hover == true) {ctx.strokeStyle = this.color.hover; ctx.fillStyle = this.color.hover;}
  15439. else {ctx.strokeStyle = this.color.color; ctx.fillStyle = this.color.color;}
  15440. ctx.lineWidth = this._getLineWidth();
  15441. if (this.from != this.to) {
  15442. // draw line
  15443. var via = this._line(ctx);
  15444. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  15445. var length = (10 + 5 * this.width) * this.arrowScaleFactor;
  15446. // draw an arrow halfway the line
  15447. if (this.smoothCurves.enabled == true && via != null) {
  15448. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  15449. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  15450. point = {x:midpointX, y:midpointY};
  15451. }
  15452. else {
  15453. point = this._pointOnLine(0.5);
  15454. }
  15455. ctx.arrow(point.x, point.y, angle, length);
  15456. ctx.fill();
  15457. ctx.stroke();
  15458. // draw label
  15459. if (this.label) {
  15460. this._label(ctx, this.label, point.x, point.y);
  15461. }
  15462. }
  15463. else {
  15464. // draw circle
  15465. var x, y;
  15466. var radius = 0.25 * Math.max(100,this.length);
  15467. var node = this.from;
  15468. if (!node.width) {
  15469. node.resize(ctx);
  15470. }
  15471. if (node.width > node.height) {
  15472. x = node.x + node.width * 0.5;
  15473. y = node.y - radius;
  15474. }
  15475. else {
  15476. x = node.x + radius;
  15477. y = node.y - node.height * 0.5;
  15478. }
  15479. this._circle(ctx, x, y, radius);
  15480. // draw all arrows
  15481. var angle = 0.2 * Math.PI;
  15482. var length = (10 + 5 * this.width) * this.arrowScaleFactor;
  15483. point = this._pointOnCircle(x, y, radius, 0.5);
  15484. ctx.arrow(point.x, point.y, angle, length);
  15485. ctx.fill();
  15486. ctx.stroke();
  15487. // draw label
  15488. if (this.label) {
  15489. point = this._pointOnCircle(x, y, radius, 0.5);
  15490. this._label(ctx, this.label, point.x, point.y);
  15491. }
  15492. }
  15493. };
  15494. /**
  15495. * Redraw a edge as a line with an arrow
  15496. * Draw this edge in the given canvas
  15497. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  15498. * @param {CanvasRenderingContext2D} ctx
  15499. * @private
  15500. */
  15501. Edge.prototype._drawArrow = function(ctx) {
  15502. // set style
  15503. if (this.selected == true) {ctx.strokeStyle = this.color.highlight; ctx.fillStyle = this.color.highlight;}
  15504. else if (this.hover == true) {ctx.strokeStyle = this.color.hover; ctx.fillStyle = this.color.hover;}
  15505. else {ctx.strokeStyle = this.color.color; ctx.fillStyle = this.color.color;}
  15506. ctx.lineWidth = this._getLineWidth();
  15507. var angle, length;
  15508. //draw a line
  15509. if (this.from != this.to) {
  15510. angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  15511. var dx = (this.to.x - this.from.x);
  15512. var dy = (this.to.y - this.from.y);
  15513. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  15514. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  15515. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  15516. var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  15517. var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  15518. var via;
  15519. if (this.smoothCurves.dynamic == true && this.smoothCurves.enabled == true ) {
  15520. via = this.via;
  15521. }
  15522. else if (this.smoothCurves.enabled == true) {
  15523. via = this._getViaCoordinates();
  15524. }
  15525. if (this.smoothCurves.enabled == true && via.x != null) {
  15526. angle = Math.atan2((this.to.y - via.y), (this.to.x - via.x));
  15527. dx = (this.to.x - via.x);
  15528. dy = (this.to.y - via.y);
  15529. edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  15530. }
  15531. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  15532. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  15533. var xTo,yTo;
  15534. if (this.smoothCurves.enabled == true && via.x != null) {
  15535. xTo = (1 - toBorderPoint) * via.x + toBorderPoint * this.to.x;
  15536. yTo = (1 - toBorderPoint) * via.y + toBorderPoint * this.to.y;
  15537. }
  15538. else {
  15539. xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  15540. yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  15541. }
  15542. ctx.beginPath();
  15543. ctx.moveTo(xFrom,yFrom);
  15544. if (this.smoothCurves.enabled == true && via.x != null) {
  15545. ctx.quadraticCurveTo(via.x,via.y,xTo, yTo);
  15546. }
  15547. else {
  15548. ctx.lineTo(xTo, yTo);
  15549. }
  15550. ctx.stroke();
  15551. // draw arrow at the end of the line
  15552. length = (10 + 5 * this.width) * this.arrowScaleFactor;
  15553. ctx.arrow(xTo, yTo, angle, length);
  15554. ctx.fill();
  15555. ctx.stroke();
  15556. // draw label
  15557. if (this.label) {
  15558. var point;
  15559. if (this.smoothCurves.enabled == true && via != null) {
  15560. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  15561. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  15562. point = {x:midpointX, y:midpointY};
  15563. }
  15564. else {
  15565. point = this._pointOnLine(0.5);
  15566. }
  15567. this._label(ctx, this.label, point.x, point.y);
  15568. }
  15569. }
  15570. else {
  15571. // draw circle
  15572. var node = this.from;
  15573. var x, y, arrow;
  15574. var radius = 0.25 * Math.max(100,this.length);
  15575. if (!node.width) {
  15576. node.resize(ctx);
  15577. }
  15578. if (node.width > node.height) {
  15579. x = node.x + node.width * 0.5;
  15580. y = node.y - radius;
  15581. arrow = {
  15582. x: x,
  15583. y: node.y,
  15584. angle: 0.9 * Math.PI
  15585. };
  15586. }
  15587. else {
  15588. x = node.x + radius;
  15589. y = node.y - node.height * 0.5;
  15590. arrow = {
  15591. x: node.x,
  15592. y: y,
  15593. angle: 0.6 * Math.PI
  15594. };
  15595. }
  15596. ctx.beginPath();
  15597. // TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
  15598. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  15599. ctx.stroke();
  15600. // draw all arrows
  15601. var length = (10 + 5 * this.width) * this.arrowScaleFactor;
  15602. ctx.arrow(arrow.x, arrow.y, arrow.angle, length);
  15603. ctx.fill();
  15604. ctx.stroke();
  15605. // draw label
  15606. if (this.label) {
  15607. point = this._pointOnCircle(x, y, radius, 0.5);
  15608. this._label(ctx, this.label, point.x, point.y);
  15609. }
  15610. }
  15611. };
  15612. /**
  15613. * Calculate the distance between a point (x3,y3) and a line segment from
  15614. * (x1,y1) to (x2,y2).
  15615. * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
  15616. * @param {number} x1
  15617. * @param {number} y1
  15618. * @param {number} x2
  15619. * @param {number} y2
  15620. * @param {number} x3
  15621. * @param {number} y3
  15622. * @private
  15623. */
  15624. Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
  15625. if (this.from != this.to) {
  15626. if (this.smoothCurves.enabled == true) {
  15627. var xVia, yVia;
  15628. if (this.smoothCurves.enabled == true && this.smoothCurves.dynamic == true) {
  15629. xVia = this.via.x;
  15630. yVia = this.via.y;
  15631. }
  15632. else {
  15633. var via = this._getViaCoordinates();
  15634. xVia = via.x;
  15635. yVia = via.y;
  15636. }
  15637. var minDistance = 1e9;
  15638. var distance;
  15639. var i,t,x,y, lastX, lastY;
  15640. for (i = 0; i < 10; i++) {
  15641. t = 0.1*i;
  15642. x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*xVia + Math.pow(t,2)*x2;
  15643. y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*yVia + Math.pow(t,2)*y2;
  15644. if (i > 0) {
  15645. distance = this._getDistanceToLine(lastX,lastY,x,y, x3,y3);
  15646. minDistance = distance < minDistance ? distance : minDistance;
  15647. }
  15648. lastX = x; lastY = y;
  15649. }
  15650. return minDistance
  15651. }
  15652. else {
  15653. return this._getDistanceToLine(x1,y1,x2,y2,x3,y3);
  15654. }
  15655. }
  15656. else {
  15657. var x, y, dx, dy;
  15658. var radius = this.length / 4;
  15659. var node = this.from;
  15660. if (!node.width) {
  15661. node.resize(ctx);
  15662. }
  15663. if (node.width > node.height) {
  15664. x = node.x + node.width / 2;
  15665. y = node.y - radius;
  15666. }
  15667. else {
  15668. x = node.x + radius;
  15669. y = node.y - node.height / 2;
  15670. }
  15671. dx = x - x3;
  15672. dy = y - y3;
  15673. return Math.abs(Math.sqrt(dx*dx + dy*dy) - radius);
  15674. }
  15675. };
  15676. Edge.prototype._getDistanceToLine = function(x1,y1,x2,y2,x3,y3) {
  15677. var px = x2-x1,
  15678. py = y2-y1,
  15679. something = px*px + py*py,
  15680. u = ((x3 - x1) * px + (y3 - y1) * py) / something;
  15681. if (u > 1) {
  15682. u = 1;
  15683. }
  15684. else if (u < 0) {
  15685. u = 0;
  15686. }
  15687. var x = x1 + u * px,
  15688. y = y1 + u * py,
  15689. dx = x - x3,
  15690. dy = y - y3;
  15691. //# Note: If the actual distance does not matter,
  15692. //# if you only want to compare what this function
  15693. //# returns to other results of this function, you
  15694. //# can just return the squared distance instead
  15695. //# (i.e. remove the sqrt) to gain a little performance
  15696. return Math.sqrt(dx*dx + dy*dy);
  15697. }
  15698. /**
  15699. * This allows the zoom level of the network to influence the rendering
  15700. *
  15701. * @param scale
  15702. */
  15703. Edge.prototype.setScale = function(scale) {
  15704. this.networkScaleInv = 1.0/scale;
  15705. };
  15706. Edge.prototype.select = function() {
  15707. this.selected = true;
  15708. };
  15709. Edge.prototype.unselect = function() {
  15710. this.selected = false;
  15711. };
  15712. Edge.prototype.positionBezierNode = function() {
  15713. if (this.via !== null) {
  15714. this.via.x = 0.5 * (this.from.x + this.to.x);
  15715. this.via.y = 0.5 * (this.from.y + this.to.y);
  15716. }
  15717. };
  15718. /**
  15719. * This function draws the control nodes for the manipulator. In order to enable this, only set the this.controlNodesEnabled to true.
  15720. * @param ctx
  15721. */
  15722. Edge.prototype._drawControlNodes = function(ctx) {
  15723. if (this.controlNodesEnabled == true) {
  15724. if (this.controlNodes.from === null && this.controlNodes.to === null) {
  15725. var nodeIdFrom = "edgeIdFrom:".concat(this.id);
  15726. var nodeIdTo = "edgeIdTo:".concat(this.id);
  15727. var constants = {
  15728. nodes:{group:'', radius:8},
  15729. physics:{damping:0},
  15730. clustering: {maxNodeSizeIncrements: 0 ,nodeScaling: {width:0, height: 0, radius:0}}
  15731. };
  15732. this.controlNodes.from = new Node(
  15733. {id:nodeIdFrom,
  15734. shape:'dot',
  15735. color:{background:'#ff4e00', border:'#3c3c3c', highlight: {background:'#07f968'}}
  15736. },{},{},constants);
  15737. this.controlNodes.to = new Node(
  15738. {id:nodeIdTo,
  15739. shape:'dot',
  15740. color:{background:'#ff4e00', border:'#3c3c3c', highlight: {background:'#07f968'}}
  15741. },{},{},constants);
  15742. }
  15743. if (this.controlNodes.from.selected == false && this.controlNodes.to.selected == false) {
  15744. this.controlNodes.positions = this.getControlNodePositions(ctx);
  15745. this.controlNodes.from.x = this.controlNodes.positions.from.x;
  15746. this.controlNodes.from.y = this.controlNodes.positions.from.y;
  15747. this.controlNodes.to.x = this.controlNodes.positions.to.x;
  15748. this.controlNodes.to.y = this.controlNodes.positions.to.y;
  15749. }
  15750. this.controlNodes.from.draw(ctx);
  15751. this.controlNodes.to.draw(ctx);
  15752. }
  15753. else {
  15754. this.controlNodes = {from:null, to:null, positions:{}};
  15755. }
  15756. };
  15757. /**
  15758. * Enable control nodes.
  15759. * @private
  15760. */
  15761. Edge.prototype._enableControlNodes = function() {
  15762. this.controlNodesEnabled = true;
  15763. };
  15764. /**
  15765. * disable control nodes
  15766. * @private
  15767. */
  15768. Edge.prototype._disableControlNodes = function() {
  15769. this.controlNodesEnabled = false;
  15770. };
  15771. /**
  15772. * This checks if one of the control nodes is selected and if so, returns the control node object. Else it returns null.
  15773. * @param x
  15774. * @param y
  15775. * @returns {null}
  15776. * @private
  15777. */
  15778. Edge.prototype._getSelectedControlNode = function(x,y) {
  15779. var positions = this.controlNodes.positions;
  15780. var fromDistance = Math.sqrt(Math.pow(x - positions.from.x,2) + Math.pow(y - positions.from.y,2));
  15781. var toDistance = Math.sqrt(Math.pow(x - positions.to.x ,2) + Math.pow(y - positions.to.y ,2));
  15782. if (fromDistance < 15) {
  15783. this.connectedNode = this.from;
  15784. this.from = this.controlNodes.from;
  15785. return this.controlNodes.from;
  15786. }
  15787. else if (toDistance < 15) {
  15788. this.connectedNode = this.to;
  15789. this.to = this.controlNodes.to;
  15790. return this.controlNodes.to;
  15791. }
  15792. else {
  15793. return null;
  15794. }
  15795. };
  15796. /**
  15797. * this resets the control nodes to their original position.
  15798. * @private
  15799. */
  15800. Edge.prototype._restoreControlNodes = function() {
  15801. if (this.controlNodes.from.selected == true) {
  15802. this.from = this.connectedNode;
  15803. this.connectedNode = null;
  15804. this.controlNodes.from.unselect();
  15805. }
  15806. if (this.controlNodes.to.selected == true) {
  15807. this.to = this.connectedNode;
  15808. this.connectedNode = null;
  15809. this.controlNodes.to.unselect();
  15810. }
  15811. };
  15812. /**
  15813. * this calculates the position of the control nodes on the edges of the parent nodes.
  15814. *
  15815. * @param ctx
  15816. * @returns {{from: {x: number, y: number}, to: {x: *, y: *}}}
  15817. */
  15818. Edge.prototype.getControlNodePositions = function(ctx) {
  15819. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  15820. var dx = (this.to.x - this.from.x);
  15821. var dy = (this.to.y - this.from.y);
  15822. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  15823. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  15824. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  15825. var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  15826. var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  15827. var via;
  15828. if (this.smoothCurves.dynamic == true && this.smoothCurves.enabled == true) {
  15829. via = this.via;
  15830. }
  15831. else if (this.smoothCurves.enabled == true) {
  15832. via = this._getViaCoordinates();
  15833. }
  15834. if (this.smoothCurves.enabled == true && via.x != null) {
  15835. angle = Math.atan2((this.to.y - via.y), (this.to.x - via.x));
  15836. dx = (this.to.x - via.x);
  15837. dy = (this.to.y - via.y);
  15838. edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  15839. }
  15840. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  15841. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  15842. var xTo,yTo;
  15843. if (this.smoothCurves.enabled == true && via.x != null) {
  15844. xTo = (1 - toBorderPoint) * via.x + toBorderPoint * this.to.x;
  15845. yTo = (1 - toBorderPoint) * via.y + toBorderPoint * this.to.y;
  15846. }
  15847. else {
  15848. xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  15849. yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  15850. }
  15851. return {from:{x:xFrom,y:yFrom},to:{x:xTo,y:yTo}};
  15852. };
  15853. module.exports = Edge;
  15854. /***/ },
  15855. /* 34 */
  15856. /***/ function(module, exports, __webpack_require__) {
  15857. var util = __webpack_require__(1);
  15858. /**
  15859. * @class Groups
  15860. * This class can store groups and properties specific for groups.
  15861. */
  15862. function Groups() {
  15863. this.clear();
  15864. this.defaultIndex = 0;
  15865. }
  15866. /**
  15867. * default constants for group colors
  15868. */
  15869. Groups.DEFAULT = [
  15870. {border: "#2B7CE9", background: "#97C2FC", highlight: {border: "#2B7CE9", background: "#D2E5FF"}, hover: {border: "#2B7CE9", background: "#D2E5FF"}}, // blue
  15871. {border: "#FFA500", background: "#FFFF00", highlight: {border: "#FFA500", background: "#FFFFA3"}, hover: {border: "#FFA500", background: "#FFFFA3"}}, // yellow
  15872. {border: "#FA0A10", background: "#FB7E81", highlight: {border: "#FA0A10", background: "#FFAFB1"}, hover: {border: "#FA0A10", background: "#FFAFB1"}}, // red
  15873. {border: "#41A906", background: "#7BE141", highlight: {border: "#41A906", background: "#A1EC76"}, hover: {border: "#41A906", background: "#A1EC76"}}, // green
  15874. {border: "#E129F0", background: "#EB7DF4", highlight: {border: "#E129F0", background: "#F0B3F5"}, hover: {border: "#E129F0", background: "#F0B3F5"}}, // magenta
  15875. {border: "#7C29F0", background: "#AD85E4", highlight: {border: "#7C29F0", background: "#D3BDF0"}, hover: {border: "#7C29F0", background: "#D3BDF0"}}, // purple
  15876. {border: "#C37F00", background: "#FFA807", highlight: {border: "#C37F00", background: "#FFCA66"}, hover: {border: "#C37F00", background: "#FFCA66"}}, // orange
  15877. {border: "#4220FB", background: "#6E6EFD", highlight: {border: "#4220FB", background: "#9B9BFD"}, hover: {border: "#4220FB", background: "#9B9BFD"}}, // darkblue
  15878. {border: "#FD5A77", background: "#FFC0CB", highlight: {border: "#FD5A77", background: "#FFD1D9"}, hover: {border: "#FD5A77", background: "#FFD1D9"}}, // pink
  15879. {border: "#4AD63A", background: "#C2FABC", highlight: {border: "#4AD63A", background: "#E6FFE3"}, hover: {border: "#4AD63A", background: "#E6FFE3"}} // mint
  15880. ];
  15881. /**
  15882. * Clear all groups
  15883. */
  15884. Groups.prototype.clear = function () {
  15885. this.groups = {};
  15886. this.groups.length = function()
  15887. {
  15888. var i = 0;
  15889. for ( var p in this ) {
  15890. if (this.hasOwnProperty(p)) {
  15891. i++;
  15892. }
  15893. }
  15894. return i;
  15895. }
  15896. };
  15897. /**
  15898. * get group properties of a groupname. If groupname is not found, a new group
  15899. * is added.
  15900. * @param {*} groupname Can be a number, string, Date, etc.
  15901. * @return {Object} group The created group, containing all group properties
  15902. */
  15903. Groups.prototype.get = function (groupname) {
  15904. var group = this.groups[groupname];
  15905. if (group == undefined) {
  15906. // create new group
  15907. var index = this.defaultIndex % Groups.DEFAULT.length;
  15908. this.defaultIndex++;
  15909. group = {};
  15910. group.color = Groups.DEFAULT[index];
  15911. this.groups[groupname] = group;
  15912. }
  15913. return group;
  15914. };
  15915. /**
  15916. * Add a custom group style
  15917. * @param {String} groupname
  15918. * @param {Object} style An object containing borderColor,
  15919. * backgroundColor, etc.
  15920. * @return {Object} group The created group object
  15921. */
  15922. Groups.prototype.add = function (groupname, style) {
  15923. this.groups[groupname] = style;
  15924. if (style.color) {
  15925. style.color = util.parseColor(style.color);
  15926. }
  15927. return style;
  15928. };
  15929. module.exports = Groups;
  15930. /***/ },
  15931. /* 35 */
  15932. /***/ function(module, exports, __webpack_require__) {
  15933. /**
  15934. * @class Images
  15935. * This class loads images and keeps them stored.
  15936. */
  15937. function Images() {
  15938. this.images = {};
  15939. this.callback = undefined;
  15940. }
  15941. /**
  15942. * Set an onload callback function. This will be called each time an image
  15943. * is loaded
  15944. * @param {function} callback
  15945. */
  15946. Images.prototype.setOnloadCallback = function(callback) {
  15947. this.callback = callback;
  15948. };
  15949. /**
  15950. *
  15951. * @param {string} url Url of the image
  15952. * @return {Image} img The image object
  15953. */
  15954. Images.prototype.load = function(url) {
  15955. var img = this.images[url];
  15956. if (img == undefined) {
  15957. // create the image
  15958. var images = this;
  15959. img = new Image();
  15960. this.images[url] = img;
  15961. img.onload = function() {
  15962. if (images.callback) {
  15963. images.callback(this);
  15964. }
  15965. };
  15966. img.src = url;
  15967. }
  15968. return img;
  15969. };
  15970. module.exports = Images;
  15971. /***/ },
  15972. /* 36 */
  15973. /***/ function(module, exports, __webpack_require__) {
  15974. var util = __webpack_require__(1);
  15975. /**
  15976. * @class Node
  15977. * A node. A node can be connected to other nodes via one or multiple edges.
  15978. * @param {object} properties An object containing properties for the node. All
  15979. * properties are optional, except for the id.
  15980. * {number} id Id of the node. Required
  15981. * {string} label Text label for the node
  15982. * {number} x Horizontal position of the node
  15983. * {number} y Vertical position of the node
  15984. * {string} shape Node shape, available:
  15985. * "database", "circle", "ellipse",
  15986. * "box", "image", "text", "dot",
  15987. * "star", "triangle", "triangleDown",
  15988. * "square"
  15989. * {string} image An image url
  15990. * {string} title An title text, can be HTML
  15991. * {anytype} group A group name or number
  15992. * @param {Network.Images} imagelist A list with images. Only needed
  15993. * when the node has an image
  15994. * @param {Network.Groups} grouplist A list with groups. Needed for
  15995. * retrieving group properties
  15996. * @param {Object} constants An object with default values for
  15997. * example for the color
  15998. *
  15999. */
  16000. function Node(properties, imagelist, grouplist, constants) {
  16001. this.selected = false;
  16002. this.hover = false;
  16003. this.edges = []; // all edges connected to this node
  16004. this.dynamicEdges = [];
  16005. this.reroutedEdges = {};
  16006. this.group = constants.nodes.group;
  16007. this.fontSize = Number(constants.nodes.fontSize);
  16008. this.fontFace = constants.nodes.fontFace;
  16009. this.fontColor = constants.nodes.fontColor;
  16010. this.fontDrawThreshold = 3;
  16011. this.color = constants.nodes.color;
  16012. // set defaults for the properties
  16013. this.id = undefined;
  16014. this.shape = constants.nodes.shape;
  16015. this.image = constants.nodes.image;
  16016. this.x = null;
  16017. this.y = null;
  16018. this.xFixed = false;
  16019. this.yFixed = false;
  16020. this.horizontalAlignLeft = true; // these are for the navigation controls
  16021. this.verticalAlignTop = true; // these are for the navigation controls
  16022. this.radius = constants.nodes.radius;
  16023. this.baseRadiusValue = constants.nodes.radius;
  16024. this.radiusFixed = false;
  16025. this.radiusMin = constants.nodes.radiusMin;
  16026. this.radiusMax = constants.nodes.radiusMax;
  16027. this.level = -1;
  16028. this.preassignedLevel = false;
  16029. this.borderWidth = constants.nodes.borderWidth;
  16030. this.borderWidthSelected = constants.nodes.borderWidthSelected;
  16031. this.imagelist = imagelist;
  16032. this.grouplist = grouplist;
  16033. // physics properties
  16034. this.fx = 0.0; // external force x
  16035. this.fy = 0.0; // external force y
  16036. this.vx = 0.0; // velocity x
  16037. this.vy = 0.0; // velocity y
  16038. this.minForce = constants.minForce;
  16039. this.damping = constants.physics.damping;
  16040. this.mass = 1; // kg
  16041. this.fixedData = {x:null,y:null};
  16042. this.setProperties(properties, constants);
  16043. // creating the variables for clustering
  16044. this.resetCluster();
  16045. this.dynamicEdgesLength = 0;
  16046. this.clusterSession = 0;
  16047. this.clusterSizeWidthFactor = constants.clustering.nodeScaling.width;
  16048. this.clusterSizeHeightFactor = constants.clustering.nodeScaling.height;
  16049. this.clusterSizeRadiusFactor = constants.clustering.nodeScaling.radius;
  16050. this.maxNodeSizeIncrements = constants.clustering.maxNodeSizeIncrements;
  16051. this.growthIndicator = 0;
  16052. // variables to tell the node about the network.
  16053. this.networkScaleInv = 1;
  16054. this.networkScale = 1;
  16055. this.canvasTopLeft = {"x": -300, "y": -300};
  16056. this.canvasBottomRight = {"x": 300, "y": 300};
  16057. this.parentEdgeId = null;
  16058. }
  16059. /**
  16060. * (re)setting the clustering variables and objects
  16061. */
  16062. Node.prototype.resetCluster = function() {
  16063. // clustering variables
  16064. this.formationScale = undefined; // this is used to determine when to open the cluster
  16065. this.clusterSize = 1; // this signifies the total amount of nodes in this cluster
  16066. this.containedNodes = {};
  16067. this.containedEdges = {};
  16068. this.clusterSessions = [];
  16069. };
  16070. /**
  16071. * Attach a edge to the node
  16072. * @param {Edge} edge
  16073. */
  16074. Node.prototype.attachEdge = function(edge) {
  16075. if (this.edges.indexOf(edge) == -1) {
  16076. this.edges.push(edge);
  16077. }
  16078. if (this.dynamicEdges.indexOf(edge) == -1) {
  16079. this.dynamicEdges.push(edge);
  16080. }
  16081. this.dynamicEdgesLength = this.dynamicEdges.length;
  16082. };
  16083. /**
  16084. * Detach a edge from the node
  16085. * @param {Edge} edge
  16086. */
  16087. Node.prototype.detachEdge = function(edge) {
  16088. var index = this.edges.indexOf(edge);
  16089. if (index != -1) {
  16090. this.edges.splice(index, 1);
  16091. this.dynamicEdges.splice(index, 1);
  16092. }
  16093. this.dynamicEdgesLength = this.dynamicEdges.length;
  16094. };
  16095. /**
  16096. * Set or overwrite properties for the node
  16097. * @param {Object} properties an object with properties
  16098. * @param {Object} constants and object with default, global properties
  16099. */
  16100. Node.prototype.setProperties = function(properties, constants) {
  16101. if (!properties) {
  16102. return;
  16103. }
  16104. this.originalLabel = undefined;
  16105. // basic properties
  16106. if (properties.id !== undefined) {this.id = properties.id;}
  16107. if (properties.label !== undefined) {this.label = properties.label; this.originalLabel = properties.label;}
  16108. if (properties.title !== undefined) {this.title = properties.title;}
  16109. if (properties.group !== undefined) {this.group = properties.group;}
  16110. if (properties.x !== undefined) {this.x = properties.x;}
  16111. if (properties.y !== undefined) {this.y = properties.y;}
  16112. if (properties.value !== undefined) {this.value = properties.value;}
  16113. if (properties.level !== undefined) {this.level = properties.level; this.preassignedLevel = true;}
  16114. if (properties.borderWidth !== undefined) {this.borderWidth = properties.borderWidth;}
  16115. if (properties.borderWidthSelected !== undefined) {this.borderWidthSelected = properties.borderWidthSelected;}
  16116. // physics
  16117. if (properties.mass !== undefined) {this.mass = properties.mass;}
  16118. // navigation controls properties
  16119. if (properties.horizontalAlignLeft !== undefined) {this.horizontalAlignLeft = properties.horizontalAlignLeft;}
  16120. if (properties.verticalAlignTop !== undefined) {this.verticalAlignTop = properties.verticalAlignTop;}
  16121. if (properties.triggerFunction !== undefined) {this.triggerFunction = properties.triggerFunction;}
  16122. if (this.id === undefined) {
  16123. throw "Node must have an id";
  16124. }
  16125. // copy group properties
  16126. if (this.group !== undefined && this.group != "") {
  16127. var groupObj = this.grouplist.get(this.group);
  16128. for (var prop in groupObj) {
  16129. if (groupObj.hasOwnProperty(prop)) {
  16130. this[prop] = groupObj[prop];
  16131. }
  16132. }
  16133. }
  16134. // individual shape properties
  16135. if (properties.shape !== undefined) {this.shape = properties.shape;}
  16136. if (properties.image !== undefined) {this.image = properties.image;}
  16137. if (properties.radius !== undefined) {this.radius = properties.radius; this.baseRadiusValue = this.radius;}
  16138. if (properties.color !== undefined) {this.color = util.parseColor(properties.color);}
  16139. if (properties.fontColor !== undefined) {this.fontColor = properties.fontColor;}
  16140. if (properties.fontSize !== undefined) {this.fontSize = properties.fontSize;}
  16141. if (properties.fontFace !== undefined) {this.fontFace = properties.fontFace;}
  16142. if (this.image !== undefined && this.image != "") {
  16143. if (this.imagelist) {
  16144. this.imageObj = this.imagelist.load(this.image);
  16145. }
  16146. else {
  16147. throw "No imagelist provided";
  16148. }
  16149. }
  16150. this.xFixed = this.xFixed || (properties.x !== undefined && !properties.allowedToMoveX);
  16151. this.yFixed = this.yFixed || (properties.y !== undefined && !properties.allowedToMoveY);
  16152. this.radiusFixed = this.radiusFixed || (properties.radius !== undefined);
  16153. if (this.shape == 'image') {
  16154. this.radiusMin = constants.nodes.widthMin;
  16155. this.radiusMax = constants.nodes.widthMax;
  16156. }
  16157. // choose draw method depending on the shape
  16158. switch (this.shape) {
  16159. case 'database': this.draw = this._drawDatabase; this.resize = this._resizeDatabase; break;
  16160. case 'box': this.draw = this._drawBox; this.resize = this._resizeBox; break;
  16161. case 'circle': this.draw = this._drawCircle; this.resize = this._resizeCircle; break;
  16162. case 'ellipse': this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
  16163. // TODO: add diamond shape
  16164. case 'image': this.draw = this._drawImage; this.resize = this._resizeImage; break;
  16165. case 'text': this.draw = this._drawText; this.resize = this._resizeText; break;
  16166. case 'dot': this.draw = this._drawDot; this.resize = this._resizeShape; break;
  16167. case 'square': this.draw = this._drawSquare; this.resize = this._resizeShape; break;
  16168. case 'triangle': this.draw = this._drawTriangle; this.resize = this._resizeShape; break;
  16169. case 'triangleDown': this.draw = this._drawTriangleDown; this.resize = this._resizeShape; break;
  16170. case 'star': this.draw = this._drawStar; this.resize = this._resizeShape; break;
  16171. default: this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
  16172. }
  16173. // reset the size of the node, this can be changed
  16174. this._reset();
  16175. };
  16176. /**
  16177. * select this node
  16178. */
  16179. Node.prototype.select = function() {
  16180. this.selected = true;
  16181. this._reset();
  16182. };
  16183. /**
  16184. * unselect this node
  16185. */
  16186. Node.prototype.unselect = function() {
  16187. this.selected = false;
  16188. this._reset();
  16189. };
  16190. /**
  16191. * Reset the calculated size of the node, forces it to recalculate its size
  16192. */
  16193. Node.prototype.clearSizeCache = function() {
  16194. this._reset();
  16195. };
  16196. /**
  16197. * Reset the calculated size of the node, forces it to recalculate its size
  16198. * @private
  16199. */
  16200. Node.prototype._reset = function() {
  16201. this.width = undefined;
  16202. this.height = undefined;
  16203. };
  16204. /**
  16205. * get the title of this node.
  16206. * @return {string} title The title of the node, or undefined when no title
  16207. * has been set.
  16208. */
  16209. Node.prototype.getTitle = function() {
  16210. return typeof this.title === "function" ? this.title() : this.title;
  16211. };
  16212. /**
  16213. * Calculate the distance to the border of the Node
  16214. * @param {CanvasRenderingContext2D} ctx
  16215. * @param {Number} angle Angle in radians
  16216. * @returns {number} distance Distance to the border in pixels
  16217. */
  16218. Node.prototype.distanceToBorder = function (ctx, angle) {
  16219. var borderWidth = 1;
  16220. if (!this.width) {
  16221. this.resize(ctx);
  16222. }
  16223. switch (this.shape) {
  16224. case 'circle':
  16225. case 'dot':
  16226. return this.radius + borderWidth;
  16227. case 'ellipse':
  16228. var a = this.width / 2;
  16229. var b = this.height / 2;
  16230. var w = (Math.sin(angle) * a);
  16231. var h = (Math.cos(angle) * b);
  16232. return a * b / Math.sqrt(w * w + h * h);
  16233. // TODO: implement distanceToBorder for database
  16234. // TODO: implement distanceToBorder for triangle
  16235. // TODO: implement distanceToBorder for triangleDown
  16236. case 'box':
  16237. case 'image':
  16238. case 'text':
  16239. default:
  16240. if (this.width) {
  16241. return Math.min(
  16242. Math.abs(this.width / 2 / Math.cos(angle)),
  16243. Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth;
  16244. // TODO: reckon with border radius too in case of box
  16245. }
  16246. else {
  16247. return 0;
  16248. }
  16249. }
  16250. // TODO: implement calculation of distance to border for all shapes
  16251. };
  16252. /**
  16253. * Set forces acting on the node
  16254. * @param {number} fx Force in horizontal direction
  16255. * @param {number} fy Force in vertical direction
  16256. */
  16257. Node.prototype._setForce = function(fx, fy) {
  16258. this.fx = fx;
  16259. this.fy = fy;
  16260. };
  16261. /**
  16262. * Add forces acting on the node
  16263. * @param {number} fx Force in horizontal direction
  16264. * @param {number} fy Force in vertical direction
  16265. * @private
  16266. */
  16267. Node.prototype._addForce = function(fx, fy) {
  16268. this.fx += fx;
  16269. this.fy += fy;
  16270. };
  16271. /**
  16272. * Perform one discrete step for the node
  16273. * @param {number} interval Time interval in seconds
  16274. */
  16275. Node.prototype.discreteStep = function(interval) {
  16276. if (!this.xFixed) {
  16277. var dx = this.damping * this.vx; // damping force
  16278. var ax = (this.fx - dx) / this.mass; // acceleration
  16279. this.vx += ax * interval; // velocity
  16280. this.x += this.vx * interval; // position
  16281. }
  16282. if (!this.yFixed) {
  16283. var dy = this.damping * this.vy; // damping force
  16284. var ay = (this.fy - dy) / this.mass; // acceleration
  16285. this.vy += ay * interval; // velocity
  16286. this.y += this.vy * interval; // position
  16287. }
  16288. };
  16289. /**
  16290. * Perform one discrete step for the node
  16291. * @param {number} interval Time interval in seconds
  16292. * @param {number} maxVelocity The speed limit imposed on the velocity
  16293. */
  16294. Node.prototype.discreteStepLimited = function(interval, maxVelocity) {
  16295. if (!this.xFixed) {
  16296. var dx = this.damping * this.vx; // damping force
  16297. var ax = (this.fx - dx) / this.mass; // acceleration
  16298. this.vx += ax * interval; // velocity
  16299. this.vx = (Math.abs(this.vx) > maxVelocity) ? ((this.vx > 0) ? maxVelocity : -maxVelocity) : this.vx;
  16300. this.x += this.vx * interval; // position
  16301. }
  16302. else {
  16303. this.fx = 0;
  16304. }
  16305. if (!this.yFixed) {
  16306. var dy = this.damping * this.vy; // damping force
  16307. var ay = (this.fy - dy) / this.mass; // acceleration
  16308. this.vy += ay * interval; // velocity
  16309. this.vy = (Math.abs(this.vy) > maxVelocity) ? ((this.vy > 0) ? maxVelocity : -maxVelocity) : this.vy;
  16310. this.y += this.vy * interval; // position
  16311. }
  16312. else {
  16313. this.fy = 0;
  16314. }
  16315. };
  16316. /**
  16317. * Check if this node has a fixed x and y position
  16318. * @return {boolean} true if fixed, false if not
  16319. */
  16320. Node.prototype.isFixed = function() {
  16321. return (this.xFixed && this.yFixed);
  16322. };
  16323. /**
  16324. * Check if this node is moving
  16325. * @param {number} vmin the minimum velocity considered as "moving"
  16326. * @return {boolean} true if moving, false if it has no velocity
  16327. */
  16328. // TODO: replace this method with calculating the kinetic energy
  16329. Node.prototype.isMoving = function(vmin) {
  16330. return (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin);
  16331. };
  16332. /**
  16333. * check if this node is selecte
  16334. * @return {boolean} selected True if node is selected, else false
  16335. */
  16336. Node.prototype.isSelected = function() {
  16337. return this.selected;
  16338. };
  16339. /**
  16340. * Retrieve the value of the node. Can be undefined
  16341. * @return {Number} value
  16342. */
  16343. Node.prototype.getValue = function() {
  16344. return this.value;
  16345. };
  16346. /**
  16347. * Calculate the distance from the nodes location to the given location (x,y)
  16348. * @param {Number} x
  16349. * @param {Number} y
  16350. * @return {Number} value
  16351. */
  16352. Node.prototype.getDistance = function(x, y) {
  16353. var dx = this.x - x,
  16354. dy = this.y - y;
  16355. return Math.sqrt(dx * dx + dy * dy);
  16356. };
  16357. /**
  16358. * Adjust the value range of the node. The node will adjust it's radius
  16359. * based on its value.
  16360. * @param {Number} min
  16361. * @param {Number} max
  16362. */
  16363. Node.prototype.setValueRange = function(min, max) {
  16364. if (!this.radiusFixed && this.value !== undefined) {
  16365. if (max == min) {
  16366. this.radius = (this.radiusMin + this.radiusMax) / 2;
  16367. }
  16368. else {
  16369. var scale = (this.radiusMax - this.radiusMin) / (max - min);
  16370. this.radius = (this.value - min) * scale + this.radiusMin;
  16371. }
  16372. }
  16373. this.baseRadiusValue = this.radius;
  16374. };
  16375. /**
  16376. * Draw this node in the given canvas
  16377. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  16378. * @param {CanvasRenderingContext2D} ctx
  16379. */
  16380. Node.prototype.draw = function(ctx) {
  16381. throw "Draw method not initialized for node";
  16382. };
  16383. /**
  16384. * Recalculate the size of this node in the given canvas
  16385. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  16386. * @param {CanvasRenderingContext2D} ctx
  16387. */
  16388. Node.prototype.resize = function(ctx) {
  16389. throw "Resize method not initialized for node";
  16390. };
  16391. /**
  16392. * Check if this object is overlapping with the provided object
  16393. * @param {Object} obj an object with parameters left, top, right, bottom
  16394. * @return {boolean} True if location is located on node
  16395. */
  16396. Node.prototype.isOverlappingWith = function(obj) {
  16397. return (this.left < obj.right &&
  16398. this.left + this.width > obj.left &&
  16399. this.top < obj.bottom &&
  16400. this.top + this.height > obj.top);
  16401. };
  16402. Node.prototype._resizeImage = function (ctx) {
  16403. // TODO: pre calculate the image size
  16404. if (!this.width || !this.height) { // undefined or 0
  16405. var width, height;
  16406. if (this.value) {
  16407. this.radius = this.baseRadiusValue;
  16408. var scale = this.imageObj.height / this.imageObj.width;
  16409. if (scale !== undefined) {
  16410. width = this.radius || this.imageObj.width;
  16411. height = this.radius * scale || this.imageObj.height;
  16412. }
  16413. else {
  16414. width = 0;
  16415. height = 0;
  16416. }
  16417. }
  16418. else {
  16419. width = this.imageObj.width;
  16420. height = this.imageObj.height;
  16421. }
  16422. this.width = width;
  16423. this.height = height;
  16424. this.growthIndicator = 0;
  16425. if (this.width > 0 && this.height > 0) {
  16426. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  16427. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  16428. this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  16429. this.growthIndicator = this.width - width;
  16430. }
  16431. }
  16432. };
  16433. Node.prototype._drawImage = function (ctx) {
  16434. this._resizeImage(ctx);
  16435. this.left = this.x - this.width / 2;
  16436. this.top = this.y - this.height / 2;
  16437. var yLabel;
  16438. if (this.imageObj.width != 0 ) {
  16439. // draw the shade
  16440. if (this.clusterSize > 1) {
  16441. var lineWidth = ((this.clusterSize > 1) ? 10 : 0.0);
  16442. lineWidth *= this.networkScaleInv;
  16443. lineWidth = Math.min(0.2 * this.width,lineWidth);
  16444. ctx.globalAlpha = 0.5;
  16445. ctx.drawImage(this.imageObj, this.left - lineWidth, this.top - lineWidth, this.width + 2*lineWidth, this.height + 2*lineWidth);
  16446. }
  16447. // draw the image
  16448. ctx.globalAlpha = 1.0;
  16449. ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height);
  16450. yLabel = this.y + this.height / 2;
  16451. }
  16452. else {
  16453. // image still loading... just draw the label for now
  16454. yLabel = this.y;
  16455. }
  16456. this._label(ctx, this.label, this.x, yLabel, undefined, "top");
  16457. };
  16458. Node.prototype._resizeBox = function (ctx) {
  16459. if (!this.width) {
  16460. var margin = 5;
  16461. var textSize = this.getTextSize(ctx);
  16462. this.width = textSize.width + 2 * margin;
  16463. this.height = textSize.height + 2 * margin;
  16464. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  16465. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  16466. this.growthIndicator = this.width - (textSize.width + 2 * margin);
  16467. // this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  16468. }
  16469. };
  16470. Node.prototype._drawBox = function (ctx) {
  16471. this._resizeBox(ctx);
  16472. this.left = this.x - this.width / 2;
  16473. this.top = this.y - this.height / 2;
  16474. var clusterLineWidth = 2.5;
  16475. var borderWidth = this.borderWidth;
  16476. var selectionLineWidth = this.borderWidthSelected || 2 * this.borderWidth;
  16477. ctx.strokeStyle = this.selected ? this.color.highlight.border : this.hover ? this.color.hover.border : this.color.border;
  16478. // draw the outer border
  16479. if (this.clusterSize > 1) {
  16480. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16481. ctx.lineWidth *= this.networkScaleInv;
  16482. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16483. ctx.roundRect(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth, this.radius);
  16484. ctx.stroke();
  16485. }
  16486. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16487. ctx.lineWidth *= this.networkScaleInv;
  16488. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16489. ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background;
  16490. ctx.roundRect(this.left, this.top, this.width, this.height, this.radius);
  16491. ctx.fill();
  16492. ctx.stroke();
  16493. this._label(ctx, this.label, this.x, this.y);
  16494. };
  16495. Node.prototype._resizeDatabase = function (ctx) {
  16496. if (!this.width) {
  16497. var margin = 5;
  16498. var textSize = this.getTextSize(ctx);
  16499. var size = textSize.width + 2 * margin;
  16500. this.width = size;
  16501. this.height = size;
  16502. // scaling used for clustering
  16503. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  16504. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  16505. this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  16506. this.growthIndicator = this.width - size;
  16507. }
  16508. };
  16509. Node.prototype._drawDatabase = function (ctx) {
  16510. this._resizeDatabase(ctx);
  16511. this.left = this.x - this.width / 2;
  16512. this.top = this.y - this.height / 2;
  16513. var clusterLineWidth = 2.5;
  16514. var borderWidth = this.borderWidth;
  16515. var selectionLineWidth = this.borderWidthSelected || 2 * this.borderWidth;
  16516. ctx.strokeStyle = this.selected ? this.color.highlight.border : this.hover ? this.color.hover.border : this.color.border;
  16517. // draw the outer border
  16518. if (this.clusterSize > 1) {
  16519. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16520. ctx.lineWidth *= this.networkScaleInv;
  16521. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16522. 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);
  16523. ctx.stroke();
  16524. }
  16525. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16526. ctx.lineWidth *= this.networkScaleInv;
  16527. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16528. ctx.fillStyle = this.selected ? this.color.highlight.background : this.hover ? this.color.hover.background : this.color.background;
  16529. ctx.database(this.x - this.width/2, this.y - this.height*0.5, this.width, this.height);
  16530. ctx.fill();
  16531. ctx.stroke();
  16532. this._label(ctx, this.label, this.x, this.y);
  16533. };
  16534. Node.prototype._resizeCircle = function (ctx) {
  16535. if (!this.width) {
  16536. var margin = 5;
  16537. var textSize = this.getTextSize(ctx);
  16538. var diameter = Math.max(textSize.width, textSize.height) + 2 * margin;
  16539. this.radius = diameter / 2;
  16540. this.width = diameter;
  16541. this.height = diameter;
  16542. // scaling used for clustering
  16543. // this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  16544. // this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  16545. this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  16546. this.growthIndicator = this.radius - 0.5*diameter;
  16547. }
  16548. };
  16549. Node.prototype._drawCircle = function (ctx) {
  16550. this._resizeCircle(ctx);
  16551. this.left = this.x - this.width / 2;
  16552. this.top = this.y - this.height / 2;
  16553. var clusterLineWidth = 2.5;
  16554. var borderWidth = this.borderWidth;
  16555. var selectionLineWidth = this.borderWidthSelected || 2 * this.borderWidth;
  16556. ctx.strokeStyle = this.selected ? this.color.highlight.border : this.hover ? this.color.hover.border : this.color.border;
  16557. // draw the outer border
  16558. if (this.clusterSize > 1) {
  16559. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16560. ctx.lineWidth *= this.networkScaleInv;
  16561. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16562. ctx.circle(this.x, this.y, this.radius+2*ctx.lineWidth);
  16563. ctx.stroke();
  16564. }
  16565. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16566. ctx.lineWidth *= this.networkScaleInv;
  16567. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16568. ctx.fillStyle = this.selected ? this.color.highlight.background : this.hover ? this.color.hover.background : this.color.background;
  16569. ctx.circle(this.x, this.y, this.radius);
  16570. ctx.fill();
  16571. ctx.stroke();
  16572. this._label(ctx, this.label, this.x, this.y);
  16573. };
  16574. Node.prototype._resizeEllipse = function (ctx) {
  16575. if (!this.width) {
  16576. var textSize = this.getTextSize(ctx);
  16577. this.width = textSize.width * 1.5;
  16578. this.height = textSize.height * 2;
  16579. if (this.width < this.height) {
  16580. this.width = this.height;
  16581. }
  16582. var defaultSize = this.width;
  16583. // scaling used for clustering
  16584. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  16585. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  16586. this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  16587. this.growthIndicator = this.width - defaultSize;
  16588. }
  16589. };
  16590. Node.prototype._drawEllipse = function (ctx) {
  16591. this._resizeEllipse(ctx);
  16592. this.left = this.x - this.width / 2;
  16593. this.top = this.y - this.height / 2;
  16594. var clusterLineWidth = 2.5;
  16595. var borderWidth = this.borderWidth;
  16596. var selectionLineWidth = this.borderWidthSelected || 2 * this.borderWidth;
  16597. ctx.strokeStyle = this.selected ? this.color.highlight.border : this.hover ? this.color.hover.border : this.color.border;
  16598. // draw the outer border
  16599. if (this.clusterSize > 1) {
  16600. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16601. ctx.lineWidth *= this.networkScaleInv;
  16602. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16603. ctx.ellipse(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth);
  16604. ctx.stroke();
  16605. }
  16606. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16607. ctx.lineWidth *= this.networkScaleInv;
  16608. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16609. ctx.fillStyle = this.selected ? this.color.highlight.background : this.hover ? this.color.hover.background : this.color.background;
  16610. ctx.ellipse(this.left, this.top, this.width, this.height);
  16611. ctx.fill();
  16612. ctx.stroke();
  16613. this._label(ctx, this.label, this.x, this.y);
  16614. };
  16615. Node.prototype._drawDot = function (ctx) {
  16616. this._drawShape(ctx, 'circle');
  16617. };
  16618. Node.prototype._drawTriangle = function (ctx) {
  16619. this._drawShape(ctx, 'triangle');
  16620. };
  16621. Node.prototype._drawTriangleDown = function (ctx) {
  16622. this._drawShape(ctx, 'triangleDown');
  16623. };
  16624. Node.prototype._drawSquare = function (ctx) {
  16625. this._drawShape(ctx, 'square');
  16626. };
  16627. Node.prototype._drawStar = function (ctx) {
  16628. this._drawShape(ctx, 'star');
  16629. };
  16630. Node.prototype._resizeShape = function (ctx) {
  16631. if (!this.width) {
  16632. this.radius = this.baseRadiusValue;
  16633. var size = 2 * this.radius;
  16634. this.width = size;
  16635. this.height = size;
  16636. // scaling used for clustering
  16637. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  16638. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  16639. this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  16640. this.growthIndicator = this.width - size;
  16641. }
  16642. };
  16643. Node.prototype._drawShape = function (ctx, shape) {
  16644. this._resizeShape(ctx);
  16645. this.left = this.x - this.width / 2;
  16646. this.top = this.y - this.height / 2;
  16647. var clusterLineWidth = 2.5;
  16648. var borderWidth = this.borderWidth;
  16649. var selectionLineWidth = this.borderWidthSelected || 2 * this.borderWidth;
  16650. var radiusMultiplier = 2;
  16651. // choose draw method depending on the shape
  16652. switch (shape) {
  16653. case 'dot': radiusMultiplier = 2; break;
  16654. case 'square': radiusMultiplier = 2; break;
  16655. case 'triangle': radiusMultiplier = 3; break;
  16656. case 'triangleDown': radiusMultiplier = 3; break;
  16657. case 'star': radiusMultiplier = 4; break;
  16658. }
  16659. ctx.strokeStyle = this.selected ? this.color.highlight.border : this.hover ? this.color.hover.border : this.color.border;
  16660. // draw the outer border
  16661. if (this.clusterSize > 1) {
  16662. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16663. ctx.lineWidth *= this.networkScaleInv;
  16664. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16665. ctx[shape](this.x, this.y, this.radius + radiusMultiplier * ctx.lineWidth);
  16666. ctx.stroke();
  16667. }
  16668. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  16669. ctx.lineWidth *= this.networkScaleInv;
  16670. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  16671. ctx.fillStyle = this.selected ? this.color.highlight.background : this.hover ? this.color.hover.background : this.color.background;
  16672. ctx[shape](this.x, this.y, this.radius);
  16673. ctx.fill();
  16674. ctx.stroke();
  16675. if (this.label) {
  16676. this._label(ctx, this.label, this.x, this.y + this.height / 2, undefined, 'top',true);
  16677. }
  16678. };
  16679. Node.prototype._resizeText = function (ctx) {
  16680. if (!this.width) {
  16681. var margin = 5;
  16682. var textSize = this.getTextSize(ctx);
  16683. this.width = textSize.width + 2 * margin;
  16684. this.height = textSize.height + 2 * margin;
  16685. // scaling used for clustering
  16686. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  16687. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  16688. this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  16689. this.growthIndicator = this.width - (textSize.width + 2 * margin);
  16690. }
  16691. };
  16692. Node.prototype._drawText = function (ctx) {
  16693. this._resizeText(ctx);
  16694. this.left = this.x - this.width / 2;
  16695. this.top = this.y - this.height / 2;
  16696. this._label(ctx, this.label, this.x, this.y);
  16697. };
  16698. Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNode) {
  16699. if (text && this.fontSize * this.networkScale > this.fontDrawThreshold) {
  16700. ctx.font = (this.selected ? "bold " : "") + this.fontSize + "px " + this.fontFace;
  16701. ctx.fillStyle = this.fontColor || "black";
  16702. ctx.textAlign = align || "center";
  16703. ctx.textBaseline = baseline || "middle";
  16704. var lines = text.split('\n');
  16705. var lineCount = lines.length;
  16706. var fontSize = (this.fontSize + 4);
  16707. var yLine = y + (1 - lineCount) / 2 * fontSize;
  16708. if (labelUnderNode == true) {
  16709. yLine = y + (1 - lineCount) / (2 * fontSize);
  16710. }
  16711. for (var i = 0; i < lineCount; i++) {
  16712. ctx.fillText(lines[i], x, yLine);
  16713. yLine += fontSize;
  16714. }
  16715. }
  16716. };
  16717. Node.prototype.getTextSize = function(ctx) {
  16718. if (this.label !== undefined) {
  16719. ctx.font = (this.selected ? "bold " : "") + this.fontSize + "px " + this.fontFace;
  16720. var lines = this.label.split('\n'),
  16721. height = (this.fontSize + 4) * lines.length,
  16722. width = 0;
  16723. for (var i = 0, iMax = lines.length; i < iMax; i++) {
  16724. width = Math.max(width, ctx.measureText(lines[i]).width);
  16725. }
  16726. return {"width": width, "height": height};
  16727. }
  16728. else {
  16729. return {"width": 0, "height": 0};
  16730. }
  16731. };
  16732. /**
  16733. * this is used to determine if a node is visible at all. this is used to determine when it needs to be drawn.
  16734. * there is a safety margin of 0.3 * width;
  16735. *
  16736. * @returns {boolean}
  16737. */
  16738. Node.prototype.inArea = function() {
  16739. if (this.width !== undefined) {
  16740. return (this.x + this.width *this.networkScaleInv >= this.canvasTopLeft.x &&
  16741. this.x - this.width *this.networkScaleInv < this.canvasBottomRight.x &&
  16742. this.y + this.height*this.networkScaleInv >= this.canvasTopLeft.y &&
  16743. this.y - this.height*this.networkScaleInv < this.canvasBottomRight.y);
  16744. }
  16745. else {
  16746. return true;
  16747. }
  16748. };
  16749. /**
  16750. * checks if the core of the node is in the display area, this is used for opening clusters around zoom
  16751. * @returns {boolean}
  16752. */
  16753. Node.prototype.inView = function() {
  16754. return (this.x >= this.canvasTopLeft.x &&
  16755. this.x < this.canvasBottomRight.x &&
  16756. this.y >= this.canvasTopLeft.y &&
  16757. this.y < this.canvasBottomRight.y);
  16758. };
  16759. /**
  16760. * This allows the zoom level of the network to influence the rendering
  16761. * We store the inverted scale and the coordinates of the top left, and bottom right points of the canvas
  16762. *
  16763. * @param scale
  16764. * @param canvasTopLeft
  16765. * @param canvasBottomRight
  16766. */
  16767. Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight) {
  16768. this.networkScaleInv = 1.0/scale;
  16769. this.networkScale = scale;
  16770. this.canvasTopLeft = canvasTopLeft;
  16771. this.canvasBottomRight = canvasBottomRight;
  16772. };
  16773. /**
  16774. * This allows the zoom level of the network to influence the rendering
  16775. *
  16776. * @param scale
  16777. */
  16778. Node.prototype.setScale = function(scale) {
  16779. this.networkScaleInv = 1.0/scale;
  16780. this.networkScale = scale;
  16781. };
  16782. /**
  16783. * set the velocity at 0. Is called when this node is contained in another during clustering
  16784. */
  16785. Node.prototype.clearVelocity = function() {
  16786. this.vx = 0;
  16787. this.vy = 0;
  16788. };
  16789. /**
  16790. * Basic preservation of (kinectic) energy
  16791. *
  16792. * @param massBeforeClustering
  16793. */
  16794. Node.prototype.updateVelocity = function(massBeforeClustering) {
  16795. var energyBefore = this.vx * this.vx * massBeforeClustering;
  16796. //this.vx = (this.vx < 0) ? -Math.sqrt(energyBefore/this.mass) : Math.sqrt(energyBefore/this.mass);
  16797. this.vx = Math.sqrt(energyBefore/this.mass);
  16798. energyBefore = this.vy * this.vy * massBeforeClustering;
  16799. //this.vy = (this.vy < 0) ? -Math.sqrt(energyBefore/this.mass) : Math.sqrt(energyBefore/this.mass);
  16800. this.vy = Math.sqrt(energyBefore/this.mass);
  16801. };
  16802. module.exports = Node;
  16803. /***/ },
  16804. /* 37 */
  16805. /***/ function(module, exports, __webpack_require__) {
  16806. /**
  16807. * Popup is a class to create a popup window with some text
  16808. * @param {Element} container The container object.
  16809. * @param {Number} [x]
  16810. * @param {Number} [y]
  16811. * @param {String} [text]
  16812. * @param {Object} [style] An object containing borderColor,
  16813. * backgroundColor, etc.
  16814. */
  16815. function Popup(container, x, y, text, style) {
  16816. if (container) {
  16817. this.container = container;
  16818. }
  16819. else {
  16820. this.container = document.body;
  16821. }
  16822. // x, y and text are optional, see if a style object was passed in their place
  16823. if (style === undefined) {
  16824. if (typeof x === "object") {
  16825. style = x;
  16826. x = undefined;
  16827. } else if (typeof text === "object") {
  16828. style = text;
  16829. text = undefined;
  16830. } else {
  16831. // for backwards compatibility, in case clients other than Network are creating Popup directly
  16832. style = {
  16833. fontColor: 'black',
  16834. fontSize: 14, // px
  16835. fontFace: 'verdana',
  16836. color: {
  16837. border: '#666',
  16838. background: '#FFFFC6'
  16839. }
  16840. }
  16841. }
  16842. }
  16843. this.x = 0;
  16844. this.y = 0;
  16845. this.padding = 5;
  16846. if (x !== undefined && y !== undefined ) {
  16847. this.setPosition(x, y);
  16848. }
  16849. if (text !== undefined) {
  16850. this.setText(text);
  16851. }
  16852. // create the frame
  16853. this.frame = document.createElement("div");
  16854. var styleAttr = this.frame.style;
  16855. styleAttr.position = "absolute";
  16856. styleAttr.visibility = "hidden";
  16857. styleAttr.border = "1px solid " + style.color.border;
  16858. styleAttr.color = style.fontColor;
  16859. styleAttr.fontSize = style.fontSize + "px";
  16860. styleAttr.fontFamily = style.fontFace;
  16861. styleAttr.padding = this.padding + "px";
  16862. styleAttr.backgroundColor = style.color.background;
  16863. styleAttr.borderRadius = "3px";
  16864. styleAttr.MozBorderRadius = "3px";
  16865. styleAttr.WebkitBorderRadius = "3px";
  16866. styleAttr.boxShadow = "3px 3px 10px rgba(128, 128, 128, 0.5)";
  16867. styleAttr.whiteSpace = "nowrap";
  16868. this.container.appendChild(this.frame);
  16869. }
  16870. /**
  16871. * @param {number} x Horizontal position of the popup window
  16872. * @param {number} y Vertical position of the popup window
  16873. */
  16874. Popup.prototype.setPosition = function(x, y) {
  16875. this.x = parseInt(x);
  16876. this.y = parseInt(y);
  16877. };
  16878. /**
  16879. * Set the text for the popup window. This can be HTML code
  16880. * @param {string} text
  16881. */
  16882. Popup.prototype.setText = function(text) {
  16883. this.frame.innerHTML = text;
  16884. };
  16885. /**
  16886. * Show the popup window
  16887. * @param {boolean} show Optional. Show or hide the window
  16888. */
  16889. Popup.prototype.show = function (show) {
  16890. if (show === undefined) {
  16891. show = true;
  16892. }
  16893. if (show) {
  16894. var height = this.frame.clientHeight;
  16895. var width = this.frame.clientWidth;
  16896. var maxHeight = this.frame.parentNode.clientHeight;
  16897. var maxWidth = this.frame.parentNode.clientWidth;
  16898. var top = (this.y - height);
  16899. if (top + height + this.padding > maxHeight) {
  16900. top = maxHeight - height - this.padding;
  16901. }
  16902. if (top < this.padding) {
  16903. top = this.padding;
  16904. }
  16905. var left = this.x;
  16906. if (left + width + this.padding > maxWidth) {
  16907. left = maxWidth - width - this.padding;
  16908. }
  16909. if (left < this.padding) {
  16910. left = this.padding;
  16911. }
  16912. this.frame.style.left = left + "px";
  16913. this.frame.style.top = top + "px";
  16914. this.frame.style.visibility = "visible";
  16915. }
  16916. else {
  16917. this.hide();
  16918. }
  16919. };
  16920. /**
  16921. * Hide the popup window
  16922. */
  16923. Popup.prototype.hide = function () {
  16924. this.frame.style.visibility = "hidden";
  16925. };
  16926. module.exports = Popup;
  16927. /***/ },
  16928. /* 38 */
  16929. /***/ function(module, exports, __webpack_require__) {
  16930. /**
  16931. * Parse a text source containing data in DOT language into a JSON object.
  16932. * The object contains two lists: one with nodes and one with edges.
  16933. *
  16934. * DOT language reference: http://www.graphviz.org/doc/info/lang.html
  16935. *
  16936. * @param {String} data Text containing a graph in DOT-notation
  16937. * @return {Object} graph An object containing two parameters:
  16938. * {Object[]} nodes
  16939. * {Object[]} edges
  16940. */
  16941. function parseDOT (data) {
  16942. dot = data;
  16943. return parseGraph();
  16944. }
  16945. // token types enumeration
  16946. var TOKENTYPE = {
  16947. NULL : 0,
  16948. DELIMITER : 1,
  16949. IDENTIFIER: 2,
  16950. UNKNOWN : 3
  16951. };
  16952. // map with all delimiters
  16953. var DELIMITERS = {
  16954. '{': true,
  16955. '}': true,
  16956. '[': true,
  16957. ']': true,
  16958. ';': true,
  16959. '=': true,
  16960. ',': true,
  16961. '->': true,
  16962. '--': true
  16963. };
  16964. var dot = ''; // current dot file
  16965. var index = 0; // current index in dot file
  16966. var c = ''; // current token character in expr
  16967. var token = ''; // current token
  16968. var tokenType = TOKENTYPE.NULL; // type of the token
  16969. /**
  16970. * Get the first character from the dot file.
  16971. * The character is stored into the char c. If the end of the dot file is
  16972. * reached, the function puts an empty string in c.
  16973. */
  16974. function first() {
  16975. index = 0;
  16976. c = dot.charAt(0);
  16977. }
  16978. /**
  16979. * Get the next character from the dot file.
  16980. * The character is stored into the char c. If the end of the dot file is
  16981. * reached, the function puts an empty string in c.
  16982. */
  16983. function next() {
  16984. index++;
  16985. c = dot.charAt(index);
  16986. }
  16987. /**
  16988. * Preview the next character from the dot file.
  16989. * @return {String} cNext
  16990. */
  16991. function nextPreview() {
  16992. return dot.charAt(index + 1);
  16993. }
  16994. /**
  16995. * Test whether given character is alphabetic or numeric
  16996. * @param {String} c
  16997. * @return {Boolean} isAlphaNumeric
  16998. */
  16999. var regexAlphaNumeric = /[a-zA-Z_0-9.:#]/;
  17000. function isAlphaNumeric(c) {
  17001. return regexAlphaNumeric.test(c);
  17002. }
  17003. /**
  17004. * Merge all properties of object b into object b
  17005. * @param {Object} a
  17006. * @param {Object} b
  17007. * @return {Object} a
  17008. */
  17009. function merge (a, b) {
  17010. if (!a) {
  17011. a = {};
  17012. }
  17013. if (b) {
  17014. for (var name in b) {
  17015. if (b.hasOwnProperty(name)) {
  17016. a[name] = b[name];
  17017. }
  17018. }
  17019. }
  17020. return a;
  17021. }
  17022. /**
  17023. * Set a value in an object, where the provided parameter name can be a
  17024. * path with nested parameters. For example:
  17025. *
  17026. * var obj = {a: 2};
  17027. * setValue(obj, 'b.c', 3); // obj = {a: 2, b: {c: 3}}
  17028. *
  17029. * @param {Object} obj
  17030. * @param {String} path A parameter name or dot-separated parameter path,
  17031. * like "color.highlight.border".
  17032. * @param {*} value
  17033. */
  17034. function setValue(obj, path, value) {
  17035. var keys = path.split('.');
  17036. var o = obj;
  17037. while (keys.length) {
  17038. var key = keys.shift();
  17039. if (keys.length) {
  17040. // this isn't the end point
  17041. if (!o[key]) {
  17042. o[key] = {};
  17043. }
  17044. o = o[key];
  17045. }
  17046. else {
  17047. // this is the end point
  17048. o[key] = value;
  17049. }
  17050. }
  17051. }
  17052. /**
  17053. * Add a node to a graph object. If there is already a node with
  17054. * the same id, their attributes will be merged.
  17055. * @param {Object} graph
  17056. * @param {Object} node
  17057. */
  17058. function addNode(graph, node) {
  17059. var i, len;
  17060. var current = null;
  17061. // find root graph (in case of subgraph)
  17062. var graphs = [graph]; // list with all graphs from current graph to root graph
  17063. var root = graph;
  17064. while (root.parent) {
  17065. graphs.push(root.parent);
  17066. root = root.parent;
  17067. }
  17068. // find existing node (at root level) by its id
  17069. if (root.nodes) {
  17070. for (i = 0, len = root.nodes.length; i < len; i++) {
  17071. if (node.id === root.nodes[i].id) {
  17072. current = root.nodes[i];
  17073. break;
  17074. }
  17075. }
  17076. }
  17077. if (!current) {
  17078. // this is a new node
  17079. current = {
  17080. id: node.id
  17081. };
  17082. if (graph.node) {
  17083. // clone default attributes
  17084. current.attr = merge(current.attr, graph.node);
  17085. }
  17086. }
  17087. // add node to this (sub)graph and all its parent graphs
  17088. for (i = graphs.length - 1; i >= 0; i--) {
  17089. var g = graphs[i];
  17090. if (!g.nodes) {
  17091. g.nodes = [];
  17092. }
  17093. if (g.nodes.indexOf(current) == -1) {
  17094. g.nodes.push(current);
  17095. }
  17096. }
  17097. // merge attributes
  17098. if (node.attr) {
  17099. current.attr = merge(current.attr, node.attr);
  17100. }
  17101. }
  17102. /**
  17103. * Add an edge to a graph object
  17104. * @param {Object} graph
  17105. * @param {Object} edge
  17106. */
  17107. function addEdge(graph, edge) {
  17108. if (!graph.edges) {
  17109. graph.edges = [];
  17110. }
  17111. graph.edges.push(edge);
  17112. if (graph.edge) {
  17113. var attr = merge({}, graph.edge); // clone default attributes
  17114. edge.attr = merge(attr, edge.attr); // merge attributes
  17115. }
  17116. }
  17117. /**
  17118. * Create an edge to a graph object
  17119. * @param {Object} graph
  17120. * @param {String | Number | Object} from
  17121. * @param {String | Number | Object} to
  17122. * @param {String} type
  17123. * @param {Object | null} attr
  17124. * @return {Object} edge
  17125. */
  17126. function createEdge(graph, from, to, type, attr) {
  17127. var edge = {
  17128. from: from,
  17129. to: to,
  17130. type: type
  17131. };
  17132. if (graph.edge) {
  17133. edge.attr = merge({}, graph.edge); // clone default attributes
  17134. }
  17135. edge.attr = merge(edge.attr || {}, attr); // merge attributes
  17136. return edge;
  17137. }
  17138. /**
  17139. * Get next token in the current dot file.
  17140. * The token and token type are available as token and tokenType
  17141. */
  17142. function getToken() {
  17143. tokenType = TOKENTYPE.NULL;
  17144. token = '';
  17145. // skip over whitespaces
  17146. while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter
  17147. next();
  17148. }
  17149. do {
  17150. var isComment = false;
  17151. // skip comment
  17152. if (c == '#') {
  17153. // find the previous non-space character
  17154. var i = index - 1;
  17155. while (dot.charAt(i) == ' ' || dot.charAt(i) == '\t') {
  17156. i--;
  17157. }
  17158. if (dot.charAt(i) == '\n' || dot.charAt(i) == '') {
  17159. // the # is at the start of a line, this is indeed a line comment
  17160. while (c != '' && c != '\n') {
  17161. next();
  17162. }
  17163. isComment = true;
  17164. }
  17165. }
  17166. if (c == '/' && nextPreview() == '/') {
  17167. // skip line comment
  17168. while (c != '' && c != '\n') {
  17169. next();
  17170. }
  17171. isComment = true;
  17172. }
  17173. if (c == '/' && nextPreview() == '*') {
  17174. // skip block comment
  17175. while (c != '') {
  17176. if (c == '*' && nextPreview() == '/') {
  17177. // end of block comment found. skip these last two characters
  17178. next();
  17179. next();
  17180. break;
  17181. }
  17182. else {
  17183. next();
  17184. }
  17185. }
  17186. isComment = true;
  17187. }
  17188. // skip over whitespaces
  17189. while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter
  17190. next();
  17191. }
  17192. }
  17193. while (isComment);
  17194. // check for end of dot file
  17195. if (c == '') {
  17196. // token is still empty
  17197. tokenType = TOKENTYPE.DELIMITER;
  17198. return;
  17199. }
  17200. // check for delimiters consisting of 2 characters
  17201. var c2 = c + nextPreview();
  17202. if (DELIMITERS[c2]) {
  17203. tokenType = TOKENTYPE.DELIMITER;
  17204. token = c2;
  17205. next();
  17206. next();
  17207. return;
  17208. }
  17209. // check for delimiters consisting of 1 character
  17210. if (DELIMITERS[c]) {
  17211. tokenType = TOKENTYPE.DELIMITER;
  17212. token = c;
  17213. next();
  17214. return;
  17215. }
  17216. // check for an identifier (number or string)
  17217. // TODO: more precise parsing of numbers/strings (and the port separator ':')
  17218. if (isAlphaNumeric(c) || c == '-') {
  17219. token += c;
  17220. next();
  17221. while (isAlphaNumeric(c)) {
  17222. token += c;
  17223. next();
  17224. }
  17225. if (token == 'false') {
  17226. token = false; // convert to boolean
  17227. }
  17228. else if (token == 'true') {
  17229. token = true; // convert to boolean
  17230. }
  17231. else if (!isNaN(Number(token))) {
  17232. token = Number(token); // convert to number
  17233. }
  17234. tokenType = TOKENTYPE.IDENTIFIER;
  17235. return;
  17236. }
  17237. // check for a string enclosed by double quotes
  17238. if (c == '"') {
  17239. next();
  17240. while (c != '' && (c != '"' || (c == '"' && nextPreview() == '"'))) {
  17241. token += c;
  17242. if (c == '"') { // skip the escape character
  17243. next();
  17244. }
  17245. next();
  17246. }
  17247. if (c != '"') {
  17248. throw newSyntaxError('End of string " expected');
  17249. }
  17250. next();
  17251. tokenType = TOKENTYPE.IDENTIFIER;
  17252. return;
  17253. }
  17254. // something unknown is found, wrong characters, a syntax error
  17255. tokenType = TOKENTYPE.UNKNOWN;
  17256. while (c != '') {
  17257. token += c;
  17258. next();
  17259. }
  17260. throw new SyntaxError('Syntax error in part "' + chop(token, 30) + '"');
  17261. }
  17262. /**
  17263. * Parse a graph.
  17264. * @returns {Object} graph
  17265. */
  17266. function parseGraph() {
  17267. var graph = {};
  17268. first();
  17269. getToken();
  17270. // optional strict keyword
  17271. if (token == 'strict') {
  17272. graph.strict = true;
  17273. getToken();
  17274. }
  17275. // graph or digraph keyword
  17276. if (token == 'graph' || token == 'digraph') {
  17277. graph.type = token;
  17278. getToken();
  17279. }
  17280. // optional graph id
  17281. if (tokenType == TOKENTYPE.IDENTIFIER) {
  17282. graph.id = token;
  17283. getToken();
  17284. }
  17285. // open angle bracket
  17286. if (token != '{') {
  17287. throw newSyntaxError('Angle bracket { expected');
  17288. }
  17289. getToken();
  17290. // statements
  17291. parseStatements(graph);
  17292. // close angle bracket
  17293. if (token != '}') {
  17294. throw newSyntaxError('Angle bracket } expected');
  17295. }
  17296. getToken();
  17297. // end of file
  17298. if (token !== '') {
  17299. throw newSyntaxError('End of file expected');
  17300. }
  17301. getToken();
  17302. // remove temporary default properties
  17303. delete graph.node;
  17304. delete graph.edge;
  17305. delete graph.graph;
  17306. return graph;
  17307. }
  17308. /**
  17309. * Parse a list with statements.
  17310. * @param {Object} graph
  17311. */
  17312. function parseStatements (graph) {
  17313. while (token !== '' && token != '}') {
  17314. parseStatement(graph);
  17315. if (token == ';') {
  17316. getToken();
  17317. }
  17318. }
  17319. }
  17320. /**
  17321. * Parse a single statement. Can be a an attribute statement, node
  17322. * statement, a series of node statements and edge statements, or a
  17323. * parameter.
  17324. * @param {Object} graph
  17325. */
  17326. function parseStatement(graph) {
  17327. // parse subgraph
  17328. var subgraph = parseSubgraph(graph);
  17329. if (subgraph) {
  17330. // edge statements
  17331. parseEdge(graph, subgraph);
  17332. return;
  17333. }
  17334. // parse an attribute statement
  17335. var attr = parseAttributeStatement(graph);
  17336. if (attr) {
  17337. return;
  17338. }
  17339. // parse node
  17340. if (tokenType != TOKENTYPE.IDENTIFIER) {
  17341. throw newSyntaxError('Identifier expected');
  17342. }
  17343. var id = token; // id can be a string or a number
  17344. getToken();
  17345. if (token == '=') {
  17346. // id statement
  17347. getToken();
  17348. if (tokenType != TOKENTYPE.IDENTIFIER) {
  17349. throw newSyntaxError('Identifier expected');
  17350. }
  17351. graph[id] = token;
  17352. getToken();
  17353. // TODO: implement comma separated list with "a_list: ID=ID [','] [a_list] "
  17354. }
  17355. else {
  17356. parseNodeStatement(graph, id);
  17357. }
  17358. }
  17359. /**
  17360. * Parse a subgraph
  17361. * @param {Object} graph parent graph object
  17362. * @return {Object | null} subgraph
  17363. */
  17364. function parseSubgraph (graph) {
  17365. var subgraph = null;
  17366. // optional subgraph keyword
  17367. if (token == 'subgraph') {
  17368. subgraph = {};
  17369. subgraph.type = 'subgraph';
  17370. getToken();
  17371. // optional graph id
  17372. if (tokenType == TOKENTYPE.IDENTIFIER) {
  17373. subgraph.id = token;
  17374. getToken();
  17375. }
  17376. }
  17377. // open angle bracket
  17378. if (token == '{') {
  17379. getToken();
  17380. if (!subgraph) {
  17381. subgraph = {};
  17382. }
  17383. subgraph.parent = graph;
  17384. subgraph.node = graph.node;
  17385. subgraph.edge = graph.edge;
  17386. subgraph.graph = graph.graph;
  17387. // statements
  17388. parseStatements(subgraph);
  17389. // close angle bracket
  17390. if (token != '}') {
  17391. throw newSyntaxError('Angle bracket } expected');
  17392. }
  17393. getToken();
  17394. // remove temporary default properties
  17395. delete subgraph.node;
  17396. delete subgraph.edge;
  17397. delete subgraph.graph;
  17398. delete subgraph.parent;
  17399. // register at the parent graph
  17400. if (!graph.subgraphs) {
  17401. graph.subgraphs = [];
  17402. }
  17403. graph.subgraphs.push(subgraph);
  17404. }
  17405. return subgraph;
  17406. }
  17407. /**
  17408. * parse an attribute statement like "node [shape=circle fontSize=16]".
  17409. * Available keywords are 'node', 'edge', 'graph'.
  17410. * The previous list with default attributes will be replaced
  17411. * @param {Object} graph
  17412. * @returns {String | null} keyword Returns the name of the parsed attribute
  17413. * (node, edge, graph), or null if nothing
  17414. * is parsed.
  17415. */
  17416. function parseAttributeStatement (graph) {
  17417. // attribute statements
  17418. if (token == 'node') {
  17419. getToken();
  17420. // node attributes
  17421. graph.node = parseAttributeList();
  17422. return 'node';
  17423. }
  17424. else if (token == 'edge') {
  17425. getToken();
  17426. // edge attributes
  17427. graph.edge = parseAttributeList();
  17428. return 'edge';
  17429. }
  17430. else if (token == 'graph') {
  17431. getToken();
  17432. // graph attributes
  17433. graph.graph = parseAttributeList();
  17434. return 'graph';
  17435. }
  17436. return null;
  17437. }
  17438. /**
  17439. * parse a node statement
  17440. * @param {Object} graph
  17441. * @param {String | Number} id
  17442. */
  17443. function parseNodeStatement(graph, id) {
  17444. // node statement
  17445. var node = {
  17446. id: id
  17447. };
  17448. var attr = parseAttributeList();
  17449. if (attr) {
  17450. node.attr = attr;
  17451. }
  17452. addNode(graph, node);
  17453. // edge statements
  17454. parseEdge(graph, id);
  17455. }
  17456. /**
  17457. * Parse an edge or a series of edges
  17458. * @param {Object} graph
  17459. * @param {String | Number} from Id of the from node
  17460. */
  17461. function parseEdge(graph, from) {
  17462. while (token == '->' || token == '--') {
  17463. var to;
  17464. var type = token;
  17465. getToken();
  17466. var subgraph = parseSubgraph(graph);
  17467. if (subgraph) {
  17468. to = subgraph;
  17469. }
  17470. else {
  17471. if (tokenType != TOKENTYPE.IDENTIFIER) {
  17472. throw newSyntaxError('Identifier or subgraph expected');
  17473. }
  17474. to = token;
  17475. addNode(graph, {
  17476. id: to
  17477. });
  17478. getToken();
  17479. }
  17480. // parse edge attributes
  17481. var attr = parseAttributeList();
  17482. // create edge
  17483. var edge = createEdge(graph, from, to, type, attr);
  17484. addEdge(graph, edge);
  17485. from = to;
  17486. }
  17487. }
  17488. /**
  17489. * Parse a set with attributes,
  17490. * for example [label="1.000", shape=solid]
  17491. * @return {Object | null} attr
  17492. */
  17493. function parseAttributeList() {
  17494. var attr = null;
  17495. while (token == '[') {
  17496. getToken();
  17497. attr = {};
  17498. while (token !== '' && token != ']') {
  17499. if (tokenType != TOKENTYPE.IDENTIFIER) {
  17500. throw newSyntaxError('Attribute name expected');
  17501. }
  17502. var name = token;
  17503. getToken();
  17504. if (token != '=') {
  17505. throw newSyntaxError('Equal sign = expected');
  17506. }
  17507. getToken();
  17508. if (tokenType != TOKENTYPE.IDENTIFIER) {
  17509. throw newSyntaxError('Attribute value expected');
  17510. }
  17511. var value = token;
  17512. setValue(attr, name, value); // name can be a path
  17513. getToken();
  17514. if (token ==',') {
  17515. getToken();
  17516. }
  17517. }
  17518. if (token != ']') {
  17519. throw newSyntaxError('Bracket ] expected');
  17520. }
  17521. getToken();
  17522. }
  17523. return attr;
  17524. }
  17525. /**
  17526. * Create a syntax error with extra information on current token and index.
  17527. * @param {String} message
  17528. * @returns {SyntaxError} err
  17529. */
  17530. function newSyntaxError(message) {
  17531. return new SyntaxError(message + ', got "' + chop(token, 30) + '" (char ' + index + ')');
  17532. }
  17533. /**
  17534. * Chop off text after a maximum length
  17535. * @param {String} text
  17536. * @param {Number} maxLength
  17537. * @returns {String}
  17538. */
  17539. function chop (text, maxLength) {
  17540. return (text.length <= maxLength) ? text : (text.substr(0, 27) + '...');
  17541. }
  17542. /**
  17543. * Execute a function fn for each pair of elements in two arrays
  17544. * @param {Array | *} array1
  17545. * @param {Array | *} array2
  17546. * @param {function} fn
  17547. */
  17548. function forEach2(array1, array2, fn) {
  17549. if (array1 instanceof Array) {
  17550. array1.forEach(function (elem1) {
  17551. if (array2 instanceof Array) {
  17552. array2.forEach(function (elem2) {
  17553. fn(elem1, elem2);
  17554. });
  17555. }
  17556. else {
  17557. fn(elem1, array2);
  17558. }
  17559. });
  17560. }
  17561. else {
  17562. if (array2 instanceof Array) {
  17563. array2.forEach(function (elem2) {
  17564. fn(array1, elem2);
  17565. });
  17566. }
  17567. else {
  17568. fn(array1, array2);
  17569. }
  17570. }
  17571. }
  17572. /**
  17573. * Convert a string containing a graph in DOT language into a map containing
  17574. * with nodes and edges in the format of graph.
  17575. * @param {String} data Text containing a graph in DOT-notation
  17576. * @return {Object} graphData
  17577. */
  17578. function DOTToGraph (data) {
  17579. // parse the DOT file
  17580. var dotData = parseDOT(data);
  17581. var graphData = {
  17582. nodes: [],
  17583. edges: [],
  17584. options: {}
  17585. };
  17586. // copy the nodes
  17587. if (dotData.nodes) {
  17588. dotData.nodes.forEach(function (dotNode) {
  17589. var graphNode = {
  17590. id: dotNode.id,
  17591. label: String(dotNode.label || dotNode.id)
  17592. };
  17593. merge(graphNode, dotNode.attr);
  17594. if (graphNode.image) {
  17595. graphNode.shape = 'image';
  17596. }
  17597. graphData.nodes.push(graphNode);
  17598. });
  17599. }
  17600. // copy the edges
  17601. if (dotData.edges) {
  17602. /**
  17603. * Convert an edge in DOT format to an edge with VisGraph format
  17604. * @param {Object} dotEdge
  17605. * @returns {Object} graphEdge
  17606. */
  17607. function convertEdge(dotEdge) {
  17608. var graphEdge = {
  17609. from: dotEdge.from,
  17610. to: dotEdge.to
  17611. };
  17612. merge(graphEdge, dotEdge.attr);
  17613. graphEdge.style = (dotEdge.type == '->') ? 'arrow' : 'line';
  17614. return graphEdge;
  17615. }
  17616. dotData.edges.forEach(function (dotEdge) {
  17617. var from, to;
  17618. if (dotEdge.from instanceof Object) {
  17619. from = dotEdge.from.nodes;
  17620. }
  17621. else {
  17622. from = {
  17623. id: dotEdge.from
  17624. }
  17625. }
  17626. if (dotEdge.to instanceof Object) {
  17627. to = dotEdge.to.nodes;
  17628. }
  17629. else {
  17630. to = {
  17631. id: dotEdge.to
  17632. }
  17633. }
  17634. if (dotEdge.from instanceof Object && dotEdge.from.edges) {
  17635. dotEdge.from.edges.forEach(function (subEdge) {
  17636. var graphEdge = convertEdge(subEdge);
  17637. graphData.edges.push(graphEdge);
  17638. });
  17639. }
  17640. forEach2(from, to, function (from, to) {
  17641. var subEdge = createEdge(graphData, from.id, to.id, dotEdge.type, dotEdge.attr);
  17642. var graphEdge = convertEdge(subEdge);
  17643. graphData.edges.push(graphEdge);
  17644. });
  17645. if (dotEdge.to instanceof Object && dotEdge.to.edges) {
  17646. dotEdge.to.edges.forEach(function (subEdge) {
  17647. var graphEdge = convertEdge(subEdge);
  17648. graphData.edges.push(graphEdge);
  17649. });
  17650. }
  17651. });
  17652. }
  17653. // copy the options
  17654. if (dotData.attr) {
  17655. graphData.options = dotData.attr;
  17656. }
  17657. return graphData;
  17658. }
  17659. // exports
  17660. exports.parseDOT = parseDOT;
  17661. exports.DOTToGraph = DOTToGraph;
  17662. /***/ },
  17663. /* 39 */
  17664. /***/ function(module, exports, __webpack_require__) {
  17665. function parseGephi(gephiJSON, options) {
  17666. var edges = [];
  17667. var nodes = [];
  17668. this.options = {
  17669. edges: {
  17670. inheritColor: true
  17671. },
  17672. nodes: {
  17673. allowedToMove: false,
  17674. parseColor: false
  17675. }
  17676. };
  17677. if (options !== undefined) {
  17678. this.options.nodes['allowedToMove'] = options.allowedToMove | false;
  17679. this.options.nodes['parseColor'] = options.parseColor | false;
  17680. this.options.edges['inheritColor'] = options.inheritColor | true;
  17681. }
  17682. var gEdges = gephiJSON.edges;
  17683. var gNodes = gephiJSON.nodes;
  17684. for (var i = 0; i < gEdges.length; i++) {
  17685. var edge = {};
  17686. var gEdge = gEdges[i];
  17687. edge['id'] = gEdge.id;
  17688. edge['from'] = gEdge.source;
  17689. edge['to'] = gEdge.target;
  17690. edge['attributes'] = gEdge.attributes;
  17691. // edge['value'] = gEdge.attributes !== undefined ? gEdge.attributes.Weight : undefined;
  17692. // edge['width'] = edge['value'] !== undefined ? undefined : edgegEdge.size;
  17693. edge['color'] = gEdge.color;
  17694. edge['inheritColor'] = edge['color'] !== undefined ? false : this.options.inheritColor;
  17695. edges.push(edge);
  17696. }
  17697. for (var i = 0; i < gNodes.length; i++) {
  17698. var node = {};
  17699. var gNode = gNodes[i];
  17700. node['id'] = gNode.id;
  17701. node['attributes'] = gNode.attributes;
  17702. node['x'] = gNode.x;
  17703. node['y'] = gNode.y;
  17704. node['label'] = gNode.label;
  17705. if (this.options.nodes.parseColor == true) {
  17706. node['color'] = gNode.color;
  17707. }
  17708. else {
  17709. node['color'] = gNode.color !== undefined ? {background:gNode.color, border:gNode.color} : undefined;
  17710. }
  17711. node['radius'] = gNode.size;
  17712. node['allowedToMoveX'] = this.options.nodes.allowedToMove;
  17713. node['allowedToMoveY'] = this.options.nodes.allowedToMove;
  17714. nodes.push(node);
  17715. }
  17716. return {nodes:nodes, edges:edges};
  17717. }
  17718. exports.parseGephi = parseGephi;
  17719. /***/ },
  17720. /* 40 */
  17721. /***/ function(module, exports, __webpack_require__) {
  17722. // first check if moment.js is already loaded in the browser window, if so,
  17723. // use this instance. Else, load via commonjs.
  17724. module.exports = (typeof window !== 'undefined') && window['moment'] || __webpack_require__(46);
  17725. /***/ },
  17726. /* 41 */
  17727. /***/ function(module, exports, __webpack_require__) {
  17728. // Only load hammer.js when in a browser environment
  17729. // (loading hammer.js in a node.js environment gives errors)
  17730. if (typeof window !== 'undefined') {
  17731. module.exports = window['Hammer'] || __webpack_require__(48);
  17732. }
  17733. else {
  17734. module.exports = function () {
  17735. throw Error('hammer.js is only available in a browser, not in node.js.');
  17736. }
  17737. }
  17738. /***/ },
  17739. /* 42 */
  17740. /***/ function(module, exports, __webpack_require__) {
  17741. var Hammer = __webpack_require__(41);
  17742. /**
  17743. * Fake a hammer.js gesture. Event can be a ScrollEvent or MouseMoveEvent
  17744. * @param {Element} element
  17745. * @param {Event} event
  17746. */
  17747. exports.fakeGesture = function(element, event) {
  17748. var eventType = null;
  17749. // for hammer.js 1.0.5
  17750. // var gesture = Hammer.event.collectEventData(this, eventType, event);
  17751. // for hammer.js 1.0.6+
  17752. var touches = Hammer.event.getTouchList(event, eventType);
  17753. var gesture = Hammer.event.collectEventData(this, eventType, touches, event);
  17754. // on IE in standards mode, no touches are recognized by hammer.js,
  17755. // resulting in NaN values for center.pageX and center.pageY
  17756. if (isNaN(gesture.center.pageX)) {
  17757. gesture.center.pageX = event.pageX;
  17758. }
  17759. if (isNaN(gesture.center.pageY)) {
  17760. gesture.center.pageY = event.pageY;
  17761. }
  17762. return gesture;
  17763. };
  17764. /***/ },
  17765. /* 43 */
  17766. /***/ function(module, exports, __webpack_require__) {
  17767. /**
  17768. * Canvas shapes used by Network
  17769. */
  17770. if (typeof CanvasRenderingContext2D !== 'undefined') {
  17771. /**
  17772. * Draw a circle shape
  17773. */
  17774. CanvasRenderingContext2D.prototype.circle = function(x, y, r) {
  17775. this.beginPath();
  17776. this.arc(x, y, r, 0, 2*Math.PI, false);
  17777. };
  17778. /**
  17779. * Draw a square shape
  17780. * @param {Number} x horizontal center
  17781. * @param {Number} y vertical center
  17782. * @param {Number} r size, width and height of the square
  17783. */
  17784. CanvasRenderingContext2D.prototype.square = function(x, y, r) {
  17785. this.beginPath();
  17786. this.rect(x - r, y - r, r * 2, r * 2);
  17787. };
  17788. /**
  17789. * Draw a triangle shape
  17790. * @param {Number} x horizontal center
  17791. * @param {Number} y vertical center
  17792. * @param {Number} r radius, half the length of the sides of the triangle
  17793. */
  17794. CanvasRenderingContext2D.prototype.triangle = function(x, y, r) {
  17795. // http://en.wikipedia.org/wiki/Equilateral_triangle
  17796. this.beginPath();
  17797. var s = r * 2;
  17798. var s2 = s / 2;
  17799. var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
  17800. var h = Math.sqrt(s * s - s2 * s2); // height
  17801. this.moveTo(x, y - (h - ir));
  17802. this.lineTo(x + s2, y + ir);
  17803. this.lineTo(x - s2, y + ir);
  17804. this.lineTo(x, y - (h - ir));
  17805. this.closePath();
  17806. };
  17807. /**
  17808. * Draw a triangle shape in downward orientation
  17809. * @param {Number} x horizontal center
  17810. * @param {Number} y vertical center
  17811. * @param {Number} r radius
  17812. */
  17813. CanvasRenderingContext2D.prototype.triangleDown = function(x, y, r) {
  17814. // http://en.wikipedia.org/wiki/Equilateral_triangle
  17815. this.beginPath();
  17816. var s = r * 2;
  17817. var s2 = s / 2;
  17818. var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
  17819. var h = Math.sqrt(s * s - s2 * s2); // height
  17820. this.moveTo(x, y + (h - ir));
  17821. this.lineTo(x + s2, y - ir);
  17822. this.lineTo(x - s2, y - ir);
  17823. this.lineTo(x, y + (h - ir));
  17824. this.closePath();
  17825. };
  17826. /**
  17827. * Draw a star shape, a star with 5 points
  17828. * @param {Number} x horizontal center
  17829. * @param {Number} y vertical center
  17830. * @param {Number} r radius, half the length of the sides of the triangle
  17831. */
  17832. CanvasRenderingContext2D.prototype.star = function(x, y, r) {
  17833. // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/
  17834. this.beginPath();
  17835. for (var n = 0; n < 10; n++) {
  17836. var radius = (n % 2 === 0) ? r * 1.3 : r * 0.5;
  17837. this.lineTo(
  17838. x + radius * Math.sin(n * 2 * Math.PI / 10),
  17839. y - radius * Math.cos(n * 2 * Math.PI / 10)
  17840. );
  17841. }
  17842. this.closePath();
  17843. };
  17844. /**
  17845. * http://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas
  17846. */
  17847. CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
  17848. var r2d = Math.PI/180;
  17849. if( w - ( 2 * r ) < 0 ) { r = ( w / 2 ); } //ensure that the radius isn't too large for x
  17850. if( h - ( 2 * r ) < 0 ) { r = ( h / 2 ); } //ensure that the radius isn't too large for y
  17851. this.beginPath();
  17852. this.moveTo(x+r,y);
  17853. this.lineTo(x+w-r,y);
  17854. this.arc(x+w-r,y+r,r,r2d*270,r2d*360,false);
  17855. this.lineTo(x+w,y+h-r);
  17856. this.arc(x+w-r,y+h-r,r,0,r2d*90,false);
  17857. this.lineTo(x+r,y+h);
  17858. this.arc(x+r,y+h-r,r,r2d*90,r2d*180,false);
  17859. this.lineTo(x,y+r);
  17860. this.arc(x+r,y+r,r,r2d*180,r2d*270,false);
  17861. };
  17862. /**
  17863. * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  17864. */
  17865. CanvasRenderingContext2D.prototype.ellipse = function(x, y, w, h) {
  17866. var kappa = .5522848,
  17867. ox = (w / 2) * kappa, // control point offset horizontal
  17868. oy = (h / 2) * kappa, // control point offset vertical
  17869. xe = x + w, // x-end
  17870. ye = y + h, // y-end
  17871. xm = x + w / 2, // x-middle
  17872. ym = y + h / 2; // y-middle
  17873. this.beginPath();
  17874. this.moveTo(x, ym);
  17875. this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  17876. this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  17877. this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  17878. this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  17879. };
  17880. /**
  17881. * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  17882. */
  17883. CanvasRenderingContext2D.prototype.database = function(x, y, w, h) {
  17884. var f = 1/3;
  17885. var wEllipse = w;
  17886. var hEllipse = h * f;
  17887. var kappa = .5522848,
  17888. ox = (wEllipse / 2) * kappa, // control point offset horizontal
  17889. oy = (hEllipse / 2) * kappa, // control point offset vertical
  17890. xe = x + wEllipse, // x-end
  17891. ye = y + hEllipse, // y-end
  17892. xm = x + wEllipse / 2, // x-middle
  17893. ym = y + hEllipse / 2, // y-middle
  17894. ymb = y + (h - hEllipse/2), // y-midlle, bottom ellipse
  17895. yeb = y + h; // y-end, bottom ellipse
  17896. this.beginPath();
  17897. this.moveTo(xe, ym);
  17898. this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  17899. this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  17900. this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  17901. this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  17902. this.lineTo(xe, ymb);
  17903. this.bezierCurveTo(xe, ymb + oy, xm + ox, yeb, xm, yeb);
  17904. this.bezierCurveTo(xm - ox, yeb, x, ymb + oy, x, ymb);
  17905. this.lineTo(x, ym);
  17906. };
  17907. /**
  17908. * Draw an arrow point (no line)
  17909. */
  17910. CanvasRenderingContext2D.prototype.arrow = function(x, y, angle, length) {
  17911. // tail
  17912. var xt = x - length * Math.cos(angle);
  17913. var yt = y - length * Math.sin(angle);
  17914. // inner tail
  17915. // TODO: allow to customize different shapes
  17916. var xi = x - length * 0.9 * Math.cos(angle);
  17917. var yi = y - length * 0.9 * Math.sin(angle);
  17918. // left
  17919. var xl = xt + length / 3 * Math.cos(angle + 0.5 * Math.PI);
  17920. var yl = yt + length / 3 * Math.sin(angle + 0.5 * Math.PI);
  17921. // right
  17922. var xr = xt + length / 3 * Math.cos(angle - 0.5 * Math.PI);
  17923. var yr = yt + length / 3 * Math.sin(angle - 0.5 * Math.PI);
  17924. this.beginPath();
  17925. this.moveTo(x, y);
  17926. this.lineTo(xl, yl);
  17927. this.lineTo(xi, yi);
  17928. this.lineTo(xr, yr);
  17929. this.closePath();
  17930. };
  17931. /**
  17932. * Sets up the dashedLine functionality for drawing
  17933. * Original code came from http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
  17934. * @author David Jordan
  17935. * @date 2012-08-08
  17936. */
  17937. CanvasRenderingContext2D.prototype.dashedLine = function(x,y,x2,y2,dashArray){
  17938. if (!dashArray) dashArray=[10,5];
  17939. if (dashLength==0) dashLength = 0.001; // Hack for Safari
  17940. var dashCount = dashArray.length;
  17941. this.moveTo(x, y);
  17942. var dx = (x2-x), dy = (y2-y);
  17943. var slope = dy/dx;
  17944. var distRemaining = Math.sqrt( dx*dx + dy*dy );
  17945. var dashIndex=0, draw=true;
  17946. while (distRemaining>=0.1){
  17947. var dashLength = dashArray[dashIndex++%dashCount];
  17948. if (dashLength > distRemaining) dashLength = distRemaining;
  17949. var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) );
  17950. if (dx<0) xStep = -xStep;
  17951. x += xStep;
  17952. y += slope*xStep;
  17953. this[draw ? 'lineTo' : 'moveTo'](x,y);
  17954. distRemaining -= dashLength;
  17955. draw = !draw;
  17956. }
  17957. };
  17958. // TODO: add diamond shape
  17959. }
  17960. /***/ },
  17961. /* 44 */
  17962. /***/ function(module, exports, __webpack_require__) {
  17963. var PhysicsMixin = __webpack_require__(55);
  17964. var ClusterMixin = __webpack_require__(49);
  17965. var SectorsMixin = __webpack_require__(50);
  17966. var SelectionMixin = __webpack_require__(51);
  17967. var ManipulationMixin = __webpack_require__(52);
  17968. var NavigationMixin = __webpack_require__(53);
  17969. var HierarchicalLayoutMixin = __webpack_require__(54);
  17970. /**
  17971. * Load a mixin into the network object
  17972. *
  17973. * @param {Object} sourceVariable | this object has to contain functions.
  17974. * @private
  17975. */
  17976. exports._loadMixin = function (sourceVariable) {
  17977. for (var mixinFunction in sourceVariable) {
  17978. if (sourceVariable.hasOwnProperty(mixinFunction)) {
  17979. this[mixinFunction] = sourceVariable[mixinFunction];
  17980. }
  17981. }
  17982. };
  17983. /**
  17984. * removes a mixin from the network object.
  17985. *
  17986. * @param {Object} sourceVariable | this object has to contain functions.
  17987. * @private
  17988. */
  17989. exports._clearMixin = function (sourceVariable) {
  17990. for (var mixinFunction in sourceVariable) {
  17991. if (sourceVariable.hasOwnProperty(mixinFunction)) {
  17992. this[mixinFunction] = undefined;
  17993. }
  17994. }
  17995. };
  17996. /**
  17997. * Mixin the physics system and initialize the parameters required.
  17998. *
  17999. * @private
  18000. */
  18001. exports._loadPhysicsSystem = function () {
  18002. this._loadMixin(PhysicsMixin);
  18003. this._loadSelectedForceSolver();
  18004. if (this.constants.configurePhysics == true) {
  18005. this._loadPhysicsConfiguration();
  18006. }
  18007. };
  18008. /**
  18009. * Mixin the cluster system and initialize the parameters required.
  18010. *
  18011. * @private
  18012. */
  18013. exports._loadClusterSystem = function () {
  18014. this.clusterSession = 0;
  18015. this.hubThreshold = 5;
  18016. this._loadMixin(ClusterMixin);
  18017. };
  18018. /**
  18019. * Mixin the sector system and initialize the parameters required
  18020. *
  18021. * @private
  18022. */
  18023. exports._loadSectorSystem = function () {
  18024. this.sectors = {};
  18025. this.activeSector = ["default"];
  18026. this.sectors["active"] = {};
  18027. this.sectors["active"]["default"] = {"nodes": {},
  18028. "edges": {},
  18029. "nodeIndices": [],
  18030. "formationScale": 1.0,
  18031. "drawingNode": undefined };
  18032. this.sectors["frozen"] = {};
  18033. this.sectors["support"] = {"nodes": {},
  18034. "edges": {},
  18035. "nodeIndices": [],
  18036. "formationScale": 1.0,
  18037. "drawingNode": undefined };
  18038. this.nodeIndices = this.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields
  18039. this._loadMixin(SectorsMixin);
  18040. };
  18041. /**
  18042. * Mixin the selection system and initialize the parameters required
  18043. *
  18044. * @private
  18045. */
  18046. exports._loadSelectionSystem = function () {
  18047. this.selectionObj = {nodes: {}, edges: {}};
  18048. this._loadMixin(SelectionMixin);
  18049. };
  18050. /**
  18051. * Mixin the navigationUI (User Interface) system and initialize the parameters required
  18052. *
  18053. * @private
  18054. */
  18055. exports._loadManipulationSystem = function () {
  18056. // reset global variables -- these are used by the selection of nodes and edges.
  18057. this.blockConnectingEdgeSelection = false;
  18058. this.forceAppendSelection = false;
  18059. if (this.constants.dataManipulation.enabled == true) {
  18060. // load the manipulator HTML elements. All styling done in css.
  18061. if (this.manipulationDiv === undefined) {
  18062. this.manipulationDiv = document.createElement('div');
  18063. this.manipulationDiv.className = 'network-manipulationDiv';
  18064. this.manipulationDiv.id = 'network-manipulationDiv';
  18065. if (this.editMode == true) {
  18066. this.manipulationDiv.style.display = "block";
  18067. }
  18068. else {
  18069. this.manipulationDiv.style.display = "none";
  18070. }
  18071. this.containerElement.insertBefore(this.manipulationDiv, this.frame);
  18072. }
  18073. if (this.editModeDiv === undefined) {
  18074. this.editModeDiv = document.createElement('div');
  18075. this.editModeDiv.className = 'network-manipulation-editMode';
  18076. this.editModeDiv.id = 'network-manipulation-editMode';
  18077. if (this.editMode == true) {
  18078. this.editModeDiv.style.display = "none";
  18079. }
  18080. else {
  18081. this.editModeDiv.style.display = "block";
  18082. }
  18083. this.containerElement.insertBefore(this.editModeDiv, this.frame);
  18084. }
  18085. if (this.closeDiv === undefined) {
  18086. this.closeDiv = document.createElement('div');
  18087. this.closeDiv.className = 'network-manipulation-closeDiv';
  18088. this.closeDiv.id = 'network-manipulation-closeDiv';
  18089. this.closeDiv.style.display = this.manipulationDiv.style.display;
  18090. this.containerElement.insertBefore(this.closeDiv, this.frame);
  18091. }
  18092. // load the manipulation functions
  18093. this._loadMixin(ManipulationMixin);
  18094. // create the manipulator toolbar
  18095. this._createManipulatorBar();
  18096. }
  18097. else {
  18098. if (this.manipulationDiv !== undefined) {
  18099. // removes all the bindings and overloads
  18100. this._createManipulatorBar();
  18101. // remove the manipulation divs
  18102. this.containerElement.removeChild(this.manipulationDiv);
  18103. this.containerElement.removeChild(this.editModeDiv);
  18104. this.containerElement.removeChild(this.closeDiv);
  18105. this.manipulationDiv = undefined;
  18106. this.editModeDiv = undefined;
  18107. this.closeDiv = undefined;
  18108. // remove the mixin functions
  18109. this._clearMixin(ManipulationMixin);
  18110. }
  18111. }
  18112. };
  18113. /**
  18114. * Mixin the navigation (User Interface) system and initialize the parameters required
  18115. *
  18116. * @private
  18117. */
  18118. exports._loadNavigationControls = function () {
  18119. this._loadMixin(NavigationMixin);
  18120. // the clean function removes the button divs, this is done to remove the bindings.
  18121. this._cleanNavigation();
  18122. if (this.constants.navigation.enabled == true) {
  18123. this._loadNavigationElements();
  18124. }
  18125. };
  18126. /**
  18127. * Mixin the hierarchical layout system.
  18128. *
  18129. * @private
  18130. */
  18131. exports._loadHierarchySystem = function () {
  18132. this._loadMixin(HierarchicalLayoutMixin);
  18133. };
  18134. /***/ },
  18135. /* 45 */
  18136. /***/ function(module, exports, __webpack_require__) {
  18137. /**
  18138. * Expose `Emitter`.
  18139. */
  18140. module.exports = Emitter;
  18141. /**
  18142. * Initialize a new `Emitter`.
  18143. *
  18144. * @api public
  18145. */
  18146. function Emitter(obj) {
  18147. if (obj) return mixin(obj);
  18148. };
  18149. /**
  18150. * Mixin the emitter properties.
  18151. *
  18152. * @param {Object} obj
  18153. * @return {Object}
  18154. * @api private
  18155. */
  18156. function mixin(obj) {
  18157. for (var key in Emitter.prototype) {
  18158. obj[key] = Emitter.prototype[key];
  18159. }
  18160. return obj;
  18161. }
  18162. /**
  18163. * Listen on the given `event` with `fn`.
  18164. *
  18165. * @param {String} event
  18166. * @param {Function} fn
  18167. * @return {Emitter}
  18168. * @api public
  18169. */
  18170. Emitter.prototype.on =
  18171. Emitter.prototype.addEventListener = function(event, fn){
  18172. this._callbacks = this._callbacks || {};
  18173. (this._callbacks[event] = this._callbacks[event] || [])
  18174. .push(fn);
  18175. return this;
  18176. };
  18177. /**
  18178. * Adds an `event` listener that will be invoked a single
  18179. * time then automatically removed.
  18180. *
  18181. * @param {String} event
  18182. * @param {Function} fn
  18183. * @return {Emitter}
  18184. * @api public
  18185. */
  18186. Emitter.prototype.once = function(event, fn){
  18187. var self = this;
  18188. this._callbacks = this._callbacks || {};
  18189. function on() {
  18190. self.off(event, on);
  18191. fn.apply(this, arguments);
  18192. }
  18193. on.fn = fn;
  18194. this.on(event, on);
  18195. return this;
  18196. };
  18197. /**
  18198. * Remove the given callback for `event` or all
  18199. * registered callbacks.
  18200. *
  18201. * @param {String} event
  18202. * @param {Function} fn
  18203. * @return {Emitter}
  18204. * @api public
  18205. */
  18206. Emitter.prototype.off =
  18207. Emitter.prototype.removeListener =
  18208. Emitter.prototype.removeAllListeners =
  18209. Emitter.prototype.removeEventListener = function(event, fn){
  18210. this._callbacks = this._callbacks || {};
  18211. // all
  18212. if (0 == arguments.length) {
  18213. this._callbacks = {};
  18214. return this;
  18215. }
  18216. // specific event
  18217. var callbacks = this._callbacks[event];
  18218. if (!callbacks) return this;
  18219. // remove all handlers
  18220. if (1 == arguments.length) {
  18221. delete this._callbacks[event];
  18222. return this;
  18223. }
  18224. // remove specific handler
  18225. var cb;
  18226. for (var i = 0; i < callbacks.length; i++) {
  18227. cb = callbacks[i];
  18228. if (cb === fn || cb.fn === fn) {
  18229. callbacks.splice(i, 1);
  18230. break;
  18231. }
  18232. }
  18233. return this;
  18234. };
  18235. /**
  18236. * Emit `event` with the given args.
  18237. *
  18238. * @param {String} event
  18239. * @param {Mixed} ...
  18240. * @return {Emitter}
  18241. */
  18242. Emitter.prototype.emit = function(event){
  18243. this._callbacks = this._callbacks || {};
  18244. var args = [].slice.call(arguments, 1)
  18245. , callbacks = this._callbacks[event];
  18246. if (callbacks) {
  18247. callbacks = callbacks.slice(0);
  18248. for (var i = 0, len = callbacks.length; i < len; ++i) {
  18249. callbacks[i].apply(this, args);
  18250. }
  18251. }
  18252. return this;
  18253. };
  18254. /**
  18255. * Return array of callbacks for `event`.
  18256. *
  18257. * @param {String} event
  18258. * @return {Array}
  18259. * @api public
  18260. */
  18261. Emitter.prototype.listeners = function(event){
  18262. this._callbacks = this._callbacks || {};
  18263. return this._callbacks[event] || [];
  18264. };
  18265. /**
  18266. * Check if this emitter has `event` handlers.
  18267. *
  18268. * @param {String} event
  18269. * @return {Boolean}
  18270. * @api public
  18271. */
  18272. Emitter.prototype.hasListeners = function(event){
  18273. return !! this.listeners(event).length;
  18274. };
  18275. /***/ },
  18276. /* 46 */
  18277. /***/ function(module, exports, __webpack_require__) {
  18278. var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(global, module) {//! moment.js
  18279. //! version : 2.7.0
  18280. //! authors : Tim Wood, Iskren Chernev, Moment.js contributors
  18281. //! license : MIT
  18282. //! momentjs.com
  18283. (function (undefined) {
  18284. /************************************
  18285. Constants
  18286. ************************************/
  18287. var moment,
  18288. VERSION = "2.7.0",
  18289. // the global-scope this is NOT the global object in Node.js
  18290. globalScope = typeof global !== 'undefined' ? global : this,
  18291. oldGlobalMoment,
  18292. round = Math.round,
  18293. i,
  18294. YEAR = 0,
  18295. MONTH = 1,
  18296. DATE = 2,
  18297. HOUR = 3,
  18298. MINUTE = 4,
  18299. SECOND = 5,
  18300. MILLISECOND = 6,
  18301. // internal storage for language config files
  18302. languages = {},
  18303. // moment internal properties
  18304. momentProperties = {
  18305. _isAMomentObject: null,
  18306. _i : null,
  18307. _f : null,
  18308. _l : null,
  18309. _strict : null,
  18310. _tzm : null,
  18311. _isUTC : null,
  18312. _offset : null, // optional. Combine with _isUTC
  18313. _pf : null,
  18314. _lang : null // optional
  18315. },
  18316. // check for nodeJS
  18317. hasModule = (typeof module !== 'undefined' && module.exports),
  18318. // ASP.NET json date format regex
  18319. aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
  18320. aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,
  18321. // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
  18322. // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
  18323. isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,
  18324. // format tokens
  18325. 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,
  18326. localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,
  18327. // parsing token regexes
  18328. parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
  18329. parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
  18330. parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999
  18331. parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
  18332. parseTokenDigits = /\d+/, // nonzero number of digits
  18333. 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.
  18334. parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
  18335. parseTokenT = /T/i, // T (ISO separator)
  18336. parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
  18337. parseTokenOrdinal = /\d{1,2}/,
  18338. //strict parsing regexes
  18339. parseTokenOneDigit = /\d/, // 0 - 9
  18340. parseTokenTwoDigits = /\d\d/, // 00 - 99
  18341. parseTokenThreeDigits = /\d{3}/, // 000 - 999
  18342. parseTokenFourDigits = /\d{4}/, // 0000 - 9999
  18343. parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999
  18344. parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf
  18345. // iso 8601 regex
  18346. // 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)
  18347. 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)?)?$/,
  18348. isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
  18349. isoDates = [
  18350. ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/],
  18351. ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/],
  18352. ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/],
  18353. ['GGGG-[W]WW', /\d{4}-W\d{2}/],
  18354. ['YYYY-DDD', /\d{4}-\d{3}/]
  18355. ],
  18356. // iso time formats and regexes
  18357. isoTimes = [
  18358. ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/],
  18359. ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
  18360. ['HH:mm', /(T| )\d\d:\d\d/],
  18361. ['HH', /(T| )\d\d/]
  18362. ],
  18363. // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"]
  18364. parseTimezoneChunker = /([\+\-]|\d\d)/gi,
  18365. // getter and setter names
  18366. proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
  18367. unitMillisecondFactors = {
  18368. 'Milliseconds' : 1,
  18369. 'Seconds' : 1e3,
  18370. 'Minutes' : 6e4,
  18371. 'Hours' : 36e5,
  18372. 'Days' : 864e5,
  18373. 'Months' : 2592e6,
  18374. 'Years' : 31536e6
  18375. },
  18376. unitAliases = {
  18377. ms : 'millisecond',
  18378. s : 'second',
  18379. m : 'minute',
  18380. h : 'hour',
  18381. d : 'day',
  18382. D : 'date',
  18383. w : 'week',
  18384. W : 'isoWeek',
  18385. M : 'month',
  18386. Q : 'quarter',
  18387. y : 'year',
  18388. DDD : 'dayOfYear',
  18389. e : 'weekday',
  18390. E : 'isoWeekday',
  18391. gg: 'weekYear',
  18392. GG: 'isoWeekYear'
  18393. },
  18394. camelFunctions = {
  18395. dayofyear : 'dayOfYear',
  18396. isoweekday : 'isoWeekday',
  18397. isoweek : 'isoWeek',
  18398. weekyear : 'weekYear',
  18399. isoweekyear : 'isoWeekYear'
  18400. },
  18401. // format function strings
  18402. formatFunctions = {},
  18403. // default relative time thresholds
  18404. relativeTimeThresholds = {
  18405. s: 45, //seconds to minutes
  18406. m: 45, //minutes to hours
  18407. h: 22, //hours to days
  18408. dd: 25, //days to month (month == 1)
  18409. dm: 45, //days to months (months > 1)
  18410. dy: 345 //days to year
  18411. },
  18412. // tokens to ordinalize and pad
  18413. ordinalizeTokens = 'DDD w W M D d'.split(' '),
  18414. paddedTokens = 'M D H h m s w W'.split(' '),
  18415. formatTokenFunctions = {
  18416. M : function () {
  18417. return this.month() + 1;
  18418. },
  18419. MMM : function (format) {
  18420. return this.lang().monthsShort(this, format);
  18421. },
  18422. MMMM : function (format) {
  18423. return this.lang().months(this, format);
  18424. },
  18425. D : function () {
  18426. return this.date();
  18427. },
  18428. DDD : function () {
  18429. return this.dayOfYear();
  18430. },
  18431. d : function () {
  18432. return this.day();
  18433. },
  18434. dd : function (format) {
  18435. return this.lang().weekdaysMin(this, format);
  18436. },
  18437. ddd : function (format) {
  18438. return this.lang().weekdaysShort(this, format);
  18439. },
  18440. dddd : function (format) {
  18441. return this.lang().weekdays(this, format);
  18442. },
  18443. w : function () {
  18444. return this.week();
  18445. },
  18446. W : function () {
  18447. return this.isoWeek();
  18448. },
  18449. YY : function () {
  18450. return leftZeroFill(this.year() % 100, 2);
  18451. },
  18452. YYYY : function () {
  18453. return leftZeroFill(this.year(), 4);
  18454. },
  18455. YYYYY : function () {
  18456. return leftZeroFill(this.year(), 5);
  18457. },
  18458. YYYYYY : function () {
  18459. var y = this.year(), sign = y >= 0 ? '+' : '-';
  18460. return sign + leftZeroFill(Math.abs(y), 6);
  18461. },
  18462. gg : function () {
  18463. return leftZeroFill(this.weekYear() % 100, 2);
  18464. },
  18465. gggg : function () {
  18466. return leftZeroFill(this.weekYear(), 4);
  18467. },
  18468. ggggg : function () {
  18469. return leftZeroFill(this.weekYear(), 5);
  18470. },
  18471. GG : function () {
  18472. return leftZeroFill(this.isoWeekYear() % 100, 2);
  18473. },
  18474. GGGG : function () {
  18475. return leftZeroFill(this.isoWeekYear(), 4);
  18476. },
  18477. GGGGG : function () {
  18478. return leftZeroFill(this.isoWeekYear(), 5);
  18479. },
  18480. e : function () {
  18481. return this.weekday();
  18482. },
  18483. E : function () {
  18484. return this.isoWeekday();
  18485. },
  18486. a : function () {
  18487. return this.lang().meridiem(this.hours(), this.minutes(), true);
  18488. },
  18489. A : function () {
  18490. return this.lang().meridiem(this.hours(), this.minutes(), false);
  18491. },
  18492. H : function () {
  18493. return this.hours();
  18494. },
  18495. h : function () {
  18496. return this.hours() % 12 || 12;
  18497. },
  18498. m : function () {
  18499. return this.minutes();
  18500. },
  18501. s : function () {
  18502. return this.seconds();
  18503. },
  18504. S : function () {
  18505. return toInt(this.milliseconds() / 100);
  18506. },
  18507. SS : function () {
  18508. return leftZeroFill(toInt(this.milliseconds() / 10), 2);
  18509. },
  18510. SSS : function () {
  18511. return leftZeroFill(this.milliseconds(), 3);
  18512. },
  18513. SSSS : function () {
  18514. return leftZeroFill(this.milliseconds(), 3);
  18515. },
  18516. Z : function () {
  18517. var a = -this.zone(),
  18518. b = "+";
  18519. if (a < 0) {
  18520. a = -a;
  18521. b = "-";
  18522. }
  18523. return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2);
  18524. },
  18525. ZZ : function () {
  18526. var a = -this.zone(),
  18527. b = "+";
  18528. if (a < 0) {
  18529. a = -a;
  18530. b = "-";
  18531. }
  18532. return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2);
  18533. },
  18534. z : function () {
  18535. return this.zoneAbbr();
  18536. },
  18537. zz : function () {
  18538. return this.zoneName();
  18539. },
  18540. X : function () {
  18541. return this.unix();
  18542. },
  18543. Q : function () {
  18544. return this.quarter();
  18545. }
  18546. },
  18547. lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'];
  18548. // Pick the first defined of two or three arguments. dfl comes from
  18549. // default.
  18550. function dfl(a, b, c) {
  18551. switch (arguments.length) {
  18552. case 2: return a != null ? a : b;
  18553. case 3: return a != null ? a : b != null ? b : c;
  18554. default: throw new Error("Implement me");
  18555. }
  18556. }
  18557. function defaultParsingFlags() {
  18558. // We need to deep clone this object, and es5 standard is not very
  18559. // helpful.
  18560. return {
  18561. empty : false,
  18562. unusedTokens : [],
  18563. unusedInput : [],
  18564. overflow : -2,
  18565. charsLeftOver : 0,
  18566. nullInput : false,
  18567. invalidMonth : null,
  18568. invalidFormat : false,
  18569. userInvalidated : false,
  18570. iso: false
  18571. };
  18572. }
  18573. function deprecate(msg, fn) {
  18574. var firstTime = true;
  18575. function printMsg() {
  18576. if (moment.suppressDeprecationWarnings === false &&
  18577. typeof console !== 'undefined' && console.warn) {
  18578. console.warn("Deprecation warning: " + msg);
  18579. }
  18580. }
  18581. return extend(function () {
  18582. if (firstTime) {
  18583. printMsg();
  18584. firstTime = false;
  18585. }
  18586. return fn.apply(this, arguments);
  18587. }, fn);
  18588. }
  18589. function padToken(func, count) {
  18590. return function (a) {
  18591. return leftZeroFill(func.call(this, a), count);
  18592. };
  18593. }
  18594. function ordinalizeToken(func, period) {
  18595. return function (a) {
  18596. return this.lang().ordinal(func.call(this, a), period);
  18597. };
  18598. }
  18599. while (ordinalizeTokens.length) {
  18600. i = ordinalizeTokens.pop();
  18601. formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i);
  18602. }
  18603. while (paddedTokens.length) {
  18604. i = paddedTokens.pop();
  18605. formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2);
  18606. }
  18607. formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3);
  18608. /************************************
  18609. Constructors
  18610. ************************************/
  18611. function Language() {
  18612. }
  18613. // Moment prototype object
  18614. function Moment(config) {
  18615. checkOverflow(config);
  18616. extend(this, config);
  18617. }
  18618. // Duration Constructor
  18619. function Duration(duration) {
  18620. var normalizedInput = normalizeObjectUnits(duration),
  18621. years = normalizedInput.year || 0,
  18622. quarters = normalizedInput.quarter || 0,
  18623. months = normalizedInput.month || 0,
  18624. weeks = normalizedInput.week || 0,
  18625. days = normalizedInput.day || 0,
  18626. hours = normalizedInput.hour || 0,
  18627. minutes = normalizedInput.minute || 0,
  18628. seconds = normalizedInput.second || 0,
  18629. milliseconds = normalizedInput.millisecond || 0;
  18630. // representation for dateAddRemove
  18631. this._milliseconds = +milliseconds +
  18632. seconds * 1e3 + // 1000
  18633. minutes * 6e4 + // 1000 * 60
  18634. hours * 36e5; // 1000 * 60 * 60
  18635. // Because of dateAddRemove treats 24 hours as different from a
  18636. // day when working around DST, we need to store them separately
  18637. this._days = +days +
  18638. weeks * 7;
  18639. // It is impossible translate months into days without knowing
  18640. // which months you are are talking about, so we have to store
  18641. // it separately.
  18642. this._months = +months +
  18643. quarters * 3 +
  18644. years * 12;
  18645. this._data = {};
  18646. this._bubble();
  18647. }
  18648. /************************************
  18649. Helpers
  18650. ************************************/
  18651. function extend(a, b) {
  18652. for (var i in b) {
  18653. if (b.hasOwnProperty(i)) {
  18654. a[i] = b[i];
  18655. }
  18656. }
  18657. if (b.hasOwnProperty("toString")) {
  18658. a.toString = b.toString;
  18659. }
  18660. if (b.hasOwnProperty("valueOf")) {
  18661. a.valueOf = b.valueOf;
  18662. }
  18663. return a;
  18664. }
  18665. function cloneMoment(m) {
  18666. var result = {}, i;
  18667. for (i in m) {
  18668. if (m.hasOwnProperty(i) && momentProperties.hasOwnProperty(i)) {
  18669. result[i] = m[i];
  18670. }
  18671. }
  18672. return result;
  18673. }
  18674. function absRound(number) {
  18675. if (number < 0) {
  18676. return Math.ceil(number);
  18677. } else {
  18678. return Math.floor(number);
  18679. }
  18680. }
  18681. // left zero fill a number
  18682. // see http://jsperf.com/left-zero-filling for performance comparison
  18683. function leftZeroFill(number, targetLength, forceSign) {
  18684. var output = '' + Math.abs(number),
  18685. sign = number >= 0;
  18686. while (output.length < targetLength) {
  18687. output = '0' + output;
  18688. }
  18689. return (sign ? (forceSign ? '+' : '') : '-') + output;
  18690. }
  18691. // helper function for _.addTime and _.subtractTime
  18692. function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) {
  18693. var milliseconds = duration._milliseconds,
  18694. days = duration._days,
  18695. months = duration._months;
  18696. updateOffset = updateOffset == null ? true : updateOffset;
  18697. if (milliseconds) {
  18698. mom._d.setTime(+mom._d + milliseconds * isAdding);
  18699. }
  18700. if (days) {
  18701. rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding);
  18702. }
  18703. if (months) {
  18704. rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding);
  18705. }
  18706. if (updateOffset) {
  18707. moment.updateOffset(mom, days || months);
  18708. }
  18709. }
  18710. // check if is an array
  18711. function isArray(input) {
  18712. return Object.prototype.toString.call(input) === '[object Array]';
  18713. }
  18714. function isDate(input) {
  18715. return Object.prototype.toString.call(input) === '[object Date]' ||
  18716. input instanceof Date;
  18717. }
  18718. // compare two arrays, return the number of differences
  18719. function compareArrays(array1, array2, dontConvert) {
  18720. var len = Math.min(array1.length, array2.length),
  18721. lengthDiff = Math.abs(array1.length - array2.length),
  18722. diffs = 0,
  18723. i;
  18724. for (i = 0; i < len; i++) {
  18725. if ((dontConvert && array1[i] !== array2[i]) ||
  18726. (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
  18727. diffs++;
  18728. }
  18729. }
  18730. return diffs + lengthDiff;
  18731. }
  18732. function normalizeUnits(units) {
  18733. if (units) {
  18734. var lowered = units.toLowerCase().replace(/(.)s$/, '$1');
  18735. units = unitAliases[units] || camelFunctions[lowered] || lowered;
  18736. }
  18737. return units;
  18738. }
  18739. function normalizeObjectUnits(inputObject) {
  18740. var normalizedInput = {},
  18741. normalizedProp,
  18742. prop;
  18743. for (prop in inputObject) {
  18744. if (inputObject.hasOwnProperty(prop)) {
  18745. normalizedProp = normalizeUnits(prop);
  18746. if (normalizedProp) {
  18747. normalizedInput[normalizedProp] = inputObject[prop];
  18748. }
  18749. }
  18750. }
  18751. return normalizedInput;
  18752. }
  18753. function makeList(field) {
  18754. var count, setter;
  18755. if (field.indexOf('week') === 0) {
  18756. count = 7;
  18757. setter = 'day';
  18758. }
  18759. else if (field.indexOf('month') === 0) {
  18760. count = 12;
  18761. setter = 'month';
  18762. }
  18763. else {
  18764. return;
  18765. }
  18766. moment[field] = function (format, index) {
  18767. var i, getter,
  18768. method = moment.fn._lang[field],
  18769. results = [];
  18770. if (typeof format === 'number') {
  18771. index = format;
  18772. format = undefined;
  18773. }
  18774. getter = function (i) {
  18775. var m = moment().utc().set(setter, i);
  18776. return method.call(moment.fn._lang, m, format || '');
  18777. };
  18778. if (index != null) {
  18779. return getter(index);
  18780. }
  18781. else {
  18782. for (i = 0; i < count; i++) {
  18783. results.push(getter(i));
  18784. }
  18785. return results;
  18786. }
  18787. };
  18788. }
  18789. function toInt(argumentForCoercion) {
  18790. var coercedNumber = +argumentForCoercion,
  18791. value = 0;
  18792. if (coercedNumber !== 0 && isFinite(coercedNumber)) {
  18793. if (coercedNumber >= 0) {
  18794. value = Math.floor(coercedNumber);
  18795. } else {
  18796. value = Math.ceil(coercedNumber);
  18797. }
  18798. }
  18799. return value;
  18800. }
  18801. function daysInMonth(year, month) {
  18802. return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
  18803. }
  18804. function weeksInYear(year, dow, doy) {
  18805. return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week;
  18806. }
  18807. function daysInYear(year) {
  18808. return isLeapYear(year) ? 366 : 365;
  18809. }
  18810. function isLeapYear(year) {
  18811. return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
  18812. }
  18813. function checkOverflow(m) {
  18814. var overflow;
  18815. if (m._a && m._pf.overflow === -2) {
  18816. overflow =
  18817. m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH :
  18818. m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE :
  18819. m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR :
  18820. m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE :
  18821. m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND :
  18822. m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND :
  18823. -1;
  18824. if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
  18825. overflow = DATE;
  18826. }
  18827. m._pf.overflow = overflow;
  18828. }
  18829. }
  18830. function isValid(m) {
  18831. if (m._isValid == null) {
  18832. m._isValid = !isNaN(m._d.getTime()) &&
  18833. m._pf.overflow < 0 &&
  18834. !m._pf.empty &&
  18835. !m._pf.invalidMonth &&
  18836. !m._pf.nullInput &&
  18837. !m._pf.invalidFormat &&
  18838. !m._pf.userInvalidated;
  18839. if (m._strict) {
  18840. m._isValid = m._isValid &&
  18841. m._pf.charsLeftOver === 0 &&
  18842. m._pf.unusedTokens.length === 0;
  18843. }
  18844. }
  18845. return m._isValid;
  18846. }
  18847. function normalizeLanguage(key) {
  18848. return key ? key.toLowerCase().replace('_', '-') : key;
  18849. }
  18850. // Return a moment from input, that is local/utc/zone equivalent to model.
  18851. function makeAs(input, model) {
  18852. return model._isUTC ? moment(input).zone(model._offset || 0) :
  18853. moment(input).local();
  18854. }
  18855. /************************************
  18856. Languages
  18857. ************************************/
  18858. extend(Language.prototype, {
  18859. set : function (config) {
  18860. var prop, i;
  18861. for (i in config) {
  18862. prop = config[i];
  18863. if (typeof prop === 'function') {
  18864. this[i] = prop;
  18865. } else {
  18866. this['_' + i] = prop;
  18867. }
  18868. }
  18869. },
  18870. _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"),
  18871. months : function (m) {
  18872. return this._months[m.month()];
  18873. },
  18874. _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),
  18875. monthsShort : function (m) {
  18876. return this._monthsShort[m.month()];
  18877. },
  18878. monthsParse : function (monthName) {
  18879. var i, mom, regex;
  18880. if (!this._monthsParse) {
  18881. this._monthsParse = [];
  18882. }
  18883. for (i = 0; i < 12; i++) {
  18884. // make the regex if we don't have it already
  18885. if (!this._monthsParse[i]) {
  18886. mom = moment.utc([2000, i]);
  18887. regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
  18888. this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
  18889. }
  18890. // test the regex
  18891. if (this._monthsParse[i].test(monthName)) {
  18892. return i;
  18893. }
  18894. }
  18895. },
  18896. _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),
  18897. weekdays : function (m) {
  18898. return this._weekdays[m.day()];
  18899. },
  18900. _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),
  18901. weekdaysShort : function (m) {
  18902. return this._weekdaysShort[m.day()];
  18903. },
  18904. _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"),
  18905. weekdaysMin : function (m) {
  18906. return this._weekdaysMin[m.day()];
  18907. },
  18908. weekdaysParse : function (weekdayName) {
  18909. var i, mom, regex;
  18910. if (!this._weekdaysParse) {
  18911. this._weekdaysParse = [];
  18912. }
  18913. for (i = 0; i < 7; i++) {
  18914. // make the regex if we don't have it already
  18915. if (!this._weekdaysParse[i]) {
  18916. mom = moment([2000, 1]).day(i);
  18917. regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
  18918. this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
  18919. }
  18920. // test the regex
  18921. if (this._weekdaysParse[i].test(weekdayName)) {
  18922. return i;
  18923. }
  18924. }
  18925. },
  18926. _longDateFormat : {
  18927. LT : "h:mm A",
  18928. L : "MM/DD/YYYY",
  18929. LL : "MMMM D YYYY",
  18930. LLL : "MMMM D YYYY LT",
  18931. LLLL : "dddd, MMMM D YYYY LT"
  18932. },
  18933. longDateFormat : function (key) {
  18934. var output = this._longDateFormat[key];
  18935. if (!output && this._longDateFormat[key.toUpperCase()]) {
  18936. output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
  18937. return val.slice(1);
  18938. });
  18939. this._longDateFormat[key] = output;
  18940. }
  18941. return output;
  18942. },
  18943. isPM : function (input) {
  18944. // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
  18945. // Using charAt should be more compatible.
  18946. return ((input + '').toLowerCase().charAt(0) === 'p');
  18947. },
  18948. _meridiemParse : /[ap]\.?m?\.?/i,
  18949. meridiem : function (hours, minutes, isLower) {
  18950. if (hours > 11) {
  18951. return isLower ? 'pm' : 'PM';
  18952. } else {
  18953. return isLower ? 'am' : 'AM';
  18954. }
  18955. },
  18956. _calendar : {
  18957. sameDay : '[Today at] LT',
  18958. nextDay : '[Tomorrow at] LT',
  18959. nextWeek : 'dddd [at] LT',
  18960. lastDay : '[Yesterday at] LT',
  18961. lastWeek : '[Last] dddd [at] LT',
  18962. sameElse : 'L'
  18963. },
  18964. calendar : function (key, mom) {
  18965. var output = this._calendar[key];
  18966. return typeof output === 'function' ? output.apply(mom) : output;
  18967. },
  18968. _relativeTime : {
  18969. future : "in %s",
  18970. past : "%s ago",
  18971. s : "a few seconds",
  18972. m : "a minute",
  18973. mm : "%d minutes",
  18974. h : "an hour",
  18975. hh : "%d hours",
  18976. d : "a day",
  18977. dd : "%d days",
  18978. M : "a month",
  18979. MM : "%d months",
  18980. y : "a year",
  18981. yy : "%d years"
  18982. },
  18983. relativeTime : function (number, withoutSuffix, string, isFuture) {
  18984. var output = this._relativeTime[string];
  18985. return (typeof output === 'function') ?
  18986. output(number, withoutSuffix, string, isFuture) :
  18987. output.replace(/%d/i, number);
  18988. },
  18989. pastFuture : function (diff, output) {
  18990. var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
  18991. return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
  18992. },
  18993. ordinal : function (number) {
  18994. return this._ordinal.replace("%d", number);
  18995. },
  18996. _ordinal : "%d",
  18997. preparse : function (string) {
  18998. return string;
  18999. },
  19000. postformat : function (string) {
  19001. return string;
  19002. },
  19003. week : function (mom) {
  19004. return weekOfYear(mom, this._week.dow, this._week.doy).week;
  19005. },
  19006. _week : {
  19007. dow : 0, // Sunday is the first day of the week.
  19008. doy : 6 // The week that contains Jan 1st is the first week of the year.
  19009. },
  19010. _invalidDate: 'Invalid date',
  19011. invalidDate: function () {
  19012. return this._invalidDate;
  19013. }
  19014. });
  19015. // Loads a language definition into the `languages` cache. The function
  19016. // takes a key and optionally values. If not in the browser and no values
  19017. // are provided, it will load the language file module. As a convenience,
  19018. // this function also returns the language values.
  19019. function loadLang(key, values) {
  19020. values.abbr = key;
  19021. if (!languages[key]) {
  19022. languages[key] = new Language();
  19023. }
  19024. languages[key].set(values);
  19025. return languages[key];
  19026. }
  19027. // Remove a language from the `languages` cache. Mostly useful in tests.
  19028. function unloadLang(key) {
  19029. delete languages[key];
  19030. }
  19031. // Determines which language definition to use and returns it.
  19032. //
  19033. // With no parameters, it will return the global language. If you
  19034. // pass in a language key, such as 'en', it will return the
  19035. // definition for 'en', so long as 'en' has already been loaded using
  19036. // moment.lang.
  19037. function getLangDefinition(key) {
  19038. var i = 0, j, lang, next, split,
  19039. get = function (k) {
  19040. if (!languages[k] && hasModule) {
  19041. try {
  19042. __webpack_require__(56)("./" + k);
  19043. } catch (e) { }
  19044. }
  19045. return languages[k];
  19046. };
  19047. if (!key) {
  19048. return moment.fn._lang;
  19049. }
  19050. if (!isArray(key)) {
  19051. //short-circuit everything else
  19052. lang = get(key);
  19053. if (lang) {
  19054. return lang;
  19055. }
  19056. key = [key];
  19057. }
  19058. //pick the language from the array
  19059. //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
  19060. //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
  19061. while (i < key.length) {
  19062. split = normalizeLanguage(key[i]).split('-');
  19063. j = split.length;
  19064. next = normalizeLanguage(key[i + 1]);
  19065. next = next ? next.split('-') : null;
  19066. while (j > 0) {
  19067. lang = get(split.slice(0, j).join('-'));
  19068. if (lang) {
  19069. return lang;
  19070. }
  19071. if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
  19072. //the next array item is better than a shallower substring of this one
  19073. break;
  19074. }
  19075. j--;
  19076. }
  19077. i++;
  19078. }
  19079. return moment.fn._lang;
  19080. }
  19081. /************************************
  19082. Formatting
  19083. ************************************/
  19084. function removeFormattingTokens(input) {
  19085. if (input.match(/\[[\s\S]/)) {
  19086. return input.replace(/^\[|\]$/g, "");
  19087. }
  19088. return input.replace(/\\/g, "");
  19089. }
  19090. function makeFormatFunction(format) {
  19091. var array = format.match(formattingTokens), i, length;
  19092. for (i = 0, length = array.length; i < length; i++) {
  19093. if (formatTokenFunctions[array[i]]) {
  19094. array[i] = formatTokenFunctions[array[i]];
  19095. } else {
  19096. array[i] = removeFormattingTokens(array[i]);
  19097. }
  19098. }
  19099. return function (mom) {
  19100. var output = "";
  19101. for (i = 0; i < length; i++) {
  19102. output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
  19103. }
  19104. return output;
  19105. };
  19106. }
  19107. // format date using native date object
  19108. function formatMoment(m, format) {
  19109. if (!m.isValid()) {
  19110. return m.lang().invalidDate();
  19111. }
  19112. format = expandFormat(format, m.lang());
  19113. if (!formatFunctions[format]) {
  19114. formatFunctions[format] = makeFormatFunction(format);
  19115. }
  19116. return formatFunctions[format](m);
  19117. }
  19118. function expandFormat(format, lang) {
  19119. var i = 5;
  19120. function replaceLongDateFormatTokens(input) {
  19121. return lang.longDateFormat(input) || input;
  19122. }
  19123. localFormattingTokens.lastIndex = 0;
  19124. while (i >= 0 && localFormattingTokens.test(format)) {
  19125. format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
  19126. localFormattingTokens.lastIndex = 0;
  19127. i -= 1;
  19128. }
  19129. return format;
  19130. }
  19131. /************************************
  19132. Parsing
  19133. ************************************/
  19134. // get the regex to find the next token
  19135. function getParseRegexForToken(token, config) {
  19136. var a, strict = config._strict;
  19137. switch (token) {
  19138. case 'Q':
  19139. return parseTokenOneDigit;
  19140. case 'DDDD':
  19141. return parseTokenThreeDigits;
  19142. case 'YYYY':
  19143. case 'GGGG':
  19144. case 'gggg':
  19145. return strict ? parseTokenFourDigits : parseTokenOneToFourDigits;
  19146. case 'Y':
  19147. case 'G':
  19148. case 'g':
  19149. return parseTokenSignedNumber;
  19150. case 'YYYYYY':
  19151. case 'YYYYY':
  19152. case 'GGGGG':
  19153. case 'ggggg':
  19154. return strict ? parseTokenSixDigits : parseTokenOneToSixDigits;
  19155. case 'S':
  19156. if (strict) { return parseTokenOneDigit; }
  19157. /* falls through */
  19158. case 'SS':
  19159. if (strict) { return parseTokenTwoDigits; }
  19160. /* falls through */
  19161. case 'SSS':
  19162. if (strict) { return parseTokenThreeDigits; }
  19163. /* falls through */
  19164. case 'DDD':
  19165. return parseTokenOneToThreeDigits;
  19166. case 'MMM':
  19167. case 'MMMM':
  19168. case 'dd':
  19169. case 'ddd':
  19170. case 'dddd':
  19171. return parseTokenWord;
  19172. case 'a':
  19173. case 'A':
  19174. return getLangDefinition(config._l)._meridiemParse;
  19175. case 'X':
  19176. return parseTokenTimestampMs;
  19177. case 'Z':
  19178. case 'ZZ':
  19179. return parseTokenTimezone;
  19180. case 'T':
  19181. return parseTokenT;
  19182. case 'SSSS':
  19183. return parseTokenDigits;
  19184. case 'MM':
  19185. case 'DD':
  19186. case 'YY':
  19187. case 'GG':
  19188. case 'gg':
  19189. case 'HH':
  19190. case 'hh':
  19191. case 'mm':
  19192. case 'ss':
  19193. case 'ww':
  19194. case 'WW':
  19195. return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits;
  19196. case 'M':
  19197. case 'D':
  19198. case 'd':
  19199. case 'H':
  19200. case 'h':
  19201. case 'm':
  19202. case 's':
  19203. case 'w':
  19204. case 'W':
  19205. case 'e':
  19206. case 'E':
  19207. return parseTokenOneOrTwoDigits;
  19208. case 'Do':
  19209. return parseTokenOrdinal;
  19210. default :
  19211. a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i"));
  19212. return a;
  19213. }
  19214. }
  19215. function timezoneMinutesFromString(string) {
  19216. string = string || "";
  19217. var possibleTzMatches = (string.match(parseTokenTimezone) || []),
  19218. tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [],
  19219. parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
  19220. minutes = +(parts[1] * 60) + toInt(parts[2]);
  19221. return parts[0] === '+' ? -minutes : minutes;
  19222. }
  19223. // function to convert string input to date
  19224. function addTimeToArrayFromToken(token, input, config) {
  19225. var a, datePartArray = config._a;
  19226. switch (token) {
  19227. // QUARTER
  19228. case 'Q':
  19229. if (input != null) {
  19230. datePartArray[MONTH] = (toInt(input) - 1) * 3;
  19231. }
  19232. break;
  19233. // MONTH
  19234. case 'M' : // fall through to MM
  19235. case 'MM' :
  19236. if (input != null) {
  19237. datePartArray[MONTH] = toInt(input) - 1;
  19238. }
  19239. break;
  19240. case 'MMM' : // fall through to MMMM
  19241. case 'MMMM' :
  19242. a = getLangDefinition(config._l).monthsParse(input);
  19243. // if we didn't find a month name, mark the date as invalid.
  19244. if (a != null) {
  19245. datePartArray[MONTH] = a;
  19246. } else {
  19247. config._pf.invalidMonth = input;
  19248. }
  19249. break;
  19250. // DAY OF MONTH
  19251. case 'D' : // fall through to DD
  19252. case 'DD' :
  19253. if (input != null) {
  19254. datePartArray[DATE] = toInt(input);
  19255. }
  19256. break;
  19257. case 'Do' :
  19258. if (input != null) {
  19259. datePartArray[DATE] = toInt(parseInt(input, 10));
  19260. }
  19261. break;
  19262. // DAY OF YEAR
  19263. case 'DDD' : // fall through to DDDD
  19264. case 'DDDD' :
  19265. if (input != null) {
  19266. config._dayOfYear = toInt(input);
  19267. }
  19268. break;
  19269. // YEAR
  19270. case 'YY' :
  19271. datePartArray[YEAR] = moment.parseTwoDigitYear(input);
  19272. break;
  19273. case 'YYYY' :
  19274. case 'YYYYY' :
  19275. case 'YYYYYY' :
  19276. datePartArray[YEAR] = toInt(input);
  19277. break;
  19278. // AM / PM
  19279. case 'a' : // fall through to A
  19280. case 'A' :
  19281. config._isPm = getLangDefinition(config._l).isPM(input);
  19282. break;
  19283. // 24 HOUR
  19284. case 'H' : // fall through to hh
  19285. case 'HH' : // fall through to hh
  19286. case 'h' : // fall through to hh
  19287. case 'hh' :
  19288. datePartArray[HOUR] = toInt(input);
  19289. break;
  19290. // MINUTE
  19291. case 'm' : // fall through to mm
  19292. case 'mm' :
  19293. datePartArray[MINUTE] = toInt(input);
  19294. break;
  19295. // SECOND
  19296. case 's' : // fall through to ss
  19297. case 'ss' :
  19298. datePartArray[SECOND] = toInt(input);
  19299. break;
  19300. // MILLISECOND
  19301. case 'S' :
  19302. case 'SS' :
  19303. case 'SSS' :
  19304. case 'SSSS' :
  19305. datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000);
  19306. break;
  19307. // UNIX TIMESTAMP WITH MS
  19308. case 'X':
  19309. config._d = new Date(parseFloat(input) * 1000);
  19310. break;
  19311. // TIMEZONE
  19312. case 'Z' : // fall through to ZZ
  19313. case 'ZZ' :
  19314. config._useUTC = true;
  19315. config._tzm = timezoneMinutesFromString(input);
  19316. break;
  19317. // WEEKDAY - human
  19318. case 'dd':
  19319. case 'ddd':
  19320. case 'dddd':
  19321. a = getLangDefinition(config._l).weekdaysParse(input);
  19322. // if we didn't get a weekday name, mark the date as invalid
  19323. if (a != null) {
  19324. config._w = config._w || {};
  19325. config._w['d'] = a;
  19326. } else {
  19327. config._pf.invalidWeekday = input;
  19328. }
  19329. break;
  19330. // WEEK, WEEK DAY - numeric
  19331. case 'w':
  19332. case 'ww':
  19333. case 'W':
  19334. case 'WW':
  19335. case 'd':
  19336. case 'e':
  19337. case 'E':
  19338. token = token.substr(0, 1);
  19339. /* falls through */
  19340. case 'gggg':
  19341. case 'GGGG':
  19342. case 'GGGGG':
  19343. token = token.substr(0, 2);
  19344. if (input) {
  19345. config._w = config._w || {};
  19346. config._w[token] = toInt(input);
  19347. }
  19348. break;
  19349. case 'gg':
  19350. case 'GG':
  19351. config._w = config._w || {};
  19352. config._w[token] = moment.parseTwoDigitYear(input);
  19353. }
  19354. }
  19355. function dayOfYearFromWeekInfo(config) {
  19356. var w, weekYear, week, weekday, dow, doy, temp, lang;
  19357. w = config._w;
  19358. if (w.GG != null || w.W != null || w.E != null) {
  19359. dow = 1;
  19360. doy = 4;
  19361. // TODO: We need to take the current isoWeekYear, but that depends on
  19362. // how we interpret now (local, utc, fixed offset). So create
  19363. // a now version of current config (take local/utc/offset flags, and
  19364. // create now).
  19365. weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year);
  19366. week = dfl(w.W, 1);
  19367. weekday = dfl(w.E, 1);
  19368. } else {
  19369. lang = getLangDefinition(config._l);
  19370. dow = lang._week.dow;
  19371. doy = lang._week.doy;
  19372. weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year);
  19373. week = dfl(w.w, 1);
  19374. if (w.d != null) {
  19375. // weekday -- low day numbers are considered next week
  19376. weekday = w.d;
  19377. if (weekday < dow) {
  19378. ++week;
  19379. }
  19380. } else if (w.e != null) {
  19381. // local weekday -- counting starts from begining of week
  19382. weekday = w.e + dow;
  19383. } else {
  19384. // default to begining of week
  19385. weekday = dow;
  19386. }
  19387. }
  19388. temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow);
  19389. config._a[YEAR] = temp.year;
  19390. config._dayOfYear = temp.dayOfYear;
  19391. }
  19392. // convert an array to a date.
  19393. // the array should mirror the parameters below
  19394. // note: all values past the year are optional and will default to the lowest possible value.
  19395. // [year, month, day , hour, minute, second, millisecond]
  19396. function dateFromConfig(config) {
  19397. var i, date, input = [], currentDate, yearToUse;
  19398. if (config._d) {
  19399. return;
  19400. }
  19401. currentDate = currentDateArray(config);
  19402. //compute day of the year from weeks and weekdays
  19403. if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
  19404. dayOfYearFromWeekInfo(config);
  19405. }
  19406. //if the day of the year is set, figure out what it is
  19407. if (config._dayOfYear) {
  19408. yearToUse = dfl(config._a[YEAR], currentDate[YEAR]);
  19409. if (config._dayOfYear > daysInYear(yearToUse)) {
  19410. config._pf._overflowDayOfYear = true;
  19411. }
  19412. date = makeUTCDate(yearToUse, 0, config._dayOfYear);
  19413. config._a[MONTH] = date.getUTCMonth();
  19414. config._a[DATE] = date.getUTCDate();
  19415. }
  19416. // Default to current date.
  19417. // * if no year, month, day of month are given, default to today
  19418. // * if day of month is given, default month and year
  19419. // * if month is given, default only year
  19420. // * if year is given, don't default anything
  19421. for (i = 0; i < 3 && config._a[i] == null; ++i) {
  19422. config._a[i] = input[i] = currentDate[i];
  19423. }
  19424. // Zero out whatever was not defaulted, including time
  19425. for (; i < 7; i++) {
  19426. config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
  19427. }
  19428. config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input);
  19429. // Apply timezone offset from input. The actual zone can be changed
  19430. // with parseZone.
  19431. if (config._tzm != null) {
  19432. config._d.setUTCMinutes(config._d.getUTCMinutes() + config._tzm);
  19433. }
  19434. }
  19435. function dateFromObject(config) {
  19436. var normalizedInput;
  19437. if (config._d) {
  19438. return;
  19439. }
  19440. normalizedInput = normalizeObjectUnits(config._i);
  19441. config._a = [
  19442. normalizedInput.year,
  19443. normalizedInput.month,
  19444. normalizedInput.day,
  19445. normalizedInput.hour,
  19446. normalizedInput.minute,
  19447. normalizedInput.second,
  19448. normalizedInput.millisecond
  19449. ];
  19450. dateFromConfig(config);
  19451. }
  19452. function currentDateArray(config) {
  19453. var now = new Date();
  19454. if (config._useUTC) {
  19455. return [
  19456. now.getUTCFullYear(),
  19457. now.getUTCMonth(),
  19458. now.getUTCDate()
  19459. ];
  19460. } else {
  19461. return [now.getFullYear(), now.getMonth(), now.getDate()];
  19462. }
  19463. }
  19464. // date from string and format string
  19465. function makeDateFromStringAndFormat(config) {
  19466. if (config._f === moment.ISO_8601) {
  19467. parseISO(config);
  19468. return;
  19469. }
  19470. config._a = [];
  19471. config._pf.empty = true;
  19472. // This array is used to make a Date, either with `new Date` or `Date.UTC`
  19473. var lang = getLangDefinition(config._l),
  19474. string = '' + config._i,
  19475. i, parsedInput, tokens, token, skipped,
  19476. stringLength = string.length,
  19477. totalParsedInputLength = 0;
  19478. tokens = expandFormat(config._f, lang).match(formattingTokens) || [];
  19479. for (i = 0; i < tokens.length; i++) {
  19480. token = tokens[i];
  19481. parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
  19482. if (parsedInput) {
  19483. skipped = string.substr(0, string.indexOf(parsedInput));
  19484. if (skipped.length > 0) {
  19485. config._pf.unusedInput.push(skipped);
  19486. }
  19487. string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
  19488. totalParsedInputLength += parsedInput.length;
  19489. }
  19490. // don't parse if it's not a known token
  19491. if (formatTokenFunctions[token]) {
  19492. if (parsedInput) {
  19493. config._pf.empty = false;
  19494. }
  19495. else {
  19496. config._pf.unusedTokens.push(token);
  19497. }
  19498. addTimeToArrayFromToken(token, parsedInput, config);
  19499. }
  19500. else if (config._strict && !parsedInput) {
  19501. config._pf.unusedTokens.push(token);
  19502. }
  19503. }
  19504. // add remaining unparsed input length to the string
  19505. config._pf.charsLeftOver = stringLength - totalParsedInputLength;
  19506. if (string.length > 0) {
  19507. config._pf.unusedInput.push(string);
  19508. }
  19509. // handle am pm
  19510. if (config._isPm && config._a[HOUR] < 12) {
  19511. config._a[HOUR] += 12;
  19512. }
  19513. // if is 12 am, change hours to 0
  19514. if (config._isPm === false && config._a[HOUR] === 12) {
  19515. config._a[HOUR] = 0;
  19516. }
  19517. dateFromConfig(config);
  19518. checkOverflow(config);
  19519. }
  19520. function unescapeFormat(s) {
  19521. return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
  19522. return p1 || p2 || p3 || p4;
  19523. });
  19524. }
  19525. // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
  19526. function regexpEscape(s) {
  19527. return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
  19528. }
  19529. // date from string and array of format strings
  19530. function makeDateFromStringAndArray(config) {
  19531. var tempConfig,
  19532. bestMoment,
  19533. scoreToBeat,
  19534. i,
  19535. currentScore;
  19536. if (config._f.length === 0) {
  19537. config._pf.invalidFormat = true;
  19538. config._d = new Date(NaN);
  19539. return;
  19540. }
  19541. for (i = 0; i < config._f.length; i++) {
  19542. currentScore = 0;
  19543. tempConfig = extend({}, config);
  19544. tempConfig._pf = defaultParsingFlags();
  19545. tempConfig._f = config._f[i];
  19546. makeDateFromStringAndFormat(tempConfig);
  19547. if (!isValid(tempConfig)) {
  19548. continue;
  19549. }
  19550. // if there is any input that was not parsed add a penalty for that format
  19551. currentScore += tempConfig._pf.charsLeftOver;
  19552. //or tokens
  19553. currentScore += tempConfig._pf.unusedTokens.length * 10;
  19554. tempConfig._pf.score = currentScore;
  19555. if (scoreToBeat == null || currentScore < scoreToBeat) {
  19556. scoreToBeat = currentScore;
  19557. bestMoment = tempConfig;
  19558. }
  19559. }
  19560. extend(config, bestMoment || tempConfig);
  19561. }
  19562. // date from iso format
  19563. function parseISO(config) {
  19564. var i, l,
  19565. string = config._i,
  19566. match = isoRegex.exec(string);
  19567. if (match) {
  19568. config._pf.iso = true;
  19569. for (i = 0, l = isoDates.length; i < l; i++) {
  19570. if (isoDates[i][1].exec(string)) {
  19571. // match[5] should be "T" or undefined
  19572. config._f = isoDates[i][0] + (match[6] || " ");
  19573. break;
  19574. }
  19575. }
  19576. for (i = 0, l = isoTimes.length; i < l; i++) {
  19577. if (isoTimes[i][1].exec(string)) {
  19578. config._f += isoTimes[i][0];
  19579. break;
  19580. }
  19581. }
  19582. if (string.match(parseTokenTimezone)) {
  19583. config._f += "Z";
  19584. }
  19585. makeDateFromStringAndFormat(config);
  19586. } else {
  19587. config._isValid = false;
  19588. }
  19589. }
  19590. // date from iso format or fallback
  19591. function makeDateFromString(config) {
  19592. parseISO(config);
  19593. if (config._isValid === false) {
  19594. delete config._isValid;
  19595. moment.createFromInputFallback(config);
  19596. }
  19597. }
  19598. function makeDateFromInput(config) {
  19599. var input = config._i,
  19600. matched = aspNetJsonRegex.exec(input);
  19601. if (input === undefined) {
  19602. config._d = new Date();
  19603. } else if (matched) {
  19604. config._d = new Date(+matched[1]);
  19605. } else if (typeof input === 'string') {
  19606. makeDateFromString(config);
  19607. } else if (isArray(input)) {
  19608. config._a = input.slice(0);
  19609. dateFromConfig(config);
  19610. } else if (isDate(input)) {
  19611. config._d = new Date(+input);
  19612. } else if (typeof(input) === 'object') {
  19613. dateFromObject(config);
  19614. } else if (typeof(input) === 'number') {
  19615. // from milliseconds
  19616. config._d = new Date(input);
  19617. } else {
  19618. moment.createFromInputFallback(config);
  19619. }
  19620. }
  19621. function makeDate(y, m, d, h, M, s, ms) {
  19622. //can't just apply() to create a date:
  19623. //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
  19624. var date = new Date(y, m, d, h, M, s, ms);
  19625. //the date constructor doesn't accept years < 1970
  19626. if (y < 1970) {
  19627. date.setFullYear(y);
  19628. }
  19629. return date;
  19630. }
  19631. function makeUTCDate(y) {
  19632. var date = new Date(Date.UTC.apply(null, arguments));
  19633. if (y < 1970) {
  19634. date.setUTCFullYear(y);
  19635. }
  19636. return date;
  19637. }
  19638. function parseWeekday(input, language) {
  19639. if (typeof input === 'string') {
  19640. if (!isNaN(input)) {
  19641. input = parseInt(input, 10);
  19642. }
  19643. else {
  19644. input = language.weekdaysParse(input);
  19645. if (typeof input !== 'number') {
  19646. return null;
  19647. }
  19648. }
  19649. }
  19650. return input;
  19651. }
  19652. /************************************
  19653. Relative Time
  19654. ************************************/
  19655. // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
  19656. function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) {
  19657. return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
  19658. }
  19659. function relativeTime(milliseconds, withoutSuffix, lang) {
  19660. var seconds = round(Math.abs(milliseconds) / 1000),
  19661. minutes = round(seconds / 60),
  19662. hours = round(minutes / 60),
  19663. days = round(hours / 24),
  19664. years = round(days / 365),
  19665. args = seconds < relativeTimeThresholds.s && ['s', seconds] ||
  19666. minutes === 1 && ['m'] ||
  19667. minutes < relativeTimeThresholds.m && ['mm', minutes] ||
  19668. hours === 1 && ['h'] ||
  19669. hours < relativeTimeThresholds.h && ['hh', hours] ||
  19670. days === 1 && ['d'] ||
  19671. days <= relativeTimeThresholds.dd && ['dd', days] ||
  19672. days <= relativeTimeThresholds.dm && ['M'] ||
  19673. days < relativeTimeThresholds.dy && ['MM', round(days / 30)] ||
  19674. years === 1 && ['y'] || ['yy', years];
  19675. args[2] = withoutSuffix;
  19676. args[3] = milliseconds > 0;
  19677. args[4] = lang;
  19678. return substituteTimeAgo.apply({}, args);
  19679. }
  19680. /************************************
  19681. Week of Year
  19682. ************************************/
  19683. // firstDayOfWeek 0 = sun, 6 = sat
  19684. // the day of the week that starts the week
  19685. // (usually sunday or monday)
  19686. // firstDayOfWeekOfYear 0 = sun, 6 = sat
  19687. // the first week is the week that contains the first
  19688. // of this day of the week
  19689. // (eg. ISO weeks use thursday (4))
  19690. function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
  19691. var end = firstDayOfWeekOfYear - firstDayOfWeek,
  19692. daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(),
  19693. adjustedMoment;
  19694. if (daysToDayOfWeek > end) {
  19695. daysToDayOfWeek -= 7;
  19696. }
  19697. if (daysToDayOfWeek < end - 7) {
  19698. daysToDayOfWeek += 7;
  19699. }
  19700. adjustedMoment = moment(mom).add('d', daysToDayOfWeek);
  19701. return {
  19702. week: Math.ceil(adjustedMoment.dayOfYear() / 7),
  19703. year: adjustedMoment.year()
  19704. };
  19705. }
  19706. //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
  19707. function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) {
  19708. var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear;
  19709. d = d === 0 ? 7 : d;
  19710. weekday = weekday != null ? weekday : firstDayOfWeek;
  19711. daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0);
  19712. dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1;
  19713. return {
  19714. year: dayOfYear > 0 ? year : year - 1,
  19715. dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear
  19716. };
  19717. }
  19718. /************************************
  19719. Top Level Functions
  19720. ************************************/
  19721. function makeMoment(config) {
  19722. var input = config._i,
  19723. format = config._f;
  19724. if (input === null || (format === undefined && input === '')) {
  19725. return moment.invalid({nullInput: true});
  19726. }
  19727. if (typeof input === 'string') {
  19728. config._i = input = getLangDefinition().preparse(input);
  19729. }
  19730. if (moment.isMoment(input)) {
  19731. config = cloneMoment(input);
  19732. config._d = new Date(+input._d);
  19733. } else if (format) {
  19734. if (isArray(format)) {
  19735. makeDateFromStringAndArray(config);
  19736. } else {
  19737. makeDateFromStringAndFormat(config);
  19738. }
  19739. } else {
  19740. makeDateFromInput(config);
  19741. }
  19742. return new Moment(config);
  19743. }
  19744. moment = function (input, format, lang, strict) {
  19745. var c;
  19746. if (typeof(lang) === "boolean") {
  19747. strict = lang;
  19748. lang = undefined;
  19749. }
  19750. // object construction must be done this way.
  19751. // https://github.com/moment/moment/issues/1423
  19752. c = {};
  19753. c._isAMomentObject = true;
  19754. c._i = input;
  19755. c._f = format;
  19756. c._l = lang;
  19757. c._strict = strict;
  19758. c._isUTC = false;
  19759. c._pf = defaultParsingFlags();
  19760. return makeMoment(c);
  19761. };
  19762. moment.suppressDeprecationWarnings = false;
  19763. moment.createFromInputFallback = deprecate(
  19764. "moment construction falls back to js Date. This is " +
  19765. "discouraged and will be removed in upcoming major " +
  19766. "release. Please refer to " +
  19767. "https://github.com/moment/moment/issues/1407 for more info.",
  19768. function (config) {
  19769. config._d = new Date(config._i);
  19770. });
  19771. // Pick a moment m from moments so that m[fn](other) is true for all
  19772. // other. This relies on the function fn to be transitive.
  19773. //
  19774. // moments should either be an array of moment objects or an array, whose
  19775. // first element is an array of moment objects.
  19776. function pickBy(fn, moments) {
  19777. var res, i;
  19778. if (moments.length === 1 && isArray(moments[0])) {
  19779. moments = moments[0];
  19780. }
  19781. if (!moments.length) {
  19782. return moment();
  19783. }
  19784. res = moments[0];
  19785. for (i = 1; i < moments.length; ++i) {
  19786. if (moments[i][fn](res)) {
  19787. res = moments[i];
  19788. }
  19789. }
  19790. return res;
  19791. }
  19792. moment.min = function () {
  19793. var args = [].slice.call(arguments, 0);
  19794. return pickBy('isBefore', args);
  19795. };
  19796. moment.max = function () {
  19797. var args = [].slice.call(arguments, 0);
  19798. return pickBy('isAfter', args);
  19799. };
  19800. // creating with utc
  19801. moment.utc = function (input, format, lang, strict) {
  19802. var c;
  19803. if (typeof(lang) === "boolean") {
  19804. strict = lang;
  19805. lang = undefined;
  19806. }
  19807. // object construction must be done this way.
  19808. // https://github.com/moment/moment/issues/1423
  19809. c = {};
  19810. c._isAMomentObject = true;
  19811. c._useUTC = true;
  19812. c._isUTC = true;
  19813. c._l = lang;
  19814. c._i = input;
  19815. c._f = format;
  19816. c._strict = strict;
  19817. c._pf = defaultParsingFlags();
  19818. return makeMoment(c).utc();
  19819. };
  19820. // creating with unix timestamp (in seconds)
  19821. moment.unix = function (input) {
  19822. return moment(input * 1000);
  19823. };
  19824. // duration
  19825. moment.duration = function (input, key) {
  19826. var duration = input,
  19827. // matching against regexp is expensive, do it on demand
  19828. match = null,
  19829. sign,
  19830. ret,
  19831. parseIso;
  19832. if (moment.isDuration(input)) {
  19833. duration = {
  19834. ms: input._milliseconds,
  19835. d: input._days,
  19836. M: input._months
  19837. };
  19838. } else if (typeof input === 'number') {
  19839. duration = {};
  19840. if (key) {
  19841. duration[key] = input;
  19842. } else {
  19843. duration.milliseconds = input;
  19844. }
  19845. } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) {
  19846. sign = (match[1] === "-") ? -1 : 1;
  19847. duration = {
  19848. y: 0,
  19849. d: toInt(match[DATE]) * sign,
  19850. h: toInt(match[HOUR]) * sign,
  19851. m: toInt(match[MINUTE]) * sign,
  19852. s: toInt(match[SECOND]) * sign,
  19853. ms: toInt(match[MILLISECOND]) * sign
  19854. };
  19855. } else if (!!(match = isoDurationRegex.exec(input))) {
  19856. sign = (match[1] === "-") ? -1 : 1;
  19857. parseIso = function (inp) {
  19858. // We'd normally use ~~inp for this, but unfortunately it also
  19859. // converts floats to ints.
  19860. // inp may be undefined, so careful calling replace on it.
  19861. var res = inp && parseFloat(inp.replace(',', '.'));
  19862. // apply sign while we're at it
  19863. return (isNaN(res) ? 0 : res) * sign;
  19864. };
  19865. duration = {
  19866. y: parseIso(match[2]),
  19867. M: parseIso(match[3]),
  19868. d: parseIso(match[4]),
  19869. h: parseIso(match[5]),
  19870. m: parseIso(match[6]),
  19871. s: parseIso(match[7]),
  19872. w: parseIso(match[8])
  19873. };
  19874. }
  19875. ret = new Duration(duration);
  19876. if (moment.isDuration(input) && input.hasOwnProperty('_lang')) {
  19877. ret._lang = input._lang;
  19878. }
  19879. return ret;
  19880. };
  19881. // version number
  19882. moment.version = VERSION;
  19883. // default format
  19884. moment.defaultFormat = isoFormat;
  19885. // constant that refers to the ISO standard
  19886. moment.ISO_8601 = function () {};
  19887. // Plugins that add properties should also add the key here (null value),
  19888. // so we can properly clone ourselves.
  19889. moment.momentProperties = momentProperties;
  19890. // This function will be called whenever a moment is mutated.
  19891. // It is intended to keep the offset in sync with the timezone.
  19892. moment.updateOffset = function () {};
  19893. // This function allows you to set a threshold for relative time strings
  19894. moment.relativeTimeThreshold = function(threshold, limit) {
  19895. if (relativeTimeThresholds[threshold] === undefined) {
  19896. return false;
  19897. }
  19898. relativeTimeThresholds[threshold] = limit;
  19899. return true;
  19900. };
  19901. // This function will load languages and then set the global language. If
  19902. // no arguments are passed in, it will simply return the current global
  19903. // language key.
  19904. moment.lang = function (key, values) {
  19905. var r;
  19906. if (!key) {
  19907. return moment.fn._lang._abbr;
  19908. }
  19909. if (values) {
  19910. loadLang(normalizeLanguage(key), values);
  19911. } else if (values === null) {
  19912. unloadLang(key);
  19913. key = 'en';
  19914. } else if (!languages[key]) {
  19915. getLangDefinition(key);
  19916. }
  19917. r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key);
  19918. return r._abbr;
  19919. };
  19920. // returns language data
  19921. moment.langData = function (key) {
  19922. if (key && key._lang && key._lang._abbr) {
  19923. key = key._lang._abbr;
  19924. }
  19925. return getLangDefinition(key);
  19926. };
  19927. // compare moment object
  19928. moment.isMoment = function (obj) {
  19929. return obj instanceof Moment ||
  19930. (obj != null && obj.hasOwnProperty('_isAMomentObject'));
  19931. };
  19932. // for typechecking Duration objects
  19933. moment.isDuration = function (obj) {
  19934. return obj instanceof Duration;
  19935. };
  19936. for (i = lists.length - 1; i >= 0; --i) {
  19937. makeList(lists[i]);
  19938. }
  19939. moment.normalizeUnits = function (units) {
  19940. return normalizeUnits(units);
  19941. };
  19942. moment.invalid = function (flags) {
  19943. var m = moment.utc(NaN);
  19944. if (flags != null) {
  19945. extend(m._pf, flags);
  19946. }
  19947. else {
  19948. m._pf.userInvalidated = true;
  19949. }
  19950. return m;
  19951. };
  19952. moment.parseZone = function () {
  19953. return moment.apply(null, arguments).parseZone();
  19954. };
  19955. moment.parseTwoDigitYear = function (input) {
  19956. return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
  19957. };
  19958. /************************************
  19959. Moment Prototype
  19960. ************************************/
  19961. extend(moment.fn = Moment.prototype, {
  19962. clone : function () {
  19963. return moment(this);
  19964. },
  19965. valueOf : function () {
  19966. return +this._d + ((this._offset || 0) * 60000);
  19967. },
  19968. unix : function () {
  19969. return Math.floor(+this / 1000);
  19970. },
  19971. toString : function () {
  19972. return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ");
  19973. },
  19974. toDate : function () {
  19975. return this._offset ? new Date(+this) : this._d;
  19976. },
  19977. toISOString : function () {
  19978. var m = moment(this).utc();
  19979. if (0 < m.year() && m.year() <= 9999) {
  19980. return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  19981. } else {
  19982. return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  19983. }
  19984. },
  19985. toArray : function () {
  19986. var m = this;
  19987. return [
  19988. m.year(),
  19989. m.month(),
  19990. m.date(),
  19991. m.hours(),
  19992. m.minutes(),
  19993. m.seconds(),
  19994. m.milliseconds()
  19995. ];
  19996. },
  19997. isValid : function () {
  19998. return isValid(this);
  19999. },
  20000. isDSTShifted : function () {
  20001. if (this._a) {
  20002. return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0;
  20003. }
  20004. return false;
  20005. },
  20006. parsingFlags : function () {
  20007. return extend({}, this._pf);
  20008. },
  20009. invalidAt: function () {
  20010. return this._pf.overflow;
  20011. },
  20012. utc : function () {
  20013. return this.zone(0);
  20014. },
  20015. local : function () {
  20016. this.zone(0);
  20017. this._isUTC = false;
  20018. return this;
  20019. },
  20020. format : function (inputString) {
  20021. var output = formatMoment(this, inputString || moment.defaultFormat);
  20022. return this.lang().postformat(output);
  20023. },
  20024. add : function (input, val) {
  20025. var dur;
  20026. // switch args to support add('s', 1) and add(1, 's')
  20027. if (typeof input === 'string' && typeof val === 'string') {
  20028. dur = moment.duration(isNaN(+val) ? +input : +val, isNaN(+val) ? val : input);
  20029. } else if (typeof input === 'string') {
  20030. dur = moment.duration(+val, input);
  20031. } else {
  20032. dur = moment.duration(input, val);
  20033. }
  20034. addOrSubtractDurationFromMoment(this, dur, 1);
  20035. return this;
  20036. },
  20037. subtract : function (input, val) {
  20038. var dur;
  20039. // switch args to support subtract('s', 1) and subtract(1, 's')
  20040. if (typeof input === 'string' && typeof val === 'string') {
  20041. dur = moment.duration(isNaN(+val) ? +input : +val, isNaN(+val) ? val : input);
  20042. } else if (typeof input === 'string') {
  20043. dur = moment.duration(+val, input);
  20044. } else {
  20045. dur = moment.duration(input, val);
  20046. }
  20047. addOrSubtractDurationFromMoment(this, dur, -1);
  20048. return this;
  20049. },
  20050. diff : function (input, units, asFloat) {
  20051. var that = makeAs(input, this),
  20052. zoneDiff = (this.zone() - that.zone()) * 6e4,
  20053. diff, output;
  20054. units = normalizeUnits(units);
  20055. if (units === 'year' || units === 'month') {
  20056. // average number of days in the months in the given dates
  20057. diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2
  20058. // difference in months
  20059. output = ((this.year() - that.year()) * 12) + (this.month() - that.month());
  20060. // adjust by taking difference in days, average number of days
  20061. // and dst in the given months.
  20062. output += ((this - moment(this).startOf('month')) -
  20063. (that - moment(that).startOf('month'))) / diff;
  20064. // same as above but with zones, to negate all dst
  20065. output -= ((this.zone() - moment(this).startOf('month').zone()) -
  20066. (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff;
  20067. if (units === 'year') {
  20068. output = output / 12;
  20069. }
  20070. } else {
  20071. diff = (this - that);
  20072. output = units === 'second' ? diff / 1e3 : // 1000
  20073. units === 'minute' ? diff / 6e4 : // 1000 * 60
  20074. units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
  20075. units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
  20076. units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
  20077. diff;
  20078. }
  20079. return asFloat ? output : absRound(output);
  20080. },
  20081. from : function (time, withoutSuffix) {
  20082. return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix);
  20083. },
  20084. fromNow : function (withoutSuffix) {
  20085. return this.from(moment(), withoutSuffix);
  20086. },
  20087. calendar : function (time) {
  20088. // We want to compare the start of today, vs this.
  20089. // Getting start-of-today depends on whether we're zone'd or not.
  20090. var now = time || moment(),
  20091. sod = makeAs(now, this).startOf('day'),
  20092. diff = this.diff(sod, 'days', true),
  20093. format = diff < -6 ? 'sameElse' :
  20094. diff < -1 ? 'lastWeek' :
  20095. diff < 0 ? 'lastDay' :
  20096. diff < 1 ? 'sameDay' :
  20097. diff < 2 ? 'nextDay' :
  20098. diff < 7 ? 'nextWeek' : 'sameElse';
  20099. return this.format(this.lang().calendar(format, this));
  20100. },
  20101. isLeapYear : function () {
  20102. return isLeapYear(this.year());
  20103. },
  20104. isDST : function () {
  20105. return (this.zone() < this.clone().month(0).zone() ||
  20106. this.zone() < this.clone().month(5).zone());
  20107. },
  20108. day : function (input) {
  20109. var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
  20110. if (input != null) {
  20111. input = parseWeekday(input, this.lang());
  20112. return this.add({ d : input - day });
  20113. } else {
  20114. return day;
  20115. }
  20116. },
  20117. month : makeAccessor('Month', true),
  20118. startOf: function (units) {
  20119. units = normalizeUnits(units);
  20120. // the following switch intentionally omits break keywords
  20121. // to utilize falling through the cases.
  20122. switch (units) {
  20123. case 'year':
  20124. this.month(0);
  20125. /* falls through */
  20126. case 'quarter':
  20127. case 'month':
  20128. this.date(1);
  20129. /* falls through */
  20130. case 'week':
  20131. case 'isoWeek':
  20132. case 'day':
  20133. this.hours(0);
  20134. /* falls through */
  20135. case 'hour':
  20136. this.minutes(0);
  20137. /* falls through */
  20138. case 'minute':
  20139. this.seconds(0);
  20140. /* falls through */
  20141. case 'second':
  20142. this.milliseconds(0);
  20143. /* falls through */
  20144. }
  20145. // weeks are a special case
  20146. if (units === 'week') {
  20147. this.weekday(0);
  20148. } else if (units === 'isoWeek') {
  20149. this.isoWeekday(1);
  20150. }
  20151. // quarters are also special
  20152. if (units === 'quarter') {
  20153. this.month(Math.floor(this.month() / 3) * 3);
  20154. }
  20155. return this;
  20156. },
  20157. endOf: function (units) {
  20158. units = normalizeUnits(units);
  20159. return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1);
  20160. },
  20161. isAfter: function (input, units) {
  20162. units = typeof units !== 'undefined' ? units : 'millisecond';
  20163. return +this.clone().startOf(units) > +moment(input).startOf(units);
  20164. },
  20165. isBefore: function (input, units) {
  20166. units = typeof units !== 'undefined' ? units : 'millisecond';
  20167. return +this.clone().startOf(units) < +moment(input).startOf(units);
  20168. },
  20169. isSame: function (input, units) {
  20170. units = units || 'ms';
  20171. return +this.clone().startOf(units) === +makeAs(input, this).startOf(units);
  20172. },
  20173. min: deprecate(
  20174. "moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",
  20175. function (other) {
  20176. other = moment.apply(null, arguments);
  20177. return other < this ? this : other;
  20178. }
  20179. ),
  20180. max: deprecate(
  20181. "moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",
  20182. function (other) {
  20183. other = moment.apply(null, arguments);
  20184. return other > this ? this : other;
  20185. }
  20186. ),
  20187. // keepTime = true means only change the timezone, without affecting
  20188. // the local hour. So 5:31:26 +0300 --[zone(2, true)]--> 5:31:26 +0200
  20189. // It is possible that 5:31:26 doesn't exist int zone +0200, so we
  20190. // adjust the time as needed, to be valid.
  20191. //
  20192. // Keeping the time actually adds/subtracts (one hour)
  20193. // from the actual represented time. That is why we call updateOffset
  20194. // a second time. In case it wants us to change the offset again
  20195. // _changeInProgress == true case, then we have to adjust, because
  20196. // there is no such time in the given timezone.
  20197. zone : function (input, keepTime) {
  20198. var offset = this._offset || 0;
  20199. if (input != null) {
  20200. if (typeof input === "string") {
  20201. input = timezoneMinutesFromString(input);
  20202. }
  20203. if (Math.abs(input) < 16) {
  20204. input = input * 60;
  20205. }
  20206. this._offset = input;
  20207. this._isUTC = true;
  20208. if (offset !== input) {
  20209. if (!keepTime || this._changeInProgress) {
  20210. addOrSubtractDurationFromMoment(this,
  20211. moment.duration(offset - input, 'm'), 1, false);
  20212. } else if (!this._changeInProgress) {
  20213. this._changeInProgress = true;
  20214. moment.updateOffset(this, true);
  20215. this._changeInProgress = null;
  20216. }
  20217. }
  20218. } else {
  20219. return this._isUTC ? offset : this._d.getTimezoneOffset();
  20220. }
  20221. return this;
  20222. },
  20223. zoneAbbr : function () {
  20224. return this._isUTC ? "UTC" : "";
  20225. },
  20226. zoneName : function () {
  20227. return this._isUTC ? "Coordinated Universal Time" : "";
  20228. },
  20229. parseZone : function () {
  20230. if (this._tzm) {
  20231. this.zone(this._tzm);
  20232. } else if (typeof this._i === 'string') {
  20233. this.zone(this._i);
  20234. }
  20235. return this;
  20236. },
  20237. hasAlignedHourOffset : function (input) {
  20238. if (!input) {
  20239. input = 0;
  20240. }
  20241. else {
  20242. input = moment(input).zone();
  20243. }
  20244. return (this.zone() - input) % 60 === 0;
  20245. },
  20246. daysInMonth : function () {
  20247. return daysInMonth(this.year(), this.month());
  20248. },
  20249. dayOfYear : function (input) {
  20250. var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1;
  20251. return input == null ? dayOfYear : this.add("d", (input - dayOfYear));
  20252. },
  20253. quarter : function (input) {
  20254. return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
  20255. },
  20256. weekYear : function (input) {
  20257. var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year;
  20258. return input == null ? year : this.add("y", (input - year));
  20259. },
  20260. isoWeekYear : function (input) {
  20261. var year = weekOfYear(this, 1, 4).year;
  20262. return input == null ? year : this.add("y", (input - year));
  20263. },
  20264. week : function (input) {
  20265. var week = this.lang().week(this);
  20266. return input == null ? week : this.add("d", (input - week) * 7);
  20267. },
  20268. isoWeek : function (input) {
  20269. var week = weekOfYear(this, 1, 4).week;
  20270. return input == null ? week : this.add("d", (input - week) * 7);
  20271. },
  20272. weekday : function (input) {
  20273. var weekday = (this.day() + 7 - this.lang()._week.dow) % 7;
  20274. return input == null ? weekday : this.add("d", input - weekday);
  20275. },
  20276. isoWeekday : function (input) {
  20277. // behaves the same as moment#day except
  20278. // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
  20279. // as a setter, sunday should belong to the previous week.
  20280. return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
  20281. },
  20282. isoWeeksInYear : function () {
  20283. return weeksInYear(this.year(), 1, 4);
  20284. },
  20285. weeksInYear : function () {
  20286. var weekInfo = this._lang._week;
  20287. return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
  20288. },
  20289. get : function (units) {
  20290. units = normalizeUnits(units);
  20291. return this[units]();
  20292. },
  20293. set : function (units, value) {
  20294. units = normalizeUnits(units);
  20295. if (typeof this[units] === 'function') {
  20296. this[units](value);
  20297. }
  20298. return this;
  20299. },
  20300. // If passed a language key, it will set the language for this
  20301. // instance. Otherwise, it will return the language configuration
  20302. // variables for this instance.
  20303. lang : function (key) {
  20304. if (key === undefined) {
  20305. return this._lang;
  20306. } else {
  20307. this._lang = getLangDefinition(key);
  20308. return this;
  20309. }
  20310. }
  20311. });
  20312. function rawMonthSetter(mom, value) {
  20313. var dayOfMonth;
  20314. // TODO: Move this out of here!
  20315. if (typeof value === 'string') {
  20316. value = mom.lang().monthsParse(value);
  20317. // TODO: Another silent failure?
  20318. if (typeof value !== 'number') {
  20319. return mom;
  20320. }
  20321. }
  20322. dayOfMonth = Math.min(mom.date(),
  20323. daysInMonth(mom.year(), value));
  20324. mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
  20325. return mom;
  20326. }
  20327. function rawGetter(mom, unit) {
  20328. return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]();
  20329. }
  20330. function rawSetter(mom, unit, value) {
  20331. if (unit === 'Month') {
  20332. return rawMonthSetter(mom, value);
  20333. } else {
  20334. return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
  20335. }
  20336. }
  20337. function makeAccessor(unit, keepTime) {
  20338. return function (value) {
  20339. if (value != null) {
  20340. rawSetter(this, unit, value);
  20341. moment.updateOffset(this, keepTime);
  20342. return this;
  20343. } else {
  20344. return rawGetter(this, unit);
  20345. }
  20346. };
  20347. }
  20348. moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false);
  20349. moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false);
  20350. moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false);
  20351. // Setting the hour should keep the time, because the user explicitly
  20352. // specified which hour he wants. So trying to maintain the same hour (in
  20353. // a new timezone) makes sense. Adding/subtracting hours does not follow
  20354. // this rule.
  20355. moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true);
  20356. // moment.fn.month is defined separately
  20357. moment.fn.date = makeAccessor('Date', true);
  20358. moment.fn.dates = deprecate("dates accessor is deprecated. Use date instead.", makeAccessor('Date', true));
  20359. moment.fn.year = makeAccessor('FullYear', true);
  20360. moment.fn.years = deprecate("years accessor is deprecated. Use year instead.", makeAccessor('FullYear', true));
  20361. // add plural methods
  20362. moment.fn.days = moment.fn.day;
  20363. moment.fn.months = moment.fn.month;
  20364. moment.fn.weeks = moment.fn.week;
  20365. moment.fn.isoWeeks = moment.fn.isoWeek;
  20366. moment.fn.quarters = moment.fn.quarter;
  20367. // add aliased format methods
  20368. moment.fn.toJSON = moment.fn.toISOString;
  20369. /************************************
  20370. Duration Prototype
  20371. ************************************/
  20372. extend(moment.duration.fn = Duration.prototype, {
  20373. _bubble : function () {
  20374. var milliseconds = this._milliseconds,
  20375. days = this._days,
  20376. months = this._months,
  20377. data = this._data,
  20378. seconds, minutes, hours, years;
  20379. // The following code bubbles up values, see the tests for
  20380. // examples of what that means.
  20381. data.milliseconds = milliseconds % 1000;
  20382. seconds = absRound(milliseconds / 1000);
  20383. data.seconds = seconds % 60;
  20384. minutes = absRound(seconds / 60);
  20385. data.minutes = minutes % 60;
  20386. hours = absRound(minutes / 60);
  20387. data.hours = hours % 24;
  20388. days += absRound(hours / 24);
  20389. data.days = days % 30;
  20390. months += absRound(days / 30);
  20391. data.months = months % 12;
  20392. years = absRound(months / 12);
  20393. data.years = years;
  20394. },
  20395. weeks : function () {
  20396. return absRound(this.days() / 7);
  20397. },
  20398. valueOf : function () {
  20399. return this._milliseconds +
  20400. this._days * 864e5 +
  20401. (this._months % 12) * 2592e6 +
  20402. toInt(this._months / 12) * 31536e6;
  20403. },
  20404. humanize : function (withSuffix) {
  20405. var difference = +this,
  20406. output = relativeTime(difference, !withSuffix, this.lang());
  20407. if (withSuffix) {
  20408. output = this.lang().pastFuture(difference, output);
  20409. }
  20410. return this.lang().postformat(output);
  20411. },
  20412. add : function (input, val) {
  20413. // supports only 2.0-style add(1, 's') or add(moment)
  20414. var dur = moment.duration(input, val);
  20415. this._milliseconds += dur._milliseconds;
  20416. this._days += dur._days;
  20417. this._months += dur._months;
  20418. this._bubble();
  20419. return this;
  20420. },
  20421. subtract : function (input, val) {
  20422. var dur = moment.duration(input, val);
  20423. this._milliseconds -= dur._milliseconds;
  20424. this._days -= dur._days;
  20425. this._months -= dur._months;
  20426. this._bubble();
  20427. return this;
  20428. },
  20429. get : function (units) {
  20430. units = normalizeUnits(units);
  20431. return this[units.toLowerCase() + 's']();
  20432. },
  20433. as : function (units) {
  20434. units = normalizeUnits(units);
  20435. return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's']();
  20436. },
  20437. lang : moment.fn.lang,
  20438. toIsoString : function () {
  20439. // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
  20440. var years = Math.abs(this.years()),
  20441. months = Math.abs(this.months()),
  20442. days = Math.abs(this.days()),
  20443. hours = Math.abs(this.hours()),
  20444. minutes = Math.abs(this.minutes()),
  20445. seconds = Math.abs(this.seconds() + this.milliseconds() / 1000);
  20446. if (!this.asSeconds()) {
  20447. // this is the same as C#'s (Noda) and python (isodate)...
  20448. // but not other JS (goog.date)
  20449. return 'P0D';
  20450. }
  20451. return (this.asSeconds() < 0 ? '-' : '') +
  20452. 'P' +
  20453. (years ? years + 'Y' : '') +
  20454. (months ? months + 'M' : '') +
  20455. (days ? days + 'D' : '') +
  20456. ((hours || minutes || seconds) ? 'T' : '') +
  20457. (hours ? hours + 'H' : '') +
  20458. (minutes ? minutes + 'M' : '') +
  20459. (seconds ? seconds + 'S' : '');
  20460. }
  20461. });
  20462. function makeDurationGetter(name) {
  20463. moment.duration.fn[name] = function () {
  20464. return this._data[name];
  20465. };
  20466. }
  20467. function makeDurationAsGetter(name, factor) {
  20468. moment.duration.fn['as' + name] = function () {
  20469. return +this / factor;
  20470. };
  20471. }
  20472. for (i in unitMillisecondFactors) {
  20473. if (unitMillisecondFactors.hasOwnProperty(i)) {
  20474. makeDurationAsGetter(i, unitMillisecondFactors[i]);
  20475. makeDurationGetter(i.toLowerCase());
  20476. }
  20477. }
  20478. makeDurationAsGetter('Weeks', 6048e5);
  20479. moment.duration.fn.asMonths = function () {
  20480. return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12;
  20481. };
  20482. /************************************
  20483. Default Lang
  20484. ************************************/
  20485. // Set default language, other languages will inherit from English.
  20486. moment.lang('en', {
  20487. ordinal : function (number) {
  20488. var b = number % 10,
  20489. output = (toInt(number % 100 / 10) === 1) ? 'th' :
  20490. (b === 1) ? 'st' :
  20491. (b === 2) ? 'nd' :
  20492. (b === 3) ? 'rd' : 'th';
  20493. return number + output;
  20494. }
  20495. });
  20496. /* EMBED_LANGUAGES */
  20497. /************************************
  20498. Exposing Moment
  20499. ************************************/
  20500. function makeGlobal(shouldDeprecate) {
  20501. /*global ender:false */
  20502. if (typeof ender !== 'undefined') {
  20503. return;
  20504. }
  20505. oldGlobalMoment = globalScope.moment;
  20506. if (shouldDeprecate) {
  20507. globalScope.moment = deprecate(
  20508. "Accessing Moment through the global scope is " +
  20509. "deprecated, and will be removed in an upcoming " +
  20510. "release.",
  20511. moment);
  20512. } else {
  20513. globalScope.moment = moment;
  20514. }
  20515. }
  20516. // CommonJS module is defined
  20517. if (hasModule) {
  20518. module.exports = moment;
  20519. } else if (true) {
  20520. !(__WEBPACK_AMD_DEFINE_RESULT__ = (function (require, exports, module) {
  20521. if (module.config && module.config() && module.config().noGlobal === true) {
  20522. // release the global variable
  20523. globalScope.moment = oldGlobalMoment;
  20524. }
  20525. return moment;
  20526. }.call(exports, __webpack_require__, exports, module)), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  20527. makeGlobal(true);
  20528. } else {
  20529. makeGlobal();
  20530. }
  20531. }).call(this);
  20532. /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()), __webpack_require__(60)(module)))
  20533. /***/ },
  20534. /* 47 */
  20535. /***/ function(module, exports, __webpack_require__) {
  20536. /**
  20537. * Copyright 2012 Craig Campbell
  20538. *
  20539. * Licensed under the Apache License, Version 2.0 (the "License");
  20540. * you may not use this file except in compliance with the License.
  20541. * You may obtain a copy of the License at
  20542. *
  20543. * http://www.apache.org/licenses/LICENSE-2.0
  20544. *
  20545. * Unless required by applicable law or agreed to in writing, software
  20546. * distributed under the License is distributed on an "AS IS" BASIS,
  20547. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20548. * See the License for the specific language governing permissions and
  20549. * limitations under the License.
  20550. *
  20551. * Mousetrap is a simple keyboard shortcut library for Javascript with
  20552. * no external dependencies
  20553. *
  20554. * @version 1.1.2
  20555. * @url craig.is/killing/mice
  20556. */
  20557. /**
  20558. * mapping of special keycodes to their corresponding keys
  20559. *
  20560. * everything in this dictionary cannot use keypress events
  20561. * so it has to be here to map to the correct keycodes for
  20562. * keyup/keydown events
  20563. *
  20564. * @type {Object}
  20565. */
  20566. var _MAP = {
  20567. 8: 'backspace',
  20568. 9: 'tab',
  20569. 13: 'enter',
  20570. 16: 'shift',
  20571. 17: 'ctrl',
  20572. 18: 'alt',
  20573. 20: 'capslock',
  20574. 27: 'esc',
  20575. 32: 'space',
  20576. 33: 'pageup',
  20577. 34: 'pagedown',
  20578. 35: 'end',
  20579. 36: 'home',
  20580. 37: 'left',
  20581. 38: 'up',
  20582. 39: 'right',
  20583. 40: 'down',
  20584. 45: 'ins',
  20585. 46: 'del',
  20586. 91: 'meta',
  20587. 93: 'meta',
  20588. 224: 'meta'
  20589. },
  20590. /**
  20591. * mapping for special characters so they can support
  20592. *
  20593. * this dictionary is only used incase you want to bind a
  20594. * keyup or keydown event to one of these keys
  20595. *
  20596. * @type {Object}
  20597. */
  20598. _KEYCODE_MAP = {
  20599. 106: '*',
  20600. 107: '+',
  20601. 109: '-',
  20602. 110: '.',
  20603. 111 : '/',
  20604. 186: ';',
  20605. 187: '=',
  20606. 188: ',',
  20607. 189: '-',
  20608. 190: '.',
  20609. 191: '/',
  20610. 192: '`',
  20611. 219: '[',
  20612. 220: '\\',
  20613. 221: ']',
  20614. 222: '\''
  20615. },
  20616. /**
  20617. * this is a mapping of keys that require shift on a US keypad
  20618. * back to the non shift equivelents
  20619. *
  20620. * this is so you can use keyup events with these keys
  20621. *
  20622. * note that this will only work reliably on US keyboards
  20623. *
  20624. * @type {Object}
  20625. */
  20626. _SHIFT_MAP = {
  20627. '~': '`',
  20628. '!': '1',
  20629. '@': '2',
  20630. '#': '3',
  20631. '$': '4',
  20632. '%': '5',
  20633. '^': '6',
  20634. '&': '7',
  20635. '*': '8',
  20636. '(': '9',
  20637. ')': '0',
  20638. '_': '-',
  20639. '+': '=',
  20640. ':': ';',
  20641. '\"': '\'',
  20642. '<': ',',
  20643. '>': '.',
  20644. '?': '/',
  20645. '|': '\\'
  20646. },
  20647. /**
  20648. * this is a list of special strings you can use to map
  20649. * to modifier keys when you specify your keyboard shortcuts
  20650. *
  20651. * @type {Object}
  20652. */
  20653. _SPECIAL_ALIASES = {
  20654. 'option': 'alt',
  20655. 'command': 'meta',
  20656. 'return': 'enter',
  20657. 'escape': 'esc'
  20658. },
  20659. /**
  20660. * variable to store the flipped version of _MAP from above
  20661. * needed to check if we should use keypress or not when no action
  20662. * is specified
  20663. *
  20664. * @type {Object|undefined}
  20665. */
  20666. _REVERSE_MAP,
  20667. /**
  20668. * a list of all the callbacks setup via Mousetrap.bind()
  20669. *
  20670. * @type {Object}
  20671. */
  20672. _callbacks = {},
  20673. /**
  20674. * direct map of string combinations to callbacks used for trigger()
  20675. *
  20676. * @type {Object}
  20677. */
  20678. _direct_map = {},
  20679. /**
  20680. * keeps track of what level each sequence is at since multiple
  20681. * sequences can start out with the same sequence
  20682. *
  20683. * @type {Object}
  20684. */
  20685. _sequence_levels = {},
  20686. /**
  20687. * variable to store the setTimeout call
  20688. *
  20689. * @type {null|number}
  20690. */
  20691. _reset_timer,
  20692. /**
  20693. * temporary state where we will ignore the next keyup
  20694. *
  20695. * @type {boolean|string}
  20696. */
  20697. _ignore_next_keyup = false,
  20698. /**
  20699. * are we currently inside of a sequence?
  20700. * type of action ("keyup" or "keydown" or "keypress") or false
  20701. *
  20702. * @type {boolean|string}
  20703. */
  20704. _inside_sequence = false;
  20705. /**
  20706. * loop through the f keys, f1 to f19 and add them to the map
  20707. * programatically
  20708. */
  20709. for (var i = 1; i < 20; ++i) {
  20710. _MAP[111 + i] = 'f' + i;
  20711. }
  20712. /**
  20713. * loop through to map numbers on the numeric keypad
  20714. */
  20715. for (i = 0; i <= 9; ++i) {
  20716. _MAP[i + 96] = i;
  20717. }
  20718. /**
  20719. * cross browser add event method
  20720. *
  20721. * @param {Element|HTMLDocument} object
  20722. * @param {string} type
  20723. * @param {Function} callback
  20724. * @returns void
  20725. */
  20726. function _addEvent(object, type, callback) {
  20727. if (object.addEventListener) {
  20728. return object.addEventListener(type, callback, false);
  20729. }
  20730. object.attachEvent('on' + type, callback);
  20731. }
  20732. /**
  20733. * takes the event and returns the key character
  20734. *
  20735. * @param {Event} e
  20736. * @return {string}
  20737. */
  20738. function _characterFromEvent(e) {
  20739. // for keypress events we should return the character as is
  20740. if (e.type == 'keypress') {
  20741. return String.fromCharCode(e.which);
  20742. }
  20743. // for non keypress events the special maps are needed
  20744. if (_MAP[e.which]) {
  20745. return _MAP[e.which];
  20746. }
  20747. if (_KEYCODE_MAP[e.which]) {
  20748. return _KEYCODE_MAP[e.which];
  20749. }
  20750. // if it is not in the special map
  20751. return String.fromCharCode(e.which).toLowerCase();
  20752. }
  20753. /**
  20754. * should we stop this event before firing off callbacks
  20755. *
  20756. * @param {Event} e
  20757. * @return {boolean}
  20758. */
  20759. function _stop(e) {
  20760. var element = e.target || e.srcElement,
  20761. tag_name = element.tagName;
  20762. // if the element has the class "mousetrap" then no need to stop
  20763. if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
  20764. return false;
  20765. }
  20766. // stop for input, select, and textarea
  20767. return tag_name == 'INPUT' || tag_name == 'SELECT' || tag_name == 'TEXTAREA' || (element.contentEditable && element.contentEditable == 'true');
  20768. }
  20769. /**
  20770. * checks if two arrays are equal
  20771. *
  20772. * @param {Array} modifiers1
  20773. * @param {Array} modifiers2
  20774. * @returns {boolean}
  20775. */
  20776. function _modifiersMatch(modifiers1, modifiers2) {
  20777. return modifiers1.sort().join(',') === modifiers2.sort().join(',');
  20778. }
  20779. /**
  20780. * resets all sequence counters except for the ones passed in
  20781. *
  20782. * @param {Object} do_not_reset
  20783. * @returns void
  20784. */
  20785. function _resetSequences(do_not_reset) {
  20786. do_not_reset = do_not_reset || {};
  20787. var active_sequences = false,
  20788. key;
  20789. for (key in _sequence_levels) {
  20790. if (do_not_reset[key]) {
  20791. active_sequences = true;
  20792. continue;
  20793. }
  20794. _sequence_levels[key] = 0;
  20795. }
  20796. if (!active_sequences) {
  20797. _inside_sequence = false;
  20798. }
  20799. }
  20800. /**
  20801. * finds all callbacks that match based on the keycode, modifiers,
  20802. * and action
  20803. *
  20804. * @param {string} character
  20805. * @param {Array} modifiers
  20806. * @param {string} action
  20807. * @param {boolean=} remove - should we remove any matches
  20808. * @param {string=} combination
  20809. * @returns {Array}
  20810. */
  20811. function _getMatches(character, modifiers, action, remove, combination) {
  20812. var i,
  20813. callback,
  20814. matches = [];
  20815. // if there are no events related to this keycode
  20816. if (!_callbacks[character]) {
  20817. return [];
  20818. }
  20819. // if a modifier key is coming up on its own we should allow it
  20820. if (action == 'keyup' && _isModifier(character)) {
  20821. modifiers = [character];
  20822. }
  20823. // loop through all callbacks for the key that was pressed
  20824. // and see if any of them match
  20825. for (i = 0; i < _callbacks[character].length; ++i) {
  20826. callback = _callbacks[character][i];
  20827. // if this is a sequence but it is not at the right level
  20828. // then move onto the next match
  20829. if (callback.seq && _sequence_levels[callback.seq] != callback.level) {
  20830. continue;
  20831. }
  20832. // if the action we are looking for doesn't match the action we got
  20833. // then we should keep going
  20834. if (action != callback.action) {
  20835. continue;
  20836. }
  20837. // if this is a keypress event that means that we need to only
  20838. // look at the character, otherwise check the modifiers as
  20839. // well
  20840. if (action == 'keypress' || _modifiersMatch(modifiers, callback.modifiers)) {
  20841. // remove is used so if you change your mind and call bind a
  20842. // second time with a new function the first one is overwritten
  20843. if (remove && callback.combo == combination) {
  20844. _callbacks[character].splice(i, 1);
  20845. }
  20846. matches.push(callback);
  20847. }
  20848. }
  20849. return matches;
  20850. }
  20851. /**
  20852. * takes a key event and figures out what the modifiers are
  20853. *
  20854. * @param {Event} e
  20855. * @returns {Array}
  20856. */
  20857. function _eventModifiers(e) {
  20858. var modifiers = [];
  20859. if (e.shiftKey) {
  20860. modifiers.push('shift');
  20861. }
  20862. if (e.altKey) {
  20863. modifiers.push('alt');
  20864. }
  20865. if (e.ctrlKey) {
  20866. modifiers.push('ctrl');
  20867. }
  20868. if (e.metaKey) {
  20869. modifiers.push('meta');
  20870. }
  20871. return modifiers;
  20872. }
  20873. /**
  20874. * actually calls the callback function
  20875. *
  20876. * if your callback function returns false this will use the jquery
  20877. * convention - prevent default and stop propogation on the event
  20878. *
  20879. * @param {Function} callback
  20880. * @param {Event} e
  20881. * @returns void
  20882. */
  20883. function _fireCallback(callback, e) {
  20884. if (callback(e) === false) {
  20885. if (e.preventDefault) {
  20886. e.preventDefault();
  20887. }
  20888. if (e.stopPropagation) {
  20889. e.stopPropagation();
  20890. }
  20891. e.returnValue = false;
  20892. e.cancelBubble = true;
  20893. }
  20894. }
  20895. /**
  20896. * handles a character key event
  20897. *
  20898. * @param {string} character
  20899. * @param {Event} e
  20900. * @returns void
  20901. */
  20902. function _handleCharacter(character, e) {
  20903. // if this event should not happen stop here
  20904. if (_stop(e)) {
  20905. return;
  20906. }
  20907. var callbacks = _getMatches(character, _eventModifiers(e), e.type),
  20908. i,
  20909. do_not_reset = {},
  20910. processed_sequence_callback = false;
  20911. // loop through matching callbacks for this key event
  20912. for (i = 0; i < callbacks.length; ++i) {
  20913. // fire for all sequence callbacks
  20914. // this is because if for example you have multiple sequences
  20915. // bound such as "g i" and "g t" they both need to fire the
  20916. // callback for matching g cause otherwise you can only ever
  20917. // match the first one
  20918. if (callbacks[i].seq) {
  20919. processed_sequence_callback = true;
  20920. // keep a list of which sequences were matches for later
  20921. do_not_reset[callbacks[i].seq] = 1;
  20922. _fireCallback(callbacks[i].callback, e);
  20923. continue;
  20924. }
  20925. // if there were no sequence matches but we are still here
  20926. // that means this is a regular match so we should fire that
  20927. if (!processed_sequence_callback && !_inside_sequence) {
  20928. _fireCallback(callbacks[i].callback, e);
  20929. }
  20930. }
  20931. // if you are inside of a sequence and the key you are pressing
  20932. // is not a modifier key then we should reset all sequences
  20933. // that were not matched by this key event
  20934. if (e.type == _inside_sequence && !_isModifier(character)) {
  20935. _resetSequences(do_not_reset);
  20936. }
  20937. }
  20938. /**
  20939. * handles a keydown event
  20940. *
  20941. * @param {Event} e
  20942. * @returns void
  20943. */
  20944. function _handleKey(e) {
  20945. // normalize e.which for key events
  20946. // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
  20947. e.which = typeof e.which == "number" ? e.which : e.keyCode;
  20948. var character = _characterFromEvent(e);
  20949. // no character found then stop
  20950. if (!character) {
  20951. return;
  20952. }
  20953. if (e.type == 'keyup' && _ignore_next_keyup == character) {
  20954. _ignore_next_keyup = false;
  20955. return;
  20956. }
  20957. _handleCharacter(character, e);
  20958. }
  20959. /**
  20960. * determines if the keycode specified is a modifier key or not
  20961. *
  20962. * @param {string} key
  20963. * @returns {boolean}
  20964. */
  20965. function _isModifier(key) {
  20966. return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta';
  20967. }
  20968. /**
  20969. * called to set a 1 second timeout on the specified sequence
  20970. *
  20971. * this is so after each key press in the sequence you have 1 second
  20972. * to press the next key before you have to start over
  20973. *
  20974. * @returns void
  20975. */
  20976. function _resetSequenceTimer() {
  20977. clearTimeout(_reset_timer);
  20978. _reset_timer = setTimeout(_resetSequences, 1000);
  20979. }
  20980. /**
  20981. * reverses the map lookup so that we can look for specific keys
  20982. * to see what can and can't use keypress
  20983. *
  20984. * @return {Object}
  20985. */
  20986. function _getReverseMap() {
  20987. if (!_REVERSE_MAP) {
  20988. _REVERSE_MAP = {};
  20989. for (var key in _MAP) {
  20990. // pull out the numeric keypad from here cause keypress should
  20991. // be able to detect the keys from the character
  20992. if (key > 95 && key < 112) {
  20993. continue;
  20994. }
  20995. if (_MAP.hasOwnProperty(key)) {
  20996. _REVERSE_MAP[_MAP[key]] = key;
  20997. }
  20998. }
  20999. }
  21000. return _REVERSE_MAP;
  21001. }
  21002. /**
  21003. * picks the best action based on the key combination
  21004. *
  21005. * @param {string} key - character for key
  21006. * @param {Array} modifiers
  21007. * @param {string=} action passed in
  21008. */
  21009. function _pickBestAction(key, modifiers, action) {
  21010. // if no action was picked in we should try to pick the one
  21011. // that we think would work best for this key
  21012. if (!action) {
  21013. action = _getReverseMap()[key] ? 'keydown' : 'keypress';
  21014. }
  21015. // modifier keys don't work as expected with keypress,
  21016. // switch to keydown
  21017. if (action == 'keypress' && modifiers.length) {
  21018. action = 'keydown';
  21019. }
  21020. return action;
  21021. }
  21022. /**
  21023. * binds a key sequence to an event
  21024. *
  21025. * @param {string} combo - combo specified in bind call
  21026. * @param {Array} keys
  21027. * @param {Function} callback
  21028. * @param {string=} action
  21029. * @returns void
  21030. */
  21031. function _bindSequence(combo, keys, callback, action) {
  21032. // start off by adding a sequence level record for this combination
  21033. // and setting the level to 0
  21034. _sequence_levels[combo] = 0;
  21035. // if there is no action pick the best one for the first key
  21036. // in the sequence
  21037. if (!action) {
  21038. action = _pickBestAction(keys[0], []);
  21039. }
  21040. /**
  21041. * callback to increase the sequence level for this sequence and reset
  21042. * all other sequences that were active
  21043. *
  21044. * @param {Event} e
  21045. * @returns void
  21046. */
  21047. var _increaseSequence = function(e) {
  21048. _inside_sequence = action;
  21049. ++_sequence_levels[combo];
  21050. _resetSequenceTimer();
  21051. },
  21052. /**
  21053. * wraps the specified callback inside of another function in order
  21054. * to reset all sequence counters as soon as this sequence is done
  21055. *
  21056. * @param {Event} e
  21057. * @returns void
  21058. */
  21059. _callbackAndReset = function(e) {
  21060. _fireCallback(callback, e);
  21061. // we should ignore the next key up if the action is key down
  21062. // or keypress. this is so if you finish a sequence and
  21063. // release the key the final key will not trigger a keyup
  21064. if (action !== 'keyup') {
  21065. _ignore_next_keyup = _characterFromEvent(e);
  21066. }
  21067. // weird race condition if a sequence ends with the key
  21068. // another sequence begins with
  21069. setTimeout(_resetSequences, 10);
  21070. },
  21071. i;
  21072. // loop through keys one at a time and bind the appropriate callback
  21073. // function. for any key leading up to the final one it should
  21074. // increase the sequence. after the final, it should reset all sequences
  21075. for (i = 0; i < keys.length; ++i) {
  21076. _bindSingle(keys[i], i < keys.length - 1 ? _increaseSequence : _callbackAndReset, action, combo, i);
  21077. }
  21078. }
  21079. /**
  21080. * binds a single keyboard combination
  21081. *
  21082. * @param {string} combination
  21083. * @param {Function} callback
  21084. * @param {string=} action
  21085. * @param {string=} sequence_name - name of sequence if part of sequence
  21086. * @param {number=} level - what part of the sequence the command is
  21087. * @returns void
  21088. */
  21089. function _bindSingle(combination, callback, action, sequence_name, level) {
  21090. // make sure multiple spaces in a row become a single space
  21091. combination = combination.replace(/\s+/g, ' ');
  21092. var sequence = combination.split(' '),
  21093. i,
  21094. key,
  21095. keys,
  21096. modifiers = [];
  21097. // if this pattern is a sequence of keys then run through this method
  21098. // to reprocess each pattern one key at a time
  21099. if (sequence.length > 1) {
  21100. return _bindSequence(combination, sequence, callback, action);
  21101. }
  21102. // take the keys from this pattern and figure out what the actual
  21103. // pattern is all about
  21104. keys = combination === '+' ? ['+'] : combination.split('+');
  21105. for (i = 0; i < keys.length; ++i) {
  21106. key = keys[i];
  21107. // normalize key names
  21108. if (_SPECIAL_ALIASES[key]) {
  21109. key = _SPECIAL_ALIASES[key];
  21110. }
  21111. // if this is not a keypress event then we should
  21112. // be smart about using shift keys
  21113. // this will only work for US keyboards however
  21114. if (action && action != 'keypress' && _SHIFT_MAP[key]) {
  21115. key = _SHIFT_MAP[key];
  21116. modifiers.push('shift');
  21117. }
  21118. // if this key is a modifier then add it to the list of modifiers
  21119. if (_isModifier(key)) {
  21120. modifiers.push(key);
  21121. }
  21122. }
  21123. // depending on what the key combination is
  21124. // we will try to pick the best event for it
  21125. action = _pickBestAction(key, modifiers, action);
  21126. // make sure to initialize array if this is the first time
  21127. // a callback is added for this key
  21128. if (!_callbacks[key]) {
  21129. _callbacks[key] = [];
  21130. }
  21131. // remove an existing match if there is one
  21132. _getMatches(key, modifiers, action, !sequence_name, combination);
  21133. // add this call back to the array
  21134. // if it is a sequence put it at the beginning
  21135. // if not put it at the end
  21136. //
  21137. // this is important because the way these are processed expects
  21138. // the sequence ones to come first
  21139. _callbacks[key][sequence_name ? 'unshift' : 'push']({
  21140. callback: callback,
  21141. modifiers: modifiers,
  21142. action: action,
  21143. seq: sequence_name,
  21144. level: level,
  21145. combo: combination
  21146. });
  21147. }
  21148. /**
  21149. * binds multiple combinations to the same callback
  21150. *
  21151. * @param {Array} combinations
  21152. * @param {Function} callback
  21153. * @param {string|undefined} action
  21154. * @returns void
  21155. */
  21156. function _bindMultiple(combinations, callback, action) {
  21157. for (var i = 0; i < combinations.length; ++i) {
  21158. _bindSingle(combinations[i], callback, action);
  21159. }
  21160. }
  21161. // start!
  21162. _addEvent(document, 'keypress', _handleKey);
  21163. _addEvent(document, 'keydown', _handleKey);
  21164. _addEvent(document, 'keyup', _handleKey);
  21165. var mousetrap = {
  21166. /**
  21167. * binds an event to mousetrap
  21168. *
  21169. * can be a single key, a combination of keys separated with +,
  21170. * a comma separated list of keys, an array of keys, or
  21171. * a sequence of keys separated by spaces
  21172. *
  21173. * be sure to list the modifier keys first to make sure that the
  21174. * correct key ends up getting bound (the last key in the pattern)
  21175. *
  21176. * @param {string|Array} keys
  21177. * @param {Function} callback
  21178. * @param {string=} action - 'keypress', 'keydown', or 'keyup'
  21179. * @returns void
  21180. */
  21181. bind: function(keys, callback, action) {
  21182. _bindMultiple(keys instanceof Array ? keys : [keys], callback, action);
  21183. _direct_map[keys + ':' + action] = callback;
  21184. return this;
  21185. },
  21186. /**
  21187. * unbinds an event to mousetrap
  21188. *
  21189. * the unbinding sets the callback function of the specified key combo
  21190. * to an empty function and deletes the corresponding key in the
  21191. * _direct_map dict.
  21192. *
  21193. * the keycombo+action has to be exactly the same as
  21194. * it was defined in the bind method
  21195. *
  21196. * TODO: actually remove this from the _callbacks dictionary instead
  21197. * of binding an empty function
  21198. *
  21199. * @param {string|Array} keys
  21200. * @param {string} action
  21201. * @returns void
  21202. */
  21203. unbind: function(keys, action) {
  21204. if (_direct_map[keys + ':' + action]) {
  21205. delete _direct_map[keys + ':' + action];
  21206. this.bind(keys, function() {}, action);
  21207. }
  21208. return this;
  21209. },
  21210. /**
  21211. * triggers an event that has already been bound
  21212. *
  21213. * @param {string} keys
  21214. * @param {string=} action
  21215. * @returns void
  21216. */
  21217. trigger: function(keys, action) {
  21218. _direct_map[keys + ':' + action]();
  21219. return this;
  21220. },
  21221. /**
  21222. * resets the library back to its initial state. this is useful
  21223. * if you want to clear out the current keyboard shortcuts and bind
  21224. * new ones - for example if you switch to another page
  21225. *
  21226. * @returns void
  21227. */
  21228. reset: function() {
  21229. _callbacks = {};
  21230. _direct_map = {};
  21231. return this;
  21232. }
  21233. };
  21234. module.exports = mousetrap;
  21235. /***/ },
  21236. /* 48 */
  21237. /***/ function(module, exports, __webpack_require__) {
  21238. var __WEBPACK_AMD_DEFINE_RESULT__;/*! Hammer.JS - v1.1.3 - 2014-05-20
  21239. * http://eightmedia.github.io/hammer.js
  21240. *
  21241. * Copyright (c) 2014 Jorik Tangelder <j.tangelder@gmail.com>;
  21242. * Licensed under the MIT license */
  21243. (function(window, undefined) {
  21244. 'use strict';
  21245. /**
  21246. * @main
  21247. * @module hammer
  21248. *
  21249. * @class Hammer
  21250. * @static
  21251. */
  21252. /**
  21253. * Hammer, use this to create instances
  21254. * ````
  21255. * var hammertime = new Hammer(myElement);
  21256. * ````
  21257. *
  21258. * @method Hammer
  21259. * @param {HTMLElement} element
  21260. * @param {Object} [options={}]
  21261. * @return {Hammer.Instance}
  21262. */
  21263. var Hammer = function Hammer(element, options) {
  21264. return new Hammer.Instance(element, options || {});
  21265. };
  21266. /**
  21267. * version, as defined in package.json
  21268. * the value will be set at each build
  21269. * @property VERSION
  21270. * @final
  21271. * @type {String}
  21272. */
  21273. Hammer.VERSION = '1.1.3';
  21274. /**
  21275. * default settings.
  21276. * more settings are defined per gesture at `/gestures`. Each gesture can be disabled/enabled
  21277. * by setting it's name (like `swipe`) to false.
  21278. * You can set the defaults for all instances by changing this object before creating an instance.
  21279. * @example
  21280. * ````
  21281. * Hammer.defaults.drag = false;
  21282. * Hammer.defaults.behavior.touchAction = 'pan-y';
  21283. * delete Hammer.defaults.behavior.userSelect;
  21284. * ````
  21285. * @property defaults
  21286. * @type {Object}
  21287. */
  21288. Hammer.defaults = {
  21289. /**
  21290. * this setting object adds styles and attributes to the element to prevent the browser from doing
  21291. * its native behavior. The css properties are auto prefixed for the browsers when needed.
  21292. * @property defaults.behavior
  21293. * @type {Object}
  21294. */
  21295. behavior: {
  21296. /**
  21297. * Disables text selection to improve the dragging gesture. When the value is `none` it also sets
  21298. * `onselectstart=false` for IE on the element. Mainly for desktop browsers.
  21299. * @property defaults.behavior.userSelect
  21300. * @type {String}
  21301. * @default 'none'
  21302. */
  21303. userSelect: 'none',
  21304. /**
  21305. * Specifies whether and how a given region can be manipulated by the user (for instance, by panning or zooming).
  21306. * Used by Chrome 35> and IE10>. By default this makes the element blocking any touch event.
  21307. * @property defaults.behavior.touchAction
  21308. * @type {String}
  21309. * @default: 'pan-y'
  21310. */
  21311. touchAction: 'pan-y',
  21312. /**
  21313. * Disables the default callout shown when you touch and hold a touch target.
  21314. * On iOS, when you touch and hold a touch target such as a link, Safari displays
  21315. * a callout containing information about the link. This property allows you to disable that callout.
  21316. * @property defaults.behavior.touchCallout
  21317. * @type {String}
  21318. * @default 'none'
  21319. */
  21320. touchCallout: 'none',
  21321. /**
  21322. * Specifies whether zooming is enabled. Used by IE10>
  21323. * @property defaults.behavior.contentZooming
  21324. * @type {String}
  21325. * @default 'none'
  21326. */
  21327. contentZooming: 'none',
  21328. /**
  21329. * Specifies that an entire element should be draggable instead of its contents.
  21330. * Mainly for desktop browsers.
  21331. * @property defaults.behavior.userDrag
  21332. * @type {String}
  21333. * @default 'none'
  21334. */
  21335. userDrag: 'none',
  21336. /**
  21337. * Overrides the highlight color shown when the user taps a link or a JavaScript
  21338. * clickable element in Safari on iPhone. This property obeys the alpha value, if specified.
  21339. *
  21340. * If you don't specify an alpha value, Safari on iPhone applies a default alpha value
  21341. * to the color. To disable tap highlighting, set the alpha value to 0 (invisible).
  21342. * If you set the alpha value to 1.0 (opaque), the element is not visible when tapped.
  21343. * @property defaults.behavior.tapHighlightColor
  21344. * @type {String}
  21345. * @default 'rgba(0,0,0,0)'
  21346. */
  21347. tapHighlightColor: 'rgba(0,0,0,0)'
  21348. }
  21349. };
  21350. /**
  21351. * hammer document where the base events are added at
  21352. * @property DOCUMENT
  21353. * @type {HTMLElement}
  21354. * @default window.document
  21355. */
  21356. Hammer.DOCUMENT = document;
  21357. /**
  21358. * detect support for pointer events
  21359. * @property HAS_POINTEREVENTS
  21360. * @type {Boolean}
  21361. */
  21362. Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;
  21363. /**
  21364. * detect support for touch events
  21365. * @property HAS_TOUCHEVENTS
  21366. * @type {Boolean}
  21367. */
  21368. Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);
  21369. /**
  21370. * detect mobile browsers
  21371. * @property IS_MOBILE
  21372. * @type {Boolean}
  21373. */
  21374. Hammer.IS_MOBILE = /mobile|tablet|ip(ad|hone|od)|android|silk/i.test(navigator.userAgent);
  21375. /**
  21376. * detect if we want to support mouseevents at all
  21377. * @property NO_MOUSEEVENTS
  21378. * @type {Boolean}
  21379. */
  21380. Hammer.NO_MOUSEEVENTS = (Hammer.HAS_TOUCHEVENTS && Hammer.IS_MOBILE) || Hammer.HAS_POINTEREVENTS;
  21381. /**
  21382. * interval in which Hammer recalculates current velocity/direction/angle in ms
  21383. * @property CALCULATE_INTERVAL
  21384. * @type {Number}
  21385. * @default 25
  21386. */
  21387. Hammer.CALCULATE_INTERVAL = 25;
  21388. /**
  21389. * eventtypes per touchevent (start, move, end) are filled by `Event.determineEventTypes` on `setup`
  21390. * the object contains the DOM event names per type (`EVENT_START`, `EVENT_MOVE`, `EVENT_END`)
  21391. * @property EVENT_TYPES
  21392. * @private
  21393. * @writeOnce
  21394. * @type {Object}
  21395. */
  21396. var EVENT_TYPES = {};
  21397. /**
  21398. * direction strings, for safe comparisons
  21399. * @property DIRECTION_DOWN|LEFT|UP|RIGHT
  21400. * @final
  21401. * @type {String}
  21402. * @default 'down' 'left' 'up' 'right'
  21403. */
  21404. var DIRECTION_DOWN = Hammer.DIRECTION_DOWN = 'down';
  21405. var DIRECTION_LEFT = Hammer.DIRECTION_LEFT = 'left';
  21406. var DIRECTION_UP = Hammer.DIRECTION_UP = 'up';
  21407. var DIRECTION_RIGHT = Hammer.DIRECTION_RIGHT = 'right';
  21408. /**
  21409. * pointertype strings, for safe comparisons
  21410. * @property POINTER_MOUSE|TOUCH|PEN
  21411. * @final
  21412. * @type {String}
  21413. * @default 'mouse' 'touch' 'pen'
  21414. */
  21415. var POINTER_MOUSE = Hammer.POINTER_MOUSE = 'mouse';
  21416. var POINTER_TOUCH = Hammer.POINTER_TOUCH = 'touch';
  21417. var POINTER_PEN = Hammer.POINTER_PEN = 'pen';
  21418. /**
  21419. * eventtypes
  21420. * @property EVENT_START|MOVE|END|RELEASE|TOUCH
  21421. * @final
  21422. * @type {String}
  21423. * @default 'start' 'change' 'move' 'end' 'release' 'touch'
  21424. */
  21425. var EVENT_START = Hammer.EVENT_START = 'start';
  21426. var EVENT_MOVE = Hammer.EVENT_MOVE = 'move';
  21427. var EVENT_END = Hammer.EVENT_END = 'end';
  21428. var EVENT_RELEASE = Hammer.EVENT_RELEASE = 'release';
  21429. var EVENT_TOUCH = Hammer.EVENT_TOUCH = 'touch';
  21430. /**
  21431. * if the window events are set...
  21432. * @property READY
  21433. * @writeOnce
  21434. * @type {Boolean}
  21435. * @default false
  21436. */
  21437. Hammer.READY = false;
  21438. /**
  21439. * plugins namespace
  21440. * @property plugins
  21441. * @type {Object}
  21442. */
  21443. Hammer.plugins = Hammer.plugins || {};
  21444. /**
  21445. * gestures namespace
  21446. * see `/gestures` for the definitions
  21447. * @property gestures
  21448. * @type {Object}
  21449. */
  21450. Hammer.gestures = Hammer.gestures || {};
  21451. /**
  21452. * setup events to detect gestures on the document
  21453. * this function is called when creating an new instance
  21454. * @private
  21455. */
  21456. function setup() {
  21457. if(Hammer.READY) {
  21458. return;
  21459. }
  21460. // find what eventtypes we add listeners to
  21461. Event.determineEventTypes();
  21462. // Register all gestures inside Hammer.gestures
  21463. Utils.each(Hammer.gestures, function(gesture) {
  21464. Detection.register(gesture);
  21465. });
  21466. // Add touch events on the document
  21467. Event.onTouch(Hammer.DOCUMENT, EVENT_MOVE, Detection.detect);
  21468. Event.onTouch(Hammer.DOCUMENT, EVENT_END, Detection.detect);
  21469. // Hammer is ready...!
  21470. Hammer.READY = true;
  21471. }
  21472. /**
  21473. * @module hammer
  21474. *
  21475. * @class Utils
  21476. * @static
  21477. */
  21478. var Utils = Hammer.utils = {
  21479. /**
  21480. * extend method, could also be used for cloning when `dest` is an empty object.
  21481. * changes the dest object
  21482. * @method extend
  21483. * @param {Object} dest
  21484. * @param {Object} src
  21485. * @param {Boolean} [merge=false] do a merge
  21486. * @return {Object} dest
  21487. */
  21488. extend: function extend(dest, src, merge) {
  21489. for(var key in src) {
  21490. if(!src.hasOwnProperty(key) || (dest[key] !== undefined && merge)) {
  21491. continue;
  21492. }
  21493. dest[key] = src[key];
  21494. }
  21495. return dest;
  21496. },
  21497. /**
  21498. * simple addEventListener wrapper
  21499. * @method on
  21500. * @param {HTMLElement} element
  21501. * @param {String} type
  21502. * @param {Function} handler
  21503. */
  21504. on: function on(element, type, handler) {
  21505. element.addEventListener(type, handler, false);
  21506. },
  21507. /**
  21508. * simple removeEventListener wrapper
  21509. * @method off
  21510. * @param {HTMLElement} element
  21511. * @param {String} type
  21512. * @param {Function} handler
  21513. */
  21514. off: function off(element, type, handler) {
  21515. element.removeEventListener(type, handler, false);
  21516. },
  21517. /**
  21518. * forEach over arrays and objects
  21519. * @method each
  21520. * @param {Object|Array} obj
  21521. * @param {Function} iterator
  21522. * @param {any} iterator.item
  21523. * @param {Number} iterator.index
  21524. * @param {Object|Array} iterator.obj the source object
  21525. * @param {Object} context value to use as `this` in the iterator
  21526. */
  21527. each: function each(obj, iterator, context) {
  21528. var i, len;
  21529. // native forEach on arrays
  21530. if('forEach' in obj) {
  21531. obj.forEach(iterator, context);
  21532. // arrays
  21533. } else if(obj.length !== undefined) {
  21534. for(i = 0, len = obj.length; i < len; i++) {
  21535. if(iterator.call(context, obj[i], i, obj) === false) {
  21536. return;
  21537. }
  21538. }
  21539. // objects
  21540. } else {
  21541. for(i in obj) {
  21542. if(obj.hasOwnProperty(i) &&
  21543. iterator.call(context, obj[i], i, obj) === false) {
  21544. return;
  21545. }
  21546. }
  21547. }
  21548. },
  21549. /**
  21550. * find if a string contains the string using indexOf
  21551. * @method inStr
  21552. * @param {String} src
  21553. * @param {String} find
  21554. * @return {Boolean} found
  21555. */
  21556. inStr: function inStr(src, find) {
  21557. return src.indexOf(find) > -1;
  21558. },
  21559. /**
  21560. * find if a array contains the object using indexOf or a simple polyfill
  21561. * @method inArray
  21562. * @param {String} src
  21563. * @param {String} find
  21564. * @return {Boolean|Number} false when not found, or the index
  21565. */
  21566. inArray: function inArray(src, find) {
  21567. if(src.indexOf) {
  21568. var index = src.indexOf(find);
  21569. return (index === -1) ? false : index;
  21570. } else {
  21571. for(var i = 0, len = src.length; i < len; i++) {
  21572. if(src[i] === find) {
  21573. return i;
  21574. }
  21575. }
  21576. return false;
  21577. }
  21578. },
  21579. /**
  21580. * convert an array-like object (`arguments`, `touchlist`) to an array
  21581. * @method toArray
  21582. * @param {Object} obj
  21583. * @return {Array}
  21584. */
  21585. toArray: function toArray(obj) {
  21586. return Array.prototype.slice.call(obj, 0);
  21587. },
  21588. /**
  21589. * find if a node is in the given parent
  21590. * @method hasParent
  21591. * @param {HTMLElement} node
  21592. * @param {HTMLElement} parent
  21593. * @return {Boolean} found
  21594. */
  21595. hasParent: function hasParent(node, parent) {
  21596. while(node) {
  21597. if(node == parent) {
  21598. return true;
  21599. }
  21600. node = node.parentNode;
  21601. }
  21602. return false;
  21603. },
  21604. /**
  21605. * get the center of all the touches
  21606. * @method getCenter
  21607. * @param {Array} touches
  21608. * @return {Object} center contains `pageX`, `pageY`, `clientX` and `clientY` properties
  21609. */
  21610. getCenter: function getCenter(touches) {
  21611. var pageX = [],
  21612. pageY = [],
  21613. clientX = [],
  21614. clientY = [],
  21615. min = Math.min,
  21616. max = Math.max;
  21617. // no need to loop when only one touch
  21618. if(touches.length === 1) {
  21619. return {
  21620. pageX: touches[0].pageX,
  21621. pageY: touches[0].pageY,
  21622. clientX: touches[0].clientX,
  21623. clientY: touches[0].clientY
  21624. };
  21625. }
  21626. Utils.each(touches, function(touch) {
  21627. pageX.push(touch.pageX);
  21628. pageY.push(touch.pageY);
  21629. clientX.push(touch.clientX);
  21630. clientY.push(touch.clientY);
  21631. });
  21632. return {
  21633. pageX: (min.apply(Math, pageX) + max.apply(Math, pageX)) / 2,
  21634. pageY: (min.apply(Math, pageY) + max.apply(Math, pageY)) / 2,
  21635. clientX: (min.apply(Math, clientX) + max.apply(Math, clientX)) / 2,
  21636. clientY: (min.apply(Math, clientY) + max.apply(Math, clientY)) / 2
  21637. };
  21638. },
  21639. /**
  21640. * calculate the velocity between two points. unit is in px per ms.
  21641. * @method getVelocity
  21642. * @param {Number} deltaTime
  21643. * @param {Number} deltaX
  21644. * @param {Number} deltaY
  21645. * @return {Object} velocity `x` and `y`
  21646. */
  21647. getVelocity: function getVelocity(deltaTime, deltaX, deltaY) {
  21648. return {
  21649. x: Math.abs(deltaX / deltaTime) || 0,
  21650. y: Math.abs(deltaY / deltaTime) || 0
  21651. };
  21652. },
  21653. /**
  21654. * calculate the angle between two coordinates
  21655. * @method getAngle
  21656. * @param {Touch} touch1
  21657. * @param {Touch} touch2
  21658. * @return {Number} angle
  21659. */
  21660. getAngle: function getAngle(touch1, touch2) {
  21661. var x = touch2.clientX - touch1.clientX,
  21662. y = touch2.clientY - touch1.clientY;
  21663. return Math.atan2(y, x) * 180 / Math.PI;
  21664. },
  21665. /**
  21666. * do a small comparision to get the direction between two touches.
  21667. * @method getDirection
  21668. * @param {Touch} touch1
  21669. * @param {Touch} touch2
  21670. * @return {String} direction matches `DIRECTION_LEFT|RIGHT|UP|DOWN`
  21671. */
  21672. getDirection: function getDirection(touch1, touch2) {
  21673. var x = Math.abs(touch1.clientX - touch2.clientX),
  21674. y = Math.abs(touch1.clientY - touch2.clientY);
  21675. if(x >= y) {
  21676. return touch1.clientX - touch2.clientX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
  21677. }
  21678. return touch1.clientY - touch2.clientY > 0 ? DIRECTION_UP : DIRECTION_DOWN;
  21679. },
  21680. /**
  21681. * calculate the distance between two touches
  21682. * @method getDistance
  21683. * @param {Touch}touch1
  21684. * @param {Touch} touch2
  21685. * @return {Number} distance
  21686. */
  21687. getDistance: function getDistance(touch1, touch2) {
  21688. var x = touch2.clientX - touch1.clientX,
  21689. y = touch2.clientY - touch1.clientY;
  21690. return Math.sqrt((x * x) + (y * y));
  21691. },
  21692. /**
  21693. * calculate the scale factor between two touchLists
  21694. * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
  21695. * @method getScale
  21696. * @param {Array} start array of touches
  21697. * @param {Array} end array of touches
  21698. * @return {Number} scale
  21699. */
  21700. getScale: function getScale(start, end) {
  21701. // need two fingers...
  21702. if(start.length >= 2 && end.length >= 2) {
  21703. return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]);
  21704. }
  21705. return 1;
  21706. },
  21707. /**
  21708. * calculate the rotation degrees between two touchLists
  21709. * @method getRotation
  21710. * @param {Array} start array of touches
  21711. * @param {Array} end array of touches
  21712. * @return {Number} rotation
  21713. */
  21714. getRotation: function getRotation(start, end) {
  21715. // need two fingers
  21716. if(start.length >= 2 && end.length >= 2) {
  21717. return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]);
  21718. }
  21719. return 0;
  21720. },
  21721. /**
  21722. * find out if the direction is vertical *
  21723. * @method isVertical
  21724. * @param {String} direction matches `DIRECTION_UP|DOWN`
  21725. * @return {Boolean} is_vertical
  21726. */
  21727. isVertical: function isVertical(direction) {
  21728. return direction == DIRECTION_UP || direction == DIRECTION_DOWN;
  21729. },
  21730. /**
  21731. * set css properties with their prefixes
  21732. * @param {HTMLElement} element
  21733. * @param {String} prop
  21734. * @param {String} value
  21735. * @param {Boolean} [toggle=true]
  21736. * @return {Boolean}
  21737. */
  21738. setPrefixedCss: function setPrefixedCss(element, prop, value, toggle) {
  21739. var prefixes = ['', 'Webkit', 'Moz', 'O', 'ms'];
  21740. prop = Utils.toCamelCase(prop);
  21741. for(var i = 0; i < prefixes.length; i++) {
  21742. var p = prop;
  21743. // prefixes
  21744. if(prefixes[i]) {
  21745. p = prefixes[i] + p.slice(0, 1).toUpperCase() + p.slice(1);
  21746. }
  21747. // test the style
  21748. if(p in element.style) {
  21749. element.style[p] = (toggle == null || toggle) && value || '';
  21750. break;
  21751. }
  21752. }
  21753. },
  21754. /**
  21755. * toggle browser default behavior by setting css properties.
  21756. * `userSelect='none'` also sets `element.onselectstart` to false
  21757. * `userDrag='none'` also sets `element.ondragstart` to false
  21758. *
  21759. * @method toggleBehavior
  21760. * @param {HtmlElement} element
  21761. * @param {Object} props
  21762. * @param {Boolean} [toggle=true]
  21763. */
  21764. toggleBehavior: function toggleBehavior(element, props, toggle) {
  21765. if(!props || !element || !element.style) {
  21766. return;
  21767. }
  21768. // set the css properties
  21769. Utils.each(props, function(value, prop) {
  21770. Utils.setPrefixedCss(element, prop, value, toggle);
  21771. });
  21772. var falseFn = toggle && function() {
  21773. return false;
  21774. };
  21775. // also the disable onselectstart
  21776. if(props.userSelect == 'none') {
  21777. element.onselectstart = falseFn;
  21778. }
  21779. // and disable ondragstart
  21780. if(props.userDrag == 'none') {
  21781. element.ondragstart = falseFn;
  21782. }
  21783. },
  21784. /**
  21785. * convert a string with underscores to camelCase
  21786. * so prevent_default becomes preventDefault
  21787. * @param {String} str
  21788. * @return {String} camelCaseStr
  21789. */
  21790. toCamelCase: function toCamelCase(str) {
  21791. return str.replace(/[_-]([a-z])/g, function(s) {
  21792. return s[1].toUpperCase();
  21793. });
  21794. }
  21795. };
  21796. /**
  21797. * @module hammer
  21798. */
  21799. /**
  21800. * @class Event
  21801. * @static
  21802. */
  21803. var Event = Hammer.event = {
  21804. /**
  21805. * when touch events have been fired, this is true
  21806. * this is used to stop mouse events
  21807. * @property prevent_mouseevents
  21808. * @private
  21809. * @type {Boolean}
  21810. */
  21811. preventMouseEvents: false,
  21812. /**
  21813. * if EVENT_START has been fired
  21814. * @property started
  21815. * @private
  21816. * @type {Boolean}
  21817. */
  21818. started: false,
  21819. /**
  21820. * when the mouse is hold down, this is true
  21821. * @property should_detect
  21822. * @private
  21823. * @type {Boolean}
  21824. */
  21825. shouldDetect: false,
  21826. /**
  21827. * simple event binder with a hook and support for multiple types
  21828. * @method on
  21829. * @param {HTMLElement} element
  21830. * @param {String} type
  21831. * @param {Function} handler
  21832. * @param {Function} [hook]
  21833. * @param {Object} hook.type
  21834. */
  21835. on: function on(element, type, handler, hook) {
  21836. var types = type.split(' ');
  21837. Utils.each(types, function(type) {
  21838. Utils.on(element, type, handler);
  21839. hook && hook(type);
  21840. });
  21841. },
  21842. /**
  21843. * simple event unbinder with a hook and support for multiple types
  21844. * @method off
  21845. * @param {HTMLElement} element
  21846. * @param {String} type
  21847. * @param {Function} handler
  21848. * @param {Function} [hook]
  21849. * @param {Object} hook.type
  21850. */
  21851. off: function off(element, type, handler, hook) {
  21852. var types = type.split(' ');
  21853. Utils.each(types, function(type) {
  21854. Utils.off(element, type, handler);
  21855. hook && hook(type);
  21856. });
  21857. },
  21858. /**
  21859. * the core touch event handler.
  21860. * this finds out if we should to detect gestures
  21861. * @method onTouch
  21862. * @param {HTMLElement} element
  21863. * @param {String} eventType matches `EVENT_START|MOVE|END`
  21864. * @param {Function} handler
  21865. * @return onTouchHandler {Function} the core event handler
  21866. */
  21867. onTouch: function onTouch(element, eventType, handler) {
  21868. var self = this;
  21869. var onTouchHandler = function onTouchHandler(ev) {
  21870. var srcType = ev.type.toLowerCase(),
  21871. isPointer = Hammer.HAS_POINTEREVENTS,
  21872. isMouse = Utils.inStr(srcType, 'mouse'),
  21873. triggerType;
  21874. // if we are in a mouseevent, but there has been a touchevent triggered in this session
  21875. // we want to do nothing. simply break out of the event.
  21876. if(isMouse && self.preventMouseEvents) {
  21877. return;
  21878. // mousebutton must be down
  21879. } else if(isMouse && eventType == EVENT_START && ev.button === 0) {
  21880. self.preventMouseEvents = false;
  21881. self.shouldDetect = true;
  21882. } else if(isPointer && eventType == EVENT_START) {
  21883. self.shouldDetect = (ev.buttons === 1 || PointerEvent.matchType(POINTER_TOUCH, ev));
  21884. // just a valid start event, but no mouse
  21885. } else if(!isMouse && eventType == EVENT_START) {
  21886. self.preventMouseEvents = true;
  21887. self.shouldDetect = true;
  21888. }
  21889. // update the pointer event before entering the detection
  21890. if(isPointer && eventType != EVENT_END) {
  21891. PointerEvent.updatePointer(eventType, ev);
  21892. }
  21893. // we are in a touch/down state, so allowed detection of gestures
  21894. if(self.shouldDetect) {
  21895. triggerType = self.doDetect.call(self, ev, eventType, element, handler);
  21896. }
  21897. // ...and we are done with the detection
  21898. // so reset everything to start each detection totally fresh
  21899. if(triggerType == EVENT_END) {
  21900. self.preventMouseEvents = false;
  21901. self.shouldDetect = false;
  21902. PointerEvent.reset();
  21903. // update the pointerevent object after the detection
  21904. }
  21905. if(isPointer && eventType == EVENT_END) {
  21906. PointerEvent.updatePointer(eventType, ev);
  21907. }
  21908. };
  21909. this.on(element, EVENT_TYPES[eventType], onTouchHandler);
  21910. return onTouchHandler;
  21911. },
  21912. /**
  21913. * the core detection method
  21914. * this finds out what hammer-touch-events to trigger
  21915. * @method doDetect
  21916. * @param {Object} ev
  21917. * @param {String} eventType matches `EVENT_START|MOVE|END`
  21918. * @param {HTMLElement} element
  21919. * @param {Function} handler
  21920. * @return {String} triggerType matches `EVENT_START|MOVE|END`
  21921. */
  21922. doDetect: function doDetect(ev, eventType, element, handler) {
  21923. var touchList = this.getTouchList(ev, eventType);
  21924. var touchListLength = touchList.length;
  21925. var triggerType = eventType;
  21926. var triggerChange = touchList.trigger; // used by fakeMultitouch plugin
  21927. var changedLength = touchListLength;
  21928. // at each touchstart-like event we want also want to trigger a TOUCH event...
  21929. if(eventType == EVENT_START) {
  21930. triggerChange = EVENT_TOUCH;
  21931. // ...the same for a touchend-like event
  21932. } else if(eventType == EVENT_END) {
  21933. triggerChange = EVENT_RELEASE;
  21934. // keep track of how many touches have been removed
  21935. changedLength = touchList.length - ((ev.changedTouches) ? ev.changedTouches.length : 1);
  21936. }
  21937. // after there are still touches on the screen,
  21938. // we just want to trigger a MOVE event. so change the START or END to a MOVE
  21939. // but only after detection has been started, the first time we actualy want a START
  21940. if(changedLength > 0 && this.started) {
  21941. triggerType = EVENT_MOVE;
  21942. }
  21943. // detection has been started, we keep track of this, see above
  21944. this.started = true;
  21945. // generate some event data, some basic information
  21946. var evData = this.collectEventData(element, triggerType, touchList, ev);
  21947. // trigger the triggerType event before the change (TOUCH, RELEASE) events
  21948. // but the END event should be at last
  21949. if(eventType != EVENT_END) {
  21950. handler.call(Detection, evData);
  21951. }
  21952. // trigger a change (TOUCH, RELEASE) event, this means the length of the touches changed
  21953. if(triggerChange) {
  21954. evData.changedLength = changedLength;
  21955. evData.eventType = triggerChange;
  21956. handler.call(Detection, evData);
  21957. evData.eventType = triggerType;
  21958. delete evData.changedLength;
  21959. }
  21960. // trigger the END event
  21961. if(triggerType == EVENT_END) {
  21962. handler.call(Detection, evData);
  21963. // ...and we are done with the detection
  21964. // so reset everything to start each detection totally fresh
  21965. this.started = false;
  21966. }
  21967. return triggerType;
  21968. },
  21969. /**
  21970. * we have different events for each device/browser
  21971. * determine what we need and set them in the EVENT_TYPES constant
  21972. * the `onTouch` method is bind to these properties.
  21973. * @method determineEventTypes
  21974. * @return {Object} events
  21975. */
  21976. determineEventTypes: function determineEventTypes() {
  21977. var types;
  21978. if(Hammer.HAS_POINTEREVENTS) {
  21979. if(window.PointerEvent) {
  21980. types = [
  21981. 'pointerdown',
  21982. 'pointermove',
  21983. 'pointerup pointercancel lostpointercapture'
  21984. ];
  21985. } else {
  21986. types = [
  21987. 'MSPointerDown',
  21988. 'MSPointerMove',
  21989. 'MSPointerUp MSPointerCancel MSLostPointerCapture'
  21990. ];
  21991. }
  21992. } else if(Hammer.NO_MOUSEEVENTS) {
  21993. types = [
  21994. 'touchstart',
  21995. 'touchmove',
  21996. 'touchend touchcancel'
  21997. ];
  21998. } else {
  21999. types = [
  22000. 'touchstart mousedown',
  22001. 'touchmove mousemove',
  22002. 'touchend touchcancel mouseup'
  22003. ];
  22004. }
  22005. EVENT_TYPES[EVENT_START] = types[0];
  22006. EVENT_TYPES[EVENT_MOVE] = types[1];
  22007. EVENT_TYPES[EVENT_END] = types[2];
  22008. return EVENT_TYPES;
  22009. },
  22010. /**
  22011. * create touchList depending on the event
  22012. * @method getTouchList
  22013. * @param {Object} ev
  22014. * @param {String} eventType
  22015. * @return {Array} touches
  22016. */
  22017. getTouchList: function getTouchList(ev, eventType) {
  22018. // get the fake pointerEvent touchlist
  22019. if(Hammer.HAS_POINTEREVENTS) {
  22020. return PointerEvent.getTouchList();
  22021. }
  22022. // get the touchlist
  22023. if(ev.touches) {
  22024. if(eventType == EVENT_MOVE) {
  22025. return ev.touches;
  22026. }
  22027. var identifiers = [];
  22028. var concat = [].concat(Utils.toArray(ev.touches), Utils.toArray(ev.changedTouches));
  22029. var touchList = [];
  22030. Utils.each(concat, function(touch) {
  22031. if(Utils.inArray(identifiers, touch.identifier) === false) {
  22032. touchList.push(touch);
  22033. }
  22034. identifiers.push(touch.identifier);
  22035. });
  22036. return touchList;
  22037. }
  22038. // make fake touchList from mouse position
  22039. ev.identifier = 1;
  22040. return [ev];
  22041. },
  22042. /**
  22043. * collect basic event data
  22044. * @method collectEventData
  22045. * @param {HTMLElement} element
  22046. * @param {String} eventType matches `EVENT_START|MOVE|END`
  22047. * @param {Array} touches
  22048. * @param {Object} ev
  22049. * @return {Object} ev
  22050. */
  22051. collectEventData: function collectEventData(element, eventType, touches, ev) {
  22052. // find out pointerType
  22053. var pointerType = POINTER_TOUCH;
  22054. if(Utils.inStr(ev.type, 'mouse') || PointerEvent.matchType(POINTER_MOUSE, ev)) {
  22055. pointerType = POINTER_MOUSE;
  22056. } else if(PointerEvent.matchType(POINTER_PEN, ev)) {
  22057. pointerType = POINTER_PEN;
  22058. }
  22059. return {
  22060. center: Utils.getCenter(touches),
  22061. timeStamp: Date.now(),
  22062. target: ev.target,
  22063. touches: touches,
  22064. eventType: eventType,
  22065. pointerType: pointerType,
  22066. srcEvent: ev,
  22067. /**
  22068. * prevent the browser default actions
  22069. * mostly used to disable scrolling of the browser
  22070. */
  22071. preventDefault: function() {
  22072. var srcEvent = this.srcEvent;
  22073. srcEvent.preventManipulation && srcEvent.preventManipulation();
  22074. srcEvent.preventDefault && srcEvent.preventDefault();
  22075. },
  22076. /**
  22077. * stop bubbling the event up to its parents
  22078. */
  22079. stopPropagation: function() {
  22080. this.srcEvent.stopPropagation();
  22081. },
  22082. /**
  22083. * immediately stop gesture detection
  22084. * might be useful after a swipe was detected
  22085. * @return {*}
  22086. */
  22087. stopDetect: function() {
  22088. return Detection.stopDetect();
  22089. }
  22090. };
  22091. }
  22092. };
  22093. /**
  22094. * @module hammer
  22095. *
  22096. * @class PointerEvent
  22097. * @static
  22098. */
  22099. var PointerEvent = Hammer.PointerEvent = {
  22100. /**
  22101. * holds all pointers, by `identifier`
  22102. * @property pointers
  22103. * @type {Object}
  22104. */
  22105. pointers: {},
  22106. /**
  22107. * get the pointers as an array
  22108. * @method getTouchList
  22109. * @return {Array} touchlist
  22110. */
  22111. getTouchList: function getTouchList() {
  22112. var touchlist = [];
  22113. // we can use forEach since pointerEvents only is in IE10
  22114. Utils.each(this.pointers, function(pointer) {
  22115. touchlist.push(pointer);
  22116. });
  22117. return touchlist;
  22118. },
  22119. /**
  22120. * update the position of a pointer
  22121. * @method updatePointer
  22122. * @param {String} eventType matches `EVENT_START|MOVE|END`
  22123. * @param {Object} pointerEvent
  22124. */
  22125. updatePointer: function updatePointer(eventType, pointerEvent) {
  22126. if(eventType == EVENT_END || (eventType != EVENT_END && pointerEvent.buttons !== 1)) {
  22127. delete this.pointers[pointerEvent.pointerId];
  22128. } else {
  22129. pointerEvent.identifier = pointerEvent.pointerId;
  22130. this.pointers[pointerEvent.pointerId] = pointerEvent;
  22131. }
  22132. },
  22133. /**
  22134. * check if ev matches pointertype
  22135. * @method matchType
  22136. * @param {String} pointerType matches `POINTER_MOUSE|TOUCH|PEN`
  22137. * @param {PointerEvent} ev
  22138. */
  22139. matchType: function matchType(pointerType, ev) {
  22140. if(!ev.pointerType) {
  22141. return false;
  22142. }
  22143. var pt = ev.pointerType,
  22144. types = {};
  22145. types[POINTER_MOUSE] = (pt === (ev.MSPOINTER_TYPE_MOUSE || POINTER_MOUSE));
  22146. types[POINTER_TOUCH] = (pt === (ev.MSPOINTER_TYPE_TOUCH || POINTER_TOUCH));
  22147. types[POINTER_PEN] = (pt === (ev.MSPOINTER_TYPE_PEN || POINTER_PEN));
  22148. return types[pointerType];
  22149. },
  22150. /**
  22151. * reset the stored pointers
  22152. * @method reset
  22153. */
  22154. reset: function resetList() {
  22155. this.pointers = {};
  22156. }
  22157. };
  22158. /**
  22159. * @module hammer
  22160. *
  22161. * @class Detection
  22162. * @static
  22163. */
  22164. var Detection = Hammer.detection = {
  22165. // contains all registred Hammer.gestures in the correct order
  22166. gestures: [],
  22167. // data of the current Hammer.gesture detection session
  22168. current: null,
  22169. // the previous Hammer.gesture session data
  22170. // is a full clone of the previous gesture.current object
  22171. previous: null,
  22172. // when this becomes true, no gestures are fired
  22173. stopped: false,
  22174. /**
  22175. * start Hammer.gesture detection
  22176. * @method startDetect
  22177. * @param {Hammer.Instance} inst
  22178. * @param {Object} eventData
  22179. */
  22180. startDetect: function startDetect(inst, eventData) {
  22181. // already busy with a Hammer.gesture detection on an element
  22182. if(this.current) {
  22183. return;
  22184. }
  22185. this.stopped = false;
  22186. // holds current session
  22187. this.current = {
  22188. inst: inst, // reference to HammerInstance we're working for
  22189. startEvent: Utils.extend({}, eventData), // start eventData for distances, timing etc
  22190. lastEvent: false, // last eventData
  22191. lastCalcEvent: false, // last eventData for calculations.
  22192. futureCalcEvent: false, // last eventData for calculations.
  22193. lastCalcData: {}, // last lastCalcData
  22194. name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
  22195. };
  22196. this.detect(eventData);
  22197. },
  22198. /**
  22199. * Hammer.gesture detection
  22200. * @method detect
  22201. * @param {Object} eventData
  22202. * @return {any}
  22203. */
  22204. detect: function detect(eventData) {
  22205. if(!this.current || this.stopped) {
  22206. return;
  22207. }
  22208. // extend event data with calculations about scale, distance etc
  22209. eventData = this.extendEventData(eventData);
  22210. // hammer instance and instance options
  22211. var inst = this.current.inst,
  22212. instOptions = inst.options;
  22213. // call Hammer.gesture handlers
  22214. Utils.each(this.gestures, function triggerGesture(gesture) {
  22215. // only when the instance options have enabled this gesture
  22216. if(!this.stopped && inst.enabled && instOptions[gesture.name]) {
  22217. gesture.handler.call(gesture, eventData, inst);
  22218. }
  22219. }, this);
  22220. // store as previous event event
  22221. if(this.current) {
  22222. this.current.lastEvent = eventData;
  22223. }
  22224. if(eventData.eventType == EVENT_END) {
  22225. this.stopDetect();
  22226. }
  22227. return eventData;
  22228. },
  22229. /**
  22230. * clear the Hammer.gesture vars
  22231. * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
  22232. * to stop other Hammer.gestures from being fired
  22233. * @method stopDetect
  22234. */
  22235. stopDetect: function stopDetect() {
  22236. // clone current data to the store as the previous gesture
  22237. // used for the double tap gesture, since this is an other gesture detect session
  22238. this.previous = Utils.extend({}, this.current);
  22239. // reset the current
  22240. this.current = null;
  22241. this.stopped = true;
  22242. },
  22243. /**
  22244. * calculate velocity, angle and direction
  22245. * @method getVelocityData
  22246. * @param {Object} ev
  22247. * @param {Object} center
  22248. * @param {Number} deltaTime
  22249. * @param {Number} deltaX
  22250. * @param {Number} deltaY
  22251. */
  22252. getCalculatedData: function getCalculatedData(ev, center, deltaTime, deltaX, deltaY) {
  22253. var cur = this.current,
  22254. recalc = false,
  22255. calcEv = cur.lastCalcEvent,
  22256. calcData = cur.lastCalcData;
  22257. if(calcEv && ev.timeStamp - calcEv.timeStamp > Hammer.CALCULATE_INTERVAL) {
  22258. center = calcEv.center;
  22259. deltaTime = ev.timeStamp - calcEv.timeStamp;
  22260. deltaX = ev.center.clientX - calcEv.center.clientX;
  22261. deltaY = ev.center.clientY - calcEv.center.clientY;
  22262. recalc = true;
  22263. }
  22264. if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
  22265. cur.futureCalcEvent = ev;
  22266. }
  22267. if(!cur.lastCalcEvent || recalc) {
  22268. calcData.velocity = Utils.getVelocity(deltaTime, deltaX, deltaY);
  22269. calcData.angle = Utils.getAngle(center, ev.center);
  22270. calcData.direction = Utils.getDirection(center, ev.center);
  22271. cur.lastCalcEvent = cur.futureCalcEvent || ev;
  22272. cur.futureCalcEvent = ev;
  22273. }
  22274. ev.velocityX = calcData.velocity.x;
  22275. ev.velocityY = calcData.velocity.y;
  22276. ev.interimAngle = calcData.angle;
  22277. ev.interimDirection = calcData.direction;
  22278. },
  22279. /**
  22280. * extend eventData for Hammer.gestures
  22281. * @method extendEventData
  22282. * @param {Object} ev
  22283. * @return {Object} ev
  22284. */
  22285. extendEventData: function extendEventData(ev) {
  22286. var cur = this.current,
  22287. startEv = cur.startEvent,
  22288. lastEv = cur.lastEvent || startEv;
  22289. // update the start touchlist to calculate the scale/rotation
  22290. if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
  22291. startEv.touches = [];
  22292. Utils.each(ev.touches, function(touch) {
  22293. startEv.touches.push({
  22294. clientX: touch.clientX,
  22295. clientY: touch.clientY
  22296. });
  22297. });
  22298. }
  22299. var deltaTime = ev.timeStamp - startEv.timeStamp,
  22300. deltaX = ev.center.clientX - startEv.center.clientX,
  22301. deltaY = ev.center.clientY - startEv.center.clientY;
  22302. this.getCalculatedData(ev, lastEv.center, deltaTime, deltaX, deltaY);
  22303. Utils.extend(ev, {
  22304. startEvent: startEv,
  22305. deltaTime: deltaTime,
  22306. deltaX: deltaX,
  22307. deltaY: deltaY,
  22308. distance: Utils.getDistance(startEv.center, ev.center),
  22309. angle: Utils.getAngle(startEv.center, ev.center),
  22310. direction: Utils.getDirection(startEv.center, ev.center),
  22311. scale: Utils.getScale(startEv.touches, ev.touches),
  22312. rotation: Utils.getRotation(startEv.touches, ev.touches)
  22313. });
  22314. return ev;
  22315. },
  22316. /**
  22317. * register new gesture
  22318. * @method register
  22319. * @param {Object} gesture object, see `gestures/` for documentation
  22320. * @return {Array} gestures
  22321. */
  22322. register: function register(gesture) {
  22323. // add an enable gesture options if there is no given
  22324. var options = gesture.defaults || {};
  22325. if(options[gesture.name] === undefined) {
  22326. options[gesture.name] = true;
  22327. }
  22328. // extend Hammer default options with the Hammer.gesture options
  22329. Utils.extend(Hammer.defaults, options, true);
  22330. // set its index
  22331. gesture.index = gesture.index || 1000;
  22332. // add Hammer.gesture to the list
  22333. this.gestures.push(gesture);
  22334. // sort the list by index
  22335. this.gestures.sort(function(a, b) {
  22336. if(a.index < b.index) {
  22337. return -1;
  22338. }
  22339. if(a.index > b.index) {
  22340. return 1;
  22341. }
  22342. return 0;
  22343. });
  22344. return this.gestures;
  22345. }
  22346. };
  22347. /**
  22348. * @module hammer
  22349. */
  22350. /**
  22351. * create new hammer instance
  22352. * all methods should return the instance itself, so it is chainable.
  22353. *
  22354. * @class Instance
  22355. * @constructor
  22356. * @param {HTMLElement} element
  22357. * @param {Object} [options={}] options are merged with `Hammer.defaults`
  22358. * @return {Hammer.Instance}
  22359. */
  22360. Hammer.Instance = function(element, options) {
  22361. var self = this;
  22362. // setup HammerJS window events and register all gestures
  22363. // this also sets up the default options
  22364. setup();
  22365. /**
  22366. * @property element
  22367. * @type {HTMLElement}
  22368. */
  22369. this.element = element;
  22370. /**
  22371. * @property enabled
  22372. * @type {Boolean}
  22373. * @protected
  22374. */
  22375. this.enabled = true;
  22376. /**
  22377. * options, merged with the defaults
  22378. * options with an _ are converted to camelCase
  22379. * @property options
  22380. * @type {Object}
  22381. */
  22382. Utils.each(options, function(value, name) {
  22383. delete options[name];
  22384. options[Utils.toCamelCase(name)] = value;
  22385. });
  22386. this.options = Utils.extend(Utils.extend({}, Hammer.defaults), options || {});
  22387. // add some css to the element to prevent the browser from doing its native behavoir
  22388. if(this.options.behavior) {
  22389. Utils.toggleBehavior(this.element, this.options.behavior, true);
  22390. }
  22391. /**
  22392. * event start handler on the element to start the detection
  22393. * @property eventStartHandler
  22394. * @type {Object}
  22395. */
  22396. this.eventStartHandler = Event.onTouch(element, EVENT_START, function(ev) {
  22397. if(self.enabled && ev.eventType == EVENT_START) {
  22398. Detection.startDetect(self, ev);
  22399. } else if(ev.eventType == EVENT_TOUCH) {
  22400. Detection.detect(ev);
  22401. }
  22402. });
  22403. /**
  22404. * keep a list of user event handlers which needs to be removed when calling 'dispose'
  22405. * @property eventHandlers
  22406. * @type {Array}
  22407. */
  22408. this.eventHandlers = [];
  22409. };
  22410. Hammer.Instance.prototype = {
  22411. /**
  22412. * bind events to the instance
  22413. * @method on
  22414. * @chainable
  22415. * @param {String} gestures multiple gestures by splitting with a space
  22416. * @param {Function} handler
  22417. * @param {Object} handler.ev event object
  22418. */
  22419. on: function onEvent(gestures, handler) {
  22420. var self = this;
  22421. Event.on(self.element, gestures, handler, function(type) {
  22422. self.eventHandlers.push({ gesture: type, handler: handler });
  22423. });
  22424. return self;
  22425. },
  22426. /**
  22427. * unbind events to the instance
  22428. * @method off
  22429. * @chainable
  22430. * @param {String} gestures
  22431. * @param {Function} handler
  22432. */
  22433. off: function offEvent(gestures, handler) {
  22434. var self = this;
  22435. Event.off(self.element, gestures, handler, function(type) {
  22436. var index = Utils.inArray({ gesture: type, handler: handler });
  22437. if(index !== false) {
  22438. self.eventHandlers.splice(index, 1);
  22439. }
  22440. });
  22441. return self;
  22442. },
  22443. /**
  22444. * trigger gesture event
  22445. * @method trigger
  22446. * @chainable
  22447. * @param {String} gesture
  22448. * @param {Object} [eventData]
  22449. */
  22450. trigger: function triggerEvent(gesture, eventData) {
  22451. // optional
  22452. if(!eventData) {
  22453. eventData = {};
  22454. }
  22455. // create DOM event
  22456. var event = Hammer.DOCUMENT.createEvent('Event');
  22457. event.initEvent(gesture, true, true);
  22458. event.gesture = eventData;
  22459. // trigger on the target if it is in the instance element,
  22460. // this is for event delegation tricks
  22461. var element = this.element;
  22462. if(Utils.hasParent(eventData.target, element)) {
  22463. element = eventData.target;
  22464. }
  22465. element.dispatchEvent(event);
  22466. return this;
  22467. },
  22468. /**
  22469. * enable of disable hammer.js detection
  22470. * @method enable
  22471. * @chainable
  22472. * @param {Boolean} state
  22473. */
  22474. enable: function enable(state) {
  22475. this.enabled = state;
  22476. return this;
  22477. },
  22478. /**
  22479. * dispose this hammer instance
  22480. * @method dispose
  22481. * @return {Null}
  22482. */
  22483. dispose: function dispose() {
  22484. var i, eh;
  22485. // undo all changes made by stop_browser_behavior
  22486. Utils.toggleBehavior(this.element, this.options.behavior, false);
  22487. // unbind all custom event handlers
  22488. for(i = -1; (eh = this.eventHandlers[++i]);) {
  22489. Utils.off(this.element, eh.gesture, eh.handler);
  22490. }
  22491. this.eventHandlers = [];
  22492. // unbind the start event listener
  22493. Event.off(this.element, EVENT_TYPES[EVENT_START], this.eventStartHandler);
  22494. return null;
  22495. }
  22496. };
  22497. /**
  22498. * @module gestures
  22499. */
  22500. /**
  22501. * Move with x fingers (default 1) around on the page.
  22502. * Preventing the default browser behavior is a good way to improve feel and working.
  22503. * ````
  22504. * hammertime.on("drag", function(ev) {
  22505. * console.log(ev);
  22506. * ev.gesture.preventDefault();
  22507. * });
  22508. * ````
  22509. *
  22510. * @class Drag
  22511. * @static
  22512. */
  22513. /**
  22514. * @event drag
  22515. * @param {Object} ev
  22516. */
  22517. /**
  22518. * @event dragstart
  22519. * @param {Object} ev
  22520. */
  22521. /**
  22522. * @event dragend
  22523. * @param {Object} ev
  22524. */
  22525. /**
  22526. * @event drapleft
  22527. * @param {Object} ev
  22528. */
  22529. /**
  22530. * @event dragright
  22531. * @param {Object} ev
  22532. */
  22533. /**
  22534. * @event dragup
  22535. * @param {Object} ev
  22536. */
  22537. /**
  22538. * @event dragdown
  22539. * @param {Object} ev
  22540. */
  22541. /**
  22542. * @param {String} name
  22543. */
  22544. (function(name) {
  22545. var triggered = false;
  22546. function dragGesture(ev, inst) {
  22547. var cur = Detection.current;
  22548. // max touches
  22549. if(inst.options.dragMaxTouches > 0 &&
  22550. ev.touches.length > inst.options.dragMaxTouches) {
  22551. return;
  22552. }
  22553. switch(ev.eventType) {
  22554. case EVENT_START:
  22555. triggered = false;
  22556. break;
  22557. case EVENT_MOVE:
  22558. // when the distance we moved is too small we skip this gesture
  22559. // or we can be already in dragging
  22560. if(ev.distance < inst.options.dragMinDistance &&
  22561. cur.name != name) {
  22562. return;
  22563. }
  22564. var startCenter = cur.startEvent.center;
  22565. // we are dragging!
  22566. if(cur.name != name) {
  22567. cur.name = name;
  22568. if(inst.options.dragDistanceCorrection && ev.distance > 0) {
  22569. // When a drag is triggered, set the event center to dragMinDistance pixels from the original event center.
  22570. // Without this correction, the dragged distance would jumpstart at dragMinDistance pixels instead of at 0.
  22571. // It might be useful to save the original start point somewhere
  22572. var factor = Math.abs(inst.options.dragMinDistance / ev.distance);
  22573. startCenter.pageX += ev.deltaX * factor;
  22574. startCenter.pageY += ev.deltaY * factor;
  22575. startCenter.clientX += ev.deltaX * factor;
  22576. startCenter.clientY += ev.deltaY * factor;
  22577. // recalculate event data using new start point
  22578. ev = Detection.extendEventData(ev);
  22579. }
  22580. }
  22581. // lock drag to axis?
  22582. if(cur.lastEvent.dragLockToAxis ||
  22583. ( inst.options.dragLockToAxis &&
  22584. inst.options.dragLockMinDistance <= ev.distance
  22585. )) {
  22586. ev.dragLockToAxis = true;
  22587. }
  22588. // keep direction on the axis that the drag gesture started on
  22589. var lastDirection = cur.lastEvent.direction;
  22590. if(ev.dragLockToAxis && lastDirection !== ev.direction) {
  22591. if(Utils.isVertical(lastDirection)) {
  22592. ev.direction = (ev.deltaY < 0) ? DIRECTION_UP : DIRECTION_DOWN;
  22593. } else {
  22594. ev.direction = (ev.deltaX < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
  22595. }
  22596. }
  22597. // first time, trigger dragstart event
  22598. if(!triggered) {
  22599. inst.trigger(name + 'start', ev);
  22600. triggered = true;
  22601. }
  22602. // trigger events
  22603. inst.trigger(name, ev);
  22604. inst.trigger(name + ev.direction, ev);
  22605. var isVertical = Utils.isVertical(ev.direction);
  22606. // block the browser events
  22607. if((inst.options.dragBlockVertical && isVertical) ||
  22608. (inst.options.dragBlockHorizontal && !isVertical)) {
  22609. ev.preventDefault();
  22610. }
  22611. break;
  22612. case EVENT_RELEASE:
  22613. if(triggered && ev.changedLength <= inst.options.dragMaxTouches) {
  22614. inst.trigger(name + 'end', ev);
  22615. triggered = false;
  22616. }
  22617. break;
  22618. case EVENT_END:
  22619. triggered = false;
  22620. break;
  22621. }
  22622. }
  22623. Hammer.gestures.Drag = {
  22624. name: name,
  22625. index: 50,
  22626. handler: dragGesture,
  22627. defaults: {
  22628. /**
  22629. * minimal movement that have to be made before the drag event gets triggered
  22630. * @property dragMinDistance
  22631. * @type {Number}
  22632. * @default 10
  22633. */
  22634. dragMinDistance: 10,
  22635. /**
  22636. * Set dragDistanceCorrection to true to make the starting point of the drag
  22637. * be calculated from where the drag was triggered, not from where the touch started.
  22638. * Useful to avoid a jerk-starting drag, which can make fine-adjustments
  22639. * through dragging difficult, and be visually unappealing.
  22640. * @property dragDistanceCorrection
  22641. * @type {Boolean}
  22642. * @default true
  22643. */
  22644. dragDistanceCorrection: true,
  22645. /**
  22646. * set 0 for unlimited, but this can conflict with transform
  22647. * @property dragMaxTouches
  22648. * @type {Number}
  22649. * @default 1
  22650. */
  22651. dragMaxTouches: 1,
  22652. /**
  22653. * prevent default browser behavior when dragging occurs
  22654. * be careful with it, it makes the element a blocking element
  22655. * when you are using the drag gesture, it is a good practice to set this true
  22656. * @property dragBlockHorizontal
  22657. * @type {Boolean}
  22658. * @default false
  22659. */
  22660. dragBlockHorizontal: false,
  22661. /**
  22662. * same as `dragBlockHorizontal`, but for vertical movement
  22663. * @property dragBlockVertical
  22664. * @type {Boolean}
  22665. * @default false
  22666. */
  22667. dragBlockVertical: false,
  22668. /**
  22669. * dragLockToAxis keeps the drag gesture on the axis that it started on,
  22670. * It disallows vertical directions if the initial direction was horizontal, and vice versa.
  22671. * @property dragLockToAxis
  22672. * @type {Boolean}
  22673. * @default false
  22674. */
  22675. dragLockToAxis: false,
  22676. /**
  22677. * drag lock only kicks in when distance > dragLockMinDistance
  22678. * This way, locking occurs only when the distance has become large enough to reliably determine the direction
  22679. * @property dragLockMinDistance
  22680. * @type {Number}
  22681. * @default 25
  22682. */
  22683. dragLockMinDistance: 25
  22684. }
  22685. };
  22686. })('drag');
  22687. /**
  22688. * @module gestures
  22689. */
  22690. /**
  22691. * trigger a simple gesture event, so you can do anything in your handler.
  22692. * only usable if you know what your doing...
  22693. *
  22694. * @class Gesture
  22695. * @static
  22696. */
  22697. /**
  22698. * @event gesture
  22699. * @param {Object} ev
  22700. */
  22701. Hammer.gestures.Gesture = {
  22702. name: 'gesture',
  22703. index: 1337,
  22704. handler: function releaseGesture(ev, inst) {
  22705. inst.trigger(this.name, ev);
  22706. }
  22707. };
  22708. /**
  22709. * @module gestures
  22710. */
  22711. /**
  22712. * Touch stays at the same place for x time
  22713. *
  22714. * @class Hold
  22715. * @static
  22716. */
  22717. /**
  22718. * @event hold
  22719. * @param {Object} ev
  22720. */
  22721. /**
  22722. * @param {String} name
  22723. */
  22724. (function(name) {
  22725. var timer;
  22726. function holdGesture(ev, inst) {
  22727. var options = inst.options,
  22728. current = Detection.current;
  22729. switch(ev.eventType) {
  22730. case EVENT_START:
  22731. clearTimeout(timer);
  22732. // set the gesture so we can check in the timeout if it still is
  22733. current.name = name;
  22734. // set timer and if after the timeout it still is hold,
  22735. // we trigger the hold event
  22736. timer = setTimeout(function() {
  22737. if(current && current.name == name) {
  22738. inst.trigger(name, ev);
  22739. }
  22740. }, options.holdTimeout);
  22741. break;
  22742. case EVENT_MOVE:
  22743. if(ev.distance > options.holdThreshold) {
  22744. clearTimeout(timer);
  22745. }
  22746. break;
  22747. case EVENT_RELEASE:
  22748. clearTimeout(timer);
  22749. break;
  22750. }
  22751. }
  22752. Hammer.gestures.Hold = {
  22753. name: name,
  22754. index: 10,
  22755. defaults: {
  22756. /**
  22757. * @property holdTimeout
  22758. * @type {Number}
  22759. * @default 500
  22760. */
  22761. holdTimeout: 500,
  22762. /**
  22763. * movement allowed while holding
  22764. * @property holdThreshold
  22765. * @type {Number}
  22766. * @default 2
  22767. */
  22768. holdThreshold: 2
  22769. },
  22770. handler: holdGesture
  22771. };
  22772. })('hold');
  22773. /**
  22774. * @module gestures
  22775. */
  22776. /**
  22777. * when a touch is being released from the page
  22778. *
  22779. * @class Release
  22780. * @static
  22781. */
  22782. /**
  22783. * @event release
  22784. * @param {Object} ev
  22785. */
  22786. Hammer.gestures.Release = {
  22787. name: 'release',
  22788. index: Infinity,
  22789. handler: function releaseGesture(ev, inst) {
  22790. if(ev.eventType == EVENT_RELEASE) {
  22791. inst.trigger(this.name, ev);
  22792. }
  22793. }
  22794. };
  22795. /**
  22796. * @module gestures
  22797. */
  22798. /**
  22799. * triggers swipe events when the end velocity is above the threshold
  22800. * for best usage, set `preventDefault` (on the drag gesture) to `true`
  22801. * ````
  22802. * hammertime.on("dragleft swipeleft", function(ev) {
  22803. * console.log(ev);
  22804. * ev.gesture.preventDefault();
  22805. * });
  22806. * ````
  22807. *
  22808. * @class Swipe
  22809. * @static
  22810. */
  22811. /**
  22812. * @event swipe
  22813. * @param {Object} ev
  22814. */
  22815. /**
  22816. * @event swipeleft
  22817. * @param {Object} ev
  22818. */
  22819. /**
  22820. * @event swiperight
  22821. * @param {Object} ev
  22822. */
  22823. /**
  22824. * @event swipeup
  22825. * @param {Object} ev
  22826. */
  22827. /**
  22828. * @event swipedown
  22829. * @param {Object} ev
  22830. */
  22831. Hammer.gestures.Swipe = {
  22832. name: 'swipe',
  22833. index: 40,
  22834. defaults: {
  22835. /**
  22836. * @property swipeMinTouches
  22837. * @type {Number}
  22838. * @default 1
  22839. */
  22840. swipeMinTouches: 1,
  22841. /**
  22842. * @property swipeMaxTouches
  22843. * @type {Number}
  22844. * @default 1
  22845. */
  22846. swipeMaxTouches: 1,
  22847. /**
  22848. * horizontal swipe velocity
  22849. * @property swipeVelocityX
  22850. * @type {Number}
  22851. * @default 0.6
  22852. */
  22853. swipeVelocityX: 0.6,
  22854. /**
  22855. * vertical swipe velocity
  22856. * @property swipeVelocityY
  22857. * @type {Number}
  22858. * @default 0.6
  22859. */
  22860. swipeVelocityY: 0.6
  22861. },
  22862. handler: function swipeGesture(ev, inst) {
  22863. if(ev.eventType == EVENT_RELEASE) {
  22864. var touches = ev.touches.length,
  22865. options = inst.options;
  22866. // max touches
  22867. if(touches < options.swipeMinTouches ||
  22868. touches > options.swipeMaxTouches) {
  22869. return;
  22870. }
  22871. // when the distance we moved is too small we skip this gesture
  22872. // or we can be already in dragging
  22873. if(ev.velocityX > options.swipeVelocityX ||
  22874. ev.velocityY > options.swipeVelocityY) {
  22875. // trigger swipe events
  22876. inst.trigger(this.name, ev);
  22877. inst.trigger(this.name + ev.direction, ev);
  22878. }
  22879. }
  22880. }
  22881. };
  22882. /**
  22883. * @module gestures
  22884. */
  22885. /**
  22886. * Single tap and a double tap on a place
  22887. *
  22888. * @class Tap
  22889. * @static
  22890. */
  22891. /**
  22892. * @event tap
  22893. * @param {Object} ev
  22894. */
  22895. /**
  22896. * @event doubletap
  22897. * @param {Object} ev
  22898. */
  22899. /**
  22900. * @param {String} name
  22901. */
  22902. (function(name) {
  22903. var hasMoved = false;
  22904. function tapGesture(ev, inst) {
  22905. var options = inst.options,
  22906. current = Detection.current,
  22907. prev = Detection.previous,
  22908. sincePrev,
  22909. didDoubleTap;
  22910. switch(ev.eventType) {
  22911. case EVENT_START:
  22912. hasMoved = false;
  22913. break;
  22914. case EVENT_MOVE:
  22915. hasMoved = hasMoved || (ev.distance > options.tapMaxDistance);
  22916. break;
  22917. case EVENT_END:
  22918. if(!Utils.inStr(ev.srcEvent.type, 'cancel') && ev.deltaTime < options.tapMaxTime && !hasMoved) {
  22919. // previous gesture, for the double tap since these are two different gesture detections
  22920. sincePrev = prev && prev.lastEvent && ev.timeStamp - prev.lastEvent.timeStamp;
  22921. didDoubleTap = false;
  22922. // check if double tap
  22923. if(prev && prev.name == name &&
  22924. (sincePrev && sincePrev < options.doubleTapInterval) &&
  22925. ev.distance < options.doubleTapDistance) {
  22926. inst.trigger('doubletap', ev);
  22927. didDoubleTap = true;
  22928. }
  22929. // do a single tap
  22930. if(!didDoubleTap || options.tapAlways) {
  22931. current.name = name;
  22932. inst.trigger(current.name, ev);
  22933. }
  22934. }
  22935. break;
  22936. }
  22937. }
  22938. Hammer.gestures.Tap = {
  22939. name: name,
  22940. index: 100,
  22941. handler: tapGesture,
  22942. defaults: {
  22943. /**
  22944. * max time of a tap, this is for the slow tappers
  22945. * @property tapMaxTime
  22946. * @type {Number}
  22947. * @default 250
  22948. */
  22949. tapMaxTime: 250,
  22950. /**
  22951. * max distance of movement of a tap, this is for the slow tappers
  22952. * @property tapMaxDistance
  22953. * @type {Number}
  22954. * @default 10
  22955. */
  22956. tapMaxDistance: 10,
  22957. /**
  22958. * always trigger the `tap` event, even while double-tapping
  22959. * @property tapAlways
  22960. * @type {Boolean}
  22961. * @default true
  22962. */
  22963. tapAlways: true,
  22964. /**
  22965. * max distance between two taps
  22966. * @property doubleTapDistance
  22967. * @type {Number}
  22968. * @default 20
  22969. */
  22970. doubleTapDistance: 20,
  22971. /**
  22972. * max time between two taps
  22973. * @property doubleTapInterval
  22974. * @type {Number}
  22975. * @default 300
  22976. */
  22977. doubleTapInterval: 300
  22978. }
  22979. };
  22980. })('tap');
  22981. /**
  22982. * @module gestures
  22983. */
  22984. /**
  22985. * when a touch is being touched at the page
  22986. *
  22987. * @class Touch
  22988. * @static
  22989. */
  22990. /**
  22991. * @event touch
  22992. * @param {Object} ev
  22993. */
  22994. Hammer.gestures.Touch = {
  22995. name: 'touch',
  22996. index: -Infinity,
  22997. defaults: {
  22998. /**
  22999. * call preventDefault at touchstart, and makes the element blocking by disabling the scrolling of the page,
  23000. * but it improves gestures like transforming and dragging.
  23001. * be careful with using this, it can be very annoying for users to be stuck on the page
  23002. * @property preventDefault
  23003. * @type {Boolean}
  23004. * @default false
  23005. */
  23006. preventDefault: false,
  23007. /**
  23008. * disable mouse events, so only touch (or pen!) input triggers events
  23009. * @property preventMouse
  23010. * @type {Boolean}
  23011. * @default false
  23012. */
  23013. preventMouse: false
  23014. },
  23015. handler: function touchGesture(ev, inst) {
  23016. if(inst.options.preventMouse && ev.pointerType == POINTER_MOUSE) {
  23017. ev.stopDetect();
  23018. return;
  23019. }
  23020. if(inst.options.preventDefault) {
  23021. ev.preventDefault();
  23022. }
  23023. if(ev.eventType == EVENT_TOUCH) {
  23024. inst.trigger('touch', ev);
  23025. }
  23026. }
  23027. };
  23028. /**
  23029. * @module gestures
  23030. */
  23031. /**
  23032. * User want to scale or rotate with 2 fingers
  23033. * Preventing the default browser behavior is a good way to improve feel and working. This can be done with the
  23034. * `preventDefault` option.
  23035. *
  23036. * @class Transform
  23037. * @static
  23038. */
  23039. /**
  23040. * @event transform
  23041. * @param {Object} ev
  23042. */
  23043. /**
  23044. * @event transformstart
  23045. * @param {Object} ev
  23046. */
  23047. /**
  23048. * @event transformend
  23049. * @param {Object} ev
  23050. */
  23051. /**
  23052. * @event pinchin
  23053. * @param {Object} ev
  23054. */
  23055. /**
  23056. * @event pinchout
  23057. * @param {Object} ev
  23058. */
  23059. /**
  23060. * @event rotate
  23061. * @param {Object} ev
  23062. */
  23063. /**
  23064. * @param {String} name
  23065. */
  23066. (function(name) {
  23067. var triggered = false;
  23068. function transformGesture(ev, inst) {
  23069. switch(ev.eventType) {
  23070. case EVENT_START:
  23071. triggered = false;
  23072. break;
  23073. case EVENT_MOVE:
  23074. // at least multitouch
  23075. if(ev.touches.length < 2) {
  23076. return;
  23077. }
  23078. var scaleThreshold = Math.abs(1 - ev.scale);
  23079. var rotationThreshold = Math.abs(ev.rotation);
  23080. // when the distance we moved is too small we skip this gesture
  23081. // or we can be already in dragging
  23082. if(scaleThreshold < inst.options.transformMinScale &&
  23083. rotationThreshold < inst.options.transformMinRotation) {
  23084. return;
  23085. }
  23086. // we are transforming!
  23087. Detection.current.name = name;
  23088. // first time, trigger dragstart event
  23089. if(!triggered) {
  23090. inst.trigger(name + 'start', ev);
  23091. triggered = true;
  23092. }
  23093. inst.trigger(name, ev); // basic transform event
  23094. // trigger rotate event
  23095. if(rotationThreshold > inst.options.transformMinRotation) {
  23096. inst.trigger('rotate', ev);
  23097. }
  23098. // trigger pinch event
  23099. if(scaleThreshold > inst.options.transformMinScale) {
  23100. inst.trigger('pinch', ev);
  23101. inst.trigger('pinch' + (ev.scale < 1 ? 'in' : 'out'), ev);
  23102. }
  23103. break;
  23104. case EVENT_RELEASE:
  23105. if(triggered && ev.changedLength < 2) {
  23106. inst.trigger(name + 'end', ev);
  23107. triggered = false;
  23108. }
  23109. break;
  23110. }
  23111. }
  23112. Hammer.gestures.Transform = {
  23113. name: name,
  23114. index: 45,
  23115. defaults: {
  23116. /**
  23117. * minimal scale factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
  23118. * @property transformMinScale
  23119. * @type {Number}
  23120. * @default 0.01
  23121. */
  23122. transformMinScale: 0.01,
  23123. /**
  23124. * rotation in degrees
  23125. * @property transformMinRotation
  23126. * @type {Number}
  23127. * @default 1
  23128. */
  23129. transformMinRotation: 1
  23130. },
  23131. handler: transformGesture
  23132. };
  23133. })('transform');
  23134. /**
  23135. * @module hammer
  23136. */
  23137. // AMD export
  23138. if(true) {
  23139. !(__WEBPACK_AMD_DEFINE_RESULT__ = (function() {
  23140. return Hammer;
  23141. }.call(exports, __webpack_require__, exports, module)), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  23142. // commonjs export
  23143. } else if(typeof module !== 'undefined' && module.exports) {
  23144. module.exports = Hammer;
  23145. // browser export
  23146. } else {
  23147. window.Hammer = Hammer;
  23148. }
  23149. })(window);
  23150. /***/ },
  23151. /* 49 */
  23152. /***/ function(module, exports, __webpack_require__) {
  23153. /**
  23154. * Creation of the ClusterMixin var.
  23155. *
  23156. * This contains all the functions the Network object can use to employ clustering
  23157. */
  23158. /**
  23159. * This is only called in the constructor of the network object
  23160. *
  23161. */
  23162. exports.startWithClustering = function() {
  23163. // cluster if the data set is big
  23164. this.clusterToFit(this.constants.clustering.initialMaxNodes, true);
  23165. // updates the lables after clustering
  23166. this.updateLabels();
  23167. // this is called here because if clusterin is disabled, the start and stabilize are called in
  23168. // the setData function.
  23169. if (this.stabilize) {
  23170. this._stabilize();
  23171. }
  23172. this.start();
  23173. };
  23174. /**
  23175. * This function clusters until the initialMaxNodes has been reached
  23176. *
  23177. * @param {Number} maxNumberOfNodes
  23178. * @param {Boolean} reposition
  23179. */
  23180. exports.clusterToFit = function(maxNumberOfNodes, reposition) {
  23181. var numberOfNodes = this.nodeIndices.length;
  23182. var maxLevels = 50;
  23183. var level = 0;
  23184. // we first cluster the hubs, then we pull in the outliers, repeat
  23185. while (numberOfNodes > maxNumberOfNodes && level < maxLevels) {
  23186. if (level % 3 == 0) {
  23187. this.forceAggregateHubs(true);
  23188. this.normalizeClusterLevels();
  23189. }
  23190. else {
  23191. this.increaseClusterLevel(); // this also includes a cluster normalization
  23192. }
  23193. numberOfNodes = this.nodeIndices.length;
  23194. level += 1;
  23195. }
  23196. // after the clustering we reposition the nodes to reduce the initial chaos
  23197. if (level > 0 && reposition == true) {
  23198. this.repositionNodes();
  23199. }
  23200. this._updateCalculationNodes();
  23201. };
  23202. /**
  23203. * This function can be called to open up a specific cluster. It is only called by
  23204. * It will unpack the cluster back one level.
  23205. *
  23206. * @param node | Node object: cluster to open.
  23207. */
  23208. exports.openCluster = function(node) {
  23209. var isMovingBeforeClustering = this.moving;
  23210. if (node.clusterSize > this.constants.clustering.sectorThreshold && this._nodeInActiveArea(node) &&
  23211. !(this._sector() == "default" && this.nodeIndices.length == 1)) {
  23212. // this loads a new sector, loads the nodes and edges and nodeIndices of it.
  23213. this._addSector(node);
  23214. var level = 0;
  23215. // we decluster until we reach a decent number of nodes
  23216. while ((this.nodeIndices.length < this.constants.clustering.initialMaxNodes) && (level < 10)) {
  23217. this.decreaseClusterLevel();
  23218. level += 1;
  23219. }
  23220. }
  23221. else {
  23222. this._expandClusterNode(node,false,true);
  23223. // update the index list, dynamic edges and labels
  23224. this._updateNodeIndexList();
  23225. this._updateDynamicEdges();
  23226. this._updateCalculationNodes();
  23227. this.updateLabels();
  23228. }
  23229. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  23230. if (this.moving != isMovingBeforeClustering) {
  23231. this.start();
  23232. }
  23233. };
  23234. /**
  23235. * This calls the updateClustes with default arguments
  23236. */
  23237. exports.updateClustersDefault = function() {
  23238. if (this.constants.clustering.enabled == true) {
  23239. this.updateClusters(0,false,false);
  23240. }
  23241. };
  23242. /**
  23243. * This function can be called to increase the cluster level. This means that the nodes with only one edge connection will
  23244. * be clustered with their connected node. This can be repeated as many times as needed.
  23245. * This can be called externally (by a keybind for instance) to reduce the complexity of big datasets.
  23246. */
  23247. exports.increaseClusterLevel = function() {
  23248. this.updateClusters(-1,false,true);
  23249. };
  23250. /**
  23251. * This function can be called to decrease the cluster level. This means that the nodes with only one edge connection will
  23252. * be unpacked if they are a cluster. This can be repeated as many times as needed.
  23253. * This can be called externally (by a key-bind for instance) to look into clusters without zooming.
  23254. */
  23255. exports.decreaseClusterLevel = function() {
  23256. this.updateClusters(1,false,true);
  23257. };
  23258. /**
  23259. * This is the main clustering function. It clusters and declusters on zoom or forced
  23260. * This function clusters on zoom, it can be called with a predefined zoom direction
  23261. * If out, check if we can form clusters, if in, check if we can open clusters.
  23262. * This function is only called from _zoom()
  23263. *
  23264. * @param {Number} zoomDirection | -1 / 0 / +1 for zoomOut / determineByZoom / zoomIn
  23265. * @param {Boolean} recursive | enabled or disable recursive calling of the opening of clusters
  23266. * @param {Boolean} force | enabled or disable forcing
  23267. * @param {Boolean} doNotStart | if true do not call start
  23268. *
  23269. */
  23270. exports.updateClusters = function(zoomDirection,recursive,force,doNotStart) {
  23271. var isMovingBeforeClustering = this.moving;
  23272. var amountOfNodes = this.nodeIndices.length;
  23273. // on zoom out collapse the sector if the scale is at the level the sector was made
  23274. if (this.previousScale > this.scale && zoomDirection == 0) {
  23275. this._collapseSector();
  23276. }
  23277. // check if we zoom in or out
  23278. if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
  23279. // forming clusters when forced pulls outliers in. When not forced, the edge length of the
  23280. // outer nodes determines if it is being clustered
  23281. this._formClusters(force);
  23282. }
  23283. else if (this.previousScale < this.scale || zoomDirection == 1) { // zoom in
  23284. if (force == true) {
  23285. // _openClusters checks for each node if the formationScale of the cluster is smaller than
  23286. // the current scale and if so, declusters. When forced, all clusters are reduced by one step
  23287. this._openClusters(recursive,force);
  23288. }
  23289. else {
  23290. // if a cluster takes up a set percentage of the active window
  23291. this._openClustersBySize();
  23292. }
  23293. }
  23294. this._updateNodeIndexList();
  23295. // if a cluster was NOT formed and the user zoomed out, we try clustering by hubs
  23296. if (this.nodeIndices.length == amountOfNodes && (this.previousScale > this.scale || zoomDirection == -1)) {
  23297. this._aggregateHubs(force);
  23298. this._updateNodeIndexList();
  23299. }
  23300. // we now reduce chains.
  23301. if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
  23302. this.handleChains();
  23303. this._updateNodeIndexList();
  23304. }
  23305. this.previousScale = this.scale;
  23306. // rest of the update the index list, dynamic edges and labels
  23307. this._updateDynamicEdges();
  23308. this.updateLabels();
  23309. // if a cluster was formed, we increase the clusterSession
  23310. if (this.nodeIndices.length < amountOfNodes) { // this means a clustering operation has taken place
  23311. this.clusterSession += 1;
  23312. // if clusters have been made, we normalize the cluster level
  23313. this.normalizeClusterLevels();
  23314. }
  23315. if (doNotStart == false || doNotStart === undefined) {
  23316. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  23317. if (this.moving != isMovingBeforeClustering) {
  23318. this.start();
  23319. }
  23320. }
  23321. this._updateCalculationNodes();
  23322. };
  23323. /**
  23324. * This function handles the chains. It is called on every updateClusters().
  23325. */
  23326. exports.handleChains = function() {
  23327. // after clustering we check how many chains there are
  23328. var chainPercentage = this._getChainFraction();
  23329. if (chainPercentage > this.constants.clustering.chainThreshold) {
  23330. this._reduceAmountOfChains(1 - this.constants.clustering.chainThreshold / chainPercentage)
  23331. }
  23332. };
  23333. /**
  23334. * this functions starts clustering by hubs
  23335. * The minimum hub threshold is set globally
  23336. *
  23337. * @private
  23338. */
  23339. exports._aggregateHubs = function(force) {
  23340. this._getHubSize();
  23341. this._formClustersByHub(force,false);
  23342. };
  23343. /**
  23344. * This function is fired by keypress. It forces hubs to form.
  23345. *
  23346. */
  23347. exports.forceAggregateHubs = function(doNotStart) {
  23348. var isMovingBeforeClustering = this.moving;
  23349. var amountOfNodes = this.nodeIndices.length;
  23350. this._aggregateHubs(true);
  23351. // update the index list, dynamic edges and labels
  23352. this._updateNodeIndexList();
  23353. this._updateDynamicEdges();
  23354. this.updateLabels();
  23355. // if a cluster was formed, we increase the clusterSession
  23356. if (this.nodeIndices.length != amountOfNodes) {
  23357. this.clusterSession += 1;
  23358. }
  23359. if (doNotStart == false || doNotStart === undefined) {
  23360. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  23361. if (this.moving != isMovingBeforeClustering) {
  23362. this.start();
  23363. }
  23364. }
  23365. };
  23366. /**
  23367. * If a cluster takes up more than a set percentage of the screen, open the cluster
  23368. *
  23369. * @private
  23370. */
  23371. exports._openClustersBySize = function() {
  23372. for (var nodeId in this.nodes) {
  23373. if (this.nodes.hasOwnProperty(nodeId)) {
  23374. var node = this.nodes[nodeId];
  23375. if (node.inView() == true) {
  23376. if ((node.width*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  23377. (node.height*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  23378. this.openCluster(node);
  23379. }
  23380. }
  23381. }
  23382. }
  23383. };
  23384. /**
  23385. * This function loops over all nodes in the nodeIndices list. For each node it checks if it is a cluster and if it
  23386. * has to be opened based on the current zoom level.
  23387. *
  23388. * @private
  23389. */
  23390. exports._openClusters = function(recursive,force) {
  23391. for (var i = 0; i < this.nodeIndices.length; i++) {
  23392. var node = this.nodes[this.nodeIndices[i]];
  23393. this._expandClusterNode(node,recursive,force);
  23394. this._updateCalculationNodes();
  23395. }
  23396. };
  23397. /**
  23398. * This function checks if a node has to be opened. This is done by checking the zoom level.
  23399. * If the node contains child nodes, this function is recursively called on the child nodes as well.
  23400. * This recursive behaviour is optional and can be set by the recursive argument.
  23401. *
  23402. * @param {Node} parentNode | to check for cluster and expand
  23403. * @param {Boolean} recursive | enabled or disable recursive calling
  23404. * @param {Boolean} force | enabled or disable forcing
  23405. * @param {Boolean} [openAll] | This will recursively force all nodes in the parent to be released
  23406. * @private
  23407. */
  23408. exports._expandClusterNode = function(parentNode, recursive, force, openAll) {
  23409. // first check if node is a cluster
  23410. if (parentNode.clusterSize > 1) {
  23411. // this means that on a double tap event or a zoom event, the cluster fully unpacks if it is smaller than 20
  23412. if (parentNode.clusterSize < this.constants.clustering.sectorThreshold) {
  23413. openAll = true;
  23414. }
  23415. recursive = openAll ? true : recursive;
  23416. // if the last child has been added on a smaller scale than current scale decluster
  23417. if (parentNode.formationScale < this.scale || force == true) {
  23418. // we will check if any of the contained child nodes should be removed from the cluster
  23419. for (var containedNodeId in parentNode.containedNodes) {
  23420. if (parentNode.containedNodes.hasOwnProperty(containedNodeId)) {
  23421. var childNode = parentNode.containedNodes[containedNodeId];
  23422. // force expand will expand the largest cluster size clusters. Since we cluster from outside in, we assume that
  23423. // the largest cluster is the one that comes from outside
  23424. if (force == true) {
  23425. if (childNode.clusterSession == parentNode.clusterSessions[parentNode.clusterSessions.length-1]
  23426. || openAll) {
  23427. this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
  23428. }
  23429. }
  23430. else {
  23431. if (this._nodeInActiveArea(parentNode)) {
  23432. this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
  23433. }
  23434. }
  23435. }
  23436. }
  23437. }
  23438. }
  23439. };
  23440. /**
  23441. * ONLY CALLED FROM _expandClusterNode
  23442. *
  23443. * This function will expel a child_node from a parent_node. This is to de-cluster the node. This function will remove
  23444. * the child node from the parent contained_node object and put it back into the global nodes object.
  23445. * The same holds for the edge that was connected to the child node. It is moved back into the global edges object.
  23446. *
  23447. * @param {Node} parentNode | the parent node
  23448. * @param {String} containedNodeId | child_node id as it is contained in the containedNodes object of the parent node
  23449. * @param {Boolean} recursive | This will also check if the child needs to be expanded.
  23450. * With force and recursive both true, the entire cluster is unpacked
  23451. * @param {Boolean} force | This will disregard the zoom level and will expel this child from the parent
  23452. * @param {Boolean} openAll | This will recursively force all nodes in the parent to be released
  23453. * @private
  23454. */
  23455. exports._expelChildFromParent = function(parentNode, containedNodeId, recursive, force, openAll) {
  23456. var childNode = parentNode.containedNodes[containedNodeId];
  23457. // if child node has been added on smaller scale than current, kick out
  23458. if (childNode.formationScale < this.scale || force == true) {
  23459. // unselect all selected items
  23460. this._unselectAll();
  23461. // put the child node back in the global nodes object
  23462. this.nodes[containedNodeId] = childNode;
  23463. // release the contained edges from this childNode back into the global edges
  23464. this._releaseContainedEdges(parentNode,childNode);
  23465. // reconnect rerouted edges to the childNode
  23466. this._connectEdgeBackToChild(parentNode,childNode);
  23467. // validate all edges in dynamicEdges
  23468. this._validateEdges(parentNode);
  23469. // undo the changes from the clustering operation on the parent node
  23470. parentNode.mass -= childNode.mass;
  23471. parentNode.clusterSize -= childNode.clusterSize;
  23472. parentNode.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
  23473. parentNode.dynamicEdgesLength = parentNode.dynamicEdges.length;
  23474. // place the child node near the parent, not at the exact same location to avoid chaos in the system
  23475. childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random());
  23476. childNode.y = parentNode.y + parentNode.growthIndicator * (0.5 - Math.random());
  23477. // remove node from the list
  23478. delete parentNode.containedNodes[containedNodeId];
  23479. // check if there are other childs with this clusterSession in the parent.
  23480. var othersPresent = false;
  23481. for (var childNodeId in parentNode.containedNodes) {
  23482. if (parentNode.containedNodes.hasOwnProperty(childNodeId)) {
  23483. if (parentNode.containedNodes[childNodeId].clusterSession == childNode.clusterSession) {
  23484. othersPresent = true;
  23485. break;
  23486. }
  23487. }
  23488. }
  23489. // if there are no others, remove the cluster session from the list
  23490. if (othersPresent == false) {
  23491. parentNode.clusterSessions.pop();
  23492. }
  23493. this._repositionBezierNodes(childNode);
  23494. // this._repositionBezierNodes(parentNode);
  23495. // remove the clusterSession from the child node
  23496. childNode.clusterSession = 0;
  23497. // recalculate the size of the node on the next time the node is rendered
  23498. parentNode.clearSizeCache();
  23499. // restart the simulation to reorganise all nodes
  23500. this.moving = true;
  23501. }
  23502. // check if a further expansion step is possible if recursivity is enabled
  23503. if (recursive == true) {
  23504. this._expandClusterNode(childNode,recursive,force,openAll);
  23505. }
  23506. };
  23507. /**
  23508. * position the bezier nodes at the center of the edges
  23509. *
  23510. * @param node
  23511. * @private
  23512. */
  23513. exports._repositionBezierNodes = function(node) {
  23514. for (var i = 0; i < node.dynamicEdges.length; i++) {
  23515. node.dynamicEdges[i].positionBezierNode();
  23516. }
  23517. };
  23518. /**
  23519. * This function checks if any nodes at the end of their trees have edges below a threshold length
  23520. * This function is called only from updateClusters()
  23521. * forceLevelCollapse ignores the length of the edge and collapses one level
  23522. * This means that a node with only one edge will be clustered with its connected node
  23523. *
  23524. * @private
  23525. * @param {Boolean} force
  23526. */
  23527. exports._formClusters = function(force) {
  23528. if (force == false) {
  23529. this._formClustersByZoom();
  23530. }
  23531. else {
  23532. this._forceClustersByZoom();
  23533. }
  23534. };
  23535. /**
  23536. * This function handles the clustering by zooming out, this is based on a minimum edge distance
  23537. *
  23538. * @private
  23539. */
  23540. exports._formClustersByZoom = function() {
  23541. var dx,dy,length,
  23542. minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
  23543. // check if any edges are shorter than minLength and start the clustering
  23544. // the clustering favours the node with the larger mass
  23545. for (var edgeId in this.edges) {
  23546. if (this.edges.hasOwnProperty(edgeId)) {
  23547. var edge = this.edges[edgeId];
  23548. if (edge.connected) {
  23549. if (edge.toId != edge.fromId) {
  23550. dx = (edge.to.x - edge.from.x);
  23551. dy = (edge.to.y - edge.from.y);
  23552. length = Math.sqrt(dx * dx + dy * dy);
  23553. if (length < minLength) {
  23554. // first check which node is larger
  23555. var parentNode = edge.from;
  23556. var childNode = edge.to;
  23557. if (edge.to.mass > edge.from.mass) {
  23558. parentNode = edge.to;
  23559. childNode = edge.from;
  23560. }
  23561. if (childNode.dynamicEdgesLength == 1) {
  23562. this._addToCluster(parentNode,childNode,false);
  23563. }
  23564. else if (parentNode.dynamicEdgesLength == 1) {
  23565. this._addToCluster(childNode,parentNode,false);
  23566. }
  23567. }
  23568. }
  23569. }
  23570. }
  23571. }
  23572. };
  23573. /**
  23574. * This function forces the network to cluster all nodes with only one connecting edge to their
  23575. * connected node.
  23576. *
  23577. * @private
  23578. */
  23579. exports._forceClustersByZoom = function() {
  23580. for (var nodeId in this.nodes) {
  23581. // another node could have absorbed this child.
  23582. if (this.nodes.hasOwnProperty(nodeId)) {
  23583. var childNode = this.nodes[nodeId];
  23584. // the edges can be swallowed by another decrease
  23585. if (childNode.dynamicEdgesLength == 1 && childNode.dynamicEdges.length != 0) {
  23586. var edge = childNode.dynamicEdges[0];
  23587. var parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId];
  23588. // group to the largest node
  23589. if (childNode.id != parentNode.id) {
  23590. if (parentNode.mass > childNode.mass) {
  23591. this._addToCluster(parentNode,childNode,true);
  23592. }
  23593. else {
  23594. this._addToCluster(childNode,parentNode,true);
  23595. }
  23596. }
  23597. }
  23598. }
  23599. }
  23600. };
  23601. /**
  23602. * To keep the nodes of roughly equal size we normalize the cluster levels.
  23603. * This function clusters a node to its smallest connected neighbour.
  23604. *
  23605. * @param node
  23606. * @private
  23607. */
  23608. exports._clusterToSmallestNeighbour = function(node) {
  23609. var smallestNeighbour = -1;
  23610. var smallestNeighbourNode = null;
  23611. for (var i = 0; i < node.dynamicEdges.length; i++) {
  23612. if (node.dynamicEdges[i] !== undefined) {
  23613. var neighbour = null;
  23614. if (node.dynamicEdges[i].fromId != node.id) {
  23615. neighbour = node.dynamicEdges[i].from;
  23616. }
  23617. else if (node.dynamicEdges[i].toId != node.id) {
  23618. neighbour = node.dynamicEdges[i].to;
  23619. }
  23620. if (neighbour != null && smallestNeighbour > neighbour.clusterSessions.length) {
  23621. smallestNeighbour = neighbour.clusterSessions.length;
  23622. smallestNeighbourNode = neighbour;
  23623. }
  23624. }
  23625. }
  23626. if (neighbour != null && this.nodes[neighbour.id] !== undefined) {
  23627. this._addToCluster(neighbour, node, true);
  23628. }
  23629. };
  23630. /**
  23631. * This function forms clusters from hubs, it loops over all nodes
  23632. *
  23633. * @param {Boolean} force | Disregard zoom level
  23634. * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
  23635. * @private
  23636. */
  23637. exports._formClustersByHub = function(force, onlyEqual) {
  23638. // we loop over all nodes in the list
  23639. for (var nodeId in this.nodes) {
  23640. // we check if it is still available since it can be used by the clustering in this loop
  23641. if (this.nodes.hasOwnProperty(nodeId)) {
  23642. this._formClusterFromHub(this.nodes[nodeId],force,onlyEqual);
  23643. }
  23644. }
  23645. };
  23646. /**
  23647. * This function forms a cluster from a specific preselected hub node
  23648. *
  23649. * @param {Node} hubNode | the node we will cluster as a hub
  23650. * @param {Boolean} force | Disregard zoom level
  23651. * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
  23652. * @param {Number} [absorptionSizeOffset] |
  23653. * @private
  23654. */
  23655. exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSizeOffset) {
  23656. if (absorptionSizeOffset === undefined) {
  23657. absorptionSizeOffset = 0;
  23658. }
  23659. // we decide if the node is a hub
  23660. if ((hubNode.dynamicEdgesLength >= this.hubThreshold && onlyEqual == false) ||
  23661. (hubNode.dynamicEdgesLength == this.hubThreshold && onlyEqual == true)) {
  23662. // initialize variables
  23663. var dx,dy,length;
  23664. var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
  23665. var allowCluster = false;
  23666. // we create a list of edges because the dynamicEdges change over the course of this loop
  23667. var edgesIdarray = [];
  23668. var amountOfInitialEdges = hubNode.dynamicEdges.length;
  23669. for (var j = 0; j < amountOfInitialEdges; j++) {
  23670. edgesIdarray.push(hubNode.dynamicEdges[j].id);
  23671. }
  23672. // if the hub clustering is not forces, we check if one of the edges connected
  23673. // to a cluster is small enough based on the constants.clustering.clusterEdgeThreshold
  23674. if (force == false) {
  23675. allowCluster = false;
  23676. for (j = 0; j < amountOfInitialEdges; j++) {
  23677. var edge = this.edges[edgesIdarray[j]];
  23678. if (edge !== undefined) {
  23679. if (edge.connected) {
  23680. if (edge.toId != edge.fromId) {
  23681. dx = (edge.to.x - edge.from.x);
  23682. dy = (edge.to.y - edge.from.y);
  23683. length = Math.sqrt(dx * dx + dy * dy);
  23684. if (length < minLength) {
  23685. allowCluster = true;
  23686. break;
  23687. }
  23688. }
  23689. }
  23690. }
  23691. }
  23692. }
  23693. // start the clustering if allowed
  23694. if ((!force && allowCluster) || force) {
  23695. // we loop over all edges INITIALLY connected to this hub
  23696. for (j = 0; j < amountOfInitialEdges; j++) {
  23697. edge = this.edges[edgesIdarray[j]];
  23698. // the edge can be clustered by this function in a previous loop
  23699. if (edge !== undefined) {
  23700. var childNode = this.nodes[(edge.fromId == hubNode.id) ? edge.toId : edge.fromId];
  23701. // we do not want hubs to merge with other hubs nor do we want to cluster itself.
  23702. if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) &&
  23703. (childNode.id != hubNode.id)) {
  23704. this._addToCluster(hubNode,childNode,force);
  23705. }
  23706. }
  23707. }
  23708. }
  23709. }
  23710. };
  23711. /**
  23712. * This function adds the child node to the parent node, creating a cluster if it is not already.
  23713. *
  23714. * @param {Node} parentNode | this is the node that will house the child node
  23715. * @param {Node} childNode | this node will be deleted from the global this.nodes and stored in the parent node
  23716. * @param {Boolean} force | true will only update the remainingEdges at the very end of the clustering, ensuring single level collapse
  23717. * @private
  23718. */
  23719. exports._addToCluster = function(parentNode, childNode, force) {
  23720. // join child node in the parent node
  23721. parentNode.containedNodes[childNode.id] = childNode;
  23722. // manage all the edges connected to the child and parent nodes
  23723. for (var i = 0; i < childNode.dynamicEdges.length; i++) {
  23724. var edge = childNode.dynamicEdges[i];
  23725. if (edge.toId == parentNode.id || edge.fromId == parentNode.id) { // edge connected to parentNode
  23726. this._addToContainedEdges(parentNode,childNode,edge);
  23727. }
  23728. else {
  23729. this._connectEdgeToCluster(parentNode,childNode,edge);
  23730. }
  23731. }
  23732. // a contained node has no dynamic edges.
  23733. childNode.dynamicEdges = [];
  23734. // remove circular edges from clusters
  23735. this._containCircularEdgesFromNode(parentNode,childNode);
  23736. // remove the childNode from the global nodes object
  23737. delete this.nodes[childNode.id];
  23738. // update the properties of the child and parent
  23739. var massBefore = parentNode.mass;
  23740. childNode.clusterSession = this.clusterSession;
  23741. parentNode.mass += childNode.mass;
  23742. parentNode.clusterSize += childNode.clusterSize;
  23743. parentNode.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
  23744. // keep track of the clustersessions so we can open the cluster up as it has been formed.
  23745. if (parentNode.clusterSessions[parentNode.clusterSessions.length - 1] != this.clusterSession) {
  23746. parentNode.clusterSessions.push(this.clusterSession);
  23747. }
  23748. // forced clusters only open from screen size and double tap
  23749. if (force == true) {
  23750. // parentNode.formationScale = Math.pow(1 - (1.0/11.0),this.clusterSession+3);
  23751. parentNode.formationScale = 0;
  23752. }
  23753. else {
  23754. parentNode.formationScale = this.scale; // The latest child has been added on this scale
  23755. }
  23756. // recalculate the size of the node on the next time the node is rendered
  23757. parentNode.clearSizeCache();
  23758. // set the pop-out scale for the childnode
  23759. parentNode.containedNodes[childNode.id].formationScale = parentNode.formationScale;
  23760. // nullify the movement velocity of the child, this is to avoid hectic behaviour
  23761. childNode.clearVelocity();
  23762. // the mass has altered, preservation of energy dictates the velocity to be updated
  23763. parentNode.updateVelocity(massBefore);
  23764. // restart the simulation to reorganise all nodes
  23765. this.moving = true;
  23766. };
  23767. /**
  23768. * This function will apply the changes made to the remainingEdges during the formation of the clusters.
  23769. * This is a seperate function to allow for level-wise collapsing of the node barnesHutTree.
  23770. * It has to be called if a level is collapsed. It is called by _formClusters().
  23771. * @private
  23772. */
  23773. exports._updateDynamicEdges = function() {
  23774. for (var i = 0; i < this.nodeIndices.length; i++) {
  23775. var node = this.nodes[this.nodeIndices[i]];
  23776. node.dynamicEdgesLength = node.dynamicEdges.length;
  23777. // this corrects for multiple edges pointing at the same other node
  23778. var correction = 0;
  23779. if (node.dynamicEdgesLength > 1) {
  23780. for (var j = 0; j < node.dynamicEdgesLength - 1; j++) {
  23781. var edgeToId = node.dynamicEdges[j].toId;
  23782. var edgeFromId = node.dynamicEdges[j].fromId;
  23783. for (var k = j+1; k < node.dynamicEdgesLength; k++) {
  23784. if ((node.dynamicEdges[k].toId == edgeToId && node.dynamicEdges[k].fromId == edgeFromId) ||
  23785. (node.dynamicEdges[k].fromId == edgeToId && node.dynamicEdges[k].toId == edgeFromId)) {
  23786. correction += 1;
  23787. }
  23788. }
  23789. }
  23790. }
  23791. node.dynamicEdgesLength -= correction;
  23792. }
  23793. };
  23794. /**
  23795. * This adds an edge from the childNode to the contained edges of the parent node
  23796. *
  23797. * @param parentNode | Node object
  23798. * @param childNode | Node object
  23799. * @param edge | Edge object
  23800. * @private
  23801. */
  23802. exports._addToContainedEdges = function(parentNode, childNode, edge) {
  23803. // create an array object if it does not yet exist for this childNode
  23804. if (!(parentNode.containedEdges.hasOwnProperty(childNode.id))) {
  23805. parentNode.containedEdges[childNode.id] = []
  23806. }
  23807. // add this edge to the list
  23808. parentNode.containedEdges[childNode.id].push(edge);
  23809. // remove the edge from the global edges object
  23810. delete this.edges[edge.id];
  23811. // remove the edge from the parent object
  23812. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  23813. if (parentNode.dynamicEdges[i].id == edge.id) {
  23814. parentNode.dynamicEdges.splice(i,1);
  23815. break;
  23816. }
  23817. }
  23818. };
  23819. /**
  23820. * This function connects an edge that was connected to a child node to the parent node.
  23821. * It keeps track of which nodes it has been connected to with the originalId array.
  23822. *
  23823. * @param {Node} parentNode | Node object
  23824. * @param {Node} childNode | Node object
  23825. * @param {Edge} edge | Edge object
  23826. * @private
  23827. */
  23828. exports._connectEdgeToCluster = function(parentNode, childNode, edge) {
  23829. // handle circular edges
  23830. if (edge.toId == edge.fromId) {
  23831. this._addToContainedEdges(parentNode, childNode, edge);
  23832. }
  23833. else {
  23834. if (edge.toId == childNode.id) { // edge connected to other node on the "to" side
  23835. edge.originalToId.push(childNode.id);
  23836. edge.to = parentNode;
  23837. edge.toId = parentNode.id;
  23838. }
  23839. else { // edge connected to other node with the "from" side
  23840. edge.originalFromId.push(childNode.id);
  23841. edge.from = parentNode;
  23842. edge.fromId = parentNode.id;
  23843. }
  23844. this._addToReroutedEdges(parentNode,childNode,edge);
  23845. }
  23846. };
  23847. /**
  23848. * If a node is connected to itself, a circular edge is drawn. When clustering we want to contain
  23849. * these edges inside of the cluster.
  23850. *
  23851. * @param parentNode
  23852. * @param childNode
  23853. * @private
  23854. */
  23855. exports._containCircularEdgesFromNode = function(parentNode, childNode) {
  23856. // manage all the edges connected to the child and parent nodes
  23857. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  23858. var edge = parentNode.dynamicEdges[i];
  23859. // handle circular edges
  23860. if (edge.toId == edge.fromId) {
  23861. this._addToContainedEdges(parentNode, childNode, edge);
  23862. }
  23863. }
  23864. };
  23865. /**
  23866. * This adds an edge from the childNode to the rerouted edges of the parent node
  23867. *
  23868. * @param parentNode | Node object
  23869. * @param childNode | Node object
  23870. * @param edge | Edge object
  23871. * @private
  23872. */
  23873. exports._addToReroutedEdges = function(parentNode, childNode, edge) {
  23874. // create an array object if it does not yet exist for this childNode
  23875. // we store the edge in the rerouted edges so we can restore it when the cluster pops open
  23876. if (!(parentNode.reroutedEdges.hasOwnProperty(childNode.id))) {
  23877. parentNode.reroutedEdges[childNode.id] = [];
  23878. }
  23879. parentNode.reroutedEdges[childNode.id].push(edge);
  23880. // this edge becomes part of the dynamicEdges of the cluster node
  23881. parentNode.dynamicEdges.push(edge);
  23882. };
  23883. /**
  23884. * This function connects an edge that was connected to a cluster node back to the child node.
  23885. *
  23886. * @param parentNode | Node object
  23887. * @param childNode | Node object
  23888. * @private
  23889. */
  23890. exports._connectEdgeBackToChild = function(parentNode, childNode) {
  23891. if (parentNode.reroutedEdges.hasOwnProperty(childNode.id)) {
  23892. for (var i = 0; i < parentNode.reroutedEdges[childNode.id].length; i++) {
  23893. var edge = parentNode.reroutedEdges[childNode.id][i];
  23894. if (edge.originalFromId[edge.originalFromId.length-1] == childNode.id) {
  23895. edge.originalFromId.pop();
  23896. edge.fromId = childNode.id;
  23897. edge.from = childNode;
  23898. }
  23899. else {
  23900. edge.originalToId.pop();
  23901. edge.toId = childNode.id;
  23902. edge.to = childNode;
  23903. }
  23904. // append this edge to the list of edges connecting to the childnode
  23905. childNode.dynamicEdges.push(edge);
  23906. // remove the edge from the parent object
  23907. for (var j = 0; j < parentNode.dynamicEdges.length; j++) {
  23908. if (parentNode.dynamicEdges[j].id == edge.id) {
  23909. parentNode.dynamicEdges.splice(j,1);
  23910. break;
  23911. }
  23912. }
  23913. }
  23914. // remove the entry from the rerouted edges
  23915. delete parentNode.reroutedEdges[childNode.id];
  23916. }
  23917. };
  23918. /**
  23919. * When loops are clustered, an edge can be both in the rerouted array and the contained array.
  23920. * This function is called last to verify that all edges in dynamicEdges are in fact connected to the
  23921. * parentNode
  23922. *
  23923. * @param parentNode | Node object
  23924. * @private
  23925. */
  23926. exports._validateEdges = function(parentNode) {
  23927. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  23928. var edge = parentNode.dynamicEdges[i];
  23929. if (parentNode.id != edge.toId && parentNode.id != edge.fromId) {
  23930. parentNode.dynamicEdges.splice(i,1);
  23931. }
  23932. }
  23933. };
  23934. /**
  23935. * This function released the contained edges back into the global domain and puts them back into the
  23936. * dynamic edges of both parent and child.
  23937. *
  23938. * @param {Node} parentNode |
  23939. * @param {Node} childNode |
  23940. * @private
  23941. */
  23942. exports._releaseContainedEdges = function(parentNode, childNode) {
  23943. for (var i = 0; i < parentNode.containedEdges[childNode.id].length; i++) {
  23944. var edge = parentNode.containedEdges[childNode.id][i];
  23945. // put the edge back in the global edges object
  23946. this.edges[edge.id] = edge;
  23947. // put the edge back in the dynamic edges of the child and parent
  23948. childNode.dynamicEdges.push(edge);
  23949. parentNode.dynamicEdges.push(edge);
  23950. }
  23951. // remove the entry from the contained edges
  23952. delete parentNode.containedEdges[childNode.id];
  23953. };
  23954. // ------------------- UTILITY FUNCTIONS ---------------------------- //
  23955. /**
  23956. * This updates the node labels for all nodes (for debugging purposes)
  23957. */
  23958. exports.updateLabels = function() {
  23959. var nodeId;
  23960. // update node labels
  23961. for (nodeId in this.nodes) {
  23962. if (this.nodes.hasOwnProperty(nodeId)) {
  23963. var node = this.nodes[nodeId];
  23964. if (node.clusterSize > 1) {
  23965. node.label = "[".concat(String(node.clusterSize),"]");
  23966. }
  23967. }
  23968. }
  23969. // update node labels
  23970. for (nodeId in this.nodes) {
  23971. if (this.nodes.hasOwnProperty(nodeId)) {
  23972. node = this.nodes[nodeId];
  23973. if (node.clusterSize == 1) {
  23974. if (node.originalLabel !== undefined) {
  23975. node.label = node.originalLabel;
  23976. }
  23977. else {
  23978. node.label = String(node.id);
  23979. }
  23980. }
  23981. }
  23982. }
  23983. // /* Debug Override */
  23984. // for (nodeId in this.nodes) {
  23985. // if (this.nodes.hasOwnProperty(nodeId)) {
  23986. // node = this.nodes[nodeId];
  23987. // node.label = String(node.level);
  23988. // }
  23989. // }
  23990. };
  23991. /**
  23992. * We want to keep the cluster level distribution rather small. This means we do not want unclustered nodes
  23993. * if the rest of the nodes are already a few cluster levels in.
  23994. * To fix this we use this function. It determines the min and max cluster level and sends nodes that have not
  23995. * clustered enough to the clusterToSmallestNeighbours function.
  23996. */
  23997. exports.normalizeClusterLevels = function() {
  23998. var maxLevel = 0;
  23999. var minLevel = 1e9;
  24000. var clusterLevel = 0;
  24001. var nodeId;
  24002. // we loop over all nodes in the list
  24003. for (nodeId in this.nodes) {
  24004. if (this.nodes.hasOwnProperty(nodeId)) {
  24005. clusterLevel = this.nodes[nodeId].clusterSessions.length;
  24006. if (maxLevel < clusterLevel) {maxLevel = clusterLevel;}
  24007. if (minLevel > clusterLevel) {minLevel = clusterLevel;}
  24008. }
  24009. }
  24010. if (maxLevel - minLevel > this.constants.clustering.clusterLevelDifference) {
  24011. var amountOfNodes = this.nodeIndices.length;
  24012. var targetLevel = maxLevel - this.constants.clustering.clusterLevelDifference;
  24013. // we loop over all nodes in the list
  24014. for (nodeId in this.nodes) {
  24015. if (this.nodes.hasOwnProperty(nodeId)) {
  24016. if (this.nodes[nodeId].clusterSessions.length < targetLevel) {
  24017. this._clusterToSmallestNeighbour(this.nodes[nodeId]);
  24018. }
  24019. }
  24020. }
  24021. this._updateNodeIndexList();
  24022. this._updateDynamicEdges();
  24023. // if a cluster was formed, we increase the clusterSession
  24024. if (this.nodeIndices.length != amountOfNodes) {
  24025. this.clusterSession += 1;
  24026. }
  24027. }
  24028. };
  24029. /**
  24030. * This function determines if the cluster we want to decluster is in the active area
  24031. * this means around the zoom center
  24032. *
  24033. * @param {Node} node
  24034. * @returns {boolean}
  24035. * @private
  24036. */
  24037. exports._nodeInActiveArea = function(node) {
  24038. return (
  24039. Math.abs(node.x - this.areaCenter.x) <= this.constants.clustering.activeAreaBoxSize/this.scale
  24040. &&
  24041. Math.abs(node.y - this.areaCenter.y) <= this.constants.clustering.activeAreaBoxSize/this.scale
  24042. )
  24043. };
  24044. /**
  24045. * This is an adaptation of the original repositioning function. This is called if the system is clustered initially
  24046. * It puts large clusters away from the center and randomizes the order.
  24047. *
  24048. */
  24049. exports.repositionNodes = function() {
  24050. for (var i = 0; i < this.nodeIndices.length; i++) {
  24051. var node = this.nodes[this.nodeIndices[i]];
  24052. if ((node.xFixed == false || node.yFixed == false)) {
  24053. var radius = 10 * 0.1*this.nodeIndices.length * Math.min(100,node.mass);
  24054. var angle = 2 * Math.PI * Math.random();
  24055. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  24056. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  24057. this._repositionBezierNodes(node);
  24058. }
  24059. }
  24060. };
  24061. /**
  24062. * We determine how many connections denote an important hub.
  24063. * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
  24064. *
  24065. * @private
  24066. */
  24067. exports._getHubSize = function() {
  24068. var average = 0;
  24069. var averageSquared = 0;
  24070. var hubCounter = 0;
  24071. var largestHub = 0;
  24072. for (var i = 0; i < this.nodeIndices.length; i++) {
  24073. var node = this.nodes[this.nodeIndices[i]];
  24074. if (node.dynamicEdgesLength > largestHub) {
  24075. largestHub = node.dynamicEdgesLength;
  24076. }
  24077. average += node.dynamicEdgesLength;
  24078. averageSquared += Math.pow(node.dynamicEdgesLength,2);
  24079. hubCounter += 1;
  24080. }
  24081. average = average / hubCounter;
  24082. averageSquared = averageSquared / hubCounter;
  24083. var variance = averageSquared - Math.pow(average,2);
  24084. var standardDeviation = Math.sqrt(variance);
  24085. this.hubThreshold = Math.floor(average + 2*standardDeviation);
  24086. // always have at least one to cluster
  24087. if (this.hubThreshold > largestHub) {
  24088. this.hubThreshold = largestHub;
  24089. }
  24090. // console.log("average",average,"averageSQ",averageSquared,"var",variance,"std",standardDeviation);
  24091. // console.log("hubThreshold:",this.hubThreshold);
  24092. };
  24093. /**
  24094. * We reduce the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
  24095. * with this amount we can cluster specifically on these chains.
  24096. *
  24097. * @param {Number} fraction | between 0 and 1, the percentage of chains to reduce
  24098. * @private
  24099. */
  24100. exports._reduceAmountOfChains = function(fraction) {
  24101. this.hubThreshold = 2;
  24102. var reduceAmount = Math.floor(this.nodeIndices.length * fraction);
  24103. for (var nodeId in this.nodes) {
  24104. if (this.nodes.hasOwnProperty(nodeId)) {
  24105. if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) {
  24106. if (reduceAmount > 0) {
  24107. this._formClusterFromHub(this.nodes[nodeId],true,true,1);
  24108. reduceAmount -= 1;
  24109. }
  24110. }
  24111. }
  24112. }
  24113. };
  24114. /**
  24115. * We get the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
  24116. * with this amount we can cluster specifically on these chains.
  24117. *
  24118. * @private
  24119. */
  24120. exports._getChainFraction = function() {
  24121. var chains = 0;
  24122. var total = 0;
  24123. for (var nodeId in this.nodes) {
  24124. if (this.nodes.hasOwnProperty(nodeId)) {
  24125. if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) {
  24126. chains += 1;
  24127. }
  24128. total += 1;
  24129. }
  24130. }
  24131. return chains/total;
  24132. };
  24133. /***/ },
  24134. /* 50 */
  24135. /***/ function(module, exports, __webpack_require__) {
  24136. var util = __webpack_require__(1);
  24137. /**
  24138. * Creation of the SectorMixin var.
  24139. *
  24140. * This contains all the functions the Network object can use to employ the sector system.
  24141. * The sector system is always used by Network, though the benefits only apply to the use of clustering.
  24142. * If clustering is not used, there is no overhead except for a duplicate object with references to nodes and edges.
  24143. */
  24144. /**
  24145. * This function is only called by the setData function of the Network object.
  24146. * This loads the global references into the active sector. This initializes the sector.
  24147. *
  24148. * @private
  24149. */
  24150. exports._putDataInSector = function() {
  24151. this.sectors["active"][this._sector()].nodes = this.nodes;
  24152. this.sectors["active"][this._sector()].edges = this.edges;
  24153. this.sectors["active"][this._sector()].nodeIndices = this.nodeIndices;
  24154. };
  24155. /**
  24156. * /**
  24157. * This function sets the global references to nodes, edges and nodeIndices back to
  24158. * those of the supplied (active) sector. If a type is defined, do the specific type
  24159. *
  24160. * @param {String} sectorId
  24161. * @param {String} [sectorType] | "active" or "frozen"
  24162. * @private
  24163. */
  24164. exports._switchToSector = function(sectorId, sectorType) {
  24165. if (sectorType === undefined || sectorType == "active") {
  24166. this._switchToActiveSector(sectorId);
  24167. }
  24168. else {
  24169. this._switchToFrozenSector(sectorId);
  24170. }
  24171. };
  24172. /**
  24173. * This function sets the global references to nodes, edges and nodeIndices back to
  24174. * those of the supplied active sector.
  24175. *
  24176. * @param sectorId
  24177. * @private
  24178. */
  24179. exports._switchToActiveSector = function(sectorId) {
  24180. this.nodeIndices = this.sectors["active"][sectorId]["nodeIndices"];
  24181. this.nodes = this.sectors["active"][sectorId]["nodes"];
  24182. this.edges = this.sectors["active"][sectorId]["edges"];
  24183. };
  24184. /**
  24185. * This function sets the global references to nodes, edges and nodeIndices back to
  24186. * those of the supplied active sector.
  24187. *
  24188. * @private
  24189. */
  24190. exports._switchToSupportSector = function() {
  24191. this.nodeIndices = this.sectors["support"]["nodeIndices"];
  24192. this.nodes = this.sectors["support"]["nodes"];
  24193. this.edges = this.sectors["support"]["edges"];
  24194. };
  24195. /**
  24196. * This function sets the global references to nodes, edges and nodeIndices back to
  24197. * those of the supplied frozen sector.
  24198. *
  24199. * @param sectorId
  24200. * @private
  24201. */
  24202. exports._switchToFrozenSector = function(sectorId) {
  24203. this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"];
  24204. this.nodes = this.sectors["frozen"][sectorId]["nodes"];
  24205. this.edges = this.sectors["frozen"][sectorId]["edges"];
  24206. };
  24207. /**
  24208. * This function sets the global references to nodes, edges and nodeIndices back to
  24209. * those of the currently active sector.
  24210. *
  24211. * @private
  24212. */
  24213. exports._loadLatestSector = function() {
  24214. this._switchToSector(this._sector());
  24215. };
  24216. /**
  24217. * This function returns the currently active sector Id
  24218. *
  24219. * @returns {String}
  24220. * @private
  24221. */
  24222. exports._sector = function() {
  24223. return this.activeSector[this.activeSector.length-1];
  24224. };
  24225. /**
  24226. * This function returns the previously active sector Id
  24227. *
  24228. * @returns {String}
  24229. * @private
  24230. */
  24231. exports._previousSector = function() {
  24232. if (this.activeSector.length > 1) {
  24233. return this.activeSector[this.activeSector.length-2];
  24234. }
  24235. else {
  24236. throw new TypeError('there are not enough sectors in the this.activeSector array.');
  24237. }
  24238. };
  24239. /**
  24240. * We add the active sector at the end of the this.activeSector array
  24241. * This ensures it is the currently active sector returned by _sector() and it reaches the top
  24242. * of the activeSector stack. When we reverse our steps we move from the end to the beginning of this stack.
  24243. *
  24244. * @param newId
  24245. * @private
  24246. */
  24247. exports._setActiveSector = function(newId) {
  24248. this.activeSector.push(newId);
  24249. };
  24250. /**
  24251. * We remove the currently active sector id from the active sector stack. This happens when
  24252. * we reactivate the previously active sector
  24253. *
  24254. * @private
  24255. */
  24256. exports._forgetLastSector = function() {
  24257. this.activeSector.pop();
  24258. };
  24259. /**
  24260. * This function creates a new active sector with the supplied newId. This newId
  24261. * is the expanding node id.
  24262. *
  24263. * @param {String} newId | Id of the new active sector
  24264. * @private
  24265. */
  24266. exports._createNewSector = function(newId) {
  24267. // create the new sector
  24268. this.sectors["active"][newId] = {"nodes":{},
  24269. "edges":{},
  24270. "nodeIndices":[],
  24271. "formationScale": this.scale,
  24272. "drawingNode": undefined};
  24273. // create the new sector render node. This gives visual feedback that you are in a new sector.
  24274. this.sectors["active"][newId]['drawingNode'] = new Node(
  24275. {id:newId,
  24276. color: {
  24277. background: "#eaefef",
  24278. border: "495c5e"
  24279. }
  24280. },{},{},this.constants);
  24281. this.sectors["active"][newId]['drawingNode'].clusterSize = 2;
  24282. };
  24283. /**
  24284. * This function removes the currently active sector. This is called when we create a new
  24285. * active sector.
  24286. *
  24287. * @param {String} sectorId | Id of the active sector that will be removed
  24288. * @private
  24289. */
  24290. exports._deleteActiveSector = function(sectorId) {
  24291. delete this.sectors["active"][sectorId];
  24292. };
  24293. /**
  24294. * This function removes the currently active sector. This is called when we reactivate
  24295. * the previously active sector.
  24296. *
  24297. * @param {String} sectorId | Id of the active sector that will be removed
  24298. * @private
  24299. */
  24300. exports._deleteFrozenSector = function(sectorId) {
  24301. delete this.sectors["frozen"][sectorId];
  24302. };
  24303. /**
  24304. * Freezing an active sector means moving it from the "active" object to the "frozen" object.
  24305. * We copy the references, then delete the active entree.
  24306. *
  24307. * @param sectorId
  24308. * @private
  24309. */
  24310. exports._freezeSector = function(sectorId) {
  24311. // we move the set references from the active to the frozen stack.
  24312. this.sectors["frozen"][sectorId] = this.sectors["active"][sectorId];
  24313. // we have moved the sector data into the frozen set, we now remove it from the active set
  24314. this._deleteActiveSector(sectorId);
  24315. };
  24316. /**
  24317. * This is the reverse operation of _freezeSector. Activating means moving the sector from the "frozen"
  24318. * object to the "active" object.
  24319. *
  24320. * @param sectorId
  24321. * @private
  24322. */
  24323. exports._activateSector = function(sectorId) {
  24324. // we move the set references from the frozen to the active stack.
  24325. this.sectors["active"][sectorId] = this.sectors["frozen"][sectorId];
  24326. // we have moved the sector data into the active set, we now remove it from the frozen stack
  24327. this._deleteFrozenSector(sectorId);
  24328. };
  24329. /**
  24330. * This function merges the data from the currently active sector with a frozen sector. This is used
  24331. * in the process of reverting back to the previously active sector.
  24332. * The data that is placed in the frozen (the previously active) sector is the node that has been removed from it
  24333. * upon the creation of a new active sector.
  24334. *
  24335. * @param sectorId
  24336. * @private
  24337. */
  24338. exports._mergeThisWithFrozen = function(sectorId) {
  24339. // copy all nodes
  24340. for (var nodeId in this.nodes) {
  24341. if (this.nodes.hasOwnProperty(nodeId)) {
  24342. this.sectors["frozen"][sectorId]["nodes"][nodeId] = this.nodes[nodeId];
  24343. }
  24344. }
  24345. // copy all edges (if not fully clustered, else there are no edges)
  24346. for (var edgeId in this.edges) {
  24347. if (this.edges.hasOwnProperty(edgeId)) {
  24348. this.sectors["frozen"][sectorId]["edges"][edgeId] = this.edges[edgeId];
  24349. }
  24350. }
  24351. // merge the nodeIndices
  24352. for (var i = 0; i < this.nodeIndices.length; i++) {
  24353. this.sectors["frozen"][sectorId]["nodeIndices"].push(this.nodeIndices[i]);
  24354. }
  24355. };
  24356. /**
  24357. * This clusters the sector to one cluster. It was a single cluster before this process started so
  24358. * we revert to that state. The clusterToFit function with a maximum size of 1 node does this.
  24359. *
  24360. * @private
  24361. */
  24362. exports._collapseThisToSingleCluster = function() {
  24363. this.clusterToFit(1,false);
  24364. };
  24365. /**
  24366. * We create a new active sector from the node that we want to open.
  24367. *
  24368. * @param node
  24369. * @private
  24370. */
  24371. exports._addSector = function(node) {
  24372. // this is the currently active sector
  24373. var sector = this._sector();
  24374. // // this should allow me to select nodes from a frozen set.
  24375. // if (this.sectors['active'][sector]["nodes"].hasOwnProperty(node.id)) {
  24376. // console.log("the node is part of the active sector");
  24377. // }
  24378. // else {
  24379. // console.log("I dont know what the fuck happened!!");
  24380. // }
  24381. // when we switch to a new sector, we remove the node that will be expanded from the current nodes list.
  24382. delete this.nodes[node.id];
  24383. var unqiueIdentifier = util.randomUUID();
  24384. // we fully freeze the currently active sector
  24385. this._freezeSector(sector);
  24386. // we create a new active sector. This sector has the Id of the node to ensure uniqueness
  24387. this._createNewSector(unqiueIdentifier);
  24388. // we add the active sector to the sectors array to be able to revert these steps later on
  24389. this._setActiveSector(unqiueIdentifier);
  24390. // we redirect the global references to the new sector's references. this._sector() now returns unqiueIdentifier
  24391. this._switchToSector(this._sector());
  24392. // finally we add the node we removed from our previous active sector to the new active sector
  24393. this.nodes[node.id] = node;
  24394. };
  24395. /**
  24396. * We close the sector that is currently open and revert back to the one before.
  24397. * If the active sector is the "default" sector, nothing happens.
  24398. *
  24399. * @private
  24400. */
  24401. exports._collapseSector = function() {
  24402. // the currently active sector
  24403. var sector = this._sector();
  24404. // we cannot collapse the default sector
  24405. if (sector != "default") {
  24406. if ((this.nodeIndices.length == 1) ||
  24407. (this.sectors["active"][sector]["drawingNode"].width*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  24408. (this.sectors["active"][sector]["drawingNode"].height*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  24409. var previousSector = this._previousSector();
  24410. // we collapse the sector back to a single cluster
  24411. this._collapseThisToSingleCluster();
  24412. // we move the remaining nodes, edges and nodeIndices to the previous sector.
  24413. // This previous sector is the one we will reactivate
  24414. this._mergeThisWithFrozen(previousSector);
  24415. // the previously active (frozen) sector now has all the data from the currently active sector.
  24416. // we can now delete the active sector.
  24417. this._deleteActiveSector(sector);
  24418. // we activate the previously active (and currently frozen) sector.
  24419. this._activateSector(previousSector);
  24420. // we load the references from the newly active sector into the global references
  24421. this._switchToSector(previousSector);
  24422. // we forget the previously active sector because we reverted to the one before
  24423. this._forgetLastSector();
  24424. // finally, we update the node index list.
  24425. this._updateNodeIndexList();
  24426. // we refresh the list with calulation nodes and calculation node indices.
  24427. this._updateCalculationNodes();
  24428. }
  24429. }
  24430. };
  24431. /**
  24432. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  24433. *
  24434. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  24435. * | we dont pass the function itself because then the "this" is the window object
  24436. * | instead of the Network object
  24437. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  24438. * @private
  24439. */
  24440. exports._doInAllActiveSectors = function(runFunction,argument) {
  24441. if (argument === undefined) {
  24442. for (var sector in this.sectors["active"]) {
  24443. if (this.sectors["active"].hasOwnProperty(sector)) {
  24444. // switch the global references to those of this sector
  24445. this._switchToActiveSector(sector);
  24446. this[runFunction]();
  24447. }
  24448. }
  24449. }
  24450. else {
  24451. for (var sector in this.sectors["active"]) {
  24452. if (this.sectors["active"].hasOwnProperty(sector)) {
  24453. // switch the global references to those of this sector
  24454. this._switchToActiveSector(sector);
  24455. var args = Array.prototype.splice.call(arguments, 1);
  24456. if (args.length > 1) {
  24457. this[runFunction](args[0],args[1]);
  24458. }
  24459. else {
  24460. this[runFunction](argument);
  24461. }
  24462. }
  24463. }
  24464. }
  24465. // we revert the global references back to our active sector
  24466. this._loadLatestSector();
  24467. };
  24468. /**
  24469. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  24470. *
  24471. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  24472. * | we dont pass the function itself because then the "this" is the window object
  24473. * | instead of the Network object
  24474. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  24475. * @private
  24476. */
  24477. exports._doInSupportSector = function(runFunction,argument) {
  24478. if (argument === undefined) {
  24479. this._switchToSupportSector();
  24480. this[runFunction]();
  24481. }
  24482. else {
  24483. this._switchToSupportSector();
  24484. var args = Array.prototype.splice.call(arguments, 1);
  24485. if (args.length > 1) {
  24486. this[runFunction](args[0],args[1]);
  24487. }
  24488. else {
  24489. this[runFunction](argument);
  24490. }
  24491. }
  24492. // we revert the global references back to our active sector
  24493. this._loadLatestSector();
  24494. };
  24495. /**
  24496. * This runs a function in all frozen sectors. This is used in the _redraw().
  24497. *
  24498. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  24499. * | we don't pass the function itself because then the "this" is the window object
  24500. * | instead of the Network object
  24501. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  24502. * @private
  24503. */
  24504. exports._doInAllFrozenSectors = function(runFunction,argument) {
  24505. if (argument === undefined) {
  24506. for (var sector in this.sectors["frozen"]) {
  24507. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  24508. // switch the global references to those of this sector
  24509. this._switchToFrozenSector(sector);
  24510. this[runFunction]();
  24511. }
  24512. }
  24513. }
  24514. else {
  24515. for (var sector in this.sectors["frozen"]) {
  24516. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  24517. // switch the global references to those of this sector
  24518. this._switchToFrozenSector(sector);
  24519. var args = Array.prototype.splice.call(arguments, 1);
  24520. if (args.length > 1) {
  24521. this[runFunction](args[0],args[1]);
  24522. }
  24523. else {
  24524. this[runFunction](argument);
  24525. }
  24526. }
  24527. }
  24528. }
  24529. this._loadLatestSector();
  24530. };
  24531. /**
  24532. * This runs a function in all sectors. This is used in the _redraw().
  24533. *
  24534. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  24535. * | we don't pass the function itself because then the "this" is the window object
  24536. * | instead of the Network object
  24537. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  24538. * @private
  24539. */
  24540. exports._doInAllSectors = function(runFunction,argument) {
  24541. var args = Array.prototype.splice.call(arguments, 1);
  24542. if (argument === undefined) {
  24543. this._doInAllActiveSectors(runFunction);
  24544. this._doInAllFrozenSectors(runFunction);
  24545. }
  24546. else {
  24547. if (args.length > 1) {
  24548. this._doInAllActiveSectors(runFunction,args[0],args[1]);
  24549. this._doInAllFrozenSectors(runFunction,args[0],args[1]);
  24550. }
  24551. else {
  24552. this._doInAllActiveSectors(runFunction,argument);
  24553. this._doInAllFrozenSectors(runFunction,argument);
  24554. }
  24555. }
  24556. };
  24557. /**
  24558. * This clears the nodeIndices list. We cannot use this.nodeIndices = [] because we would break the link with the
  24559. * active sector. Thus we clear the nodeIndices in the active sector, then reconnect the this.nodeIndices to it.
  24560. *
  24561. * @private
  24562. */
  24563. exports._clearNodeIndexList = function() {
  24564. var sector = this._sector();
  24565. this.sectors["active"][sector]["nodeIndices"] = [];
  24566. this.nodeIndices = this.sectors["active"][sector]["nodeIndices"];
  24567. };
  24568. /**
  24569. * Draw the encompassing sector node
  24570. *
  24571. * @param ctx
  24572. * @param sectorType
  24573. * @private
  24574. */
  24575. exports._drawSectorNodes = function(ctx,sectorType) {
  24576. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  24577. for (var sector in this.sectors[sectorType]) {
  24578. if (this.sectors[sectorType].hasOwnProperty(sector)) {
  24579. if (this.sectors[sectorType][sector]["drawingNode"] !== undefined) {
  24580. this._switchToSector(sector,sectorType);
  24581. minY = 1e9; maxY = -1e9; minX = 1e9; maxX = -1e9;
  24582. for (var nodeId in this.nodes) {
  24583. if (this.nodes.hasOwnProperty(nodeId)) {
  24584. node = this.nodes[nodeId];
  24585. node.resize(ctx);
  24586. if (minX > node.x - 0.5 * node.width) {minX = node.x - 0.5 * node.width;}
  24587. if (maxX < node.x + 0.5 * node.width) {maxX = node.x + 0.5 * node.width;}
  24588. if (minY > node.y - 0.5 * node.height) {minY = node.y - 0.5 * node.height;}
  24589. if (maxY < node.y + 0.5 * node.height) {maxY = node.y + 0.5 * node.height;}
  24590. }
  24591. }
  24592. node = this.sectors[sectorType][sector]["drawingNode"];
  24593. node.x = 0.5 * (maxX + minX);
  24594. node.y = 0.5 * (maxY + minY);
  24595. node.width = 2 * (node.x - minX);
  24596. node.height = 2 * (node.y - minY);
  24597. node.radius = Math.sqrt(Math.pow(0.5*node.width,2) + Math.pow(0.5*node.height,2));
  24598. node.setScale(this.scale);
  24599. node._drawCircle(ctx);
  24600. }
  24601. }
  24602. }
  24603. };
  24604. exports._drawAllSectorNodes = function(ctx) {
  24605. this._drawSectorNodes(ctx,"frozen");
  24606. this._drawSectorNodes(ctx,"active");
  24607. this._loadLatestSector();
  24608. };
  24609. /***/ },
  24610. /* 51 */
  24611. /***/ function(module, exports, __webpack_require__) {
  24612. var Node = __webpack_require__(36);
  24613. /**
  24614. * This function can be called from the _doInAllSectors function
  24615. *
  24616. * @param object
  24617. * @param overlappingNodes
  24618. * @private
  24619. */
  24620. exports._getNodesOverlappingWith = function(object, overlappingNodes) {
  24621. var nodes = this.nodes;
  24622. for (var nodeId in nodes) {
  24623. if (nodes.hasOwnProperty(nodeId)) {
  24624. if (nodes[nodeId].isOverlappingWith(object)) {
  24625. overlappingNodes.push(nodeId);
  24626. }
  24627. }
  24628. }
  24629. };
  24630. /**
  24631. * retrieve all nodes overlapping with given object
  24632. * @param {Object} object An object with parameters left, top, right, bottom
  24633. * @return {Number[]} An array with id's of the overlapping nodes
  24634. * @private
  24635. */
  24636. exports._getAllNodesOverlappingWith = function (object) {
  24637. var overlappingNodes = [];
  24638. this._doInAllActiveSectors("_getNodesOverlappingWith",object,overlappingNodes);
  24639. return overlappingNodes;
  24640. };
  24641. /**
  24642. * Return a position object in canvasspace from a single point in screenspace
  24643. *
  24644. * @param pointer
  24645. * @returns {{left: number, top: number, right: number, bottom: number}}
  24646. * @private
  24647. */
  24648. exports._pointerToPositionObject = function(pointer) {
  24649. var x = this._XconvertDOMtoCanvas(pointer.x);
  24650. var y = this._YconvertDOMtoCanvas(pointer.y);
  24651. return {
  24652. left: x,
  24653. top: y,
  24654. right: x,
  24655. bottom: y
  24656. };
  24657. };
  24658. /**
  24659. * Get the top node at the a specific point (like a click)
  24660. *
  24661. * @param {{x: Number, y: Number}} pointer
  24662. * @return {Node | null} node
  24663. * @private
  24664. */
  24665. exports._getNodeAt = function (pointer) {
  24666. // we first check if this is an navigation controls element
  24667. var positionObject = this._pointerToPositionObject(pointer);
  24668. var overlappingNodes = this._getAllNodesOverlappingWith(positionObject);
  24669. // if there are overlapping nodes, select the last one, this is the
  24670. // one which is drawn on top of the others
  24671. if (overlappingNodes.length > 0) {
  24672. return this.nodes[overlappingNodes[overlappingNodes.length - 1]];
  24673. }
  24674. else {
  24675. return null;
  24676. }
  24677. };
  24678. /**
  24679. * retrieve all edges overlapping with given object, selector is around center
  24680. * @param {Object} object An object with parameters left, top, right, bottom
  24681. * @return {Number[]} An array with id's of the overlapping nodes
  24682. * @private
  24683. */
  24684. exports._getEdgesOverlappingWith = function (object, overlappingEdges) {
  24685. var edges = this.edges;
  24686. for (var edgeId in edges) {
  24687. if (edges.hasOwnProperty(edgeId)) {
  24688. if (edges[edgeId].isOverlappingWith(object)) {
  24689. overlappingEdges.push(edgeId);
  24690. }
  24691. }
  24692. }
  24693. };
  24694. /**
  24695. * retrieve all nodes overlapping with given object
  24696. * @param {Object} object An object with parameters left, top, right, bottom
  24697. * @return {Number[]} An array with id's of the overlapping nodes
  24698. * @private
  24699. */
  24700. exports._getAllEdgesOverlappingWith = function (object) {
  24701. var overlappingEdges = [];
  24702. this._doInAllActiveSectors("_getEdgesOverlappingWith",object,overlappingEdges);
  24703. return overlappingEdges;
  24704. };
  24705. /**
  24706. * Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call
  24707. * _getNodeAt and _getEdgesAt, then priortize the selection to user preferences.
  24708. *
  24709. * @param pointer
  24710. * @returns {null}
  24711. * @private
  24712. */
  24713. exports._getEdgeAt = function(pointer) {
  24714. var positionObject = this._pointerToPositionObject(pointer);
  24715. var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject);
  24716. if (overlappingEdges.length > 0) {
  24717. return this.edges[overlappingEdges[overlappingEdges.length - 1]];
  24718. }
  24719. else {
  24720. return null;
  24721. }
  24722. };
  24723. /**
  24724. * Add object to the selection array.
  24725. *
  24726. * @param obj
  24727. * @private
  24728. */
  24729. exports._addToSelection = function(obj) {
  24730. if (obj instanceof Node) {
  24731. this.selectionObj.nodes[obj.id] = obj;
  24732. }
  24733. else {
  24734. this.selectionObj.edges[obj.id] = obj;
  24735. }
  24736. };
  24737. /**
  24738. * Add object to the selection array.
  24739. *
  24740. * @param obj
  24741. * @private
  24742. */
  24743. exports._addToHover = function(obj) {
  24744. if (obj instanceof Node) {
  24745. this.hoverObj.nodes[obj.id] = obj;
  24746. }
  24747. else {
  24748. this.hoverObj.edges[obj.id] = obj;
  24749. }
  24750. };
  24751. /**
  24752. * Remove a single option from selection.
  24753. *
  24754. * @param {Object} obj
  24755. * @private
  24756. */
  24757. exports._removeFromSelection = function(obj) {
  24758. if (obj instanceof Node) {
  24759. delete this.selectionObj.nodes[obj.id];
  24760. }
  24761. else {
  24762. delete this.selectionObj.edges[obj.id];
  24763. }
  24764. };
  24765. /**
  24766. * Unselect all. The selectionObj is useful for this.
  24767. *
  24768. * @param {Boolean} [doNotTrigger] | ignore trigger
  24769. * @private
  24770. */
  24771. exports._unselectAll = function(doNotTrigger) {
  24772. if (doNotTrigger === undefined) {
  24773. doNotTrigger = false;
  24774. }
  24775. for(var nodeId in this.selectionObj.nodes) {
  24776. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  24777. this.selectionObj.nodes[nodeId].unselect();
  24778. }
  24779. }
  24780. for(var edgeId in this.selectionObj.edges) {
  24781. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  24782. this.selectionObj.edges[edgeId].unselect();
  24783. }
  24784. }
  24785. this.selectionObj = {nodes:{},edges:{}};
  24786. if (doNotTrigger == false) {
  24787. this.emit('select', this.getSelection());
  24788. }
  24789. };
  24790. /**
  24791. * Unselect all clusters. The selectionObj is useful for this.
  24792. *
  24793. * @param {Boolean} [doNotTrigger] | ignore trigger
  24794. * @private
  24795. */
  24796. exports._unselectClusters = function(doNotTrigger) {
  24797. if (doNotTrigger === undefined) {
  24798. doNotTrigger = false;
  24799. }
  24800. for (var nodeId in this.selectionObj.nodes) {
  24801. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  24802. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  24803. this.selectionObj.nodes[nodeId].unselect();
  24804. this._removeFromSelection(this.selectionObj.nodes[nodeId]);
  24805. }
  24806. }
  24807. }
  24808. if (doNotTrigger == false) {
  24809. this.emit('select', this.getSelection());
  24810. }
  24811. };
  24812. /**
  24813. * return the number of selected nodes
  24814. *
  24815. * @returns {number}
  24816. * @private
  24817. */
  24818. exports._getSelectedNodeCount = function() {
  24819. var count = 0;
  24820. for (var nodeId in this.selectionObj.nodes) {
  24821. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  24822. count += 1;
  24823. }
  24824. }
  24825. return count;
  24826. };
  24827. /**
  24828. * return the selected node
  24829. *
  24830. * @returns {number}
  24831. * @private
  24832. */
  24833. exports._getSelectedNode = function() {
  24834. for (var nodeId in this.selectionObj.nodes) {
  24835. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  24836. return this.selectionObj.nodes[nodeId];
  24837. }
  24838. }
  24839. return null;
  24840. };
  24841. /**
  24842. * return the selected edge
  24843. *
  24844. * @returns {number}
  24845. * @private
  24846. */
  24847. exports._getSelectedEdge = function() {
  24848. for (var edgeId in this.selectionObj.edges) {
  24849. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  24850. return this.selectionObj.edges[edgeId];
  24851. }
  24852. }
  24853. return null;
  24854. };
  24855. /**
  24856. * return the number of selected edges
  24857. *
  24858. * @returns {number}
  24859. * @private
  24860. */
  24861. exports._getSelectedEdgeCount = function() {
  24862. var count = 0;
  24863. for (var edgeId in this.selectionObj.edges) {
  24864. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  24865. count += 1;
  24866. }
  24867. }
  24868. return count;
  24869. };
  24870. /**
  24871. * return the number of selected objects.
  24872. *
  24873. * @returns {number}
  24874. * @private
  24875. */
  24876. exports._getSelectedObjectCount = function() {
  24877. var count = 0;
  24878. for(var nodeId in this.selectionObj.nodes) {
  24879. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  24880. count += 1;
  24881. }
  24882. }
  24883. for(var edgeId in this.selectionObj.edges) {
  24884. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  24885. count += 1;
  24886. }
  24887. }
  24888. return count;
  24889. };
  24890. /**
  24891. * Check if anything is selected
  24892. *
  24893. * @returns {boolean}
  24894. * @private
  24895. */
  24896. exports._selectionIsEmpty = function() {
  24897. for(var nodeId in this.selectionObj.nodes) {
  24898. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  24899. return false;
  24900. }
  24901. }
  24902. for(var edgeId in this.selectionObj.edges) {
  24903. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  24904. return false;
  24905. }
  24906. }
  24907. return true;
  24908. };
  24909. /**
  24910. * check if one of the selected nodes is a cluster.
  24911. *
  24912. * @returns {boolean}
  24913. * @private
  24914. */
  24915. exports._clusterInSelection = function() {
  24916. for(var nodeId in this.selectionObj.nodes) {
  24917. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  24918. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  24919. return true;
  24920. }
  24921. }
  24922. }
  24923. return false;
  24924. };
  24925. /**
  24926. * select the edges connected to the node that is being selected
  24927. *
  24928. * @param {Node} node
  24929. * @private
  24930. */
  24931. exports._selectConnectedEdges = function(node) {
  24932. for (var i = 0; i < node.dynamicEdges.length; i++) {
  24933. var edge = node.dynamicEdges[i];
  24934. edge.select();
  24935. this._addToSelection(edge);
  24936. }
  24937. };
  24938. /**
  24939. * select the edges connected to the node that is being selected
  24940. *
  24941. * @param {Node} node
  24942. * @private
  24943. */
  24944. exports._hoverConnectedEdges = function(node) {
  24945. for (var i = 0; i < node.dynamicEdges.length; i++) {
  24946. var edge = node.dynamicEdges[i];
  24947. edge.hover = true;
  24948. this._addToHover(edge);
  24949. }
  24950. };
  24951. /**
  24952. * unselect the edges connected to the node that is being selected
  24953. *
  24954. * @param {Node} node
  24955. * @private
  24956. */
  24957. exports._unselectConnectedEdges = function(node) {
  24958. for (var i = 0; i < node.dynamicEdges.length; i++) {
  24959. var edge = node.dynamicEdges[i];
  24960. edge.unselect();
  24961. this._removeFromSelection(edge);
  24962. }
  24963. };
  24964. /**
  24965. * This is called when someone clicks on a node. either select or deselect it.
  24966. * If there is an existing selection and we don't want to append to it, clear the existing selection
  24967. *
  24968. * @param {Node || Edge} object
  24969. * @param {Boolean} append
  24970. * @param {Boolean} [doNotTrigger] | ignore trigger
  24971. * @private
  24972. */
  24973. exports._selectObject = function(object, append, doNotTrigger, highlightEdges) {
  24974. if (doNotTrigger === undefined) {
  24975. doNotTrigger = false;
  24976. }
  24977. if (highlightEdges === undefined) {
  24978. highlightEdges = true;
  24979. }
  24980. if (this._selectionIsEmpty() == false && append == false && this.forceAppendSelection == false) {
  24981. this._unselectAll(true);
  24982. }
  24983. if (object.selected == false) {
  24984. object.select();
  24985. this._addToSelection(object);
  24986. if (object instanceof Node && this.blockConnectingEdgeSelection == false && highlightEdges == true) {
  24987. this._selectConnectedEdges(object);
  24988. }
  24989. }
  24990. else {
  24991. object.unselect();
  24992. this._removeFromSelection(object);
  24993. }
  24994. if (doNotTrigger == false) {
  24995. this.emit('select', this.getSelection());
  24996. }
  24997. };
  24998. /**
  24999. * This is called when someone clicks on a node. either select or deselect it.
  25000. * If there is an existing selection and we don't want to append to it, clear the existing selection
  25001. *
  25002. * @param {Node || Edge} object
  25003. * @private
  25004. */
  25005. exports._blurObject = function(object) {
  25006. if (object.hover == true) {
  25007. object.hover = false;
  25008. this.emit("blurNode",{node:object.id});
  25009. }
  25010. };
  25011. /**
  25012. * This is called when someone clicks on a node. either select or deselect it.
  25013. * If there is an existing selection and we don't want to append to it, clear the existing selection
  25014. *
  25015. * @param {Node || Edge} object
  25016. * @private
  25017. */
  25018. exports._hoverObject = function(object) {
  25019. if (object.hover == false) {
  25020. object.hover = true;
  25021. this._addToHover(object);
  25022. if (object instanceof Node) {
  25023. this.emit("hoverNode",{node:object.id});
  25024. }
  25025. }
  25026. if (object instanceof Node) {
  25027. this._hoverConnectedEdges(object);
  25028. }
  25029. };
  25030. /**
  25031. * handles the selection part of the touch, only for navigation controls elements;
  25032. * Touch is triggered before tap, also before hold. Hold triggers after a while.
  25033. * This is the most responsive solution
  25034. *
  25035. * @param {Object} pointer
  25036. * @private
  25037. */
  25038. exports._handleTouch = function(pointer) {
  25039. };
  25040. /**
  25041. * handles the selection part of the tap;
  25042. *
  25043. * @param {Object} pointer
  25044. * @private
  25045. */
  25046. exports._handleTap = function(pointer) {
  25047. var node = this._getNodeAt(pointer);
  25048. if (node != null) {
  25049. this._selectObject(node,false);
  25050. }
  25051. else {
  25052. var edge = this._getEdgeAt(pointer);
  25053. if (edge != null) {
  25054. this._selectObject(edge,false);
  25055. }
  25056. else {
  25057. this._unselectAll();
  25058. }
  25059. }
  25060. this.emit("click", this.getSelection());
  25061. this._redraw();
  25062. };
  25063. /**
  25064. * handles the selection part of the double tap and opens a cluster if needed
  25065. *
  25066. * @param {Object} pointer
  25067. * @private
  25068. */
  25069. exports._handleDoubleTap = function(pointer) {
  25070. var node = this._getNodeAt(pointer);
  25071. if (node != null && node !== undefined) {
  25072. // we reset the areaCenter here so the opening of the node will occur
  25073. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  25074. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  25075. this.openCluster(node);
  25076. }
  25077. this.emit("doubleClick", this.getSelection());
  25078. };
  25079. /**
  25080. * Handle the onHold selection part
  25081. *
  25082. * @param pointer
  25083. * @private
  25084. */
  25085. exports._handleOnHold = function(pointer) {
  25086. var node = this._getNodeAt(pointer);
  25087. if (node != null) {
  25088. this._selectObject(node,true);
  25089. }
  25090. else {
  25091. var edge = this._getEdgeAt(pointer);
  25092. if (edge != null) {
  25093. this._selectObject(edge,true);
  25094. }
  25095. }
  25096. this._redraw();
  25097. };
  25098. /**
  25099. * handle the onRelease event. These functions are here for the navigation controls module.
  25100. *
  25101. * @private
  25102. */
  25103. exports._handleOnRelease = function(pointer) {
  25104. };
  25105. /**
  25106. *
  25107. * retrieve the currently selected objects
  25108. * @return {{nodes: Array.<String>, edges: Array.<String>}} selection
  25109. */
  25110. exports.getSelection = function() {
  25111. var nodeIds = this.getSelectedNodes();
  25112. var edgeIds = this.getSelectedEdges();
  25113. return {nodes:nodeIds, edges:edgeIds};
  25114. };
  25115. /**
  25116. *
  25117. * retrieve the currently selected nodes
  25118. * @return {String[]} selection An array with the ids of the
  25119. * selected nodes.
  25120. */
  25121. exports.getSelectedNodes = function() {
  25122. var idArray = [];
  25123. for(var nodeId in this.selectionObj.nodes) {
  25124. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25125. idArray.push(nodeId);
  25126. }
  25127. }
  25128. return idArray
  25129. };
  25130. /**
  25131. *
  25132. * retrieve the currently selected edges
  25133. * @return {Array} selection An array with the ids of the
  25134. * selected nodes.
  25135. */
  25136. exports.getSelectedEdges = function() {
  25137. var idArray = [];
  25138. for(var edgeId in this.selectionObj.edges) {
  25139. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25140. idArray.push(edgeId);
  25141. }
  25142. }
  25143. return idArray;
  25144. };
  25145. /**
  25146. * select zero or more nodes
  25147. * @param {Number[] | String[]} selection An array with the ids of the
  25148. * selected nodes.
  25149. */
  25150. exports.setSelection = function(selection) {
  25151. var i, iMax, id;
  25152. if (!selection || (selection.length == undefined))
  25153. throw 'Selection must be an array with ids';
  25154. // first unselect any selected node
  25155. this._unselectAll(true);
  25156. for (i = 0, iMax = selection.length; i < iMax; i++) {
  25157. id = selection[i];
  25158. var node = this.nodes[id];
  25159. if (!node) {
  25160. throw new RangeError('Node with id "' + id + '" not found');
  25161. }
  25162. this._selectObject(node,true,true);
  25163. }
  25164. console.log("setSelection is deprecated. Please use selectNodes instead.")
  25165. this.redraw();
  25166. };
  25167. /**
  25168. * select zero or more nodes with the option to highlight edges
  25169. * @param {Number[] | String[]} selection An array with the ids of the
  25170. * selected nodes.
  25171. * @param {boolean} [highlightEdges]
  25172. */
  25173. exports.selectNodes = function(selection, highlightEdges) {
  25174. var i, iMax, id;
  25175. if (!selection || (selection.length == undefined))
  25176. throw 'Selection must be an array with ids';
  25177. // first unselect any selected node
  25178. this._unselectAll(true);
  25179. for (i = 0, iMax = selection.length; i < iMax; i++) {
  25180. id = selection[i];
  25181. var node = this.nodes[id];
  25182. if (!node) {
  25183. throw new RangeError('Node with id "' + id + '" not found');
  25184. }
  25185. this._selectObject(node,true,true,highlightEdges);
  25186. }
  25187. this.redraw();
  25188. };
  25189. /**
  25190. * select zero or more edges
  25191. * @param {Number[] | String[]} selection An array with the ids of the
  25192. * selected nodes.
  25193. */
  25194. exports.selectEdges = function(selection) {
  25195. var i, iMax, id;
  25196. if (!selection || (selection.length == undefined))
  25197. throw 'Selection must be an array with ids';
  25198. // first unselect any selected node
  25199. this._unselectAll(true);
  25200. for (i = 0, iMax = selection.length; i < iMax; i++) {
  25201. id = selection[i];
  25202. var edge = this.edges[id];
  25203. if (!edge) {
  25204. throw new RangeError('Edge with id "' + id + '" not found');
  25205. }
  25206. this._selectObject(edge,true,true,highlightEdges);
  25207. }
  25208. this.redraw();
  25209. };
  25210. /**
  25211. * Validate the selection: remove ids of nodes which no longer exist
  25212. * @private
  25213. */
  25214. exports._updateSelection = function () {
  25215. for(var nodeId in this.selectionObj.nodes) {
  25216. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  25217. if (!this.nodes.hasOwnProperty(nodeId)) {
  25218. delete this.selectionObj.nodes[nodeId];
  25219. }
  25220. }
  25221. }
  25222. for(var edgeId in this.selectionObj.edges) {
  25223. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  25224. if (!this.edges.hasOwnProperty(edgeId)) {
  25225. delete this.selectionObj.edges[edgeId];
  25226. }
  25227. }
  25228. }
  25229. };
  25230. /***/ },
  25231. /* 52 */
  25232. /***/ function(module, exports, __webpack_require__) {
  25233. var util = __webpack_require__(1);
  25234. var Node = __webpack_require__(36);
  25235. var Edge = __webpack_require__(33);
  25236. /**
  25237. * clears the toolbar div element of children
  25238. *
  25239. * @private
  25240. */
  25241. exports._clearManipulatorBar = function() {
  25242. while (this.manipulationDiv.hasChildNodes()) {
  25243. this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
  25244. }
  25245. };
  25246. /**
  25247. * Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore
  25248. * these functions to their original functionality, we saved them in this.cachedFunctions.
  25249. * This function restores these functions to their original function.
  25250. *
  25251. * @private
  25252. */
  25253. exports._restoreOverloadedFunctions = function() {
  25254. for (var functionName in this.cachedFunctions) {
  25255. if (this.cachedFunctions.hasOwnProperty(functionName)) {
  25256. this[functionName] = this.cachedFunctions[functionName];
  25257. }
  25258. }
  25259. };
  25260. /**
  25261. * Enable or disable edit-mode.
  25262. *
  25263. * @private
  25264. */
  25265. exports._toggleEditMode = function() {
  25266. this.editMode = !this.editMode;
  25267. var toolbar = document.getElementById("network-manipulationDiv");
  25268. var closeDiv = document.getElementById("network-manipulation-closeDiv");
  25269. var editModeDiv = document.getElementById("network-manipulation-editMode");
  25270. if (this.editMode == true) {
  25271. toolbar.style.display="block";
  25272. closeDiv.style.display="block";
  25273. editModeDiv.style.display="none";
  25274. closeDiv.onclick = this._toggleEditMode.bind(this);
  25275. }
  25276. else {
  25277. toolbar.style.display="none";
  25278. closeDiv.style.display="none";
  25279. editModeDiv.style.display="block";
  25280. closeDiv.onclick = null;
  25281. }
  25282. this._createManipulatorBar()
  25283. };
  25284. /**
  25285. * main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
  25286. *
  25287. * @private
  25288. */
  25289. exports._createManipulatorBar = function() {
  25290. // remove bound functions
  25291. if (this.boundFunction) {
  25292. this.off('select', this.boundFunction);
  25293. }
  25294. if (this.edgeBeingEdited !== undefined) {
  25295. this.edgeBeingEdited._disableControlNodes();
  25296. this.edgeBeingEdited = undefined;
  25297. this.selectedControlNode = null;
  25298. this.controlNodesActive = false;
  25299. }
  25300. // restore overloaded functions
  25301. this._restoreOverloadedFunctions();
  25302. // resume calculation
  25303. this.freezeSimulation = false;
  25304. // reset global variables
  25305. this.blockConnectingEdgeSelection = false;
  25306. this.forceAppendSelection = false;
  25307. if (this.editMode == true) {
  25308. while (this.manipulationDiv.hasChildNodes()) {
  25309. this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
  25310. }
  25311. // add the icons to the manipulator div
  25312. this.manipulationDiv.innerHTML = "" +
  25313. "<span class='network-manipulationUI add' id='network-manipulate-addNode'>" +
  25314. "<span class='network-manipulationLabel'>"+this.constants.labels['add'] +"</span></span>" +
  25315. "<div class='network-seperatorLine'></div>" +
  25316. "<span class='network-manipulationUI connect' id='network-manipulate-connectNode'>" +
  25317. "<span class='network-manipulationLabel'>"+this.constants.labels['link'] +"</span></span>";
  25318. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  25319. this.manipulationDiv.innerHTML += "" +
  25320. "<div class='network-seperatorLine'></div>" +
  25321. "<span class='network-manipulationUI edit' id='network-manipulate-editNode'>" +
  25322. "<span class='network-manipulationLabel'>"+this.constants.labels['editNode'] +"</span></span>";
  25323. }
  25324. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  25325. this.manipulationDiv.innerHTML += "" +
  25326. "<div class='network-seperatorLine'></div>" +
  25327. "<span class='network-manipulationUI edit' id='network-manipulate-editEdge'>" +
  25328. "<span class='network-manipulationLabel'>"+this.constants.labels['editEdge'] +"</span></span>";
  25329. }
  25330. if (this._selectionIsEmpty() == false) {
  25331. this.manipulationDiv.innerHTML += "" +
  25332. "<div class='network-seperatorLine'></div>" +
  25333. "<span class='network-manipulationUI delete' id='network-manipulate-delete'>" +
  25334. "<span class='network-manipulationLabel'>"+this.constants.labels['del'] +"</span></span>";
  25335. }
  25336. // bind the icons
  25337. var addNodeButton = document.getElementById("network-manipulate-addNode");
  25338. addNodeButton.onclick = this._createAddNodeToolbar.bind(this);
  25339. var addEdgeButton = document.getElementById("network-manipulate-connectNode");
  25340. addEdgeButton.onclick = this._createAddEdgeToolbar.bind(this);
  25341. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  25342. var editButton = document.getElementById("network-manipulate-editNode");
  25343. editButton.onclick = this._editNode.bind(this);
  25344. }
  25345. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  25346. var editButton = document.getElementById("network-manipulate-editEdge");
  25347. editButton.onclick = this._createEditEdgeToolbar.bind(this);
  25348. }
  25349. if (this._selectionIsEmpty() == false) {
  25350. var deleteButton = document.getElementById("network-manipulate-delete");
  25351. deleteButton.onclick = this._deleteSelected.bind(this);
  25352. }
  25353. var closeDiv = document.getElementById("network-manipulation-closeDiv");
  25354. closeDiv.onclick = this._toggleEditMode.bind(this);
  25355. this.boundFunction = this._createManipulatorBar.bind(this);
  25356. this.on('select', this.boundFunction);
  25357. }
  25358. else {
  25359. this.editModeDiv.innerHTML = "" +
  25360. "<span class='network-manipulationUI edit editmode' id='network-manipulate-editModeButton'>" +
  25361. "<span class='network-manipulationLabel'>" + this.constants.labels['edit'] + "</span></span>";
  25362. var editModeButton = document.getElementById("network-manipulate-editModeButton");
  25363. editModeButton.onclick = this._toggleEditMode.bind(this);
  25364. }
  25365. };
  25366. /**
  25367. * Create the toolbar for adding Nodes
  25368. *
  25369. * @private
  25370. */
  25371. exports._createAddNodeToolbar = function() {
  25372. // clear the toolbar
  25373. this._clearManipulatorBar();
  25374. if (this.boundFunction) {
  25375. this.off('select', this.boundFunction);
  25376. }
  25377. // create the toolbar contents
  25378. this.manipulationDiv.innerHTML = "" +
  25379. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  25380. "<span class='network-manipulationLabel'>" + this.constants.labels['back'] + " </span></span>" +
  25381. "<div class='network-seperatorLine'></div>" +
  25382. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  25383. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + this.constants.labels['addDescription'] + "</span></span>";
  25384. // bind the icon
  25385. var backButton = document.getElementById("network-manipulate-back");
  25386. backButton.onclick = this._createManipulatorBar.bind(this);
  25387. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  25388. this.boundFunction = this._addNode.bind(this);
  25389. this.on('select', this.boundFunction);
  25390. };
  25391. /**
  25392. * create the toolbar to connect nodes
  25393. *
  25394. * @private
  25395. */
  25396. exports._createAddEdgeToolbar = function() {
  25397. // clear the toolbar
  25398. this._clearManipulatorBar();
  25399. this._unselectAll(true);
  25400. this.freezeSimulation = true;
  25401. if (this.boundFunction) {
  25402. this.off('select', this.boundFunction);
  25403. }
  25404. this._unselectAll();
  25405. this.forceAppendSelection = false;
  25406. this.blockConnectingEdgeSelection = true;
  25407. this.manipulationDiv.innerHTML = "" +
  25408. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  25409. "<span class='network-manipulationLabel'>" + this.constants.labels['back'] + " </span></span>" +
  25410. "<div class='network-seperatorLine'></div>" +
  25411. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  25412. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + this.constants.labels['linkDescription'] + "</span></span>";
  25413. // bind the icon
  25414. var backButton = document.getElementById("network-manipulate-back");
  25415. backButton.onclick = this._createManipulatorBar.bind(this);
  25416. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  25417. this.boundFunction = this._handleConnect.bind(this);
  25418. this.on('select', this.boundFunction);
  25419. // temporarily overload functions
  25420. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  25421. this.cachedFunctions["_handleOnRelease"] = this._handleOnRelease;
  25422. this._handleTouch = this._handleConnect;
  25423. this._handleOnRelease = this._finishConnect;
  25424. // redraw to show the unselect
  25425. this._redraw();
  25426. };
  25427. /**
  25428. * create the toolbar to edit edges
  25429. *
  25430. * @private
  25431. */
  25432. exports._createEditEdgeToolbar = function() {
  25433. // clear the toolbar
  25434. this._clearManipulatorBar();
  25435. this.controlNodesActive = true;
  25436. if (this.boundFunction) {
  25437. this.off('select', this.boundFunction);
  25438. }
  25439. this.edgeBeingEdited = this._getSelectedEdge();
  25440. this.edgeBeingEdited._enableControlNodes();
  25441. this.manipulationDiv.innerHTML = "" +
  25442. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  25443. "<span class='network-manipulationLabel'>" + this.constants.labels['back'] + " </span></span>" +
  25444. "<div class='network-seperatorLine'></div>" +
  25445. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  25446. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + this.constants.labels['editEdgeDescription'] + "</span></span>";
  25447. // bind the icon
  25448. var backButton = document.getElementById("network-manipulate-back");
  25449. backButton.onclick = this._createManipulatorBar.bind(this);
  25450. // temporarily overload functions
  25451. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  25452. this.cachedFunctions["_handleOnRelease"] = this._handleOnRelease;
  25453. this.cachedFunctions["_handleTap"] = this._handleTap;
  25454. this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
  25455. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  25456. this._handleTouch = this._selectControlNode;
  25457. this._handleTap = function () {};
  25458. this._handleOnDrag = this._controlNodeDrag;
  25459. this._handleDragStart = function () {}
  25460. this._handleOnRelease = this._releaseControlNode;
  25461. // redraw to show the unselect
  25462. this._redraw();
  25463. };
  25464. /**
  25465. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  25466. * to walk the user through the process.
  25467. *
  25468. * @private
  25469. */
  25470. exports._selectControlNode = function(pointer) {
  25471. this.edgeBeingEdited.controlNodes.from.unselect();
  25472. this.edgeBeingEdited.controlNodes.to.unselect();
  25473. this.selectedControlNode = this.edgeBeingEdited._getSelectedControlNode(this._XconvertDOMtoCanvas(pointer.x),this._YconvertDOMtoCanvas(pointer.y));
  25474. if (this.selectedControlNode !== null) {
  25475. this.selectedControlNode.select();
  25476. this.freezeSimulation = true;
  25477. }
  25478. this._redraw();
  25479. };
  25480. /**
  25481. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  25482. * to walk the user through the process.
  25483. *
  25484. * @private
  25485. */
  25486. exports._controlNodeDrag = function(event) {
  25487. var pointer = this._getPointer(event.gesture.center);
  25488. if (this.selectedControlNode !== null && this.selectedControlNode !== undefined) {
  25489. this.selectedControlNode.x = this._XconvertDOMtoCanvas(pointer.x);
  25490. this.selectedControlNode.y = this._YconvertDOMtoCanvas(pointer.y);
  25491. }
  25492. this._redraw();
  25493. };
  25494. exports._releaseControlNode = function(pointer) {
  25495. var newNode = this._getNodeAt(pointer);
  25496. if (newNode != null) {
  25497. if (this.edgeBeingEdited.controlNodes.from.selected == true) {
  25498. this._editEdge(newNode.id, this.edgeBeingEdited.to.id);
  25499. this.edgeBeingEdited.controlNodes.from.unselect();
  25500. }
  25501. if (this.edgeBeingEdited.controlNodes.to.selected == true) {
  25502. this._editEdge(this.edgeBeingEdited.from.id, newNode.id);
  25503. this.edgeBeingEdited.controlNodes.to.unselect();
  25504. }
  25505. }
  25506. else {
  25507. this.edgeBeingEdited._restoreControlNodes();
  25508. }
  25509. this.freezeSimulation = false;
  25510. this._redraw();
  25511. };
  25512. /**
  25513. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  25514. * to walk the user through the process.
  25515. *
  25516. * @private
  25517. */
  25518. exports._handleConnect = function(pointer) {
  25519. if (this._getSelectedNodeCount() == 0) {
  25520. var node = this._getNodeAt(pointer);
  25521. if (node != null) {
  25522. if (node.clusterSize > 1) {
  25523. alert("Cannot create edges to a cluster.")
  25524. }
  25525. else {
  25526. this._selectObject(node,false);
  25527. // create a node the temporary line can look at
  25528. this.sectors['support']['nodes']['targetNode'] = new Node({id:'targetNode'},{},{},this.constants);
  25529. this.sectors['support']['nodes']['targetNode'].x = node.x;
  25530. this.sectors['support']['nodes']['targetNode'].y = node.y;
  25531. this.sectors['support']['nodes']['targetViaNode'] = new Node({id:'targetViaNode'},{},{},this.constants);
  25532. this.sectors['support']['nodes']['targetViaNode'].x = node.x;
  25533. this.sectors['support']['nodes']['targetViaNode'].y = node.y;
  25534. this.sectors['support']['nodes']['targetViaNode'].parentEdgeId = "connectionEdge";
  25535. // create a temporary edge
  25536. this.edges['connectionEdge'] = new Edge({id:"connectionEdge",from:node.id,to:this.sectors['support']['nodes']['targetNode'].id}, this, this.constants);
  25537. this.edges['connectionEdge'].from = node;
  25538. this.edges['connectionEdge'].connected = true;
  25539. this.edges['connectionEdge'].smooth = true;
  25540. this.edges['connectionEdge'].selected = true;
  25541. this.edges['connectionEdge'].to = this.sectors['support']['nodes']['targetNode'];
  25542. this.edges['connectionEdge'].via = this.sectors['support']['nodes']['targetViaNode'];
  25543. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  25544. this._handleOnDrag = function(event) {
  25545. var pointer = this._getPointer(event.gesture.center);
  25546. this.sectors['support']['nodes']['targetNode'].x = this._XconvertDOMtoCanvas(pointer.x);
  25547. this.sectors['support']['nodes']['targetNode'].y = this._YconvertDOMtoCanvas(pointer.y);
  25548. this.sectors['support']['nodes']['targetViaNode'].x = 0.5 * (this._XconvertDOMtoCanvas(pointer.x) + this.edges['connectionEdge'].from.x);
  25549. this.sectors['support']['nodes']['targetViaNode'].y = this._YconvertDOMtoCanvas(pointer.y);
  25550. };
  25551. this.moving = true;
  25552. this.start();
  25553. }
  25554. }
  25555. }
  25556. };
  25557. exports._finishConnect = function(pointer) {
  25558. if (this._getSelectedNodeCount() == 1) {
  25559. // restore the drag function
  25560. this._handleOnDrag = this.cachedFunctions["_handleOnDrag"];
  25561. delete this.cachedFunctions["_handleOnDrag"];
  25562. // remember the edge id
  25563. var connectFromId = this.edges['connectionEdge'].fromId;
  25564. // remove the temporary nodes and edge
  25565. delete this.edges['connectionEdge'];
  25566. delete this.sectors['support']['nodes']['targetNode'];
  25567. delete this.sectors['support']['nodes']['targetViaNode'];
  25568. var node = this._getNodeAt(pointer);
  25569. if (node != null) {
  25570. if (node.clusterSize > 1) {
  25571. alert("Cannot create edges to a cluster.")
  25572. }
  25573. else {
  25574. this._createEdge(connectFromId,node.id);
  25575. this._createManipulatorBar();
  25576. }
  25577. }
  25578. this._unselectAll();
  25579. }
  25580. };
  25581. /**
  25582. * Adds a node on the specified location
  25583. */
  25584. exports._addNode = function() {
  25585. if (this._selectionIsEmpty() && this.editMode == true) {
  25586. var positionObject = this._pointerToPositionObject(this.pointerPosition);
  25587. var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",allowedToMoveX:true,allowedToMoveY:true};
  25588. if (this.triggerFunctions.add) {
  25589. if (this.triggerFunctions.add.length == 2) {
  25590. var me = this;
  25591. this.triggerFunctions.add(defaultData, function(finalizedData) {
  25592. me.nodesData.add(finalizedData);
  25593. me._createManipulatorBar();
  25594. me.moving = true;
  25595. me.start();
  25596. });
  25597. }
  25598. else {
  25599. alert(this.constants.labels['addError']);
  25600. this._createManipulatorBar();
  25601. this.moving = true;
  25602. this.start();
  25603. }
  25604. }
  25605. else {
  25606. this.nodesData.add(defaultData);
  25607. this._createManipulatorBar();
  25608. this.moving = true;
  25609. this.start();
  25610. }
  25611. }
  25612. };
  25613. /**
  25614. * connect two nodes with a new edge.
  25615. *
  25616. * @private
  25617. */
  25618. exports._createEdge = function(sourceNodeId,targetNodeId) {
  25619. if (this.editMode == true) {
  25620. var defaultData = {from:sourceNodeId, to:targetNodeId};
  25621. if (this.triggerFunctions.connect) {
  25622. if (this.triggerFunctions.connect.length == 2) {
  25623. var me = this;
  25624. this.triggerFunctions.connect(defaultData, function(finalizedData) {
  25625. me.edgesData.add(finalizedData);
  25626. me.moving = true;
  25627. me.start();
  25628. });
  25629. }
  25630. else {
  25631. alert(this.constants.labels["linkError"]);
  25632. this.moving = true;
  25633. this.start();
  25634. }
  25635. }
  25636. else {
  25637. this.edgesData.add(defaultData);
  25638. this.moving = true;
  25639. this.start();
  25640. }
  25641. }
  25642. };
  25643. /**
  25644. * connect two nodes with a new edge.
  25645. *
  25646. * @private
  25647. */
  25648. exports._editEdge = function(sourceNodeId,targetNodeId) {
  25649. if (this.editMode == true) {
  25650. var defaultData = {id: this.edgeBeingEdited.id, from:sourceNodeId, to:targetNodeId};
  25651. if (this.triggerFunctions.editEdge) {
  25652. if (this.triggerFunctions.editEdge.length == 2) {
  25653. var me = this;
  25654. this.triggerFunctions.editEdge(defaultData, function(finalizedData) {
  25655. me.edgesData.update(finalizedData);
  25656. me.moving = true;
  25657. me.start();
  25658. });
  25659. }
  25660. else {
  25661. alert(this.constants.labels["linkError"]);
  25662. this.moving = true;
  25663. this.start();
  25664. }
  25665. }
  25666. else {
  25667. this.edgesData.update(defaultData);
  25668. this.moving = true;
  25669. this.start();
  25670. }
  25671. }
  25672. };
  25673. /**
  25674. * Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color.
  25675. *
  25676. * @private
  25677. */
  25678. exports._editNode = function() {
  25679. if (this.triggerFunctions.edit && this.editMode == true) {
  25680. var node = this._getSelectedNode();
  25681. var data = {id:node.id,
  25682. label: node.label,
  25683. group: node.group,
  25684. shape: node.shape,
  25685. color: {
  25686. background:node.color.background,
  25687. border:node.color.border,
  25688. highlight: {
  25689. background:node.color.highlight.background,
  25690. border:node.color.highlight.border
  25691. }
  25692. }};
  25693. if (this.triggerFunctions.edit.length == 2) {
  25694. var me = this;
  25695. this.triggerFunctions.edit(data, function (finalizedData) {
  25696. me.nodesData.update(finalizedData);
  25697. me._createManipulatorBar();
  25698. me.moving = true;
  25699. me.start();
  25700. });
  25701. }
  25702. else {
  25703. alert(this.constants.labels["editError"]);
  25704. }
  25705. }
  25706. else {
  25707. alert(this.constants.labels["editBoundError"]);
  25708. }
  25709. };
  25710. /**
  25711. * delete everything in the selection
  25712. *
  25713. * @private
  25714. */
  25715. exports._deleteSelected = function() {
  25716. if (!this._selectionIsEmpty() && this.editMode == true) {
  25717. if (!this._clusterInSelection()) {
  25718. var selectedNodes = this.getSelectedNodes();
  25719. var selectedEdges = this.getSelectedEdges();
  25720. if (this.triggerFunctions.del) {
  25721. var me = this;
  25722. var data = {nodes: selectedNodes, edges: selectedEdges};
  25723. if (this.triggerFunctions.del.length = 2) {
  25724. this.triggerFunctions.del(data, function (finalizedData) {
  25725. me.edgesData.remove(finalizedData.edges);
  25726. me.nodesData.remove(finalizedData.nodes);
  25727. me._unselectAll();
  25728. me.moving = true;
  25729. me.start();
  25730. });
  25731. }
  25732. else {
  25733. alert(this.constants.labels["deleteError"])
  25734. }
  25735. }
  25736. else {
  25737. this.edgesData.remove(selectedEdges);
  25738. this.nodesData.remove(selectedNodes);
  25739. this._unselectAll();
  25740. this.moving = true;
  25741. this.start();
  25742. }
  25743. }
  25744. else {
  25745. alert(this.constants.labels["deleteClusterError"]);
  25746. }
  25747. }
  25748. };
  25749. /***/ },
  25750. /* 53 */
  25751. /***/ function(module, exports, __webpack_require__) {
  25752. var util = __webpack_require__(1);
  25753. exports._cleanNavigation = function() {
  25754. // clean up previous navigation items
  25755. var wrapper = document.getElementById('network-navigation_wrapper');
  25756. if (wrapper != null) {
  25757. this.containerElement.removeChild(wrapper);
  25758. }
  25759. document.onmouseup = null;
  25760. };
  25761. /**
  25762. * Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation
  25763. * they have a triggerFunction which is called on click. If the position of the navigation controls is dependent
  25764. * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false.
  25765. * This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas.
  25766. *
  25767. * @private
  25768. */
  25769. exports._loadNavigationElements = function() {
  25770. this._cleanNavigation();
  25771. this.navigationDivs = {};
  25772. var navigationDivs = ['up','down','left','right','zoomIn','zoomOut','zoomExtends'];
  25773. var navigationDivActions = ['_moveUp','_moveDown','_moveLeft','_moveRight','_zoomIn','_zoomOut','zoomExtent'];
  25774. this.navigationDivs['wrapper'] = document.createElement('div');
  25775. this.navigationDivs['wrapper'].id = "network-navigation_wrapper";
  25776. this.navigationDivs['wrapper'].style.position = "absolute";
  25777. this.navigationDivs['wrapper'].style.width = this.frame.canvas.clientWidth + "px";
  25778. this.navigationDivs['wrapper'].style.height = this.frame.canvas.clientHeight + "px";
  25779. this.containerElement.insertBefore(this.navigationDivs['wrapper'],this.frame);
  25780. for (var i = 0; i < navigationDivs.length; i++) {
  25781. this.navigationDivs[navigationDivs[i]] = document.createElement('div');
  25782. this.navigationDivs[navigationDivs[i]].id = "network-navigation_" + navigationDivs[i];
  25783. this.navigationDivs[navigationDivs[i]].className = "network-navigation " + navigationDivs[i];
  25784. this.navigationDivs['wrapper'].appendChild(this.navigationDivs[navigationDivs[i]]);
  25785. this.navigationDivs[navigationDivs[i]].onmousedown = this[navigationDivActions[i]].bind(this);
  25786. }
  25787. document.onmouseup = this._stopMovement.bind(this);
  25788. };
  25789. /**
  25790. * this stops all movement induced by the navigation buttons
  25791. *
  25792. * @private
  25793. */
  25794. exports._stopMovement = function() {
  25795. this._xStopMoving();
  25796. this._yStopMoving();
  25797. this._stopZoom();
  25798. };
  25799. /**
  25800. * move the screen up
  25801. * By using the increments, instead of adding a fixed number to the translation, we keep fluent and
  25802. * instant movement. The onKeypress event triggers immediately, then pauses, then triggers frequently
  25803. * To avoid this behaviour, we do the translation in the start loop.
  25804. *
  25805. * @private
  25806. */
  25807. exports._moveUp = function(event) {
  25808. this.yIncrement = this.constants.keyboard.speed.y;
  25809. this.start(); // if there is no node movement, the calculation wont be done
  25810. util.preventDefault(event);
  25811. if (this.navigationDivs) {
  25812. this.navigationDivs['up'].className += " active";
  25813. }
  25814. };
  25815. /**
  25816. * move the screen down
  25817. * @private
  25818. */
  25819. exports._moveDown = function(event) {
  25820. this.yIncrement = -this.constants.keyboard.speed.y;
  25821. this.start(); // if there is no node movement, the calculation wont be done
  25822. util.preventDefault(event);
  25823. if (this.navigationDivs) {
  25824. this.navigationDivs['down'].className += " active";
  25825. }
  25826. };
  25827. /**
  25828. * move the screen left
  25829. * @private
  25830. */
  25831. exports._moveLeft = function(event) {
  25832. this.xIncrement = this.constants.keyboard.speed.x;
  25833. this.start(); // if there is no node movement, the calculation wont be done
  25834. util.preventDefault(event);
  25835. if (this.navigationDivs) {
  25836. this.navigationDivs['left'].className += " active";
  25837. }
  25838. };
  25839. /**
  25840. * move the screen right
  25841. * @private
  25842. */
  25843. exports._moveRight = function(event) {
  25844. this.xIncrement = -this.constants.keyboard.speed.y;
  25845. this.start(); // if there is no node movement, the calculation wont be done
  25846. util.preventDefault(event);
  25847. if (this.navigationDivs) {
  25848. this.navigationDivs['right'].className += " active";
  25849. }
  25850. };
  25851. /**
  25852. * Zoom in, using the same method as the movement.
  25853. * @private
  25854. */
  25855. exports._zoomIn = function(event) {
  25856. this.zoomIncrement = this.constants.keyboard.speed.zoom;
  25857. this.start(); // if there is no node movement, the calculation wont be done
  25858. util.preventDefault(event);
  25859. if (this.navigationDivs) {
  25860. this.navigationDivs['zoomIn'].className += " active";
  25861. }
  25862. };
  25863. /**
  25864. * Zoom out
  25865. * @private
  25866. */
  25867. exports._zoomOut = function() {
  25868. this.zoomIncrement = -this.constants.keyboard.speed.zoom;
  25869. this.start(); // if there is no node movement, the calculation wont be done
  25870. util.preventDefault(event);
  25871. if (this.navigationDivs) {
  25872. this.navigationDivs['zoomOut'].className += " active";
  25873. }
  25874. };
  25875. /**
  25876. * Stop zooming and unhighlight the zoom controls
  25877. * @private
  25878. */
  25879. exports._stopZoom = function() {
  25880. this.zoomIncrement = 0;
  25881. if (this.navigationDivs) {
  25882. this.navigationDivs['zoomIn'].className = this.navigationDivs['zoomIn'].className.replace(" active","");
  25883. this.navigationDivs['zoomOut'].className = this.navigationDivs['zoomOut'].className.replace(" active","");
  25884. }
  25885. };
  25886. /**
  25887. * Stop moving in the Y direction and unHighlight the up and down
  25888. * @private
  25889. */
  25890. exports._yStopMoving = function() {
  25891. this.yIncrement = 0;
  25892. if (this.navigationDivs) {
  25893. this.navigationDivs['up'].className = this.navigationDivs['up'].className.replace(" active","");
  25894. this.navigationDivs['down'].className = this.navigationDivs['down'].className.replace(" active","");
  25895. }
  25896. };
  25897. /**
  25898. * Stop moving in the X direction and unHighlight left and right.
  25899. * @private
  25900. */
  25901. exports._xStopMoving = function() {
  25902. this.xIncrement = 0;
  25903. if (this.navigationDivs) {
  25904. this.navigationDivs['left'].className = this.navigationDivs['left'].className.replace(" active","");
  25905. this.navigationDivs['right'].className = this.navigationDivs['right'].className.replace(" active","");
  25906. }
  25907. };
  25908. /***/ },
  25909. /* 54 */
  25910. /***/ function(module, exports, __webpack_require__) {
  25911. exports._resetLevels = function() {
  25912. for (var nodeId in this.nodes) {
  25913. if (this.nodes.hasOwnProperty(nodeId)) {
  25914. var node = this.nodes[nodeId];
  25915. if (node.preassignedLevel == false) {
  25916. node.level = -1;
  25917. }
  25918. }
  25919. }
  25920. };
  25921. /**
  25922. * This is the main function to layout the nodes in a hierarchical way.
  25923. * It checks if the node details are supplied correctly
  25924. *
  25925. * @private
  25926. */
  25927. exports._setupHierarchicalLayout = function() {
  25928. if (this.constants.hierarchicalLayout.enabled == true && this.nodeIndices.length > 0) {
  25929. if (this.constants.hierarchicalLayout.direction == "RL" || this.constants.hierarchicalLayout.direction == "DU") {
  25930. this.constants.hierarchicalLayout.levelSeparation *= -1;
  25931. }
  25932. else {
  25933. this.constants.hierarchicalLayout.levelSeparation = Math.abs(this.constants.hierarchicalLayout.levelSeparation);
  25934. }
  25935. if (this.constants.hierarchicalLayout.direction == "RL" || this.constants.hierarchicalLayout.direction == "LR") {
  25936. if (this.constants.smoothCurves.enabled == true) {
  25937. this.constants.smoothCurves.type = "vertical";
  25938. }
  25939. }
  25940. else {
  25941. if (this.constants.smoothCurves.enabled == true) {
  25942. this.constants.smoothCurves.type = "horizontal";
  25943. }
  25944. }
  25945. // get the size of the largest hubs and check if the user has defined a level for a node.
  25946. var hubsize = 0;
  25947. var node, nodeId;
  25948. var definedLevel = false;
  25949. var undefinedLevel = false;
  25950. for (nodeId in this.nodes) {
  25951. if (this.nodes.hasOwnProperty(nodeId)) {
  25952. node = this.nodes[nodeId];
  25953. if (node.level != -1) {
  25954. definedLevel = true;
  25955. }
  25956. else {
  25957. undefinedLevel = true;
  25958. }
  25959. if (hubsize < node.edges.length) {
  25960. hubsize = node.edges.length;
  25961. }
  25962. }
  25963. }
  25964. // if the user defined some levels but not all, alert and run without hierarchical layout
  25965. if (undefinedLevel == true && definedLevel == true) {
  25966. alert("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.");
  25967. this.zoomExtent(true,this.constants.clustering.enabled);
  25968. if (!this.constants.clustering.enabled) {
  25969. this.start();
  25970. }
  25971. }
  25972. else {
  25973. // setup the system to use hierarchical method.
  25974. this._changeConstants();
  25975. // define levels if undefined by the users. Based on hubsize
  25976. if (undefinedLevel == true) {
  25977. this._determineLevels(hubsize);
  25978. }
  25979. // check the distribution of the nodes per level.
  25980. var distribution = this._getDistribution();
  25981. // place the nodes on the canvas. This also stablilizes the system.
  25982. this._placeNodesByHierarchy(distribution);
  25983. // start the simulation.
  25984. this.start();
  25985. }
  25986. }
  25987. };
  25988. /**
  25989. * This function places the nodes on the canvas based on the hierarchial distribution.
  25990. *
  25991. * @param {Object} distribution | obtained by the function this._getDistribution()
  25992. * @private
  25993. */
  25994. exports._placeNodesByHierarchy = function(distribution) {
  25995. var nodeId, node;
  25996. // start placing all the level 0 nodes first. Then recursively position their branches.
  25997. for (var level in distribution) {
  25998. if (distribution.hasOwnProperty(level)) {
  25999. for (nodeId in distribution[level].nodes) {
  26000. if (distribution[level].nodes.hasOwnProperty(nodeId)) {
  26001. node = distribution[level].nodes[nodeId];
  26002. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  26003. if (node.xFixed) {
  26004. node.x = distribution[level].minPos;
  26005. node.xFixed = false;
  26006. distribution[level].minPos += distribution[level].nodeSpacing;
  26007. }
  26008. }
  26009. else {
  26010. if (node.yFixed) {
  26011. node.y = distribution[level].minPos;
  26012. node.yFixed = false;
  26013. distribution[level].minPos += distribution[level].nodeSpacing;
  26014. }
  26015. }
  26016. this._placeBranchNodes(node.edges,node.id,distribution,node.level);
  26017. }
  26018. }
  26019. }
  26020. }
  26021. // stabilize the system after positioning. This function calls zoomExtent.
  26022. this._stabilize();
  26023. };
  26024. /**
  26025. * This function get the distribution of levels based on hubsize
  26026. *
  26027. * @returns {Object}
  26028. * @private
  26029. */
  26030. exports._getDistribution = function() {
  26031. var distribution = {};
  26032. var nodeId, node, level;
  26033. // 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.
  26034. // the fix of X is removed after the x value has been set.
  26035. for (nodeId in this.nodes) {
  26036. if (this.nodes.hasOwnProperty(nodeId)) {
  26037. node = this.nodes[nodeId];
  26038. node.xFixed = true;
  26039. node.yFixed = true;
  26040. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  26041. node.y = this.constants.hierarchicalLayout.levelSeparation*node.level;
  26042. }
  26043. else {
  26044. node.x = this.constants.hierarchicalLayout.levelSeparation*node.level;
  26045. }
  26046. if (distribution[node.level] === undefined) {
  26047. distribution[node.level] = {amount: 0, nodes: {}, minPos:0, nodeSpacing:0};
  26048. }
  26049. distribution[node.level].amount += 1;
  26050. distribution[node.level].nodes[nodeId] = node;
  26051. }
  26052. }
  26053. // determine the largest amount of nodes of all levels
  26054. var maxCount = 0;
  26055. for (level in distribution) {
  26056. if (distribution.hasOwnProperty(level)) {
  26057. if (maxCount < distribution[level].amount) {
  26058. maxCount = distribution[level].amount;
  26059. }
  26060. }
  26061. }
  26062. // set the initial position and spacing of each nodes accordingly
  26063. for (level in distribution) {
  26064. if (distribution.hasOwnProperty(level)) {
  26065. distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing;
  26066. distribution[level].nodeSpacing /= (distribution[level].amount + 1);
  26067. distribution[level].minPos = distribution[level].nodeSpacing - (0.5 * (distribution[level].amount + 1) * distribution[level].nodeSpacing);
  26068. }
  26069. }
  26070. return distribution;
  26071. };
  26072. /**
  26073. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  26074. *
  26075. * @param hubsize
  26076. * @private
  26077. */
  26078. exports._determineLevels = function(hubsize) {
  26079. var nodeId, node;
  26080. // determine hubs
  26081. for (nodeId in this.nodes) {
  26082. if (this.nodes.hasOwnProperty(nodeId)) {
  26083. node = this.nodes[nodeId];
  26084. if (node.edges.length == hubsize) {
  26085. node.level = 0;
  26086. }
  26087. }
  26088. }
  26089. // branch from hubs
  26090. for (nodeId in this.nodes) {
  26091. if (this.nodes.hasOwnProperty(nodeId)) {
  26092. node = this.nodes[nodeId];
  26093. if (node.level == 0) {
  26094. this._setLevel(1,node.edges,node.id);
  26095. }
  26096. }
  26097. }
  26098. };
  26099. /**
  26100. * Since hierarchical layout does not support:
  26101. * - smooth curves (based on the physics),
  26102. * - clustering (based on dynamic node counts)
  26103. *
  26104. * We disable both features so there will be no problems.
  26105. *
  26106. * @private
  26107. */
  26108. exports._changeConstants = function() {
  26109. this.constants.clustering.enabled = false;
  26110. this.constants.physics.barnesHut.enabled = false;
  26111. this.constants.physics.hierarchicalRepulsion.enabled = true;
  26112. this._loadSelectedForceSolver();
  26113. if (this.constants.smoothCurves.enabled == true) {
  26114. this.constants.smoothCurves.dynamic = false;
  26115. }
  26116. this._configureSmoothCurves();
  26117. };
  26118. /**
  26119. * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
  26120. * on a X position that ensures there will be no overlap.
  26121. *
  26122. * @param edges
  26123. * @param parentId
  26124. * @param distribution
  26125. * @param parentLevel
  26126. * @private
  26127. */
  26128. exports._placeBranchNodes = function(edges, parentId, distribution, parentLevel) {
  26129. for (var i = 0; i < edges.length; i++) {
  26130. var childNode = null;
  26131. if (edges[i].toId == parentId) {
  26132. childNode = edges[i].from;
  26133. }
  26134. else {
  26135. childNode = edges[i].to;
  26136. }
  26137. // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
  26138. var nodeMoved = false;
  26139. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  26140. if (childNode.xFixed && childNode.level > parentLevel) {
  26141. childNode.xFixed = false;
  26142. childNode.x = distribution[childNode.level].minPos;
  26143. nodeMoved = true;
  26144. }
  26145. }
  26146. else {
  26147. if (childNode.yFixed && childNode.level > parentLevel) {
  26148. childNode.yFixed = false;
  26149. childNode.y = distribution[childNode.level].minPos;
  26150. nodeMoved = true;
  26151. }
  26152. }
  26153. if (nodeMoved == true) {
  26154. distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing;
  26155. if (childNode.edges.length > 1) {
  26156. this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level);
  26157. }
  26158. }
  26159. }
  26160. };
  26161. /**
  26162. * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
  26163. *
  26164. * @param level
  26165. * @param edges
  26166. * @param parentId
  26167. * @private
  26168. */
  26169. exports._setLevel = function(level, edges, parentId) {
  26170. for (var i = 0; i < edges.length; i++) {
  26171. var childNode = null;
  26172. if (edges[i].toId == parentId) {
  26173. childNode = edges[i].from;
  26174. }
  26175. else {
  26176. childNode = edges[i].to;
  26177. }
  26178. if (childNode.level == -1 || childNode.level > level) {
  26179. childNode.level = level;
  26180. if (edges.length > 1) {
  26181. this._setLevel(level+1, childNode.edges, childNode.id);
  26182. }
  26183. }
  26184. }
  26185. };
  26186. /**
  26187. * Unfix nodes
  26188. *
  26189. * @private
  26190. */
  26191. exports._restoreNodes = function() {
  26192. for (var nodeId in this.nodes) {
  26193. if (this.nodes.hasOwnProperty(nodeId)) {
  26194. this.nodes[nodeId].xFixed = false;
  26195. this.nodes[nodeId].yFixed = false;
  26196. }
  26197. }
  26198. };
  26199. /***/ },
  26200. /* 55 */
  26201. /***/ function(module, exports, __webpack_require__) {
  26202. var util = __webpack_require__(1);
  26203. var RepulsionMixin = __webpack_require__(57);
  26204. var HierarchialRepulsionMixin = __webpack_require__(58);
  26205. var BarnesHutMixin = __webpack_require__(59);
  26206. /**
  26207. * Toggling barnes Hut calculation on and off.
  26208. *
  26209. * @private
  26210. */
  26211. exports._toggleBarnesHut = function () {
  26212. this.constants.physics.barnesHut.enabled = !this.constants.physics.barnesHut.enabled;
  26213. this._loadSelectedForceSolver();
  26214. this.moving = true;
  26215. this.start();
  26216. };
  26217. /**
  26218. * This loads the node force solver based on the barnes hut or repulsion algorithm
  26219. *
  26220. * @private
  26221. */
  26222. exports._loadSelectedForceSolver = function () {
  26223. // this overloads the this._calculateNodeForces
  26224. if (this.constants.physics.barnesHut.enabled == true) {
  26225. this._clearMixin(RepulsionMixin);
  26226. this._clearMixin(HierarchialRepulsionMixin);
  26227. this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity;
  26228. this.constants.physics.springLength = this.constants.physics.barnesHut.springLength;
  26229. this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant;
  26230. this.constants.physics.damping = this.constants.physics.barnesHut.damping;
  26231. this._loadMixin(BarnesHutMixin);
  26232. }
  26233. else if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  26234. this._clearMixin(BarnesHutMixin);
  26235. this._clearMixin(RepulsionMixin);
  26236. this.constants.physics.centralGravity = this.constants.physics.hierarchicalRepulsion.centralGravity;
  26237. this.constants.physics.springLength = this.constants.physics.hierarchicalRepulsion.springLength;
  26238. this.constants.physics.springConstant = this.constants.physics.hierarchicalRepulsion.springConstant;
  26239. this.constants.physics.damping = this.constants.physics.hierarchicalRepulsion.damping;
  26240. this._loadMixin(HierarchialRepulsionMixin);
  26241. }
  26242. else {
  26243. this._clearMixin(BarnesHutMixin);
  26244. this._clearMixin(HierarchialRepulsionMixin);
  26245. this.barnesHutTree = undefined;
  26246. this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity;
  26247. this.constants.physics.springLength = this.constants.physics.repulsion.springLength;
  26248. this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant;
  26249. this.constants.physics.damping = this.constants.physics.repulsion.damping;
  26250. this._loadMixin(RepulsionMixin);
  26251. }
  26252. };
  26253. /**
  26254. * Before calculating the forces, we check if we need to cluster to keep up performance and we check
  26255. * if there is more than one node. If it is just one node, we dont calculate anything.
  26256. *
  26257. * @private
  26258. */
  26259. exports._initializeForceCalculation = function () {
  26260. // stop calculation if there is only one node
  26261. if (this.nodeIndices.length == 1) {
  26262. this.nodes[this.nodeIndices[0]]._setForce(0, 0);
  26263. }
  26264. else {
  26265. // if there are too many nodes on screen, we cluster without repositioning
  26266. if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
  26267. this.clusterToFit(this.constants.clustering.reduceToNodes, false);
  26268. }
  26269. // we now start the force calculation
  26270. this._calculateForces();
  26271. }
  26272. };
  26273. /**
  26274. * Calculate the external forces acting on the nodes
  26275. * Forces are caused by: edges, repulsing forces between nodes, gravity
  26276. * @private
  26277. */
  26278. exports._calculateForces = function () {
  26279. // Gravity is required to keep separated groups from floating off
  26280. // the forces are reset to zero in this loop by using _setForce instead
  26281. // of _addForce
  26282. this._calculateGravitationalForces();
  26283. this._calculateNodeForces();
  26284. if (this.constants.physics.springConstant > 0) {
  26285. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  26286. this._calculateSpringForcesWithSupport();
  26287. }
  26288. else {
  26289. if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  26290. this._calculateHierarchicalSpringForces();
  26291. }
  26292. else {
  26293. this._calculateSpringForces();
  26294. }
  26295. }
  26296. }
  26297. };
  26298. /**
  26299. * Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also
  26300. * handled in the calculateForces function. We then use a quadratic curve with the center node as control.
  26301. * This function joins the datanodes and invisible (called support) nodes into one object.
  26302. * We do this so we do not contaminate this.nodes with the support nodes.
  26303. *
  26304. * @private
  26305. */
  26306. exports._updateCalculationNodes = function () {
  26307. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  26308. this.calculationNodes = {};
  26309. this.calculationNodeIndices = [];
  26310. for (var nodeId in this.nodes) {
  26311. if (this.nodes.hasOwnProperty(nodeId)) {
  26312. this.calculationNodes[nodeId] = this.nodes[nodeId];
  26313. }
  26314. }
  26315. var supportNodes = this.sectors['support']['nodes'];
  26316. for (var supportNodeId in supportNodes) {
  26317. if (supportNodes.hasOwnProperty(supportNodeId)) {
  26318. if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) {
  26319. this.calculationNodes[supportNodeId] = supportNodes[supportNodeId];
  26320. }
  26321. else {
  26322. supportNodes[supportNodeId]._setForce(0, 0);
  26323. }
  26324. }
  26325. }
  26326. for (var idx in this.calculationNodes) {
  26327. if (this.calculationNodes.hasOwnProperty(idx)) {
  26328. this.calculationNodeIndices.push(idx);
  26329. }
  26330. }
  26331. }
  26332. else {
  26333. this.calculationNodes = this.nodes;
  26334. this.calculationNodeIndices = this.nodeIndices;
  26335. }
  26336. };
  26337. /**
  26338. * this function applies the central gravity effect to keep groups from floating off
  26339. *
  26340. * @private
  26341. */
  26342. exports._calculateGravitationalForces = function () {
  26343. var dx, dy, distance, node, i;
  26344. var nodes = this.calculationNodes;
  26345. var gravity = this.constants.physics.centralGravity;
  26346. var gravityForce = 0;
  26347. for (i = 0; i < this.calculationNodeIndices.length; i++) {
  26348. node = nodes[this.calculationNodeIndices[i]];
  26349. node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters.
  26350. // gravity does not apply when we are in a pocket sector
  26351. if (this._sector() == "default" && gravity != 0) {
  26352. dx = -node.x;
  26353. dy = -node.y;
  26354. distance = Math.sqrt(dx * dx + dy * dy);
  26355. gravityForce = (distance == 0) ? 0 : (gravity / distance);
  26356. node.fx = dx * gravityForce;
  26357. node.fy = dy * gravityForce;
  26358. }
  26359. else {
  26360. node.fx = 0;
  26361. node.fy = 0;
  26362. }
  26363. }
  26364. };
  26365. /**
  26366. * this function calculates the effects of the springs in the case of unsmooth curves.
  26367. *
  26368. * @private
  26369. */
  26370. exports._calculateSpringForces = function () {
  26371. var edgeLength, edge, edgeId;
  26372. var dx, dy, fx, fy, springForce, distance;
  26373. var edges = this.edges;
  26374. // forces caused by the edges, modelled as springs
  26375. for (edgeId in edges) {
  26376. if (edges.hasOwnProperty(edgeId)) {
  26377. edge = edges[edgeId];
  26378. if (edge.connected) {
  26379. // only calculate forces if nodes are in the same sector
  26380. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  26381. edgeLength = edge.customLength ? edge.length : this.constants.physics.springLength;
  26382. // this implies that the edges between big clusters are longer
  26383. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  26384. dx = (edge.from.x - edge.to.x);
  26385. dy = (edge.from.y - edge.to.y);
  26386. distance = Math.sqrt(dx * dx + dy * dy);
  26387. if (distance == 0) {
  26388. distance = 0.01;
  26389. }
  26390. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  26391. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  26392. fx = dx * springForce;
  26393. fy = dy * springForce;
  26394. edge.from.fx += fx;
  26395. edge.from.fy += fy;
  26396. edge.to.fx -= fx;
  26397. edge.to.fy -= fy;
  26398. }
  26399. }
  26400. }
  26401. }
  26402. };
  26403. /**
  26404. * This function calculates the springforces on the nodes, accounting for the support nodes.
  26405. *
  26406. * @private
  26407. */
  26408. exports._calculateSpringForcesWithSupport = function () {
  26409. var edgeLength, edge, edgeId, combinedClusterSize;
  26410. var edges = this.edges;
  26411. // forces caused by the edges, modelled as springs
  26412. for (edgeId in edges) {
  26413. if (edges.hasOwnProperty(edgeId)) {
  26414. edge = edges[edgeId];
  26415. if (edge.connected) {
  26416. // only calculate forces if nodes are in the same sector
  26417. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  26418. if (edge.via != null) {
  26419. var node1 = edge.to;
  26420. var node2 = edge.via;
  26421. var node3 = edge.from;
  26422. edgeLength = edge.customLength ? edge.length : this.constants.physics.springLength;
  26423. combinedClusterSize = node1.clusterSize + node3.clusterSize - 2;
  26424. // this implies that the edges between big clusters are longer
  26425. edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth;
  26426. this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
  26427. this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
  26428. }
  26429. }
  26430. }
  26431. }
  26432. }
  26433. };
  26434. /**
  26435. * This is the code actually performing the calculation for the function above. It is split out to avoid repetition.
  26436. *
  26437. * @param node1
  26438. * @param node2
  26439. * @param edgeLength
  26440. * @private
  26441. */
  26442. exports._calculateSpringForce = function (node1, node2, edgeLength) {
  26443. var dx, dy, fx, fy, springForce, distance;
  26444. dx = (node1.x - node2.x);
  26445. dy = (node1.y - node2.y);
  26446. distance = Math.sqrt(dx * dx + dy * dy);
  26447. if (distance == 0) {
  26448. distance = 0.01;
  26449. }
  26450. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  26451. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  26452. fx = dx * springForce;
  26453. fy = dy * springForce;
  26454. node1.fx += fx;
  26455. node1.fy += fy;
  26456. node2.fx -= fx;
  26457. node2.fy -= fy;
  26458. };
  26459. /**
  26460. * Load the HTML for the physics config and bind it
  26461. * @private
  26462. */
  26463. exports._loadPhysicsConfiguration = function () {
  26464. if (this.physicsConfiguration === undefined) {
  26465. this.backupConstants = {};
  26466. util.deepExtend(this.backupConstants,this.constants);
  26467. var hierarchicalLayoutDirections = ["LR", "RL", "UD", "DU"];
  26468. this.physicsConfiguration = document.createElement('div');
  26469. this.physicsConfiguration.className = "PhysicsConfiguration";
  26470. this.physicsConfiguration.innerHTML = '' +
  26471. '<table><tr><td><b>Simulation Mode:</b></td></tr>' +
  26472. '<tr>' +
  26473. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod1" value="BH" checked="checked">Barnes Hut</td>' +
  26474. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod2" value="R">Repulsion</td>' +
  26475. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod3" value="H">Hierarchical</td>' +
  26476. '</tr>' +
  26477. '</table>' +
  26478. '<table id="graph_BH_table" style="display:none">' +
  26479. '<tr><td><b>Barnes Hut</b></td></tr>' +
  26480. '<tr>' +
  26481. '<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>' +
  26482. '</tr>' +
  26483. '<tr>' +
  26484. '<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>' +
  26485. '</tr>' +
  26486. '<tr>' +
  26487. '<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>' +
  26488. '</tr>' +
  26489. '<tr>' +
  26490. '<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>' +
  26491. '</tr>' +
  26492. '<tr>' +
  26493. '<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>' +
  26494. '</tr>' +
  26495. '</table>' +
  26496. '<table id="graph_R_table" style="display:none">' +
  26497. '<tr><td><b>Repulsion</b></td></tr>' +
  26498. '<tr>' +
  26499. '<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>' +
  26500. '</tr>' +
  26501. '<tr>' +
  26502. '<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>' +
  26503. '</tr>' +
  26504. '<tr>' +
  26505. '<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>' +
  26506. '</tr>' +
  26507. '<tr>' +
  26508. '<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>' +
  26509. '</tr>' +
  26510. '<tr>' +
  26511. '<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>' +
  26512. '</tr>' +
  26513. '</table>' +
  26514. '<table id="graph_H_table" style="display:none">' +
  26515. '<tr><td width="150"><b>Hierarchical</b></td></tr>' +
  26516. '<tr>' +
  26517. '<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>' +
  26518. '</tr>' +
  26519. '<tr>' +
  26520. '<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>' +
  26521. '</tr>' +
  26522. '<tr>' +
  26523. '<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>' +
  26524. '</tr>' +
  26525. '<tr>' +
  26526. '<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>' +
  26527. '</tr>' +
  26528. '<tr>' +
  26529. '<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>' +
  26530. '</tr>' +
  26531. '<tr>' +
  26532. '<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>' +
  26533. '</tr>' +
  26534. '<tr>' +
  26535. '<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>' +
  26536. '</tr>' +
  26537. '<tr>' +
  26538. '<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>' +
  26539. '</tr>' +
  26540. '</table>' +
  26541. '<table><tr><td><b>Options:</b></td></tr>' +
  26542. '<tr>' +
  26543. '<td width="180px"><input type="button" id="graph_toggleSmooth" value="Toggle smoothCurves" style="width:150px"></td>' +
  26544. '<td width="180px"><input type="button" id="graph_repositionNodes" value="Reinitialize" style="width:150px"></td>' +
  26545. '<td width="180px"><input type="button" id="graph_generateOptions" value="Generate Options" style="width:150px"></td>' +
  26546. '</tr>' +
  26547. '</table>'
  26548. this.containerElement.parentElement.insertBefore(this.physicsConfiguration, this.containerElement);
  26549. this.optionsDiv = document.createElement("div");
  26550. this.optionsDiv.style.fontSize = "14px";
  26551. this.optionsDiv.style.fontFamily = "verdana";
  26552. this.containerElement.parentElement.insertBefore(this.optionsDiv, this.containerElement);
  26553. var rangeElement;
  26554. rangeElement = document.getElementById('graph_BH_gc');
  26555. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_gc', -1, "physics_barnesHut_gravitationalConstant");
  26556. rangeElement = document.getElementById('graph_BH_cg');
  26557. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_cg', 1, "physics_centralGravity");
  26558. rangeElement = document.getElementById('graph_BH_sc');
  26559. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sc', 1, "physics_springConstant");
  26560. rangeElement = document.getElementById('graph_BH_sl');
  26561. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sl', 1, "physics_springLength");
  26562. rangeElement = document.getElementById('graph_BH_damp');
  26563. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_damp', 1, "physics_damping");
  26564. rangeElement = document.getElementById('graph_R_nd');
  26565. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_nd', 1, "physics_repulsion_nodeDistance");
  26566. rangeElement = document.getElementById('graph_R_cg');
  26567. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_cg', 1, "physics_centralGravity");
  26568. rangeElement = document.getElementById('graph_R_sc');
  26569. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sc', 1, "physics_springConstant");
  26570. rangeElement = document.getElementById('graph_R_sl');
  26571. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sl', 1, "physics_springLength");
  26572. rangeElement = document.getElementById('graph_R_damp');
  26573. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_damp', 1, "physics_damping");
  26574. rangeElement = document.getElementById('graph_H_nd');
  26575. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  26576. rangeElement = document.getElementById('graph_H_cg');
  26577. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_cg', 1, "physics_centralGravity");
  26578. rangeElement = document.getElementById('graph_H_sc');
  26579. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sc', 1, "physics_springConstant");
  26580. rangeElement = document.getElementById('graph_H_sl');
  26581. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sl', 1, "physics_springLength");
  26582. rangeElement = document.getElementById('graph_H_damp');
  26583. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_damp', 1, "physics_damping");
  26584. rangeElement = document.getElementById('graph_H_direction');
  26585. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_direction', hierarchicalLayoutDirections, "hierarchicalLayout_direction");
  26586. rangeElement = document.getElementById('graph_H_levsep');
  26587. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_levsep', 1, "hierarchicalLayout_levelSeparation");
  26588. rangeElement = document.getElementById('graph_H_nspac');
  26589. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nspac', 1, "hierarchicalLayout_nodeSpacing");
  26590. var radioButton1 = document.getElementById("graph_physicsMethod1");
  26591. var radioButton2 = document.getElementById("graph_physicsMethod2");
  26592. var radioButton3 = document.getElementById("graph_physicsMethod3");
  26593. radioButton2.checked = true;
  26594. if (this.constants.physics.barnesHut.enabled) {
  26595. radioButton1.checked = true;
  26596. }
  26597. if (this.constants.hierarchicalLayout.enabled) {
  26598. radioButton3.checked = true;
  26599. }
  26600. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  26601. var graph_repositionNodes = document.getElementById("graph_repositionNodes");
  26602. var graph_generateOptions = document.getElementById("graph_generateOptions");
  26603. graph_toggleSmooth.onclick = graphToggleSmoothCurves.bind(this);
  26604. graph_repositionNodes.onclick = graphRepositionNodes.bind(this);
  26605. graph_generateOptions.onclick = graphGenerateOptions.bind(this);
  26606. if (this.constants.smoothCurves == true && this.constants.dynamicSmoothCurves == false) {
  26607. graph_toggleSmooth.style.background = "#A4FF56";
  26608. }
  26609. else {
  26610. graph_toggleSmooth.style.background = "#FF8532";
  26611. }
  26612. switchConfigurations.apply(this);
  26613. radioButton1.onchange = switchConfigurations.bind(this);
  26614. radioButton2.onchange = switchConfigurations.bind(this);
  26615. radioButton3.onchange = switchConfigurations.bind(this);
  26616. }
  26617. };
  26618. /**
  26619. * This overwrites the this.constants.
  26620. *
  26621. * @param constantsVariableName
  26622. * @param value
  26623. * @private
  26624. */
  26625. exports._overWriteGraphConstants = function (constantsVariableName, value) {
  26626. var nameArray = constantsVariableName.split("_");
  26627. if (nameArray.length == 1) {
  26628. this.constants[nameArray[0]] = value;
  26629. }
  26630. else if (nameArray.length == 2) {
  26631. this.constants[nameArray[0]][nameArray[1]] = value;
  26632. }
  26633. else if (nameArray.length == 3) {
  26634. this.constants[nameArray[0]][nameArray[1]][nameArray[2]] = value;
  26635. }
  26636. };
  26637. /**
  26638. * this function is bound to the toggle smooth curves button. That is also why it is not in the prototype.
  26639. */
  26640. function graphToggleSmoothCurves () {
  26641. this.constants.smoothCurves.enabled = !this.constants.smoothCurves.enabled;
  26642. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  26643. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  26644. else {graph_toggleSmooth.style.background = "#FF8532";}
  26645. this._configureSmoothCurves(false);
  26646. }
  26647. /**
  26648. * this function is used to scramble the nodes
  26649. *
  26650. */
  26651. function graphRepositionNodes () {
  26652. for (var nodeId in this.calculationNodes) {
  26653. if (this.calculationNodes.hasOwnProperty(nodeId)) {
  26654. this.calculationNodes[nodeId].vx = 0; this.calculationNodes[nodeId].vy = 0;
  26655. this.calculationNodes[nodeId].fx = 0; this.calculationNodes[nodeId].fy = 0;
  26656. }
  26657. }
  26658. if (this.constants.hierarchicalLayout.enabled == true) {
  26659. this._setupHierarchicalLayout();
  26660. showValueOfRange.call(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  26661. showValueOfRange.call(this, 'graph_H_cg', 1, "physics_centralGravity");
  26662. showValueOfRange.call(this, 'graph_H_sc', 1, "physics_springConstant");
  26663. showValueOfRange.call(this, 'graph_H_sl', 1, "physics_springLength");
  26664. showValueOfRange.call(this, 'graph_H_damp', 1, "physics_damping");
  26665. }
  26666. else {
  26667. this.repositionNodes();
  26668. }
  26669. this.moving = true;
  26670. this.start();
  26671. }
  26672. /**
  26673. * this is used to generate an options file from the playing with physics system.
  26674. */
  26675. function graphGenerateOptions () {
  26676. var options = "No options are required, default values used.";
  26677. var optionsSpecific = [];
  26678. var radioButton1 = document.getElementById("graph_physicsMethod1");
  26679. var radioButton2 = document.getElementById("graph_physicsMethod2");
  26680. if (radioButton1.checked == true) {
  26681. if (this.constants.physics.barnesHut.gravitationalConstant != this.backupConstants.physics.barnesHut.gravitationalConstant) {optionsSpecific.push("gravitationalConstant: " + this.constants.physics.barnesHut.gravitationalConstant);}
  26682. if (this.constants.physics.centralGravity != this.backupConstants.physics.barnesHut.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  26683. if (this.constants.physics.springLength != this.backupConstants.physics.barnesHut.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  26684. if (this.constants.physics.springConstant != this.backupConstants.physics.barnesHut.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  26685. if (this.constants.physics.damping != this.backupConstants.physics.barnesHut.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  26686. if (optionsSpecific.length != 0) {
  26687. options = "var options = {";
  26688. options += "physics: {barnesHut: {";
  26689. for (var i = 0; i < optionsSpecific.length; i++) {
  26690. options += optionsSpecific[i];
  26691. if (i < optionsSpecific.length - 1) {
  26692. options += ", "
  26693. }
  26694. }
  26695. options += '}}'
  26696. }
  26697. if (this.constants.smoothCurves.enabled != this.backupConstants.smoothCurves.enabled) {
  26698. if (optionsSpecific.length == 0) {options = "var options = {";}
  26699. else {options += ", "}
  26700. options += "smoothCurves: " + this.constants.smoothCurves.enabled;
  26701. }
  26702. if (options != "No options are required, default values used.") {
  26703. options += '};'
  26704. }
  26705. }
  26706. else if (radioButton2.checked == true) {
  26707. options = "var options = {";
  26708. options += "physics: {barnesHut: {enabled: false}";
  26709. if (this.constants.physics.repulsion.nodeDistance != this.backupConstants.physics.repulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.repulsion.nodeDistance);}
  26710. if (this.constants.physics.centralGravity != this.backupConstants.physics.repulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  26711. if (this.constants.physics.springLength != this.backupConstants.physics.repulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  26712. if (this.constants.physics.springConstant != this.backupConstants.physics.repulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  26713. if (this.constants.physics.damping != this.backupConstants.physics.repulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  26714. if (optionsSpecific.length != 0) {
  26715. options += ", repulsion: {";
  26716. for (var i = 0; i < optionsSpecific.length; i++) {
  26717. options += optionsSpecific[i];
  26718. if (i < optionsSpecific.length - 1) {
  26719. options += ", "
  26720. }
  26721. }
  26722. options += '}}'
  26723. }
  26724. if (optionsSpecific.length == 0) {options += "}"}
  26725. if (this.constants.smoothCurves != this.backupConstants.smoothCurves) {
  26726. options += ", smoothCurves: " + this.constants.smoothCurves;
  26727. }
  26728. options += '};'
  26729. }
  26730. else {
  26731. options = "var options = {";
  26732. if (this.constants.physics.hierarchicalRepulsion.nodeDistance != this.backupConstants.physics.hierarchicalRepulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.hierarchicalRepulsion.nodeDistance);}
  26733. if (this.constants.physics.centralGravity != this.backupConstants.physics.hierarchicalRepulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  26734. if (this.constants.physics.springLength != this.backupConstants.physics.hierarchicalRepulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  26735. if (this.constants.physics.springConstant != this.backupConstants.physics.hierarchicalRepulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  26736. if (this.constants.physics.damping != this.backupConstants.physics.hierarchicalRepulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  26737. if (optionsSpecific.length != 0) {
  26738. options += "physics: {hierarchicalRepulsion: {";
  26739. for (var i = 0; i < optionsSpecific.length; i++) {
  26740. options += optionsSpecific[i];
  26741. if (i < optionsSpecific.length - 1) {
  26742. options += ", ";
  26743. }
  26744. }
  26745. options += '}},';
  26746. }
  26747. options += 'hierarchicalLayout: {';
  26748. optionsSpecific = [];
  26749. if (this.constants.hierarchicalLayout.direction != this.backupConstants.hierarchicalLayout.direction) {optionsSpecific.push("direction: " + this.constants.hierarchicalLayout.direction);}
  26750. if (Math.abs(this.constants.hierarchicalLayout.levelSeparation) != this.backupConstants.hierarchicalLayout.levelSeparation) {optionsSpecific.push("levelSeparation: " + this.constants.hierarchicalLayout.levelSeparation);}
  26751. if (this.constants.hierarchicalLayout.nodeSpacing != this.backupConstants.hierarchicalLayout.nodeSpacing) {optionsSpecific.push("nodeSpacing: " + this.constants.hierarchicalLayout.nodeSpacing);}
  26752. if (optionsSpecific.length != 0) {
  26753. for (var i = 0; i < optionsSpecific.length; i++) {
  26754. options += optionsSpecific[i];
  26755. if (i < optionsSpecific.length - 1) {
  26756. options += ", "
  26757. }
  26758. }
  26759. options += '}'
  26760. }
  26761. else {
  26762. options += "enabled:true}";
  26763. }
  26764. options += '};'
  26765. }
  26766. this.optionsDiv.innerHTML = options;
  26767. }
  26768. /**
  26769. * this is used to switch between barnesHut, repulsion and hierarchical.
  26770. *
  26771. */
  26772. function switchConfigurations () {
  26773. var ids = ["graph_BH_table", "graph_R_table", "graph_H_table"];
  26774. var radioButton = document.querySelector('input[name="graph_physicsMethod"]:checked').value;
  26775. var tableId = "graph_" + radioButton + "_table";
  26776. var table = document.getElementById(tableId);
  26777. table.style.display = "block";
  26778. for (var i = 0; i < ids.length; i++) {
  26779. if (ids[i] != tableId) {
  26780. table = document.getElementById(ids[i]);
  26781. table.style.display = "none";
  26782. }
  26783. }
  26784. this._restoreNodes();
  26785. if (radioButton == "R") {
  26786. this.constants.hierarchicalLayout.enabled = false;
  26787. this.constants.physics.hierarchicalRepulsion.enabled = false;
  26788. this.constants.physics.barnesHut.enabled = false;
  26789. }
  26790. else if (radioButton == "H") {
  26791. if (this.constants.hierarchicalLayout.enabled == false) {
  26792. this.constants.hierarchicalLayout.enabled = true;
  26793. this.constants.physics.hierarchicalRepulsion.enabled = true;
  26794. this.constants.physics.barnesHut.enabled = false;
  26795. this.constants.smoothCurves.enabled = false;
  26796. this._setupHierarchicalLayout();
  26797. }
  26798. }
  26799. else {
  26800. this.constants.hierarchicalLayout.enabled = false;
  26801. this.constants.physics.hierarchicalRepulsion.enabled = false;
  26802. this.constants.physics.barnesHut.enabled = true;
  26803. }
  26804. this._loadSelectedForceSolver();
  26805. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  26806. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  26807. else {graph_toggleSmooth.style.background = "#FF8532";}
  26808. this.moving = true;
  26809. this.start();
  26810. }
  26811. /**
  26812. * this generates the ranges depending on the iniital values.
  26813. *
  26814. * @param id
  26815. * @param map
  26816. * @param constantsVariableName
  26817. */
  26818. function showValueOfRange (id,map,constantsVariableName) {
  26819. var valueId = id + "_value";
  26820. var rangeValue = document.getElementById(id).value;
  26821. if (map instanceof Array) {
  26822. document.getElementById(valueId).value = map[parseInt(rangeValue)];
  26823. this._overWriteGraphConstants(constantsVariableName,map[parseInt(rangeValue)]);
  26824. }
  26825. else {
  26826. document.getElementById(valueId).value = parseInt(map) * parseFloat(rangeValue);
  26827. this._overWriteGraphConstants(constantsVariableName, parseInt(map) * parseFloat(rangeValue));
  26828. }
  26829. if (constantsVariableName == "hierarchicalLayout_direction" ||
  26830. constantsVariableName == "hierarchicalLayout_levelSeparation" ||
  26831. constantsVariableName == "hierarchicalLayout_nodeSpacing") {
  26832. this._setupHierarchicalLayout();
  26833. }
  26834. this.moving = true;
  26835. this.start();
  26836. }
  26837. /***/ },
  26838. /* 56 */
  26839. /***/ function(module, exports, __webpack_require__) {
  26840. var map = {};
  26841. function webpackContext(req) {
  26842. return __webpack_require__(webpackContextResolve(req));
  26843. };
  26844. function webpackContextResolve(req) {
  26845. return map[req] || (function() { throw new Error("Cannot find module '" + req + "'.") }());
  26846. };
  26847. webpackContext.keys = function webpackContextKeys() {
  26848. return Object.keys(map);
  26849. };
  26850. webpackContext.resolve = webpackContextResolve;
  26851. module.exports = webpackContext;
  26852. /***/ },
  26853. /* 57 */
  26854. /***/ function(module, exports, __webpack_require__) {
  26855. /**
  26856. * Calculate the forces the nodes apply on each other based on a repulsion field.
  26857. * This field is linearly approximated.
  26858. *
  26859. * @private
  26860. */
  26861. exports._calculateNodeForces = function () {
  26862. var dx, dy, angle, distance, fx, fy, combinedClusterSize,
  26863. repulsingForce, node1, node2, i, j;
  26864. var nodes = this.calculationNodes;
  26865. var nodeIndices = this.calculationNodeIndices;
  26866. // approximation constants
  26867. var a_base = -2 / 3;
  26868. var b = 4 / 3;
  26869. // repulsing forces between nodes
  26870. var nodeDistance = this.constants.physics.repulsion.nodeDistance;
  26871. var minimumDistance = nodeDistance;
  26872. // we loop from i over all but the last entree in the array
  26873. // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  26874. for (i = 0; i < nodeIndices.length - 1; i++) {
  26875. node1 = nodes[nodeIndices[i]];
  26876. for (j = i + 1; j < nodeIndices.length; j++) {
  26877. node2 = nodes[nodeIndices[j]];
  26878. combinedClusterSize = node1.clusterSize + node2.clusterSize - 2;
  26879. dx = node2.x - node1.x;
  26880. dy = node2.y - node1.y;
  26881. distance = Math.sqrt(dx * dx + dy * dy);
  26882. minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification));
  26883. var a = a_base / minimumDistance;
  26884. if (distance < 2 * minimumDistance) {
  26885. if (distance < 0.5 * minimumDistance) {
  26886. repulsingForce = 1.0;
  26887. }
  26888. else {
  26889. repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness))
  26890. }
  26891. // amplify the repulsion for clusters.
  26892. repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification;
  26893. repulsingForce = repulsingForce / distance;
  26894. fx = dx * repulsingForce;
  26895. fy = dy * repulsingForce;
  26896. node1.fx -= fx;
  26897. node1.fy -= fy;
  26898. node2.fx += fx;
  26899. node2.fy += fy;
  26900. }
  26901. }
  26902. }
  26903. };
  26904. /***/ },
  26905. /* 58 */
  26906. /***/ function(module, exports, __webpack_require__) {
  26907. /**
  26908. * Calculate the forces the nodes apply on eachother based on a repulsion field.
  26909. * This field is linearly approximated.
  26910. *
  26911. * @private
  26912. */
  26913. exports._calculateNodeForces = function () {
  26914. var dx, dy, distance, fx, fy,
  26915. repulsingForce, node1, node2, i, j;
  26916. var nodes = this.calculationNodes;
  26917. var nodeIndices = this.calculationNodeIndices;
  26918. // repulsing forces between nodes
  26919. var nodeDistance = this.constants.physics.hierarchicalRepulsion.nodeDistance;
  26920. // we loop from i over all but the last entree in the array
  26921. // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  26922. for (i = 0; i < nodeIndices.length - 1; i++) {
  26923. node1 = nodes[nodeIndices[i]];
  26924. for (j = i + 1; j < nodeIndices.length; j++) {
  26925. node2 = nodes[nodeIndices[j]];
  26926. // nodes only affect nodes on their level
  26927. if (node1.level == node2.level) {
  26928. dx = node2.x - node1.x;
  26929. dy = node2.y - node1.y;
  26930. distance = Math.sqrt(dx * dx + dy * dy);
  26931. var steepness = 0.05;
  26932. if (distance < nodeDistance) {
  26933. repulsingForce = -Math.pow(steepness*distance,2) + Math.pow(steepness*nodeDistance,2);
  26934. }
  26935. else {
  26936. repulsingForce = 0;
  26937. }
  26938. // normalize force with
  26939. if (distance == 0) {
  26940. distance = 0.01;
  26941. }
  26942. else {
  26943. repulsingForce = repulsingForce / distance;
  26944. }
  26945. fx = dx * repulsingForce;
  26946. fy = dy * repulsingForce;
  26947. node1.fx -= fx;
  26948. node1.fy -= fy;
  26949. node2.fx += fx;
  26950. node2.fy += fy;
  26951. }
  26952. }
  26953. }
  26954. };
  26955. /**
  26956. * this function calculates the effects of the springs in the case of unsmooth curves.
  26957. *
  26958. * @private
  26959. */
  26960. exports._calculateHierarchicalSpringForces = function () {
  26961. var edgeLength, edge, edgeId;
  26962. var dx, dy, fx, fy, springForce, distance;
  26963. var edges = this.edges;
  26964. var nodes = this.calculationNodes;
  26965. var nodeIndices = this.calculationNodeIndices;
  26966. for (var i = 0; i < nodeIndices.length; i++) {
  26967. var node1 = nodes[nodeIndices[i]];
  26968. node1.springFx = 0;
  26969. node1.springFy = 0;
  26970. }
  26971. // forces caused by the edges, modelled as springs
  26972. for (edgeId in edges) {
  26973. if (edges.hasOwnProperty(edgeId)) {
  26974. edge = edges[edgeId];
  26975. if (edge.connected) {
  26976. // only calculate forces if nodes are in the same sector
  26977. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  26978. edgeLength = edge.customLength ? edge.length : this.constants.physics.springLength;
  26979. // this implies that the edges between big clusters are longer
  26980. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  26981. dx = (edge.from.x - edge.to.x);
  26982. dy = (edge.from.y - edge.to.y);
  26983. distance = Math.sqrt(dx * dx + dy * dy);
  26984. if (distance == 0) {
  26985. distance = 0.01;
  26986. }
  26987. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  26988. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  26989. fx = dx * springForce;
  26990. fy = dy * springForce;
  26991. if (edge.to.level != edge.from.level) {
  26992. edge.to.springFx -= fx;
  26993. edge.to.springFy -= fy;
  26994. edge.from.springFx += fx;
  26995. edge.from.springFy += fy;
  26996. }
  26997. else {
  26998. var factor = 0.5;
  26999. edge.to.fx -= factor*fx;
  27000. edge.to.fy -= factor*fy;
  27001. edge.from.fx += factor*fx;
  27002. edge.from.fy += factor*fy;
  27003. }
  27004. }
  27005. }
  27006. }
  27007. }
  27008. // normalize spring forces
  27009. var springForce = 1;
  27010. var springFx, springFy;
  27011. for (i = 0; i < nodeIndices.length; i++) {
  27012. var node = nodes[nodeIndices[i]];
  27013. springFx = Math.min(springForce,Math.max(-springForce,node.springFx));
  27014. springFy = Math.min(springForce,Math.max(-springForce,node.springFy));
  27015. node.fx += springFx;
  27016. node.fy += springFy;
  27017. }
  27018. // retain energy balance
  27019. var totalFx = 0;
  27020. var totalFy = 0;
  27021. for (i = 0; i < nodeIndices.length; i++) {
  27022. var node = nodes[nodeIndices[i]];
  27023. totalFx += node.fx;
  27024. totalFy += node.fy;
  27025. }
  27026. var correctionFx = totalFx / nodeIndices.length;
  27027. var correctionFy = totalFy / nodeIndices.length;
  27028. for (i = 0; i < nodeIndices.length; i++) {
  27029. var node = nodes[nodeIndices[i]];
  27030. node.fx -= correctionFx;
  27031. node.fy -= correctionFy;
  27032. }
  27033. };
  27034. /***/ },
  27035. /* 59 */
  27036. /***/ function(module, exports, __webpack_require__) {
  27037. /**
  27038. * This function calculates the forces the nodes apply on eachother based on a gravitational model.
  27039. * The Barnes Hut method is used to speed up this N-body simulation.
  27040. *
  27041. * @private
  27042. */
  27043. exports._calculateNodeForces = function() {
  27044. if (this.constants.physics.barnesHut.gravitationalConstant != 0) {
  27045. var node;
  27046. var nodes = this.calculationNodes;
  27047. var nodeIndices = this.calculationNodeIndices;
  27048. var nodeCount = nodeIndices.length;
  27049. this._formBarnesHutTree(nodes,nodeIndices);
  27050. var barnesHutTree = this.barnesHutTree;
  27051. // place the nodes one by one recursively
  27052. for (var i = 0; i < nodeCount; i++) {
  27053. node = nodes[nodeIndices[i]];
  27054. // starting with root is irrelevant, it never passes the BarnesHut condition
  27055. this._getForceContribution(barnesHutTree.root.children.NW,node);
  27056. this._getForceContribution(barnesHutTree.root.children.NE,node);
  27057. this._getForceContribution(barnesHutTree.root.children.SW,node);
  27058. this._getForceContribution(barnesHutTree.root.children.SE,node);
  27059. }
  27060. }
  27061. };
  27062. /**
  27063. * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass.
  27064. * If a region contains a single node, we check if it is not itself, then we apply the force.
  27065. *
  27066. * @param parentBranch
  27067. * @param node
  27068. * @private
  27069. */
  27070. exports._getForceContribution = function(parentBranch,node) {
  27071. // we get no force contribution from an empty region
  27072. if (parentBranch.childrenCount > 0) {
  27073. var dx,dy,distance;
  27074. // get the distance from the center of mass to the node.
  27075. dx = parentBranch.centerOfMass.x - node.x;
  27076. dy = parentBranch.centerOfMass.y - node.y;
  27077. distance = Math.sqrt(dx * dx + dy * dy);
  27078. // BarnesHut condition
  27079. // original condition : s/d < theta = passed === d/s > 1/theta = passed
  27080. // calcSize = 1/s --> d * 1/s > 1/theta = passed
  27081. if (distance * parentBranch.calcSize > this.constants.physics.barnesHut.theta) {
  27082. // duplicate code to reduce function calls to speed up program
  27083. if (distance == 0) {
  27084. distance = 0.1*Math.random();
  27085. dx = distance;
  27086. }
  27087. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.mass / (distance * distance * distance);
  27088. var fx = dx * gravityForce;
  27089. var fy = dy * gravityForce;
  27090. node.fx += fx;
  27091. node.fy += fy;
  27092. }
  27093. else {
  27094. // Did not pass the condition, go into children if available
  27095. if (parentBranch.childrenCount == 4) {
  27096. this._getForceContribution(parentBranch.children.NW,node);
  27097. this._getForceContribution(parentBranch.children.NE,node);
  27098. this._getForceContribution(parentBranch.children.SW,node);
  27099. this._getForceContribution(parentBranch.children.SE,node);
  27100. }
  27101. else { // parentBranch must have only one node, if it was empty we wouldnt be here
  27102. if (parentBranch.children.data.id != node.id) { // if it is not self
  27103. // duplicate code to reduce function calls to speed up program
  27104. if (distance == 0) {
  27105. distance = 0.5*Math.random();
  27106. dx = distance;
  27107. }
  27108. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.mass / (distance * distance * distance);
  27109. var fx = dx * gravityForce;
  27110. var fy = dy * gravityForce;
  27111. node.fx += fx;
  27112. node.fy += fy;
  27113. }
  27114. }
  27115. }
  27116. }
  27117. };
  27118. /**
  27119. * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes.
  27120. *
  27121. * @param nodes
  27122. * @param nodeIndices
  27123. * @private
  27124. */
  27125. exports._formBarnesHutTree = function(nodes,nodeIndices) {
  27126. var node;
  27127. var nodeCount = nodeIndices.length;
  27128. var minX = Number.MAX_VALUE,
  27129. minY = Number.MAX_VALUE,
  27130. maxX =-Number.MAX_VALUE,
  27131. maxY =-Number.MAX_VALUE;
  27132. // get the range of the nodes
  27133. for (var i = 0; i < nodeCount; i++) {
  27134. var x = nodes[nodeIndices[i]].x;
  27135. var y = nodes[nodeIndices[i]].y;
  27136. if (x < minX) { minX = x; }
  27137. if (x > maxX) { maxX = x; }
  27138. if (y < minY) { minY = y; }
  27139. if (y > maxY) { maxY = y; }
  27140. }
  27141. // make the range a square
  27142. var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
  27143. if (sizeDiff > 0) {minY -= 0.5 * sizeDiff; maxY += 0.5 * sizeDiff;} // xSize > ySize
  27144. else {minX += 0.5 * sizeDiff; maxX -= 0.5 * sizeDiff;} // xSize < ySize
  27145. var minimumTreeSize = 1e-5;
  27146. var rootSize = Math.max(minimumTreeSize,Math.abs(maxX - minX));
  27147. var halfRootSize = 0.5 * rootSize;
  27148. var centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY);
  27149. // construct the barnesHutTree
  27150. var barnesHutTree = {
  27151. root:{
  27152. centerOfMass: {x:0, y:0},
  27153. mass:0,
  27154. range: {
  27155. minX: centerX-halfRootSize,maxX:centerX+halfRootSize,
  27156. minY: centerY-halfRootSize,maxY:centerY+halfRootSize
  27157. },
  27158. size: rootSize,
  27159. calcSize: 1 / rootSize,
  27160. children: { data:null},
  27161. maxWidth: 0,
  27162. level: 0,
  27163. childrenCount: 4
  27164. }
  27165. };
  27166. this._splitBranch(barnesHutTree.root);
  27167. // place the nodes one by one recursively
  27168. for (i = 0; i < nodeCount; i++) {
  27169. node = nodes[nodeIndices[i]];
  27170. this._placeInTree(barnesHutTree.root,node);
  27171. }
  27172. // make global
  27173. this.barnesHutTree = barnesHutTree
  27174. };
  27175. /**
  27176. * this updates the mass of a branch. this is increased by adding a node.
  27177. *
  27178. * @param parentBranch
  27179. * @param node
  27180. * @private
  27181. */
  27182. exports._updateBranchMass = function(parentBranch, node) {
  27183. var totalMass = parentBranch.mass + node.mass;
  27184. var totalMassInv = 1/totalMass;
  27185. parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.mass;
  27186. parentBranch.centerOfMass.x *= totalMassInv;
  27187. parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.mass;
  27188. parentBranch.centerOfMass.y *= totalMassInv;
  27189. parentBranch.mass = totalMass;
  27190. var biggestSize = Math.max(Math.max(node.height,node.radius),node.width);
  27191. parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth;
  27192. };
  27193. /**
  27194. * determine in which branch the node will be placed.
  27195. *
  27196. * @param parentBranch
  27197. * @param node
  27198. * @param skipMassUpdate
  27199. * @private
  27200. */
  27201. exports._placeInTree = function(parentBranch,node,skipMassUpdate) {
  27202. if (skipMassUpdate != true || skipMassUpdate === undefined) {
  27203. // update the mass of the branch.
  27204. this._updateBranchMass(parentBranch,node);
  27205. }
  27206. if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW
  27207. if (parentBranch.children.NW.range.maxY > node.y) { // in NW
  27208. this._placeInRegion(parentBranch,node,"NW");
  27209. }
  27210. else { // in SW
  27211. this._placeInRegion(parentBranch,node,"SW");
  27212. }
  27213. }
  27214. else { // in NE or SE
  27215. if (parentBranch.children.NW.range.maxY > node.y) { // in NE
  27216. this._placeInRegion(parentBranch,node,"NE");
  27217. }
  27218. else { // in SE
  27219. this._placeInRegion(parentBranch,node,"SE");
  27220. }
  27221. }
  27222. };
  27223. /**
  27224. * actually place the node in a region (or branch)
  27225. *
  27226. * @param parentBranch
  27227. * @param node
  27228. * @param region
  27229. * @private
  27230. */
  27231. exports._placeInRegion = function(parentBranch,node,region) {
  27232. switch (parentBranch.children[region].childrenCount) {
  27233. case 0: // place node here
  27234. parentBranch.children[region].children.data = node;
  27235. parentBranch.children[region].childrenCount = 1;
  27236. this._updateBranchMass(parentBranch.children[region],node);
  27237. break;
  27238. case 1: // convert into children
  27239. // if there are two nodes exactly overlapping (on init, on opening of cluster etc.)
  27240. // we move one node a pixel and we do not put it in the tree.
  27241. if (parentBranch.children[region].children.data.x == node.x &&
  27242. parentBranch.children[region].children.data.y == node.y) {
  27243. node.x += Math.random();
  27244. node.y += Math.random();
  27245. }
  27246. else {
  27247. this._splitBranch(parentBranch.children[region]);
  27248. this._placeInTree(parentBranch.children[region],node);
  27249. }
  27250. break;
  27251. case 4: // place in branch
  27252. this._placeInTree(parentBranch.children[region],node);
  27253. break;
  27254. }
  27255. };
  27256. /**
  27257. * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch
  27258. * after the split is complete.
  27259. *
  27260. * @param parentBranch
  27261. * @private
  27262. */
  27263. exports._splitBranch = function(parentBranch) {
  27264. // if the branch is shaded with a node, replace the node in the new subset.
  27265. var containedNode = null;
  27266. if (parentBranch.childrenCount == 1) {
  27267. containedNode = parentBranch.children.data;
  27268. parentBranch.mass = 0; parentBranch.centerOfMass.x = 0; parentBranch.centerOfMass.y = 0;
  27269. }
  27270. parentBranch.childrenCount = 4;
  27271. parentBranch.children.data = null;
  27272. this._insertRegion(parentBranch,"NW");
  27273. this._insertRegion(parentBranch,"NE");
  27274. this._insertRegion(parentBranch,"SW");
  27275. this._insertRegion(parentBranch,"SE");
  27276. if (containedNode != null) {
  27277. this._placeInTree(parentBranch,containedNode);
  27278. }
  27279. };
  27280. /**
  27281. * This function subdivides the region into four new segments.
  27282. * Specifically, this inserts a single new segment.
  27283. * It fills the children section of the parentBranch
  27284. *
  27285. * @param parentBranch
  27286. * @param region
  27287. * @param parentRange
  27288. * @private
  27289. */
  27290. exports._insertRegion = function(parentBranch, region) {
  27291. var minX,maxX,minY,maxY;
  27292. var childSize = 0.5 * parentBranch.size;
  27293. switch (region) {
  27294. case "NW":
  27295. minX = parentBranch.range.minX;
  27296. maxX = parentBranch.range.minX + childSize;
  27297. minY = parentBranch.range.minY;
  27298. maxY = parentBranch.range.minY + childSize;
  27299. break;
  27300. case "NE":
  27301. minX = parentBranch.range.minX + childSize;
  27302. maxX = parentBranch.range.maxX;
  27303. minY = parentBranch.range.minY;
  27304. maxY = parentBranch.range.minY + childSize;
  27305. break;
  27306. case "SW":
  27307. minX = parentBranch.range.minX;
  27308. maxX = parentBranch.range.minX + childSize;
  27309. minY = parentBranch.range.minY + childSize;
  27310. maxY = parentBranch.range.maxY;
  27311. break;
  27312. case "SE":
  27313. minX = parentBranch.range.minX + childSize;
  27314. maxX = parentBranch.range.maxX;
  27315. minY = parentBranch.range.minY + childSize;
  27316. maxY = parentBranch.range.maxY;
  27317. break;
  27318. }
  27319. parentBranch.children[region] = {
  27320. centerOfMass:{x:0,y:0},
  27321. mass:0,
  27322. range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY},
  27323. size: 0.5 * parentBranch.size,
  27324. calcSize: 2 * parentBranch.calcSize,
  27325. children: {data:null},
  27326. maxWidth: 0,
  27327. level: parentBranch.level+1,
  27328. childrenCount: 0
  27329. };
  27330. };
  27331. /**
  27332. * This function is for debugging purposed, it draws the tree.
  27333. *
  27334. * @param ctx
  27335. * @param color
  27336. * @private
  27337. */
  27338. exports._drawTree = function(ctx,color) {
  27339. if (this.barnesHutTree !== undefined) {
  27340. ctx.lineWidth = 1;
  27341. this._drawBranch(this.barnesHutTree.root,ctx,color);
  27342. }
  27343. };
  27344. /**
  27345. * This function is for debugging purposes. It draws the branches recursively.
  27346. *
  27347. * @param branch
  27348. * @param ctx
  27349. * @param color
  27350. * @private
  27351. */
  27352. exports._drawBranch = function(branch,ctx,color) {
  27353. if (color === undefined) {
  27354. color = "#FF0000";
  27355. }
  27356. if (branch.childrenCount == 4) {
  27357. this._drawBranch(branch.children.NW,ctx);
  27358. this._drawBranch(branch.children.NE,ctx);
  27359. this._drawBranch(branch.children.SE,ctx);
  27360. this._drawBranch(branch.children.SW,ctx);
  27361. }
  27362. ctx.strokeStyle = color;
  27363. ctx.beginPath();
  27364. ctx.moveTo(branch.range.minX,branch.range.minY);
  27365. ctx.lineTo(branch.range.maxX,branch.range.minY);
  27366. ctx.stroke();
  27367. ctx.beginPath();
  27368. ctx.moveTo(branch.range.maxX,branch.range.minY);
  27369. ctx.lineTo(branch.range.maxX,branch.range.maxY);
  27370. ctx.stroke();
  27371. ctx.beginPath();
  27372. ctx.moveTo(branch.range.maxX,branch.range.maxY);
  27373. ctx.lineTo(branch.range.minX,branch.range.maxY);
  27374. ctx.stroke();
  27375. ctx.beginPath();
  27376. ctx.moveTo(branch.range.minX,branch.range.maxY);
  27377. ctx.lineTo(branch.range.minX,branch.range.minY);
  27378. ctx.stroke();
  27379. /*
  27380. if (branch.mass > 0) {
  27381. ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass);
  27382. ctx.stroke();
  27383. }
  27384. */
  27385. };
  27386. /***/ },
  27387. /* 60 */
  27388. /***/ function(module, exports, __webpack_require__) {
  27389. module.exports = function(module) {
  27390. if(!module.webpackPolyfill) {
  27391. module.deprecate = function() {};
  27392. module.paths = [];
  27393. // module.parent = undefined by default
  27394. module.children = [];
  27395. module.webpackPolyfill = 1;
  27396. }
  27397. return module;
  27398. }
  27399. /***/ }
  27400. /******/ ])
  27401. })