1/** 2 * vis.js 3 * https://github.com/almende/vis 4 * 5 * A dynamic, browser-based visualization library. 6 * 7 * @version 4.9.0 8 * @date 2015-10-01 9 * 10 * @license 11 * Copyright (C) 2011-2015 Almende B.V, http://almende.com 12 * 13 * Vis.js is dual licensed under both 14 * 15 * * The Apache 2.0 License 16 * http://www.apache.org/licenses/LICENSE-2.0 17 * 18 * and 19 * 20 * * The MIT License 21 * http://opensource.org/licenses/MIT 22 * 23 * Vis.js may be distributed under either license. 24 */ 25 26"use strict"; 27 28(function webpackUniversalModuleDefinition(root, factory) { 29 if(typeof exports === 'object' && typeof module === 'object') 30 module.exports = factory(); 31 else if(typeof define === 'function' && define.amd) 32 define([], factory); 33 else if(typeof exports === 'object') 34 exports["vis"] = factory(); 35 else 36 root["vis"] = factory(); 37})(this, function() { 38return /******/ (function(modules) { // webpackBootstrap 39/******/ // The module cache 40/******/ var installedModules = {}; 41 42/******/ // The require function 43/******/ function __webpack_require__(moduleId) { 44 45/******/ // Check if module is in cache 46/******/ if(installedModules[moduleId]) 47/******/ return installedModules[moduleId].exports; 48 49/******/ // Create a new module (and put it into the cache) 50/******/ var module = installedModules[moduleId] = { 51/******/ exports: {}, 52/******/ id: moduleId, 53/******/ loaded: false 54/******/ }; 55 56/******/ // Execute the module function 57/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 58 59/******/ // Flag the module as loaded 60/******/ module.loaded = true; 61 62/******/ // Return the exports of the module 63/******/ return module.exports; 64/******/ } 65 66 67/******/ // expose the modules object (__webpack_modules__) 68/******/ __webpack_require__.m = modules; 69 70/******/ // expose the module cache 71/******/ __webpack_require__.c = installedModules; 72 73/******/ // __webpack_public_path__ 74/******/ __webpack_require__.p = ""; 75 76/******/ // Load entry module and return exports 77/******/ return __webpack_require__(0); 78/******/ }) 79/************************************************************************/ 80/******/ ([ 81/* 0 */ 82/***/ function(module, exports, __webpack_require__) { 83 84 // utils 85 'use strict'; 86 87 exports.util = __webpack_require__(1); 88 exports.DOMutil = __webpack_require__(7); 89 90 // data 91 exports.DataSet = __webpack_require__(8); 92 exports.DataView = __webpack_require__(10); 93 exports.Queue = __webpack_require__(9); 94 95 // Graph3d 96 exports.Graph3d = __webpack_require__(11); 97 exports.graph3d = { 98 Camera: __webpack_require__(15), 99 Filter: __webpack_require__(16), 100 Point2d: __webpack_require__(14), 101 Point3d: __webpack_require__(13), 102 Slider: __webpack_require__(17), 103 StepNumber: __webpack_require__(18) 104 }; 105 106 // Timeline 107 exports.Timeline = __webpack_require__(19); 108 exports.Graph2d = __webpack_require__(49); 109 exports.timeline = { 110 DateUtil: __webpack_require__(27), 111 DataStep: __webpack_require__(52), 112 Range: __webpack_require__(24), 113 stack: __webpack_require__(32), 114 TimeStep: __webpack_require__(30), 115 116 components: { 117 items: { 118 Item: __webpack_require__(34), 119 BackgroundItem: __webpack_require__(38), 120 BoxItem: __webpack_require__(36), 121 PointItem: __webpack_require__(37), 122 RangeItem: __webpack_require__(33) 123 }, 124 125 Component: __webpack_require__(26), 126 CurrentTime: __webpack_require__(44), 127 CustomTime: __webpack_require__(42), 128 DataAxis: __webpack_require__(51), 129 GraphGroup: __webpack_require__(53), 130 Group: __webpack_require__(31), 131 BackgroundGroup: __webpack_require__(35), 132 ItemSet: __webpack_require__(29), 133 Legend: __webpack_require__(57), 134 LineGraph: __webpack_require__(50), 135 TimeAxis: __webpack_require__(39) 136 } 137 }; 138 139 // Network 140 exports.Network = __webpack_require__(59); 141 exports.network = { 142 Images: __webpack_require__(117), 143 dotparser: __webpack_require__(115), 144 gephiParser: __webpack_require__(116), 145 allOptions: __webpack_require__(111) 146 }; 147 exports.network.convertDot = function (input) { 148 return exports.network.dotparser.DOTToGraph(input); 149 }; 150 exports.network.convertGephi = function (input, options) { 151 return exports.network.gephiParser.parseGephi(input, options); 152 }; 153 154 // bundled external libraries 155 exports.moment = __webpack_require__(2); 156 exports.Hammer = __webpack_require__(20); 157 exports.keycharm = __webpack_require__(41); 158 159/***/ }, 160/* 1 */ 161/***/ function(module, exports, __webpack_require__) { 162 163 // utility functions 164 165 // first check if moment.js is already loaded in the browser window, if so, 166 // use this instance. Else, load via commonjs. 167 168 'use strict'; 169 170 var moment = __webpack_require__(2); 171 var uuid = __webpack_require__(6); 172 173 /** 174 * Test whether given object is a number 175 * @param {*} object 176 * @return {Boolean} isNumber 177 */ 178 exports.isNumber = function (object) { 179 return object instanceof Number || typeof object == 'number'; 180 }; 181 182 /** 183 * Remove everything in the DOM object 184 * @param DOMobject 185 */ 186 exports.recursiveDOMDelete = function (DOMobject) { 187 if (DOMobject) { 188 while (DOMobject.hasChildNodes() === true) { 189 exports.recursiveDOMDelete(DOMobject.firstChild); 190 DOMobject.removeChild(DOMobject.firstChild); 191 } 192 } 193 }; 194 195 /** 196 * this function gives you a range between 0 and 1 based on the min and max values in the set, the total sum of all values and the current value. 197 * 198 * @param min 199 * @param max 200 * @param total 201 * @param value 202 * @returns {number} 203 */ 204 exports.giveRange = function (min, max, total, value) { 205 if (max == min) { 206 return 0.5; 207 } else { 208 var scale = 1 / (max - min); 209 return Math.max(0, (value - min) * scale); 210 } 211 }; 212 213 /** 214 * Test whether given object is a string 215 * @param {*} object 216 * @return {Boolean} isString 217 */ 218 exports.isString = function (object) { 219 return object instanceof String || typeof object == 'string'; 220 }; 221 222 /** 223 * Test whether given object is a Date, or a String containing a Date 224 * @param {Date | String} object 225 * @return {Boolean} isDate 226 */ 227 exports.isDate = function (object) { 228 if (object instanceof Date) { 229 return true; 230 } else if (exports.isString(object)) { 231 // test whether this string contains a date 232 var match = ASPDateRegex.exec(object); 233 if (match) { 234 return true; 235 } else if (!isNaN(Date.parse(object))) { 236 return true; 237 } 238 } 239 240 return false;
241 }; 242 243 /** 244 * Create a semi UUID 245 * source: http://stackoverflow.com/a/105074/1262753 246 * @return {String} uuid 247 */ 248 exports.randomUUID = function () { 249 return uuid.v4(); 250 }; 251 252 /** 253 * assign all keys of an object that are not nested objects to a certain value (used for color objects). 254 * @param obj 255 * @param value 256 */ 257 exports.assignAllKeys = function (obj, value) { 258 for (var prop in obj) { 259 if (obj.hasOwnProperty(prop)) { 260 if (typeof obj[prop] !== 'object') { 261 obj[prop] = value; 262 } 263 } 264 } 265 }; 266 267 /** 268 * Fill an object with a possibly partially defined other object. Only copies values if the a object has an object requiring values. 269 * That means an object is not created on a property if only the b object has it. 270 * @param obj 271 * @param value 272 */ 273 exports.fillIfDefined = function (a, b) { 274 var allowDeletion = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 275 276 for (var prop in a) { 277 if (b[prop] !== undefined) { 278 if (typeof b[prop] !== 'object') { 279 if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) { 280 delete a[prop]; 281 } else { 282 a[prop] = b[prop]; 283 } 284 } else { 285 if (typeof a[prop] === 'object') { 286 exports.fillIfDefined(a[prop], b[prop], allowDeletion); 287 } 288 } 289 } 290 } 291 }; 292 293 /** 294 * Extend object a with the properties of object b or a series of objects 295 * Only properties with defined values are copied 296 * @param {Object} a 297 * @param {... Object} b 298 * @return {Object} a 299 */ 300 exports.protoExtend = function (a, b) { 301 for (var i = 1; i < arguments.length; i++) { 302 var other = arguments[i]; 303 for (var prop in other) { 304 a[prop] = other[prop]; 305 } 306 } 307 return a; 308 }; 309 310 /** 311 * Extend object a with the properties of object b or a series of objects 312 * Only properties with defined values are copied 313 * @param {Object} a 314 * @param {... Object} b 315 * @return {Object} a 316 */ 317 exports.extend = function (a, b) { 318 for (var i = 1; i < arguments.length; i++) { 319 var other = arguments[i]; 320 for (var prop in other) { 321 if (other.hasOwnProperty(prop)) { 322 a[prop] = other[prop]; 323 } 324 } 325 } 326 return a; 327 }; 328 329 /** 330 * Extend object a with selected properties of object b or a series of objects 331 * Only properties with defined values are copied 332 * @param {Array.<String>} props 333 * @param {Object} a 334 * @param {Object} b 335 * @return {Object} a 336 */ 337 exports.selectiveExtend = function (props, a, b) { 338 if (!Array.isArray(props)) { 339 throw new Error('Array with property names expected as first argument'); 340 } 341 342 for (var i = 2; i < arguments.length; i++) { 343 var other = arguments[i]; 344 345 for (var p = 0; p < props.length; p++) { 346 var prop = props[p]; 347 if (other.hasOwnProperty(prop)) { 348 a[prop] = other[prop]; 349 } 350 } 351 } 352 return a; 353 }; 354 355 /** 356 * Extend object a with selected properties of object b or a series of objects 357 * Only properties with defined values are copied 358 * @param {Array.<String>} props 359 * @param {Object} a 360 * @param {Object} b 361 * @return {Object} a 362 */ 363 exports.selectiveDeepExtend = function (props, a, b) { 364 var allowDeletion = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3]; 365 366 // TODO: add support for Arrays to deepExtend 367 if (Array.isArray(b)) { 368 throw new TypeError('Arrays are not supported by deepExtend'); 369 } 370 for (var i = 2; i < arguments.length; i++) { 371 var other = arguments[i]; 372 for (var p = 0; p < props.length; p++) { 373 var prop = props[p]; 374 if (other.hasOwnProperty(prop)) { 375 if (b[prop] && b[prop].constructor === Object) { 376 if (a[prop] === undefined) { 377 a[prop] = {}; 378 } 379 if (a[prop].constructor === Object) { 380 exports.deepExtend(a[prop], b[prop], false, allowDeletion); 381 } else { 382 if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { 383 delete a[prop]; 384 } else { 385 a[prop] = b[prop]; 386 } 387 } 388 } else if (Array.isArray(b[prop])) { 389 throw new TypeError('Arrays are not supported by deepExtend'); 390 } else { 391 if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { 392 delete a[prop]; 393 } else { 394 a[prop] = b[prop]; 395 } 396 } 397 } 398 } 399 } 400 return a; 401 }; 402 403 /** 404 * Extend object a with selected properties of object b or a series of objects 405 * Only properties with defined values are copied 406 * @param {Array.<String>} props 407 * @param {Object} a 408 * @param {Object} b 409 * @return {Object} a 410 */ 411 exports.selectiveNotDeepExtend = function (props, a, b) { 412 var allowDeletion = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3]; 413 414 // TODO: add support for Arrays to deepExtend 415 if (Array.isArray(b)) { 416 throw new TypeError('Arrays are not supported by deepExtend'); 417 } 418 for (var prop in b) { 419 if (b.hasOwnProperty(prop)) { 420 if (props.indexOf(prop) == -1) { 421 if (b[prop] && b[prop].constructor === Object) { 422 if (a[prop] === undefined) { 423 a[prop] = {}; 424 } 425 if (a[prop].constructor === Object) { 426 exports.deepExtend(a[prop], b[prop]); 427 } else { 428 if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { 429 delete a[prop]; 430 } else { 431 a[prop] = b[prop]; 432 } 433 } 434 } else if (Array.isArray(b[prop])) { 435 a[prop] = []; 436 for (var i = 0; i < b[prop].length; i++) { 437 a[prop].push(b[prop][i]); 438 } 439 } else { 440 if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { 441 delete a[prop]; 442 } else { 443 a[prop] = b[prop]; 444 } 445 } 446 } 447 } 448 } 449 return a; 450 }; 451 452 /** 453 * Deep extend an object a with the properties of object b 454 * @param {Object} a 455 * @param {Object} b 456 * @param [Boolean] protoExtend --> optional parameter. If true, the prototype values will also be extended. 457 * (ie. the options objects that inherit from others will also get the inherited options) 458 * @param [Boolean] global --> optional parameter. If true, the values of fields that are null will not deleted 459 * @returns {Object} 460 */ 461 exports.deepExtend = function (a, b, protoExtend, allowDeletion) { 462 for (var prop in b) { 463 if (b.hasOwnProperty(prop) || protoExtend === true) { 464 if (b[prop] && b[prop].constructor === Object) { 465 if (a[prop] === undefined) { 466 a[prop] = {}; 467 } 468 if (a[prop].constructor === Object) { 469 exports.deepExtend(a[prop], b[prop], protoExtend); 470 } else { 471 if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { 472 delete a[prop]; 473 } else { 474 a[prop] = b[prop]; 475 } 476 } 477 } else if (Array.isArray(b[prop])) { 478 a[prop] = []; 479 for (var i = 0; i < b[prop].length; i++) { 480 a[prop].push(b[prop][i]); 481 } 482 } else { 483 if (b[prop] === null && a[prop] !== undefined && allowDeletion === true) { 484 delete a[prop]; 485 } else { 486 a[prop] = b[prop]; 487 } 488 } 489 } 490 } 491 return a; 492 }; 493 494 /** 495 * Test whether all elements in two arrays are equal. 496 * @param {Array} a 497 * @param {Array} b 498 * @return {boolean} Returns true if both arrays have the same length and same 499 * elements. 500 */ 501 exports.equalArray = function (a, b) { 502 if (a.length != b.length) return false;
503 504 for (var i = 0, len = a.length; i < len; i++) { 505 if (a[i] != b[i]) return false; 506 } 507 508 return true; 509 }; 510 511 /** 512 * Convert an object to another type 513 * @param {Boolean | Number | String | Date | Moment | Null | undefined} object 514 * @param {String | undefined} type Name of the type. Available types: 515 * 'Boolean', 'Number', 'String', 516 * 'Date', 'Moment', ISODate', 'ASPDate'. 517 * @return {*} object 518 * @throws Error 519 */ 520 exports.convert = function (object, type) { 521 var match; 522 523 if (object === undefined) { 524 return undefined; 525 } 526 if (object === null) { 527 return null; 528 } 529 530 if (!type) { 531 return object; 532 } 533 if (!(typeof type === 'string') && !(type instanceof String)) { 534 throw new Error('Type must be a string'); 535 } 536 537 //noinspection FallthroughInSwitchStatementJS 538 switch (type) { 539 case 'boolean': 540 case 'Boolean': 541 return Boolean(object); 542 543 case 'number': 544 case 'Number': 545 return Number(object.valueOf()); 546 547 case 'string': 548 case 'String': 549 return String(object); 550 551 case 'Date': 552 if (exports.isNumber(object)) { 553 return new Date(object); 554 } 555 if (object instanceof Date) { 556 return new Date(object.valueOf()); 557 } else if (moment.isMoment(object)) { 558 return new Date(object.valueOf()); 559 } 560 if (exports.isString(object)) { 561 match = ASPDateRegex.exec(object); 562 if (match) { 563 // object is an ASP date 564 return new Date(Number(match[1])); // parse number 565 } else { 566 return moment(object).toDate(); // parse string 567 } 568 } else { 569 throw new Error('Cannot convert object of type ' + exports.getType(object) + ' to type Date'); 570 } 571 572 case 'Moment': 573 if (exports.isNumber(object)) { 574 return moment(object); 575 } 576 if (object instanceof Date) { 577 return moment(object.valueOf()); 578 } else if (moment.isMoment(object)) { 579 return moment(object); 580 } 581 if (exports.isString(object)) { 582 match = ASPDateRegex.exec(object); 583 if (match) { 584 // object is an ASP date 585 return moment(Number(match[1])); // parse number 586 } else { 587 return moment(object); // parse string 588 } 589 } else { 590 throw new Error('Cannot convert object of type ' + exports.getType(object) + ' to type Date'); 591 } 592 593 case 'ISODate': 594 if (exports.isNumber(object)) { 595 return new Date(object); 596 } else if (object instanceof Date) { 597 return object.toISOString(); 598 } else if (moment.isMoment(object)) { 599 return object.toDate().toISOString(); 600 } else if (exports.isString(object)) { 601 match = ASPDateRegex.exec(object); 602 if (match) { 603 // object is an ASP date 604 return new Date(Number(match[1])).toISOString(); // parse number 605 } else { 606 return new Date(object).toISOString(); // parse string 607 } 608 } else { 609 throw new Error('Cannot convert object of type ' + exports.getType(object) + ' to type ISODate'); 610 } 611 612 case 'ASPDate': 613 if (exports.isNumber(object)) { 614 return '/Date(' + object + ')/'; 615 } else if (object instanceof Date) { 616 return '/Date(' + object.valueOf() + ')/'; 617 } else if (exports.isString(object)) { 618 match = ASPDateRegex.exec(object); 619 var value; 620 if (match) { 621 // object is an ASP date 622 value = new Date(Number(match[1])).valueOf(); // parse number 623 } else { 624 value = new Date(object).valueOf(); // parse string 625 } 626 return '/Date(' + value + ')/'; 627 } else { 628 throw new Error('Cannot convert object of type ' + exports.getType(object) + ' to type ASPDate'); 629 } 630 631 default: 632 throw new Error('Unknown type "' + type + '"'); 633 } 634 }; 635 636 // parse ASP.Net Date pattern, 637 // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/' 638 // code from http://momentjs.com/ 639 var ASPDateRegex = /^\/?Date\((\-?\d+)/i; 640 641 /** 642 * Get the type of an object, for example exports.getType([]) returns 'Array' 643 * @param {*} object 644 * @return {String} type 645 */ 646 exports.getType = function (object) { 647 var type = typeof object; 648 649 if (type == 'object') { 650 if (object === null) { 651 return 'null';
652 } 653 if (object instanceof Boolean) { 654 return 'Boolean'; 655 } 656 if (object instanceof Number) { 657 return 'Number'; 658 } 659 if (object instanceof String) { 660 return 'String'; 661 } 662 if (Array.isArray(object)) { 663 return 'Array'; 664 } 665 if (object instanceof Date) { 666 return 'Date'; 667 } 668 return 'Object'; 669 } else if (type == 'number') { 670 return 'Number'; 671 } else if (type == 'boolean') { 672 return 'Boolean'; 673 } else if (type == 'string') { 674 return 'String'; 675 } else if (type === undefined) { 676 return 'undefined'; 677 } 678 679 return type; 680 }; 681 682 /** 683 * Used to extend an array and copy it. This is used to propagate paths recursively. 684 * 685 * @param arr 686 * @param newValue 687 * @returns {Array} 688 */ 689 exports.copyAndExtendArray = function (arr, newValue) { 690 var newArr = []; 691 for (var i = 0; i < arr.length; i++) { 692 newArr.push(arr[i]); 693 } 694 newArr.push(newValue); 695 return newArr; 696 }; 697 698 /** 699 * Used to extend an array and copy it. This is used to propagate paths recursively. 700 * 701 * @param arr 702 * @param newValue 703 * @returns {Array} 704 */ 705 exports.copyArray = function (arr) { 706 var newArr = []; 707 for (var i = 0; i < arr.length; i++) { 708 newArr.push(arr[i]); 709 } 710 return newArr; 711 }; 712 713 /** 714 * Retrieve the absolute left value of a DOM element 715 * @param {Element} elem A dom element, for example a div 716 * @return {number} left The absolute left position of this element 717 * in the browser page. 718 */ 719 exports.getAbsoluteLeft = function (elem) { 720 return elem.getBoundingClientRect().left; 721 }; 722 723 /** 724 * Retrieve the absolute top value of a DOM element 725 * @param {Element} elem A dom element, for example a div 726 * @return {number} top The absolute top position of this element 727 * in the browser page. 728 */ 729 exports.getAbsoluteTop = function (elem) { 730 return elem.getBoundingClientRect().top; 731 }; 732 733 /** 734 * add a className to the given elements style 735 * @param {Element} elem 736 * @param {String} className 737 */ 738 exports.addClassName = function (elem, className) { 739 var classes = elem.className.split(' '); 740 if (classes.indexOf(className) == -1) { 741 classes.push(className); // add the class to the array 742 elem.className = classes.join(' '); 743 } 744 }; 745 746 /** 747 * add a className to the given elements style 748 * @param {Element} elem 749 * @param {String} className 750 */ 751 exports.removeClassName = function (elem, className) { 752 var classes = elem.className.split(' '); 753 var index = classes.indexOf(className); 754 if (index != -1) { 755 classes.splice(index, 1); // remove the class from the array 756 elem.className = classes.join(' '); 757 } 758 }; 759 760 /** 761 * For each method for both arrays and objects. 762 * In case of an array, the built-in Array.forEach() is applied. 763 * In case of an Object, the method loops over all properties of the object. 764 * @param {Object | Array} object An Object or Array 765 * @param {function} callback Callback method, called for each item in 766 * the object or array with three parameters: 767 * callback(value, index, object) 768 */ 769 exports.forEach = function (object, callback) { 770 var i, len; 771 if (Array.isArray(object)) { 772 // array 773 for (i = 0, len = object.length; i < len; i++) { 774 callback(object[i], i, object); 775 } 776 } else { 777 // object 778 for (i in object) { 779 if (object.hasOwnProperty(i)) { 780 callback(object[i], i, object); 781 } 782 } 783 } 784 }; 785 786 /** 787 * Convert an object into an array: all objects properties are put into the 788 * array. The resulting array is unordered. 789 * @param {Object} object 790 * @param {Array} array 791 */ 792 exports.toArray = function (object) { 793 var array = []; 794 795 for (var prop in object) { 796 if (object.hasOwnProperty(prop)) array.push(object[prop]); 797 } 798 799 return array; 800 }; 801 802 /** 803 * Update a property in an object 804 * @param {Object} object 805 * @param {String} key 806 * @param {*} value 807 * @return {Boolean} changed 808 */ 809 exports.updateProperty = function (object, key, value) { 810 if (object[key] !== value) { 811 object[key] = value; 812 return true; 813 } else { 814 return false;
815 } 816 }; 817 818 /** 819 * Throttle the given function to be only executed once every `wait` milliseconds 820 * @param {function} fn 821 * @param {number} wait Time in milliseconds 822 * @returns {function} Returns the throttled function 823 */ 824 exports.throttle = function (fn, wait) { 825 var timeout = null; 826 var needExecution = false; 827 828 return function throttled() { 829 if (!timeout) { 830 needExecution = false; 831 fn(); 832 833 timeout = setTimeout(function () { 834 timeout = null; 835 if (needExecution) { 836 throttled(); 837 } 838 }, wait); 839 } else { 840 needExecution = true; 841 } 842 }; 843 }; 844 845 /** 846 * Add and event listener. Works for all browsers 847 * @param {Element} element An html element 848 * @param {string} action The action, for example "click", 849 * without the prefix "on" 850 * @param {function} listener The callback function to be executed 851 * @param {boolean} [useCapture] 852 */ 853 exports.addEventListener = function (element, action, listener, useCapture) { 854 if (element.addEventListener) { 855 if (useCapture === undefined) useCapture = false; 856 857 if (action === 'mousewheel' && navigator.userAgent.indexOf('Firefox') >= 0) { 858 action = 'DOMMouseScroll'; // For Firefox 859 } 860 861 element.addEventListener(action, listener, useCapture); 862 } else { 863 element.attachEvent('on' + action, listener); // IE browsers 864 } 865 }; 866 867 /** 868 * Remove an event listener from an element 869 * @param {Element} element An html dom element 870 * @param {string} action The name of the event, for example "mousedown" 871 * @param {function} listener The listener function 872 * @param {boolean} [useCapture] 873 */ 874 exports.removeEventListener = function (element, action, listener, useCapture) { 875 if (element.removeEventListener) { 876 // non-IE browsers 877 if (useCapture === undefined) useCapture = false; 878 879 if (action === 'mousewheel' && navigator.userAgent.indexOf('Firefox') >= 0) { 880 action = 'DOMMouseScroll'; // For Firefox 881 } 882 883 element.removeEventListener(action, listener, useCapture); 884 } else { 885 // IE browsers 886 element.detachEvent('on' + action, listener); 887 } 888 }; 889 890 /** 891 * Cancels the event if it is cancelable, without stopping further propagation of the event. 892 */ 893 exports.preventDefault = function (event) { 894 if (!event) event = window.event; 895 896 if (event.preventDefault) { 897 event.preventDefault(); // non-IE browsers 898 } else { 899 event.returnValue = false; // IE browsers 900 } 901 }; 902 903 /** 904 * Get HTML element which is the target of the event 905 * @param {Event} event 906 * @return {Element} target element 907 */ 908 exports.getTarget = function (event) { 909 // code from http://www.quirksmode.org/js/events_properties.html 910 if (!event) { 911 event = window.event; 912 } 913 914 var target; 915 916 if (event.target) { 917 target = event.target; 918 } else if (event.srcElement) { 919 target = event.srcElement; 920 } 921 922 if (target.nodeType != undefined && target.nodeType == 3) { 923 // defeat Safari bug 924 target = target.parentNode; 925 } 926 927 return target; 928 }; 929 930 /** 931 * Check if given element contains given parent somewhere in the DOM tree 932 * @param {Element} element 933 * @param {Element} parent 934 */ 935 exports.hasParent = function (element, parent) { 936 var e = element; 937 938 while (e) { 939 if (e === parent) { 940 return true; 941 } 942 e = e.parentNode; 943 } 944 945 return false; 946 }; 947 948 exports.option = {}; 949 950 /** 951 * Convert a value into a boolean 952 * @param {Boolean | function | undefined} value 953 * @param {Boolean} [defaultValue] 954 * @returns {Boolean} bool 955 */ 956 exports.option.asBoolean = function (value, defaultValue) { 957 if (typeof value == 'function') { 958 value = value(); 959 } 960 961 if (value != null) { 962 return value != false; 963 } 964 965 return defaultValue || null; 966 }; 967 968 /** 969 * Convert a value into a number 970 * @param {Boolean | function | undefined} value 971 * @param {Number} [defaultValue] 972 * @returns {Number} number 973 */ 974 exports.option.asNumber = function (value, defaultValue) { 975 if (typeof value == 'function') { 976 value = value(); 977 } 978 979 if (value != null) { 980 return Number(value) || defaultValue || null; 981 } 982 983 return defaultValue || null; 984 }; 985 986 /** 987 * Convert a value into a string 988 * @param {String | function | undefined} value 989 * @param {String} [defaultValue] 990 * @returns {String} str 991 */ 992 exports.option.asString = function (value, defaultValue) { 993 if (typeof value == 'function') { 994 value = value(); 995 } 996 997 if (value != null) { 998 return String(value); 999 } 1000 1001 return defaultValue || null; 1002 }; 1003 1004 /** 1005 * Convert a size or location into a string with pixels or a percentage 1006 * @param {String | Number | function | undefined} value 1007 * @param {String} [defaultValue] 1008 * @returns {String} size 1009 */ 1010 exports.option.asSize = function (value, defaultValue) { 1011 if (typeof value == 'function') { 1012 value = value(); 1013 } 1014 1015 if (exports.isString(value)) { 1016 return value; 1017 } else if (exports.isNumber(value)) { 1018 return value + 'px'; 1019 } else { 1020 return defaultValue || null; 1021 } 1022 }; 1023 1024 /** 1025 * Convert a value into a DOM element 1026 * @param {HTMLElement | function | undefined} value 1027 * @param {HTMLElement} [defaultValue] 1028 * @returns {HTMLElement | null} dom 1029 */ 1030 exports.option.asElement = function (value, defaultValue) { 1031 if (typeof value == 'function') { 1032 value = value(); 1033 } 1034 1035 return value || defaultValue || null; 1036 }; 1037 1038 /** 1039 * http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb 1040 * 1041 * @param {String} hex 1042 * @returns {{r: *, g: *, b: *}} | 255 range 1043 */ 1044 exports.hexToRGB = function (hex) { 1045 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") 1046 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 1047 hex = hex.replace(shorthandRegex, function (m, r, g, b) { 1048 return r + r + g + g + b + b; 1049 }); 1050 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 1051 return result ? { 1052 r: parseInt(result[1], 16), 1053 g: parseInt(result[2], 16), 1054 b: parseInt(result[3], 16) 1055 } : null; 1056 }; 1057 1058 /** 1059 * This function takes color in hex format or rgb() or rgba() format and overrides the opacity. Returns rgba() string. 1060 * @param color 1061 * @param opacity 1062 * @returns {*} 1063 */ 1064 exports.overrideOpacity = function (color, opacity) { 1065 if (color.indexOf('rgba') != -1) { 1066 return color; 1067 } else if (color.indexOf('rgb') != -1) { 1068 var rgb = color.substr(color.indexOf('(') + 1).replace(')', '').split(','); 1069 return 'rgba(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ',' + opacity + ')'; 1070 } else { 1071 var rgb = exports.hexToRGB(color); 1072 if (rgb == null) { 1073 return color; 1074 } else { 1075 return 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + opacity + ')'; 1076 } 1077 } 1078 }; 1079 1080 /** 1081 * 1082 * @param red 0 -- 255 1083 * @param green 0 -- 255 1084 * @param blue 0 -- 255 1085 * @returns {string} 1086 * @constructor 1087 */ 1088 exports.RGBToHex = function (red, green, blue) { 1089 return '#' + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1); 1090 }; 1091 1092 /** 1093 * Parse a color property into an object with border, background, and 1094 * highlight colors 1095 * @param {Object | String} color 1096 * @return {Object} colorObject 1097 */ 1098 exports.parseColor = function (color) { 1099 var c; 1100 if (exports.isString(color) === true) { 1101 if (exports.isValidRGB(color) === true) { 1102 var rgb = color.substr(4).substr(0, color.length - 5).split(',').map(function (value) { 1103 return parseInt(value); 1104 }); 1105 color = exports.RGBToHex(rgb[0], rgb[1], rgb[2]); 1106 } 1107 if (exports.isValidHex(color) === true) { 1108 var hsv = exports.hexToHSV(color); 1109 var lighterColorHSV = { h: hsv.h, s: hsv.s * 0.8, v: Math.min(1, hsv.v * 1.02) }; 1110 var darkerColorHSV = { h: hsv.h, s: Math.min(1, hsv.s * 1.25), v: hsv.v * 0.8 }; 1111 var darkerColorHex = exports.HSVToHex(darkerColorHSV.h, darkerColorHSV.s, darkerColorHSV.v); 1112 var lighterColorHex = exports.HSVToHex(lighterColorHSV.h, lighterColorHSV.s, lighterColorHSV.v); 1113 c = { 1114 background: color, 1115 border: darkerColorHex, 1116 highlight: { 1117 background: lighterColorHex, 1118 border: darkerColorHex 1119 }, 1120 hover: { 1121 background: lighterColorHex, 1122 border: darkerColorHex 1123 } 1124 }; 1125 } else { 1126 c = { 1127 background: color, 1128 border: color, 1129 highlight: { 1130 background: color, 1131 border: color 1132 }, 1133 hover: { 1134 background: color, 1135 border: color 1136 } 1137 }; 1138 } 1139 } else { 1140 c = {}; 1141 c.background = color.background || undefined; 1142 c.border = color.border || undefined; 1143 1144 if (exports.isString(color.highlight)) { 1145 c.highlight = { 1146 border: color.highlight, 1147 background: color.highlight 1148 }; 1149 } else { 1150 c.highlight = {}; 1151 c.highlight.background = color.highlight && color.highlight.background || undefined; 1152 c.highlight.border = color.highlight && color.highlight.border || undefined; 1153 } 1154 1155 if (exports.isString(color.hover)) { 1156 c.hover = { 1157 border: color.hover, 1158 background: color.hover 1159 }; 1160 } else { 1161 c.hover = {}; 1162 c.hover.background = color.hover && color.hover.background || undefined; 1163 c.hover.border = color.hover && color.hover.border || undefined; 1164 } 1165 } 1166 1167 return c; 1168 }; 1169 1170 /** 1171 * http://www.javascripter.net/faq/rgb2hsv.htm 1172 * 1173 * @param red 1174 * @param green 1175 * @param blue 1176 * @returns {*} 1177 * @constructor 1178 */ 1179 exports.RGBToHSV = function (red, green, blue) { 1180 red = red / 255;
1180green = green / 255;blue = blue / 255; 1181 var minRGB = Math.min(red, Math.min(green, blue)); 1182 var maxRGB = Math.max(red, Math.max(green, blue)); 1183 1184 // Black-gray-white 1185 if (minRGB == maxRGB) { 1186 return { h: 0, s: 0, v: minRGB }; 1187 } 1188 1189 // Colors other than black-gray-white: 1190 var d = red == minRGB ? green - blue : blue == minRGB ? red - green : blue - red; 1191 var h = red == minRGB ? 3 : blue == minRGB ? 1 : 5; 1192 var hue = 60 * (h - d / (maxRGB - minRGB)) / 360; 1193 var saturation = (maxRGB - minRGB) / maxRGB; 1194 var value = maxRGB; 1195 return { h: hue, s: saturation, v: value }; 1196 }; 1197 1198 var cssUtil = { 1199 // split a string with css styles into an object with key/values 1200 split: function split(cssText) { 1201 var styles = {}; 1202 1203 cssText.split(';').forEach(function (style) { 1204 if (style.trim() != '') { 1205 var parts = style.split(':'); 1206 var key = parts[0].trim(); 1207 var value = parts[1].trim(); 1208 styles[key] = value; 1209 } 1210 }); 1211 1212 return styles; 1213 }, 1214 1215 // build a css text string from an object with key/values 1216 join: function join(styles) { 1217 return Object.keys(styles).map(function (key) { 1218 return key + ': ' + styles[key]; 1219 }).join('; '); 1220 } 1221 }; 1222 1223 /** 1224 * Append a string with css styles to an element 1225 * @param {Element} element 1226 * @param {String} cssText 1227 */ 1228 exports.addCssText = function (element, cssText) { 1229 var currentStyles = cssUtil.split(element.style.cssText); 1230 var newStyles = cssUtil.split(cssText); 1231 var styles = exports.extend(currentStyles, newStyles); 1232 1233 element.style.cssText = cssUtil.join(styles); 1234 }; 1235 1236 /** 1237 * Remove a string with css styles from an element 1238 * @param {Element} element 1239 * @param {String} cssText 1240 */ 1241 exports.removeCssText = function (element, cssText) { 1242 var styles = cssUtil.split(element.style.cssText); 1243 var removeStyles = cssUtil.split(cssText); 1244 1245 for (var key in removeStyles) { 1246 if (removeStyles.hasOwnProperty(key)) { 1247 delete styles[key]; 1248 } 1249 } 1250 1251 element.style.cssText = cssUtil.join(styles); 1252 }; 1253 1254 /** 1255 * https://gist.github.com/mjijackson/5311256 1256 * @param h 1257 * @param s 1258 * @param v 1259 * @returns {{r: number, g: number, b: number}} 1260 * @constructor 1261 */ 1262 exports.HSVToRGB = function (h, s, v) { 1263 var r, g, b; 1264 1265 var i = Math.floor(h * 6); 1266 var f = h * 6 - i; 1267 var p = v * (1 - s); 1268 var q = v * (1 - f * s); 1269 var t = v * (1 - (1 - f) * s); 1270 1271 switch (i % 6) { 1272 case 0: 1273 r = v, g = t, b = p;break; 1274 case 1: 1275 r = q, g = v, b = p;break; 1276 case 2: 1277 r = p, g = v, b = t;break; 1278 case 3: 1279 r = p, g = q, b = v;break; 1280 case 4: 1281 r = t, g = p, b = v;break; 1282 case 5: 1283 r = v, g = p, b = q;break; 1284 } 1285 1286 return { r: Math.floor(r * 255), g: Math.floor(g * 255), b: Math.floor(b * 255) }; 1287 }; 1288 1289 exports.HSVToHex = function (h, s, v) { 1290 var rgb = exports.HSVToRGB(h, s, v); 1291 return exports.RGBToHex(rgb.r, rgb.g, rgb.b); 1292 }; 1293 1294 exports.hexToHSV = function (hex) { 1295 var rgb = exports.hexToRGB(hex); 1296 return exports.RGBToHSV(rgb.r, rgb.g, rgb.b); 1297 }; 1298 1299 exports.isValidHex = function (hex) { 1300 var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex); 1301 return isOk; 1302 }; 1303 1304 exports.isValidRGB = function (rgb) { 1305 rgb = rgb.replace(' ', ''); 1306 var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb); 1307 return isOk; 1308 }; 1309 exports.isValidRGBA = function (rgba) { 1310 rgba = rgba.replace(' ', ''); 1311 var isOk = /rgba\((\d{1,3}),(\d{1,3}),(\d{1,3}),(.{1,3})\)/i.test(rgba); 1312 return isOk; 1313 }; 1314 1315 /** 1316 * This recursively redirects the prototype of JSON objects to the referenceObject 1317 * This is used for default options. 1318 * 1319 * @param referenceObject 1320 * @returns {*} 1321 */ 1322 exports.selectiveBridgeObject = function (fields, referenceObject) { 1323 if (typeof referenceObject == 'object') { 1324 var objectTo = Object.create(referenceObject); 1325 for (var i = 0; i < fields.length; i++) { 1326 if (referenceObject.hasOwnProperty(fields[i])) { 1327 if (typeof referenceObject[fields[i]] == 'object') {
1328 objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]); 1329 } 1330 } 1331 } 1332 return objectTo; 1333 } else { 1334 return null; 1335 } 1336 }; 1337 1338 /** 1339 * This recursively redirects the prototype of JSON objects to the referenceObject 1340 * This is used for default options. 1341 * 1342 * @param referenceObject 1343 * @returns {*} 1344 */ 1345 exports.bridgeObject = function (referenceObject) { 1346 if (typeof referenceObject == 'object') { 1347 var objectTo = Object.create(referenceObject); 1348 for (var i in referenceObject) { 1349 if (referenceObject.hasOwnProperty(i)) { 1350 if (typeof referenceObject[i] == 'object') { 1351 objectTo[i] = exports.bridgeObject(referenceObject[i]); 1352 } 1353 } 1354 } 1355 return objectTo; 1356 } else { 1357 return null; 1358 } 1359 }; 1360 1361 /** 1362 * this is used to set the options of subobjects in the options object. A requirement of these subobjects 1363 * is that they have an 'enabled' element which is optional for the user but mandatory for the program. 1364 * 1365 * @param [object] mergeTarget | this is either this.options or the options used for the groups. 1366 * @param [object] options | options 1367 * @param [String] option | this is the option key in the options argument 1368 * @private 1369 */ 1370 exports.mergeOptions = function (mergeTarget, options, option) { 1371 var allowDeletion = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3]; 1372 var globalOptions = arguments.length <= 4 || arguments[4] === undefined ? {} : arguments[4]; 1373 1374 if (options[option] === null) { 1375 mergeTarget[option] = Object.create(globalOptions[option]); 1376 } else { 1377 if (options[option] !== undefined) { 1378 if (typeof options[option] === 'boolean') { 1379 mergeTarget[option].enabled = options[option]; 1380 } else { 1381 if (options[option].enabled === undefined) { 1382 mergeTarget[option].enabled = true; 1383 } 1384 for (var prop in options[option]) { 1385 if (options[option].hasOwnProperty(prop)) { 1386 mergeTarget[option][prop] = options[option][prop]; 1387 } 1388 } 1389 } 1390 } 1391 } 1392 }; 1393 1394 /** 1395 * This function does a binary search for a visible item in a sorted list. If we find a visible item, the code that uses 1396 * this function will then iterate in both directions over this sorted list to find all visible items. 1397 * 1398 * @param {Item[]} orderedItems | Items ordered by start 1399 * @param {function} searchFunction | -1 is lower, 0 is found, 1 is higher 1400 * @param {String} field 1401 * @param {String} field2 1402 * @returns {number} 1403 * @private 1404 */ 1405 exports.binarySearchCustom = function (orderedItems, searchFunction, field, field2) { 1406 var maxIterations = 10000; 1407 var iteration = 0; 1408 var low = 0; 1409 var high = orderedItems.length - 1; 1410 1411 while (low <= high && iteration < maxIterations) { 1412 var middle = Math.floor((low + high) / 2); 1413 1414 var item = orderedItems[middle]; 1415 var value = field2 === undefined ? item[field] : item[field][field2]; 1416 1417 var searchResult = searchFunction(value); 1418 if (searchResult == 0) { 1419 // jihaa, found a visible item! 1420 return middle; 1421 } else if (searchResult == -1) { 1422 // it is too small --> increase low 1423 low = middle + 1; 1424 } else { 1425 // it is too big --> decrease high 1426 high = middle - 1; 1427 } 1428 1429 iteration++; 1430 } 1431 1432 return -1; 1433 }; 1434 1435 /** 1436 * This function does a binary search for a specific value in a sorted array. If it does not exist but is in between of 1437 * two values, we return either the one before or the one after, depending on user input 1438 * If it is found, we return the index, else -1. 1439 * 1440 * @param {Array} orderedItems 1441 * @param {{start: number, end: number}} target 1442 * @param {String} field 1443 * @param {String} sidePreference 'before' or 'after' 1444 * @returns {number} 1445 * @private 1446 */ 1447 exports.binarySearchValue = function (orderedItems, target, field, sidePreference) { 1448 var maxIterations = 10000; 1449 var iteration = 0; 1450 var low = 0; 1451 var high = orderedItems.length - 1; 1452 var prevValue, value, nextValue, middle; 1453 1454 while (low <= high && iteration < maxIterations) { 1455 // get a new guess 1456 middle = Math.floor(0.5 * (high + low)); 1457 prevValue = orderedItems[Math.max(0, middle - 1)][field]; 1458 value = orderedItems[middle][field]; 1459 nextValue = orderedItems[Math.min(orderedItems.length - 1, middle + 1)][field]; 1460 1461 if (value == target) { 1462 // we found the target 1463 return middle; 1464 } else if (prevValue < target && value > target) {
1465 // target is in between of the previous and the current 1466 return sidePreference == 'before' ? Math.max(0, middle - 1) : middle; 1467 } else if (value < target && nextValue > target) { 1468 // target is in between of the current and the next 1469 return sidePreference == 'before' ? middle : Math.min(orderedItems.length - 1, middle + 1); 1470 } else { 1471 // didnt find the target, we need to change our boundaries. 1472 if (value < target) { 1473 // it is too small --> increase low 1474 low = middle + 1; 1475 } else { 1476 // it is too big --> decrease high 1477 high = middle - 1; 1478 } 1479 } 1480 iteration++; 1481 } 1482 1483 // didnt find anything. Return -1. 1484 return -1; 1485 }; 1486 1487 /* 1488 * Easing Functions - inspired from http://gizma.com/easing/ 1489 * only considering the t value for the range [0, 1] => [0, 1] 1490 * https://gist.github.com/gre/1650294 1491 */ 1492 exports.easingFunctions = { 1493 // no easing, no acceleration 1494 linear: function linear(t) { 1495 return t; 1496 }, 1497 // accelerating from zero velocity 1498 easeInQuad: function easeInQuad(t) { 1499 return t * t; 1500 }, 1501 // decelerating to zero velocity 1502 easeOutQuad: function easeOutQuad(t) { 1503 return t * (2 - t); 1504 }, 1505 // acceleration until halfway, then deceleration 1506 easeInOutQuad: function easeInOutQuad(t) { 1507 return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t; 1508 }, 1509 // accelerating from zero velocity 1510 easeInCubic: function easeInCubic(t) { 1511 return t * t * t; 1512 }, 1513 // decelerating to zero velocity 1514 easeOutCubic: function easeOutCubic(t) { 1515 return --t * t * t + 1; 1516 }, 1517 // acceleration until halfway, then deceleration 1518 easeInOutCubic: function easeInOutCubic(t) { 1519 return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; 1520 }, 1521 // accelerating from zero velocity 1522 easeInQuart: function easeInQuart(t) { 1523 return t * t * t * t; 1524 }, 1525 // decelerating to zero velocity 1526 easeOutQuart: function easeOutQuart(t) { 1527 return 1 - --t * t * t * t; 1528 }, 1529 // acceleration until halfway, then deceleration 1530 easeInOutQuart: function easeInOutQuart(t) { 1531 return t < .5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t; 1532 }, 1533 // accelerating from zero velocity 1534 easeInQuint: function easeInQuint(t) { 1535 return t * t * t * t * t; 1536 }, 1537 // decelerating to zero velocity 1538 easeOutQuint: function easeOutQuint(t) { 1539 return 1 + --t * t * t * t * t; 1540 }, 1541 // acceleration until halfway, then deceleration 1542 easeInOutQuint: function easeInOutQuint(t) { 1543 return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t; 1544 } 1545 }; 1546 1547/***/ }, 1548/* 2 */ 1549/***/ function(module, exports, __webpack_require__) { 1550 1551 // first check if moment.js is already loaded in the browser window, if so, 1552 // use this instance. Else, load via commonjs. 1553 'use strict'; 1554 1555 module.exports = typeof window !== 'undefined' && window['moment'] || __webpack_require__(3); 1556 1557/***/ }, 1558/* 3 */ 1559/***/ function(module, exports, __webpack_require__) { 1560 1561 /* WEBPACK VAR INJECTION */(function(module) {//! moment.js 1562 //! version : 2.10.6 1563 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors 1564 //! license : MIT 1565 //! momentjs.com 1566 1567 (function (global, factory) { 1568 true ? module.exports = factory() : 1569 typeof define === 'function' && define.amd ? define(factory) : 1570 global.moment = factory() 1571 }(this, function () { 'use strict'; 1572 1573 var hookCallback; 1574 1575 function utils_hooks__hooks () { 1576 return hookCallback.apply(null, arguments); 1577 } 1578 1579 // This is done to register the method called with moment() 1580 // without creating circular dependencies. 1581 function setHookCallback (callback) { 1582 hookCallback = callback; 1583 } 1584 1585 function isArray(input) { 1586 return Object.prototype.toString.call(input) === '[object Array]'; 1587 } 1588 1589 function isDate(input) { 1590 return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; 1591 } 1592 1593 function map(arr, fn) { 1594 var res = [], i; 1595 for (i = 0; i < arr.length; ++i) { 1596 res.push(fn(arr[i], i)); 1597 } 1598 return res; 1599 } 1600 1601 function hasOwnProp(a, b) { 1602 return Object.prototype.hasOwnProperty.call(a, b); 1603 } 1604 1605 function extend(a, b) { 1606 for (var i in b) { 1607 if (hasOwnProp(b, i)) { 1608 a[i] = b[i]; 1609 } 1610 } 1611 1612 if (hasOwnProp(b, 'toString')) { 1613 a.toString = b.toString; 1614 } 1615 1616 if (hasOwnProp(b, 'valueOf')) { 1617 a.valueOf = b.valueOf; 1618 } 1619 1620 return a; 1621 } 1622 1623 function create_utc__createUTC (input, format, locale, strict) {
1624 return createLocalOrUTC(input, format, locale, strict, true).utc(); 1625 } 1626 1627 function defaultParsingFlags() { 1628 // We need to deep clone this object. 1629 return { 1630 empty : false, 1631 unusedTokens : [], 1632 unusedInput : [], 1633 overflow : -2, 1634 charsLeftOver : 0, 1635 nullInput : false, 1636 invalidMonth : null, 1637 invalidFormat : false, 1638 userInvalidated : false, 1639 iso : false 1640 }; 1641 } 1642 1643 function getParsingFlags(m) { 1644 if (m._pf == null) { 1645 m._pf = defaultParsingFlags(); 1646 } 1647 return m._pf; 1648 } 1649 1650 function valid__isValid(m) { 1651 if (m._isValid == null) { 1652 var flags = getParsingFlags(m); 1653 m._isValid = !isNaN(m._d.getTime()) && 1654 flags.overflow < 0 && 1655 !flags.empty && 1656 !flags.invalidMonth && 1657 !flags.invalidWeekday && 1658 !flags.nullInput && 1659 !flags.invalidFormat && 1660 !flags.userInvalidated; 1661 1662 if (m._strict) { 1663 m._isValid = m._isValid && 1664 flags.charsLeftOver === 0 && 1665 flags.unusedTokens.length === 0 && 1666 flags.bigHour === undefined; 1667 } 1668 } 1669 return m._isValid; 1670 } 1671 1672 function valid__createInvalid (flags) { 1673 var m = create_utc__createUTC(NaN); 1674 if (flags != null) { 1675 extend(getParsingFlags(m), flags); 1676 } 1677 else { 1678 getParsingFlags(m).userInvalidated = true; 1679 } 1680 1681 return m; 1682 } 1683 1684 var momentProperties = utils_hooks__hooks.momentProperties = []; 1685 1686 function copyConfig(to, from) { 1687 var i, prop, val; 1688 1689 if (typeof from._isAMomentObject !== 'undefined') { 1690 to._isAMomentObject = from._isAMomentObject; 1691 } 1692 if (typeof from._i !== 'undefined') { 1693 to._i = from._i; 1694 } 1695 if (typeof from._f !== 'undefined') { 1696 to._f = from._f; 1697 } 1698 if (typeof from._l !== 'undefined') { 1699 to._l = from._l; 1700 } 1701 if (typeof from._strict !== 'undefined') { 1702 to._strict = from._strict; 1703 } 1704 if (typeof from._tzm !== 'undefined') { 1705 to._tzm = from._tzm; 1706 } 1707 if (typeof from._isUTC !== 'undefined') { 1708 to._isUTC = from._isUTC; 1709 } 1710 if (typeof from._offset !== 'undefined') { 1711 to._offset = from._offset; 1712 } 1713 if (typeof from._pf !== 'undefined') { 1714 to._pf = getParsingFlags(from); 1715 } 1716 if (typeof from._locale !== 'undefined') { 1717 to._locale = from._locale; 1718 } 1719 1720 if (momentProperties.length > 0) { 1721 for (i in momentProperties) { 1722 prop = momentProperties[i]; 1723 val = from[prop]; 1724 if (typeof val !== 'undefined') { 1725 to[prop] = val; 1726 } 1727 } 1728 } 1729 1730 return to; 1731 } 1732 1733 var updateInProgress = false; 1734 1735 // Moment prototype object 1736 function Moment(config) { 1737 copyConfig(this, config); 1738 this._d = new Date(config._d != null ? config._d.getTime() : NaN); 1739 // Prevent infinite loop in case updateOffset creates new moment 1740 // objects. 1741 if (updateInProgress === false) { 1742 updateInProgress = true; 1743 utils_hooks__hooks.updateOffset(this); 1744 updateInProgress = false; 1745 } 1746 } 1747 1748 function isMoment (obj) { 1749 return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); 1750 } 1751 1752 function absFloor (number) { 1753 if (number < 0) { 1754 return Math.ceil(number); 1755 } else { 1756 return Math.floor(number); 1757 } 1758 } 1759 1760 function toInt(argumentForCoercion) { 1761 var coercedNumber = +argumentForCoercion, 1762 value = 0; 1763 1764 if (coercedNumber !== 0 && isFinite(coercedNumber)) { 1765 value = absFloor(coercedNumber); 1766 } 1767 1768 return value; 1769 } 1770 1771 function compareArrays(array1, array2, dontConvert) { 1772 var len = Math.min(array1.length, array2.length), 1773 lengthDiff = Math.abs(array1.length - array2.length), 1774 diffs = 0, 1775 i; 1776 for (i = 0; i < len; i++) { 1777 if ((dontConvert && array1[i] !== array2[i]) || 1778 (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { 1779 diffs++; 1780 } 1781 } 1782 return diffs + lengthDiff; 1783 } 1784 1785 function Locale() { 1786 } 1787 1788 var locales = {}; 1789 var globalLocale; 1790 1791 function normalizeLocale(key) { 1792 return key ? key.toLowerCase().replace('_', '-') : key; 1793 } 1794 1795 // pick the locale from the array 1796 // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list tr
1796ying each 1797 // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root 1798 function chooseLocale(names) { 1799 var i = 0, j, next, locale, split; 1800 1801 while (i < names.length) { 1802 split = normalizeLocale(names[i]).split('-'); 1803 j = split.length; 1804 next = normalizeLocale(names[i + 1]); 1805 next = next ? next.split('-') : null; 1806 while (j > 0) { 1807 locale = loadLocale(split.slice(0, j).join('-')); 1808 if (locale) { 1809 return locale; 1810 } 1811 if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { 1812 //the next array item is better than a shallower substring of this one 1813 break; 1814 } 1815 j--; 1816 } 1817 i++; 1818 } 1819 return null; 1820 } 1821 1822 function loadLocale(name) { 1823 var oldLocale = null; 1824 // TODO: Find a better way to register and load all the locales in Node 1825 if (!locales[name] && typeof module !== 'undefined' && 1826 module && module.exports) { 1827 try { 1828 oldLocale = globalLocale._abbr; 1829 !(function webpackMissingModule() { var e = new Error("Cannot find module \"./locale\""); e.code = 'MODULE_NOT_FOUND'; throw e; }()); 1830 // because defineLocale currently also sets the global locale, we 1831 // want to undo that for lazy loaded locales 1832 locale_locales__getSetGlobalLocale(oldLocale); 1833 } catch (e) { } 1834 } 1835 return locales[name]; 1836 } 1837 1838 // This function will load locale and then set the global locale. If 1839 // no arguments are passed in, it will simply return the current global 1840 // locale key. 1841 function locale_locales__getSetGlobalLocale (key, values) { 1842 var data; 1843 if (key) { 1844 if (typeof values === 'undefined') { 1845 data = locale_locales__getLocale(key); 1846 } 1847 else { 1848 data = defineLocale(key, values); 1849 } 1850 1851 if (data) { 1852 // moment.duration._locale = moment._locale = data; 1853 globalLocale = data; 1854 } 1855 } 1856 1857 return globalLocale._abbr; 1858 } 1859 1860 function defineLocale (name, values) { 1861 if (values !== null) { 1862 values.abbr = name; 1863 locales[name] = locales[name] || new Locale(); 1864 locales[name].set(values); 1865 1866 // backwards compat for now: also set the locale 1867 locale_locales__getSetGlobalLocale(name); 1868 1869 return locales[name]; 1870 } else { 1871 // useful for testing 1872 delete locales[name]; 1873 return null; 1874 } 1875 } 1876 1877 // returns locale data 1878 function locale_locales__getLocale (key) { 1879 var locale; 1880 1881 if (key && key._locale && key._locale._abbr) { 1882 key = key._locale._abbr; 1883 } 1884 1885 if (!key) { 1886 return globalLocale; 1887 } 1888 1889 if (!isArray(key)) { 1890 //short-circuit everything else 1891 locale = loadLocale(key); 1892 if (locale) { 1893 return locale; 1894 } 1895 key = [key]; 1896 } 1897 1898 return chooseLocale(key); 1899 } 1900 1901 var aliases = {}; 1902 1903 function addUnitAlias (unit, shorthand) { 1904 var lowerCase = unit.toLowerCase(); 1905 aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; 1906 } 1907 1908 function normalizeUnits(units) { 1909 return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; 1910 } 1911 1912 function normalizeObjectUnits(inputObject) { 1913 var normalizedInput = {}, 1914 normalizedProp, 1915 prop; 1916 1917 for (prop in inputObject) { 1918 if (hasOwnProp(inputObject, prop)) { 1919 normalizedProp = normalizeUnits(prop); 1920 if (normalizedProp) { 1921 normalizedInput[normalizedProp] = inputObject[prop]; 1922 } 1923 } 1924 } 1925 1926 return normalizedInput; 1927 } 1928 1929 function makeGetSet (unit, keepTime) { 1930 return function (value) { 1931 if (value != null) { 1932 get_set__set(this, unit, value); 1933 utils_hooks__hooks.updateOffset(this, keepTime); 1934 return this; 1935 } else { 1936 return get_set__get(this, unit); 1937 } 1938 }; 1939 } 1940 1941 function get_set__get (mom, unit) { 1942 return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); 1943 } 1944 1945 function get_set__set (mom, unit, value) { 1946 return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); 1947 } 1948 1949 // MOMENTS 1950 1951 function getSet (units, value) { 1952 var unit; 1953 if (typeof units === 'object') { 1954 for (unit in units) { 1955 this.set(unit, units[unit]); 1956 } 1957 } else { 1958 units = normalizeUnits(units); 1959 if (typeof this[units] === 'function') { 1960 return this[units](value); 1961 } 1962 } 1963 return this; 1964 } 1965 1966 function zeroFill(number, targetLength, forceSign) { 1967 var absNumber = '' + Math.abs(number), 1968 zerosToFill = targetLength - absNumber.length, 1969 sign = number >= 0; 1970 return (sign ? (forceSign ? '+' : '') : '-') + 1971 Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; 1972 } 1973 1974 var 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,9}
vendor: 11,529 bytes, lines 1974-2307
1974|x|X|zz?|ZZ?|.)/g; 1975 1976 var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; 1977 1978 var formatFunctions = {}; 1979 1980 var formatTokenFunctions = {}; 1981 1982 // token: 'M' 1983 // padded: ['MM', 2] 1984 // ordinal: 'Mo' 1985 // callback: function () { this.month() + 1 } 1986 function addFormatToken (token, padded, ordinal, callback) { 1987 var func = callback; 1988 if (typeof callback === 'string') { 1989 func = function () { 1990 return this[callback](); 1991 }; 1992 } 1993 if (token) { 1994 formatTokenFunctions[token] = func; 1995 } 1996 if (padded) { 1997 formatTokenFunctions[padded[0]] = function () { 1998 return zeroFill(func.apply(this, arguments), padded[1], padded[2]); 1999 }; 2000 } 2001 if (ordinal) { 2002 formatTokenFunctions[ordinal] = function () { 2003 return this.localeData().ordinal(func.apply(this, arguments), token); 2004 }; 2005 } 2006 } 2007 2008 function removeFormattingTokens(input) { 2009 if (input.match(/\[[\s\S]/)) { 2010 return input.replace(/^\[|\]$/g, ''); 2011 } 2012 return input.replace(/\\/g, ''); 2013 } 2014 2015 function makeFormatFunction(format) { 2016 var array = format.match(formattingTokens), i, length; 2017 2018 for (i = 0, length = array.length; i < length; i++) { 2019 if (formatTokenFunctions[array[i]]) { 2020 array[i] = formatTokenFunctions[array[i]]; 2021 } else { 2022 array[i] = removeFormattingTokens(array[i]); 2023 } 2024 } 2025 2026 return function (mom) { 2027 var output = ''; 2028 for (i = 0; i < length; i++) { 2029 output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; 2030 } 2031 return output; 2032 }; 2033 } 2034 2035 // format date using native date object 2036 function formatMoment(m, format) { 2037 if (!m.isValid()) { 2038 return m.localeData().invalidDate(); 2039 } 2040 2041 format = expandFormat(format, m.localeData()); 2042 formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format); 2043 2044 return formatFunctions[format](m); 2045 } 2046 2047 function expandFormat(format, locale) { 2048 var i = 5; 2049 2050 function replaceLongDateFormatTokens(input) { 2051 return locale.longDateFormat(input) || input; 2052 } 2053 2054 localFormattingTokens.lastIndex = 0; 2055 while (i >= 0 && localFormattingTokens.test(format)) { 2056 format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); 2057 localFormattingTokens.lastIndex = 0; 2058 i -= 1; 2059 } 2060 2061 return format; 2062 } 2063 2064 var match1 = /\d/; // 0 - 9 2065 var match2 = /\d\d/; // 00 - 99 2066 var match3 = /\d{3}/; // 000 - 999 2067 var match4 = /\d{4}/; // 0000 - 9999 2068 var match6 = /[+-]?\d{6}/; // -999999 - 999999 2069 var match1to2 = /\d\d?/; // 0 - 99 2070 var match1to3 = /\d{1,3}/; // 0 - 999 2071 var match1to4 = /\d{1,4}/; // 0 - 9999 2072 var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 2073 2074 var matchUnsigned = /\d+/; // 0 - inf 2075 var matchSigned = /[+-]?\d+/; // -inf - inf 2076 2077 var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z 2078 2079 var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 2080 2081 // any word (or two) characters or numbers including two/three word month in arabic. 2082 var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; 2083 2084 var regexes = {}; 2085 2086 function isFunction (sth) { 2087 // https://github.com/moment/moment/issues/2325 2088 return typeof sth === 'function' && 2089 Object.prototype.toString.call(sth) === '[object Function]'; 2090 } 2091 2092 2093 function addRegexToken (token, regex, strictRegex) { 2094 regexes[token] = isFunction(regex) ? regex : function (isStrict) { 2095 return (isStrict && strictRegex) ? strictRegex : regex; 2096 }; 2097 } 2098 2099 function getParseRegexForToken (token, config) { 2100 if (!hasOwnProp(regexes, token)) { 2101 return new RegExp(unescapeFormat(token)); 2102 } 2103 2104 return regexes[token](config._strict, config._locale); 2105 } 2106 2107 // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript 2108 function unescapeFormat(s) { 2109 return s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { 2110 return p1 || p2 || p3 || p4; 2111 }).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); 2112 } 2113 2114 var tokens = {}; 2115 2116 function addParseToken (token, callback) { 2117 var i, func = callback; 2118 if (typeof token === 'string') { 2119 token = [token]; 2120 } 2121 if (typeof callback === 'number') { 2122 func = function (input, array) { 2123 array[callback] = toInt(input); 2124 }; 2125 } 2126 for (i = 0; i < token.length; i++) { 2127 tokens[token[i]] = func; 2128 } 2129 } 2130 2131 function addWeekParseToken (token, callback) { 2132 addParseToken(token, function (input, array, config, token) { 2133 config._w = config._w || {}; 2134 callback(input, config._w, config, token); 2135 }); 2136 } 2137 2138 function addTimeToArrayFromToken(token, input, config) { 2139 if (input != null && hasOwnProp(tokens, token)) { 2140 tokens[token](input, config._a, config, token); 2141 } 2142 } 2143 2144 var YEAR = 0; 2145 var MONTH = 1; 2146 var DATE = 2; 2147 var HOUR = 3; 2148 var MINUTE = 4; 2149 var SECOND = 5; 2150 var MILLISECOND = 6; 2151 2152 function daysInMonth(year, month) { 2153 return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); 2154 } 2155 2156 // FORMATTING 2157 2158 addFormatToken('M', ['MM', 2], 'Mo', function () { 2159 return this.month() + 1; 2160 }); 2161 2162 addFormatToken('MMM', 0, 0, function (format) { 2163 return this.localeData().monthsShort(this, format); 2164 }); 2165 2166 addFormatToken('MMMM', 0, 0, function (format) { 2167 return this.localeData().months(this, format); 2168 }); 2169 2170 // ALIASES 2171 2172 addUnitAlias('month', 'M'); 2173 2174 // PARSING 2175 2176 addRegexToken('M', match1to2); 2177 addRegexToken('MM', match1to2, match2); 2178 addRegexToken('MMM', matchWord); 2179 addRegexToken('MMMM', matchWord); 2180 2181 addParseToken(['M', 'MM'], function (input, array) { 2182 array[MONTH] = toInt(input) - 1; 2183 }); 2184 2185 addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { 2186 var month = config._locale.monthsParse(input, token, config._strict); 2187 // if we didn't find a month name, mark the date as invalid. 2188 if (month != null) { 2189 array[MONTH] = month; 2190 } else { 2191 getParsingFlags(config).invalidMonth = input; 2192 } 2193 }); 2194 2195 // LOCALES 2196 2197 var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); 2198 function localeMonths (m) { 2199 return this._months[m.month()]; 2200 } 2201 2202 var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); 2203 function localeMonthsShort (m) { 2204 return this._monthsShort[m.month()]; 2205 } 2206 2207 function localeMonthsParse (monthName, format, strict) { 2208 var i, mom, regex; 2209 2210 if (!this._monthsParse) { 2211 this._monthsParse = []; 2212 this._longMonthsParse = []; 2213 this._shortMonthsParse = []; 2214 } 2215 2216 for (i = 0; i < 12; i++) { 2217 // make the regex if we don't have it already 2218 mom = create_utc__createUTC([2000, i]); 2219 if (strict && !this._longMonthsParse[i]) { 2220 this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); 2221 this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); 2222 } 2223 if (!strict && !this._monthsParse[i]) { 2224 regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); 2225 this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); 2226 } 2227 // test the regex 2228 if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { 2229 return i; 2230 } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { 2231 return i; 2232 } else if (!strict && this._monthsParse[i].test(monthName)) { 2233 return i; 2234 } 2235 } 2236 } 2237 2238 // MOMENTS 2239 2240 function setMonth (mom, value) { 2241 var dayOfMonth; 2242 2243 // TODO: Move this out of here! 2244 if (typeof value === 'string') { 2245 value = mom.localeData().monthsParse(value); 2246 // TODO: Another silent failure? 2247 if (typeof value !== 'number') { 2248 return mom; 2249 } 2250 } 2251 2252 dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); 2253 mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); 2254 return mom; 2255 } 2256 2257 function getSetMonth (value) { 2258 if (value != null) { 2259 setMonth(this, value); 2260 utils_hooks__hooks.updateOffset(this, true); 2261 return this; 2262 } else { 2263 return get_set__get(this, 'Month'); 2264 } 2265 } 2266 2267 function getDaysInMonth () { 2268 return daysInMonth(this.year(), this.month()); 2269 } 2270 2271 function checkOverflow (m) { 2272 var overflow; 2273 var a = m._a; 2274 2275 if (a && getParsingFlags(m).overflow === -2) { 2276 overflow = 2277 a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : 2278 a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : 2279 a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : 2280 a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : 2281 a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : 2282 a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : 2283 -1; 2284 2285 if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { 2286 overflow = DATE; 2287 } 2288 2289 getParsingFlags(m).overflow = overflow; 2290 } 2291 2292 return m; 2293 } 2294 2295 function warn(msg) { 2296 if (utils_hooks__hooks.suppressDeprecationWarnings === false && typeof console !== 'undefined' && console.warn) { 2297 console.warn('Deprecation warning: ' + msg); 2298 } 2299 } 2300 2301 function deprecate(msg, fn) { 2302 var firstTime = true; 2303 2304 return extend(function () { 2305 if (firstTime) { 2306 warn(msg + '\n' + (new Error()).stack); 2307 firstTime = false;
2308 } 2309 return fn.apply(this, arguments); 2310 }, fn); 2311 } 2312 2313 var deprecations = {}; 2314 2315 function deprecateSimple(name, msg) { 2316 if (!deprecations[name]) { 2317 warn(msg); 2318 deprecations[name] = true; 2319 } 2320 } 2321 2322 utils_hooks__hooks.suppressDeprecationWarnings = false; 2323 2324 var from_string__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)?)?$/; 2325 2326 var isoDates = [ 2327 ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/], 2328 ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/], 2329 ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/], 2330 ['GGGG-[W]WW', /\d{4}-W\d{2}/], 2331 ['YYYY-DDD', /\d{4}-\d{3}/] 2332 ]; 2333 2334 // iso time formats and regexes 2335 var isoTimes = [ 2336 ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/], 2337 ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], 2338 ['HH:mm', /(T| )\d\d:\d\d/], 2339 ['HH', /(T| )\d\d/] 2340 ]; 2341 2342 var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; 2343 2344 // date from iso format 2345 function configFromISO(config) { 2346 var i, l, 2347 string = config._i, 2348 match = from_string__isoRegex.exec(string); 2349 2350 if (match) { 2351 getParsingFlags(config).iso = true; 2352 for (i = 0, l = isoDates.length; i < l; i++) { 2353 if (isoDates[i][1].exec(string)) { 2354 config._f = isoDates[i][0]; 2355 break; 2356 } 2357 } 2358 for (i = 0, l = isoTimes.length; i < l; i++) { 2359 if (isoTimes[i][1].exec(string)) { 2360 // match[6] should be 'T' or space 2361 config._f += (match[6] || ' ') + isoTimes[i][0]; 2362 break; 2363 } 2364 } 2365 if (string.match(matchOffset)) { 2366 config._f += 'Z'; 2367 } 2368 configFromStringAndFormat(config); 2369 } else { 2370 config._isValid = false; 2371 } 2372 } 2373 2374 // date from iso format or fallback 2375 function configFromString(config) { 2376 var matched = aspNetJsonRegex.exec(config._i); 2377 2378 if (matched !== null) { 2379 config._d = new Date(+matched[1]); 2380 return; 2381 } 2382 2383 configFromISO(config); 2384 if (config._isValid === false) { 2385 delete config._isValid; 2386 utils_hooks__hooks.createFromInputFallback(config); 2387 } 2388 } 2389 2390 utils_hooks__hooks.createFromInputFallback = deprecate( 2391 'moment construction falls back to js Date. This is ' + 2392 'discouraged and will be removed in upcoming major ' + 2393 'release. Please refer to ' + 2394 'https://github.com/moment/moment/issues/1407 for more info.', 2395 function (config) { 2396 config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); 2397 } 2398 ); 2399 2400 function createDate (y, m, d, h, M, s, ms) { 2401 //can't just apply() to create a date: 2402 //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply 2403 var date = new Date(y, m, d, h, M, s, ms); 2404 2405 //the date constructor doesn't accept years < 1970 2406 if (y < 1970) { 2407 date.setFullYear(y); 2408 } 2409 return date; 2410 } 2411 2412 function createUTCDate (y) { 2413 var date = new Date(Date.UTC.apply(null, arguments)); 2414 if (y < 1970) { 2415 date.setUTCFullYear(y); 2416 } 2417 return date; 2418 } 2419 2420 addFormatToken(0, ['YY', 2], 0, function () { 2421 return this.year() % 100; 2422 }); 2423 2424 addFormatToken(0, ['YYYY', 4], 0, 'year'); 2425 addFormatToken(0, ['YYYYY', 5], 0, 'year'); 2426 addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); 2427 2428 // ALIASES 2429 2430 addUnitAlias('year', 'y'); 2431 2432 // PARSING 2433 2434 addRegexToken('Y', matchSigned); 2435 addRegexToken('YY', match1to2, match2); 2436 addRegexToken('YYYY', match1to4, match4); 2437 addRegexToken('YYYYY', match1to6, match6); 2438 addRegexToken('YYYYYY', match1to6, match6); 2439 2440 addParseToken(['YYYYY', 'YYYYYY'], YEAR); 2441 addParseToken('YYYY', function (input, array) { 2442 array[YEAR] = input.length === 2 ? utils_hooks__hooks.parseTwoDigitYear(input) : toInt(input); 2443 }); 2444 addParseToken('YY', function (input, array) { 2445 array[YEAR] = utils_hooks__hooks.parseTwoDigitYear(input); 2446 }); 2447 2448 // HELPERS 2449 2450 function daysInYear(year) { 2451 return isLeapYear(year) ? 366 : 365; 2452 } 2453 2454 function isLeapYear(year) { 2455 return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; 2456 } 2457 2458 // HOOKS 2459 2460 utils_hooks__hooks.parseTwoDigitYear = function (input) { 2461 return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); 2462 }; 2463 2464 // MOMENTS 2465 2466 var getSetYear = makeGetSet('FullYear', false); 2467 2468 function getIsLeapYear () { 2469 return isLeapYear(this.year()); 2470 } 2471 2472 addFormatToken('w', ['ww', 2], 'wo', 'week'); 2473 addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); 2474 2475 // ALIASES 2476 2477 addUnitAlias('week', 'w'); 2478 addUnitAlias('isoWeek', 'W'); 2479 2480 // PARSING 2481 2482 addRegexToken('w', match1to2); 2483 addRegexToken('ww', match1to2, match2); 2484 addRegexToken('W', match1to2); 2485 addRegexToken('WW', match1to2, match2); 2486
2487 addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { 2488 week[token.substr(0, 1)] = toInt(input); 2489 }); 2490 2491 // HELPERS 2492 2493 // firstDayOfWeek 0 = sun, 6 = sat 2494 // the day of the week that starts the week 2495 // (usually sunday or monday) 2496 // firstDayOfWeekOfYear 0 = sun, 6 = sat 2497 // the first week is the week that contains the first 2498 // of this day of the week 2499 // (eg. ISO weeks use thursday (4)) 2500 function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { 2501 var end = firstDayOfWeekOfYear - firstDayOfWeek, 2502 daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), 2503 adjustedMoment; 2504 2505 2506 if (daysToDayOfWeek > end) { 2507 daysToDayOfWeek -= 7; 2508 } 2509 2510 if (daysToDayOfWeek < end - 7) { 2511 daysToDayOfWeek += 7; 2512 } 2513 2514 adjustedMoment = local__createLocal(mom).add(daysToDayOfWeek, 'd'); 2515 return { 2516 week: Math.ceil(adjustedMoment.dayOfYear() / 7), 2517 year: adjustedMoment.year() 2518 }; 2519 } 2520 2521 // LOCALES 2522 2523 function localeWeek (mom) { 2524 return weekOfYear(mom, this._week.dow, this._week.doy).week; 2525 } 2526 2527 var defaultLocaleWeek = { 2528 dow : 0, // Sunday is the first day of the week. 2529 doy : 6 // The week that contains Jan 1st is the first week of the year. 2530 }; 2531 2532 function localeFirstDayOfWeek () { 2533 return this._week.dow; 2534 } 2535 2536 function localeFirstDayOfYear () { 2537 return this._week.doy; 2538 } 2539 2540 // MOMENTS 2541 2542 function getSetWeek (input) { 2543 var week = this.localeData().week(this); 2544 return input == null ? week : this.add((input - week) * 7, 'd'); 2545 } 2546 2547 function getSetISOWeek (input) { 2548 var week = weekOfYear(this, 1, 4).week; 2549 return input == null ? week : this.add((input - week) * 7, 'd'); 2550 } 2551 2552 addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); 2553 2554 // ALIASES 2555 2556 addUnitAlias('dayOfYear', 'DDD'); 2557 2558 // PARSING 2559 2560 addRegexToken('DDD', match1to3); 2561 addRegexToken('DDDD', match3); 2562 addParseToken(['DDD', 'DDDD'], function (input, array, config) { 2563 config._dayOfYear = toInt(input); 2564 }); 2565 2566 // HELPERS 2567 2568 //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday 2569 function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { 2570 var week1Jan = 6 + firstDayOfWeek - firstDayOfWeekOfYear, janX = createUTCDate(year, 0, 1 + week1Jan), d = janX.getUTCDay(), dayOfYear; 2571 if (d < firstDayOfWeek) { 2572 d += 7; 2573 } 2574 2575 weekday = weekday != null ? 1 * weekday : firstDayOfWeek; 2576 2577 dayOfYear = 1 + week1Jan + 7 * (week - 1) - d + weekday; 2578 2579 return { 2580 year: dayOfYear > 0 ? year : year - 1, 2581 dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear 2582 }; 2583 } 2584 2585 // MOMENTS 2586 2587 function getSetDayOfYear (input) { 2588 var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; 2589 return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); 2590 } 2591 2592 // Pick the first defined of two or three arguments. 2593 function defaults(a, b, c) { 2594 if (a != null) { 2595 return a; 2596 } 2597 if (b != null) { 2598 return b; 2599 } 2600 return c; 2601 } 2602 2603 function currentDateArray(config) { 2604 var now = new Date(); 2605 if (config._useUTC) { 2606 return [now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()]; 2607 } 2608 return [now.getFullYear(), now.getMonth(), now.getDate()]; 2609 } 2610 2611 // convert an array to a date. 2612 // the array should mirror the parameters below 2613 // note: all values past the year are optional and will default to the lowest possible value. 2614 // [year, month, day , hour, minute, second, millisecond] 2615 function configFromArray (config) { 2616 var i, date, input = [], currentDate, yearToUse; 2617 2618 if (config._d) { 2619 return; 2620 } 2621 2622 currentDate = currentDateArray(config); 2623 2624 //compute day of the year from weeks and weekdays 2625 if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { 2626 dayOfYearFromWeekInfo(config); 2627 } 2628 2629 //if the day of the year is set, figure out what it is 2630 if (config._dayOfYear) { 2631 yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); 2632 2633 if (config._dayOfYear > daysInYear(yearToUse)) { 2634 getParsingFlags(config)._overflowDayOfYear = true; 2635 } 2636 2637 date = createUTCDate(yearToUse, 0, config._dayOfYear); 2638 config._a[MONTH] = date.getUTCMonth(); 2639 config._a[DATE] = date.getUTCDate(); 2640 } 2641 2642 // Default to current date. 2643 // * if no year, month, day of month are given, default to today 2644 // * if day of month is given, default month and year 2645 // * if month is given, default only year 2646 // * if year is given, don't default anything 2647 for (i = 0; i < 3 && config._a[i] == null; ++i) {
vendor: 16,336 bytes, lines 2648-3116
2648 config._a[i] = input[i] = currentDate[i]; 2649 } 2650 2651 // Zero out whatever was not defaulted, including time 2652 for (; i < 7; i++) { 2653 config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; 2654 } 2655 2656 // Check for 24:00:00.000 2657 if (config._a[HOUR] === 24 && 2658 config._a[MINUTE] === 0 && 2659 config._a[SECOND] === 0 && 2660 config._a[MILLISECOND] === 0) { 2661 config._nextDay = true; 2662 config._a[HOUR] = 0; 2663 } 2664 2665 config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); 2666 // Apply timezone offset from input. The actual utcOffset can be changed 2667 // with parseZone. 2668 if (config._tzm != null) { 2669 config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); 2670 } 2671 2672 if (config._nextDay) { 2673 config._a[HOUR] = 24; 2674 } 2675 } 2676 2677 function dayOfYearFromWeekInfo(config) { 2678 var w, weekYear, week, weekday, dow, doy, temp; 2679 2680 w = config._w; 2681 if (w.GG != null || w.W != null || w.E != null) { 2682 dow = 1; 2683 doy = 4; 2684 2685 // TODO: We need to take the current isoWeekYear, but that depends on 2686 // how we interpret now (local, utc, fixed offset). So create 2687 // a now version of current config (take local/utc/offset flags, and 2688 // create now). 2689 weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(local__createLocal(), 1, 4).year); 2690 week = defaults(w.W, 1); 2691 weekday = defaults(w.E, 1); 2692 } else { 2693 dow = config._locale._week.dow; 2694 doy = config._locale._week.doy; 2695 2696 weekYear = defaults(w.gg, config._a[YEAR], weekOfYear(local__createLocal(), dow, doy).year); 2697 week = defaults(w.w, 1); 2698 2699 if (w.d != null) { 2700 // weekday -- low day numbers are considered next week 2701 weekday = w.d; 2702 if (weekday < dow) { 2703 ++week; 2704 } 2705 } else if (w.e != null) { 2706 // local weekday -- counting starts from begining of week 2707 weekday = w.e + dow; 2708 } else { 2709 // default to begining of week 2710 weekday = dow; 2711 } 2712 } 2713 temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow); 2714 2715 config._a[YEAR] = temp.year; 2716 config._dayOfYear = temp.dayOfYear; 2717 } 2718 2719 utils_hooks__hooks.ISO_8601 = function () {}; 2720 2721 // date from string and format string 2722 function configFromStringAndFormat(config) { 2723 // TODO: Move this to another part of the creation flow to prevent circular deps 2724 if (config._f === utils_hooks__hooks.ISO_8601) { 2725 configFromISO(config); 2726 return; 2727 } 2728 2729 config._a = []; 2730 getParsingFlags(config).empty = true; 2731 2732 // This array is used to make a Date, either with `new Date` or `Date.UTC` 2733 var string = '' + config._i, 2734 i, parsedInput, tokens, token, skipped, 2735 stringLength = string.length, 2736 totalParsedInputLength = 0; 2737 2738 tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; 2739 2740 for (i = 0; i < tokens.length; i++) { 2741 token = tokens[i]; 2742 parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; 2743 if (parsedInput) { 2744 skipped = string.substr(0, string.indexOf(parsedInput)); 2745 if (skipped.length > 0) { 2746 getParsingFlags(config).unusedInput.push(skipped); 2747 } 2748 string = string.slice(string.indexOf(parsedInput) + parsedInput.length); 2749 totalParsedInputLength += parsedInput.length; 2750 } 2751 // don't parse if it's not a known token 2752 if (formatTokenFunctions[token]) { 2753 if (parsedInput) { 2754 getParsingFlags(config).empty = false; 2755 } 2756 else { 2757 getParsingFlags(config).unusedTokens.push(token); 2758 } 2759 addTimeToArrayFromToken(token, parsedInput, config); 2760 } 2761 else if (config._strict && !parsedInput) { 2762 getParsingFlags(config).unusedTokens.push(token); 2763 } 2764 } 2765 2766 // add remaining unparsed input length to the string 2767 getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; 2768 if (string.length > 0) { 2769 getParsingFlags(config).unusedInput.push(string); 2770 } 2771 2772 // clear _12h flag if hour is <= 12 2773 if (getParsingFlags(config).bigHour === true && 2774 config._a[HOUR] <= 12 && 2775 config._a[HOUR] > 0) { 2776 getParsingFlags(config).bigHour = undefined; 2777 } 2778 // handle meridiem 2779 config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); 2780 2781 configFromArray(config); 2782 checkOverflow(config); 2783 } 2784 2785 2786 function meridiemFixWrap (locale, hour, meridiem) { 2787 var isPm; 2788 2789 if (meridiem == null) { 2790 // nothing to do 2791 return hour; 2792 } 2793 if (locale.meridiemHour != null) { 2794 return locale.meridiemHour(hour, meridiem); 2795 } else if (locale.isPM != null) { 2796 // Fallback 2797 isPm = locale.isPM(meridiem); 2798 if (isPm && hour < 12) { 2799 hour += 12; 2800 } 2801 if (!isPm && hour === 12) { 2802 hour = 0; 2803 } 2804 return hour; 2805 } else { 2806 // this is not supposed to happen 2807 return hour; 2808 } 2809 } 2810 2811 function configFromStringAndArray(config) { 2812 var tempConfig, 2813 bestMoment, 2814 2815 scoreToBeat, 2816 i, 2817 currentScore; 2818 2819 if (config._f.length === 0) { 2820 getParsingFlags(config).invalidFormat = true; 2821 config._d = new Date(NaN); 2822 return; 2823 } 2824 2825 for (i = 0; i < config._f.length; i++) { 2826 currentScore = 0; 2827 tempConfig = copyConfig({}, config); 2828 if (config._useUTC != null) { 2829 tempConfig._useUTC = config._useUTC; 2830 } 2831 tempConfig._f = config._f[i]; 2832 configFromStringAndFormat(tempConfig); 2833 2834 if (!valid__isValid(tempConfig)) { 2835 continue; 2836 } 2837 2838 // if there is any input that was not parsed add a penalty for that format 2839 currentScore += getParsingFlags(tempConfig).charsLeftOver; 2840 2841 //or tokens 2842 currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; 2843 2844 getParsingFlags(tempConfig).score = currentScore; 2845 2846 if (scoreToBeat == null || currentScore < scoreToBeat) { 2847 scoreToBeat = currentScore; 2848 bestMoment = tempConfig; 2849 } 2850 } 2851 2852 extend(config, bestMoment || tempConfig); 2853 } 2854 2855 function configFromObject(config) { 2856 if (config._d) { 2857 return; 2858 } 2859 2860 var i = normalizeObjectUnits(config._i); 2861 config._a = [i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond]; 2862 2863 configFromArray(config); 2864 } 2865 2866 function createFromConfig (config) { 2867 var res = new Moment(checkOverflow(prepareConfig(config))); 2868 if (res._nextDay) { 2869 // Adding is smart enough around DST 2870 res.add(1, 'd'); 2871 res._nextDay = undefined; 2872 } 2873 2874 return res; 2875 } 2876 2877 function prepareConfig (config) { 2878 var input = config._i, 2879 format = config._f; 2880 2881 config._locale = config._locale || locale_locales__getLocale(config._l); 2882 2883 if (input === null || (format === undefined && input === '')) { 2884 return valid__createInvalid({nullInput: true}); 2885 } 2886 2887 if (typeof input === 'string') { 2888 config._i = input = config._locale.preparse(input); 2889 } 2890 2891 if (isMoment(input)) { 2892 return new Moment(checkOverflow(input)); 2893 } else if (isArray(format)) { 2894 configFromStringAndArray(config); 2895 } else if (format) { 2896 configFromStringAndFormat(config); 2897 } else if (isDate(input)) { 2898 config._d = input; 2899 } else { 2900 configFromInput(config); 2901 } 2902 2903 return config; 2904 } 2905 2906 function configFromInput(config) { 2907 var input = config._i; 2908 if (input === undefined) { 2909 config._d = new Date(); 2910 } else if (isDate(input)) { 2911 config._d = new Date(+input); 2912 } else if (typeof input === 'string') { 2913 configFromString(config); 2914 } else if (isArray(input)) { 2915 config._a = map(input.slice(0), function (obj) { 2916 return parseInt(obj, 10); 2917 }); 2918 configFromArray(config); 2919 } else if (typeof(input) === 'object') { 2920 configFromObject(config); 2921 } else if (typeof(input) === 'number') { 2922 // from milliseconds 2923 config._d = new Date(input); 2924 } else { 2925 utils_hooks__hooks.createFromInputFallback(config); 2926 } 2927 } 2928 2929 function createLocalOrUTC (input, format, locale, strict, isUTC) { 2930 var c = {}; 2931 2932 if (typeof(locale) === 'boolean') { 2933 strict = locale; 2934 locale = undefined; 2935 } 2936 // object construction must be done this way. 2937 // https://github.com/moment/moment/issues/1423 2938 c._isAMomentObject = true; 2939 c._useUTC = c._isUTC = isUTC; 2940 c._l = locale; 2941 c._i = input; 2942 c._f = format; 2943 c._strict = strict; 2944 2945 return createFromConfig(c); 2946 } 2947 2948 function local__createLocal (input, format, locale, strict) { 2949 return createLocalOrUTC(input, format, locale, strict, false); 2950 } 2951 2952 var prototypeMin = deprecate( 2953 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548', 2954 function () { 2955 var other = local__createLocal.apply(null, arguments); 2956 return other < this ? this : other; 2957 } 2958 ); 2959 2960 var prototypeMax = deprecate( 2961 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548', 2962 function () { 2963 var other = local__createLocal.apply(null, arguments); 2964 return other > this ? this : other; 2965 } 2966 ); 2967 2968 // Pick a moment m from moments so that m[fn](other) is true for all 2969 // other. This relies on the function fn to be transitive. 2970 // 2971 // moments should either be an array of moment objects or an array, whose 2972 // first element is an array of moment objects. 2973 function pickBy(fn, moments) { 2974 var res, i; 2975 if (moments.length === 1 && isArray(moments[0])) { 2976 moments = moments[0]; 2977 } 2978 if (!moments.length) { 2979 return local__createLocal(); 2980 } 2981 res = moments[0]; 2982 for (i = 1; i < moments.length; ++i) { 2983 if (!moments[i].isValid() || moments[i][fn](res)) { 2984 res = moments[i]; 2985 } 2986 } 2987 return res; 2988 } 2989 2990 // TODO: Use [].sort instead? 2991 function min () { 2992 var args = [].slice.call(arguments, 0); 2993 2994 return pickBy('isBefore', args); 2995 } 2996 2997 function max () { 2998 var args = [].slice.call(arguments, 0); 2999 3000 return pickBy('isAfter', args); 3001 } 3002 3003 function Duration (duration) { 3004 var normalizedInput = normalizeObjectUnits(duration), 3005 years = normalizedInput.year || 0, 3006 quarters = normalizedInput.quarter || 0, 3007 months = normalizedInput.month || 0, 3008 weeks = normalizedInput.week || 0, 3009 days = normalizedInput.day || 0, 3010 hours = normalizedInput.hour || 0, 3011 minutes = normalizedInput.minute || 0, 3012 seconds = normalizedInput.second || 0, 3013 milliseconds = normalizedInput.millisecond || 0; 3014 3015 // representation for dateAddRemove 3016 this._milliseconds = +milliseconds + 3017 seconds * 1e3 + // 1000 3018 minutes * 6e4 + // 1000 * 60 3019 hours * 36e5; // 1000 * 60 * 60 3020 // Because of dateAddRemove treats 24 hours as different from a 3021 // day when working around DST, we need to store them separately 3022 this._days = +days + 3023 weeks * 7; 3024 // It is impossible translate months into days without knowing 3025 // which months you are are talking about, so we have to store 3026 // it separately. 3027 this._months = +months + 3028 quarters * 3 + 3029 years * 12; 3030 3031 this._data = {}; 3032 3033 this._locale = locale_locales__getLocale(); 3034 3035 this._bubble(); 3036 } 3037 3038 function isDuration (obj) { 3039 return obj instanceof Duration; 3040 } 3041 3042 function offset (token, separator) { 3043 addFormatToken(token, 0, 0, function () { 3044 var offset = this.utcOffset(); 3045 var sign = '+'; 3046 if (offset < 0) { 3047 offset = -offset; 3048 sign = '-'; 3049 } 3050 return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); 3051 }); 3052 } 3053 3054 offset('Z', ':'); 3055 offset('ZZ', ''); 3056 3057 // PARSING 3058 3059 addRegexToken('Z', matchOffset); 3060 addRegexToken('ZZ', matchOffset); 3061 addParseToken(['Z', 'ZZ'], function (input, array, config) { 3062 config._useUTC = true; 3063 config._tzm = offsetFromString(input); 3064 }); 3065 3066 // HELPERS 3067 3068 // timezone chunker 3069 // '+10:00' > ['10', '00'] 3070 // '-1530' > ['-15', '30'] 3071 var chunkOffset = /([\+\-]|\d\d)/gi; 3072 3073 function offsetFromString(string) { 3074 var matches = ((string || '').match(matchOffset) || []); 3075 var chunk = matches[matches.length - 1] || []; 3076 var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; 3077 var minutes = +(parts[1] * 60) + toInt(parts[2]); 3078 3079 return parts[0] === '+' ? minutes : -minutes; 3080 } 3081 3082 // Return a moment from input, that is local/utc/zone equivalent to model. 3083 function cloneWithOffset(input, model) { 3084 var res, diff; 3085 if (model._isUTC) { 3086 res = model.clone(); 3087 diff = (isMoment(input) || isDate(input) ? +input : +local__createLocal(input)) - (+res); 3088 // Use low-level api, because this fn is low-level api. 3089 res._d.setTime(+res._d + diff); 3090 utils_hooks__hooks.updateOffset(res, false); 3091 return res; 3092 } else { 3093 return local__createLocal(input).local(); 3094 } 3095 } 3096 3097 function getDateOffset (m) { 3098 // On Firefox.24 Date#getTimezoneOffset returns a floating point. 3099 // https://github.com/moment/moment/pull/1871 3100 return -Math.round(m._d.getTimezoneOffset() / 15) * 15; 3101 } 3102 3103 // HOOKS 3104 3105 // This function will be called whenever a moment is mutated. 3106 // It is intended to keep the offset in sync with the timezone. 3107 utils_hooks__hooks.updateOffset = function () {}; 3108 3109 // MOMENTS 3110 3111 // keepLocalTime = true means only change the timezone, without 3112 // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> 3113 // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset 3114 // +0200, so we adjust the time as needed, to be valid. 3115 // 3116 // Keeping the time actually adds/subtracts (one hour)
3117 // from the actual represented time. That is why we call updateOffset 3118 // a second time. In case it wants us to change the offset again 3119 // _changeInProgress == true case, then we have to adjust, because 3120 // there is no such time in the given timezone. 3121 function getSetOffset (input, keepLocalTime) { 3122 var offset = this._offset || 0, 3123 localAdjust; 3124 if (input != null) { 3125 if (typeof input === 'string') { 3126 input = offsetFromString(input); 3127 } 3128 if (Math.abs(input) < 16) { 3129 input = input * 60; 3130 } 3131 if (!this._isUTC && keepLocalTime) { 3132 localAdjust = getDateOffset(this); 3133 } 3134 this._offset = input; 3135 this._isUTC = true; 3136 if (localAdjust != null) { 3137 this.add(localAdjust, 'm'); 3138 } 3139 if (offset !== input) { 3140 if (!keepLocalTime || this._changeInProgress) { 3141 add_subtract__addSubtract(this, create__createDuration(input - offset, 'm'), 1, false); 3142 } else if (!this._changeInProgress) { 3143 this._changeInProgress = true; 3144 utils_hooks__hooks.updateOffset(this, true); 3145 this._changeInProgress = null; 3146 } 3147 } 3148 return this; 3149 } else { 3150 return this._isUTC ? offset : getDateOffset(this); 3151 } 3152 } 3153 3154 function getSetZone (input, keepLocalTime) { 3155 if (input != null) { 3156 if (typeof input !== 'string') { 3157 input = -input; 3158 } 3159 3160 this.utcOffset(input, keepLocalTime); 3161 3162 return this; 3163 } else { 3164 return -this.utcOffset(); 3165 } 3166 } 3167 3168 function setOffsetToUTC (keepLocalTime) { 3169 return this.utcOffset(0, keepLocalTime); 3170 } 3171 3172 function setOffsetToLocal (keepLocalTime) { 3173 if (this._isUTC) { 3174 this.utcOffset(0, keepLocalTime); 3175 this._isUTC = false; 3176 3177 if (keepLocalTime) { 3178 this.subtract(getDateOffset(this), 'm'); 3179 } 3180 } 3181 return this; 3182 } 3183 3184 function setOffsetToParsedOffset () { 3185 if (this._tzm) { 3186 this.utcOffset(this._tzm); 3187 } else if (typeof this._i === 'string') { 3188 this.utcOffset(offsetFromString(this._i)); 3189 } 3190 return this; 3191 } 3192 3193 function hasAlignedHourOffset (input) { 3194 input = input ? local__createLocal(input).utcOffset() : 0; 3195 3196 return (this.utcOffset() - input) % 60 === 0; 3197 } 3198 3199 function isDaylightSavingTime () { 3200 return ( 3201 this.utcOffset() > this.clone().month(0).utcOffset() || 3202 this.utcOffset() > this.clone().month(5).utcOffset() 3203 ); 3204 } 3205 3206 function isDaylightSavingTimeShifted () { 3207 if (typeof this._isDSTShifted !== 'undefined') { 3208 return this._isDSTShifted; 3209 } 3210 3211 var c = {}; 3212 3213 copyConfig(c, this); 3214 c = prepareConfig(c); 3215 3216 if (c._a) { 3217 var other = c._isUTC ? create_utc__createUTC(c._a) : local__createLocal(c._a); 3218 this._isDSTShifted = this.isValid() && 3219 compareArrays(c._a, other.toArray()) > 0; 3220 } else { 3221 this._isDSTShifted = false; 3222 } 3223 3224 return this._isDSTShifted; 3225 } 3226 3227 function isLocal () { 3228 return !this._isUTC; 3229 } 3230 3231 function isUtcOffset () { 3232 return this._isUTC; 3233 } 3234 3235 function isUtc () { 3236 return this._isUTC && this._offset === 0; 3237 } 3238 3239 var aspNetRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/; 3240 3241 // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html 3242 // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere 3243 var create__isoRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/; 3244 3245 function create__createDuration (input, key) { 3246 var duration = input, 3247 // matching against regexp is expensive, do it on demand 3248 match = null, 3249 sign, 3250 ret, 3251 diffRes; 3252 3253 if (isDuration(input)) { 3254 duration = { 3255 ms : input._milliseconds, 3256 d : input._days, 3257 M : input._months 3258 }; 3259 } else if (typeof input === 'number') { 3260 duration = {}; 3261 if (key) { 3262 duration[key] = input; 3263 } else { 3264 duration.milliseconds = input; 3265 } 3266 } else if (!!(match = aspNetRegex.exec(input))) { 3267 sign = (match[1] === '-') ? -1 : 1; 3268 duration = { 3269 y : 0, 3270 d : toInt(match[DATE]) * sign, 3271 h : toInt(match[HOUR]) * sign, 3272 m : toInt(match[MINUTE]) * sign, 3273 s : toInt(match[SECOND]) * sign, 3274 ms : toInt(match[MILLISECOND]) * sign 3275 }; 3276 } else if (!!(match = create__isoRegex.exec(input))) { 3277 sign = (match[1] === '-') ? -1 : 1; 3278 duration = { 3279 y : parseIso(match[2], sign), 3280 M : parseIso(match[3], sign), 3281 d : parseIso(match[4], sign), 3282 h : parseIso(match[5], sign), 3283 m : parseIso(match[6], sign), 3284 s : parseIso(match[7], sign), 3285 w : parseIso(match[8], sign) 3286 }; 3287 } else if (duration == null) {// checks for null or undefined 3288 duration = {}; 3289 } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { 3290 diffRes = momentsDifference(local__createLocal(duration.from), local__createLocal(duration.to)); 3291 3292 duration = {}; 3293 duration.ms = diffRes.milliseconds; 3294 duration.M = diffRes.months; 3295 } 3296 3297 ret = new Duration(duration); 3298 3299 if (isDuration(input) && hasOwnProp(input, '_locale')) { 3300 ret._locale = input._locale; 3301 } 3302 3303 return ret; 3304 } 3305 3306 create__createDuration.fn = Duration.prototype; 3307 3308 function parseIso (inp, sign) { 3309 // We'd normally use ~~inp for this, but unfortunately it also 3310 // converts floats to ints. 3311 // inp may be undefined, so careful calling replace on it. 3312 var res = inp && parseFloat(inp.replace(',', '.')); 3313 // apply sign while we're at it 3314 return (isNaN(res) ? 0 : res) * sign; 3315 } 3316 3317 function positiveMomentsDifference(base, other) { 3318 var res = {milliseconds: 0, months: 0}; 3319 3320 res.months = other.month() - base.month() + 3321 (other.year() - base.year()) * 12; 3322 if (base.clone().add(res.months, 'M').isAfter(other)) { 3323 --res.months; 3324 } 3325 3326 res.milliseconds = +other - +(base.clone().add(res.months, 'M')); 3327 3328 return res; 3329 } 3330 3331 function momentsDifference(base, other) { 3332 var res; 3333 other = cloneWithOffset(other, base); 3334 if (base.isBefore(other)) { 3335 res = positiveMomentsDifference(base, other); 3336 } else { 3337 res = positiveMomentsDifference(other, base); 3338 res.milliseconds = -res.milliseconds; 3339 res.months = -res.months; 3340 } 3341 3342 return res; 3343 } 3344 3345 function createAdder(direction, name) { 3346 return function (val, period) { 3347 var dur, tmp; 3348 //invert the arguments, but complain about it 3349 if (period !== null && !isNaN(+period)) { 3350 deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).'); 3351 tmp = val; val = period; period = tmp; 3352 } 3353 3354 val = typeof val === 'string' ? +val : val; 3355 dur = create__createDuration(val, period); 3356 add_subtract__addSubtract(this, dur, direction); 3357 return this; 3358 }; 3359 } 3360 3361 function add_subtract__addSubtract (mom, duration, isAdding, updateOffset) { 3362 var milliseconds = duration._milliseconds, 3363 days = duration._days, 3364 months = duration._months; 3365 updateOffset = updateOffset == null ? true : updateOffset; 3366 3367 if (milliseconds) { 3368 mom._d.setTime(+mom._d + milliseconds * isAdding); 3369 } 3370 if (days) { 3371 get_set__set(mom, 'Date', get_set__get(mom, 'Date') + days * isAdding); 3372 } 3373 if (months) { 3374 setMonth(mom, get_set__get(mom, 'Month') + months * isAdding); 3375 } 3376 if (updateOffset) { 3377 utils_hooks__hooks.updateOffset(mom, days || months); 3378 } 3379 } 3380 3381 var add_subtract__add = createAdder(1, 'add'); 3382 var add_subtract__subtract = createAdder(-1, 'subtract'); 3383 3384 function moment_calendar__calendar (time, formats) { 3385 // We want to compare the start of today, vs this. 3386 // Getting start-of-today depends on whether we're local/utc/offset or not. 3387 var now = time || local__createLocal(), 3388 sod = cloneWithOffset(now, this).startOf('day'), 3389 diff = this.diff(sod, 'days', true), 3390 format = diff < -6 ? 'sameElse' : 3391 diff < -1 ? 'lastWeek' : 3392 diff < 0 ? 'lastDay' : 3393 diff < 1 ? 'sameDay' : 3394 diff < 2 ? 'nextDay' : 3395 diff < 7 ? 'nextWeek' : 'sameElse'; 3396 return this.format(formats && formats[format] || this.localeData().calendar(format, this, local__createLocal(now))); 3397 } 3398 3399 function clone () { 3400 return new Moment(this); 3401 } 3402 3403 function isAfter (input, units) { 3404 var inputMs; 3405 units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); 3406 if (units === 'millisecond') { 3407 input = isMoment(input) ? input : local__createLocal(input); 3408 return +this > +input; 3409 } else { 3410 inputMs = isMoment(input) ? +input : +local__createLocal(input); 3411 return inputMs < +this.clone().startOf(units); 3412 } 3413 } 3414 3415 function isBefore (input, units) { 3416 var inputMs; 3417 units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); 3418 if (units === 'millisecond') { 3419 input = isMoment(input) ? input : local__createLocal(input); 3420 return +this < +input; 3421 } else { 3422 inputMs = isMoment(input) ? +input : +local__createLocal(input); 3423 return +this.clone().endOf(units) < inputMs; 3424 } 3425 } 3426 3427 function isBetween (from, to, units) { 3428 return this.isAfter(from, units) && this.isBefore(to, units); 3429 } 3430 3431 function isSame (input, units) { 3432 var inputMs; 3433 units = normalizeUnits(units || 'millisecond'); 3434 if (units === 'millisecond') { 3435 input = isMoment(input) ? input : local__createLocal(input); 3436 return +this === +input; 3437 } else { 3438 inputMs = +local__createLocal(input); 3439 return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units)); 3440 } 3441 } 3442 3443 function diff (input, units, asFloat) { 3444 var that = cloneWithOffset(input, this), 3445 zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4, 3446 delta, output; 3447 3448 units = normalizeUnits(units); 3449 3450 if (units === 'year' || units === 'month' || units === 'quarter') { 3451 output = monthDiff(this, that); 3452 if (units === 'quarter') { 3453 output = output / 3; 3454 } else if (units === 'year') { 3455 output = output / 12; 3456 } 3457 } else { 3458 delta = this - that; 3459 output = units === 'second' ? delta / 1e3 : // 1000 3460 units === 'minute' ? delta / 6e4 : // 1000 * 60 3461 units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60 3462 units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst 3463 units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst 3464 delta; 3465 } 3466 return asFloat ? output : absFloor(output); 3467 } 3468 3469 function monthDiff (a, b) { 3470 // difference in months 3471 var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), 3472 // b is in (anchor - 1 month, anchor + 1 month) 3473 anchor = a.clone().add(wholeMonthDiff, 'months'), 3474 anchor2, adjust; 3475 3476 if (b - anchor < 0) { 3477 anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); 3478 // linear across the month 3479 adjust = (b - anchor) / (anchor - anchor2); 3480 } else { 3481 anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); 3482 // linear across the month 3483 adjust = (b - anchor) / (anchor2 - anchor); 3484 } 3485 3486 return -(wholeMonthDiff + adjust); 3487 } 3488 3489 utils_hooks__hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; 3490 3491 function toString () { 3492 return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); 3493 } 3494 3495 function moment_format__toISOString () { 3496 var m = this.clone().utc(); 3497 if (0 < m.year() && m.year() <= 9999) { 3498 if ('function' === typeof Date.prototype.toISOString) { 3499 // native implementation is ~50x faster, use it when we can 3500 return this.toDate().toISOString(); 3501 } else { 3502 return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); 3503 } 3504 } else { 3505 return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); 3506 } 3507 } 3508 3509 function format (inputString) { 3510 var output = formatMoment(this, inputString || utils_hooks__hooks.defaultFormat);
3511 return this.localeData().postformat(output); 3512 } 3513 3514 function from (time, withoutSuffix) { 3515 if (!this.isValid()) { 3516 return this.localeData().invalidDate(); 3517 } 3518 return create__createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); 3519 } 3520 3521 function fromNow (withoutSuffix) { 3522 return this.from(local__createLocal(), withoutSuffix); 3523 } 3524 3525 function to (time, withoutSuffix) { 3526 if (!this.isValid()) { 3527 return this.localeData().invalidDate(); 3528 } 3529 return create__createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); 3530 } 3531 3532 function toNow (withoutSuffix) { 3533 return this.to(local__createLocal(), withoutSuffix); 3534 } 3535 3536 function locale (key) { 3537 var newLocaleData; 3538 3539 if (key === undefined) { 3540 return this._locale._abbr; 3541 } else { 3542 newLocaleData = locale_locales__getLocale(key); 3543 if (newLocaleData != null) { 3544 this._locale = newLocaleData; 3545 } 3546 return this; 3547 } 3548 } 3549 3550 var lang = deprecate( 3551 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', 3552 function (key) { 3553 if (key === undefined) { 3554 return this.localeData(); 3555 } else { 3556 return this.locale(key); 3557 } 3558 } 3559 ); 3560 3561 function localeData () { 3562 return this._locale; 3563 } 3564 3565 function startOf (units) { 3566 units = normalizeUnits(units); 3567 // the following switch intentionally omits break keywords 3568 // to utilize falling through the cases. 3569 switch (units) { 3570 case 'year': 3571 this.month(0); 3572 /* falls through */ 3573 case 'quarter': 3574 case 'month': 3575 this.date(1); 3576 /* falls through */ 3577 case 'week': 3578 case 'isoWeek': 3579 case 'day': 3580 this.hours(0); 3581 /* falls through */ 3582 case 'hour': 3583 this.minutes(0); 3584 /* falls through */ 3585 case 'minute': 3586 this.seconds(0); 3587 /* falls through */ 3588 case 'second': 3589 this.milliseconds(0); 3590 } 3591 3592 // weeks are a special case 3593 if (units === 'week') { 3594 this.weekday(0); 3595 } 3596 if (units === 'isoWeek') { 3597 this.isoWeekday(1); 3598 } 3599 3600 // quarters are also special 3601 if (units === 'quarter') { 3602 this.month(Math.floor(this.month() / 3) * 3); 3603 } 3604 3605 return this; 3606 } 3607 3608 function endOf (units) { 3609 units = normalizeUnits(units); 3610 if (units === undefined || units === 'millisecond') { 3611 return this; 3612 } 3613 return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); 3614 } 3615 3616 function to_type__valueOf () { 3617 return +this._d - ((this._offset || 0) * 60000); 3618 } 3619 3620 function unix () { 3621 return Math.floor(+this / 1000); 3622 } 3623 3624 function toDate () { 3625 return this._offset ? new Date(+this) : this._d; 3626 } 3627 3628 function toArray () { 3629 var m = this; 3630 return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; 3631 } 3632 3633 function toObject () { 3634 var m = this; 3635 return { 3636 years: m.year(), 3637 months: m.month(), 3638 date: m.date(), 3639 hours: m.hours(), 3640 minutes: m.minutes(), 3641 seconds: m.seconds(), 3642 milliseconds: m.milliseconds() 3643 }; 3644 } 3645 3646 function moment_valid__isValid () { 3647 return valid__isValid(this); 3648 } 3649 3650 function parsingFlags () { 3651 return extend({}, getParsingFlags(this)); 3652 } 3653
vendor: 4,245 bytes, lines 3654-3799
3654 function invalidAt () { 3655 return getParsingFlags(this).overflow; 3656 } 3657 3658 addFormatToken(0, ['gg', 2], 0, function () { 3659 return this.weekYear() % 100; 3660 }); 3661 3662 addFormatToken(0, ['GG', 2], 0, function () { 3663 return this.isoWeekYear() % 100; 3664 }); 3665 3666 function addWeekYearFormatToken (token, getter) { 3667 addFormatToken(0, [token, token.length], 0, getter); 3668 } 3669 3670 addWeekYearFormatToken('gggg', 'weekYear'); 3671 addWeekYearFormatToken('ggggg', 'weekYear'); 3672 addWeekYearFormatToken('GGGG', 'isoWeekYear'); 3673 addWeekYearFormatToken('GGGGG', 'isoWeekYear'); 3674 3675 // ALIASES 3676 3677 addUnitAlias('weekYear', 'gg'); 3678 addUnitAlias('isoWeekYear', 'GG'); 3679 3680 // PARSING 3681 3682 addRegexToken('G', matchSigned); 3683 addRegexToken('g', matchSigned); 3684 addRegexToken('GG', match1to2, match2); 3685 addRegexToken('gg', match1to2, match2); 3686 addRegexToken('GGGG', match1to4, match4); 3687 addRegexToken('gggg', match1to4, match4); 3688 addRegexToken('GGGGG', match1to6, match6); 3689 addRegexToken('ggggg', match1to6, match6); 3690 3691 addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { 3692 week[token.substr(0, 2)] = toInt(input); 3693 }); 3694 3695 addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { 3696 week[token] = utils_hooks__hooks.parseTwoDigitYear(input); 3697 }); 3698 3699 // HELPERS 3700 3701 function weeksInYear(year, dow, doy) { 3702 return weekOfYear(local__createLocal([year, 11, 31 + dow - doy]), dow, doy).week; 3703 } 3704 3705 // MOMENTS 3706 3707 function getSetWeekYear (input) { 3708 var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year; 3709 return input == null ? year : this.add((input - year), 'y'); 3710 } 3711 3712 function getSetISOWeekYear (input) { 3713 var year = weekOfYear(this, 1, 4).year; 3714 return input == null ? year : this.add((input - year), 'y'); 3715 } 3716 3717 function getISOWeeksInYear () { 3718 return weeksInYear(this.year(), 1, 4); 3719 } 3720 3721 function getWeeksInYear () { 3722 var weekInfo = this.localeData()._week; 3723 return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); 3724 } 3725 3726 addFormatToken('Q', 0, 0, 'quarter'); 3727 3728 // ALIASES 3729 3730 addUnitAlias('quarter', 'Q'); 3731 3732 // PARSING 3733 3734 addRegexToken('Q', match1); 3735 addParseToken('Q', function (input, array) { 3736 array[MONTH] = (toInt(input) - 1) * 3; 3737 }); 3738 3739 // MOMENTS 3740 3741 function getSetQuarter (input) { 3742 return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); 3743 } 3744 3745 addFormatToken('D', ['DD', 2], 'Do', 'date'); 3746 3747 // ALIASES 3748 3749 addUnitAlias('date', 'D'); 3750 3751 // PARSING 3752 3753 addRegexToken('D', match1to2); 3754 addRegexToken('DD', match1to2, match2); 3755 addRegexToken('Do', function (isStrict, locale) { 3756 return isStrict ? locale._ordinalParse : locale._ordinalParseLenient; 3757 }); 3758 3759 addParseToken(['D', 'DD'], DATE); 3760 addParseToken('Do', function (input, array) { 3761 array[DATE] = toInt(input.match(match1to2)[0], 10); 3762 }); 3763 3764 // MOMENTS 3765 3766 var getSetDayOfMonth = makeGetSet('Date', true); 3767 3768 addFormatToken('d', 0, 'do', 'day'); 3769 3770 addFormatToken('dd', 0, 0, function (format) { 3771 return this.localeData().weekdaysMin(this, format); 3772 }); 3773 3774 addFormatToken('ddd', 0, 0, function (format) { 3775 return this.localeData().weekdaysShort(this, format); 3776 }); 3777 3778 addFormatToken('dddd', 0, 0, function (format) { 3779 return this.localeData().weekdays(this, format); 3780 }); 3781 3782 addFormatToken('e', 0, 0, 'weekday'); 3783 addFormatToken('E', 0, 0, 'isoWeekday'); 3784 3785 // ALIASES 3786 3787 addUnitAlias('day', 'd'); 3788 addUnitAlias('weekday', 'e'); 3789 addUnitAlias('isoWeekday', 'E'); 3790 3791 // PARSING 3792 3793 addRegexToken('d', match1to2); 3794 addRegexToken('e', match1to2); 3795 addRegexToken('E', match1to2); 3796 addRegexToken('dd', matchWord); 3797 addRegexToken('ddd', matchWord); 3798 addRegexToken('dddd', matchWord); 3799
3800 addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config) { 3801 var weekday = config._locale.weekdaysParse(input); 3802 // if we didn't get a weekday name, mark the date as invalid 3803 if (weekday != null) { 3804 week.d = weekday; 3805 } else { 3806 getParsingFlags(config).invalidWeekday = input; 3807 } 3808 }); 3809 3810 addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { 3811 week[token] = toInt(input); 3812 }); 3813 3814 // HELPERS 3815 3816 function parseWeekday(input, locale) { 3817 if (typeof input !== 'string') { 3818 return input; 3819 } 3820 3821 if (!isNaN(input)) { 3822 return parseInt(input, 10); 3823 } 3824 3825 input = locale.weekdaysParse(input); 3826 if (typeof input === 'number') { 3827 return input; 3828 } 3829 3830 return null; 3831 } 3832 3833 // LOCALES 3834 3835 var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); 3836 function localeWeekdays (m) { 3837 return this._weekdays[m.day()]; 3838 } 3839 3840 var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); 3841 function localeWeekdaysShort (m) { 3842 return this._weekdaysShort[m.day()]; 3843 } 3844 3845 var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); 3846 function localeWeekdaysMin (m) { 3847 return this._weekdaysMin[m.day()]; 3848 } 3849 3850 function localeWeekdaysParse (weekdayName) { 3851 var i, mom, regex; 3852 3853 this._weekdaysParse = this._weekdaysParse || []; 3854 3855 for (i = 0; i < 7; i++) { 3856 // make the regex if we don't have it already 3857 if (!this._weekdaysParse[i]) { 3858 mom = local__createLocal([2000, 1]).day(i); 3859 regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); 3860 this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); 3861 } 3862 // test the regex 3863 if (this._weekdaysParse[i].test(weekdayName)) { 3864 return i; 3865 } 3866 } 3867 } 3868 3869 // MOMENTS 3870 3871 function getSetDayOfWeek (input) { 3872 var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); 3873 if (input != null) { 3874 input = parseWeekday(input, this.localeData()); 3875 return this.add(input - day, 'd'); 3876 } else { 3877 return day; 3878 } 3879 } 3880 3881 function getSetLocaleDayOfWeek (input) { 3882 var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; 3883 return input == null ? weekday : this.add(input - weekday, 'd'); 3884 } 3885 3886 function getSetISODayOfWeek (input) { 3887 // behaves the same as moment#day except 3888 // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) 3889 // as a setter, sunday should belong to the previous week. 3890 return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); 3891 } 3892 3893 addFormatToken('H', ['HH', 2], 0, 'hour'); 3894 addFormatToken('h', ['hh', 2], 0, function () { 3895 return this.hours() % 12 || 12; 3896 }); 3897 3898 function meridiem (token, lowercase) { 3899 addFormatToken(token, 0, 0, function () { 3900 return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); 3901 }); 3902 } 3903 3904 meridiem('a', true); 3905 meridiem('A', false); 3906 3907 // ALIASES 3908 3909 addUnitAlias('hour', 'h'); 3910 3911 // PARSING 3912 3913 function matchMeridiem (isStrict, locale) { 3914 return locale._meridiemParse; 3915 } 3916 3917 addRegexToken('a', matchMeridiem); 3918 addRegexToken('A', matchMeridiem); 3919 addRegexToken('H', match1to2); 3920 addRegexToken('h', match1to2); 3921 addRegexToken('HH', match1to2, match2); 3922 addRegexToken('hh', match1to2, match2); 3923 3924 addParseToken(['H', 'HH'], HOUR); 3925 addParseToken(['a', 'A'], function (input, array, config) { 3926 config._isPm = config._locale.isPM(input); 3927 config._meridiem = input; 3928 }); 3929 addParseToken(['h', 'hh'], function (input, array, config) { 3930 array[HOUR] = toInt(input); 3931 getParsingFlags(config).bigHour = true; 3932 }); 3933 3934 // LOCALES 3935 3936 function localeIsPM (input) { 3937 // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing str
vendor: 9,770 bytes, lines 3937-4209
3937ings like arrays 3938 // Using charAt should be more compatible. 3939 return ((input + '').toLowerCase().charAt(0) === 'p'); 3940 } 3941 3942 var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; 3943 function localeMeridiem (hours, minutes, isLower) { 3944 if (hours > 11) { 3945 return isLower ? 'pm' : 'PM'; 3946 } else { 3947 return isLower ? 'am' : 'AM'; 3948 } 3949 } 3950 3951 3952 // MOMENTS 3953 3954 // Setting the hour should keep the time, because the user explicitly 3955 // specified which hour he wants. So trying to maintain the same hour (in 3956 // a new timezone) makes sense. Adding/subtracting hours does not follow 3957 // this rule. 3958 var getSetHour = makeGetSet('Hours', true); 3959 3960 addFormatToken('m', ['mm', 2], 0, 'minute'); 3961 3962 // ALIASES 3963 3964 addUnitAlias('minute', 'm'); 3965 3966 // PARSING 3967 3968 addRegexToken('m', match1to2); 3969 addRegexToken('mm', match1to2, match2); 3970 addParseToken(['m', 'mm'], MINUTE); 3971 3972 // MOMENTS 3973 3974 var getSetMinute = makeGetSet('Minutes', false); 3975 3976 addFormatToken('s', ['ss', 2], 0, 'second'); 3977 3978 // ALIASES 3979 3980 addUnitAlias('second', 's'); 3981 3982 // PARSING 3983 3984 addRegexToken('s', match1to2); 3985 addRegexToken('ss', match1to2, match2); 3986 addParseToken(['s', 'ss'], SECOND); 3987 3988 // MOMENTS 3989 3990 var getSetSecond = makeGetSet('Seconds', false); 3991 3992 addFormatToken('S', 0, 0, function () { 3993 return ~~(this.millisecond() / 100); 3994 }); 3995 3996 addFormatToken(0, ['SS', 2], 0, function () { 3997 return ~~(this.millisecond() / 10); 3998 }); 3999 4000 addFormatToken(0, ['SSS', 3], 0, 'millisecond'); 4001 addFormatToken(0, ['SSSS', 4], 0, function () { 4002 return this.millisecond() * 10; 4003 }); 4004 addFormatToken(0, ['SSSSS', 5], 0, function () { 4005 return this.millisecond() * 100; 4006 }); 4007 addFormatToken(0, ['SSSSSS', 6], 0, function () { 4008 return this.millisecond() * 1000; 4009 }); 4010 addFormatToken(0, ['SSSSSSS', 7], 0, function () { 4011 return this.millisecond() * 10000; 4012 }); 4013 addFormatToken(0, ['SSSSSSSS', 8], 0, function () { 4014 return this.millisecond() * 100000; 4015 }); 4016 addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { 4017 return this.millisecond() * 1000000; 4018 }); 4019 4020 4021 // ALIASES 4022 4023 addUnitAlias('millisecond', 'ms'); 4024 4025 // PARSING 4026 4027 addRegexToken('S', match1to3, match1); 4028 addRegexToken('SS', match1to3, match2); 4029 addRegexToken('SSS', match1to3, match3); 4030 4031 var token; 4032 for (token = 'SSSS'; token.length <= 9; token += 'S') { 4033 addRegexToken(token, matchUnsigned); 4034 } 4035 4036 function parseMs(input, array) { 4037 array[MILLISECOND] = toInt(('0.' + input) * 1000); 4038 } 4039 4040 for (token = 'S'; token.length <= 9; token += 'S') { 4041 addParseToken(token, parseMs); 4042 } 4043 // MOMENTS 4044 4045 var getSetMillisecond = makeGetSet('Milliseconds', false); 4046 4047 addFormatToken('z', 0, 0, 'zoneAbbr'); 4048 addFormatToken('zz', 0, 0, 'zoneName'); 4049 4050 // MOMENTS 4051 4052 function getZoneAbbr () { 4053 return this._isUTC ? 'UTC' : ''; 4054 } 4055 4056 function getZoneName () { 4057 return this._isUTC ? 'Coordinated Universal Time' : ''; 4058 } 4059 4060 var momentPrototype__proto = Moment.prototype; 4061 4062 momentPrototype__proto.add = add_subtract__add; 4063 momentPrototype__proto.calendar = moment_calendar__calendar; 4064 momentPrototype__proto.clone = clone; 4065 momentPrototype__proto.diff = diff; 4066 momentPrototype__proto.endOf = endOf; 4067 momentPrototype__proto.format = format; 4068 momentPrototype__proto.from = from; 4069 momentPrototype__proto.fromNow = fromNow; 4070 momentPrototype__proto.to = to; 4071 momentPrototype__proto.toNow = toNow; 4072 momentPrototype__proto.get = getSet; 4073 momentPrototype__proto.invalidAt = invalidAt; 4074 momentPrototype__proto.isAfter = isAfter; 4075 momentPrototype__proto.isBefore = isBefore; 4076 momentPrototype__proto.isBetween = isBetween; 4077 momentPrototype__proto.isSame = isSame; 4078 momentPrototype__proto.isValid = moment_valid__isValid; 4079 momentPrototype__proto.lang = lang; 4080 momentPrototype__proto.locale = locale; 4081 momentPrototype__proto.localeData = localeData; 4082 momentPrototype__proto.max = prototypeMax; 4083 momentPrototype__proto.min = prototypeMin; 4084 momentPrototype__proto.parsingFlags = parsingFlags; 4085 momentPrototype__proto.set = getSet; 4086 momentPrototype__proto.startOf = startOf; 4087 momentPrototype__proto.subtract = add_subtract__subtract; 4088 momentPrototype__proto.toArray = toArray; 4089 momentPrototype__proto.toObject = toObject; 4090 momentPrototype__proto.toDate = toDate; 4091 momentPrototype__proto.toISOString = moment_format__toISOString; 4092 momentPrototype__proto.toJSON = moment_format__toISOString; 4093 momentPrototype__proto.toString = toString; 4094 momentPrototype__proto.unix = unix; 4095 momentPrototype__proto.valueOf = to_type__valueOf; 4096 4097 // Year 4098 momentPrototype__proto.year = getSetYear; 4099 momentPrototype__proto.isLeapYear = getIsLeapYear; 4100 4101 // Week Year 4102 momentPrototype__proto.weekYear = getSetWeekYear; 4103 momentPrototype__proto.isoWeekYear = getSetISOWeekYear; 4104 4105 // Quarter 4106 momentPrototype__proto.quarter = momentPrototype__proto.quarters = getSetQuarter; 4107 4108 // Month 4109 momentPrototype__proto.month = getSetMonth; 4110 momentPrototype__proto.daysInMonth = getDaysInMonth; 4111 4112 // Week 4113 momentPrototype__proto.week = momentPrototype__proto.weeks = getSetWeek; 4114 momentPrototype__proto.isoWeek = momentPrototype__proto.isoWeeks = getSetISOWeek; 4115 momentPrototype__proto.weeksInYear = getWeeksInYear; 4116 momentPrototype__proto.isoWeeksInYear = getISOWeeksInYear; 4117 4118 // Day 4119 momentPrototype__proto.date = getSetDayOfMonth; 4120 momentPrototype__proto.day = momentPrototype__proto.days = getSetDayOfWeek; 4121 momentPrototype__proto.weekday = getSetLocaleDayOfWeek; 4122 momentPrototype__proto.isoWeekday = getSetISODayOfWeek; 4123 momentPrototype__proto.dayOfYear = getSetDayOfYear; 4124 4125 // Hour 4126 momentPrototype__proto.hour = momentPrototype__proto.hours = getSetHour; 4127 4128 // Minute 4129 momentPrototype__proto.minute = momentPrototype__proto.minutes = getSetMinute; 4130 4131 // Second 4132 momentPrototype__proto.second = momentPrototype__proto.seconds = getSetSecond; 4133 4134 // Millisecond 4135 momentPrototype__proto.millisecond = momentPrototype__proto.milliseconds = getSetMillisecond; 4136 4137 // Offset 4138 momentPrototype__proto.utcOffset = getSetOffset; 4139 momentPrototype__proto.utc = setOffsetToUTC; 4140 momentPrototype__proto.local = setOffsetToLocal; 4141 momentPrototype__proto.parseZone = setOffsetToParsedOffset; 4142 momentPrototype__proto.hasAlignedHourOffset = hasAlignedHourOffset; 4143 momentPrototype__proto.isDST = isDaylightSavingTime; 4144 momentPrototype__proto.isDSTShifted = isDaylightSavingTimeShifted; 4145 momentPrototype__proto.isLocal = isLocal; 4146 momentPrototype__proto.isUtcOffset = isUtcOffset; 4147 momentPrototype__proto.isUtc = isUtc; 4148 momentPrototype__proto.isUTC = isUtc; 4149 4150 // Timezone 4151 momentPrototype__proto.zoneAbbr = getZoneAbbr; 4152 momentPrototype__proto.zoneName = getZoneName; 4153 4154 // Deprecations 4155 momentPrototype__proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); 4156 momentPrototype__proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); 4157 momentPrototype__proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); 4158 momentPrototype__proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779', getSetZone); 4159 4160 var momentPrototype = momentPrototype__proto; 4161 4162 function moment__createUnix (input) { 4163 return local__createLocal(input * 1000); 4164 } 4165 4166 function moment__createInZone () { 4167 return local__createLocal.apply(null, arguments).parseZone(); 4168 } 4169 4170 var defaultCalendar = { 4171 sameDay : '[Today at] LT', 4172 nextDay : '[Tomorrow at] LT', 4173 nextWeek : 'dddd [at] LT', 4174 lastDay : '[Yesterday at] LT', 4175 lastWeek : '[Last] dddd [at] LT', 4176 sameElse : 'L' 4177 }; 4178 4179 function locale_calendar__calendar (key, mom, now) { 4180 var output = this._calendar[key]; 4181 return typeof output === 'function' ? output.call(mom, now) : output; 4182 } 4183 4184 var defaultLongDateFormat = { 4185 LTS : 'h:mm:ss A', 4186 LT : 'h:mm A', 4187 L : 'MM/DD/YYYY', 4188 LL : 'MMMM D, YYYY', 4189 LLL : 'MMMM D, YYYY h:mm A', 4190 LLLL : 'dddd, MMMM D, YYYY h:mm A' 4191 }; 4192 4193 function longDateFormat (key) { 4194 var format = this._longDateFormat[key], 4195 formatUpper = this._longDateFormat[key.toUpperCase()]; 4196 4197 if (format || !formatUpper) { 4198 return format; 4199 } 4200 4201 this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) { 4202 return val.slice(1); 4203 }); 4204 4205 return this._longDateFormat[key]; 4206 } 4207 4208 var defaultInvalidDate = 'Invalid date'; 4209
4210 function invalidDate () { 4211 return this._invalidDate; 4212 } 4213 4214 var defaultOrdinal = '%d'; 4215 var defaultOrdinalParse = /\d{1,2}/; 4216 4217 function ordinal (number) { 4218 return this._ordinal.replace('%d', number); 4219 } 4220 4221 function preParsePostFormat (string) { 4222 return string; 4223 } 4224 4225 var defaultRelativeTime = { 4226 future : 'in %s', 4227 past : '%s ago', 4228 s : 'a few seconds', 4229 m : 'a minute', 4230 mm : '%d minutes', 4231 h : 'an hour', 4232 hh : '%d hours', 4233 d : 'a day', 4234 dd : '%d days', 4235 M : 'a month', 4236 MM : '%d months', 4237 y : 'a year', 4238 yy : '%d years' 4239 }; 4240 4241 function relative__relativeTime (number, withoutSuffix, string, isFuture) { 4242 var output = this._relativeTime[string]; 4243 return (typeof output === 'function') ? 4244 output(number, withoutSuffix, string, isFuture) : 4245 output.replace(/%d/i, number); 4246 } 4247 4248 function pastFuture (diff, output) { 4249 var format = this._relativeTime[diff > 0 ? 'future' : 'past']; 4250 return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); 4251 } 4252 4253 function locale_set__set (config) { 4254 var prop, i; 4255 for (i in config) { 4256 prop = config[i]; 4257 if (typeof prop === 'function') { 4258 this[i] = prop; 4259 } else { 4260 this['_' + i] = prop; 4261 } 4262 } 4263 // Lenient ordinal parsing accepts just a number in addition to 4264 // number + (possibly) stuff coming from _ordinalParseLenient. 4265 this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + (/\d{1,2}/).source); 4266 } 4267 4268 var prototype__proto = Locale.prototype; 4269 4270 prototype__proto._calendar = defaultCalendar; 4271 prototype__proto.calendar = locale_calendar__calendar; 4272 prototype__proto._longDateFormat = defaultLongDateFormat; 4273 prototype__proto.longDateFormat = longDateFormat; 4274 prototype__proto._invalidDate = defaultInvalidDate; 4275 prototype__proto.invalidDate = invalidDate; 4276 prototype__proto._ordinal = defaultOrdinal; 4277 prototype__proto.ordinal = ordinal; 4278 prototype__proto._ordinalParse = defaultOrdinalParse; 4279 prototype__proto.preparse = preParsePostFormat; 4280 prototype__proto.postformat = preParsePostFormat; 4281 prototype__proto._relativeTime = defaultRelativeTime; 4282 prototype__proto.relativeTime = relative__relativeTime; 4283 prototype__proto.pastFuture = pastFuture; 4284 prototype__proto.set = locale_set__set; 4285 4286 // Month 4287 prototype__proto.months = localeMonths; 4288 prototype__proto._months = defaultLocaleMonths; 4289 prototype__proto.monthsShort = localeMonthsShort; 4290 prototype__proto._monthsShort = defaultLocaleMonthsShort; 4291 prototype__proto.monthsParse = localeMonthsParse; 4292 4293 // Week 4294 prototype__proto.week = localeWeek; 4295 prototype__proto._week = defaultLocaleWeek; 4296 prototype__proto.firstDayOfYear = localeFirstDayOfYear; 4297 prototype__proto.firstDayOfWeek = localeFirstDayOfWeek; 4298 4299 // Day of Week 4300 prototype__proto.weekdays = localeWeekdays; 4301 prototype__proto._weekdays = defaultLocaleWeekdays; 4302 prototype__proto.weekdaysMin = localeWeekdaysMin; 4303 prototype__proto._weekdaysMin = defaultLocaleWeekdaysMin; 4304 prototype__proto.weekdaysShort = localeWeekdaysShort; 4305 prototype__proto._weekdaysShort = defaultLocaleWeekdaysShort; 4306 prototype__proto.weekdaysParse = localeWeekdaysParse; 4307 4308 // Hours 4309 prototype__proto.isPM = localeIsPM; 4310 prototype__proto._meridiemParse = defaultLocaleMeridiemParse; 4311 prototype__proto.meridiem = localeMeridiem; 4312 4313 function lists__get (format, index, field, setter) { 4314 var locale = locale_locales__getLocale(); 4315 var utc = create_utc__createUTC().set(setter, index);
vendor: 16,852 bytes, lines 4316-4759
4316 return locale[field](utc, format); 4317 } 4318 4319 function list (format, index, field, count, setter) { 4320 if (typeof format === 'number') { 4321 index = format; 4322 format = undefined; 4323 } 4324 4325 format = format || ''; 4326 4327 if (index != null) { 4328 return lists__get(format, index, field, setter); 4329 } 4330 4331 var i; 4332 var out = []; 4333 for (i = 0; i < count; i++) { 4334 out[i] = lists__get(format, i, field, setter); 4335 } 4336 return out; 4337 } 4338 4339 function lists__listMonths (format, index) { 4340 return list(format, index, 'months', 12, 'month'); 4341 } 4342 4343 function lists__listMonthsShort (format, index) { 4344 return list(format, index, 'monthsShort', 12, 'month'); 4345 } 4346 4347 function lists__listWeekdays (format, index) { 4348 return list(format, index, 'weekdays', 7, 'day'); 4349 } 4350 4351 function lists__listWeekdaysShort (format, index) { 4352 return list(format, index, 'weekdaysShort', 7, 'day'); 4353 } 4354 4355 function lists__listWeekdaysMin (format, index) { 4356 return list(format, index, 'weekdaysMin', 7, 'day'); 4357 } 4358 4359 locale_locales__getSetGlobalLocale('en', { 4360 ordinalParse: /\d{1,2}(th|st|nd|rd)/, 4361 ordinal : function (number) { 4362 var b = number % 10, 4363 output = (toInt(number % 100 / 10) === 1) ? 'th' : 4364 (b === 1) ? 'st' : 4365 (b === 2) ? 'nd' : 4366 (b === 3) ? 'rd' : 'th'; 4367 return number + output; 4368 } 4369 }); 4370 4371 // Side effect imports 4372 utils_hooks__hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', locale_locales__getSetGlobalLocale); 4373 utils_hooks__hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', locale_locales__getLocale); 4374 4375 var mathAbs = Math.abs; 4376 4377 function duration_abs__abs () { 4378 var data = this._data; 4379 4380 this._milliseconds = mathAbs(this._milliseconds); 4381 this._days = mathAbs(this._days); 4382 this._months = mathAbs(this._months); 4383 4384 data.milliseconds = mathAbs(data.milliseconds); 4385 data.seconds = mathAbs(data.seconds); 4386 data.minutes = mathAbs(data.minutes); 4387 data.hours = mathAbs(data.hours); 4388 data.months = mathAbs(data.months); 4389 data.years = mathAbs(data.years); 4390 4391 return this; 4392 } 4393 4394 function duration_add_subtract__addSubtract (duration, input, value, direction) { 4395 var other = create__createDuration(input, value); 4396 4397 duration._milliseconds += direction * other._milliseconds; 4398 duration._days += direction * other._days; 4399 duration._months += direction * other._months; 4400 4401 return duration._bubble(); 4402 } 4403 4404 // supports only 2.0-style add(1, 's') or add(duration) 4405 function duration_add_subtract__add (input, value) { 4406 return duration_add_subtract__addSubtract(this, input, value, 1); 4407 } 4408 4409 // supports only 2.0-style subtract(1, 's') or subtract(duration) 4410 function duration_add_subtract__subtract (input, value) { 4411 return duration_add_subtract__addSubtract(this, input, value, -1); 4412 } 4413 4414 function absCeil (number) { 4415 if (number < 0) { 4416 return Math.floor(number); 4417 } else { 4418 return Math.ceil(number); 4419 } 4420 } 4421 4422 function bubble () { 4423 var milliseconds = this._milliseconds; 4424 var days = this._days; 4425 var months = this._months; 4426 var data = this._data; 4427 var seconds, minutes, hours, years, monthsFromDays; 4428 4429 // if we have a mix of positive and negative values, bubble down first 4430 // check: https://github.com/moment/moment/issues/2166 4431 if (!((milliseconds >= 0 && days >= 0 && months >= 0) || 4432 (milliseconds <= 0 && days <= 0 && months <= 0))) { 4433 milliseconds += absCeil(monthsToDays(months) + days) * 864e5; 4434 days = 0; 4435 months = 0; 4436 } 4437 4438 // The following code bubbles up values, see the tests for 4439 // examples of what that means. 4440 data.milliseconds = milliseconds % 1000; 4441 4442 seconds = absFloor(milliseconds / 1000); 4443 data.seconds = seconds % 60; 4444 4445 minutes = absFloor(seconds / 60); 4446 data.minutes = minutes % 60; 4447 4448 hours = absFloor(minutes / 60); 4449 data.hours = hours % 24; 4450 4451 days += absFloor(hours / 24); 4452 4453 // convert days to months 4454 monthsFromDays = absFloor(daysToMonths(days)); 4455 months += monthsFromDays; 4456 days -= absCeil(monthsToDays(monthsFromDays)); 4457 4458 // 12 months -> 1 year 4459 years = absFloor(months / 12); 4460 months %= 12; 4461 4462 data.days = days; 4463 data.months = months; 4464 data.years = years; 4465 4466 return this; 4467 } 4468 4469 function daysToMonths (days) { 4470 // 400 years have 146097 days (taking into account leap year rules) 4471 // 400 years have 12 months === 4800 4472 return days * 4800 / 146097; 4473 } 4474 4475 function monthsToDays (months) { 4476 // the reverse of daysToMonths 4477 return months * 146097 / 4800; 4478 } 4479 4480 function as (units) { 4481 var days; 4482 var months; 4483 var milliseconds = this._milliseconds; 4484 4485 units = normalizeUnits(units); 4486 4487 if (units === 'month' || units === 'year') { 4488 days = this._days + milliseconds / 864e5; 4489 months = this._months + daysToMonths(days); 4490 return units === 'month' ? months : months / 12; 4491 } else { 4492 // handle milliseconds separately because of floating point math errors (issue #1867) 4493 days = this._days + Math.round(monthsToDays(this._months)); 4494 switch (units) { 4495 case 'week' : return days / 7 + milliseconds / 6048e5; 4496 case 'day' : return days + milliseconds / 864e5; 4497 case 'hour' : return days * 24 + milliseconds / 36e5; 4498 case 'minute' : return days * 1440 + milliseconds / 6e4; 4499 case 'second' : return days * 86400 + milliseconds / 1000; 4500 // Math.floor prevents floating point math errors here 4501 case 'millisecond': return Math.floor(days * 864e5) + milliseconds; 4502 default: throw new Error('Unknown unit ' + units); 4503 } 4504 } 4505 } 4506 4507 // TODO: Use this.as('ms')? 4508 function duration_as__valueOf () { 4509 return ( 4510 this._milliseconds + 4511 this._days * 864e5 + 4512 (this._months % 12) * 2592e6 + 4513 toInt(this._months / 12) * 31536e6 4514 ); 4515 } 4516 4517 function makeAs (alias) { 4518 return function () { 4519 return this.as(alias); 4520 }; 4521 } 4522 4523 var asMilliseconds = makeAs('ms'); 4524 var asSeconds = makeAs('s'); 4525 var asMinutes = makeAs('m'); 4526 var asHours = makeAs('h'); 4527 var asDays = makeAs('d'); 4528 var asWeeks = makeAs('w'); 4529 var asMonths = makeAs('M'); 4530 var asYears = makeAs('y'); 4531 4532 function duration_get__get (units) { 4533 units = normalizeUnits(units); 4534 return this[units + 's'](); 4535 } 4536 4537 function makeGetter(name) { 4538 return function () { 4539 return this._data[name]; 4540 }; 4541 } 4542 4543 var milliseconds = makeGetter('milliseconds'); 4544 var seconds = makeGetter('seconds'); 4545 var minutes = makeGetter('minutes'); 4546 var hours = makeGetter('hours'); 4547 var days = makeGetter('days'); 4548 var months = makeGetter('months'); 4549 var years = makeGetter('years'); 4550 4551 function weeks () { 4552 return absFloor(this.days() / 7); 4553 } 4554 4555 var round = Math.round; 4556 var thresholds = { 4557 s: 45, // seconds to minute 4558 m: 45, // minutes to hour 4559 h: 22, // hours to day 4560 d: 26, // days to month 4561 M: 11 // months to year 4562 }; 4563 4564 // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize 4565 function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { 4566 return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); 4567 } 4568 4569 function duration_humanize__relativeTime (posNegDuration, withoutSuffix, locale) { 4570 var duration = create__createDuration(posNegDuration).abs(); 4571 var seconds = round(duration.as('s')); 4572 var minutes = round(duration.as('m')); 4573 var hours = round(duration.as('h')); 4574 var days = round(duration.as('d')); 4575 var months = round(duration.as('M')); 4576 var years = round(duration.as('y')); 4577 4578 var a = seconds < thresholds.s && ['s', seconds] || 4579 minutes === 1 && ['m'] || 4580 minutes < thresholds.m && ['mm', minutes] || 4581 hours === 1 && ['h'] || 4582 hours < thresholds.h && ['hh', hours] || 4583 days === 1 && ['d'] || 4584 days < thresholds.d && ['dd', days] || 4585 months === 1 && ['M'] || 4586 months < thresholds.M && ['MM', months] || 4587 years === 1 && ['y'] || ['yy', years]; 4588 4589 a[2] = withoutSuffix; 4590 a[3] = +posNegDuration > 0; 4591 a[4] = locale; 4592 return substituteTimeAgo.apply(null, a); 4593 } 4594 4595 // This function allows you to set a threshold for relative time strings 4596 function duration_humanize__getSetRelativeTimeThreshold (threshold, limit) { 4597 if (thresholds[threshold] === undefined) { 4598 return false; 4599 } 4600 if (limit === undefined) { 4601 return thresholds[threshold]; 4602 } 4603 thresholds[threshold] = limit; 4604 return true; 4605 } 4606 4607 function humanize (withSuffix) { 4608 var locale = this.localeData(); 4609 var output = duration_humanize__relativeTime(this, !withSuffix, locale); 4610 4611 if (withSuffix) { 4612 output = locale.pastFuture(+this, output); 4613 } 4614 4615 return locale.postformat(output); 4616 } 4617 4618 var iso_string__abs = Math.abs; 4619 4620 function iso_string__toISOString() { 4621 // for ISO strings we do not use the normal bubbling rules: 4622 // * milliseconds bubble up until they become hours 4623 // * days do not bubble at all 4624 // * months bubble up until they become years 4625 // This is because there is no context-free conversion between hours and days 4626 // (think of clock changes) 4627 // and also not between days and months (28-31 days per month) 4628 var seconds = iso_string__abs(this._milliseconds) / 1000; 4629 var days = iso_string__abs(this._days); 4630 var months = iso_string__abs(this._months); 4631 var minutes, hours, years; 4632 4633 // 3600 seconds -> 60 minutes -> 1 hour 4634 minutes = absFloor(seconds / 60); 4635 hours = absFloor(minutes / 60); 4636 seconds %= 60; 4637 minutes %= 60; 4638 4639 // 12 months -> 1 year 4640 years = absFloor(months / 12); 4641 months %= 12; 4642 4643 4644 // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js 4645 var Y = years; 4646 var M = months; 4647 var D = days; 4648 var h = hours; 4649 var m = minutes; 4650 var s = seconds; 4651 var total = this.asSeconds(); 4652 4653 if (!total) { 4654 // this is the same as C#'s (Noda) and python (isodate)... 4655 // but not other JS (goog.date) 4656 return 'P0D'; 4657 } 4658 4659 return (total < 0 ? '-' : '') + 4660 'P' + 4661 (Y ? Y + 'Y' : '') + 4662 (M ? M + 'M' : '') + 4663 (D ? D + 'D' : '') + 4664 ((h || m || s) ? 'T' : '') + 4665 (h ? h + 'H' : '') + 4666 (m ? m + 'M' : '') + 4667 (s ? s + 'S' : ''); 4668 } 4669 4670 var duration_prototype__proto = Duration.prototype; 4671 4672 duration_prototype__proto.abs = duration_abs__abs; 4673 duration_prototype__proto.add = duration_add_subtract__add; 4674 duration_prototype__proto.subtract = duration_add_subtract__subtract; 4675 duration_prototype__proto.as = as; 4676 duration_prototype__proto.asMilliseconds = asMilliseconds; 4677 duration_prototype__proto.asSeconds = asSeconds; 4678 duration_prototype__proto.asMinutes = asMinutes; 4679 duration_prototype__proto.asHours = asHours; 4680 duration_prototype__proto.asDays = asDays; 4681 duration_prototype__proto.asWeeks = asWeeks; 4682 duration_prototype__proto.asMonths = asMonths; 4683 duration_prototype__proto.asYears = asYears; 4684 duration_prototype__proto.valueOf = duration_as__valueOf; 4685 duration_prototype__proto._bubble = bubble; 4686 duration_prototype__proto.get = duration_get__get; 4687 duration_prototype__proto.milliseconds = milliseconds; 4688 duration_prototype__proto.seconds = seconds; 4689 duration_prototype__proto.minutes = minutes; 4690 duration_prototype__proto.hours = hours; 4691 duration_prototype__proto.days = days; 4692 duration_prototype__proto.weeks = weeks; 4693 duration_prototype__proto.months = months; 4694 duration_prototype__proto.years = years; 4695 duration_prototype__proto.humanize = humanize; 4696 duration_prototype__proto.toISOString = iso_string__toISOString; 4697 duration_prototype__proto.toString = iso_string__toISOString; 4698 duration_prototype__proto.toJSON = iso_string__toISOString; 4699 duration_prototype__proto.locale = locale; 4700 duration_prototype__proto.localeData = localeData; 4701 4702 // Deprecations 4703 duration_prototype__proto.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', iso_string__toISOString); 4704 duration_prototype__proto.lang = lang; 4705 4706 // Side effect imports 4707 4708 addFormatToken('X', 0, 0, 'unix'); 4709 addFormatToken('x', 0, 0, 'valueOf'); 4710 4711 // PARSING 4712 4713 addRegexToken('x', matchSigned); 4714 addRegexToken('X', matchTimestamp); 4715 addParseToken('X', function (input, array, config) { 4716 config._d = new Date(parseFloat(input, 10) * 1000); 4717 }); 4718 addParseToken('x', function (input, array, config) { 4719 config._d = new Date(toInt(input)); 4720 }); 4721 4722 // Side effect imports 4723 4724 4725 utils_hooks__hooks.version = '2.10.6'; 4726 4727 setHookCallback(local__createLocal); 4728 4729 utils_hooks__hooks.fn = momentPrototype; 4730 utils_hooks__hooks.min = min; 4731 utils_hooks__hooks.max = max; 4732 utils_hooks__hooks.utc = create_utc__createUTC; 4733 utils_hooks__hooks.unix = moment__createUnix; 4734 utils_hooks__hooks.months = lists__listMonths; 4735 utils_hooks__hooks.isDate = isDate; 4736 utils_hooks__hooks.locale = locale_locales__getSetGlobalLocale; 4737 utils_hooks__hooks.invalid = valid__createInvalid; 4738 utils_hooks__hooks.duration = create__createDuration; 4739 utils_hooks__hooks.isMoment = isMoment; 4740 utils_hooks__hooks.weekdays = lists__listWeekdays; 4741 utils_hooks__hooks.parseZone = moment__createInZone; 4742 utils_hooks__hooks.localeData = locale_locales__getLocale; 4743 utils_hooks__hooks.isDuration = isDuration; 4744 utils_hooks__hooks.monthsShort = lists__listMonthsShort; 4745 utils_hooks__hooks.weekdaysMin = lists__listWeekdaysMin; 4746 utils_hooks__hooks.defineLocale = defineLocale; 4747 utils_hooks__hooks.weekdaysShort = lists__listWeekdaysShort; 4748 utils_hooks__hooks.normalizeUnits = normalizeUnits; 4749 utils_hooks__hooks.relativeTimeThreshold = duration_humanize__getSetRelativeTimeThreshold; 4750 4751 var _moment = utils_hooks__hooks; 4752 4753 return _moment; 4754 4755 })); 4756 /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)(module))) 4757 4758/***/ }, 4759/* 4 */
4760/***/ function(module, exports) { 4761 4762 module.exports = function(module) { 4763 if(!module.webpackPolyfill) { 4764 module.deprecate = function() {}; 4765 module.paths = []; 4766 // module.parent = undefined by default 4767 module.children = []; 4768 module.webpackPolyfill = 1; 4769 } 4770 return module; 4771 } 4772 4773 4774/***/ }, 4775/* 5 */ 4776/***/ function(module, exports) { 4777 4778 function webpackContext(req) { 4779 throw new Error("Cannot find module '" + req + "'."); 4780 } 4781 webpackContext.keys = function() { return []; }; 4782 webpackContext.resolve = webpackContext; 4783 module.exports = webpackContext; 4784 webpackContext.id = 5; 4785 4786 4787/***/ }, 4788/* 6 */ 4789/***/ function(module, exports) { 4790 4791 /* WEBPACK VAR INJECTION */(function(global) {'use strict'; 4792 4793 var _rng; 4794 4795 var globalVar = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : null; 4796 4797 if (globalVar && globalVar.crypto && crypto.getRandomValues) { 4798 // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto 4799 // Moderately fast, high quality 4800 var _rnds8 = new Uint8Array(16); 4801 _rng = function whatwgRNG() { 4802 crypto.getRandomValues(_rnds8); 4803 return _rnds8; 4804 }; 4805 } 4806 4807 if (!_rng) { 4808 // Math.random()-based (RNG) 4809 // 4810 // If all else fails, use Math.random(). It's fast, but is of unspecified 4811 // quality. 4812 var _rnds = new Array(16); 4813 _rng = function () { 4814 for (var i = 0, r; i < 16; i++) { 4815 if ((i & 0x03) === 0) r = Math.random() * 0x100000000; 4816 _rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;
vendor: 4,189 bytes, lines 4817-4943
4817 } 4818 4819 return _rnds; 4820 }; 4821 } 4822 4823 // uuid.js 4824 // 4825 // Copyright (c) 2010-2012 Robert Kieffer 4826 // MIT License - http://opensource.org/licenses/mit-license.php 4827 4828 // Unique ID creation requires a high quality random # generator. We feature 4829 // detect to determine the best RNG source, normalizing to a function that 4830 // returns 128-bits of randomness, since that's what's usually required 4831 4832 //var _rng = require('./rng'); 4833 4834 // Maps for number <-> hex string conversion 4835 var _byteToHex = []; 4836 var _hexToByte = {}; 4837 for (var i = 0; i < 256; i++) { 4838 _byteToHex[i] = (i + 0x100).toString(16).substr(1); 4839 _hexToByte[_byteToHex[i]] = i; 4840 } 4841 4842 // **`parse()` - Parse a UUID into it's component bytes** 4843 function parse(s, buf, offset) { 4844 var i = buf && offset || 0, 4845 ii = 0; 4846 4847 buf = buf || []; 4848 s.toLowerCase().replace(/[0-9a-f]{2}/g, function (oct) { 4849 if (ii < 16) { 4850 // Don't overflow! 4851 buf[i + ii++] = _hexToByte[oct]; 4852 } 4853 }); 4854 4855 // Zero out remaining bytes if string was short 4856 while (ii < 16) { 4857 buf[i + ii++] = 0; 4858 } 4859 4860 return buf; 4861 } 4862 4863 // **`unparse()` - Convert UUID byte array (ala parse()) into a string** 4864 function unparse(buf, offset) { 4865 var i = offset || 0, 4866 bth = _byteToHex; 4867 return bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + '-' + bth[buf[i++]] + bth[buf[i++]] + '-' + bth[buf[i++]] + bth[buf[i++]] + '-' + bth[buf[i++]] + bth[buf[i++]] + '-' + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]]; 4868 } 4869 4870 // **`v1()` - Generate time-based UUID** 4871 // 4872 // Inspired by https://github.com/LiosK/UUID.js 4873 // and http://docs.python.org/library/uuid.html 4874 4875 // random #'s we need to init node and clockseq 4876 var _seedBytes = _rng(); 4877 4878 // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1) 4879 var _nodeId = [_seedBytes[0] | 0x01, _seedBytes[1], _seedBytes[2], _seedBytes[3], _seedBytes[4], _seedBytes[5]]; 4880 4881 // Per 4.2.2, randomize (14 bit) clockseq 4882 var _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff; 4883 4884 // Previous uuid creation time 4885 var _lastMSecs = 0, 4886 _lastNSecs = 0; 4887 4888 // See https://github.com/broofa/node-uuid for API details 4889 function v1(options, buf, offset) { 4890 var i = buf && offset || 0; 4891 var b = buf || []; 4892 4893 options = options || {}; 4894 4895 var clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq; 4896 4897 // UUID timestamps are 100 nano-second units since the Gregorian epoch, 4898 // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so 4899 // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs' 4900 // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00. 4901 var msecs = options.msecs !== undefined ? options.msecs : new Date().getTime(); 4902 4903 // Per 4.2.1.2, use count of uuid's generated during the current clock 4904 // cycle to simulate higher resolution clock 4905 var nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1; 4906 4907 // Time since last uuid creation (in msecs) 4908 var dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000; 4909 4910 // Per 4.2.1.2, Bump clockseq on clock regression 4911 if (dt < 0 && options.clockseq === undefined) { 4912 clockseq = clockseq + 1 & 0x3fff; 4913 } 4914 4915 // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new 4916 // time interval 4917 if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) { 4918 nsecs = 0; 4919 } 4920 4921 // Per 4.2.1.2 Throw error if too many uuids are requested 4922 if (nsecs >= 10000) { 4923 throw new Error('uuid.v1(): Can\'t create more than 10M uuids/sec'); 4924 } 4925 4926 _lastMSecs = msecs; 4927 _lastNSecs = nsecs; 4928 _clockseq = clockseq; 4929 4930 // Per 4.1.4 - Convert from unix epoch to Gregorian epoch 4931 msecs += 12219292800000; 4932 4933 // `time_low` 4934 var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000; 4935 b[i++] = tl >>> 24 & 0xff; 4936 b[i++] = tl >>> 16 & 0xff; 4937 b[i++] = tl >>> 8 & 0xff; 4938 b[i++] = tl & 0xff; 4939 4940 // `time_mid` 4941 var tmh = msecs / 0x100000000 * 10000 & 0xfffffff; 4942 b[i++] = tmh >>> 8 & 0xff; 4943 b[i++] = tmh & 0xff;
4944 4945 // `time_high_and_version` 4946 b[i++] = tmh >>> 24 & 0xf | 0x10; // include version 4947 b[i++] = tmh >>> 16 & 0xff; 4948 4949 // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant) 4950 b[i++] = clockseq >>> 8 | 0x80; 4951 4952 // `clock_seq_low` 4953 b[i++] = clockseq & 0xff; 4954 4955 // `node` 4956 var node = options.node || _nodeId; 4957 for (var n = 0; n < 6; n++) { 4958 b[i + n] = node[n]; 4959 } 4960 4961 return buf ? buf : unparse(b); 4962 } 4963 4964 // **`v4()` - Generate random UUID** 4965 4966 // See https://github.com/broofa/node-uuid for API details 4967 function v4(options, buf, offset) { 4968 // Deprecated - 'format' argument, as supported in v1.2 4969 var i = buf && offset || 0; 4970 4971 if (typeof options == 'string') { 4972 buf = options == 'binary' ? new Array(16) : null; 4973 options = null; 4974 } 4975 options = options || {}; 4976 4977 var rnds = options.random || (options.rng || _rng)(); 4978 4979 // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` 4980 rnds[6] = rnds[6] & 0x0f | 0x40; 4981 rnds[8] = rnds[8] & 0x3f | 0x80; 4982 4983 // Copy bytes to buffer, if provided 4984 if (buf) { 4985 for (var ii = 0; ii < 16; ii++) { 4986 buf[i + ii] = rnds[ii]; 4987 } 4988 } 4989 4990 return buf || unparse(rnds); 4991 } 4992 4993 // Export public API 4994 var uuid = v4; 4995 uuid.v1 = v1; 4996 uuid.v4 = v4; 4997 uuid.parse = parse; 4998 uuid.unparse = unparse; 4999 5000 module.exports = uuid; 5001 /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) 5002 5003/***/ }, 5004/* 7 */ 5005/***/ function(module, exports) { 5006 5007 // DOM utility methods 5008 5009 /** 5010 * this prepares the JSON container for allocating SVG elements 5011 * @param JSONcontainer 5012 * @private 5013 */ 5014 'use strict'; 5015 5016 exports.prepareElements = function (JSONcontainer) { 5017 // cleanup the redundant svgElements; 5018 for (var elementType in JSONcontainer) { 5019 if (JSONcontainer.hasOwnProperty(elementType)) { 5020 JSONcontainer[elementType].redundant = JSONcontainer[elementType].used; 5021 JSONcontainer[elementType].used = []; 5022 } 5023 } 5024 }; 5025 5026 /** 5027 * this cleans up all the unused SVG elements. By asking for the parentNode, we only need to supply the JSON container from 5028 * which to remove the redundant elements. 5029 * 5030 * @param JSONcontainer 5031 * @private 5032 */ 5033 exports.cleanupElements = function (JSONcontainer) { 5034 // cleanup the redundant svgElements; 5035 for (var elementType in JSONcontainer) { 5036 if (JSONcontainer.hasOwnProperty(elementType)) { 5037 if (JSONcontainer[elementType].redundant) { 5038 for (var i = 0; i < JSONcontainer[elementType].redundant.length; i++) { 5039 JSONcontainer[elementType].redundant[i].parentNode.removeChild(JSONcontainer[elementType].redundant[i]); 5040 } 5041 JSONcontainer[elementType].redundant = []; 5042 } 5043 } 5044 } 5045 }; 5046 5047 /** 5048 * Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer 5049 * the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this. 5050 * 5051 * @param elementType 5052 * @param JSONcontainer 5053 * @param svgContainer 5054 * @returns {*} 5055 * @private 5056 */ 5057 exports.getSVGElement = function (elementType, JSONcontainer, svgContainer) { 5058 var element; 5059 // allocate SVG element, if it doesnt yet exist, create one. 5060 if (JSONcontainer.hasOwnProperty(elementType)) { 5061 // this element has been created before 5062 // check if there is an redundant element 5063 if (JSONcontainer[elementType].redundant.length > 0) { 5064 element = JSONcontainer[elementType].redundant[0]; 5065 JSONcontainer[elementType].redundant.shift(); 5066 } else { 5067 // create a new element and add it to the SVG 5068 element = document.createElementNS('http://www.w3.org/2000/svg', elementType); 5069 svgContainer.appendChild(element); 5070 } 5071 } else { 5072 // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it. 5073 element = document.createElementNS('http://www.w3.org/2000/svg', elementType); 5074 JSONcontainer[elementType] = { used: [], redundant: [] }; 5075 svgContainer.appendChild(element); 5076 } 5077 JSONcontainer[elementType].used.push(element); 5078 return element; 5079 }; 5080 5081 /** 5082 * Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer 5083 * the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this. 5084 * 5085 * @param elementType 5086 * @param JSONcontainer 5087 * @param DOMContainer 5088 * @returns {*} 5089 * @private 5090 */ 5091 exports.getDOMElement = function (elementType, JSONcontainer, DOMContainer, insertBefore) { 5092 var element; 5093 // allocate DOM element, if it doesnt yet exist, create one. 5094 if (JSONcontainer.hasOwnProperty(elementType)) { 5095 // this element has been created before 5096 // check if there is an redundant element 5097 if (JSONcontainer[elementType].redundant.length > 0) { 5098 element = JSONcontainer[elementType].redundant[0]; 5099 JSONcontainer[elementType].redundant.shift(); 5100 } else { 5101 // create a new element and add it to the SVG 5102 element = document.createElement(elementType); 5103 if (insertBefore !== undefined) { 5104 DOMContainer.insertBefore(element, insertBefore); 5105 } else { 5106 DOMContainer.appendChild(element); 5107 } 5108 } 5109 } else { 5110 // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it. 5111 element = document.createElement(elementType); 5112 JSONcontainer[elementType] = { used: [], redundant: [] }; 5113 if (insertBefore !== undefined) { 5114 DOMContainer.insertBefore(element, insertBefore); 5115 } else { 5116 DOMContainer.appendChild(element); 5117 } 5118 } 5119 JSONcontainer[elementType].used.push(element); 5120 return element; 5121 }; 5122 5123 /** 5124 * Draw a point object. This is a separate function because it can also be called by the legend. 5125 * The reason the JSONcontainer and the target SVG svgContainer have to be supplied is so the legend can use these functions 5126 * as well. 5127 * 5128 * @param x 5129 * @param y 5130 * @param groupTemplate: A template containing the necessary information to draw the datapoint e.g., {style: 'circle', size: 5, className: 'className' } 5131 * @param JSONcontainer 5132 * @param svgContainer 5133 * @param labelObj 5134 * @returns {*} 5135 */ 5136 exports.drawPoint = function (x, y, groupTemplate, JSONcontainer, svgContainer, labelObj) { 5137 var point; 5138 if (groupTemplate.style == 'circle') { 5139 point = exports.getSVGElement('circle', JSONcontainer, svgContainer); 5140 point.setAttributeNS(null, 'cx', x); 5141 point.setAttributeNS(null, 'cy', y); 5142 point.setAttributeNS(null, 'r', 0.5 * groupTemplate.size); 5143 } else { 5144 point = exports.getSVGElement('rect', JSONcontainer, svgContainer); 5145 point.setAttributeNS(null, 'x', x - 0.5 * groupTemplate.size); 5146 point.setAttributeNS(null, 'y', y - 0.5 * groupTemplate.size); 5147 point.setAttributeNS(null, 'width', groupTemplate.size); 5148 point.setAttributeNS(null, 'height', groupTemplate.size); 5149 } 5150 5151 if (groupTemplate.styles !== undefined) { 5152 point.setAttributeNS(null, 'style', groupTemplate.styles); 5153 } 5154 point.setAttributeNS(null, 'class', groupTemplate.className + ' vis-point'); 5155 //handle label 5156 5157 if (labelObj) { 5158 var label = exports.getSVGElement('text', JSONcontainer, svgContainer); 5159 if (labelObj.xOffset) { 5160 x = x + labelObj.xOffset; 5161 } 5162 5163 if (labelObj.yOffset) { 5164 y = y + labelObj.yOffset; 5165 } 5166 if (labelObj.content) { 5167 label.textContent = labelObj.content; 5168 } 5169 5170 if (labelObj.className) { 5171 label.setAttributeNS(null, 'class', labelObj.className + ' vis-label'); 5172 } 5173 label.setAttributeNS(null, 'x', x); 5174 label.setAttributeNS(null, 'y', y); 5175 } 5176 5177 return point; 5178 }; 5179 5180 /** 5181 * draw a bar SVG element centered on the X coordinate 5182 * 5183 * @param x 5184 * @param y 5185 * @param className 5186 */ 5187 exports.drawBar = function (x, y, width, height, className, JSONcontainer, svgContainer, style) { 5188 if (height != 0) { 5189 if (height < 0) { 5190 height *= -1; 5191 y -= height; 5192 } 5193 var rect = exports.getSVGElement('rect', JSONcontainer, svgContainer); 5194 rect.setAttributeNS(null, 'x', x - 0.5 * width); 5195 rect.setAttributeNS(null, 'y', y); 5196 rect.setAttributeNS(null, 'width', width); 5197 rect.setAttributeNS(null, 'height', height); 5198 rect.setAttributeNS(null, 'class', className); 5199 if (style) { 5200 rect.setAttributeNS(null, 'style', style); 5201 } 5202 } 5203 }; 5204 5205/***/ }, 5206/* 8 */ 5207/***/ function(module, exports, __webpack_require__) { 5208 5209 'use strict'; 5210 5211 var util = __webpack_require__(1); 5212 var Queue = __webpack_require__(9); 5213 5214 /** 5215 * DataSet 5216 * 5217 * Usage: 5218 * var dataSet = new DataSet({ 5219 * fieldId: '_id', 5220 * type: { 5221 * // ... 5222 * } 5223 * }); 5224 * 5225 * dataSet.add(item); 5226 * dataSet.add(data); 5227 * dataSet.update(item); 5228 * dataSet.update(data); 5229 * dataSet.remove(id); 5230 * dataSet.remove(ids); 5231 * var data = dataSet.get(); 5232 * var data = dataSet.get(id); 5233 * var data = dataSet.get(ids); 5234 * var data = dataSet.get(ids, options, data); 5235 * dataSet.clear(); 5236 * 5237 * A data set can: 5238 * - add/remove/update data 5239 * - gives triggers upon changes in the data 5240 * - can import/export data in various data formats 5241 * 5242 * @param {Array} [data] Optional array with initial data 5243 * @param {Object} [options] Available options: 5244 * {String} fieldId Field name of the id in the 5245 * items, 'id' by default. 5246 * {Object.<String, String} type 5247 * A map with field names as key, 5248 * and the field type as value. 5249 * {Object} queue Queue changes to the DataSet, 5250 * flush them all at once. 5251 * Queue options: 5252 * - {number}
5252 delay Delay in ms, null by default 5253 * - {number} max Maximum number of entries in the queue, Infinity by default 5254 * @constructor DataSet 5255 */ 5256 // TODO: add a DataSet constructor DataSet(data, options) 5257 function DataSet(data, options) { 5258 // correctly read optional arguments 5259 if (data && !Array.isArray(data)) { 5260 options = data; 5261 data = null; 5262 } 5263 5264 this._options = options || {}; 5265 this._data = {}; // map with data indexed by id 5266 this.length = 0; // number of items in the DataSet 5267 this._fieldId = this._options.fieldId || 'id'; // name of the field containing id 5268 this._type = {}; // internal field types (NOTE: this can differ from this._options.type) 5269 5270 // all variants of a Date are internally stored as Date, so we can convert 5271 // from everything to everything (also from ISODate to Number for example) 5272 if (this._options.type) { 5273 for (var field in this._options.type) { 5274 if (this._options.type.hasOwnProperty(field)) { 5275 var value = this._options.type[field]; 5276 if (value == 'Date' || value == 'ISODate' || value == 'ASPDate') { 5277 this._type[field] = 'Date'; 5278 } else { 5279 this._type[field] = value; 5280 } 5281 } 5282 } 5283 } 5284 5285 // TODO: deprecated since version 1.1.1 (or 2.0.0?) 5286 if (this._options.convert) { 5287 throw new Error('Option "convert" is deprecated. Use "type" instead.'); 5288 } 5289 5290 this._subscribers = {}; // event subscribers 5291 5292 // add initial data when provided 5293 if (data) { 5294 this.add(data); 5295 } 5296 5297 this.setOptions(options); 5298 } 5299 5300 /** 5301 * @param {Object} [options] Available options: 5302 * {Object} queue Queue changes to the DataSet, 5303 * flush them all at once. 5304 * Queue options: 5305 * - {number} delay Delay in ms, null by default 5306 * - {number} max Maximum number of entries in the queue, Infinity by default 5307 * @param options 5308 */ 5309 DataSet.prototype.setOptions = function (options) { 5310 if (options && options.queue !== undefined) { 5311 if (options.queue === false) { 5312 // delete queue if loaded 5313 if (this._queue) { 5314 this._queue.destroy(); 5315 delete this._queue; 5316 } 5317 } else { 5318 // create queue and update its options 5319 if (!this._queue) { 5320 this._queue = Queue.extend(this, { 5321 replace: ['add', 'update', 'remove'] 5322 }); 5323 } 5324 5325 if (typeof options.queue === 'object') { 5326 this._queue.setOptions(options.queue); 5327 } 5328 } 5329 } 5330 }; 5331 5332 /** 5333 * Subscribe to an event, add an event listener 5334 * @param {String} event Event name. Available events: 'put', 'update', 5335 * 'remove' 5336 * @param {function} callback Callback method. Called with three parameters: 5337 * {String} event 5338 * {Object | null} params 5339 * {String | Number} senderId 5340 */ 5341 DataSet.prototype.on = function (event, callback) { 5342 var subscribers = this._subscribers[event]; 5343 if (!subscribers) { 5344 subscribers = []; 5345 this._subscribers[event] = subscribers; 5346 } 5347 5348 subscribers.push({ 5349 callback: callback 5350 }); 5351 }; 5352 5353 // TODO: remove this deprecated function some day (replaced with `on` since version 0.5, deprecated since v4.0) 5354 DataSet.prototype.subscribe = function () { 5355 throw new Error('DataSet.subscribe is deprecated. Use DataSet.on instead.'); 5356 }; 5357 5358 /** 5359 * Unsubscribe from an event, remove an event listener 5360 * @param {String} event 5361 * @param {function} callback 5362 */ 5363 DataSet.prototype.off = function (event, callback) { 5364 var subscribers = this._subscribers[event]; 5365 if (subscribers) { 5366 this._subscribers[event] = subscribers.filter(function (listener) { 5367 return listener.callback != callback; 5368 }); 5369 } 5370 }; 5371 5372 // TODO: remove this deprecated function some day (replaced with `on` since version 0.5, deprecated since v4.0) 5373 DataSet.prototype.unsubscribe = function () { 5374 throw new Error('DataSet.unsubscribe is deprecated. Use DataSet.off instead.'); 5375 }; 5376 5377 /** 5378 * Trigger an event 5379 * @param {String} event 5380 * @param {Object | null} params 5381 * @param {String} [senderId] Optional id of the sender. 5382 * @private 5383 */ 5384 DataSet.prototype._trigger = function (event, params, senderId) { 5385 if (event == '*') { 5386 throw new Error('Cannot trigger event *'); 5387 } 5388 5389 var subscribers = []; 5390 if (event in this._subscribers) { 5391 subscribers = subscribers.concat(this._subscribers[event]); 5392 } 5393 if ('*' in this._subscribers) { 5394 subscribers = subscribers.concat(this._subscribers['*']); 5395 } 5396 5397 for (var i = 0; i < subscribers.length; i++) { 5398 var subscriber = subscribers[i]; 5399 if (subscriber.callback) { 5400 subscriber.callback(event, params, senderId || null); 5401 } 5402 } 5403 }; 5404 5405 /** 5406 * Add data. 5407 * Adding an item will fail when there already is an item with the same id. 5408 * @param {Object | Array} data 5409 * @param {String} [senderId] Optional sender id 5410 * @return {Array} addedIds Array with the ids of the added items 5411 */ 5412 DataSet.prototype.add = function (data, senderId) { 5413 var addedIds = [], 5414 id, 5415 me = this; 5416 5417 if (Array.isArray(data)) { 5418 // Array 5419 for (var i = 0, len = data.length; i < len; i++) { 5420 id = me._addItem(data[i]); 5421 addedIds.push(id); 5422 } 5423 } else if (data instanceof Object) { 5424 // Single item 5425 id = me._addItem(data); 5426 addedIds.push(id); 5427 } else { 5428 throw new Error('Unknown dataType'); 5429 } 5430 5431 if (addedIds.length) { 5432 this._trigger('add', { items: addedIds }, senderId); 5433 } 5434 5435 return addedIds; 5436 }; 5437 5438 /** 5439 * Update existing items. When an item does not exist, it will be created 5440 * @param {Object | Array} data 5441 * @param {String} [senderId] Optional sender id 5442 * @return {Array} updatedIds The ids of the added or updated items 5443 */ 5444 DataSet.prototype.update = function (data, senderId) { 5445 var addedIds = []; 5446 var updatedIds = []; 5447 var updatedData = []; 5448 var me = this; 5449 var fieldId = me._fieldId; 5450 5451 var addOrUpdate = function addOrUpdate(item) { 5452 var id = item[fieldId]; 5453 if (me._data[id]) { 5454 // update item 5455 id = me._updateItem(item); 5456 updatedIds.push(id); 5457 updatedData.push(item); 5458 } else { 5459 // add new item 5460 id = me._addItem(item); 5461 addedIds.push(id); 5462 } 5463 }; 5464 5465 if (Array.isArray(data)) { 5466 // Array 5467 for (var i = 0, len = data.length; i < len; i++) { 5468 addOrUpdate(data[i]); 5469 } 5470 } else if (data instanceof Object) { 5471 // Single item 5472 addOrUpdate(data); 5473 } else { 5474 throw new Error('Unknown dataType'); 5475 } 5476 5477 if (addedIds.length) { 5478 this._trigger('add', { items: addedIds }, senderId); 5479 } 5480 if (updatedIds.length) { 5481 this._trigger('update', { items: updatedIds, data: updatedData }, senderId); 5482 } 5483 5484 return addedIds.concat(updatedIds); 5485 }; 5486 5487 /** 5488 * Get a data item or multiple items. 5489 * 5490 * Usage: 5491 * 5492 * get() 5493 * get(options: Object) 5494 * 5495 * get(id: Number | String) 5496 * get(id: Number | String, options: Object) 5497 * 5498 * get(ids: Number[] | String[]) 5499 * get(ids: Number[] | String[], options: Object) 5500 * 5501 * Where: 5502 * 5503 * {Number | String} id The id of an item 5504 * {Number[] | String{}} ids An array with ids of items 5505 * {Object} options An Object with options. Available options: 5506 * {String} [returnType] Type of data to be returned. 5507 * Can be 'Array' (default) or 'Object'. 5508 * {Object.<String, String>} [type] 5509 * {String[]} [fields] field names to be returned 5510 * {function} [filter] filter items 5511 * {String | function} [order] Order the items by a field name or custom sort function. 5512 * @throws Error 5513 */ 5514 DataSet.prototype.get = function (args) { 5515 var me = this; 5516 5517 // parse the arguments 5518 var id, ids, options; 5519 var firstType = util.getType(arguments[0]); 5520 if (firstType == 'String' || firstType == 'Number') { 5521 // get(id [, options]) 5522 id = arguments[0]; 5523 options = arguments[1]; 5524 } else if (firstType == 'Array') { 5525 // get(ids [, options]) 5526 ids = arguments[0]; 5527 options = arguments[1]; 5528 } else { 5529 // get([, options]) 5530 options = arguments[0]; 5531 } 5532 5533 // determine the return type 5534 var returnType; 5535 if (options && options.returnType) { 5536 var allowedValues = ['Array', 'Object']; 5537 returnType = allowedValues.indexOf(options.returnType) == -1 ? 'Array' : options.returnType; 5538 } else { 5539 returnType = 'Array'; 5540 } 5541 5542 // build options 5543 var type = options && options.type || this._options.type; 5544 var filter = options && options.filter; 5545 var items = [], 5546 item, 5547 itemId, 5548 i, 5549 len; 5550 5551 // convert items 5552 if (id != undefined) { 5553 // return a single item 5554 item = me._getItem(id, type); 5555 if (filter && !filter(item)) { 5556 item = null; 5557 } 5558 } else if (ids != undefined) { 5559 // return a subset of items 5560 for (i = 0, len = ids.length; i < len; i++) { 5561 item = me._getItem(ids[i], type); 5562 if (!filter || filter(item)) { 5563 items.push(item); 5564 } 5565 } 5566 } else { 5567 // return all items 5568 for (itemId in this._data) { 5569 if (this._data.hasOwnProperty(itemId)) { 5570 item = me._getItem(itemId, type); 5571 if (!filter || filter(item)) { 5572 items.push(item); 5573 } 5574 } 5575 } 5576 } 5577 5578 // order the results 5579 if (options && options.order && id == undefined) { 5580 this._sort(items, options.order); 5581 } 5582 5583 // filter fields of the items 5584 if (options && options.fields) { 5585 var fields = options.fields; 5586 if (id != undefined) { 5587 item = this._filterFields(item, fields); 5588 } else { 5589 for (i = 0, len = items.length; i < len; i++) { 5590 items[i] = this._filterFields(items[i], fields); 5591 } 5592 } 5593 } 5594 5595 // return the results 5596 if (returnType == 'Object') { 5597 var result = {}; 5598 for (i = 0; i < items.length; i++) { 5599 result[items[i].id] = items[i]; 5600 } 5601 return result; 5602 } else { 5603 if (id != undefined) { 5604 // a single item 5605 return item; 5606 } else { 5607 // just return our array 5608 return items; 5609 } 5610 } 5611 }; 5612 5613 /** 5614 * Get ids of all items or from a filtered set of items. 5615 * @param {Object} [options] An Object with options. Available options: 5616 * {function} [filter] filter items 5617 * {String | function} [order] Order the items by 5618 * a field name or custom sort function. 5619 * @return {Array} ids 5620 */ 5621 DataSet.prototype.getIds = function (options) { 5622 var data = this._data, 5623 filter = options && options.filter, 5624 order = options && options.order, 5625 type = options && options.type || this._options.type, 5626 i, 5627 len, 5628 id, 5629 item, 5630 items, 5631 ids = []; 5632 5633 if (filter) { 5634 // get filtered items 5635 if (order) { 5636 // create ordered list 5637 items = []; 5638 for (id in data) { 5639 if (data.hasOwnProperty(id)) { 5640 item = this._getItem(id, type); 5641 if (filter(item)) { 5642 items.push(item); 5643 } 5644 } 5645 } 5646 5647 this._sort(items, order); 5648 5649 for (i = 0, len = items.length; i < len; i++) { 5650 ids[i] = items[i][this._fieldId]; 5651 } 5652 } else { 5653 // create unordered list 5654 for (id in data) { 5655 if (data.hasOwnProperty(id)) { 5656 item = this._getItem(id, type); 5657 if (filter(item)) { 5658 ids.push(item[this._fieldId]); 5659 } 5660 } 5661 } 5662 } 5663 } else { 5664 // get all items 5665 if (order) { 5666 // create an ordered list 5667 items = []; 5668 for (id in data) { 5669 if (data.hasOwnProperty(id)) { 5670 items.push(data[id]); 5671 } 5672 } 5673 5674 this._sort(items, order); 5675 5676 for (i = 0, len = items.length; i < len; i++) { 5677 ids[i] = items[i][this._fieldId]; 5678 } 5679 } else { 5680 // create unordered list 5681 for (id in data) { 5682 if (data.hasOwnProperty(id)) { 5683 item = data[id]; 5684 ids.push(item[this._fieldId]); 5685 } 5686 } 5687 } 5688 } 5689 5690 return ids; 5691 }; 5692 5693 /** 5694 * Returns the DataSet itself. Is overwritten for example by the DataView, 5695 * which returns the DataSet it is connected to instead. 5696 */ 5697 DataSet.prototype.getDataSet = function () { 5698 return this; 5699 }; 5700 5701 /** 5702 * Execute a callback function for every item in the dataset. 5703 * @param {function} callback 5704 * @param {Object} [options] Available options: 5705 * {Object.<String, String>} [type] 5706 * {String[]} [fields] filter fields 5707 * {function} [filter] filter items 5708 * {String | function} [order] Order the items by 5709 * a field name or custom sort function. 5710 */ 5711 DataSet.prototype.forEach = function (callback, options) { 5712 var filter = options && options.filter, 5713 type = options && options.type || this._options.type, 5714 data = this._data, 5715 item, 5716 id; 5717 5718 if (options && options.order) {
5719 // execute forEach on ordered list 5720 var items = this.get(options); 5721 5722 for (var i = 0, len = items.length; i < len; i++) { 5723 item = items[i]; 5724 id = item[this._fieldId]; 5725 callback(item, id); 5726 } 5727 } else { 5728 // unordered 5729 for (id in data) { 5730 if (data.hasOwnProperty(id)) { 5731 item = this._getItem(id, type); 5732 if (!filter || filter(item)) { 5733 callback(item, id); 5734 } 5735 } 5736 } 5737 } 5738 }; 5739 5740 /** 5741 * Map every item in the dataset. 5742 * @param {function} callback 5743 * @param {Object} [options] Available options: 5744 * {Object.<String, String>} [type] 5745 * {String[]} [fields] filter fields 5746 * {function} [filter] filter items 5747 * {String | function} [order] Order the items by 5748 * a field name or custom sort function. 5749 * @return {Object[]} mappedItems 5750 */ 5751 DataSet.prototype.map = function (callback, options) { 5752 var filter = options && options.filter, 5753 type = options && options.type || this._options.type, 5754 mappedItems = [], 5755 data = this._data, 5756 item; 5757 5758 // convert and filter items 5759 for (var id in data) { 5760 if (data.hasOwnProperty(id)) { 5761 item = this._getItem(id, type); 5762 if (!filter || filter(item)) { 5763 mappedItems.push(callback(item, id)); 5764 } 5765 } 5766 } 5767 5768 // order items 5769 if (options && options.order) { 5770 this._sort(mappedItems, options.order); 5771 } 5772 5773 return mappedItems; 5774 }; 5775 5776 /** 5777 * Filter the fields of an item 5778 * @param {Object | null} item 5779 * @param {String[]} fields Field names 5780 * @return {Object | null} filteredItem or null if no item is provided 5781 * @private 5782 */ 5783 DataSet.prototype._filterFields = function (item, fields) { 5784 if (!item) { 5785 // item is null 5786 return item; 5787 } 5788 5789 var filteredItem = {}; 5790 5791 if (Array.isArray(fields)) { 5792 for (var field in item) { 5793 if (item.hasOwnProperty(field) && fields.indexOf(field) != -1) { 5794 filteredItem[field] = item[field]; 5795 } 5796 } 5797 } else { 5798 for (var field in item) { 5799 if (item.hasOwnProperty(field) && fields.hasOwnProperty(field)) { 5800 filteredItem[fields[field]] = item[field]; 5801 } 5802 } 5803 } 5804 5805 return filteredItem; 5806 }; 5807 5808 /** 5809 * Sort the provided array with items 5810 * @param {Object[]} items 5811 * @param {String | function} order A field name or custom sort function. 5812 * @private 5813 */ 5814 DataSet.prototype._sort = function (items, order) { 5815 if (util.isString(order)) { 5816 // order by provided field name 5817 var name = order; // field name 5818 items.sort(function (a, b) { 5819 var av = a[name]; 5820 var bv = b[name]; 5821 return av > bv ? 1 : av < bv ? -1 : 0; 5822 }); 5823 } else if (typeof order === 'function') { 5824 // order by sort function 5825 items.sort(order); 5826 } 5827 // TODO: extend order by an Object {field:String, direction:String} 5828 // where direction can be 'asc' or 'desc' 5829 else { 5830 throw new TypeError('Order must be a function or a string'); 5831 } 5832 }; 5833 5834 /** 5835 * Remove an object by pointer or by id 5836 * @param {String | Number | Object | Array} id Object or id, or an array with 5837 * objects or ids to be removed 5838 * @param {String} [senderId] Optional sender id 5839 * @return {Array} removedIds 5840 */ 5841 DataSet.prototype.remove = function (id, senderId) { 5842 var removedIds = [], 5843 i, 5844 len, 5845 removedId; 5846 5847 if (Array.isArray(id)) { 5848 for (i = 0, len = id.length; i < len; i++) { 5849 removedId = this._remove(id[i]); 5850 if (removedId != null) { 5851 removedIds.push(removedId); 5852 } 5853 } 5854 } else { 5855 removedId = this._remove(id); 5856 if (removedId != null) { 5857 removedIds.push(removedId); 5858 } 5859 } 5860 5861 if (removedIds.length) { 5862 this._trigger('remove', { items: removedIds }, senderId); 5863 } 5864 5865 return removedIds; 5866 }; 5867 5868 /** 5869 * Remove an item by its id 5870 * @param {Number | String | Object} id id or item 5871 * @returns {Number | String | null} id 5872 * @private 5873 */ 5874 DataSet.prototype._remove = function (id) { 5875 if (util.isNumber(id) || util.isString(id)) { 5876 if (this._data[id]) { 5877 delete this._data[id]; 5878 this.length--; 5879 return id; 5880 } 5881 } else if (id instanceof Object) { 5882 var itemId = id[this._fieldId]; 5883 if (itemId && this._data[itemId]) { 5884 delete this._data[itemId]; 5885 this.length--; 5886 return itemId; 5887 } 5888 } 5889 return null; 5890 }; 5891 5892 /** 5893 * Clear the data 5894 * @param {String} [senderId] Optional sender id 5895 * @return {Array} removedIds The ids of all removed items 5896 */ 5897 DataSet.prototype.clear = function (senderId) { 5898 var ids = Object.keys(this._data); 5899 5900 this._data = {}; 5901 this.length = 0; 5902 5903 this._trigger('remove', { items: ids }, senderId); 5904 5905 return ids; 5906 }; 5907 5908 /** 5909 * Find the item with maximum value of a specified field 5910 * @param {String} field 5911 * @return {Object | null} item Item containing max value, or null if no items 5912 */ 5913 DataSet.prototype.max = function (field) { 5914 var data = this._data, 5915 max = null, 5916 maxField = null; 5917 5918 for (var id in data) { 5919 if (data.hasOwnProperty(id)) { 5920 var item = data[id]; 5921 var itemField = item[field]; 5922 if (itemField != null && (!max || itemField > maxField)) { 5923 max = item; 5924 maxField = itemField; 5925 } 5926 } 5927 } 5928 5929 return max; 5930 }; 5931 5932 /** 5933 * Find the item with minimum value of a specified field 5934 * @param {String} field 5935 * @return {Object | null} item Item containing max value, or null if no items 5936 */ 5937 DataSet.prototype.min = function (field) { 5938 var data = this._data, 5939 min = null, 5940 minField = null; 5941 5942 for (var id in data) { 5943 if (data.hasOwnProperty(id)) { 5944 var item = data[id]; 5945 var itemField = item[field];
5946 if (itemField != null && (!min || itemField < minField)) { 5947 min = item; 5948 minField = itemField; 5949 } 5950 } 5951 } 5952 5953 return min; 5954 }; 5955 5956 /** 5957 * Find all distinct values of a specified field 5958 * @param {String} field 5959 * @return {Array} values Array containing all distinct values. If data items 5960 * do not contain the specified field are ignored. 5961 * The returned array is unordered. 5962 */ 5963 DataSet.prototype.distinct = function (field) { 5964 var data = this._data; 5965 var values = []; 5966 var fieldType = this._options.type && this._options.type[field] || null; 5967 var count = 0; 5968 var i; 5969 5970 for (var prop in data) { 5971 if (data.hasOwnProperty(prop)) { 5972 var item = data[prop]; 5973 var value = item[field]; 5974 var exists = false; 5975 for (i = 0; i < count; i++) { 5976 if (values[i] == value) { 5977 exists = true; 5978 break; 5979 } 5980 } 5981 if (!exists && value !== undefined) { 5982 values[count] = value; 5983 count++; 5984 } 5985 } 5986 } 5987 5988 if (fieldType) { 5989 for (i = 0; i < values.length; i++) { 5990 values[i] = util.convert(values[i], fieldType); 5991 } 5992 } 5993 5994 return values; 5995 }; 5996 5997 /** 5998 * Add a single item. Will fail when an item with the same id already exists. 5999 * @param {Object} item 6000 * @return {String} id 6001 * @private 6002 */ 6003 DataSet.prototype._addItem = function (item) { 6004 var id = item[this._fieldId]; 6005 6006 if (id != undefined) { 6007 // check whether this id is already taken 6008 if (this._data[id]) { 6009 // item already exists 6010 throw new Error('Cannot add item: item with id ' + id + ' already exists'); 6011 } 6012 } else { 6013 // generate an id 6014 id = util.randomUUID(); 6015 item[this._fieldId] = id; 6016 } 6017 6018 var d = {}; 6019 for (var field in item) { 6020 if (item.hasOwnProperty(field)) { 6021 var fieldType = this._type[field]; // type may be undefined 6022 d[field] = util.convert(item[field], fieldType); 6023 } 6024 } 6025 this._data[id] = d; 6026 this.length++; 6027 6028 return id; 6029 }; 6030 6031 /** 6032 * Get an item. Fields can be converted to a specific type 6033 * @param {String} id 6034 * @param {Object.<String, String>} [types] field types to convert 6035 * @return {Object | null} item 6036 * @private 6037 */ 6038 DataSet.prototype._getItem = function (id, types) { 6039 var field, value; 6040 6041 // get the item from the dataset 6042 var raw = this._data[id]; 6043 if (!raw) { 6044 return null; 6045 } 6046 6047 // convert the items field types 6048 var converted = {}; 6049 if (types) { 6050 for (field in raw) { 6051 if (raw.hasOwnProperty(field)) { 6052 value = raw[field]; 6053 converted[field] = util.convert(value, types[field]); 6054 } 6055 } 6056 } else { 6057 // no field types specified, no converting needed 6058 for (field in raw) { 6059 if (raw.hasOwnProperty(field)) { 6060 value = raw[field]; 6061 converted[field] = value; 6062 } 6063 } 6064 } 6065 return converted; 6066 }; 6067 6068 /** 6069 * Update a single item: merge with existing item. 6070 * Will fail when the item has no id, or when there does not exist an item 6071 * with the same id. 6072 * @param {Object} item 6073 * @return {String} id 6074 * @private 6075 */ 6076 DataSet.prototype._updateItem = function (item) { 6077 var id = item[this._fieldId]; 6078 if (id == undefined) { 6079 throw new Error('Cannot update item: item has no id (item: ' + JSON.stringify(item) + ')'); 6080 } 6081 var d = this._data[id]; 6082 if (!d) { 6083 // item doesn't exist 6084 throw new Error('Cannot update item: no item with id ' + id + ' found'); 6085 } 6086 6087 // merge with current item 6088 for (var field in item) { 6089 if (item.hasOwnProperty(field)) { 6090 var fieldType = this._type[field]; // type may be undefined 6091 d[field] = util.convert(item[field], fieldType); 6092 } 6093 } 6094 6095 return id; 6096 }; 6097 6098 module.exports = DataSet; 6099 6100/***/ }, 6101/* 9 */ 6102/***/ function(module, exports) { 6103 6104 /** 6105 * A queue 6106 * @param {Object} options 6107 * Available options: 6108 * - delay: number When provided, the queue will be flushed 6109 * automatically after an inactivity of this delay 6110 * in milliseconds. 6111 * Default value is null. 6112 * - max: number When the queue exceeds the given maximum number 6113 * of entries, the queue is flushed automatically. 6114 * Default value of max is Infinity. 6115 * @constructor 6116 */ 6117 'use strict'; 6118 6119 function Queue(options) { 6120 // options 6121 this.delay = null; 6122 this.max = Infinity; 6123 6124 // properties 6125 this._queue = []; 6126 this._timeout = null; 6127 this._extended = null; 6128 6129 this.setOptions(options); 6130 } 6131 6132 /** 6133 * Update the configuration of the queue 6134 * @param {Object} options 6135 * Available options: 6136 * - delay: number When provided, the queue will be flushed 6137 * automatically after an inactivity of this delay 6138 * in milliseconds. 6139 * Default value is null. 6140 * - max: number When the queue exceeds the given maximum number 6141 * of entries, the queue is flushed automatically. 6142 * Default value of max is Infinity. 6143 * @param options 6144 */ 6145 Queue.prototype.setOptions = function (options) { 6146 if (options && typeof options.delay !== 'undefined') { 6147 this.delay = options.delay; 6148 } 6149 if (options && typeof options.max !== 'undefined') { 6150 this.max = options.max; 6151 } 6152 6153 this._flushIfNeeded(); 6154 }; 6155 6156 /** 6157 * Extend an object with queuing functionality. 6158 * The object will be extended with a function flush, and the methods provided 6159 * in options.replace will be replaced with queued ones. 6160 * @param {Object} object 6161 * @param {Object} options 6162 * Available options: 6163 * - replace: Array.<string>
6164 * A list with method names of the methods 6165 * on the object to be replaced with queued ones. 6166 * - delay: number When provided, the queue will be flushed 6167 * automatically after an inactivity of this delay 6168 * in milliseconds. 6169 * Default value is null. 6170 * - max: number When the queue exceeds the given maximum number 6171 * of entries, the queue is flushed automatically. 6172 * Default value of max is Infinity. 6173 * @return {Queue} Returns the created queue 6174 */ 6175 Queue.extend = function (object, options) { 6176 var queue = new Queue(options); 6177 6178 if (object.flush !== undefined) { 6179 throw new Error('Target object already has a property flush'); 6180 } 6181 object.flush = function () { 6182 queue.flush(); 6183 }; 6184 6185 var methods = [{ 6186 name: 'flush', 6187 original: undefined 6188 }]; 6189 6190 if (options && options.replace) { 6191 for (var i = 0; i < options.replace.length; i++) { 6192 var name = options.replace[i]; 6193 methods.push({ 6194 name: name, 6195 original: object[name] 6196 }); 6197 queue.replace(object, name); 6198 } 6199 } 6200 6201 queue._extended = { 6202 object: object, 6203 methods: methods 6204 }; 6205 6206 return queue; 6207 }; 6208 6209 /** 6210 * Destroy the queue. The queue will first flush all queued actions, and in 6211 * case it has extended an object, will restore the original object. 6212 */ 6213 Queue.prototype.destroy = function () { 6214 this.flush(); 6215 6216 if (this._extended) { 6217 var object = this._extended.object; 6218 var methods = this._extended.methods; 6219 for (var i = 0; i < methods.length; i++) { 6220 var method = methods[i]; 6221 if (method.original) { 6222 object[method.name] = method.original; 6223 } else { 6224 delete object[method.name]; 6225 } 6226 } 6227 this._extended = null; 6228 } 6229 }; 6230 6231 /** 6232 * Replace a method on an object with a queued version 6233 * @param {Object} object Object having the method 6234 * @param {string} method The method name 6235 */ 6236 Queue.prototype.replace = function (object, method) { 6237 var me = this; 6238 var original = object[method]; 6239 if (!original) { 6240 throw new Error('Method ' + method + ' undefined'); 6241 } 6242 6243 object[method] = function () { 6244 // create an Array with the arguments 6245 var args = []; 6246 for (var i = 0; i < arguments.length; i++) { 6247 args[i] = arguments[i]; 6248 } 6249 6250 // add this call to the queue 6251 me.queue({ 6252 args: args, 6253 fn: original, 6254 context: this 6255 }); 6256 }; 6257 }; 6258 6259 /** 6260 * Queue a call 6261 * @param {function | {fn: function, args: Array} | {fn: function, args: Array, context: Object}} entry 6262 */ 6263 Queue.prototype.queue = function (entry) { 6264 if (typeof entry === 'function') { 6265 this._queue.push({ fn: entry }); 6266 } else { 6267 this._queue.push(entry); 6268 } 6269 6270 this._flushIfNeeded(); 6271 }; 6272 6273 /** 6274 * Check whether the queue needs to be flushed 6275 * @private 6276 */ 6277 Queue.prototype._flushIfNeeded = function () { 6278 // flush when the maximum is exceeded. 6279 if (this._queue.length > this.max) { 6280 this.flush(); 6281 } 6282 6283 // flush after a period of inactivity when a delay is configured 6284 clearTimeout(this._timeout); 6285 if (this.queue.length > 0 && typeof this.delay === 'number') { 6286 var me = this; 6287 this._timeout = setTimeout(function () { 6288 me.flush(); 6289 }, this.delay); 6290 } 6291 }; 6292 6293 /** 6294 * Flush all queued calls 6295 */ 6296 Queue.prototype.flush = function () { 6297 while (this._queue.length > 0) { 6298 var entry = this._queue.shift(); 6299 entry.fn.apply(entry.context || entry.fn, entry.args || []); 6300 } 6301 }; 6302 6303 module.exports = Queue; 6304 6305/***/ }, 6306/* 10 */ 6307/***/ function(module, exports, __webpack_require__) { 6308 6309 'use strict'; 6310 6311 var util = __webpack_require__(1); 6312 var DataSet = __webpack_require__(8); 6313 6314 /** 6315 * DataView 6316 * 6317 * a dataview offers a filtered view on a dataset or an other dataview. 6318 * 6319 * @param {DataSet | DataView} data 6320 * @param {Object} [options] Available options: see method get 6321 * 6322 * @constructor DataView 6323 */ 6324 function DataView(data, options) { 6325 this._data = null; 6326 this._ids = {}; // ids of the items currently in memory (just contains a boolean true) 6327 this.length = 0; // number of items in the DataView 6328 this._options = options || {}; 6329 this._fieldId = 'id'; // name of the field containing id 6330 this._subscribers = {}; // event subscribers 6331 6332 var me = this; 6333 this.listener = function () { 6334 me._onEvent.apply(me, arguments); 6335 }; 6336 6337 this.setData(data); 6338 } 6339 6340 // TODO: implement a function .config() to dynamically update things like configured filter 6341 // and trigger changes accordingly 6342 6343 /** 6344 * Set a data source for the view 6345 * @param {DataSet | DataView} data 6346 */ 6347 DataView.prototype.setData = function (data) { 6348 var ids, i, len; 6349 6350 if (this._data) { 6351 // unsubscribe from current dataset 6352 if (this._data.off) { 6353 this._data.off('*', this.listener); 6354 } 6355 6356 // trigger a remove of all items in memory 6357 ids = []; 6358 for (var id in this._ids) { 6359 if (this._ids.hasOwnProperty(id)) { 6360 ids.push(id); 6361 } 6362 } 6363 this._ids = {}; 6364 this.length = 0; 6365 this._trigger('remove', { items: ids }); 6366 } 6367 6368 this._data = data; 6369 6370 if (this._data) { 6371 // update fieldId 6372 this._fieldId = this._options.fieldId || this._data && this._data.options && this._data.options.fieldId || 'id'; 6373 6374 // trigger an add of all added items 6375 ids = this._data.getIds({ filter: this._options && this._options.filter }); 6376 for (i = 0, len = ids.length; i < len; i++) { 6377 id = ids[i]; 6378 this._ids[id] = true; 6379 } 6380 this.length = ids.length; 6381 this._trigger('add', { items: ids }); 6382 6383 // subscribe to new dataset 6384 if (this._data.on) { 6385 this._data.on('*', this.listener); 6386 } 6387 } 6388 }; 6389 6390 /** 6391 * Refresh the DataView. Useful when the DataView has a filter function 6392 * containing a variable parameter. 6393 */ 6394 DataView.prototype.refresh = function () { 6395 var id; 6396 var ids = this._data.getIds({ filter: this._options && this._options.filter }); 6397 var newIds = {}; 6398 var added = []; 6399 var removed = []; 6400 6401 // check for additions 6402 for (var i = 0; i < ids.length; i++) { 6403 id = ids[i]; 6404 newIds[id] = true; 6405 if (!this._ids[id]) { 6406 added.push(id); 6407 this._ids[id] = true; 6408 this.length++; 6409 } 6410 } 6411 6412 // check for removals 6413 for (id in this._ids) { 6414 if (this._ids.hasOwnProperty(id)) { 6415 if (!newIds[id]) { 6416 removed.push(id); 6417 delete this._ids[id]; 6418 this.length--; 6419 } 6420 } 6421 } 6422 6423 // trigger events 6424 if (added.length) { 6425 this._trigger('add', { items: added }); 6426 } 6427 if (removed.length) { 6428 this._trigger('remove', { items: removed }); 6429 } 6430 }; 6431 6432 /** 6433 * Get data from the data view 6434 * 6435 * Usage: 6436 * 6437 * get() 6438 * get(options: Object) 6439 * get(options: Object, data: Array | DataTable) 6440 * 6441 * get(id: Number) 6442 * get(id: Number, options: Object) 6443 * get(id: Number, options: Object, data: Array | DataTable) 6444 * 6445 * get(ids: Number[]) 6446 * get(ids: Number[], options: Object) 6447 * get(ids: Number[], options: Object, data: Array | DataTable) 6448 * 6449 * Where: 6450 * 6451 * {Number | String} id The id of an item 6452 * {Number[] | String{}} ids An array with ids of items 6453 * {Object} options An Object with options. Available options: 6454 * {String} [type] Type of data to be returned. Can 6455 * be 'DataTable' or 'Array' (default) 6456 * {Object.<String, String>} [convert] 6457 * {String[]} [fields] field names to be returned 6458 * {function} [filter] filter items 6459 * {String | function} [order] Order the items by 6460 * a field name or custom sort function. 6461 * {Array | DataTable} [data] If provided, items will be appended to this 6462 * array or table. Required in case of Google 6463 * DataTable. 6464 * @param args 6465 */ 6466 DataView.prototype.get = function (args) { 6467 var me = this; 6468 6469 // parse the arguments 6470 var ids, options, data; 6471 var firstType = util.getType(arguments[0]); 6472 if (firstType == 'String' || firstType == 'Number' || firstType == 'Array') { 6473 // get(id(s) [, options] [, data]) 6474 ids = arguments[0]; // can be a single id or an array with ids 6475 options = arguments[1]; 6476 data = arguments[2]; 6477 } else { 6478 // get([, options] [, data]) 6479 options = arguments[0]; 6480 data = arguments[1]; 6481 } 6482 6483 // extend the options with the default options and provided options 6484 var viewOptions = util.extend({}, this._options, options); 6485 6486 // create a combined filter method when needed 6487 if (this._options.filter && options && options.filter) { 6488 viewOptions.filter = function (item) { 6489 return me._options.filter(item) && options.filter(item); 6490 }; 6491 } 6492 6493 // build up the call to the linked data set 6494 var getArguments = []; 6495 if (ids != undefined) { 6496 getArguments.push(ids); 6497 } 6498 getArguments.push(viewOptions); 6499 getArguments.push(data); 6500 6501 return this._data && this._data.get.apply(this._data, getArguments); 6502 }; 6503 6504 /** 6505 * Get ids of all items or from a filtered set of items. 6506 * @param {Object} [options] An Object with options. Available options: 6507 * {function} [filter] filter items 6508 * {String | function} [order] Order the items by 6509 * a field name or custom sort function. 6510 * @return {Array} ids 6511 */ 6512 DataView.prototype.getIds = function (options) { 6513 var ids; 6514 6515 if (this._data) { 6516 var defaultFilter = this._options.filter; 6517 var filter; 6518 6519 if (options && options.filter) { 6520 if (defaultFilter) { 6521 filter = function (item) { 6522 return defaultFilter(item) && options.filter(item); 6523 }; 6524 } else { 6525 filter = options.filter; 6526 } 6527 } else { 6528 filter = defaultFilter; 6529 } 6530 6531 ids = this._data.getIds({ 6532 filter: filter, 6533 order: options && options.order 6534 }); 6535 } else { 6536 ids = []; 6537 } 6538 6539 return ids; 6540 }; 6541 6542 /** 6543 * Get the DataSet to which this DataView is connected. In case there is a chain 6544 * of multiple DataViews, the root DataSet of this chain is returned. 6545 * @return {DataSet} dataSet 6546 */ 6547 DataView.prototype.getDataSet = function () { 6548 var dataSet = this; 6549 while (dataSet instanceof DataView) { 6550 dataSet = dataSet._data; 6551 } 6552 return dataSet || null; 6553 }; 6554 6555 /** 6556 * Event listener. Will propagate all events from the connected data set to 6557 * the subscribers of the DataView, but will filter the items and only trigger 6558 * when there are changes in the filtered data set. 6559 * @param {String} event 6560 * @param {Object | null} params 6561 * @param {String} senderId 6562 * @private 6563 */ 6564 DataView.prototype._onEvent = function (event, params, senderId) { 6565 var i, len, id, item; 6566 var ids = params && params.items; 6567 var data = this._data; 6568 var updatedData = []; 6569 var added = []; 6570 var updated = []; 6571 var removed = []; 6572 6573 if (ids && data) { 6574 switch (event) { 6575 case 'add': 6576 // filter the ids of the added items 6577 for (i = 0, len = ids.length; i < len; i++) { 6578 id = ids[i]; 6579 item = this.get(id); 6580 if (item) { 6581 this._ids[id] = true; 6582 added.push(id); 6583 } 6584 } 6585 6586 break; 6587 6588 case 'update': 6589 // determine the event from the views viewpoint: an updated 6590 // item can be added, updated, or removed from this view. 6591 for (i = 0, len = ids.length; i < len; i++) { 6592 id = ids[i]; 6593 item = this.get(id); 6594 6595 if (item) { 6596 if (this._ids[id]) { 6597 updated.push(id); 6598 updatedData.push(params.data[i]); 6599 } else { 6600 this._ids[id] = true; 6601 added.push(id); 6602 } 6603 } else { 6604 if (this._ids[id]) { 6605 delete this._ids[id]; 6606 removed.push(id); 6607 } else {} 6608 } 6609 } 6610 6611 break; 6612 6613 case 'remove': 6614 // filter the ids of the removed items 6615 for (i = 0, len = ids.length; i < len; i++) { 6616 id = ids[i]; 6617 if (this._ids[id]) { 6618 delete this._ids[id]; 6619 removed.push(id); 6620 } 6621 } 6622 6623 break; 6624 } 6625 6626 this.length += added.length - removed.length; 6627 6628 if (added.length) { 6629 this._trigger('add', { items: added }, senderId); 6630 } 6631 if (updated.length) { 6632 this._trigger('update', { items: updated, data: updatedData }, senderId); 6633 } 6634 if (removed.length) { 6635 this._trigger('remove', { items: removed }, senderId); 6636 } 6637 } 6638 }; 6639 6640 // copy subscription functionality from DataSet 6641 DataView.prototype.on = DataSet.prototype.on; 6642 DataView.prototype.off = DataSet.prototype.off; 6643 DataView.prototype._trigger = DataSet.prototype._trigger; 6644 6645 // TODO: make these functions deprecated (replaced with `on` and `off` since version 0.5) 6646 DataView.prototype.subscribe = DataView.prototype.on; 6647 DataView.prototype.unsubscribe = DataView.prototype.off; 6648 6649 module.exports = DataView; 6650 6651 // nothing interesting for me :-( 6652 6653/***/ }, 6654/* 11 */ 6655/***/ function(module, exports, __webpack_require__) { 6656 6657 'use strict'; 6658 6659 var Emitter = __webpack_require__(12); 6660 var DataSet = __webpack_require__(8); 6661 var DataView = __webpack_require__(10); 6662 var util = __webpack_require__(1); 6663 var Point3d = __webpack_require__(13); 6664 var Point2d = __webpack_require__(14); 6665 var Camera = __webpack_require__(15); 6666 var Filter = __webpack_require__(16); 6667 var Slider = __webpack_require__(17); 6668 var StepNumber = __webpack_require__(18); 6669 6670 /** 6671 * @constructor Graph3d 6672 * Graph3d displays data in 3d. 6673 * 6674 * Graph3d is developed in javascript as a Google Visualization Chart. 6675 * 6676 * @param {Element} container The DOM element in which the Graph3d will 6677 * be created. Normally a div element. 6678 * @param {DataSet | DataView | Array} [data] 6679 * @param {Object} [options] 6680 */ 6681 function Graph3d(container, data, options) { 6682 if (!(this instanceof Graph3d)) { 6683 throw new SyntaxError('Constructor must be called with the new operator'); 6684 } 6685 6686 // create variables and set default values 6687 this.containerElement = container; 6688 this.width = '400px'; 6689 this.height = '400px'; 6690 this.margin = 10; // px 6691 this.defaultXCenter = '55%'; 6692 this.defaultYCenter = '50%'; 6693 6694 this.xLabel = 'x'; 6695 this.yLabel = 'y'; 6696 this.zLabel = 'z'; 6697 6698 var passValueFn = function passValueFn(v) { 6699 return v; 6700 }; 6701 this.xValueLabel = passValueFn; 6702 this.yValueLabel = passValueFn; 6703 this.zValueLabel = passValueFn; 6704 6705 this.filterLabel = 'time'; 6706 this.legendLabel = 'value'; 6707 6708 this.style = Graph3d.STYLE.DOT; 6709 this.showPerspective = true; 6710 this.showGrid = true; 6711 this.keepAspectRatio = true; 6712 this.showShadow = false;
6713 this.showGrayBottom = false; // TODO: this does not work correctly 6714 this.showTooltip = false; 6715 this.verticalRatio = 0.5; // 0.1 to 1.0, where 1.0 results in a 'cube' 6716 6717 this.animationInterval = 1000; // milliseconds 6718 this.animationPreload = false; 6719 6720 this.camera = new Camera(); 6721 this.camera.setArmRotation(1.0, 0.5); 6722 this.camera.setArmLength(1.7); 6723 this.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window? 6724 6725 this.dataTable = null; // The original data table 6726 this.dataPoints = null; // The table with point objects 6727 6728 // the column indexes 6729 this.colX = undefined; 6730 this.colY = undefined; 6731 this.colZ = undefined; 6732 this.colValue = undefined; 6733 this.colFilter = undefined; 6734 6735 this.xMin = 0; 6736 this.xStep = undefined; // auto by default 6737 this.xMax = 1; 6738 this.yMin = 0; 6739 this.yStep = undefined; // auto by default 6740 this.yMax = 1; 6741 this.zMin = 0; 6742 this.zStep = undefined; // auto by default 6743 this.zMax = 1; 6744 this.valueMin = 0; 6745 this.valueMax = 1; 6746 this.xBarWidth = 1; 6747 this.yBarWidth = 1; 6748 // TODO: customize axis range 6749 6750 // colors 6751 this.axisColor = '#4D4D4D'; 6752 this.gridColor = '#D3D3D3'; 6753 this.dataColor = { 6754 fill: '#7DC1FF', 6755 stroke: '#3267D2', 6756 strokeWidth: 1 // px 6757 }; 6758 6759 // create a frame and canvas 6760 this.create(); 6761 6762 // apply options (also when undefined) 6763 this.setOptions(options); 6764 6765 // apply data 6766 if (data) { 6767 this.setData(data); 6768 } 6769 } 6770 6771 // Extend Graph3d with an Emitter mixin 6772 Emitter(Graph3d.prototype); 6773 6774 /** 6775 * Calculate the scaling values, dependent on the range in x, y, and z direction 6776 */ 6777 Graph3d.prototype._setScale = function () { 6778 this.scale = new Point3d(1 / (this.xMax - this.xMin), 1 / (this.yMax - this.yMin), 1 / (this.zMax - this.zMin)); 6779 6780 // keep aspect ration between x and y scale if desired 6781 if (this.keepAspectRatio) { 6782 if (this.scale.x < this.scale.y) { 6783 //noinspection JSSuspiciousNameCombination 6784 this.scale.y = this.scale.x; 6785 } else { 6786 //noinspection JSSuspiciousNameCombination 6787 this.scale.x = this.scale.y; 6788 } 6789 } 6790 6791 // scale the vertical axis 6792 this.scale.z *= this.verticalRatio; 6793 // TODO: can this be automated? verticalRatio? 6794 6795 // determine scale for (optional) value 6796 this.scale.value = 1 / (this.valueMax - this.valueMin); 6797 6798 // position the camera arm 6799 var xCenter = (this.xMax + this.xMin) / 2 * this.scale.x; 6800 var yCenter = (this.yMax + this.yMin) / 2 * this.scale.y; 6801 var zCenter = (this.zMax + this.zMin) / 2 * this.scale.z; 6802 this.camera.setArmLocation(xCenter, yCenter, zCenter); 6803 }; 6804 6805 /** 6806 * Convert a 3D location to a 2D location on screen 6807 * http://en.wikipedia.org/wiki/3D_projection 6808 * @param {Point3d} point3d A 3D point with parameters x, y, z 6809 * @return {Point2d} point2d A 2D point with parameters x, y 6810 */ 6811 Graph3d.prototype._convert3Dto2D = function (point3d) { 6812 var translation = this._convertPointToTranslation(point3d); 6813 return this._convertTranslationToScreen(translation); 6814 }; 6815 6816 /** 6817 * Convert a 3D location its translation seen from the camera 6818 * http://en.wikipedia.org/wiki/3D_projection 6819 * @param {Point3d} point3d A 3D point with parameters x, y, z 6820 * @return {Point3d} translation A 3D point with parameters x, y, z This is 6821 * the translation of the point, seen from the 6822 * camera 6823 */ 6824 Graph3d.prototype._convertPointToTranslation = function (point3d) { 6825 var ax = point3d.x * this.scale.x, 6826 ay = point3d.y * this.scale.y, 6827 az = point3d.z * this.scale.z, 6828 cx = this.camera.getCameraLocation().x, 6829 cy = this.camera.getCameraLocation().y, 6830 cz = this.camera.getCameraLocation().z, 6831 6832 // calculate angles 6833 sinTx = Math.sin(this.camera.getCameraRotation().x), 6834 cosTx = Math.cos(this.camera.getCameraRotation().x), 6835 sinTy = Math.sin(this.camera.getCameraRotation().y), 6836 cosTy = Math.cos(this.camera.getCameraRotation().y), 6837 sinTz = Math.sin(this.camera.getCameraRotation().z),
6838 cosTz = Math.cos(this.camera.getCameraRotation().z), 6839 6840 // calculate translation 6841 dx = cosTy * (sinTz * (ay - cy) + cosTz * (ax - cx)) - sinTy * (az - cz), 6842 dy = sinTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) + cosTx * (cosTz * (ay - cy) - sinTz * (ax - cx)), 6843 dz = cosTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) - sinTx * (cosTz * (ay - cy) - sinTz * (ax - cx)); 6844 6845 return new Point3d(dx, dy, dz); 6846 }; 6847 6848 /** 6849 * Convert a translation point to a point on the screen 6850 * @param {Point3d} translation A 3D point with parameters x, y, z This is 6851 * the translation of the point, seen from the 6852 * camera 6853 * @return {Point2d} point2d A 2D point with parameters x, y 6854 */ 6855 Graph3d.prototype._convertTranslationToScreen = function (translation) { 6856 var ex = this.eye.x, 6857 ey = this.eye.y, 6858 ez = this.eye.z, 6859 dx = translation.x, 6860 dy = translation.y, 6861 dz = translation.z; 6862 6863 // calculate position on screen from translation 6864 var bx; 6865 var by; 6866 if (this.showPerspective) { 6867 bx = (dx - ex) * (ez / dz); 6868 by = (dy - ey) * (ez / dz); 6869 } else { 6870 bx = dx * -(ez / this.camera.getArmLength()); 6871 by = dy * -(ez / this.camera.getArmLength()); 6872 } 6873 6874 // shift and scale the point to the center of the screen 6875 // use the width of the graph to scale both horizontally and vertically. 6876 return new Point2d(this.xcenter + bx * this.frame.canvas.clientWidth, this.ycenter - by * this.frame.canvas.clientWidth); 6877 }; 6878 6879 /** 6880 * Set the background styling for the graph 6881 * @param {string | {fill: string, stroke: string, strokeWidth: string}} backgroundColor 6882 */ 6883 Graph3d.prototype._setBackgroundColor = function (backgroundColor) { 6884 var fill = 'white'; 6885 var stroke = 'gray'; 6886 var strokeWidth = 1; 6887 6888 if (typeof backgroundColor === 'string') { 6889 fill = backgroundColor; 6890 stroke = 'none'; 6891 strokeWidth = 0; 6892 } else if (typeof backgroundColor === 'object') { 6893 if (backgroundColor.fill !== undefined) fill = backgroundColor.fill; 6894 if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke; 6895 if (backgroundColor.strokeWidth !== undefined) strokeWidth = backgroundColor.strokeWidth; 6896 } else if (backgroundColor === undefined) {} else { 6897 throw 'Unsupported type of backgroundColor'; 6898 } 6899 6900 this.frame.style.backgroundColor = fill; 6901 this.frame.style.borderColor = stroke; 6902 this.frame.style.borderWidth = strokeWidth + 'px'; 6903 this.frame.style.borderStyle = 'solid'; 6904 }; 6905 6906 /// enumerate the available styles 6907 Graph3d.STYLE = { 6908 BAR: 0, 6909 BARCOLOR: 1, 6910 BARSIZE: 2, 6911 DOT: 3, 6912 DOTLINE: 4, 6913 DOTCOLOR: 5, 6914 DOTSIZE: 6, 6915 GRID: 7, 6916 LINE: 8, 6917 SURFACE: 9 6918 }; 6919 6920 /** 6921 * Retrieve the style index from given styleName 6922 * @param {string} styleName Style name such as 'dot', 'grid', 'dot-line' 6923 * @return {Number} styleNumber Enumeration value representing the style, or -1 6924 * when not found 6925 */ 6926 Graph3d.prototype._getStyleNumber = function (styleName) { 6927 switch (styleName) { 6928 case 'dot': 6929 return Graph3d.STYLE.DOT; 6930 case 'dot-line': 6931 return Graph3d.STYLE.DOTLINE; 6932 case 'dot-color': 6933 return Graph3d.STYLE.DOTCOLOR; 6934 case 'dot-size': 6935 return Graph3d.STYLE.DOTSIZE; 6936 case 'line': 6937 return Graph3d.STYLE.LINE; 6938 case 'grid': 6939 return Graph3d.STYLE.GRID; 6940 case 'surface': 6941 return Graph3d.STYLE.SURFACE; 6942 case 'bar': 6943 return Graph3d.STYLE.BAR; 6944 case 'bar-color': 6945 return Graph3d.STYLE.BARCOLOR; 6946 case 'bar-size': 6947 return Graph3d.STYLE.BARSIZE; 6948 } 6949 6950 return -1; 6951 }; 6952 6953 /** 6954 * Determine the indexes of the data columns, based on the given style and data 6955 * @param {DataSet} data 6956 * @param {Number} style 6957 */ 6958 Graph3d.prototype._determineColumnIndexes = function (data, style) { 6959 if (this.style === Graph3d.STYLE.DOT || this.style === Graph3d.STYLE.DOTLINE || this.style === Graph3d.STYLE.LINE || this.style === Graph3d.STYLE.GRID || this.style === Graph3d.STYLE.SURFACE || this.style === Graph3d.STYLE.BAR) { 6960 // 3 columns expected, and optionally a 4th with filter values 6961 this.colX = 0; 6962 this.colY = 1; 6963 this.colZ = 2; 6964 this.colValue = undefined; 6965 6966 if (data.getNumberOfColumns() > 3) { 6967 this.colFilter = 3; 6968 } 6969 } else if (this.style === Graph3d.STYLE.DOTCOLOR || this.style === Graph3d.STYLE.DOTSIZE || this.style === Graph3d.STYLE.BARCOLOR || this.style === Graph3d.STYLE.BARSIZE) { 6970 // 4 columns expected, and optionally a 5th with filter values 6971 this.colX = 0; 6972 this.colY = 1; 6973 this.colZ = 2; 6974 this.colValue = 3; 6975 6976 if (data.getNumberOfColumns() > 4) { 6977 this.colFilter = 4;
6978 } 6979 } else { 6980 throw 'Unknown style "' + this.style + '"'; 6981 } 6982 }; 6983 6984 Graph3d.prototype.getNumberOfRows = function (data) { 6985 return data.length; 6986 }; 6987 6988 Graph3d.prototype.getNumberOfColumns = function (data) { 6989 var counter = 0; 6990 for (var column in data[0]) { 6991 if (data[0].hasOwnProperty(column)) { 6992 counter++; 6993 } 6994 } 6995 return counter; 6996 }; 6997 6998 Graph3d.prototype.getDistinctValues = function (data, column) { 6999 var distinctValues = []; 7000 for (var i = 0; i < data.length; i++) { 7001 if (distinctValues.indexOf(data[i][column]) == -1) { 7002 distinctValues.push(data[i][column]); 7003 } 7004 } 7005 return distinctValues; 7006 }; 7007 7008 Graph3d.prototype.getColumnRange = function (data, column) { 7009 var minMax = { min: data[0][column], max: data[0][column] }; 7010 for (var i = 0; i < data.length; i++) { 7011 if (minMax.min > data[i][column]) { 7012 minMax.min = data[i][column]; 7013 } 7014 if (minMax.max < data[i][column]) { 7015 minMax.max = data[i][column]; 7016 } 7017 } 7018 return minMax; 7019 }; 7020 7021 /** 7022 * Initialize the data from the data table. Calculate minimum and maximum values 7023 * and column index values 7024 * @param {Array | DataSet | DataView} rawData The data containing the items for the Graph. 7025 * @param {Number} style Style Number 7026 */ 7027 Graph3d.prototype._dataInitialize = function (rawData, style) { 7028 var me = this; 7029 7030 // unsubscribe from the dataTable 7031 if (this.dataSet) { 7032 this.dataSet.off('*', this._onChange); 7033 } 7034 7035 if (rawData === undefined) return; 7036 7037 if (Array.isArray(rawData)) { 7038 rawData = new DataSet(rawData); 7039 } 7040 7041 var data; 7042 if (rawData instanceof DataSet || rawData instanceof DataView) { 7043 data = rawData.get(); 7044 } else { 7045 throw new Error('Array, DataSet, or DataView expected'); 7046 } 7047 7048 if (data.length == 0) return; 7049 7050 this.dataSet = rawData; 7051 this.dataTable = data; 7052 7053 // subscribe to changes in the dataset 7054 this._onChange = function () { 7055 me.setData(me.dataSet); 7056 }; 7057 this.dataSet.on('*', this._onChange); 7058 7059 // _determineColumnIndexes 7060 // getNumberOfRows (points) 7061 // getNumberOfColumns (x,y,z,v,t,t1,t2...) 7062 // getDistinctValues (unique values?) 7063 // getColumnRange 7064 7065 // determine the location of x,y,z,value,filter columns 7066 this.colX = 'x'; 7067 this.colY = 'y'; 7068 this.colZ = 'z'; 7069 this.colValue = 'style'; 7070 this.colFilter = 'filter'; 7071 7072 // check if a filter column is provided 7073 if (data[0].hasOwnProperty('filter')) { 7074 if (this.dataFilter === undefined) { 7075 this.dataFilter = new Filter(rawData, this.colFilter, this); 7076 this.dataFilter.setOnLoadCallback(function () { 7077 me.redraw(); 7078 }); 7079 } 7080 } 7081 7082 var withBars = this.style == Graph3d.STYLE.BAR || this.style == Graph3d.STYLE.BARCOLOR || this.style == Graph3d.STYLE.BARSIZE; 7083 7084 // determine barWidth from data 7085 if (withBars) { 7086 if (this.defaultXBarWidth !== undefined) { 7087 this.xBarWidth = this.defaultXBarWidth; 7088 } else { 7089 var dataX = this.getDistinctValues(data, this.colX); 7090 this.xBarWidth = dataX[1] - dataX[0] || 1; 7091 } 7092 7093 if (this.defaultYBarWidth !== undefined) { 7094 this.yBarWidth = this.defaultYBarWidth; 7095 } else { 7096 var dataY = this.getDistinctValues(data, this.colY); 7097 this.yBarWidth = dataY[1] - dataY[0] || 1; 7098 } 7099 } 7100 7101 // calculate minimums and maximums 7102 var xRange = this.getColumnRange(data, this.colX); 7103 if (withBars) { 7104 xRange.min -= this.xBarWidth / 2; 7105 xRange.max += this.xBarWidth / 2; 7106 } 7107 this.xMin = this.defaultXMin !== undefined ? this.defaultXMin : xRange.min; 7108 this.xMax = this.defaultXMax !== undefined ? this.defaultXMax : xRange.max; 7109 if (this.xMax <= this.xMin) this.xMax = this.xMin + 1; 7110 this.xStep = this.defaultXStep !== undefined ? this.defaultXStep : (this.xMax - this.xMin) / 5; 7111 7112 var yRange = this.getColumnRange(data, this.colY); 7113 if (withBars) { 7114 yRange.min -= this.yBarWidth / 2; 7115 yRange.max += this.yBarWidth / 2; 7116 } 7117 this.yMin = this.defaultYMin !== undefined ? this.defaultYMin : yRange.min; 7118 this.yMax = this.defaultYMax !== undefined ? this.defaultYMax : yRange.max; 7119 if (this.yMax <= this.yMin) this.yMax = this.yMin + 1; 7120 this.yStep = this.defaultYStep !== undefined ? this.defaultYStep
7120: (this.yMax - this.yMin) / 5; 7121 7122 var zRange = this.getColumnRange(data, this.colZ); 7123 this.zMin = this.defaultZMin !== undefined ? this.defaultZMin : zRange.min; 7124 this.zMax = this.defaultZMax !== undefined ? this.defaultZMax : zRange.max; 7125 if (this.zMax <= this.zMin) this.zMax = this.zMin + 1; 7126 this.zStep = this.defaultZStep !== undefined ? this.defaultZStep : (this.zMax - this.zMin) / 5; 7127 7128 if (this.colValue !== undefined) { 7129 var valueRange = this.getColumnRange(data, this.colValue); 7130 this.valueMin = this.defaultValueMin !== undefined ? this.defaultValueMin : valueRange.min; 7131 this.valueMax = this.defaultValueMax !== undefined ? this.defaultValueMax : valueRange.max; 7132 if (this.valueMax <= this.valueMin) this.valueMax = this.valueMin + 1; 7133 } 7134 7135 // set the scale dependent on the ranges. 7136 this._setScale(); 7137 }; 7138 7139 /** 7140 * Filter the data based on the current filter 7141 * @param {Array} data 7142 * @return {Array} dataPoints Array with point objects which can be drawn on screen 7143 */ 7144 Graph3d.prototype._getDataPoints = function (data) { 7145 // TODO: store the created matrix dataPoints in the filters instead of reloading each time 7146 var x, y, i, z, obj, point; 7147 7148 var dataPoints = []; 7149 7150 if (this.style === Graph3d.STYLE.GRID || this.style === Graph3d.STYLE.SURFACE) { 7151 // copy all values from the google data table to a matrix 7152 // the provided values are supposed to form a grid of (x,y) positions 7153 7154 // create two lists with all present x and y values 7155 var dataX = []; 7156 var dataY = []; 7157 for (i = 0; i < this.getNumberOfRows(data); i++) { 7158 x = data[i][this.colX] || 0; 7159 y = data[i][this.colY] || 0; 7160 7161 if (dataX.indexOf(x) === -1) { 7162 dataX.push(x); 7163 } 7164 if (dataY.indexOf(y) === -1) { 7165 dataY.push(y); 7166 } 7167 } 7168 7169 var sortNumber = function sortNumber(a, b) { 7170 return a - b; 7171 }; 7172 dataX.sort(sortNumber); 7173 dataY.sort(sortNumber); 7174 7175 // create a grid, a 2d matrix, with all values. 7176 var dataMatrix = []; // temporary data matrix 7177 for (i = 0; i < data.length; i++) { 7178 x = data[i][this.colX] || 0; 7179 y = data[i][this.colY] || 0; 7180 z = data[i][this.colZ] || 0; 7181 7182 var xIndex = dataX.indexOf(x); // TODO: implement Array().indexOf() for Internet Explorer 7183 var yIndex = dataY.indexOf(y); 7184 7185 if (dataMatrix[xIndex] === undefined) { 7186 dataMatrix[xIndex] = []; 7187 } 7188 7189 var point3d = new Point3d(); 7190 point3d.x = x; 7191 point3d.y = y; 7192 point3d.z = z; 7193 7194 obj = {}; 7195 obj.point = point3d; 7196 obj.trans = undefined; 7197 obj.screen = undefined; 7198 obj.bottom = new Point3d(x, y, this.zMin); 7199 7200 dataMatrix[xIndex][yIndex] = obj; 7201 7202 dataPoints.push(obj); 7203 } 7204 7205 // fill in the pointers to the neighbors. 7206 for (x = 0; x < dataMatrix.length; x++) { 7207 for (y = 0; y < dataMatrix[x].length; y++) { 7208 if (dataMatrix[x][y]) { 7209 dataMatrix[x][y].pointRight = x < dataMatrix.length - 1 ? dataMatrix[x + 1][y] : undefined; 7210 dataMatrix[x][y].pointTop = y < dataMatrix[x].length - 1 ? dataMatrix[x][y + 1] : undefined; 7211 dataMatrix[x][y].pointCross = x < dataMatrix.length - 1 && y < dataMatrix[x].length - 1 ? dataMatrix[x + 1][y + 1] : undefined; 7212 } 7213 } 7214 } 7215 } else { 7216 // 'dot', 'dot-line', etc. 7217 // copy all values from the google data table to a list with Point3d objects 7218 for (i = 0; i < data.length; i++) { 7219 point = new Point3d(); 7220 point.x = data[i][this.colX] || 0; 7221 point.y = data[i][this.colY] || 0; 7222 point.z = data[i][this.colZ] || 0; 7223 7224 if (this.colValue !== undefined) { 7225 point.value = data[i][this.colValue] || 0; 7226 } 7227 7228 obj = {}; 7229 obj.point = point; 7230 obj.bottom = new Point3d(point.x, point.y, this.zMin); 7231 obj.trans = undefined; 7232 obj.screen = undefined; 7233 7234 dataPoints.push(obj); 7235 } 7236 } 7237 7238 return dataPoints; 7239 }; 7240 7241 /** 7242 * Create the main frame for the Graph3d. 7243 * This function is executed once when a Graph3d object is created. The frame 7244 * contains a canvas, and this canvas contains all objects like the axis and 7245 * nodes. 7246 */ 7247 Graph3d.prototype.create = function () { 7248 // remove all elements from the container element. 7249 while (this.containerElement.hasChildNodes()) { 7250 this.containerElement.removeChild(this.containerElement.firstChild); 7251 } 7252 7253 this.frame = document.createElement('div'); 7254 this.frame.style.position = 'relative'; 7255 this.frame.style.overflow = 'hidden'; 7256 7257 // create the graph canvas (HTML canvas element) 7258 this.frame.canvas = document.createElement('canvas'); 7259 this.frame.canvas.style.position = 'relative'; 7260 this.frame.appendChild(this.frame.canvas); 7261 //if (!this.frame.canvas.getContext) { 7262 { 7263 var noCanvas = document.createElement('DIV'); 7264 noCanvas.style.color = 'red'; 7265 noCanvas.style.fontWeight = 'bold'; 7266 noCanvas.style.padding = '10px'; 7267 noCanvas.innerHTML = 'Error: your browser does not support HTML canvas'; 7268 this.frame.canvas.appendChild(noCanvas); 7269 } 7270 7271 this.frame.filter = document.createElement('div'); 7272 this.frame.filter.style.position = 'absolute'; 7273 this.frame.filter.style.bottom = '0px'; 7274 this.frame.filter.style.left = '0px'; 7275 this.frame.filter.style.width = '100%'; 7276 this.frame.appendChild(this.frame.filter); 7277 7278 // add event listeners to handle moving and zooming the contents 7279 var me = this; 7280 var onmousedown = function onmousedown(event) { 7281 me._onMouseDown(event); 7282 }; 7283 var ontouchstart = function ontouchstart(event) { 7284 me._onTouchStart(event); 7285 }; 7286 var onmousewheel = function onmousewheel(event) { 7287 me._onWheel(event); 7288 }; 7289 var ontooltip = function ontooltip(event) { 7290 me._onTooltip(event); 7291 }; 7292 // TODO: these events are never cleaned up... can give a 'memory leakage' 7293 7294 util.addEventListener(this.frame.canvas, 'keydown', onkeydown); 7295 util.addEventListener(this.frame.canvas, 'mousedown', onmousedown); 7296 util.addEventListener(this.frame.canvas, 'touchstart', ontouchstart); 7297 util.addEventListener(this.frame.canvas, 'mousewheel', onmousewheel); 7298 util.addEventListener(this.frame.canvas, 'mousemove', ontooltip); 7299 7300 // add the new graph to the container element 7301 this.containerElement.appendChild(this.frame); 7302 }; 7303 7304 /** 7305 * Set a new size for the graph 7306 * @param {string} width Width in pixels or percentage (for example '800px' 7307 * or '50%') 7308 * @param {string} height Height in pixels or percentage (for example '400px'
7309 * or '30%') 7310 */ 7311 Graph3d.prototype.setSize = function (width, height) { 7312 this.frame.style.width = width; 7313 this.frame.style.height = height; 7314 7315 this._resizeCanvas(); 7316 }; 7317 7318 /** 7319 * Resize the canvas to the current size of the frame 7320 */ 7321 Graph3d.prototype._resizeCanvas = function () { 7322 this.frame.canvas.style.width = '100%'; 7323 this.frame.canvas.style.height = '100%'; 7324 7325 this.frame.canvas.width = this.frame.canvas.clientWidth; 7326 this.frame.canvas.height = this.frame.canvas.clientHeight; 7327 7328 // adjust with for margin 7329 this.frame.filter.style.width = this.frame.canvas.clientWidth - 2 * 10 + 'px'; 7330 }; 7331 7332 /** 7333 * Start animation 7334 */ 7335 Graph3d.prototype.animationStart = function () { 7336 if (!this.frame.filter || !this.frame.filter.slider) throw 'No animation available'; 7337 7338 this.frame.filter.slider.play(); 7339 }; 7340 7341 /** 7342 * Stop animation 7343 */ 7344 Graph3d.prototype.animationStop = function () { 7345 if (!this.frame.filter || !this.frame.filter.slider) return; 7346 7347 this.frame.filter.slider.stop(); 7348 }; 7349 7350 /** 7351 * Resize the center position based on the current values in this.defaultXCenter 7352 * and this.defaultYCenter (which are strings with a percentage or a value 7353 * in pixels). The center positions are the variables this.xCenter 7354 * and this.yCenter 7355 */ 7356 Graph3d.prototype._resizeCenter = function () { 7357 // calculate the horizontal center position 7358 if (this.defaultXCenter.charAt(this.defaultXCenter.length - 1) === '%') { 7359 this.xcenter = parseFloat(this.defaultXCenter) / 100 * this.frame.canvas.clientWidth; 7360 } else { 7361 this.xcenter = parseFloat(this.defaultXCenter); // supposed to be in px 7362 } 7363 7364 // calculate the vertical center position 7365 if (this.defaultYCenter.charAt(this.defaultYCenter.length - 1) === '%') { 7366 this.ycenter = parseFloat(this.defaultYCenter) / 100 * (this.frame.canvas.clientHeight - this.frame.filter.clientHeight); 7367 } else { 7368 this.ycenter = parseFloat(this.defaultYCenter); // supposed to be in px 7369 } 7370 }; 7371 7372 /** 7373 * Set the rotation and distance of the camera 7374 * @param {Object} pos An object with the camera position. The object 7375 * contains three parameters: 7376 * - horizontal {Number} 7377 * The horizontal rotation, between 0 and 2*PI. 7378 * Optional, can be left undefined. 7379 * - vertical {Number} 7380 * The vertical rotation, between 0 and 0.5*PI 7381 * if vertical=0.5*PI, the graph is shown from the 7382 * top. Optional, can be left undefined. 7383 * - distance {Number} 7384 * The (normalized) distance of the camera to the 7385 * center of the graph, a value between 0.71 and 5.0. 7386 * Optional, can be left undefined. 7387 */ 7388 Graph3d.prototype.setCameraPosition = function (pos) { 7389 if (pos === undefined) { 7390 return; 7391 } 7392 7393 if (pos.horizontal !== undefined && pos.vertical !== undefined) { 7394 this.camera.setArmRotation(pos.horizontal, pos.vertical); 7395 } 7396 7397 if (pos.distance !== undefined) { 7398 this.camera.setArmLength(pos.distance); 7399 } 7400 7401 this.redraw(); 7402 }; 7403 7404 /** 7405 * Retrieve the current camera rotation 7406 * @return {object} An object with parameters horizontal, vertical, and 7407 * distance 7408 */ 7409 Graph3d.prototype.getCameraPosition = function () { 7410 var pos = this.camera.getArmRotation(); 7411 pos.distance = this.camera.getArmLength(); 7412 return pos; 7413 }; 7414 7415 /** 7416 * Load data into the 3D Graph 7417 */ 7418 Graph3d.prototype._readData = function (data) { 7419 // read the data 7420 this._dataInitialize(data, this.style); 7421 7422 if (this.dataFilter) { 7423 // apply filtering 7424 this.dataPoints = this.dataFilter._getDataPoints(); 7425 } else { 7426 // no filtering. load all data 7427 this.dataPoints = this._getDataPoints(this.dataTable); 7428 } 7429 7430 // draw the filter 7431 this._redrawFilter(); 7432 }; 7433 7434 /** 7435 * Replace the dataset of the Graph3d 7436 * @param {Array | DataSet | DataView} data 7437 */ 7438 Graph3d.prototype.setData = function (data) { 7439 this._readData(data); 7440 this.redraw(); 7441 7442 // start animation when option is true 7443 if (this.animationAutoStart && this.dataFilter) { 7444 this.animationStart(); 7445 } 7446 }; 7447 7448 /** 7449 * Update the options. Options will be merged with current options 7450 * @param {Object} options 7451 */ 7452 Graph3d.prototype.setOptions = function (options) { 7453 var cameraPosition = undefined; 7454 7455 this.animationStop(); 7456 7457 if (options !== undefined) { 7458 // retrieve parameter values 7459 if (options.width !== undefined) this.width = options.width; 7460 if (options.height !== undefined) this.height = options.height; 7461 7462 if (options.xCenter !== undefined) this.defaultXCenter = options.xCenter; 7463 if (options.yCenter !== undefined) this.defaultYCenter = options.yCenter; 7464 7465 if (options.filterLabel !== undefined) this.filterLabel = options.filterLabel; 7466 if (options.legendLabel !== undefined) this.legendLabel = options.legendLabel; 7467 if (options.xLabel !== undefined) this.xLabel = options.xLabel; 7468 if (options.yLabel !== undefined) this.yLabel = options.yLabel; 7469 if (options.zLabel !== undefined) this.zLabel = options.zLabel; 7470 7471 if (options.xValueLabel !== undefined) this.xValueLabel = options.xValueLabel; 7472 if (options.yValueLabel !== undefined) this.yValueLabel = options.yValueLabel; 7473 if (options.zValueLabel !== undefined) this.zValueLabel = options.zValueLabel; 7474 7475 if (options.style !== undefined) { 7476 var styleNumber = this._getStyleNumber(options.style); 7477 if (styleNumber !== -1) { 7478 this.style = styleNumber; 7479 } 7480 } 7481 if (options.showGrid !== undefined) this.showGrid = options.showGrid; 7482 if (options.showPerspective !== undefined) this.showPerspective = options.showPerspective; 7483 if (options.showShadow !== undefined) this.showShadow = options.showShadow; 7484 if (options.tooltip !== undefined) this.showTooltip = options.tooltip; 7485 if (options.showAnimationControls !== undefined) this.showAnimationControls = options.showAnimationControls; 7486 if (options.keepAspectRatio !== undefined) this.keepAspectRatio = options.keepAspectRatio; 7487 if (options.verticalRatio !== undefined) this.verticalRatio = options.verticalRatio; 7488 7489 if (options.animationInterval !== undefined) this.animationInterval = options.animationInterval; 7490 if (options.animationPreload !== undefined) this.animationPreload = options.animationPreload; 7491 if (options.animationAutoStart !== undefined) this.animationAutoStart = options.animationAutoStart; 7492 7493 if (options.xBarWidth !== undefined) this.defaultXBarWidth = options.xBarWidth; 7494 if (options.yBarWidth !== undefined) this.defaultYBarWidth = options.yBarWidth; 7495 7496 if (options.xMin !== undefined) this.defaultXMin = options.xMin; 7497 if (options.xStep !== undefined) this.defaultXStep = options.xStep; 7498 if (options.xMax !== undefined) this.defaultXMax = options.xMax; 7499 if (options.yMin !== undefined) this.defaultYMin = options.yMin; 7500 if (options.yStep !== undefined) this.defaultYStep = options.yStep; 7501 if (options.yMax !== undefined) this.defaultYMax = options.yMax; 7502 if (options.zMin !== undefined) this.defaultZMin = options.zMin; 7503 if (options.zStep !== undefined) this.defaultZStep = options.zStep; 7504 if (options.zMax !== undefined) this.defaultZMax = options.zMax;
7505 if (options.valueMin !== undefined) this.defaultValueMin = options.valueMin; 7506 if (options.valueMax !== undefined) this.defaultValueMax = options.valueMax; 7507 if (options.backgroundColor !== undefined) this._setBackgroundColor(options.backgroundColor); 7508 7509 if (options.cameraPosition !== undefined) cameraPosition = options.cameraPosition; 7510 7511 if (cameraPosition !== undefined) { 7512 this.camera.setArmRotation(cameraPosition.horizontal, cameraPosition.vertical); 7513 this.camera.setArmLength(cameraPosition.distance); 7514 } 7515 7516 // colors 7517 if (options.axisColor !== undefined) this.axisColor = options.axisColor; 7518 if (options.gridColor !== undefined) this.gridColor = options.gridColor; 7519 if (options.dataColor) { 7520 if (typeof options.dataColor === 'string') { 7521 this.dataColor.fill = options.dataColor; 7522 this.dataColor.stroke = options.dataColor; 7523 } else { 7524 if (options.dataColor.fill) { 7525 this.dataColor.fill = options.dataColor.fill; 7526 } 7527 if (options.dataColor.stroke) { 7528 this.dataColor.stroke = options.dataColor.stroke; 7529 } 7530 if (options.dataColor.strokeWidth !== undefined) { 7531 this.dataColor.strokeWidth = options.dataColor.strokeWidth; 7532 } 7533 } 7534 } 7535 } 7536 7537 this.setSize(this.width, this.height); 7538 7539 // re-load the data 7540 if (this.dataTable) { 7541 this.setData(this.dataTable); 7542 } 7543 7544 // start animation when option is true 7545 if (this.animationAutoStart && this.dataFilter) { 7546 this.animationStart(); 7547 } 7548 }; 7549 7550 /** 7551 * Redraw the Graph. 7552 */ 7553 Graph3d.prototype.redraw = function () { 7554 if (this.dataPoints === undefined) { 7555 throw 'Error: graph data not initialized'; 7556 } 7557 7558 this._resizeCanvas(); 7559 this._resizeCenter(); 7560 this._redrawSlider(); 7561 this._redrawClear(); 7562 this._redrawAxis(); 7563 7564 if (this.style === Graph3d.STYLE.GRID || this.style === Graph3d.STYLE.SURFACE) { 7565 this._redrawDataGrid(); 7566 } else if (this.style === Graph3d.STYLE.LINE) { 7567 this._redrawDataLine(); 7568 } else if (this.style === Graph3d.STYLE.BAR || this.style === Graph3d.STYLE.BARCOLOR || this.style === Graph3d.STYLE.BARSIZE) { 7569 this._redrawDataBar(); 7570 } else { 7571 // style is DOT, DOTLINE, DOTCOLOR, DOTSIZE 7572 this._redrawDataDot(); 7573 } 7574 7575 this._redrawInfo(); 7576 this._redrawLegend(); 7577 }; 7578 7579 /** 7580 * Clear the canvas before redrawing 7581 */ 7582 Graph3d.prototype._redrawClear = function () { 7583 var canvas = this.frame.canvas; 7584 var ctx = canvas.getContext('2d'); 7585 7586 ctx.clearRect(0, 0, canvas.width, canvas.height); 7587 }; 7588 7589 /** 7590 * Redraw the legend showing the colors 7591 */ 7592 Graph3d.prototype._redrawLegend = function () { 7593 var y; 7594 7595 if (this.style === Graph3d.STYLE.DOTCOLOR || this.style === Graph3d.STYLE.DOTSIZE) { 7596 7597 var dotSize = this.frame.clientWidth * 0.02; 7598 7599 var widthMin, widthMax; 7600 if (this.style === Graph3d.STYLE.DOTSIZE) { 7601 widthMin = dotSize / 2; // px 7602 widthMax = dotSize / 2 + dotSize * 2; // Todo: put this in one function 7603 } else { 7604 widthMin = 20; // px 7605 widthMax = 20; // px 7606 } 7607 7608 var height = Math.max(this.frame.clientHeight * 0.25, 100); 7609 var top = this.margin; 7610 var right = this.frame.clientWidth - this.margin; 7611 var left = right - widthMax; 7612 var bottom = top + height; 7613 } 7614 7615 var canvas = this.frame.canvas; 7616 var ctx = canvas.getContext('2d'); 7617 ctx.lineWidth = 1; 7618 ctx.font = '14px arial'; // TODO: put in options 7619 7620 if (this.style === Graph3d.STYLE.DOTCOLOR) { 7621 // draw the color bar 7622 var ymin = 0; 7623 var ymax = height; // Todo: make height customizable 7624 for (y = ymin; y < ymax; y++) { 7625 var f = (y - ymin) / (ymax - ymin); 7626 7627 //var width = (dotSize / 2 + (1-f) * dotSize * 2); // Todo: put this in one function 7628 var hue = f * 240; 7629 var color = this._hsv2rgb(hue, 1, 1); 7630 7631 ctx.strokeStyle = color; 7632 ctx.beginPath(); 7633 ctx.moveTo(left, top + y); 7634 ctx.lineTo(right, top + y); 7635 ctx.stroke(); 7636 } 7637 7638 ctx.strokeStyle = this.axisColor; 7639 ctx.strokeRect(left, top, widthMax, height); 7640 } 7641 7642 if (this.style === Graph3d.STYLE.DOTSIZE) { 7643 // draw border around color bar 7644 ctx.strokeStyle = this.axisColor; 7645 ctx.fillStyle = this.dataColor.fill; 7646 ctx.beginPath(); 7647 ctx.moveTo(left, top); 7648 ctx.lineTo(right, top); 7649 ctx.lineTo(right - widthMax + widthMin, bottom); 7650 ctx.lineTo(left, bottom); 7651 ctx.closePath(); 7652 ctx.fill(); 7653 ctx.stroke(); 7654 } 7655 7656 if (this.style === Graph3d.STYLE.DOTCOLOR || this.style === Graph3d.STYLE.DOTSIZE) { 7657 // print values along the color bar 7658 var gridLineLen = 5; // px 7659 var step = new StepNumber(this.valueMin, this.valueMax, (this.valueMax - this.valueMin) / 5, true); 7660 step.start(); 7661 if (step.getCurrent() < this.valueMin) { 7662 step.next(); 7663 } 7664 while (!step.end()) { 7665 y = bottom - (step.getCurrent() - this.valueMin) / (this.valueMax - this.valueMin) * height; 7666 7667 ctx.beginPath(); 7668 ctx.moveTo(left - gridLineLen, y); 7669 ctx.lineTo(left, y); 7670 ctx.stroke(); 7671 7672 ctx.textAlign = 'right'; 7673 ctx.textBaseline = 'middle'; 7674 ctx.fillStyle = this.axisColor; 7675 ctx.fillText(step.getCurrent(), left - 2 * gridLineLen, y); 7676 7677 step.next(); 7678 } 7679 7680 ctx.textAlign = 'right'; 7681 ctx.textBaseline = 'top'; 7682 var label = this.legendLabel; 7683 ctx.fillText(label, right, bottom + this.margin); 7684 } 7685 }; 7686 7687 /** 7688 * Redraw the filter 7689 */ 7690 Graph3d.prototype._redrawFilter = function () { 7691 this.frame.filter.innerHTML = ''; 7692 7693 if (this.dataFilter) { 7694 var options = { 7695 'visible': this.showAnimationControls 7696 }; 7697 var slider = new Slider(this.frame.filter, options); 7698 this.frame.filter.slider = slider; 7699 7700 // TODO: css here is not nice here... 7701 this.frame.filter.style.padding = '10px'; 7702 //this.frame.filter.style.backgroundColor = '#EFEFEF'; 7703 7704 slider.setValues(this.dataFilter.values); 7705 slider.setPlayInterval(this.animationInterval); 7706 7707 // create an event handler 7708 var me = this; 7709 var onchange = function onchange() { 7710 var index = slider.getIndex(); 7711 7712 me.dataFilter.selectValue(index); 7713 me.dataPoints = me.dataFilter._getDataPoints(); 7714 7715 me.redraw(); 7716 }; 7717 slider.setOnChangeCallback(onchange); 7718 } else { 7719 this.frame.filter.slider = undefined; 7720 } 7721 }; 7722 7723 /** 7724 * Redraw the slider 7725 */ 7726 Graph3d.prototype._redrawSlider = function () { 7727 if (this.frame.filter.slider !== undefined) { 7728 this.frame.filter.slider.redraw(); 7729 } 7730 }; 7731 7732 /** 7733 * Redraw common information 7734 */ 7735 Graph3d.prototype._redrawInfo = function () { 7736 if (this.dataFilter) { 7737 var canvas = this.frame.canvas; 7738 var ctx = canvas.getContext('2d'); 7739 7740 ctx.font = '14px arial'; // TODO: put in options 7741 ctx.lineStyle = 'gray'; 7742 ctx.fillStyle = 'gray'; 7743 ctx.textAlign = 'left'; 7744 ctx.textBaseline = 'top'; 7745 7746 var x = this.margin; 7747 var y = this.margin; 7748 ctx.fillText(this.dataFilter.getLabel() + ': ' + this.dataFilter.getSelectedValue(), x, y); 7749 } 7750 }; 7751 7752 /** 7753 * Redraw the axis 7754 */ 7755 Graph3d.prototype._redrawAxis = function () { 7756 var canvas = this.frame.canvas, 7757 ctx = canvas.getContext('2d'), 7758 from, 7759 to, 7760 step, 7761 prettyStep, 7762 text, 7763 xText, 7764 yText, 7765 zText, 7766 offset, 7767 xOffset, 7768 yOffset, 7769 xMin2d, 7770 xMax2d; 7771 7772 // TODO: get the actual rendered style of the containerElement 7773 //ctx.font = this.containerElement.style.font; 7774 ctx.font = 24 / this.camera.getArmLength() + 'px arial'; 7775 7776 // calculate the length for the short grid lines 7777 var gridLenX = 0.025 / this.scale.x; 7778 var gridLenY = 0.025 / this.scale.y; 7779 var textMargin = 5 / this.camera.getArmLength(); // px 7780 var armAngle = this.camera.getArmRotation().horizontal; 7781 7782 // draw x-grid lines 7783 ctx.lineWidth = 1; 7784 prettyStep = this.defaultXStep === undefined; 7785 step = new StepNumber(this.xMin, this.xMax, this.xStep, prettyStep); 7786 step.start(); 7787 if (step.getCurrent() < this.xMin) { 7788 step.next(); 7789 } 7790 while (!step.end()) { 7791 var x = step.getCurrent(); 7792 7793 if (this.showGrid) { 7794 from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin)); 7795 to = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin)); 7796 ctx.strokeStyle = this.gridColor; 7797 ctx.beginPath(); 7798 ctx.moveTo(from.x, from.y); 7799 ctx.lineTo(to.x, to.y); 7800 ctx.stroke(); 7801 } else { 7802 from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin)); 7803 to = this._convert3Dto2D(new Point3d(x, this.yMin + gridLenX, this.zMin)); 7804 ctx.strokeStyle = this.axisColor; 7805 ctx.beginPath(); 7806 ctx.moveTo(from.x, from.y); 7807 ctx.lineTo(to.x, to.y); 7808 ctx.stroke(); 7809 7810 from = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin)); 7811 to = this._convert3Dto2D(new Point3d(x, this.yMax - gridLenX, this.zMin)); 7812 ctx.strokeStyle = this.axisColor; 7813 ctx.beginPath(); 7814 ctx.moveTo(from.x, from.y); 7815 ctx.lineTo(to.x, to.y); 7816 ctx.stroke(); 7817 } 7818
7819 yText = Math.cos(armAngle) > 0 ? this.yMin : this.yMax; 7820 text = this._convert3Dto2D(new Point3d(x, yText, this.zMin)); 7821 if (Math.cos(armAngle * 2) > 0) { 7822 ctx.textAlign = 'center'; 7823 ctx.textBaseline = 'top'; 7824 text.y += textMargin; 7825 } else if (Math.sin(armAngle * 2) < 0) { 7826 ctx.textAlign = 'right'; 7827 ctx.textBaseline = 'middle'; 7828 } else { 7829 ctx.textAlign = 'left'; 7830 ctx.textBaseline = 'middle'; 7831 } 7832 ctx.fillStyle = this.axisColor; 7833 ctx.fillText(' ' + this.xValueLabel(step.getCurrent()) + ' ', text.x, text.y); 7834 7835 step.next(); 7836 } 7837 7838 // draw y-grid lines 7839 ctx.lineWidth = 1; 7840 prettyStep = this.defaultYStep === undefined; 7841 step = new StepNumber(this.yMin, this.yMax, this.yStep, prettyStep); 7842 step.start(); 7843 if (step.getCurrent() < this.yMin) { 7844 step.next(); 7845 } 7846 while (!step.end()) { 7847 if (this.showGrid) { 7848 from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin)); 7849 to = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin)); 7850 ctx.strokeStyle = this.gridColor; 7851 ctx.beginPath(); 7852 ctx.moveTo(from.x, from.y); 7853 ctx.lineTo(to.x, to.y); 7854 ctx.stroke(); 7855 } else { 7856 from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin)); 7857 to = this._convert3Dto2D(new Point3d(this.xMin + gridLenY, step.getCurrent(), this.zMin)); 7858 ctx.strokeStyle = this.axisColor; 7859 ctx.beginPath(); 7860 ctx.moveTo(from.x, from.y); 7861 ctx.lineTo(to.x, to.y); 7862 ctx.stroke(); 7863 7864 from = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin)); 7865 to = this._convert3Dto2D(new Point3d(this.xMax - gridLenY, step.getCurrent(), this.zMin)); 7866 ctx.strokeStyle = this.axisColor; 7867 ctx.beginPath(); 7868 ctx.moveTo(from.x, from.y); 7869 ctx.lineTo(to.x, to.y); 7870 ctx.stroke(); 7871 } 7872 7873 xText = Math.sin(armAngle) > 0 ? this.xMin : this.xMax; 7874 text = this._convert3Dto2D(new Point3d(xText, step.getCurrent(), this.zMin)); 7875 if (Math.cos(armAngle * 2) < 0) { 7876 ctx.textAlign = 'center'; 7877 ctx.textBaseline = 'top'; 7878 text.y += textMargin; 7879 } else if (Math.sin(armAngle * 2) > 0) { 7880 ctx.textAlign = 'right'; 7881 ctx.textBaseline = 'middle'; 7882 } else { 7883 ctx.textAlign = 'left'; 7884 ctx.textBaseline = 'middle'; 7885 } 7886 ctx.fillStyle = this.axisColor; 7887 ctx.fillText(' ' + this.yValueLabel(step.getCurrent()) + ' ', text.x, text.y); 7888 7889 step.next(); 7890 } 7891 7892 // draw z-grid lines and axis 7893 ctx.lineWidth = 1; 7894 prettyStep = this.defaultZStep === undefined; 7895 step = new StepNumber(this.zMin, this.zMax, this.zStep, prettyStep); 7896 step.start(); 7897 if (step.getCurrent() < this.zMin) { 7898 step.next(); 7899 } 7900 xText = Math.cos(armAngle) > 0 ? this.xMin : this.xMax; 7901 yText = Math.sin(armAngle) < 0 ? this.yMin : this.yMax; 7902 while (!step.end()) { 7903 // TODO: make z-grid lines really 3d? 7904 from = this._convert3Dto2D(new Point3d(xText, yText, step.getCurrent())); 7905 ctx.strokeStyle = this.axisColor; 7906 ctx.beginPath(); 7907 ctx.moveTo(from.x, from.y); 7908 ctx.lineTo(from.x - textMargin, from.y); 7909 ctx.stroke(); 7910 7911 ctx.textAlign = 'right'; 7912 ctx.textBaseline = 'middle'; 7913 ctx.fillStyle = this.axisColor; 7914 ctx.fillText(this.zValueLabel(step.getCurrent()) + ' ', from.x - 5, from.y); 7915 7916 step.next(); 7917 } 7918 ctx.lineWidth = 1; 7919 from = this._convert3Dto2D(new Point3d(xText, yText, this.zMin)); 7920 to = this._convert3Dto2D(new Point3d(xText, yText, this.zMax)); 7921 ctx.strokeStyle = this.axisColor; 7922 ctx.beginPath(); 7923 ctx.moveTo(from.x, from.y); 7924 ctx.lineTo(to.x, to.y); 7925 ctx.stroke(); 7926 7927 // draw x-axis 7928 ctx.lineWidth = 1; 7929 // line at yMin 7930 xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin)); 7931 xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin)); 7932 ctx.strokeStyle = this.axisColor; 7933 ctx.beginPath(); 7934 ctx.moveTo(xMin2d.x, xMin2d.y); 7935 ctx.lineTo(xMax2d.x, xMax2d.y); 7936 ctx.stroke(); 7937 // line at ymax 7938 xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin)); 7939 xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin)); 7940 ctx.strokeStyle = this.axisColor; 7941 ctx.beginPath(); 7942 ctx.moveTo(xMin2d.x, xMin2d.y); 7943 ctx.lineTo(xMax2d.x, xMax2d.y); 7944 ctx.stroke(); 7945 7946 // draw y-axis 7947 ctx.lineWidth = 1; 7948 // line at xMin 7949 from = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin)); 7950 to = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin)); 7951 ctx.strokeStyle = this.axisColor; 7952 ctx.beginPath(); 7953 ctx.moveTo(from.x, from.y); 7954 ctx.lineTo(to.x, to.y); 7955 ctx.stroke(); 7956 // line at xMax 7957 from = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin)); 7958 to = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin)); 7959 ctx.strokeStyle = this.axisColor; 7960 ctx.beginPath(); 7961 ctx.moveTo(from.x, from.y); 7962 ctx.lineTo(to.x, to.y); 7963 ctx.stroke(); 7964 7965 // draw x-label 7966 var xLabel = this.xLabel; 7967 if (xLabel.length > 0) { 7968 yOffset = 0.1 / this.scale.y; 7969 xText = (this.xMin + this.xMax) / 2;
7970 yText = Math.cos(armAngle) > 0 ? this.yMin - yOffset : this.yMax + yOffset; 7971 text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin)); 7972 if (Math.cos(armAngle * 2) > 0) { 7973 ctx.textAlign = 'center'; 7974 ctx.textBaseline = 'top'; 7975 } else if (Math.sin(armAngle * 2) < 0) { 7976 ctx.textAlign = 'right'; 7977 ctx.textBaseline = 'middle'; 7978 } else { 7979 ctx.textAlign = 'left'; 7980 ctx.textBaseline = 'middle'; 7981 } 7982 ctx.fillStyle = this.axisColor; 7983 ctx.fillText(xLabel, text.x, text.y); 7984 } 7985 7986 // draw y-label 7987 var yLabel = this.yLabel; 7988 if (yLabel.length > 0) { 7989 xOffset = 0.1 / this.scale.x; 7990 xText = Math.sin(armAngle) > 0 ? this.xMin - xOffset : this.xMax + xOffset; 7991 yText = (this.yMin + this.yMax) / 2; 7992 text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin)); 7993 if (Math.cos(armAngle * 2) < 0) { 7994 ctx.textAlign = 'center'; 7995 ctx.textBaseline = 'top'; 7996 } else if (Math.sin(armAngle * 2) > 0) { 7997 ctx.textAlign = 'right'; 7998 ctx.textBaseline = 'middle'; 7999 } else { 8000 ctx.textAlign = 'left'; 8001 ctx.textBaseline = 'middle'; 8002 } 8003 ctx.fillStyle = this.axisColor; 8004 ctx.fillText(yLabel, text.x, text.y); 8005 } 8006 8007 // draw z-label 8008 var zLabel = this.zLabel; 8009 if (zLabel.length > 0) { 8010 offset = 30; // pixels. // TODO: relate to the max width of the values on the z axis? 8011 xText = Math.cos(armAngle) > 0 ? this.xMin : this.xMax; 8012 yText = Math.sin(armAngle) < 0 ? this.yMin : this.yMax; 8013 zText = (this.zMin + this.zMax) / 2; 8014 text = this._convert3Dto2D(new Point3d(xText, yText, zText)); 8015 ctx.textAlign = 'right'; 8016 ctx.textBaseline = 'middle'; 8017 ctx.fillStyle = this.axisColor; 8018 ctx.fillText(zLabel, text.x - offset, text.y); 8019 } 8020 }; 8021 8022 /** 8023 * Calculate the color based on the given value. 8024 * @param {Number} H Hue, a value be between 0 and 360 8025 * @param {Number} S Saturation, a value between 0 and 1 8026 * @param {Number} V Value, a value between 0 and 1 8027 */ 8028 Graph3d.prototype._hsv2rgb = function (H, S, V) { 8029 var R, G, B, C, Hi, X; 8030 8031 C = V * S; 8032 Hi = Math.floor(H / 60); // hi = 0,1,2,3,4,5 8033 X = C * (1 - Math.abs(H / 60 % 2 - 1)); 8034 8035 switch (Hi) { 8036 case 0: 8037 R = C;G = X;B = 0;break; 8038 case 1: 8039 R = X;G = C;B = 0;break; 8040 case 2: 8041 R = 0;G = C;B = X;break; 8042 case 3: 8043 R = 0;G = X;B = C;break; 8044 case 4: 8045 R = X;G = 0;B = C;break; 8046 case 5: 8047 R = C;G = 0;B = X;break; 8048 8049 default: 8050 R = 0;G = 0;B = 0;break; 8051 } 8052 8053 return 'RGB(' + parseInt(R * 255) + ',' + parseInt(G * 255) + ',' + parseInt(B * 255) + ')'; 8054 }; 8055 8056 /** 8057 * Draw all datapoints as a grid 8058 * This function can be used when the style is 'grid' 8059 */ 8060 Graph3d.prototype._redrawDataGrid = function () { 8061 var canvas = this.frame.canvas, 8062 ctx = canvas.getContext('2d'), 8063 point, 8064 right, 8065 top, 8066 cross, 8067 i, 8068 topSideVisible, 8069 fillStyle, 8070 strokeStyle, 8071 lineWidth, 8072 h, 8073 s, 8074 v, 8075 zAvg; 8076 8077 ctx.lineJoin = 'round'; 8078 ctx.lineCap = 'round'; 8079 8080 if (this.dataPoints === undefined || this.dataPoints.length <= 0) return; // TODO: throw exception? 8081 8082 // calculate the translations and screen position of all points 8083 for (i = 0; i < this.dataPoints.length; i++) { 8084 var trans = this._convertPointToTranslation(this.dataPoints[i].point); 8085 var screen = this._convertTranslationToScreen(trans); 8086 8087 this.dataPoints[i].trans = trans; 8088 this.dataPoints[i].screen = screen; 8089 8090 // calculate the translation of the point at the bottom (needed for sorting) 8091 var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom); 8092 this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z; 8093 } 8094 8095 // sort the points on depth of their (x,y) position (not on z) 8096 var sortDepth = function sortDepth(a, b) { 8097 return b.dist - a.dist; 8098 }; 8099 this.dataPoints.sort(sortDepth); 8100 8101 if (this.style === Graph3d.STYLE.SURFACE) { 8102 for (i = 0; i < this.dataPoints.length; i++) { 8103 point = this.dataPoints[i]; 8104 right = this.dataPoints[i].pointRight; 8105 top = this.dataPoints[i].pointTop; 8106 cross = this.dataPoints[i].pointCross; 8107 8108 if (point !== undefined && right !== undefined && top !== undefined && cross !== undefined) { 8109 8110 if (this.showGrayBottom || this.showShadow) { 8111 // calculate the cross product of the two vectors from center 8112 // to left and right, in order to know whether we are looking at the 8113 // bottom or at the top side. We can also use the cross product 8114 // for calculating light intensity 8115 var aDiff = Point3d.subtract(cross.trans, point.trans); 8116 var bDiff = Point3d.subtract(top.trans, right.trans); 8117 var crossproduct = Point3d.crossProduct(aDiff, bDiff); 8118 var len = crossproduct.length(); 8119 // FIXME: there is a bug with determining the surface side (shadow or colored) 8120 8121 topSideVisible = crossproduct.z > 0; 8122 } else { 8123 topSideVisible = true; 8124 } 8125 8126 if (topSideVisible) { 8127 // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0 8128 zAvg = (point.point.z + right.point.z + top.point.z + cross.point.z) / 4; 8129 h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240; 8130 s = 1; // saturation 8131 8132 if (this.showShadow) { 8133 v = Math.min(1 + crossproduct.x / len / 2, 1); // value. TODO: scale 8134 fillStyle = this._hsv2rgb(h, s, v); 8135 strokeStyle = fillStyle; 8136 } else { 8137 v = 1; 8138 fillStyle = this._hsv2rgb(h, s, v); 8139 strokeStyle = this.axisColor; // TODO: should be customizable 8140 } 8141 } else { 8142 fillStyle = 'gray'; 8143 strokeStyle = this.axisColor; 8144 } 8145 8146 ctx.lineWidth = this._getStrokeWidth(point); 8147 ctx.fillStyle = fillStyle; 8148 ctx.strokeStyle = strokeStyle; 8149 ctx.beginPath(); 8150 ctx.moveTo(point.screen.x, point.screen.y); 8151 ctx.lineTo(right.screen.x, right.screen.y); 8152 ctx.lineTo(cross.screen.x, cross.screen.y); 8153 ctx.lineTo(top.screen.x, top.screen.y); 8154 ctx.closePath(); 8155 ctx.fill(); 8156 ctx.stroke(); // TODO: only draw stroke when strokeWidth > 0 8157 } 8158 } 8159 } else { 8160 // grid style 8161 for (i = 0; i < this.dataPoints.length; i++) { 8162 point = this.dataPoints[i]; 8163 right = this.dataPoints[i].pointRight; 8164 top = this.dataPoints[i].pointTop; 8165 8166 if (point !== undefined && right !== undefined) { 8167 // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0 8168 zAvg = (point.point.z + right.point.z) / 2; 8169 h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240; 8170 8171 ctx.lineWidth = this._getStrokeWidth(point) * 2; 8172 ctx.strokeStyle = this._hsv2rgb(h, 1, 1); 8173 ctx.beginPath(); 8174 ctx.moveTo(point.screen.x, point.screen.y); 8175 ctx.lineTo(right.screen.x, right.screen.y); 8176 ctx.stroke(); 8177 } 8178 8179 if (point !== undefined && top !== undefined) { 8180 // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0 8181 zAvg = (point.point.z + top.point.z) / 2; 8182 h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240; 8183 8184 ctx.lineWidth = this._getStrokeWidth(point) * 2; 8185 ctx.strokeStyle = this._hsv2rgb(h, 1, 1); 8186 ctx.beginPath(); 8187 ctx.moveTo(point.screen.x, point.screen.y); 8188 ctx.lineTo(top.screen.x, top.screen.y); 8189 ctx.stroke(); 8190 } 8191 } 8192 } 8193 }; 8194 8195 Graph3d.prototype._getStrokeWidth = function (point) { 8196 if (point !== undefined) { 8197 if (this.showPerspective) { 8198 return 1 / -point.trans.z * this.dataColor.strokeWidth; 8199 } else { 8200 return -(this.eye.z / this.camera.getArmLength()) * this.dataColor.strokeWidth; 8201 } 8202 } 8203 8204 return this.dataColor.strokeWidth; 8205 }; 8206 8207 /** 8208 * Draw all datapoints as dots. 8209 * This function can be used when the style is 'dot' or 'dot-line' 8210 */ 8211 Graph3d.prototype._redrawDataDot = function () { 8212 var canvas = this.frame.canvas; 8213 var ctx = canvas.getContext('2d'); 8214 var i; 8215 8216 if (this.dataPoints === undefined || this.dataPoints.length <= 0) return; // TODO: throw exception? 8217 8218 // calculate the translations of all points 8219 for (i = 0; i < this.dataPoints.length; i++) { 8220 var trans = this._convertPointToTranslation(this.dataPoints[i].point); 8221 var screen = this._convertTranslationToScreen(trans); 8222 this.dataPoints[i].trans = trans; 8223 this.dataPoints[i].screen = screen; 8224 8225 // calculate the distance from the point at the bottom to the camera 8226 var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom); 8227 this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z; 8228 } 8229 8230 // order the translated points by depth 8231 var sortDepth = function sortDepth(a, b) { 8232 return b.dist - a.dist; 8233 }; 8234 this.dataPoints.sort(sortDepth); 8235 8236 // draw the datapoints as colored circles 8237 var dotSize = this.frame.clientWidth * 0.02; // px 8238 for (i = 0; i < this.dataPoints.length; i++) { 8239 var point = this.dataPoints[i]; 8240 8241 if (this.style === Graph3d.STYLE.DOTLINE) { 8242 // draw a vertical line from the bottom to the graph value 8243 //var from = this._convert3Dto2D(new Point3d(point.point.x, point.point.y, this.zMin)); 8244 var from = this._convert3Dto2D(point.bottom); 8245 ctx.lineWidth = 1; 8246 ctx.strokeStyle = this.gridColor; 8247 ctx.beginPath(); 8248 ctx.moveTo(from.x, from.y); 8249 ctx.lineTo(point.screen.x, point.screen.y); 8250 ctx.stroke(); 8251 } 8252 8253 // calculate radius for the circle 8254 var size; 8255 if (this.style === Graph3d.STYLE.DOTSIZE) { 8256 size = dotSize / 2 + 2 * dotSize * (point.point.value - this.valueMin) / (this.valueMax - this.valueMin); 8257 } else { 8258 size = dotSize; 8259 } 8260 8261 var radius; 8262 if (this.showPerspective) { 8263 radius = size / -point.trans.z; 8264 } else { 8265 radius = size * -(this.eye.z / this.camera.getArmLength()); 8266 } 8267 if (radius < 0) { 8268 radius = 0; 8269 } 8270 8271 var hue, color, borderColor; 8272 if (this.style === Graph3d.STYLE.DOTCOLOR) { 8273 // calculate the color based on the value 8274 hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240; 8275 color = this._hsv2rgb(hue, 1, 1); 8276 borderColor = this._hsv2rgb(hue, 1, 0.8); 8277 } else if (this.style === Graph3d.STYLE.DOTSIZE) { 8278 color = this.dataColor.fill; 8279 borderColor = this.dataColor.stroke; 8280 } else { 8281 // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0 8282 hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240; 8283 color = this._hsv2rgb(hue, 1, 1); 8284 borderColor = this._hsv2rgb(hue, 1, 0.8); 8285 } 8286 8287 // draw the circle 8288 ctx.lineWidth = this._getStrokeWidth(point); 8289 ctx.strokeStyle = borderColor; 8290 ctx.fillStyle = color; 8291 ctx.beginPath(); 8292 ctx.arc(point.screen.x, point.screen.y, radius, 0, Math.PI * 2, true); 8293 ctx.fill(); 8294 ctx.stroke(); 8295 } 8296 }; 8297 8298 /** 8299 * Draw all datapoints as bars. 8300 * This function can be used when the style is 'bar', 'bar-color', or 'bar-size' 8301 */ 8302 Graph3d.prototype._redrawDataBar = function () { 8303 var canvas = this.frame.canvas; 8304 var ctx = canvas.getContext('2d'); 8305 var i, j, surface, corners; 8306 8307 if (this.dataPoints === undefined || this.dataPoints.length <= 0) return; // TODO: throw exception? 8308 8309 // calculate the translations of all points 8310 for (i = 0; i < this.dataPoints.length; i++) { 8311 var trans = this._convertPointToTranslation(this.dataPoints[i].point); 8312 var screen = this._convertTranslationToScreen(trans); 8313 this.dataPoints[i].trans = trans; 8314 this.dataPoints[i].screen = screen; 8315 8316 // calculate the distance from the point at the bottom to the camera 8317 var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom); 8318 this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z; 8319 } 8320 8321 // order the translated points by depth 8322 var sortDepth = function sortDepth(a, b) { 8323 return b.dist - a.dist; 8324 }; 8325 this.dataPoints.sort(sortDepth); 8326 8327 ctx.lineJoin = 'round'; 8328 ctx.lineCap = 'round'; 8329 8330 // draw the datapoints as bars 8331 var xWidth = this.xBarWidth / 2; 8332 var yWidth = this.yBarWidth / 2; 8333 for (i = 0; i < this.dataPoints.length; i++) { 8334 var point = this.dataPoints[i]; 8335 8336 // determine color 8337 var hue, color, borderColor; 8338 if (this.style === Graph3d.STYLE.BARCOLOR) { 8339 // calculate the color based on the value 8340 hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240; 8341 color = this._hsv2rgb(hue, 1, 1); 8342 borderColor = this._hsv2rgb(hue, 1, 0.8); 8343 } else if (this.style === Graph3d.STYLE.BARSIZE) { 8344 color = this.dataColor.fill; 8345 borderColor = this.dataColor.stroke; 8346 } else { 8347 // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0 8348 hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240; 8349 color = this._hsv2rgb(hue, 1, 1); 8350 borderColor = this._hsv2rgb(hue, 1, 0.8); 8351 } 8352 8353 // calculate size for the bar 8354 if (this.style === Graph3d.STYLE.BARSIZE) { 8355 xWidth = this.xBarWidth / 2 * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2); 8356 yWidth = this.yBarWidth / 2 * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2); 8357 } 8358 8359 // calculate all corner points 8360 var me = this; 8361 var point3d = point.point; 8362 var top = [{ point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, point3d.z) }, { point: new Point3d(point3d.x + xWidth, point3d.y - yWidth, point3d.z) }, { point: new Point3d(point3d.x + xWidth, point3d.y + yWidth, point3d.z) }, { point: new Point3d(point3d.x - xWidth, point3d.y + yWidth, point3d.z) }]; 8363 var bottom = [{ point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, this.zMin) }, { point: new Point3d(point3d.x + xWidth, point3d.y - yWidth, this.zMin) }, { point: new Point3d(point3d.x + xWidth, point3d.y + yWidth, this.zMin) }, { point: new Point3d(point3d.x - xWidth, point3d.y + yWidth, this.zMin) }]; 8364 8365 // calculate screen location of the points
8366 top.forEach(function (obj) { 8367 obj.screen = me._convert3Dto2D(obj.point); 8368 }); 8369 bottom.forEach(function (obj) { 8370 obj.screen = me._convert3Dto2D(obj.point); 8371 }); 8372 8373 // create five sides, calculate both corner points and center points 8374 var surfaces = [{ corners: top, center: Point3d.avg(bottom[0].point, bottom[2].point) }, { corners: [top[0], top[1], bottom[1], bottom[0]], center: Point3d.avg(bottom[1].point, bottom[0].point) }, { corners: [top[1], top[2], bottom[2], bottom[1]], center: Point3d.avg(bottom[2].point, bottom[1].point) }, { corners: [top[2], top[3], bottom[3], bottom[2]], center: Point3d.avg(bottom[3].point, bottom[2].point) }, { corners: [top[3], top[0], bottom[0], bottom[3]], center: Point3d.avg(bottom[0].point, bottom[3].point) }]; 8375 point.surfaces = surfaces; 8376 8377 // calculate the distance of each of the surface centers to the camera 8378 for (j = 0; j < surfaces.length; j++) { 8379 surface = surfaces[j]; 8380 var transCenter = this._convertPointToTranslation(surface.center); 8381 surface.dist = this.showPerspective ? transCenter.length() : -transCenter.z; 8382 // TODO: this dept calculation doesn't work 100% of the cases due to perspective, 8383 // but the current solution is fast/simple and works in 99.9% of all cases 8384 // the issue is visible in example 14, with graph.setCameraPosition({horizontal: 2.97, vertical: 0.5, distance: 0.9}) 8385 } 8386 8387 // order the surfaces by their (translated) depth 8388 surfaces.sort(function (a, b) { 8389 var diff = b.dist - a.dist; 8390 if (diff) return diff; 8391 8392 // if equal depth, sort the top surface last 8393 if (a.corners === top) return 1; 8394 if (b.corners === top) return -1; 8395 8396 // both are equal 8397 return 0; 8398 }); 8399 8400 // draw the ordered surfaces 8401 ctx.lineWidth = this._getStrokeWidth(point); 8402 ctx.strokeStyle = borderColor; 8403 ctx.fillStyle = color; 8404 // NOTE: we start at j=2 instead of j=0 as we don't need to draw the two surfaces at the backside 8405 for (j = 2; j < surfaces.length; j++) { 8406 surface = surfaces[j]; 8407 corners = surface.corners; 8408 ctx.beginPath(); 8409 ctx.moveTo(corners[3].screen.x, corners[3].screen.y); 8410 ctx.lineTo(corners[0].screen.x, corners[0].screen.y); 8411 ctx.lineTo(corners[1].screen.x, corners[1].screen.y); 8412 ctx.lineTo(corners[2].screen.x, corners[2].screen.y); 8413 ctx.lineTo(corners[3].screen.x, corners[3].screen.y); 8414 ctx.fill(); 8415 ctx.stroke(); 8416 } 8417 } 8418 }; 8419 8420 /** 8421 * Draw a line through all datapoints. 8422 * This function can be used when the style is 'line' 8423 */ 8424 Graph3d.prototype._redrawDataLine = function () { 8425 var canvas = this.frame.canvas, 8426 ctx = canvas.getContext('2d'), 8427 point, 8428 i; 8429 8430 if (this.dataPoints === undefined || this.dataPoints.length <= 0) return; // TODO: throw exception? 8431 8432 // calculate the translations of all points 8433 for (i = 0; i < this.dataPoints.length; i++) { 8434 var trans = this._convertPointToTranslation(this.dataPoints[i].point); 8435 var screen = this._convertTranslationToScreen(trans); 8436 8437 this.dataPoints[i].trans = trans; 8438 this.dataPoints[i].screen = screen; 8439 } 8440 8441 // start the line 8442 if (this.dataPoints.length > 0) { 8443 point = this.dataPoints[0]; 8444 8445 ctx.lineWidth = this._getStrokeWidth(point); 8446 ctx.lineJoin = 'round'; 8447 ctx.lineCap = 'round'; 8448 ctx.strokeStyle = this.dataColor.stroke; 8449 ctx.beginPath(); 8450 ctx.moveTo(point.screen.x, point.screen.y); 8451 8452 // draw the datapoints as colored circles 8453 for (i = 1; i < this.dataPoints.length; i++) { 8454 point = this.dataPoints[i]; 8455 ctx.lineTo(point.screen.x, point.screen.y); 8456 } 8457 8458 // finish the line 8459 ctx.stroke(); 8460 } 8461 }; 8462 8463 /** 8464 * Start a moving operation inside the provided parent element 8465 * @param {Event} event The event that occurred (required for 8466 * retrieving the mouse position) 8467 */ 8468 Graph3d.prototype._onMouseDown = function (event) { 8469 event = event || window.event; 8470
8471 // check if mouse is still down (may be up when focus is lost for example 8472 // in an iframe) 8473 if (this.leftButtonDown) { 8474 this._onMouseUp(event); 8475 } 8476 8477 // only react on left mouse button down 8478 this.leftButtonDown = event.which ? event.which === 1 : event.button === 1; 8479 if (!this.leftButtonDown && !this.touchDown) return; 8480 8481 // get mouse position (different code for IE and all other browsers) 8482 this.startMouseX = getMouseX(event); 8483 this.startMouseY = getMouseY(event); 8484 8485 this.startStart = new Date(this.start); 8486 this.startEnd = new Date(this.end); 8487 this.startArmRotation = this.camera.getArmRotation(); 8488 8489 this.frame.style.cursor = 'move'; 8490 8491 // add event listeners to handle moving the contents 8492 // we store the function onmousemove and onmouseup in the graph, so we can 8493 // remove the eventlisteners lateron in the function mouseUp() 8494 var me = this; 8495 this.onmousemove = function (event) { 8496 me._onMouseMove(event); 8497 }; 8498 this.onmouseup = function (event) { 8499 me._onMouseUp(event); 8500 }; 8501 util.addEventListener(document, 'mousemove', me.onmousemove); 8502 util.addEventListener(document, 'mouseup', me.onmouseup); 8503 util.preventDefault(event); 8504 }; 8505 8506 /** 8507 * Perform moving operating. 8508 * This function activated from within the funcion Graph.mouseDown(). 8509 * @param {Event} event Well, eehh, the event 8510 */ 8511 Graph3d.prototype._onMouseMove = function (event) { 8512 event = event || window.event; 8513 8514 // calculate change in mouse position 8515 var diffX = parseFloat(getMouseX(event)) - this.startMouseX; 8516 var diffY = parseFloat(getMouseY(event)) - this.startMouseY; 8517 8518 var horizontalNew = this.startArmRotation.horizontal + diffX / 200; 8519 var verticalNew = this.startArmRotation.vertical + diffY / 200; 8520 8521 var snapAngle = 4; // degrees 8522 var snapValue = Math.sin(snapAngle / 360 * 2 * Math.PI); 8523 8524 // snap horizontally to nice angles at 0pi, 0.5pi, 1pi, 1.5pi, etc... 8525 // the -0.001 is to take care that the vertical axis is always drawn at the left front corner 8526 if (Math.abs(Math.sin(horizontalNew)) < snapValue) { 8527 horizontalNew = Math.round(horizontalNew / Math.PI) * Math.PI - 0.001; 8528 } 8529 if (Math.abs(Math.cos(horizontalNew)) < snapValue) { 8530 horizontalNew = (Math.round(horizontalNew / Math.PI - 0.5) + 0.5) * Math.PI - 0.001; 8531 } 8532 8533 // snap vertically to nice angles 8534 if (Math.abs(Math.sin(verticalNew)) < snapValue) { 8535 verticalNew = Math.round(verticalNew / Math.PI) * Math.PI; 8536 } 8537 if (Math.abs(Math.cos(verticalNew)) < snapValue) { 8538 verticalNew = (Math.round(verticalNew / Math.PI - 0.5) + 0.5) * Math.PI; 8539 } 8540 8541 this.camera.setArmRotation(horizontalNew, verticalNew); 8542 this.redraw(); 8543 8544 // fire a cameraPositionChange event 8545 var parameters = this.getCameraPosition(); 8546 this.emit('cameraPositionChange', parameters); 8547 8548 util.preventDefault(event); 8549 }; 8550 8551 /** 8552 * Stop moving operating. 8553 * This function activated from within the funcion Graph.mouseDown(). 8554 * @param {event} event The event 8555 */ 8556 Graph3d.prototype._onMouseUp = function (event) { 8557 this.frame.style.cursor = 'auto'; 8558 this.leftButtonDown = false; 8559 8560 // remove event listeners here 8561 util.removeEventListener(document, 'mousemove', this.onmousemove); 8562 util.removeEventListener(document, 'mouseup', this.onmouseup); 8563 util.preventDefault(event); 8564 }; 8565 8566 /** 8567 * After having moved the mouse, a tooltip should pop up when the mouse is resting on a data point 8568 * @param {Event} event A mouse move event 8569 */ 8570 Graph3d.prototype._onTooltip = function (event) { 8571 var delay = 300; // ms 8572 var boundingRect = this.frame.getBoundingClientRect(); 8573 var mouseX = getMouseX(event) - boundingRect.left; 8574 var mouseY = getMouseY(event) - boundingRect.top; 8575 8576 if (!this.showTooltip) { 8577 return; 8578 } 8579 8580 if (this.tooltipTimeout) { 8581 clearTimeout(this.tooltipTimeout); 8582 } 8583 8584 // (delayed) display of a tooltip only if no mouse button is down 8585 if (this.leftButtonDown) { 8586 this._hideTooltip(); 8587 return; 8588 } 8589 8590 if (this.tooltip && this.tooltip.dataPoint) { 8591 // tooltip is currently visible 8592 var dataPoint = this._dataPointFromXY(mouseX, mouseY); 8593 if (dataPoint !== this.tooltip.dataPoint) { 8594 // datapoint changed 8595 if (dataPoint) { 8596 this._showTooltip(dataPoint); 8597 } else { 8598 this._hideTooltip(); 8599 } 8600 } 8601 } else { 8602 // tooltip is currently not visible 8603 var me = this; 8604 this.tooltipTimeout = setTimeout(function () { 8605 me.tooltipTimeout = null; 8606 8607 // show a tooltip if we have a data point 8608 var dataPoint = me._dataPointFromXY(mouseX, mouseY); 8609 if (dataPoint) { 8610 me._showTooltip(dataPoint); 8611 } 8612 }, delay); 8613 } 8614 }; 8615 8616 /** 8617 * Event handler for touchstart event on mobile devices 8618 */ 8619 Graph3d.prototype._onTouchStart = function (event) { 8620 this.touchDown = true; 8621 8622 var me = this; 8623 this.ontouchmove = function (event) { 8624 me._onTouchMove(event); 8625 }; 8626 this.ontouchend = function (event) { 8627 me._onTouchEnd(event); 8628 }; 8629 util.addEventListener(document, 'touchmove', me.ontouchmove); 8630 util.addEventListener(document, 'touchend', me.ontouchend); 8631 8632 this._onMouseDown(event); 8633 }; 8634 8635 /** 8636 * Event handler for touchmove event on mobile devices 8637 */ 8638 Graph3d.prototype._onTouchMove = function (event) { 8639 this._onMouseMove(event); 8640 }; 8641 8642 /** 8643 * Event handler for touchend event on mobile devices 8644 */ 8645 Graph3d.prototype._onTouchEnd = function (event) { 8646 this.touchDown = false;
8647 8648 util.removeEventListener(document, 'touchmove', this.ontouchmove); 8649 util.removeEventListener(document, 'touchend', this.ontouchend); 8650 8651 this._onMouseUp(event); 8652 }; 8653 8654 /** 8655 * Event handler for mouse wheel event, used to zoom the graph 8656 * Code from http://adomas.org/javascript-mouse-wheel/ 8657 * @param {event} event The event 8658 */ 8659 Graph3d.prototype._onWheel = function (event) { 8660 if (!event) /* For IE. */ 8661 event = window.event; 8662 8663 // retrieve delta 8664 var delta = 0; 8665 if (event.wheelDelta) { 8666 /* IE/Opera. */ 8667 delta = event.wheelDelta / 120; 8668 } else if (event.detail) { 8669 /* Mozilla case. */ 8670 // In Mozilla, sign of delta is different than in IE. 8671 // Also, delta is multiple of 3. 8672 delta = -event.detail / 3; 8673 } 8674 8675 // If delta is nonzero, handle it. 8676 // Basically, delta is now positive if wheel was scrolled up, 8677 // and negative, if wheel was scrolled down. 8678 if (delta) { 8679 var oldLength = this.camera.getArmLength(); 8680 var newLength = oldLength * (1 - delta / 10); 8681 8682 this.camera.setArmLength(newLength); 8683 this.redraw(); 8684 8685 this._hideTooltip(); 8686 } 8687 8688 // fire a cameraPositionChange event 8689 var parameters = this.getCameraPosition(); 8690 this.emit('cameraPositionChange', parameters); 8691 8692 // Prevent default actions caused by mouse wheel. 8693 // That might be ugly, but we handle scrolls somehow 8694 // anyway, so don't bother here.. 8695 util.preventDefault(event); 8696 }; 8697 8698 /** 8699 * Test whether a point lies inside given 2D triangle 8700 * @param {Point2d} point 8701 * @param {Point2d[]} triangle 8702 * @return {boolean} Returns true if given point lies inside or on the edge of the triangle 8703 * @private 8704 */ 8705 Graph3d.prototype._insideTriangle = function (point, triangle) { 8706 var a = triangle[0], 8707 b = triangle[1], 8708 c = triangle[2]; 8709 8710 function sign(x) { 8711 return x > 0 ? 1 : x < 0 ? -1 : 0; 8712 } 8713 8714 var as = sign((b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x)); 8715 var bs = sign((c.x - b.x) * (point.y - b.y) - (c.y - b.y) * (point.x - b.x)); 8716 var cs = sign((a.x - c.x) * (point.y - c.y) - (a.y - c.y) * (point.x - c.x)); 8717 8718 // each of the three signs must be either equal to each other or zero 8719 return (as == 0 || bs == 0 || as == bs) && (bs == 0 || cs == 0 || bs == cs) && (as == 0 || cs == 0 || as == cs); 8720 }; 8721 8722 /** 8723 * Find a data point close to given screen position (x, y) 8724 * @param {Number} x 8725 * @param {Number} y 8726 * @return {Object | null} The closest data point or null if not close to any data point 8727 * @private 8728 */ 8729 Graph3d.prototype._dataPointFromXY = function (x, y) { 8730 var i, 8731 distMax = 100, 8732 // px 8733 dataPoint = null, 8734 closestDataPoint = null, 8735 closestDist = null, 8736 center = new Point2d(x, y); 8737 8738 if (this.style === Graph3d.STYLE.BAR || this.style === Graph3d.STYLE.BARCOLOR || this.style === Graph3d.STYLE.BARSIZE) { 8739 // the data points are ordered from far away to closest 8740 for (i = this.dataPoints.length - 1; i >= 0; i--) { 8741 dataPoint = this.dataPoints[i]; 8742 var surfaces = dataPoint.surfaces; 8743 if (surfaces) { 8744 for (var s = surfaces.length - 1; s >= 0; s--) { 8745 // split each surface in two triangles, and see if the center point is inside one of these 8746 var surface = surfaces[s]; 8747 var corners = surface.corners; 8748 var triangle1 = [corners[0].screen, corners[1].screen, corners[2].screen]; 8749 var triangle2 = [corners[2].screen, corners[3].screen, corners[0].screen]; 8750 if (this._insideTriangle(center, triangle1) || this._insideTriangle(center, triangle2)) { 8751 // return immediately at the first hit 8752 return dataPoint; 8753 } 8754 } 8755 } 8756 } 8757 } else { 8758 // find the closest data point, using distance to the center of the point on 2d screen 8759 for (i = 0; i < this.dataPoints.length; i++) { 8760 dataPoint = this.dataPoints[i]; 8761 var point = dataPoint.screen; 8762 if (point) { 8763 var distX = Math.abs(x - point.x); 8764 var distY = Math.abs(y - point.y); 8765 var dist = Math.sqrt(distX * distX + distY * distY); 8766
8767 if ((closestDist === null || dist < closestDist) && dist < distMax) { 8768 closestDist = dist; 8769 closestDataPoint = dataPoint; 8770 } 8771 } 8772 } 8773 } 8774 8775 return closestDataPoint; 8776 }; 8777 8778 /** 8779 * Display a tooltip for given data point 8780 * @param {Object} dataPoint 8781 * @private 8782 */ 8783 Graph3d.prototype._showTooltip = function (dataPoint) { 8784 var content, line, dot; 8785 8786 if (!this.tooltip) { 8787 content = document.createElement('div'); 8788 content.style.position = 'absolute'; 8789 content.style.padding = '10px'; 8790 content.style.border = '1px solid #4d4d4d'; 8791 content.style.color = '#1a1a1a'; 8792 content.style.background = 'rgba(255,255,255,0.7)'; 8793 content.style.borderRadius = '2px'; 8794 content.style.boxShadow = '5px 5px 10px rgba(128,128,128,0.5)'; 8795 8796 line = document.createElement('div'); 8797 line.style.position = 'absolute'; 8798 line.style.height = '40px'; 8799 line.style.width = '0'; 8800 line.style.borderLeft = '1px solid #4d4d4d'; 8801 8802 dot = document.createElement('div'); 8803 dot.style.position = 'absolute'; 8804 dot.style.height = '0'; 8805 dot.style.width = '0'; 8806 dot.style.border = '5px solid #4d4d4d'; 8807 dot.style.borderRadius = '5px'; 8808 8809 this.tooltip = { 8810 dataPoint: null, 8811 dom: { 8812 content: content, 8813 line: line, 8814 dot: dot 8815 } 8816 }; 8817 } else { 8818 content = this.tooltip.dom.content; 8819 line = this.tooltip.dom.line; 8820 dot = this.tooltip.dom.dot; 8821 } 8822 8823 this._hideTooltip(); 8824 8825 this.tooltip.dataPoint = dataPoint; 8826 if (typeof this.showTooltip === 'function') { 8827 content.innerHTML = this.showTooltip(dataPoint.point); 8828 } else { 8829 content.innerHTML = '<table>' + '<tr><td>x:</td><td>' + dataPoint.point.x + '</td></tr>' + '<tr><td>y:</td><td>' + dataPoint.point.y + '</td></tr>' + '<tr><td>z:</td><td>' + dataPoint.point.z + '</td></tr>' + '</table>'; 8830 } 8831 8832 content.style.left = '0'; 8833 content.style.top = '0'; 8834 this.frame.appendChild(content); 8835 this.frame.appendChild(line); 8836 this.frame.appendChild(dot); 8837 8838 // calculate sizes 8839 var contentWidth = content.offsetWidth; 8840 var contentHeight = content.offsetHeight; 8841 var lineHeight = line.offsetHeight; 8842 var dotWidth = dot.offsetWidth; 8843 var dotHeight = dot.offsetHeight; 8844 8845 var left = dataPoint.screen.x - contentWidth / 2; 8846 left = Math.min(Math.max(left, 10), this.frame.clientWidth - 10 - contentWidth); 8847 8848 line.style.left = dataPoint.screen.x + 'px'; 8849 line.style.top = dataPoint.screen.y - lineHeight + 'px'; 8850 content.style.left = left + 'px'; 8851 content.style.top = dataPoint.screen.y - lineHeight - contentHeight + 'px'; 8852 dot.style.left = dataPoint.screen.x - dotWidth / 2 + 'px'; 8853 dot.style.top = dataPoint.screen.y - dotHeight / 2 + 'px'; 8854 }; 8855 8856 /** 8857 * Hide the tooltip when displayed 8858 * @private 8859 */ 8860 Graph3d.prototype._hideTooltip = function () { 8861 if (this.tooltip) { 8862 this.tooltip.dataPoint = null; 8863 8864 for (var prop in this.tooltip.dom) { 8865 if (this.tooltip.dom.hasOwnProperty(prop)) { 8866 var elem = this.tooltip.dom[prop]; 8867 if (elem && elem.parentNode) { 8868 elem.parentNode.removeChild(elem); 8869 } 8870 } 8871 } 8872 } 8873 }; 8874 8875 /**--------------------------------------------------------------------------**/ 8876 8877 /** 8878 * Get the horizontal mouse position from a mouse event 8879 * @param {Event} event 8880 * @return {Number} mouse x 8881 */ 8882 function getMouseX(event) {
8883 if ('clientX' in event) return event.clientX; 8884 return event.targetTouches[0] && event.targetTouches[0].clientX || 0; 8885 } 8886 8887 /** 8888 * Get the vertical mouse position from a mouse event 8889 * @param {Event} event 8890 * @return {Number} mouse y 8891 */ 8892 function getMouseY(event) { 8893 if ('clientY' in event) return event.clientY; 8894 return event.targetTouches[0] && event.targetTouches[0].clientY || 0; 8895 } 8896 8897 module.exports = Graph3d; 8898 8899 // use use defaults 8900 8901/***/ }, 8902/* 12 */ 8903/***/ function(module, exports) { 8904 8905 8906 /** 8907 * Expose `Emitter`. 8908 */ 8909 8910 module.exports = Emitter; 8911 8912 /** 8913 * Initialize a new `Emitter`. 8914 * 8915 * @api public 8916 */ 8917 8918 function Emitter(obj) { 8919 if (obj) return mixin(obj); 8920 }; 8921 8922 /** 8923 * Mixin the emitter properties. 8924 * 8925 * @param {Object} obj 8926 * @return {Object} 8927 * @api private 8928 */ 8929 8930 function mixin(obj) { 8931 for (var key in Emitter.prototype) { 8932 obj[key] = Emitter.prototype[key]; 8933 } 8934 return obj; 8935 } 8936 8937 /** 8938 * Listen on the given `event` with `fn`. 8939 * 8940 * @param {String} event 8941 * @param {Function} fn 8942 * @return {Emitter} 8943 * @api public 8944 */ 8945 8946 Emitter.prototype.on = 8947 Emitter.prototype.addEventListener = function(event, fn){ 8948 this._callbacks = this._callbacks || {}; 8949 (this._callbacks[event] = this._callbacks[event] || []) 8950 .push(fn); 8951 return this; 8952 }; 8953 8954 /** 8955 * Adds an `event` listener that will be invoked a single 8956 * time then automatically removed. 8957 * 8958 * @param {String} event 8959 * @param {Function} fn 8960 * @return {Emitter} 8961 * @api public 8962 */ 8963 8964 Emitter.prototype.once = function(event, fn){ 8965 var self = this; 8966 this._callbacks = this._callbacks || {}; 8967 8968 function on() { 8969 self.off(event, on); 8970 fn.apply(this, arguments); 8971 } 8972 8973 on.fn = fn; 8974 this.on(event, on); 8975 return this; 8976 }; 8977 8978 /** 8979 * Remove the given callback for `event` or all 8980 * registered callbacks. 8981 * 8982 * @param {String} event 8983 * @param {Function} fn 8984 * @return {Emitter} 8985 * @api public 8986 */ 8987 8988 Emitter.prototype.off = 8989 Emitter.prototype.removeListener = 8990 Emitter.prototype.removeAllListeners = 8991 Emitter.prototype.removeEventListener = function(event, fn){ 8992 this._callbacks = this._callbacks || {}; 8993 8994 // all 8995 if (0 == arguments.length) { 8996 this._callbacks = {}; 8997 return this; 8998 } 8999 9000 // specific event 9001 var callbacks = this._callbacks[event]; 9002 if (!callbacks) return this; 9003 9004 // remove all handlers 9005 if (1 == arguments.length) { 9006 delete this._callbacks[event]; 9007 return this; 9008 } 9009 9010 // remove specific handler 9011 var cb; 9012 for (var i = 0; i < callbacks.length; i++) { 9013 cb = callbacks[i]; 9014 if (cb === fn || cb.fn === fn) { 9015 callbacks.splice(i, 1); 9016 break; 9017 } 9018 } 9019 return this; 9020 }; 9021 9022 /** 9023 * Emit `event` with the given args. 9024 * 9025 * @param {String} event 9026 * @param {Mixed} ... 9027 * @return {Emitter} 9028 */ 9029 9030 Emitter.prototype.emit = function(event){ 9031 this._callbacks = this._callbacks || {}; 9032 var args = [].slice.call(arguments, 1) 9033 , callbacks = this._callbacks[event]; 9034 9035 if (callbacks) { 9036 callbacks = callbacks.slice(0); 9037 for (var i = 0, len = callbacks.length; i < len; ++i) { 9038 callbacks[i].apply(this, args); 9039 } 9040 } 9041 9042 return this; 9043 }; 9044 9045 /** 9046 * Return array of callbacks for `event`. 9047 * 9048 * @param {String} event 9049 * @return {Array} 9050 * @api public 9051 */ 9052 9053 Emitter.prototype.listeners = function(event){ 9054 this._callbacks = this._callbacks || {}; 9055 return this._callbacks[event] || []; 9056 }; 9057 9058 /** 9059 * Check if this emitter has `event` handlers. 9060 * 9061 * @param {String} event 9062 * @return {Boolean} 9063 * @api public 9064 */ 9065 9066 Emitter.prototype.hasListeners = function(event){ 9067 return !! this.listeners(event).length; 9068 }; 9069 9070 9071/***/ }, 9072/* 13 */ 9073/***/ function(module, exports) { 9074 9075 /** 9076 * @prototype Point3d 9077 * @param {Number} [x] 9078 * @param {Number} [y] 9079 * @param {Number} [z] 9080 */ 9081 "use strict"; 9082 9083 function Point3d(x, y, z) { 9084 this.x = x !== undefined ? x : 0; 9085 this.y = y !== undefined ? y : 0; 9086 this.z = z !== undefined ? z : 0; 9087 }; 9088 9089 /** 9090 * Subtract the two provided points, returns a-b 9091 * @param {Point3d} a 9092 * @param {Point3d} b 9093 * @return {Point3d} a-b 9094 */ 9095 Point3d.subtract = function (a, b) { 9096 var sub = new Point3d(); 9097 sub.x = a.x - b.x; 9098 sub.y = a.y - b.y; 9099 sub.z = a.z - b.z; 9100 return sub; 9101 }; 9102 9103 /** 9104 * Add the two provided points, returns a+b 9105 * @param {Point3d} a 9106 * @param {Point3d} b 9107 * @return {Point3d} a+b 9108 */ 9109 Point3d.add = function (a, b) { 9110 var sum = new Point3d(); 9111 sum.x = a.x + b.x; 9112 sum.y = a.y + b.y; 9113 sum.z = a.z + b.z; 9114 return sum; 9115 }; 9116 9117 /** 9118 * Calculate the average of two 3d points 9119 * @param {Point3d} a 9120 * @param {Point3d} b 9121 * @return {Point3d} The average, (a+b)/2 9122 */ 9123 Point3d.avg = function (a, b) { 9124 return new Point3d((a.x + b.x) / 2, (a.y + b.y) / 2, (a.z + b.z) / 2); 9125 }; 9126 9127 /** 9128 * Calculate the cross product of the two provided points, returns axb 9129 * Documentation: http://en.wikipedia.org/wiki/Cross_product 9130 * @param {Point3d} a 9131 * @param {Point3d} b 9132 * @return {Point3d} cross product axb 9133 */ 9134 Point3d.crossProduct = function (a, b) { 9135 var crossproduct = new Point3d(); 9136 9137 crossproduct.x = a.y * b.z - a.z * b.y; 9138 crossproduct.y = a.z * b.x - a.x * b.z; 9139 crossproduct.z = a.x * b.y - a.y * b.x; 9140 9141 return crossproduct; 9142 }; 9143 9144 /** 9145 * Rtrieve the length of the vector (or the distance from this point to the origin 9146 * @return {Number} length 9147 */ 9148 Point3d.prototype.length = function () { 9149 return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); 9150 }; 9151 9152 module.exports = Point3d; 9153 9154/***/ }, 9155/* 14 */
9156/***/ function(module, exports) { 9157 9158 /** 9159 * @prototype Point2d 9160 * @param {Number} [x] 9161 * @param {Number} [y] 9162 */ 9163 "use strict"; 9164 9165 function Point2d(x, y) { 9166 this.x = x !== undefined ? x : 0; 9167 this.y = y !== undefined ? y : 0; 9168 } 9169 9170 module.exports = Point2d; 9171 9172/***/ }, 9173/* 15 */ 9174/***/ function(module, exports, __webpack_require__) { 9175 9176 'use strict'; 9177 9178 var Point3d = __webpack_require__(13); 9179 9180 /** 9181 * @class Camera 9182 * The camera is mounted on a (virtual) camera arm. The camera arm can rotate 9183 * The camera is always looking in the direction of the origin of the arm. 9184 * This way, the camera always rotates around one fixed point, the location 9185 * of the camera arm. 9186 * 9187 * Documentation: 9188 * http://en.wikipedia.org/wiki/3D_projection 9189 */ 9190 function Camera() { 9191 this.armLocation = new Point3d(); 9192 this.armRotation = {}; 9193 this.armRotation.horizontal = 0; 9194 this.armRotation.vertical = 0; 9195 this.armLength = 1.7; 9196 9197 this.cameraLocation = new Point3d(); 9198 this.cameraRotation = new Point3d(0.5 * Math.PI, 0, 0); 9199 9200 this.calculateCameraOrientation(); 9201 } 9202 9203 /** 9204 * Set the location (origin) of the arm 9205 * @param {Number} x Normalized value of x 9206 * @param {Number} y Normalized value of y 9207 * @param {Number} z Normalized value of z 9208 */ 9209 Camera.prototype.setArmLocation = function (x, y, z) { 9210 this.armLocation.x = x; 9211 this.armLocation.y = y; 9212 this.armLocation.z = z; 9213 9214 this.calculateCameraOrientation(); 9215 }; 9216 9217 /** 9218 * Set the rotation of the camera arm 9219 * @param {Number} horizontal The horizontal rotation, between 0 and 2*PI. 9220 * Optional, can be left undefined. 9221 * @param {Number} vertical The vertical rotation, between 0 and 0.5*PI 9222 * if vertical=0.5*PI, the graph is shown from the 9223 * top. Optional, can be left undefined. 9224 */ 9225 Camera.prototype.setArmRotation = function (horizontal, vertical) { 9226 if (horizontal !== undefined) { 9227 this.armRotation.horizontal = horizontal; 9228 } 9229 9230 if (vertical !== undefined) { 9231 this.armRotation.vertical = vertical; 9232 if (this.armRotation.vertical < 0) this.armRotation.vertical = 0; 9233 if (this.armRotation.vertical > 0.5 * Math.PI) this.armRotation.vertical = 0.5 * Math.PI; 9234 } 9235 9236 if (horizontal !== undefined || vertical !== undefined) { 9237 this.calculateCameraOrientation(); 9238 } 9239 }; 9240 9241 /** 9242 * Retrieve the current arm rotation 9243 * @return {object} An object with parameters horizontal and vertical 9244 */ 9245 Camera.prototype.getArmRotation = function () { 9246 var rot = {}; 9247 rot.horizontal = this.armRotation.horizontal; 9248 rot.vertical = this.armRotation.vertical; 9249 9250 return rot; 9251 }; 9252 9253 /** 9254 * Set the (normalized) length of the camera arm. 9255 * @param {Number} length A length between 0.71 and 5.0 9256 */ 9257 Camera.prototype.setArmLength = function (length) { 9258 if (length === undefined) return; 9259 9260 this.armLength = length; 9261 9262 // Radius must be larger than the corner of the graph, 9263 // which has a distance of sqrt(0.5^2+0.5^2) = 0.71 from the center of the 9264 // graph 9265 if (this.armLength < 0.71) this.armLength = 0.71; 9266 if (this.armLength > 5.0) this.armLength = 5.0; 9267 9268 this.calculateCameraOrientation(); 9269 }; 9270 9271 /** 9272 * Retrieve the arm length 9273 * @return {Number} length 9274 */ 9275 Camera.prototype.getArmLength = function () { 9276 return this.armLength; 9277 }; 9278 9279 /** 9280 * Retrieve the camera location 9281 * @return {Point3d} cameraLocation 9282 */ 9283 Camera.prototype.getCameraLocation = function () { 9284 return this.cameraLocation; 9285 }; 9286 9287 /** 9288 * Retrieve the camera rotation 9289 * @return {Point3d} cameraRotation 9290 */ 9291 Camera.prototype.getCameraRotation = function () { 9292 return this.cameraRotation; 9293 }; 9294 9295 /** 9296 * Calculate the location and rotation of the camera based on the 9297 * position and orientation of the camera arm 9298 */ 9299 Camera.prototype.calculateCameraOrientation = function () { 9300 // calculate location of the camera 9301 this.cameraLocation.x = this.armLocation.x - this.armLength * Math.sin(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical); 9302 this.cameraLocation.y = this.armLocation.y - this.armLength * Math.cos(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical); 9303 this.cameraLocation.z = this.armLocation.z + this.armLength * Math.sin(this.armRotation.vertical); 9304 9305 // calculate rotation of the camera 9306 this.cameraRotation.x = Math.PI / 2 - this.armRotation.vertical; 9307 this.cameraRotation.y = 0; 9308 this.cameraRotation.z = -this.armRotation.horizontal; 9309 }; 9310 9311 module.exports = Camera; 9312 9313/***/ }, 9314/* 16 */ 9315/***/ function(module, exports, __webpack_require__) { 9316 9317 'use strict'; 9318 9319 var DataView = __webpack_require__(10); 9320 9321 /** 9322 * @class Filter 9323 * 9324 * @param {DataSet} data The google data table 9325 * @param {Number} column The index of the column to be filtered 9326 * @param {Graph} graph The graph 9327 */ 9328 function Filter(data, column, graph) { 9329 this.data = data; 9330 this.column = column; 9331 this.graph = graph; // the parent graph 9332 9333 this.index = undefined; 9334 this.value = undefined; 9335 9336 // read all distinct values and select the first one 9337 this.values = graph.getDistinctValues(data.get(), this.column); 9338 9339 // sort both numeric and string values correctly 9340 this.values.sort(function (a, b) { 9341 return a > b ? 1 : a < b ? -1 : 0; 9342 }); 9343 9344 if (this.values.length > 0) { 9345 this.selectValue(0); 9346 } 9347 9348 // create an array with the filtered datapoints. this will be loaded afterwards 9349 this.dataPoints = []; 9350 9351 this.loaded = false;
9352 this.onLoadCallback = undefined; 9353 9354 if (graph.animationPreload) { 9355 this.loaded = false; 9356 this.loadInBackground(); 9357 } else { 9358 this.loaded = true; 9359 } 9360 }; 9361 9362 /** 9363 * Return the label 9364 * @return {string} label 9365 */ 9366 Filter.prototype.isLoaded = function () { 9367 return this.loaded; 9368 }; 9369 9370 /** 9371 * Return the loaded progress 9372 * @return {Number} percentage between 0 and 100 9373 */ 9374 Filter.prototype.getLoadedProgress = function () { 9375 var len = this.values.length; 9376 9377 var i = 0; 9378 while (this.dataPoints[i]) { 9379 i++; 9380 } 9381 9382 return Math.round(i / len * 100); 9383 }; 9384 9385 /** 9386 * Return the label 9387 * @return {string} label 9388 */ 9389 Filter.prototype.getLabel = function () { 9390 return this.graph.filterLabel; 9391 }; 9392 9393 /** 9394 * Return the columnIndex of the filter 9395 * @return {Number} columnIndex 9396 */ 9397 Filter.prototype.getColumn = function () { 9398 return this.column; 9399 }; 9400 9401 /** 9402 * Return the currently selected value. Returns undefined if there is no selection 9403 * @return {*} value 9404 */ 9405 Filter.prototype.getSelectedValue = function () { 9406 if (this.index === undefined) return undefined; 9407 9408 return this.values[this.index]; 9409 }; 9410 9411 /** 9412 * Retrieve all values of the filter 9413 * @return {Array} values 9414 */ 9415 Filter.prototype.getValues = function () { 9416 return this.values; 9417 }; 9418 9419 /** 9420 * Retrieve one value of the filter 9421 * @param {Number} index 9422 * @return {*} value 9423 */ 9424 Filter.prototype.getValue = function (index) { 9425 if (index >= this.values.length) throw 'Error: index out of range'; 9426 9427 return this.values[index]; 9428 }; 9429 9430 /** 9431 * Retrieve the (filtered) dataPoints for the currently selected filter index 9432 * @param {Number} [index] (optional) 9433 * @return {Array} dataPoints 9434 */ 9435 Filter.prototype._getDataPoints = function (index) { 9436 if (index === undefined) index = this.index; 9437 9438 if (index === undefined) return []; 9439 9440 var dataPoints; 9441 if (this.dataPoints[index]) { 9442 dataPoints = this.dataPoints[index]; 9443 } else { 9444 var f = {}; 9445 f.column = this.column; 9446 f.value = this.values[index]; 9447 9448 var dataView = new DataView(this.data, { filter: function filter(item) { 9449 return item[f.column] == f.value; 9450 } }).get(); 9451 dataPoints = this.graph._getDataPoints(dataView); 9452 9453 this.dataPoints[index] = dataPoints; 9454 } 9455 9456 return dataPoints; 9457 }; 9458 9459 /** 9460 * Set a callback function when the filter is fully loaded. 9461 */ 9462 Filter.prototype.setOnLoadCallback = function (callback) { 9463 this.onLoadCallback = callback; 9464 }; 9465 9466 /** 9467 * Add a value to the list with available values for this filter 9468 * No double entries will be created. 9469 * @param {Number} index 9470 */ 9471 Filter.prototype.selectValue = function (index) { 9472 if (index >= this.values.length) throw 'Error: index out of range'; 9473 9474 this.index = index; 9475 this.value = this.values[index]; 9476 }; 9477 9478 /** 9479 * Load all filtered rows in the background one by one 9480 * Start this method without providing an index! 9481 */ 9482 Filter.prototype.loadInBackground = function (index) { 9483 if (index === undefined) index = 0; 9484 9485 var frame = this.graph.frame; 9486 9487 if (index < this.values.length) { 9488 var dataPointsTemp = this._getDataPoints(index); 9489 //this.graph.redrawInfo(); // TODO: not neat 9490 9491 // create a progress box 9492 if (frame.progress === undefined) { 9493 frame.progress = document.createElement('DIV'); 9494 frame.progress.style.position = 'absolute'; 9495 frame.progress.style.color = 'gray'; 9496 frame.appendChild(frame.progress); 9497 } 9498 var progress = this.getLoadedProgress(); 9499 frame.progress.innerHTML = 'Loading animation... ' + progress + '%'; 9500 // TODO: this is no nice solution... 9501 frame.progress.style.bottom = 60 + 'px'; // TODO: use height of slider 9502 frame.progress.style.left = 10 + 'px'; 9503 9504 var me = this; 9505 setTimeout(function () { 9506 me.loadInBackground(index + 1); 9507 }, 10); 9508 this.loaded = false; 9509 } else { 9510 this.loaded = true; 9511 9512 // remove the progress box 9513 if (frame.progress !== undefined) { 9514 frame.removeChild(frame.progress); 9515 frame.progress = undefined; 9516 } 9517 9518 if (this.onLoadCallback) this.onLoadCallback(); 9519 } 9520 }; 9521 9522 module.exports = Filter; 9523 9524/***/ }, 9525/* 17 */ 9526/***/ function(module, exports, __webpack_require__) { 9527 9528 'use strict'; 9529 9530 var util = __webpack_require__(1); 9531 9532 /** 9533 * @constructor Slider 9534 * 9535 * An html slider control with start/stop/prev/next buttons 9536 * @param {Element} container The element where the slider will be created 9537 * @param {Object} options Available options: 9538 * {boolean} visible If true (default) the 9539 * slider is visible. 9540 */ 9541 function Slider(container, options) { 9542 if (container === undefined) { 9543 throw 'Error: No container element defined'; 9544 } 9545 this.container = container; 9546 this.visible = options && options.visible != undefined ? options.visible : true; 9547 9548 if (this.visible) { 9549 this.frame = document.createElement('DIV'); 9550 //this.frame.style.backgroundColor = '#E5E5E5'; 9551 this.frame.style.width = '100%'; 9552 this.frame.style.position = 'relative'; 9553 this.container.appendChild(this.frame); 9554 9555 this.frame.prev = document.createElement('INPUT'); 9556 this.frame.prev.type = 'BUTTON'; 9557 this.frame.prev.value = 'Prev'; 9558 this.frame.appendChild(this.frame.prev); 9559 9560 this.frame.play = document.createElement('INPUT'); 9561 this.frame.play.type = 'BUTTON'; 9562 this.frame.play.value = 'Play'; 9563 this.frame.appendChild(this.frame.play); 9564 9565 this.frame.next = document.createElement('INPUT'); 9566 this.frame.next.type = 'BUTTON'; 9567 this.frame.next.value = 'Next'; 9568 this.frame.appendChild(this.frame.next); 9569 9570 this.frame.bar = document.createElement('INPUT'); 9571 this.frame.bar.type = 'BUTTON'; 9572 this.frame.bar.style.position = 'absolute'; 9573 this.frame.bar.style.border = '1px solid red'; 9574 this.frame.bar.style.width = '100px'; 9575 this.frame.bar.style.height = '6px'; 9576 this.frame.bar.style.borderRadius = '2px'; 9577 this.frame.bar.style.MozBorderRadius = '2px'; 9578 this.frame.bar.style.border = '1px solid #7F7F7F'; 9579 this.frame.bar.style.backgroundColor = '#E5E5E5'; 9580 this.frame.appendChild(this.frame.bar); 9581 9582 this.frame.slide = document.createElement('INPUT'); 9583 this.frame.slide.type = 'BUTTON'; 9584 this.frame.slide.style.margin = '0px'; 9585 this.frame.slide.value = ' '; 9586 this.frame.slide.style.position = 'relative'; 9587 this.frame.slide.style.left = '-100px'; 9588 this.frame.appendChild(this.frame.slide); 9589 9590 // create events 9591 var me = this; 9592 this.frame.slide.onmousedown = function (event) { 9593 me._onMouseDown(event); 9594 }; 9595 this.frame.prev.onclick = function (event) { 9596 me.prev(event); 9597 }; 9598 this.frame.play.onclick = function (event) { 9599 me.togglePlay(event); 9600 }; 9601 this.frame.next.onclick = function (event) { 9602 me.next(event); 9603 }; 9604 } 9605 9606 this.onChangeCallback = undefined; 9607 9608 this.values = []; 9609 this.index = undefined; 9610 9611 this.playTimeout = undefined; 9612 this.playInterval = 1000; // milliseconds 9613 this.playLoop = true; 9614 } 9615 9616 /** 9617 * Select the previous index 9618 */ 9619 Slider.prototype.prev = function () { 9620 var index = this.getIndex(); 9621 if (index > 0) { 9622 index--; 9623 this.setIndex(index); 9624 } 9625 }; 9626 9627 /** 9628 * Select the next index 9629 */ 9630 Slider.prototype.next = function () { 9631 var index = this.getIndex(); 9632 if (index < this.values.length - 1) { 9633 index++; 9634 this.setIndex(index); 9635 } 9636 }; 9637 9638 /** 9639 * Select the next index 9640 */ 9641 Slider.prototype.playNext = function () { 9642 var start = new Date(); 9643 9644 var index = this.getIndex(); 9645 if (index < this.values.length - 1) { 9646 index++; 9647 this.setIndex(index); 9648 } else if (this.playLoop) { 9649 // jump to the start 9650 index = 0; 9651 this.setIndex(index); 9652 } 9653 9654 var end = new Date(); 9655 var diff = end - start; 9656 9657 // calculate how much time it to to set the index and to execute the callback 9658 // function. 9659 var interval = Math.max(this.playInterval - diff, 0); 9660 // document.title = diff // TODO: cleanup 9661 9662 var me = this; 9663 this.playTimeout = setTimeout(function () { 9664 me.playNext(); 9665 }, interval); 9666 }; 9667 9668 /** 9669 * Toggle start or stop playing 9670 */ 9671 Slider.prototype.togglePlay = function () { 9672 if (this.playTimeout === undefined) { 9673 this.play(); 9674 } else { 9675 this.stop(); 9676 } 9677 }; 9678 9679 /** 9680 * Start playing 9681 */ 9682 Slider.prototype.play = function () { 9683 // Test whether already playing 9684 if (this.playTimeout) return; 9685 9686 this.playNext(); 9687 9688 if (this.frame) { 9689 this.frame.play.value = 'Stop'; 9690 } 9691 }; 9692 9693 /** 9694 * Stop playing 9695 */ 9696 Slider.prototype.stop = function () { 9697 clearInterval(this.playTimeout); 9698 this.playTimeout = undefined; 9699 9700 if (this.frame) { 9701 this.frame.play.value = 'Play'; 9702 } 9703 }; 9704 9705 /** 9706 * Set a callback function which will be triggered when the value of the 9707 * slider bar has changed. 9708 */ 9709 Slider.prototype.setOnChangeCallback = function (callback) { 9710 this.onChangeCallback = callback; 9711 }; 9712 9713 /** 9714 * Set the interval for playing the list 9715 * @param {Number} interval The interval in milliseconds 9716 */ 9717 Slider.prototype.setPlayInterval = function (interval) { 9718 this.playInterval = interval; 9719 }; 9720 9721 /** 9722 * Retrieve the current play interval 9723 * @return {Number} interval The interval in milliseconds 9724 */ 9725 Slider.prototype.getPlayInterval = function (interval) { 9726 return this.playInterval; 9727 }; 9728 9729 /** 9730 * Set looping on or off 9731 * @pararm {boolean} doLoop If true, the slider will jump to the start when 9732 * the end is passed, and will jump to the end 9733 * when the start is passed. 9734 */ 9735 Slider.prototype.setPlayLoop = function (doLoop) { 9736 this.playLoop = doLoop; 9737 }; 9738 9739 /**
9740 * Execute the onchange callback function 9741 */ 9742 Slider.prototype.onChange = function () { 9743 if (this.onChangeCallback !== undefined) { 9744 this.onChangeCallback(); 9745 } 9746 }; 9747 9748 /** 9749 * redraw the slider on the correct place 9750 */ 9751 Slider.prototype.redraw = function () { 9752 if (this.frame) { 9753 // resize the bar 9754 this.frame.bar.style.top = this.frame.clientHeight / 2 - this.frame.bar.offsetHeight / 2 + 'px'; 9755 this.frame.bar.style.width = this.frame.clientWidth - this.frame.prev.clientWidth - this.frame.play.clientWidth - this.frame.next.clientWidth - 30 + 'px'; 9756 9757 // position the slider button 9758 var left = this.indexToLeft(this.index); 9759 this.frame.slide.style.left = left + 'px'; 9760 } 9761 }; 9762 9763 /** 9764 * Set the list with values for the slider 9765 * @param {Array} values A javascript array with values (any type) 9766 */ 9767 Slider.prototype.setValues = function (values) { 9768 this.values = values; 9769 9770 if (this.values.length > 0) this.setIndex(0);else this.index = undefined; 9771 }; 9772 9773 /** 9774 * Select a value by its index 9775 * @param {Number} index 9776 */ 9777 Slider.prototype.setIndex = function (index) { 9778 if (index < this.values.length) { 9779 this.index = index; 9780 9781 this.redraw(); 9782 this.onChange(); 9783 } else { 9784 throw 'Error: index out of range'; 9785 } 9786 }; 9787 9788 /** 9789 * retrieve the index of the currently selected vaue 9790 * @return {Number} index 9791 */ 9792 Slider.prototype.getIndex = function () { 9793 return this.index; 9794 }; 9795 9796 /** 9797 * retrieve the currently selected value 9798 * @return {*} value 9799 */ 9800 Slider.prototype.get = function () { 9801 return this.values[this.index]; 9802 }; 9803 9804 Slider.prototype._onMouseDown = function (event) { 9805 // only react on left mouse button down 9806 var leftButtonDown = event.which ? event.which === 1 : event.button === 1; 9807 if (!leftButtonDown) return; 9808 9809 this.startClientX = event.clientX; 9810 this.startSlideX = parseFloat(this.frame.slide.style.left); 9811 9812 this.frame.style.cursor = 'move'; 9813 9814 // add event listeners to handle moving the contents 9815 // we store the function onmousemove and onmouseup in the graph, so we can 9816 // remove the eventlisteners lateron in the function mouseUp() 9817 var me = this; 9818 this.onmousemove = function (event) { 9819 me._onMouseMove(event); 9820 }; 9821 this.onmouseup = function (event) { 9822 me._onMouseUp(event); 9823 }; 9824 util.addEventListener(document, 'mousemove', this.onmousemove); 9825 util.addEventListener(document, 'mouseup', this.onmouseup); 9826 util.preventDefault(event); 9827 }; 9828 9829 Slider.prototype.leftToIndex = function (left) { 9830 var width = parseFloat(this.frame.bar.style.width) - this.frame.slide.clientWidth - 10; 9831 var x = left - 3; 9832 9833 var index = Math.round(x / width * (this.values.length - 1)); 9834 if (index < 0) index = 0; 9835 if (index > this.values.length - 1) index = this.values.length - 1; 9836 9837 return index; 9838 }; 9839 9840 Slider.prototype.indexToLeft = function (index) { 9841 var width = parseFloat(this.frame.bar.style.width) - this.frame.slide.clientWidth - 10; 9842 9843 var x = index / (this.values.length - 1) * width; 9844 var left = x + 3; 9845 9846 return left; 9847 }; 9848 9849 Slider.prototype._onMouseMove = function (event) { 9850 var diff = event.clientX - this.startClientX; 9851 var x = this.startSlideX + diff; 9852 9853 var index = this.leftToIndex(x); 9854 9855 this.setIndex(index); 9856 9857 util.preventDefault(); 9858 }; 9859 9860 Slider.prototype._onMouseUp = function (event) { 9861 this.frame.style.cursor = 'auto'; 9862 9863 // remove event listeners 9864 util.removeEventListener(document, 'mousemove', this.onmousemove); 9865 util.removeEventListener(document, 'mouseup', this.onmouseup); 9866 9867 util.preventDefault(); 9868 }; 9869 9870 module.exports = Slider; 9871 9872/***/ }, 9873/* 18 */ 9874/***/ function(module, exports) { 9875 9876 /** 9877 * @prototype StepNumber 9878 * The class StepNumber is an iterator for Numbers. You provide a start and end 9879 * value, and a best step size. StepNumber itself rounds to fixed values and 9880 * a finds the step that best fits the provided step. 9881 * 9882 * If prettyStep is true, the step size is chosen as close as possible to the 9883 * provided step, but being a round value like 1, 2, 5, 10, 20, 50, .... 9884 * 9885 * Example usage: 9886 * var step = new StepNumber(0, 10, 2.5, true); 9887 * step.start(); 9888 * while (!step.end()) { 9889 * alert(step.getCurrent()); 9890 * step.next(); 9891 * } 9892 * 9893 * Version: 1.0 9894 * 9895 * @param {Number} start The start value 9896 * @param {Number} end The end value 9897 * @param {Number} step Optional. Step size. Must be a positive value. 9898 * @param {boolean} prettyStep Optional. If true, the step size is rounded 9899 * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...) 9900 */ 9901 "use strict"; 9902 9903 function StepNumber(start, end, step, prettyStep) { 9904 // set default values 9905 this._start = 0; 9906 this._end = 0; 9907 this._step = 1; 9908 this.prettyStep = true; 9909 this.precision = 5; 9910 9911 this._current = 0; 9912 this.setRange(start, end, step, prettyStep); 9913 }; 9914 9915 /** 9916 * Set a new range: start, end and step. 9917 * 9918 * @param {Number} start The start value 9919 * @param {Number} end The end value 9920 * @param {Number} step Optional. Step size. Must be a positive value. 9921 * @param {boolean} prettyStep Optional. If true, the step size is rounded 9922 * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...) 9923 */ 9924 StepNumber.prototype.setRange = function (start, end, step, prettyStep) { 9925 this._start = start ? start : 0; 9926 this._end = end ? end : 0; 9927 9928 this.setStep(step, prettyStep); 9929 }; 9930 9931 /** 9932 * Set a new step size 9933 * @param {Number} step New step size. Must be a positive value 9934 * @param {boolean} prettyStep Optional. If true, the provided step is rounded 9935 * to a pretty step size (like 1, 2, 5, 10, 20, 50, ...) 9936 */ 9937 StepNumber.prototype.setStep = function (step, prettyStep) { 9938 if (step === undefined || step <= 0) return; 9939 9940 if (prettyStep !== undefined) this.prettyStep = prettyStep; 9941 9942 if (this.prettyStep === true) this._step = StepNumber.calculatePrettyStep(step);else this._step = step; 9943 }; 9944 9945 /** 9946 * Calculate a nice step size, closest to the desired step size. 9947 * Returns a value in one of the ranges 1*10^n, 2*10^n, or 5*10^n, where n is an 9948 * integer Number. For example 1, 2, 5, 10, 20, 50, etc... 9949 * @param {Number} step Desired step size 9950 * @return {Number} Nice step size 9951 */ 9952 StepNumber.calculatePrettyStep = function (step) { 9953 var log10 = function log10(x) { 9954 return Math.log(x) / Math.LN10; 9955 }; 9956 9957 // try three steps (multiple of 1, 2, or 5 9958 var step1 = Math.pow(10, Math.round(log10(step))), 9959 step2 = 2 * Math.pow(10, Math.round(log10(step / 2))), 9960 step5 = 5 * Math.pow(10, Math.round(log10(step / 5))); 9961 9962 // choose the best step (closest to minimum step) 9963 var prettyStep = step1; 9964 if (Math.abs(step2 - step) <= Math.abs(prettyStep - step)) prettyStep = step2; 9965 if (Math.abs(step5 - step) <= Math.abs(prettyStep - step)) prettyStep = step5; 9966 9967 // for safety 9968 if (prettyStep <= 0) { 9969 prettyStep = 1; 9970 } 9971 9972 return prettyStep; 9973 }; 9974 9975 /** 9976 * returns the current value of the step 9977 * @return {Number} current value 9978 */ 9979 StepNumber.prototype.getCurrent = function () { 9980 return parseFloat(this._current.toPrecision(this.precision)); 9981 }; 9982 9983 /** 9984 * returns the current step size 9985 * @return {Number} current step size 9986 */ 9987 StepNumber.prototype.getStep = function () { 9988 return this._step; 9989 }; 9990 9991 /** 9992 * Set the current value to the largest value smaller than start, which 9993 * is a multiple of the step size 9994 */ 9995 StepNumber.prototype.start = function () { 9996 this._current = this._start - this._start % this._step; 9997 }; 9998 9999 /** 10000 * Do a step, add the step size to the current value 10001 */ 10002 StepNumber.prototype.next = function () { 10003 this._current += this._step; 10004 }; 10005 10006 /** 10007 * Returns true whether the end is reached 10008 * @return {boolean} True if the current value has passed the end value. 10009 */ 10010 StepNumber.prototype.end = function () { 10011 return this._current > this._end; 10012 }; 10013 10014 module.exports = StepNumber; 10015 10016/***/ }, 10017/* 19 */ 10018/***/ function(module, exports, __webpack_require__) { 10019 10020 'use strict'; 10021 10022 var Emitter = __webpack_require__(12); 10023 var Hammer = __webpack_require__(20); 10024 var moment = __webpack_require__(2); 10025 var util = __webpack_require__(1); 10026 var DataSet = __webpack_require__(8); 10027 var DataView = __webpack_require__(10); 10028 var Range = __webpack_require__(24); 10029 var Core = __webpack_require__(28);
10030 var TimeAxis = __webpack_require__(39); 10031 var CurrentTime = __webpack_require__(44); 10032 var CustomTime = __webpack_require__(42); 10033 var ItemSet = __webpack_require__(29); 10034 10035 var Configurator = __webpack_require__(45); 10036 var Validator = __webpack_require__(47)['default']; 10037 var printStyle = __webpack_require__(47).printStyle; 10038 var allOptions = __webpack_require__(48).allOptions; 10039 var configureOptions = __webpack_require__(48).configureOptions; 10040 10041 /** 10042 * Create a timeline visualization 10043 * @param {HTMLElement} container 10044 * @param {vis.DataSet | vis.DataView | Array} [items] 10045 * @param {vis.DataSet | vis.DataView | Array} [groups] 10046 * @param {Object} [options] See Timeline.setOptions for the available options. 10047 * @constructor 10048 * @extends Core 10049 */ 10050 function Timeline(container, items, groups, options) { 10051 if (!(this instanceof Timeline)) { 10052 throw new SyntaxError('Constructor must be called with the new operator'); 10053 } 10054 10055 // if the third element is options, the forth is groups (optionally); 10056 if (!(Array.isArray(groups) || groups instanceof DataSet || groups instanceof DataView) && groups instanceof Object) { 10057 var forthArgument = options; 10058 options = groups; 10059 groups = forthArgument; 10060 } 10061 10062 var me = this; 10063 this.defaultOptions = { 10064 start: null, 10065 end: null, 10066 10067 autoResize: true, 10068 throttleRedraw: 0, // ms 10069 10070 orientation: { 10071 axis: 'bottom', // axis orientation: 'bottom', 'top', or 'both' 10072 item: 'bottom' // not relevant 10073 }, 10074 10075 moment: moment, 10076 10077 width: null, 10078 height: null, 10079 maxHeight: null, 10080 minHeight: null 10081 }; 10082 this.options = util.deepExtend({}, this.defaultOptions); 10083 10084 // Create the DOM, props, and emitter 10085 this._create(container); 10086 10087 // all components listed here will be repainted automatically 10088 this.components = []; 10089 10090 this.body = { 10091 dom: this.dom, 10092 domProps: this.props, 10093 emitter: { 10094 on: this.on.bind(this), 10095 off: this.off.bind(this), 10096 emit: this.emit.bind(this) 10097 }, 10098 hiddenDates: [], 10099 util: { 10100 getScale: function getScale() { 10101 return me.timeAxis.step.scale; 10102 }, 10103 getStep: function getStep() { 10104 return me.timeAxis.step.step; 10105 }, 10106 10107 toScreen: me._toScreen.bind(me), 10108 toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width 10109 toTime: me._toTime.bind(me), 10110 toGlobalTime: me._toGlobalTime.bind(me) 10111 } 10112 }; 10113 10114 // range 10115 this.range = new Range(this.body); 10116 this.components.push(this.range); 10117 this.body.range = this.range; 10118 10119 // time axis 10120 this.timeAxis = new TimeAxis(this.body); 10121 this.timeAxis2 = null; // used in case of orientation option 'both' 10122 this.components.push(this.timeAxis); 10123 10124 // current time bar 10125 this.currentTime = new CurrentTime(this.body); 10126 this.components.push(this.currentTime); 10127 10128 // item set 10129 this.itemSet = new ItemSet(this.body); 10130 this.components.push(this.itemSet); 10131 10132 this.itemsData = null; // DataSet 10133 this.groupsData = null; // DataSet 10134 10135 this.on('tap', function (event) { 10136 me.emit('click', me.getEventProperties(event)); 10137 }); 10138 this.on('doubletap', function (event) { 10139 me.emit('doubleClick', me.getEventProperties(event)); 10140 }); 10141 this.dom.root.oncontextmenu = function (event) { 10142 me.emit('contextmenu', me.getEventProperties(event)); 10143 }; 10144 10145 // apply options 10146 if (options) { 10147 this.setOptions(options); 10148 } 10149 10150 // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS! 10151 if (groups) { 10152 this.setGroups(groups); 10153 } 10154 10155 // create itemset 10156 if (items) { 10157 this.setItems(items); 10158 } else { 10159 this._redraw(); 10160 } 10161 } 10162 10163 // Extend the functionality from Core 10164 Timeline.prototype = new Core(); 10165 10166 /** 10167 * Load a configurator 10168 * @return {Object} 10169 * @private 10170 */ 10171 Timeline.prototype._createConfigurator = function () { 10172 return new Configurator(this, this.dom.container, configureOptions); 10173 }; 10174 10175 /** 10176 * Force a redraw. The size of all items will be recalculated. 10177 * Can be useful to manually redraw when option autoResize=false and the window 10178 * has been resized, or when the items CSS has been changed. 10179 * 10180 * Note: this function will be overridden on construction with a trottled version 10181 */ 10182 Timeline.prototype.redraw = function () { 10183 this.itemSet && this.itemSet.markDirty({ refreshItems: true }); 10184 this._redraw(); 10185 }; 10186 10187 Timeline.prototype.setOptions = function (options) { 10188 // validate options 10189 var errorFound = Validator.validate(options, allOptions); 10190 if (errorFound === true) { 10191 console.log('%cErrors have been found in the supplied options object.', printStyle); 10192 } 10193 10194 Core.prototype.setOptions.call(this, options); 10195 10196 if ('type' in options) { 10197 if (options.type !== this.options.type) { 10198 this.options.type = options.type; 10199 10200 // force recreation of all items 10201 var itemsData = this.itemsData; 10202 if (itemsData) {
10203 var selection = this.getSelection(); 10204 this.setItems(null); // remove all 10205 this.setItems(itemsData); // add all 10206 this.setSelection(selection); // restore selection 10207 } 10208 } 10209 } 10210 }; 10211 10212 /** 10213 * Set items 10214 * @param {vis.DataSet | Array | null} items 10215 */ 10216 Timeline.prototype.setItems = function (items) { 10217 var initialLoad = this.itemsData == null; 10218 10219 // convert to type DataSet when needed 10220 var newDataSet; 10221 if (!items) { 10222 newDataSet = null; 10223 } else if (items instanceof DataSet || items instanceof DataView) { 10224 newDataSet = items; 10225 } else { 10226 // turn an array into a dataset 10227 newDataSet = new DataSet(items, { 10228 type: { 10229 start: 'Date', 10230 end: 'Date' 10231 } 10232 }); 10233 } 10234 10235 // set items 10236 this.itemsData = newDataSet; 10237 this.itemSet && this.itemSet.setItems(newDataSet); 10238 10239 if (initialLoad) { 10240 if (this.options.start != undefined || this.options.end != undefined) { 10241 if (this.options.start == undefined || this.options.end == undefined) { 10242 var range = this.getItemRange(); 10243 } 10244 10245 var start = this.options.start != undefined ? this.options.start : range.min; 10246 var end = this.options.end != undefined ? this.options.end : range.max; 10247 10248 this.setWindow(start, end, { animation: false }); 10249 } else { 10250 this.fit({ animation: false }); 10251 } 10252 } 10253 }; 10254 10255 /** 10256 * Set groups 10257 * @param {vis.DataSet | Array} groups 10258 */ 10259 Timeline.prototype.setGroups = function (groups) { 10260 // convert to type DataSet when needed 10261 var newDataSet; 10262 if (!groups) { 10263 newDataSet = null; 10264 } else if (groups instanceof DataSet || groups instanceof DataView) { 10265 newDataSet = groups; 10266 } else { 10267 // turn an array into a dataset 10268 newDataSet = new DataSet(groups); 10269 } 10270 10271 this.groupsData = newDataSet; 10272 this.itemSet.setGroups(newDataSet); 10273 }; 10274 10275 /** 10276 * Set both items and groups in one go 10277 * @param {{items: Array | vis.DataSet, groups: Array | vis.DataSet}} data 10278 */ 10279 Timeline.prototype.setData = function (data) { 10280 if (data && data.groups) { 10281 this.setGroups(data.groups); 10282 } 10283 10284 if (data && data.items) { 10285 this.setItems(data.items); 10286 } 10287 }; 10288 10289 /** 10290 * Set selected items by their id. Replaces the current selection 10291 * Unknown id's are silently ignored. 10292 * @param {string[] | string} [ids] An array with zero or more id's of the items to be 10293 * selected. If ids is an empty array, all items will be 10294 * unselected. 10295 * @param {Object} [options] Available options: 10296 * `focus: boolean` 10297 * If true, focus will be set to the selected item(s) 10298 * `animation: boolean | {duration: number, easingFunction: string}` 10299 * If true (default), the range is animated 10300 * smoothly to the new window. An object can be 10301 * provided to specify duration and easing function. 10302 * Default duration is 500 ms, and default easing 10303 * function is 'easeInOutQuad'. 10304 * Only applicable when option focus is true. 10305 */ 10306 Timeline.prototype.setSelection = function (ids, options) { 10307 this.itemSet && this.itemSet.setSelection(ids); 10308 10309 if (options && options.focus) { 10310 this.focus(ids, options); 10311 } 10312 }; 10313 10314 /** 10315 * Get the selected items by their id 10316 * @return {Array} ids The ids of the selected items 10317 */ 10318 Timeline.prototype.getSelection = function () { 10319 return this.itemSet && this.itemSet.getSelection() || []; 10320 }; 10321 10322 /** 10323 * Adjust the visible window such that the selected item (or multiple items) 10324 * are centered on screen. 10325 * @param {String | String[]} id An item id or array with item ids 10326 * @param {Object} [options] Available options: 10327 * `animation: boolean | {duration: number, easingFunction: string}` 10328 * If true (default), the range is animated 10329 * smoothly to the new window. An object can be 10330 * provided to specify duration and easing function. 10331 * Default duration is 500 ms, and default easing 10332 * function is 'easeInOutQuad'. 10333 */
10334 Timeline.prototype.focus = function (id, options) { 10335 if (!this.itemsData || id == undefined) return; 10336 10337 var ids = Array.isArray(id) ? id : [id]; 10338 10339 // get the specified item(s) 10340 var itemsData = this.itemsData.getDataSet().get(ids, { 10341 type: { 10342 start: 'Date', 10343 end: 'Date' 10344 } 10345 }); 10346 10347 // calculate minimum start and maximum end of specified items 10348 var start = null; 10349 var end = null; 10350 itemsData.forEach(function (itemData) { 10351 var s = itemData.start.valueOf(); 10352 var e = 'end' in itemData ? itemData.end.valueOf() : itemData.start.valueOf(); 10353 10354 if (start === null || s < start) { 10355 start = s; 10356 } 10357 10358 if (end === null || e > end) { 10359 end = e; 10360 } 10361 }); 10362 10363 if (start !== null && end !== null) { 10364 // calculate the new middle and interval for the window 10365 var middle = (start + end) / 2; 10366 var interval = Math.max(this.range.end - this.range.start, (end - start) * 1.1); 10367 10368 var animation = options && options.animation !== undefined ? options.animation : true; 10369 this.range.setRange(middle - interval / 2, middle + interval / 2, animation); 10370 } 10371 }; 10372 10373 /** 10374 * Set Timeline window such that it fits all items 10375 * @param {Object} [options] Available options: 10376 * `animation: boolean | {duration: number, easingFunction: string}` 10377 * If true (default), the range is animated 10378 * smoothly to the new window. An object can be 10379 * provided to specify duration and easing function. 10380 * Default duration is 500 ms, and default easing 10381 * function is 'easeInOutQuad'. 10382 */ 10383 Timeline.prototype.fit = function (options) { 10384 var animation = options && options.animation !== undefined ? options.animation : true; 10385 var range = this.getItemRange(); 10386 this.range.setRange(range.min, range.max, animation); 10387 }; 10388 10389 /** 10390 * Determine the range of the items, taking into account their actual width 10391 * and a margin of 10 pixels on both sides. 10392 * @return {{min: Date | null, max: Date | null}} 10393 */ 10394 Timeline.prototype.getItemRange = function () { 10395 var _this = this; 10396 10397 // get a rough approximation for the range based on the items start and end dates 10398 var range = this.getDataRange(); 10399 var min = range.min; 10400 var max = range.max; 10401 var minItem = null; 10402 var maxItem = null; 10403 10404 if (min != null && max != null) { 10405 var interval; 10406 var factor; 10407 var lhs; 10408 var rhs; 10409 var delta; 10410 10411 (function () { 10412 var getStart = function getStart(item) { 10413 return util.convert(item.data.start, 'Date').valueOf(); 10414 }; 10415 10416 var getEnd = function getEnd(item) { 10417 var end = item.data.end != undefined ? item.data.end : item.data.start; 10418 return util.convert(end, 'Date').valueOf(); 10419 }; 10420 10421 interval = max - min; 10422 // ms 10423 if (interval <= 0) { 10424 interval = 10; 10425 } 10426 factor = interval / _this.props.center.width; 10427 10428 // calculate the date of the left side and right side of the items given 10429 util.forEach(_this.itemSet.items, (function (item) { 10430 item.show(); 10431 10432 var start = getStart(item); 10433 var end = getEnd(item); 10434 10435 var left = new Date(start - (item.getWidthLeft() + 10) * factor); 10436 var right = new Date(end + (item.getWidthRight() + 10) * factor); 10437 10438 if (left < min) { 10439 min = left; 10440 minItem = item; 10441 } 10442 if (right > max) { 10443 max = right; 10444 maxItem = item; 10445 } 10446 }).bind(_this)); 10447 10448 if (minItem && maxItem) { 10449 lhs = minItem.getWidthLeft() + 10; 10450 rhs = maxItem.getWidthRight() + 10; 10451 delta = _this.props.center.width - lhs - rhs; 10452 // px 10453 10454 if (delta > 0) { 10455 min = getStart(minItem) - lhs * interval / delta; // ms 10456 max = getEnd(maxItem) + rhs * interval / delta; // ms 10457 } 10458 } 10459 })(); 10460 } 10461 10462 return { 10463 min: min != null ? new Date(min) : null, 10464 max: max != null ? new Date(max) : null 10465 }; 10466 }; 10467 10468 /** 10469 * Calculate the data range of the items start and end dates 10470 * @returns {{min: Date | null, max: Date | null}} 10471 */ 10472 Timeline.prototype.getDataRange = function () { 10473 var min = null; 10474 var max = null; 10475 10476 var dataset = this.itemsData && this.itemsData.getDataSet(); 10477 if (dataset) { 10478 dataset.forEach(function (item) { 10479 var start = util.convert(item.start, 'Date').valueOf(); 10480 var end = util.convert(item.end != undefined ? item.end : item.start, 'Date').valueOf(); 10481 if (min === null || start < min) { 10482 min = start; 10483 } 10484 if (max === null || end > max) { 10485 max = start; 10486 } 10487 }); 10488 } 10489 10490 return { 10491 min: min != null ? new Date(min) : null, 10492 max: max != null ? new Date(max) : null 10493 }; 10494 }; 10495 10496 /** 10497 * Generate Timeline related information from an event 10498 * @param {Event} event 10499 * @return {Object} An object with related information, like on which area 10500 * The event happened, whether clicked on an item, etc. 10501 */ 10502 Timeline.prototype.getEventProperties = function (event) { 10503 var clientX = event.center ? event.center.x : event.clientX; 10504 var clientY = event.center ? event.center.y : event.clientY; 10505 var x = clientX - util.getAbsoluteLeft(this.dom.centerContainer); 10506 var y = clientY - util.getAbsoluteTop(this.dom.centerContainer); 10507 10508 var item = this.itemSet.itemFromTarget(event); 10509 var group = this.itemSet.groupFromTarget(event); 10510 var customTime = CustomTime.customTimeFromTarget(event); 10511 10512 var snap = this.itemSet.options.snap || null; 10513 var scale = this.body.util.getScale(); 10514 var step = this.body.util.getStep(); 10515 var time = this._toTime(x); 10516 var snappedTime = snap ? snap(time, scale, step) : time; 10517 10518 var element = util.getTarget(event); 10519 var what = null; 10520 if (item != null) { 10521 what = 'item'; 10522 } else if (customTime != null) { 10523 what = 'custom-time'; 10524 } else if (util.hasParent(element, this.timeAxis.dom.foreground)) { 10525 what = 'axis'; 10526 } else if (this.timeAxis2 && util.hasParent(element, this.timeAxis2.dom.foreground)) { 10527 what = 'axis'; 10528 } else if (util.hasParent(element, this.itemSet.dom.labelSet)) { 10529 what = 'group-label'; 10530 } else if (util.hasParent(element, this.currentTime.bar)) { 10531 what = 'current-time'; 10532 } else if (util.hasParent(element, this.dom.center)) { 10533 what = 'background'; 10534 } 10535 10536 return { 10537 event: event, 10538 item: item ? item.id : null, 10539 group: group ? group.groupId : null, 10540 what: what, 10541 pageX: event.srcEvent ? event.srcEvent.pageX : event.pageX, 10542 pageY: event.srcEvent ? event.srcEvent.pageY : event.pageY, 10543 x: x, 10544 y: y, 10545 time: time, 10546 snappedTime: snappedTime 10547 }; 10548 }; 10549 10550 module.exports = Timeline; 10551 10552/***/ }, 10553/* 20 */ 10554/***/ function(module, exports, __webpack_require__) { 10555 10556 // Only load hammer.js when in a browser environment 10557 // (loading hammer.js in a node.js environment gives errors) 10558 'use strict'; 10559 10560 if (typeof window !== 'undefined') { 10561 var propagating = __webpack_require__(21); 10562 var Hammer = window['Hammer'] || __webpack_require__(22); 10563 module.exports = propagating(Hammer, { 10564 preventDefault: 'mouse' 10565 }); 10566 } else { 10567 module.exports = function () { 10568 throw Error('hammer.js is only available in a browser, not in node.js.'); 10569 }; 10570 } 10571 10572/***/ }, 10573/* 21 */ 10574/***/ function(module, exports, __webpack_require__) { 10575 10576 var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;'use strict'; 10577 10578 (function (factory) { 10579 if (true) { 10580 // AMD. Register as an anonymous module. 10581 !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); 10582 } else if (typeof exports === 'object') { 10583 // Node. Does not work with strict CommonJS, but 10584 // only CommonJS-like environments that support module.exports, 10585 // like Node. 10586 module.exports = factory(); 10587 } else { 10588 // Browser globals (root is window) 10589 window.propagating = factory(); 10590 } 10591 }(function () { 10592 var _firstTarget = null; // singleton, will contain the target element where the touch event started 10593 var _processing = false; // singleton, true when a touch event is being handled 10594 10595 /** 10596 * Extend an Hammer.js instance with event propagation. 10597 * 10598 * Features: 10599 * - Events emitted by hammer will propagate in order from child to parent 10600 * elements. 10601 * - Events are extended with a function `event.stopPropagation()` to stop 10602 * propagation to parent elements. 10603 * - An option `preventDefault` to stop all default browser behavior. 10604 * 10605 * Usage: 10606 * var hammer = propagatingHammer(new Hammer(element)); 10607 * var hammer = propagatingHammer(new Hammer(element), {preventDefault: true}); 10608 * 10609 * @param {Hammer.Manager} hammer An hammer instance. 10610 * @param {Object} [options] Available options: 10611 * - `preventDefault: true | 'mouse' | 'touch' | 'pen'`. 10612 * Enforce preventing the default browser behavior. 10613 * Cannot be set to `false`. 10614 * @return {Hammer.Manager} Returns the same hammer instance with extended 10615 * functionality 10616 */ 10617 return function propagating(hammer, options) { 10618 var _options = options || { 10619 preventDefault: false 10620 }; 10621 10622 if (hammer.Manager) { 10623 // This looks like the Hammer constructor. 10624 // Overload the constructors with our own. 10625 var Hammer = hammer; 10626 10627 var PropagatingHammer = function(element, options) { 10628 var o = Object.create(_options); 10629 if (options) Hammer.extend(o, options); 10630 return propagating(new Hammer(element, o), o); 10631 }; 10632 Hammer.extend(PropagatingHammer, Hammer); 10633 10634 PropagatingHammer.Manager = function (element, options) { 10635 var o = Object.create(_options); 10636 if (options) Hammer.extend(o, options); 10637 return propagating(new Hammer.Manager(element, o), o); 10638 }; 10639 10640 return PropagatingHammer; 10641 } 10642 10643 // create a wrapper object which will override the functions 10644 // `on`, `off`, `destroy`, and `emit` of the hammer instance 10645 var wrapper = Object.create(hammer); 10646 10647 // attach to DOM element 10648 var element = hammer.element; 10649 element.hammer = wrapper; 10650 10651 // register an event to catch the start of a gesture and store the 10652 // target in a singleton 10653 hammer.on('hammer.input', function (event) { 10654 if (_options.preventDefault === true || (_options.preventDefault === event.pointerType)) { 10655 event.preventDefault(); 10656 } 10657 if (event.isFirst) { 10658 _firstTarget = event.target; 10659 } 10660 }); 10661 10662 /** @type {Object.<String, Array.<function>>} */ 10663 wrapper._handlers = {}; 10664 10665 /** 10666 * Register a handler for one or multiple events 10667 * @param {String} events A space separated string with events 10668 * @param {function}
10668 handler A callback function, called as handler(event) 10669 * @returns {Hammer.Manager} Returns the hammer instance 10670 */ 10671 wrapper.on = function (events, handler) { 10672 // register the handler 10673 split(events).forEach(function (event) { 10674 var _handlers = wrapper._handlers[event]; 10675 if (!_handlers) { 10676 wrapper._handlers[event] = _handlers = []; 10677 10678 // register the static, propagated handler 10679 hammer.on(event, propagatedHandler); 10680 } 10681 _handlers.push(handler); 10682 }); 10683 10684 return wrapper; 10685 }; 10686 10687 /** 10688 * Unregister a handler for one or multiple events 10689 * @param {String} events A space separated string with events 10690 * @param {function} [handler] Optional. The registered handler. If not 10691 * provided, all handlers for given events 10692 * are removed. 10693 * @returns {Hammer.Manager} Returns the hammer instance 10694 */ 10695 wrapper.off = function (events, handler) { 10696 // unregister the handler 10697 split(events).forEach(function (event) { 10698 var _handlers = wrapper._handlers[event]; 10699 if (_handlers) { 10700 _handlers = handler ? _handlers.filter(function (h) { 10701 return h !== handler; 10702 }) : []; 10703 10704 if (_handlers.length > 0) { 10705 wrapper._handlers[event] = _handlers; 10706 } 10707 else { 10708 // remove static, propagated handler 10709 hammer.off(event, propagatedHandler); 10710 delete wrapper._handlers[event]; 10711 } 10712 } 10713 }); 10714 10715 return wrapper; 10716 }; 10717 10718 /** 10719 * Emit to the event listeners 10720 * @param {string} eventType 10721 * @param {Event} event 10722 */ 10723 wrapper.emit = function(eventType, event) { 10724 _firstTarget = event.target; 10725 hammer.emit(eventType, event); 10726 }; 10727 10728 wrapper.destroy = function () { 10729 // Detach from DOM element 10730 delete hammer.element.hammer; 10731 10732 // clear all handlers 10733 wrapper._handlers = {}; 10734 10735 // call original hammer destroy 10736 hammer.destroy(); 10737 }; 10738 10739 // split a string with space separated words 10740 function split(events) { 10741 return events.match(/[^ ]+/g); 10742 } 10743 10744 /** 10745 * A static event handler, applying event propagation. 10746 * @param {Object} event 10747 */ 10748 function propagatedHandler(event) { 10749 // let only a single hammer instance handle this event 10750 if (event.type !== 'hammer.input') { 10751 // it is possible that the same srcEvent is used with multiple hammer events, 10752 // we keep track on which events are handled in an object _handled 10753 if (!event.srcEvent._handled) { 10754 event.srcEvent._handled = {}; 10755 } 10756 10757 if (event.srcEvent._handled[event.type]) { 10758 return; 10759 } 10760 else { 10761 event.srcEvent._handled[event.type] = true; 10762 } 10763 } 10764 10765 // attach a stopPropagation function to the event 10766 var stopped = false; 10767 event.stopPropagation = function () { 10768 stopped = true; 10769 }; 10770 10771 // attach firstTarget property to the event 10772 event.firstTarget = _firstTarget; 10773 10774 // propagate over all elements (until stopped) 10775 var elem = _firstTarget; 10776 while (elem && !stopped) { 10777 var _handlers = elem.hammer && elem.hammer._handlers[event.type]; 10778 if (_handlers) { 10779 for (var i = 0; i < _handlers.length && !stopped; i++) { 10780 _handlers[i](event); 10781 } 10782 } 10783 10784 elem = elem.parentNode; 10785 } 10786 } 10787 10788 return wrapper; 10789 }; 10790 })); 10791 10792 10793/***/ }, 10794/* 22 */ 10795/***/ function(module, exports, __webpack_require__) { 10796 10797 var __WEBPACK_AMD_DEFINE_RESULT__;/*! Hammer.JS - v2.0.4 - 2014-09-28 10798 * http://hammerjs.github.io/ 10799 * 10800 * Copyright (c) 2014 Jorik Tangelder; 10801 * Licensed under the MIT license */ 10802 (function(window, document, exportName, undefined) { 10803 'use strict'; 10804 10805 var VENDOR_PREFIXES = ['', 'webkit', 'moz', 'MS', 'ms', 'o']; 10806 var TEST_ELEMENT = document.createElement('div'); 10807 10808 var TYPE_FUNCTION = 'function'; 10809 10810 var round = Math.round; 10811 var abs = Math.abs; 10812 var now = Date.now; 10813 10814 /** 10815 * set a timeout with a given scope 10816 * @param {Function} fn 10817 * @param {Number} timeout 10818 * @param {Object} context 10819 * @returns {number} 10820 */ 10821 function setTimeoutContext(fn, timeout, context) { 10822 return setTimeout(bindFn(fn, context), timeout); 10823 } 10824 10825 /** 10826 * if the argument is an array, we want to execute the fn on each entry 10827 * if it aint an array we don't want to do a thing. 10828 * this is used by all the methods that accept a single and array argument. 10829 * @param {*|Array} arg 10830 * @param {String} fn 10831 * @param {Object} [context] 10832 * @returns {Boolean} 10833 */ 10834 function invokeArrayArg(arg, fn, context) { 10835 if (Array.isArray(arg)) { 10836 each(arg, context[fn], context); 10837 return true; 10838 } 10839 return false;
vendor: 28,175 bytes, lines 10840-11857
10840 } 10841 10842 /** 10843 * walk objects and arrays 10844 * @param {Object} obj 10845 * @param {Function} iterator 10846 * @param {Object} context 10847 */ 10848 function each(obj, iterator, context) { 10849 var i; 10850 10851 if (!obj) { 10852 return; 10853 } 10854 10855 if (obj.forEach) { 10856 obj.forEach(iterator, context); 10857 } else if (obj.length !== undefined) { 10858 i = 0; 10859 while (i < obj.length) { 10860 iterator.call(context, obj[i], i, obj); 10861 i++; 10862 } 10863 } else { 10864 for (i in obj) { 10865 obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj); 10866 } 10867 } 10868 } 10869 10870 /** 10871 * extend object. 10872 * means that properties in dest will be overwritten by the ones in src. 10873 * @param {Object} dest 10874 * @param {Object} src 10875 * @param {Boolean} [merge] 10876 * @returns {Object} dest 10877 */ 10878 function extend(dest, src, merge) { 10879 var keys = Object.keys(src); 10880 var i = 0; 10881 while (i < keys.length) { 10882 if (!merge || (merge && dest[keys[i]] === undefined)) { 10883 dest[keys[i]] = src[keys[i]]; 10884 } 10885 i++; 10886 } 10887 return dest; 10888 } 10889 10890 /** 10891 * merge the values from src in the dest. 10892 * means that properties that exist in dest will not be overwritten by src 10893 * @param {Object} dest 10894 * @param {Object} src 10895 * @returns {Object} dest 10896 */ 10897 function merge(dest, src) { 10898 return extend(dest, src, true); 10899 } 10900 10901 /** 10902 * simple class inheritance 10903 * @param {Function} child 10904 * @param {Function} base 10905 * @param {Object} [properties] 10906 */ 10907 function inherit(child, base, properties) { 10908 var baseP = base.prototype, 10909 childP; 10910 10911 childP = child.prototype = Object.create(baseP); 10912 childP.constructor = child; 10913 childP._super = baseP; 10914 10915 if (properties) { 10916 extend(childP, properties); 10917 } 10918 } 10919 10920 /** 10921 * simple function bind 10922 * @param {Function} fn 10923 * @param {Object} context 10924 * @returns {Function} 10925 */ 10926 function bindFn(fn, context) { 10927 return function boundFn() { 10928 return fn.apply(context, arguments); 10929 }; 10930 } 10931 10932 /** 10933 * let a boolean value also be a function that must return a boolean 10934 * this first item in args will be used as the context 10935 * @param {Boolean|Function} val 10936 * @param {Array} [args] 10937 * @returns {Boolean} 10938 */ 10939 function boolOrFn(val, args) { 10940 if (typeof val == TYPE_FUNCTION) { 10941 return val.apply(args ? args[0] || undefined : undefined, args); 10942 } 10943 return val; 10944 } 10945 10946 /** 10947 * use the val2 when val1 is undefined 10948 * @param {*} val1 10949 * @param {*} val2 10950 * @returns {*} 10951 */ 10952 function ifUndefined(val1, val2) { 10953 return (val1 === undefined) ? val2 : val1; 10954 } 10955 10956 /** 10957 * addEventListener with multiple events at once 10958 * @param {EventTarget} target 10959 * @param {String} types 10960 * @param {Function} handler 10961 */ 10962 function addEventListeners(target, types, handler) { 10963 each(splitStr(types), function(type) { 10964 target.addEventListener(type, handler, false); 10965 }); 10966 } 10967 10968 /** 10969 * removeEventListener with multiple events at once 10970 * @param {EventTarget} target 10971 * @param {String} types 10972 * @param {Function} handler 10973 */ 10974 function removeEventListeners(target, types, handler) { 10975 each(splitStr(types), function(type) { 10976 target.removeEventListener(type, handler, false); 10977 }); 10978 } 10979 10980 /** 10981 * find if a node is in the given parent 10982 * @method hasParent 10983 * @param {HTMLElement} node 10984 * @param {HTMLElement} parent 10985 * @return {Boolean} found 10986 */ 10987 function hasParent(node, parent) { 10988 while (node) { 10989 if (node == parent) { 10990 return true; 10991 } 10992 node = node.parentNode; 10993 } 10994 return false; 10995 } 10996 10997 /** 10998 * small indexOf wrapper 10999 * @param {String} str 11000 * @param {String} find 11001 * @returns {Boolean} found 11002 */ 11003 function inStr(str, find) { 11004 return str.indexOf(find) > -1; 11005 } 11006 11007 /** 11008 * split string on whitespace 11009 * @param {String} str 11010 * @returns {Array} words 11011 */ 11012 function splitStr(str) { 11013 return str.trim().split(/\s+/g); 11014 } 11015 11016 /** 11017 * find if a array contains the object using indexOf or a simple polyFill 11018 * @param {Array} src 11019 * @param {String} find 11020 * @param {String} [findByKey] 11021 * @return {Boolean|Number} false when not found, or the index 11022 */ 11023 function inArray(src, find, findByKey) { 11024 if (src.indexOf && !findByKey) { 11025 return src.indexOf(find); 11026 } else { 11027 var i = 0; 11028 while (i < src.length) { 11029 if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) { 11030 return i; 11031 } 11032 i++; 11033 } 11034 return -1; 11035 } 11036 } 11037 11038 /** 11039 * convert array-like objects to real arrays 11040 * @param {Object} obj 11041 * @returns {Array} 11042 */ 11043 function toArray(obj) { 11044 return Array.prototype.slice.call(obj, 0); 11045 } 11046 11047 /** 11048 * unique array with objects based on a key (like 'id') or just by the array's value 11049 * @param {Array} src [{id:1},{id:2},{id:1}] 11050 * @param {String} [key] 11051 * @param {Boolean} [sort=False] 11052 * @returns {Array} [{id:1},{id:2}] 11053 */ 11054 function uniqueArray(src, key, sort) { 11055 var results = []; 11056 var values = []; 11057 var i = 0; 11058 11059 while (i < src.length) { 11060 var val = key ? src[i][key] : src[i]; 11061 if (inArray(values, val) < 0) { 11062 results.push(src[i]); 11063 } 11064 values[i] = val; 11065 i++; 11066 } 11067 11068 if (sort) { 11069 if (!key) { 11070 results = results.sort(); 11071 } else { 11072 results = results.sort(function sortUniqueArray(a, b) { 11073 return a[key] > b[key]; 11074 }); 11075 } 11076 } 11077 11078 return results; 11079 } 11080 11081 /** 11082 * get the prefixed property 11083 * @param {Object} obj 11084 * @param {String} property 11085 * @returns {String|Undefined} prefixed 11086 */ 11087 function prefixed(obj, property) { 11088 var prefix, prop; 11089 var camelProp = property[0].toUpperCase() + property.slice(1); 11090 11091 var i = 0; 11092 while (i < VENDOR_PREFIXES.length) { 11093 prefix = VENDOR_PREFIXES[i]; 11094 prop = (prefix) ? prefix + camelProp : property; 11095 11096 if (prop in obj) { 11097 return prop; 11098 } 11099 i++; 11100 } 11101 return undefined; 11102 } 11103 11104 /** 11105 * get a unique id 11106 * @returns {number} uniqueId 11107 */ 11108 var _uniqueId = 1; 11109 function uniqueId() { 11110 return _uniqueId++; 11111 } 11112 11113 /** 11114 * get the window object of an element 11115 * @param {HTMLElement} element 11116 * @returns {DocumentView|Window} 11117 */ 11118 function getWindowForElement(element) { 11119 var doc = element.ownerDocument; 11120 return (doc.defaultView || doc.parentWindow); 11121 } 11122 11123 var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i; 11124 11125 var SUPPORT_TOUCH = ('ontouchstart' in window); 11126 var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined; 11127 var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent); 11128 11129 var INPUT_TYPE_TOUCH = 'touch'; 11130 var INPUT_TYPE_PEN = 'pen'; 11131 var INPUT_TYPE_MOUSE = 'mouse'; 11132 var INPUT_TYPE_KINECT = 'kinect'; 11133 11134 var COMPUTE_INTERVAL = 25; 11135 11136 var INPUT_START = 1; 11137 var INPUT_MOVE = 2; 11138 var INPUT_END = 4; 11139 var INPUT_CANCEL = 8; 11140 11141 var DIRECTION_NONE = 1; 11142 var DIRECTION_LEFT = 2; 11143 var DIRECTION_RIGHT = 4; 11144 var DIRECTION_UP = 8; 11145 var DIRECTION_DOWN = 16; 11146 11147 var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT; 11148 var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN; 11149 var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL; 11150 11151 var PROPS_XY = ['x', 'y']; 11152 var PROPS_CLIENT_XY = ['clientX', 'clientY']; 11153 11154 /** 11155 * create new input type manager 11156 * @param {Manager} manager 11157 * @param {Function} callback 11158 * @returns {Input} 11159 * @constructor 11160 */ 11161 function Input(manager, callback) { 11162 var self = this; 11163 this.manager = manager; 11164 this.callback = callback; 11165 this.element = manager.element; 11166 this.target = manager.options.inputTarget; 11167 11168 // smaller wrapper around the handler, for the scope and the enabled state of the manager, 11169 // so when disabled the input events are completely bypassed. 11170 this.domHandler = function(ev) { 11171 if (boolOrFn(manager.options.enable, [manager])) { 11172 self.handler(ev); 11173 } 11174 }; 11175 11176 this.init(); 11177 11178 } 11179 11180 Input.prototype = { 11181 /** 11182 * should handle the inputEvent data and trigger the callback 11183 * @virtual 11184 */ 11185 handler: function() { }, 11186 11187 /** 11188 * bind the events 11189 */ 11190 init: function() { 11191 this.evEl && addEventListeners(this.element, this.evEl, this.domHandler); 11192 this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler); 11193 this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler); 11194 }, 11195 11196 /** 11197 * unbind the events 11198 */ 11199 destroy: function() { 11200 this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler); 11201 this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler); 11202 this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler); 11203 } 11204 }; 11205 11206 /** 11207 * create new input type manager 11208 * called by the Manager constructor 11209 * @param {Hammer} manager 11210 * @returns {Input} 11211 */ 11212 function createInputInstance(manager) { 11213 var Type; 11214 var inputClass = manager.options.inputClass; 11215 11216 if (inputClass) { 11217 Type = inputClass; 11218 } else if (SUPPORT_POINTER_EVENTS) { 11219 Type = PointerEventInput; 11220 } else if (SUPPORT_ONLY_TOUCH) { 11221 Type = TouchInput; 11222 } else if (!SUPPORT_TOUCH) { 11223 Type = MouseInput; 11224 } else { 11225 Type = TouchMouseInput; 11226 } 11227 return new (Type)(manager, inputHandler); 11228 } 11229 11230 /** 11231 * handle input events 11232 * @param {Manager} manager 11233 * @param {String} eventType 11234 * @param {Object} input 11235 */ 11236 function inputHandler(manager, eventType, input) { 11237 var pointersLen = input.pointers.length; 11238 var changedPointersLen = input.changedPointers.length; 11239 var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0)); 11240 var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0)); 11241 11242 input.isFirst = !!isFirst; 11243 input.isFinal = !!isFinal; 11244 11245 if (isFirst) { 11246 manager.session = {}; 11247 } 11248 11249 // source event is the normalized value of the domEvents 11250 // like 'touchstart, mouseup, pointerdown' 11251 input.eventType = eventType; 11252 11253 // compute scale, rotation etc 11254 computeInputData(manager, input); 11255 11256 // emit secret event 11257 manager.emit('hammer.input', input); 11258 11259 manager.recognize(input); 11260 manager.session.prevInput = input; 11261 } 11262 11263 /** 11264 * extend the data with some usable properties like scale, rotate, velocity etc 11265 * @param {Object} manager 11266 * @param {Object} input 11267 */ 11268 function computeInputData(manager, input) { 11269 var session = manager.session; 11270 var pointers = input.pointers; 11271 var pointersLength = pointers.length; 11272 11273 // store the first input to calculate the distance and direction 11274 if (!session.firstInput) { 11275 session.firstInput = simpleCloneInputData(input); 11276 } 11277 11278 // to compute scale and rotation we need to store the multiple touches 11279 if (pointersLength > 1 && !session.firstMultiple) { 11280 session.firstMultiple = simpleCloneInputData(input); 11281 } else if (pointersLength === 1) { 11282 session.firstMultiple = false; 11283 } 11284 11285 var firstInput = session.firstInput; 11286 var firstMultiple = session.firstMultiple; 11287 var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center; 11288 11289 var center = input.center = getCenter(pointers); 11290 input.timeStamp = now(); 11291 input.deltaTime = input.timeStamp - firstInput.timeStamp; 11292 11293 input.angle = getAngle(offsetCenter, center); 11294 input.distance = getDistance(offsetCenter, center); 11295 11296 computeDeltaXY(session, input); 11297 input.offsetDirection = getDirection(input.deltaX, input.deltaY); 11298 11299 input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1; 11300 input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0; 11301 11302 computeIntervalInputData(session, input); 11303 11304 // find the correct target 11305 var target = manager.element; 11306 if (hasParent(input.srcEvent.target, target)) { 11307 target = input.srcEvent.target; 11308 } 11309 input.target = target; 11310 } 11311 11312 function computeDeltaXY(session, input) { 11313 var center = input.center; 11314 var offset = session.offsetDelta || {}; 11315 var prevDelta = session.prevDelta || {}; 11316 var prevInput = session.prevInput || {}; 11317 11318 if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) { 11319 prevDelta = session.prevDelta = { 11320 x: prevInput.deltaX || 0, 11321 y: prevInput.deltaY || 0 11322 }; 11323 11324 offset = session.offsetDelta = { 11325 x: center.x, 11326 y: center.y 11327 }; 11328 } 11329 11330 input.deltaX = prevDelta.x + (center.x - offset.x); 11331 input.deltaY = prevDelta.y + (center.y - offset.y); 11332 } 11333 11334 /** 11335 * velocity is calculated every x ms 11336 * @param {Object} session 11337 * @param {Object} input 11338 */ 11339 function computeIntervalInputData(session, input) { 11340 var last = session.lastInterval || input, 11341 deltaTime = input.timeStamp - last.timeStamp, 11342 velocity, velocityX, velocityY, direction; 11343 11344 if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) { 11345 var deltaX = last.deltaX - input.deltaX; 11346 var deltaY = last.deltaY - input.deltaY; 11347 11348 var v = getVelocity(deltaTime, deltaX, deltaY); 11349 velocityX = v.x; 11350 velocityY = v.y; 11351 velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y; 11352 direction = getDirection(deltaX, deltaY); 11353 11354 session.lastInterval = input; 11355 } else { 11356 // use latest velocity info if it doesn't overtake a minimum period 11357 velocity = last.velocity; 11358 velocityX = last.velocityX; 11359 velocityY = last.velocityY; 11360 direction = last.direction; 11361 } 11362 11363 input.velocity = velocity; 11364 input.velocityX = velocityX; 11365 input.velocityY = velocityY; 11366 input.direction = direction; 11367 } 11368 11369 /** 11370 * create a simple clone from the input used for storage of firstInput and firstMultiple 11371 * @param {Object} input 11372 * @returns {Object} clonedInputData 11373 */ 11374 function simpleCloneInputData(input) { 11375 // make a simple copy of the pointers because we will get a reference if we don't 11376 // we only need clientXY for the calculations 11377 var pointers = []; 11378 var i = 0; 11379 while (i < input.pointers.length) { 11380 pointers[i] = { 11381 clientX: round(input.pointers[i].clientX), 11382 clientY: round(input.pointers[i].clientY) 11383 }; 11384 i++; 11385 } 11386 11387 return { 11388 timeStamp: now(), 11389 pointers: pointers, 11390 center: getCenter(pointers), 11391 deltaX: input.deltaX, 11392 deltaY: input.deltaY 11393 }; 11394 } 11395 11396 /** 11397 * get the center of all the pointers 11398 * @param {Array} pointers 11399 * @return {Object} center contains `x` and `y` properties 11400 */ 11401 function getCenter(pointers) { 11402 var pointersLength = pointers.length; 11403 11404 // no need to loop when only one touch 11405 if (pointersLength === 1) { 11406 return { 11407 x: round(pointers[0].clientX), 11408 y: round(pointers[0].clientY) 11409 }; 11410 } 11411 11412 var x = 0, y = 0, i = 0; 11413 while (i < pointersLength) { 11414 x += pointers[i].clientX; 11415 y += pointers[i].clientY; 11416 i++; 11417 } 11418 11419 return { 11420 x: round(x / pointersLength), 11421 y: round(y / pointersLength) 11422 }; 11423 } 11424 11425 /** 11426 * calculate the velocity between two points. unit is in px per ms. 11427 * @param {Number} deltaTime 11428 * @param {Number} x 11429 * @param {Number} y 11430 * @return {Object} velocity `x` and `y` 11431 */ 11432 function getVelocity(deltaTime, x, y) { 11433 return { 11434 x: x / deltaTime || 0, 11435 y: y / deltaTime || 0 11436 }; 11437 } 11438 11439 /** 11440 * get the direction between two points 11441 * @param {Number} x 11442 * @param {Number} y 11443 * @return {Number} direction 11444 */ 11445 function getDirection(x, y) { 11446 if (x === y) { 11447 return DIRECTION_NONE; 11448 } 11449 11450 if (abs(x) >= abs(y)) { 11451 return x > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; 11452 } 11453 return y > 0 ? DIRECTION_UP : DIRECTION_DOWN; 11454 } 11455 11456 /** 11457 * calculate the absolute distance between two points 11458 * @param {Object} p1 {x, y} 11459 * @param {Object} p2 {x, y} 11460 * @param {Array} [props] containing x and y keys 11461 * @return {Number} distance 11462 */ 11463 function getDistance(p1, p2, props) { 11464 if (!props) { 11465 props = PROPS_XY; 11466 } 11467 var x = p2[props[0]] - p1[props[0]], 11468 y = p2[props[1]] - p1[props[1]]; 11469 11470 return Math.sqrt((x * x) + (y * y)); 11471 } 11472 11473 /** 11474 * calculate the angle between two coordinates 11475 * @param {Object} p1 11476 * @param {Object} p2 11477 * @param {Array} [props] containing x and y keys 11478 * @return {Number} angle 11479 */ 11480 function getAngle(p1, p2, props) { 11481 if (!props) { 11482 props = PROPS_XY; 11483 } 11484 var x = p2[props[0]] - p1[props[0]], 11485 y = p2[props[1]] - p1[props[1]]; 11486 return Math.atan2(y, x) * 180 / Math.PI; 11487 } 11488 11489 /** 11490 * calculate the rotation degrees between two pointersets 11491 * @param {Array} start array of pointers 11492 * @param {Array} end array of pointers 11493 * @return {Number} rotation 11494 */ 11495 function getRotation(start, end) { 11496 return getAngle(end[1], end[0], PROPS_CLIENT_XY) - getAngle(start[1], start[0], PROPS_CLIENT_XY); 11497 } 11498 11499 /** 11500 * calculate the scale factor between two pointersets 11501 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out 11502 * @param {Array} start array of pointers 11503 * @param {Array} end array of pointers 11504 * @return {Number} scale 11505 */ 11506 function getScale(start, end) { 11507 return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY); 11508 } 11509 11510 var MOUSE_INPUT_MAP = { 11511 mousedown: INPUT_START, 11512 mousemove: INPUT_MOVE, 11513 mouseup: INPUT_END 11514 }; 11515 11516 var MOUSE_ELEMENT_EVENTS = 'mousedown'; 11517 var MOUSE_WINDOW_EVENTS = 'mousemove mouseup'; 11518 11519 /** 11520 * Mouse events input 11521 * @constructor 11522 * @extends Input 11523 */ 11524 function MouseInput() { 11525 this.evEl = MOUSE_ELEMENT_EVENTS; 11526 this.evWin = MOUSE_WINDOW_EVENTS; 11527 11528 this.allow = true; // used by Input.TouchMouse to disable mouse events 11529 this.pressed = false; // mousedown state 11530 11531 Input.apply(this, arguments); 11532 } 11533 11534 inherit(MouseInput, Input, { 11535 /** 11536 * handle mouse events 11537 * @param {Object} ev 11538 */ 11539 handler: function MEhandler(ev) { 11540 var eventType = MOUSE_INPUT_MAP[ev.type]; 11541 11542 // on start we want to have the left mouse button down 11543 if (eventType & INPUT_START && ev.button === 0) { 11544 this.pressed = true; 11545 } 11546 11547 if (eventType & INPUT_MOVE && ev.which !== 1) { 11548 eventType = INPUT_END; 11549 } 11550 11551 // mouse must be down, and mouse events are allowed (see the TouchMouse input) 11552 if (!this.pressed || !this.allow) { 11553 return; 11554 } 11555 11556 if (eventType & INPUT_END) { 11557 this.pressed = false; 11558 } 11559 11560 this.callback(this.manager, eventType, { 11561 pointers: [ev], 11562 changedPointers: [ev], 11563 pointerType: INPUT_TYPE_MOUSE, 11564 srcEvent: ev 11565 }); 11566 } 11567 }); 11568 11569 var POINTER_INPUT_MAP = { 11570 pointerdown: INPUT_START, 11571 pointermove: INPUT_MOVE, 11572 pointerup: INPUT_END, 11573 pointercancel: INPUT_CANCEL, 11574 pointerout: INPUT_CANCEL 11575 }; 11576 11577 // in IE10 the pointer types is defined as an enum 11578 var IE10_POINTER_TYPE_ENUM = { 11579 2: INPUT_TYPE_TOUCH, 11580 3: INPUT_TYPE_PEN, 11581 4: INPUT_TYPE_MOUSE, 11582 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816 11583 }; 11584 11585 var POINTER_ELEMENT_EVENTS = 'pointerdown'; 11586 var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel'; 11587 11588 // IE10 has prefixed support, and case-sensitive 11589 if (window.MSPointerEvent) { 11590 POINTER_ELEMENT_EVENTS = 'MSPointerDown'; 11591 POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel'; 11592 } 11593 11594 /** 11595 * Pointer events input 11596 * @constructor 11597 * @extends Input 11598 */ 11599 function PointerEventInput() { 11600 this.evEl = POINTER_ELEMENT_EVENTS; 11601 this.evWin = POINTER_WINDOW_EVENTS; 11602 11603 Input.apply(this, arguments); 11604 11605 this.store = (this.manager.session.pointerEvents = []); 11606 } 11607 11608 inherit(PointerEventInput, Input, { 11609 /** 11610 * handle mouse events 11611 * @param {Object} ev 11612 */ 11613 handler: function PEhandler(ev) { 11614 var store = this.store; 11615 var removePointer = false; 11616 11617 var eventTypeNormalized = ev.type.toLowerCase().replace('ms', ''); 11618 var eventType = POINTER_INPUT_MAP[eventTypeNormalized]; 11619 var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType; 11620 11621 var isTouch = (pointerType == INPUT_TYPE_TOUCH); 11622 11623 // get index of the event in the store 11624 var storeIndex = inArray(store, ev.pointerId, 'pointerId'); 11625 11626 // start and mouse must be down 11627 if (eventType & INPUT_START && (ev.button === 0 || isTouch)) { 11628 if (storeIndex < 0) { 11629 store.push(ev); 11630 storeIndex = store.length - 1; 11631 } 11632 } else if (eventType & (INPUT_END | INPUT_CANCEL)) { 11633 removePointer = true; 11634 } 11635 11636 // it not found, so the pointer hasn't been down (so it's probably a hover) 11637 if (storeIndex < 0) { 11638 return; 11639 } 11640 11641 // update the event in the store 11642 store[storeIndex] = ev; 11643 11644 this.callback(this.manager, eventType, { 11645 pointers: store, 11646 changedPointers: [ev], 11647 pointerType: pointerType, 11648 srcEvent: ev 11649 }); 11650 11651 if (removePointer) { 11652 // remove from the store 11653 store.splice(storeIndex, 1); 11654 } 11655 } 11656 }); 11657 11658 var SINGLE_TOUCH_INPUT_MAP = { 11659 touchstart: INPUT_START, 11660 touchmove: INPUT_MOVE, 11661 touchend: INPUT_END, 11662 touchcancel: INPUT_CANCEL 11663 }; 11664 11665 var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart'; 11666 var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel'; 11667 11668 /** 11669 * Touch events input 11670 * @constructor 11671 * @extends Input 11672 */ 11673 function SingleTouchInput() { 11674 this.evTarget = SINGLE_TOUCH_TARGET_EVENTS; 11675 this.evWin = SINGLE_TOUCH_WINDOW_EVENTS; 11676 this.started = false; 11677 11678 Input.apply(this, arguments); 11679 } 11680 11681 inherit(SingleTouchInput, Input, { 11682 handler: function TEhandler(ev) { 11683 var type = SINGLE_TOUCH_INPUT_MAP[ev.type]; 11684 11685 // should we handle the touch events? 11686 if (type === INPUT_START) { 11687 this.started = true; 11688 } 11689 11690 if (!this.started) { 11691 return; 11692 } 11693 11694 var touches = normalizeSingleTouches.call(this, ev, type); 11695 11696 // when done, reset the started state 11697 if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) { 11698 this.started = false; 11699 } 11700 11701 this.callback(this.manager, type, { 11702 pointers: touches[0], 11703 changedPointers: touches[1], 11704 pointerType: INPUT_TYPE_TOUCH, 11705 srcEvent: ev 11706 }); 11707 } 11708 }); 11709 11710 /** 11711 * @this {TouchInput} 11712 * @param {Object} ev 11713 * @param {Number} type flag 11714 * @returns {undefined|Array} [all, changed] 11715 */ 11716 function normalizeSingleTouches(ev, type) { 11717 var all = toArray(ev.touches); 11718 var changed = toArray(ev.changedTouches); 11719 11720 if (type & (INPUT_END | INPUT_CANCEL)) { 11721 all = uniqueArray(all.concat(changed), 'identifier', true); 11722 } 11723 11724 return [all, changed]; 11725 } 11726 11727 var TOUCH_INPUT_MAP = { 11728 touchstart: INPUT_START, 11729 touchmove: INPUT_MOVE, 11730 touchend: INPUT_END, 11731 touchcancel: INPUT_CANCEL 11732 }; 11733 11734 var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel'; 11735 11736 /** 11737 * Multi-user touch events input 11738 * @constructor 11739 * @extends Input 11740 */ 11741 function TouchInput() { 11742 this.evTarget = TOUCH_TARGET_EVENTS; 11743 this.targetIds = {}; 11744 11745 Input.apply(this, arguments); 11746 } 11747 11748 inherit(TouchInput, Input, { 11749 handler: function MTEhandler(ev) { 11750 var type = TOUCH_INPUT_MAP[ev.type]; 11751 var touches = getTouches.call(this, ev, type); 11752 if (!touches) { 11753 return; 11754 } 11755 11756 this.callback(this.manager, type, { 11757 pointers: touches[0], 11758 changedPointers: touches[1], 11759 pointerType: INPUT_TYPE_TOUCH, 11760 srcEvent: ev 11761 }); 11762 } 11763 }); 11764 11765 /** 11766 * @this {TouchInput} 11767 * @param {Object} ev 11768 * @param {Number} type flag 11769 * @returns {undefined|Array} [all, changed] 11770 */ 11771 function getTouches(ev, type) { 11772 var allTouches = toArray(ev.touches); 11773 var targetIds = this.targetIds; 11774 11775 // when there is only one touch, the process can be simplified 11776 if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) { 11777 targetIds[allTouches[0].identifier] = true; 11778 return [allTouches, allTouches]; 11779 } 11780 11781 var i, 11782 targetTouches, 11783 changedTouches = toArray(ev.changedTouches), 11784 changedTargetTouches = [], 11785 target = this.target; 11786 11787 // get target touches from touches 11788 targetTouches = allTouches.filter(function(touch) { 11789 return hasParent(touch.target, target); 11790 }); 11791 11792 // collect touches 11793 if (type === INPUT_START) { 11794 i = 0; 11795 while (i < targetTouches.length) { 11796 targetIds[targetTouches[i].identifier] = true; 11797 i++; 11798 } 11799 } 11800 11801 // filter changed touches to only contain touches that exist in the collected target ids 11802 i = 0; 11803 while (i < changedTouches.length) { 11804 if (targetIds[changedTouches[i].identifier]) { 11805 changedTargetTouches.push(changedTouches[i]); 11806 } 11807 11808 // cleanup removed touches 11809 if (type & (INPUT_END | INPUT_CANCEL)) { 11810 delete targetIds[changedTouches[i].identifier]; 11811 } 11812 i++; 11813 } 11814 11815 if (!changedTargetTouches.length) { 11816 return; 11817 } 11818 11819 return [ 11820 // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel' 11821 uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true), 11822 changedTargetTouches 11823 ]; 11824 } 11825 11826 /** 11827 * Combined touch and mouse input 11828 * 11829 * Touch has a higher priority then mouse, and while touching no mouse events are allowed. 11830 * This because touch devices also emit mouse events while doing a touch. 11831 * 11832 * @constructor 11833 * @extends Input 11834 */ 11835 function TouchMouseInput() { 11836 Input.apply(this, arguments); 11837 11838 var handler = bindFn(this.handler, this); 11839 this.touch = new TouchInput(this.manager, handler); 11840 this.mouse = new MouseInput(this.manager, handler); 11841 } 11842 11843 inherit(TouchMouseInput, Input, { 11844 /** 11845 * handle mouse and touch events 11846 * @param {Hammer} manager 11847 * @param {String} inputEvent 11848 * @param {Object} inputData 11849 */ 11850 handler: function TMEhandler(manager, inputEvent, inputData) { 11851 var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH), 11852 isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE); 11853 11854 // when we're in a touch event, so block all upcoming mouse events 11855 // most mobile browser also emit mouseevents, right after touchstart 11856 if (isTouch) { 11857 this.mouse.allow = false;
vendor: 10,820 bytes, lines 11858-12208
11858 } else if (isMouse && !this.mouse.allow) { 11859 return; 11860 } 11861 11862 // reset the allowMouse when we're done 11863 if (inputEvent & (INPUT_END | INPUT_CANCEL)) { 11864 this.mouse.allow = true; 11865 } 11866 11867 this.callback(manager, inputEvent, inputData); 11868 }, 11869 11870 /** 11871 * remove the event listeners 11872 */ 11873 destroy: function destroy() { 11874 this.touch.destroy(); 11875 this.mouse.destroy(); 11876 } 11877 }); 11878 11879 var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction'); 11880 var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined; 11881 11882 // magical touchAction value 11883 var TOUCH_ACTION_COMPUTE = 'compute'; 11884 var TOUCH_ACTION_AUTO = 'auto'; 11885 var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented 11886 var TOUCH_ACTION_NONE = 'none'; 11887 var TOUCH_ACTION_PAN_X = 'pan-x'; 11888 var TOUCH_ACTION_PAN_Y = 'pan-y'; 11889 11890 /** 11891 * Touch Action 11892 * sets the touchAction property or uses the js alternative 11893 * @param {Manager} manager 11894 * @param {String} value 11895 * @constructor 11896 */ 11897 function TouchAction(manager, value) { 11898 this.manager = manager; 11899 this.set(value); 11900 } 11901 11902 TouchAction.prototype = { 11903 /** 11904 * set the touchAction value on the element or enable the polyfill 11905 * @param {String} value 11906 */ 11907 set: function(value) { 11908 // find out the touch-action by the event handlers 11909 if (value == TOUCH_ACTION_COMPUTE) { 11910 value = this.compute(); 11911 } 11912 11913 if (NATIVE_TOUCH_ACTION) { 11914 this.manager.element.style[PREFIXED_TOUCH_ACTION] = value; 11915 } 11916 this.actions = value.toLowerCase().trim(); 11917 }, 11918 11919 /** 11920 * just re-set the touchAction value 11921 */ 11922 update: function() { 11923 this.set(this.manager.options.touchAction); 11924 }, 11925 11926 /** 11927 * compute the value for the touchAction property based on the recognizer's settings 11928 * @returns {String} value 11929 */ 11930 compute: function() { 11931 var actions = []; 11932 each(this.manager.recognizers, function(recognizer) { 11933 if (boolOrFn(recognizer.options.enable, [recognizer])) { 11934 actions = actions.concat(recognizer.getTouchAction()); 11935 } 11936 }); 11937 return cleanTouchActions(actions.join(' ')); 11938 }, 11939 11940 /** 11941 * this method is called on each input cycle and provides the preventing of the browser behavior 11942 * @param {Object} input 11943 */ 11944 preventDefaults: function(input) { 11945 // not needed with native support for the touchAction property 11946 if (NATIVE_TOUCH_ACTION) { 11947 return; 11948 } 11949 11950 var srcEvent = input.srcEvent; 11951 var direction = input.offsetDirection; 11952 11953 // if the touch action did prevented once this session 11954 if (this.manager.session.prevented) { 11955 srcEvent.preventDefault(); 11956 return; 11957 } 11958 11959 var actions = this.actions; 11960 var hasNone = inStr(actions, TOUCH_ACTION_NONE); 11961 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); 11962 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); 11963 11964 if (hasNone || 11965 (hasPanY && direction & DIRECTION_HORIZONTAL) || 11966 (hasPanX && direction & DIRECTION_VERTICAL)) { 11967 return this.preventSrc(srcEvent); 11968 } 11969 }, 11970 11971 /** 11972 * call preventDefault to prevent the browser's default behavior (scrolling in most cases) 11973 * @param {Object} srcEvent 11974 */ 11975 preventSrc: function(srcEvent) { 11976 this.manager.session.prevented = true; 11977 srcEvent.preventDefault(); 11978 } 11979 }; 11980 11981 /** 11982 * when the touchActions are collected they are not a valid value, so we need to clean things up. * 11983 * @param {String} actions 11984 * @returns {*} 11985 */ 11986 function cleanTouchActions(actions) { 11987 // none 11988 if (inStr(actions, TOUCH_ACTION_NONE)) { 11989 return TOUCH_ACTION_NONE; 11990 } 11991 11992 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); 11993 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); 11994 11995 // pan-x and pan-y can be combined 11996 if (hasPanX && hasPanY) { 11997 return TOUCH_ACTION_PAN_X + ' ' + TOUCH_ACTION_PAN_Y; 11998 } 11999 12000 // pan-x OR pan-y 12001 if (hasPanX || hasPanY) { 12002 return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y; 12003 } 12004 12005 // manipulation 12006 if (inStr(actions, TOUCH_ACTION_MANIPULATION)) { 12007 return TOUCH_ACTION_MANIPULATION; 12008 } 12009 12010 return TOUCH_ACTION_AUTO; 12011 } 12012 12013 /** 12014 * Recognizer flow explained; * 12015 * All recognizers have the initial state of POSSIBLE when a input session starts. 12016 * The definition of a input session is from the first input until the last input, with all it's movement in it. * 12017 * Example session for mouse-input: mousedown -> mousemove -> mouseup 12018 * 12019 * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed 12020 * which determines with state it should be. 12021 * 12022 * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to 12023 * POSSIBLE to give it another change on the next cycle. 12024 * 12025 * Possible 12026 * | 12027 * +-----+---------------+ 12028 * | | 12029 * +-----+-----+ | 12030 * | | | 12031 * Failed Cancelled | 12032 * +-------+------+ 12033 * | | 12034 * Recognized Began 12035 * | 12036 * Changed 12037 * | 12038 * Ended/Recognized 12039 */ 12040 var STATE_POSSIBLE = 1; 12041 var STATE_BEGAN = 2; 12042 var STATE_CHANGED = 4; 12043 var STATE_ENDED = 8; 12044 var STATE_RECOGNIZED = STATE_ENDED; 12045 var STATE_CANCELLED = 16; 12046 var STATE_FAILED = 32; 12047 12048 /** 12049 * Recognizer 12050 * Every recognizer needs to extend from this class. 12051 * @constructor 12052 * @param {Object} options 12053 */ 12054 function Recognizer(options) { 12055 this.id = uniqueId(); 12056 12057 this.manager = null; 12058 this.options = merge(options || {}, this.defaults); 12059 12060 // default is enable true 12061 this.options.enable = ifUndefined(this.options.enable, true); 12062 12063 this.state = STATE_POSSIBLE; 12064 12065 this.simultaneous = {}; 12066 this.requireFail = []; 12067 } 12068 12069 Recognizer.prototype = { 12070 /** 12071 * @virtual 12072 * @type {Object} 12073 */ 12074 defaults: {}, 12075 12076 /** 12077 * set options 12078 * @param {Object} options 12079 * @return {Recognizer} 12080 */ 12081 set: function(options) { 12082 extend(this.options, options); 12083 12084 // also update the touchAction, in case something changed about the directions/enabled state 12085 this.manager && this.manager.touchAction.update(); 12086 return this; 12087 }, 12088 12089 /** 12090 * recognize simultaneous with an other recognizer. 12091 * @param {Recognizer} otherRecognizer 12092 * @returns {Recognizer} this 12093 */ 12094 recognizeWith: function(otherRecognizer) { 12095 if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) { 12096 return this; 12097 } 12098 12099 var simultaneous = this.simultaneous; 12100 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); 12101 if (!simultaneous[otherRecognizer.id]) { 12102 simultaneous[otherRecognizer.id] = otherRecognizer; 12103 otherRecognizer.recognizeWith(this); 12104 } 12105 return this; 12106 }, 12107 12108 /** 12109 * drop the simultaneous link. it doesnt remove the link on the other recognizer. 12110 * @param {Recognizer} otherRecognizer 12111 * @returns {Recognizer} this 12112 */ 12113 dropRecognizeWith: function(otherRecognizer) { 12114 if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) { 12115 return this; 12116 } 12117 12118 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); 12119 delete this.simultaneous[otherRecognizer.id]; 12120 return this; 12121 }, 12122 12123 /** 12124 * recognizer can only run when an other is failing 12125 * @param {Recognizer} otherRecognizer 12126 * @returns {Recognizer} this 12127 */ 12128 requireFailure: function(otherRecognizer) { 12129 if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) { 12130 return this; 12131 } 12132 12133 var requireFail = this.requireFail; 12134 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); 12135 if (inArray(requireFail, otherRecognizer) === -1) { 12136 requireFail.push(otherRecognizer); 12137 otherRecognizer.requireFailure(this); 12138 } 12139 return this; 12140 }, 12141 12142 /** 12143 * drop the requireFailure link. it does not remove the link on the other recognizer. 12144 * @param {Recognizer} otherRecognizer 12145 * @returns {Recognizer} this 12146 */ 12147 dropRequireFailure: function(otherRecognizer) { 12148 if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) { 12149 return this; 12150 } 12151 12152 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); 12153 var index = inArray(this.requireFail, otherRecognizer); 12154 if (index > -1) { 12155 this.requireFail.splice(index, 1); 12156 } 12157 return this; 12158 }, 12159 12160 /** 12161 * has require failures boolean 12162 * @returns {boolean} 12163 */ 12164 hasRequireFailures: function() { 12165 return this.requireFail.length > 0; 12166 }, 12167 12168 /** 12169 * if the recognizer can recognize simultaneous with an other recognizer 12170 * @param {Recognizer} otherRecognizer 12171 * @returns {Boolean} 12172 */ 12173 canRecognizeWith: function(otherRecognizer) { 12174 return !!this.simultaneous[otherRecognizer.id]; 12175 }, 12176 12177 /** 12178 * You should use `tryEmit` instead of `emit` directly to check 12179 * that all the needed recognizers has failed before emitting. 12180 * @param {Object} input 12181 */ 12182 emit: function(input) { 12183 var self = this; 12184 var state = this.state; 12185 12186 function emit(withState) { 12187 self.manager.emit(self.options.event + (withState ? stateStr(state) : ''), input); 12188 } 12189 12190 // 'panstart' and 'panmove' 12191 if (state < STATE_ENDED) { 12192 emit(true); 12193 } 12194 12195 emit(); // simple 'eventName' events 12196 12197 // panend and pancancel 12198 if (state >= STATE_ENDED) { 12199 emit(true); 12200 } 12201 }, 12202 12203 /** 12204 * Check that all the require failure recognizers has failed, 12205 * if true, it emits a gesture event, 12206 * otherwise, setup the state to FAILED. 12207 * @param {Object} input 12208 */
vendor: 7,710 bytes, lines 12209-12477
12209 tryEmit: function(input) { 12210 if (this.canEmit()) { 12211 return this.emit(input); 12212 } 12213 // it's failing anyway 12214 this.state = STATE_FAILED; 12215 }, 12216 12217 /** 12218 * can we emit? 12219 * @returns {boolean} 12220 */ 12221 canEmit: function() { 12222 var i = 0; 12223 while (i < this.requireFail.length) { 12224 if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) { 12225 return false; 12226 } 12227 i++; 12228 } 12229 return true; 12230 }, 12231 12232 /** 12233 * update the recognizer 12234 * @param {Object} inputData 12235 */ 12236 recognize: function(inputData) { 12237 // make a new copy of the inputData 12238 // so we can change the inputData without messing up the other recognizers 12239 var inputDataClone = extend({}, inputData); 12240 12241 // is is enabled and allow recognizing? 12242 if (!boolOrFn(this.options.enable, [this, inputDataClone])) { 12243 this.reset(); 12244 this.state = STATE_FAILED; 12245 return; 12246 } 12247 12248 // reset when we've reached the end 12249 if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) { 12250 this.state = STATE_POSSIBLE; 12251 } 12252 12253 this.state = this.process(inputDataClone); 12254 12255 // the recognizer has recognized a gesture 12256 // so trigger an event 12257 if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) { 12258 this.tryEmit(inputDataClone); 12259 } 12260 }, 12261 12262 /** 12263 * return the state of the recognizer 12264 * the actual recognizing happens in this method 12265 * @virtual 12266 * @param {Object} inputData 12267 * @returns {Const} STATE 12268 */ 12269 process: function(inputData) { }, // jshint ignore:line 12270 12271 /** 12272 * return the preferred touch-action 12273 * @virtual 12274 * @returns {Array} 12275 */ 12276 getTouchAction: function() { }, 12277 12278 /** 12279 * called when the gesture isn't allowed to recognize 12280 * like when another is being recognized or it is disabled 12281 * @virtual 12282 */ 12283 reset: function() { } 12284 }; 12285 12286 /** 12287 * get a usable string, used as event postfix 12288 * @param {Const} state 12289 * @returns {String} state 12290 */ 12291 function stateStr(state) { 12292 if (state & STATE_CANCELLED) { 12293 return 'cancel'; 12294 } else if (state & STATE_ENDED) { 12295 return 'end'; 12296 } else if (state & STATE_CHANGED) { 12297 return 'move'; 12298 } else if (state & STATE_BEGAN) { 12299 return 'start'; 12300 } 12301 return ''; 12302 } 12303 12304 /** 12305 * direction cons to string 12306 * @param {Const} direction 12307 * @returns {String} 12308 */ 12309 function directionStr(direction) { 12310 if (direction == DIRECTION_DOWN) { 12311 return 'down'; 12312 } else if (direction == DIRECTION_UP) { 12313 return 'up'; 12314 } else if (direction == DIRECTION_LEFT) { 12315 return 'left'; 12316 } else if (direction == DIRECTION_RIGHT) { 12317 return 'right'; 12318 } 12319 return ''; 12320 } 12321 12322 /** 12323 * get a recognizer by name if it is bound to a manager 12324 * @param {Recognizer|String} otherRecognizer 12325 * @param {Recognizer} recognizer 12326 * @returns {Recognizer} 12327 */ 12328 function getRecognizerByNameIfManager(otherRecognizer, recognizer) { 12329 var manager = recognizer.manager; 12330 if (manager) { 12331 return manager.get(otherRecognizer); 12332 } 12333 return otherRecognizer; 12334 } 12335 12336 /** 12337 * This recognizer is just used as a base for the simple attribute recognizers. 12338 * @constructor 12339 * @extends Recognizer 12340 */ 12341 function AttrRecognizer() { 12342 Recognizer.apply(this, arguments); 12343 } 12344 12345 inherit(AttrRecognizer, Recognizer, { 12346 /** 12347 * @namespace 12348 * @memberof AttrRecognizer 12349 */ 12350 defaults: { 12351 /** 12352 * @type {Number} 12353 * @default 1 12354 */ 12355 pointers: 1 12356 }, 12357 12358 /** 12359 * Used to check if it the recognizer receives valid input, like input.distance > 10. 12360 * @memberof AttrRecognizer 12361 * @param {Object} input 12362 * @returns {Boolean} recognized 12363 */ 12364 attrTest: function(input) { 12365 var optionPointers = this.options.pointers; 12366 return optionPointers === 0 || input.pointers.length === optionPointers; 12367 }, 12368 12369 /** 12370 * Process the input and return the state for the recognizer 12371 * @memberof AttrRecognizer 12372 * @param {Object} input 12373 * @returns {*} State 12374 */ 12375 process: function(input) { 12376 var state = this.state; 12377 var eventType = input.eventType; 12378 12379 var isRecognized = state & (STATE_BEGAN | STATE_CHANGED); 12380 var isValid = this.attrTest(input); 12381 12382 // on cancel input and we've recognized before, return STATE_CANCELLED 12383 if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) { 12384 return state | STATE_CANCELLED; 12385 } else if (isRecognized || isValid) { 12386 if (eventType & INPUT_END) { 12387 return state | STATE_ENDED; 12388 } else if (!(state & STATE_BEGAN)) { 12389 return STATE_BEGAN; 12390 } 12391 return state | STATE_CHANGED; 12392 } 12393 return STATE_FAILED; 12394 } 12395 }); 12396 12397 /** 12398 * Pan 12399 * Recognized when the pointer is down and moved in the allowed direction. 12400 * @constructor 12401 * @extends AttrRecognizer 12402 */ 12403 function PanRecognizer() { 12404 AttrRecognizer.apply(this, arguments); 12405 12406 this.pX = null; 12407 this.pY = null; 12408 } 12409 12410 inherit(PanRecognizer, AttrRecognizer, { 12411 /** 12412 * @namespace 12413 * @memberof PanRecognizer 12414 */ 12415 defaults: { 12416 event: 'pan', 12417 threshold: 10, 12418 pointers: 1, 12419 direction: DIRECTION_ALL 12420 }, 12421 12422 getTouchAction: function() { 12423 var direction = this.options.direction; 12424 var actions = []; 12425 if (direction & DIRECTION_HORIZONTAL) { 12426 actions.push(TOUCH_ACTION_PAN_Y); 12427 } 12428 if (direction & DIRECTION_VERTICAL) { 12429 actions.push(TOUCH_ACTION_PAN_X); 12430 } 12431 return actions; 12432 }, 12433 12434 directionTest: function(input) { 12435 var options = this.options; 12436 var hasMoved = true; 12437 var distance = input.distance; 12438 var direction = input.direction; 12439 var x = input.deltaX; 12440 var y = input.deltaY; 12441 12442 // lock to axis? 12443 if (!(direction & options.direction)) { 12444 if (options.direction & DIRECTION_HORIZONTAL) { 12445 direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT; 12446 hasMoved = x != this.pX; 12447 distance = Math.abs(input.deltaX); 12448 } else { 12449 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN; 12450 hasMoved = y != this.pY; 12451 distance = Math.abs(input.deltaY); 12452 } 12453 } 12454 input.direction = direction; 12455 return hasMoved && distance > options.threshold && direction & options.direction; 12456 }, 12457 12458 attrTest: function(input) { 12459 return AttrRecognizer.prototype.attrTest.call(this, input) && 12460 (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input))); 12461 }, 12462 12463 emit: function(input) { 12464 this.pX = input.deltaX; 12465 this.pY = input.deltaY; 12466 12467 var direction = directionStr(input.direction); 12468 if (direction) { 12469 this.manager.emit(this.options.event + direction, input); 12470 } 12471 12472 this._super.emit.call(this, input); 12473 } 12474 }); 12475 12476 /** 12477 * Pinch
12478 * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out). 12479 * @constructor 12480 * @extends AttrRecognizer 12481 */ 12482 function PinchRecognizer() { 12483 AttrRecognizer.apply(this, arguments); 12484 } 12485 12486 inherit(PinchRecognizer, AttrRecognizer, { 12487 /** 12488 * @namespace 12489 * @memberof PinchRecognizer 12490 */ 12491 defaults: { 12492 event: 'pinch', 12493 threshold: 0, 12494 pointers: 2 12495 }, 12496 12497 getTouchAction: function() { 12498 return [TOUCH_ACTION_NONE]; 12499 }, 12500 12501 attrTest: function(input) { 12502 return this._super.attrTest.call(this, input) && 12503 (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN); 12504 }, 12505 12506 emit: function(input) { 12507 this._super.emit.call(this, input); 12508 if (input.scale !== 1) { 12509 var inOut = input.scale < 1 ? 'in' : 'out'; 12510 this.manager.emit(this.options.event + inOut, input); 12511 } 12512 } 12513 }); 12514 12515 /** 12516 * Press 12517 * Recognized
vendor: 6,473 bytes, lines 12517-12733
12517when the pointer is down for x ms without any movement. 12518 * @constructor 12519 * @extends Recognizer 12520 */ 12521 function PressRecognizer() { 12522 Recognizer.apply(this, arguments); 12523 12524 this._timer = null; 12525 this._input = null; 12526 } 12527 12528 inherit(PressRecognizer, Recognizer, { 12529 /** 12530 * @namespace 12531 * @memberof PressRecognizer 12532 */ 12533 defaults: { 12534 event: 'press', 12535 pointers: 1, 12536 time: 500, // minimal time of the pointer to be pressed 12537 threshold: 5 // a minimal movement is ok, but keep it low 12538 }, 12539 12540 getTouchAction: function() { 12541 return [TOUCH_ACTION_AUTO]; 12542 }, 12543 12544 process: function(input) { 12545 var options = this.options; 12546 var validPointers = input.pointers.length === options.pointers; 12547 var validMovement = input.distance < options.threshold; 12548 var validTime = input.deltaTime > options.time; 12549 12550 this._input = input; 12551 12552 // we only allow little movement 12553 // and we've reached an end event, so a tap is possible 12554 if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) { 12555 this.reset(); 12556 } else if (input.eventType & INPUT_START) { 12557 this.reset(); 12558 this._timer = setTimeoutContext(function() { 12559 this.state = STATE_RECOGNIZED; 12560 this.tryEmit(); 12561 }, options.time, this); 12562 } else if (input.eventType & INPUT_END) { 12563 return STATE_RECOGNIZED; 12564 } 12565 return STATE_FAILED; 12566 }, 12567 12568 reset: function() { 12569 clearTimeout(this._timer); 12570 }, 12571 12572 emit: function(input) { 12573 if (this.state !== STATE_RECOGNIZED) { 12574 return; 12575 } 12576 12577 if (input && (input.eventType & INPUT_END)) { 12578 this.manager.emit(this.options.event + 'up', input); 12579 } else { 12580 this._input.timeStamp = now(); 12581 this.manager.emit(this.options.event, this._input); 12582 } 12583 } 12584 }); 12585 12586 /** 12587 * Rotate 12588 * Recognized when two or more pointer are moving in a circular motion. 12589 * @constructor 12590 * @extends AttrRecognizer 12591 */ 12592 function RotateRecognizer() { 12593 AttrRecognizer.apply(this, arguments); 12594 } 12595 12596 inherit(RotateRecognizer, AttrRecognizer, { 12597 /** 12598 * @namespace 12599 * @memberof RotateRecognizer 12600 */ 12601 defaults: { 12602 event: 'rotate', 12603 threshold: 0, 12604 pointers: 2 12605 }, 12606 12607 getTouchAction: function() { 12608 return [TOUCH_ACTION_NONE]; 12609 }, 12610 12611 attrTest: function(input) { 12612 return this._super.attrTest.call(this, input) && 12613 (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN); 12614 } 12615 }); 12616 12617 /** 12618 * Swipe 12619 * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction. 12620 * @constructor 12621 * @extends AttrRecognizer 12622 */ 12623 function SwipeRecognizer() { 12624 AttrRecognizer.apply(this, arguments); 12625 } 12626 12627 inherit(SwipeRecognizer, AttrRecognizer, { 12628 /** 12629 * @namespace 12630 * @memberof SwipeRecognizer 12631 */ 12632 defaults: { 12633 event: 'swipe', 12634 threshold: 10, 12635 velocity: 0.65, 12636 direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL, 12637 pointers: 1 12638 }, 12639 12640 getTouchAction: function() { 12641 return PanRecognizer.prototype.getTouchAction.call(this); 12642 }, 12643 12644 attrTest: function(input) { 12645 var direction = this.options.direction; 12646 var velocity; 12647 12648 if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) { 12649 velocity = input.velocity; 12650 } else if (direction & DIRECTION_HORIZONTAL) { 12651 velocity = input.velocityX; 12652 } else if (direction & DIRECTION_VERTICAL) { 12653 velocity = input.velocityY; 12654 } 12655 12656 return this._super.attrTest.call(this, input) && 12657 direction & input.direction && 12658 input.distance > this.options.threshold && 12659 abs(velocity) > this.options.velocity && input.eventType & INPUT_END; 12660 }, 12661 12662 emit: function(input) { 12663 var direction = directionStr(input.direction); 12664 if (direction) { 12665 this.manager.emit(this.options.event + direction, input); 12666 } 12667 12668 this.manager.emit(this.options.event, input); 12669 } 12670 }); 12671 12672 /** 12673 * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur 12674 * between the given interval and position. The delay option can be used to recognize multi-taps without firing 12675 * a single tap. 12676 * 12677 * The eventData from the emitted event contains the property `tapCount`, which contains the amount of 12678 * multi-taps being recognized. 12679 * @constructor 12680 * @extends Recognizer 12681 */ 12682 function TapRecognizer() { 12683 Recognizer.apply(this, arguments); 12684 12685 // previous time and center, 12686 // used for tap counting 12687 this.pTime = false; 12688 this.pCenter = false; 12689 12690 this._timer = null; 12691 this._input = null; 12692 this.count = 0; 12693 } 12694 12695 inherit(TapRecognizer, Recognizer, { 12696 /** 12697 * @namespace 12698 * @memberof PinchRecognizer 12699 */ 12700 defaults: { 12701 event: 'tap', 12702 pointers: 1, 12703 taps: 1, 12704 interval: 300, // max time between the multi-tap taps 12705 time: 250, // max time of the pointer to be down (like finger on the screen) 12706 threshold: 2, // a minimal movement is ok, but keep it low 12707 posThreshold: 10 // a multi-tap can be a bit off the initial position 12708 }, 12709 12710 getTouchAction: function() { 12711 return [TOUCH_ACTION_MANIPULATION]; 12712 }, 12713 12714 process: function(input) { 12715 var options = this.options; 12716 12717 var validPointers = input.pointers.length === options.pointers; 12718 var validMovement = input.distance < options.threshold; 12719 var validTouchTime = input.deltaTime < options.time; 12720 12721 this.reset(); 12722 12723 if ((input.eventType & INPUT_START) && (this.count === 0)) { 12724 return this.failTimeout(); 12725 } 12726 12727 // we only allow little movement 12728 // and we've reached an end event, so a tap is possible 12729 if (validMovement && validTouchTime && validPointers) { 12730 if (input.eventType != INPUT_END) { 12731 return this.failTimeout(); 12732 } 12733
vendor: 10,716 bytes, lines 12734-13055
12734 var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true; 12735 var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold; 12736 12737 this.pTime = input.timeStamp; 12738 this.pCenter = input.center; 12739 12740 if (!validMultiTap || !validInterval) { 12741 this.count = 1; 12742 } else { 12743 this.count += 1; 12744 } 12745 12746 this._input = input; 12747 12748 // if tap count matches we have recognized it, 12749 // else it has began recognizing... 12750 var tapCount = this.count % options.taps; 12751 if (tapCount === 0) { 12752 // no failing requirements, immediately trigger the tap event 12753 // or wait as long as the multitap interval to trigger 12754 if (!this.hasRequireFailures()) { 12755 return STATE_RECOGNIZED; 12756 } else { 12757 this._timer = setTimeoutContext(function() { 12758 this.state = STATE_RECOGNIZED; 12759 this.tryEmit(); 12760 }, options.interval, this); 12761 return STATE_BEGAN; 12762 } 12763 } 12764 } 12765 return STATE_FAILED; 12766 }, 12767 12768 failTimeout: function() { 12769 this._timer = setTimeoutContext(function() { 12770 this.state = STATE_FAILED; 12771 }, this.options.interval, this); 12772 return STATE_FAILED; 12773 }, 12774 12775 reset: function() { 12776 clearTimeout(this._timer); 12777 }, 12778 12779 emit: function() { 12780 if (this.state == STATE_RECOGNIZED ) { 12781 this._input.tapCount = this.count; 12782 this.manager.emit(this.options.event, this._input); 12783 } 12784 } 12785 }); 12786 12787 /** 12788 * Simple way to create an manager with a default set of recognizers. 12789 * @param {HTMLElement} element 12790 * @param {Object} [options] 12791 * @constructor 12792 */ 12793 function Hammer(element, options) { 12794 options = options || {}; 12795 options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset); 12796 return new Manager(element, options); 12797 } 12798 12799 /** 12800 * @const {string} 12801 */ 12802 Hammer.VERSION = '2.0.4'; 12803 12804 /** 12805 * default settings 12806 * @namespace 12807 */ 12808 Hammer.defaults = { 12809 /** 12810 * set if DOM events are being triggered. 12811 * But this is slower and unused by simple implementations, so disabled by default. 12812 * @type {Boolean} 12813 * @default false 12814 */ 12815 domEvents: false, 12816 12817 /** 12818 * The value for the touchAction property/fallback. 12819 * When set to `compute` it will magically set the correct value based on the added recognizers. 12820 * @type {String} 12821 * @default compute 12822 */ 12823 touchAction: TOUCH_ACTION_COMPUTE, 12824 12825 /** 12826 * @type {Boolean} 12827 * @default true 12828 */ 12829 enable: true, 12830 12831 /** 12832 * EXPERIMENTAL FEATURE -- can be removed/changed 12833 * Change the parent input target element. 12834 * If Null, then it is being set the to main element. 12835 * @type {Null|EventTarget} 12836 * @default null 12837 */ 12838 inputTarget: null, 12839 12840 /** 12841 * force an input class 12842 * @type {Null|Function} 12843 * @default null 12844 */ 12845 inputClass: null, 12846 12847 /** 12848 * Default recognizer setup when calling `Hammer()` 12849 * When creating a new Manager these will be skipped. 12850 * @type {Array} 12851 */ 12852 preset: [ 12853 // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...] 12854 [RotateRecognizer, { enable: false }], 12855 [PinchRecognizer, { enable: false }, ['rotate']], 12856 [SwipeRecognizer,{ direction: DIRECTION_HORIZONTAL }], 12857 [PanRecognizer, { direction: DIRECTION_HORIZONTAL }, ['swipe']], 12858 [TapRecognizer], 12859 [TapRecognizer, { event: 'doubletap', taps: 2 }, ['tap']], 12860 [PressRecognizer] 12861 ], 12862 12863 /** 12864 * Some CSS properties can be used to improve the working of Hammer. 12865 * Add them to this method and they will be set when creating a new Manager. 12866 * @namespace 12867 */ 12868 cssProps: { 12869 /** 12870 * Disables text selection to improve the dragging gesture. Mainly for desktop browsers. 12871 * @type {String} 12872 * @default 'none' 12873 */ 12874 userSelect: 'none', 12875 12876 /** 12877 * Disable the Windows Phone grippers when pressing an element. 12878 * @type {String} 12879 * @default 'none' 12880 */ 12881 touchSelect: 'none', 12882 12883 /** 12884 * Disables the default callout shown when you touch and hold a touch target. 12885 * On iOS, when you touch and hold a touch target such as a link, Safari displays 12886 * a callout containing information about the link. This property allows you to disable that callout. 12887 * @type {String} 12888 * @default 'none' 12889 */ 12890 touchCallout: 'none', 12891 12892 /** 12893 * Specifies whether zooming is enabled. Used by IE10> 12894 * @type {String} 12895 * @default 'none' 12896 */ 12897 contentZooming: 'none', 12898 12899 /** 12900 * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers. 12901 * @type {String} 12902 * @default 'none' 12903 */ 12904 userDrag: 'none', 12905 12906 /** 12907 * Overrides the highlight color shown when the user taps a link or a JavaScript 12908 * clickable element in iOS. This property obeys the alpha value, if specified. 12909 * @type {String} 12910 * @default 'rgba(0,0,0,0)' 12911 */ 12912 tapHighlightColor: 'rgba(0,0,0,0)' 12913 } 12914 }; 12915 12916 var STOP = 1; 12917 var FORCED_STOP = 2; 12918 12919 /** 12920 * Manager 12921 * @param {HTMLElement} element 12922 * @param {Object} [options] 12923 * @constructor 12924 */ 12925 function Manager(element, options) { 12926 options = options || {}; 12927 12928 this.options = merge(options, Hammer.defaults); 12929 this.options.inputTarget = this.options.inputTarget || element; 12930 12931 this.handlers = {}; 12932 this.session = {}; 12933 this.recognizers = []; 12934 12935 this.element = element; 12936 this.input = createInputInstance(this); 12937 this.touchAction = new TouchAction(this, this.options.touchAction); 12938 12939 toggleCssProps(this, true); 12940 12941 each(options.recognizers, function(item) { 12942 var recognizer = this.add(new (item[0])(item[1])); 12943 item[2] && recognizer.recognizeWith(item[2]); 12944 item[3] && recognizer.requireFailure(item[3]); 12945 }, this); 12946 } 12947 12948 Manager.prototype = { 12949 /** 12950 * set options 12951 * @param {Object} options 12952 * @returns {Manager} 12953 */ 12954 set: function(options) { 12955 extend(this.options, options); 12956 12957 // Options that need a little more setup 12958 if (options.touchAction) { 12959 this.touchAction.update(); 12960 } 12961 if (options.inputTarget) { 12962 // Clean up existing event listeners and reinitialize 12963 this.input.destroy(); 12964 this.input.target = options.inputTarget; 12965 this.input.init(); 12966 } 12967 return this; 12968 }, 12969 12970 /** 12971 * stop recognizing for this session. 12972 * This session will be discarded, when a new [input]start event is fired. 12973 * When forced, the recognizer cycle is stopped immediately. 12974 * @param {Boolean} [force] 12975 */ 12976 stop: function(force) { 12977 this.session.stopped = force ? FORCED_STOP : STOP; 12978 }, 12979 12980 /** 12981 * run the recognizers! 12982 * called by the inputHandler function on every movement of the pointers (touches) 12983 * it walks through all the recognizers and tries to detect the gesture that is being made 12984 * @param {Object} inputData 12985 */ 12986 recognize: function(inputData) { 12987 var session = this.session; 12988 if (session.stopped) { 12989 return; 12990 } 12991 12992 // run the touch-action polyfill 12993 this.touchAction.preventDefaults(inputData); 12994 12995 var recognizer; 12996 var recognizers = this.recognizers; 12997 12998 // this holds the recognizer that is being recognized. 12999 // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED 13000 // if no recognizer is detecting a thing, it is set to `null` 13001 var curRecognizer = session.curRecognizer; 13002 13003 // reset when the last recognizer is recognized 13004 // or when we're in a new session 13005 if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) { 13006 curRecognizer = session.curRecognizer = null; 13007 } 13008 13009 var i = 0; 13010 while (i < recognizers.length) { 13011 recognizer = recognizers[i]; 13012 13013 // find out if we are allowed try to recognize the input for this one. 13014 // 1. allow if the session is NOT forced stopped (see the .stop() method) 13015 // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one 13016 // that is being recognized. 13017 // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer. 13018 // this can be setup with the `recognizeWith()` method on the recognizer. 13019 if (session.stopped !== FORCED_STOP && ( // 1 13020 !curRecognizer || recognizer == curRecognizer || // 2 13021 recognizer.canRecognizeWith(curRecognizer))) { // 3 13022 recognizer.recognize(inputData); 13023 } else { 13024 recognizer.reset(); 13025 } 13026 13027 // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the 13028 // current active recognizer. but only if we don't already have an active recognizer 13029 if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) { 13030 curRecognizer = session.curRecognizer = recognizer; 13031 } 13032 i++; 13033 } 13034 }, 13035 13036 /** 13037 * get a recognizer by its event name. 13038 * @param {Recognizer|String} recognizer 13039 * @returns {Recognizer|Null} 13040 */ 13041 get: function(recognizer) { 13042 if (recognizer instanceof Recognizer) { 13043 return recognizer; 13044 } 13045 13046 var recognizers = this.recognizers; 13047 for (var i = 0; i < recognizers.length; i++) { 13048 if (recognizers[i].options.event == recognizer) { 13049 return recognizers[i]; 13050 } 13051 } 13052 return null; 13053 }, 13054 13055 /**
vendor: 5,886 bytes, lines 13056-13261
13056 * add a recognizer to the manager 13057 * existing recognizers with the same event name will be removed 13058 * @param {Recognizer} recognizer 13059 * @returns {Recognizer|Manager} 13060 */ 13061 add: function(recognizer) { 13062 if (invokeArrayArg(recognizer, 'add', this)) { 13063 return this; 13064 } 13065 13066 // remove existing 13067 var existing = this.get(recognizer.options.event); 13068 if (existing) { 13069 this.remove(existing); 13070 } 13071 13072 this.recognizers.push(recognizer); 13073 recognizer.manager = this; 13074 13075 this.touchAction.update(); 13076 return recognizer; 13077 }, 13078 13079 /** 13080 * remove a recognizer by name or instance 13081 * @param {Recognizer|String} recognizer 13082 * @returns {Manager} 13083 */ 13084 remove: function(recognizer) { 13085 if (invokeArrayArg(recognizer, 'remove', this)) { 13086 return this; 13087 } 13088 13089 var recognizers = this.recognizers; 13090 recognizer = this.get(recognizer); 13091 recognizers.splice(inArray(recognizers, recognizer), 1); 13092 13093 this.touchAction.update(); 13094 return this; 13095 }, 13096 13097 /** 13098 * bind event 13099 * @param {String} events 13100 * @param {Function} handler 13101 * @returns {EventEmitter} this 13102 */ 13103 on: function(events, handler) { 13104 var handlers = this.handlers; 13105 each(splitStr(events), function(event) { 13106 handlers[event] = handlers[event] || []; 13107 handlers[event].push(handler); 13108 }); 13109 return this; 13110 }, 13111 13112 /** 13113 * unbind event, leave emit blank to remove all handlers 13114 * @param {String} events 13115 * @param {Function} [handler] 13116 * @returns {EventEmitter} this 13117 */ 13118 off: function(events, handler) { 13119 var handlers = this.handlers; 13120 each(splitStr(events), function(event) { 13121 if (!handler) { 13122 delete handlers[event]; 13123 } else { 13124 handlers[event].splice(inArray(handlers[event], handler), 1); 13125 } 13126 }); 13127 return this; 13128 }, 13129 13130 /** 13131 * emit event to the listeners 13132 * @param {String} event 13133 * @param {Object} data 13134 */ 13135 emit: function(event, data) { 13136 // we also want to trigger dom events 13137 if (this.options.domEvents) { 13138 triggerDomEvent(event, data); 13139 } 13140 13141 // no handlers, so skip it all 13142 var handlers = this.handlers[event] && this.handlers[event].slice(); 13143 if (!handlers || !handlers.length) { 13144 return; 13145 } 13146 13147 data.type = event; 13148 data.preventDefault = function() { 13149 data.srcEvent.preventDefault(); 13150 }; 13151 13152 var i = 0; 13153 while (i < handlers.length) { 13154 handlers[i](data); 13155 i++; 13156 } 13157 }, 13158 13159 /** 13160 * destroy the manager and unbinds all events 13161 * it doesn't unbind dom events, that is the user own responsibility 13162 */ 13163 destroy: function() { 13164 this.element && toggleCssProps(this, false); 13165 13166 this.handlers = {}; 13167 this.session = {}; 13168 this.input.destroy(); 13169 this.element = null; 13170 } 13171 }; 13172 13173 /** 13174 * add/remove the css properties as defined in manager.options.cssProps 13175 * @param {Manager} manager 13176 * @param {Boolean} add 13177 */ 13178 function toggleCssProps(manager, add) { 13179 var element = manager.element; 13180 each(manager.options.cssProps, function(value, name) { 13181 element.style[prefixed(element.style, name)] = add ? value : ''; 13182 }); 13183 } 13184 13185 /** 13186 * trigger dom event 13187 * @param {String} event 13188 * @param {Object} data 13189 */ 13190 function triggerDomEvent(event, data) { 13191 var gestureEvent = document.createEvent('Event'); 13192 gestureEvent.initEvent(event, true, true); 13193 gestureEvent.gesture = data; 13194 data.target.dispatchEvent(gestureEvent); 13195 } 13196 13197 extend(Hammer, { 13198 INPUT_START: INPUT_START, 13199 INPUT_MOVE: INPUT_MOVE, 13200 INPUT_END: INPUT_END, 13201 INPUT_CANCEL: INPUT_CANCEL, 13202 13203 STATE_POSSIBLE: STATE_POSSIBLE, 13204 STATE_BEGAN: STATE_BEGAN, 13205 STATE_CHANGED: STATE_CHANGED, 13206 STATE_ENDED: STATE_ENDED, 13207 STATE_RECOGNIZED: STATE_RECOGNIZED, 13208 STATE_CANCELLED: STATE_CANCELLED, 13209 STATE_FAILED: STATE_FAILED, 13210 13211 DIRECTION_NONE: DIRECTION_NONE, 13212 DIRECTION_LEFT: DIRECTION_LEFT, 13213 DIRECTION_RIGHT: DIRECTION_RIGHT, 13214 DIRECTION_UP: DIRECTION_UP, 13215 DIRECTION_DOWN: DIRECTION_DOWN, 13216 DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL, 13217 DIRECTION_VERTICAL: DIRECTION_VERTICAL, 13218 DIRECTION_ALL: DIRECTION_ALL, 13219 13220 Manager: Manager, 13221 Input: Input, 13222 TouchAction: TouchAction, 13223 13224 TouchInput: TouchInput, 13225 MouseInput: MouseInput, 13226 PointerEventInput: PointerEventInput, 13227 TouchMouseInput: TouchMouseInput, 13228 SingleTouchInput: SingleTouchInput, 13229 13230 Recognizer: Recognizer, 13231 AttrRecognizer: AttrRecognizer, 13232 Tap: TapRecognizer, 13233 Pan: PanRecognizer, 13234 Swipe: SwipeRecognizer, 13235 Pinch: PinchRecognizer, 13236 Rotate: RotateRecognizer, 13237 Press: PressRecognizer, 13238 13239 on: addEventListeners, 13240 off: removeEventListeners, 13241 each: each, 13242 merge: merge, 13243 extend: extend, 13244 inherit: inherit, 13245 bindFn: bindFn, 13246 prefixed: prefixed 13247 }); 13248 13249 if ("function" == TYPE_FUNCTION && __webpack_require__(23)) { 13250 !(__WEBPACK_AMD_DEFINE_RESULT__ = function() { 13251 return Hammer; 13252 }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); 13253 } else if (typeof module != 'undefined' && module.exports) { 13254 module.exports = Hammer; 13255 } else { 13256 window[exportName] = Hammer; 13257 } 13258 13259 })(window, document, 'Hammer'); 13260 13261
13262/***/ }, 13263/* 23 */ 13264/***/ function(module, exports) { 13265 13266 /* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {module.exports = __webpack_amd_options__; 13267 13268 /* WEBPACK VAR INJECTION */}.call(exports, {})) 13269 13270/***/ }, 13271/* 24 */ 13272/***/ function(module, exports, __webpack_require__) { 13273 13274 'use strict'; 13275 13276 var util = __webpack_require__(1); 13277 var hammerUtil = __webpack_require__(25); 13278 var moment = __webpack_require__(2); 13279 var Component = __webpack_require__(26); 13280 var DateUtil = __webpack_require__(27); 13281 13282 /** 13283 * @constructor Range 13284 * A Range controls a numeric range with a start and end value. 13285 * The Range adjusts the range based on mouse events or programmatic changes, 13286 * and triggers events when the range is changing or has been changed. 13287 * @param {{dom: Object, domProps: Object, emitter: Emitter}} body 13288 * @param {Object} [options] See description at Range.setOptions 13289 */ 13290 function Range(body, options) { 13291 var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0); 13292 this.start = now.clone().add(-3, 'days').valueOf(); // Number 13293 this.end = now.clone().add(4, 'days').valueOf(); // Number 13294 13295 this.body = body; 13296 this.deltaDifference = 0; 13297 this.scaleOffset = 0; 13298 this.startToFront = false; 13299 this.endToFront = true; 13300 13301 // default options 13302 this.defaultOptions = { 13303 start: null, 13304 end: null, 13305 moment: moment, 13306 direction: 'horizontal', // 'horizontal' or 'vertical' 13307 moveable: true, 13308 zoomable: true, 13309 min: null, 13310 max: null, 13311 zoomMin: 10, // milliseconds 13312 zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000 // milliseconds 13313 }; 13314 this.options = util.extend({}, this.defaultOptions); 13315 13316 this.props = { 13317 touch: {} 13318 }; 13319 this.animationTimer = null; 13320 13321 // drag listeners for dragging 13322 this.body.emitter.on('panstart', this._onDragStart.bind(this)); 13323 this.body.emitter.on('panmove', this._onDrag.bind(this)); 13324 this.body.emitter.on('panend', this._onDragEnd.bind(this)); 13325 13326 // mouse wheel for zooming 13327 this.body.emitter.on('mousewheel', this._onMouseWheel.bind(this)); 13328 13329 // pinch to zoom 13330 this.body.emitter.on('touch', this._onTouch.bind(this)); 13331 this.body.emitter.on('pinch', this._onPinch.bind(this)); 13332 13333 this.setOptions(options); 13334 } 13335 13336 Range.prototype = new Component(); 13337 13338 /** 13339 * Set options for the range controller 13340 * @param {Object} options Available options: 13341 * {Number | Date | String} start Start date for the range 13342 * {Number | Date | String} end End date for the range 13343 * {Number} min Minimum value for start 13344 * {Number} max Maximum value for end 13345 * {Number} zoomMin Set a minimum value for 13346 * (end - start). 13347 * {Number} zoomMax Set a maximum value for 13348 * (end - start). 13349 * {Boolean} moveable Enable moving of the range 13350 * by dragging. True by default 13351 * {Boolean} zoomable Enable zooming of the range 13352 * by pinching/scrolling. True by default 13353 */ 13354 Range.prototype.setOptions = function (options) { 13355 if (options) { 13356 // copy the options that we know 13357 var fields = ['direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable', 'moment', 'activate', 'hiddenDates', 'zoomKey']; 13358 util.selectiveExtend(fields, this.options, options); 13359 13360 if ('start' in options || 'end' in options) { 13361 // apply a new range. both start and end are optional 13362 this.setRange(options.start, options.end); 13363 } 13364 } 13365 }; 13366 13367 /** 13368 * Test whether direction has a valid value 13369 * @param {String} direction 'horizontal' or 'vertical' 13370 */ 13371 function validateDirection(direction) { 13372 if (direction != 'horizontal' && direction != 'vertical') { 13373 throw new TypeError('Unknown direction "' + direction + '". ' + 'Choose "horizontal" or "vertical".'); 13374 } 13375 } 13376 13377 /** 13378 * Set a new start and end range 13379 * @param {Date | Number | String} [start] 13380 * @param {Date | Number | String} [end] 13381 * @param {boolean | {duration: number, easingFunction: string}} [animation=false] 13382 * If true (default), the range is animated 13383 * smoothly to the new window. An object can be 13384 * provided to specify duration and easing function. 13385 * Default duration is 500 ms, and default easing 13386 * function is 'easeInOutQuad'. 13387 * @param {Boolean} [byUser=false] 13388 * 13389 */ 13390 Range.prototype.setRange = function (start, end, animation, byUser) { 13391 if (byUser !== true) { 13392 byUser = false;
13393 } 13394 var finalStart = start != undefined ? util.convert(start, 'Date').valueOf() : null; 13395 var finalEnd = end != undefined ? util.convert(end, 'Date').valueOf() : null; 13396 this._cancelAnimation(); 13397 13398 if (animation) { 13399 // true or an Object 13400 var me = this; 13401 var initStart = this.start; 13402 var initEnd = this.end; 13403 var duration = typeof animation === 'object' && 'duration' in animation ? animation.duration : 500; 13404 var easingName = typeof animation === 'object' && 'easingFunction' in animation ? animation.easingFunction : 'easeInOutQuad'; 13405 var easingFunction = util.easingFunctions[easingName]; 13406 if (!easingFunction) { 13407 throw new Error('Unknown easing function ' + JSON.stringify(easingName) + '. ' + 'Choose from: ' + Object.keys(util.easingFunctions).join(', ')); 13408 } 13409 13410 var initTime = new Date().valueOf(); 13411 var anyChanged = false; 13412 13413 var next = function next() { 13414 if (!me.props.touch.dragging) { 13415 var now = new Date().valueOf(); 13416 var time = now - initTime; 13417 var ease = easingFunction(time / duration); 13418 var done = time > duration; 13419 var s = done || finalStart === null ? finalStart : initStart + (finalStart - initStart) * ease; 13420 var e = done || finalEnd === null ? finalEnd : initEnd + (finalEnd - initEnd) * ease; 13421 13422 changed = me._applyRange(s, e); 13423 DateUtil.updateHiddenDates(me.options.moment, me.body, me.options.hiddenDates); 13424 anyChanged = anyChanged || changed; 13425 if (changed) { 13426 me.body.emitter.emit('rangechange', { start: new Date(me.start), end: new Date(me.end), byUser: byUser }); 13427 } 13428 13429 if (done) { 13430 if (anyChanged) { 13431 me.body.emitter.emit('rangechanged', { start: new Date(me.start), end: new Date(me.end), byUser: byUser }); 13432 } 13433 } else { 13434 // animate with as high as possible frame rate, leave 20 ms in between 13435 // each to prevent the browser from blocking 13436 me.animationTimer = setTimeout(next, 20); 13437 } 13438 } 13439 }; 13440 13441 return next(); 13442 } else { 13443 var changed = this._applyRange(finalStart, finalEnd); 13444 DateUtil.updateHiddenDates(this.options.moment, this.body, this.options.hiddenDates); 13445 if (changed) { 13446 var params = { start: new Date(this.start), end: new Date(this.end), byUser: byUser }; 13447 this.body.emitter.emit('rangechange', params); 13448 this.body.emitter.emit('rangechanged', params); 13449 } 13450 } 13451 }; 13452 13453 /** 13454 * Stop an animation 13455 * @private 13456 */ 13457 Range.prototype._cancelAnimation = function () { 13458 if (this.animationTimer) { 13459 clearTimeout(this.animationTimer); 13460 this.animationTimer = null; 13461 } 13462 }; 13463 13464 /** 13465 * Set a new start and end range. This method is the same as setRange, but 13466 * does not trigger a range change and range changed event, and it returns 13467 * true when the range is changed 13468 * @param {Number} [start] 13469 * @param {Number} [end] 13470 * @return {Boolean} changed 13471 * @private 13472 */ 13473 Range.prototype._applyRange = function (start, end) { 13474 var newStart = start != null ? util.convert(start, 'Date').valueOf() : this.start, 13475 newEnd = end != null ? util.convert(end, 'Date').valueOf() : this.end, 13476 max = this.options.max != null ? util.convert(this.options.max, 'Date').valueOf() : null, 13477 min = this.options.min != null ? util.convert(this.options.min, 'Date').valueOf() : null, 13478 diff; 13479 13480 // check for valid number 13481 if (isNaN(newStart) || newStart === null) { 13482 throw new Error('Invalid start "' + start + '"'); 13483 } 13484 if (isNaN(newEnd) || newEnd === null) { 13485 throw new Error('Invalid end "' + end + '"'); 13486 } 13487 13488 // prevent start < end 13489 if (newEnd < newStart) { 13490 newEnd = newStart; 13491 } 13492 13493 // prevent start < min 13494 if (min !== null) { 13495 if (newStart < min) { 13496 diff = min - newStart; 13497 newStart += diff; 13498 newEnd += diff; 13499 13500 // prevent end > max 13501 if (max != null) { 13502 if (newEnd > max) { 13503 newEnd = max; 13504 } 13505 } 13506 } 13507 } 13508 13509 // prevent end > max 13510 if (max !== null) { 13511 if (newEnd > max) { 13512 diff = newEnd - max; 13513 newStart -= diff; 13514 newEnd -= diff; 13515 13516 // prevent start < min 13517 if (min != null) { 13518 if (newStart < min) { 13519 newStart = min; 13520 } 13521 } 13522 } 13523 } 13524 13525 // prevent (end-start) < zoomMin 13526 if (this.options.zoomMin !== null) { 13527 var zoomMin = parseFloat(this.options.zoomMin); 13528 if (zoomMin < 0) { 13529 zoomMin = 0; 13530 } 13531 if (newEnd - newStart < zoomMin) {
vendor: 6,184 bytes, lines 13532-13721
13532 if (this.end - this.start === zoomMin && newStart > this.start && newEnd < this.end) { 13533 // ignore this action, we are already zoomed to the minimum 13534 newStart = this.start; 13535 newEnd = this.end; 13536 } else { 13537 // zoom to the minimum 13538 diff = zoomMin - (newEnd - newStart); 13539 newStart -= diff / 2; 13540 newEnd += diff / 2; 13541 } 13542 } 13543 } 13544 13545 // prevent (end-start) > zoomMax 13546 if (this.options.zoomMax !== null) { 13547 var zoomMax = parseFloat(this.options.zoomMax); 13548 if (zoomMax < 0) { 13549 zoomMax = 0; 13550 } 13551 13552 if (newEnd - newStart > zoomMax) { 13553 if (this.end - this.start === zoomMax && newStart < this.start && newEnd > this.end) { 13554 // ignore this action, we are already zoomed to the maximum 13555 newStart = this.start; 13556 newEnd = this.end; 13557 } else { 13558 // zoom to the maximum 13559 diff = newEnd - newStart - zoomMax; 13560 newStart += diff / 2; 13561 newEnd -= diff / 2; 13562 } 13563 } 13564 } 13565 13566 var changed = this.start != newStart || this.end != newEnd; 13567 13568 // if the new range does NOT overlap with the old range, emit checkRangedItems to avoid not showing ranged items (ranged meaning has end time, not necessarily of type Range) 13569 if (!(newStart >= this.start && newStart <= this.end || newEnd >= this.start && newEnd <= this.end) && !(this.start >= newStart && this.start <= newEnd || this.end >= newStart && this.end <= newEnd)) { 13570 this.body.emitter.emit('checkRangedItems'); 13571 } 13572 13573 this.start = newStart; 13574 this.end = newEnd; 13575 return changed; 13576 }; 13577 13578 /** 13579 * Retrieve the current range. 13580 * @return {Object} An object with start and end properties 13581 */ 13582 Range.prototype.getRange = function () { 13583 return { 13584 start: this.start, 13585 end: this.end 13586 }; 13587 }; 13588 13589 /** 13590 * Calculate the conversion offset and scale for current range, based on 13591 * the provided width 13592 * @param {Number} width 13593 * @returns {{offset: number, scale: number}} conversion 13594 */ 13595 Range.prototype.conversion = function (width, totalHidden) { 13596 return Range.conversion(this.start, this.end, width, totalHidden); 13597 }; 13598 13599 /** 13600 * Static method to calculate the conversion offset and scale for a range, 13601 * based on the provided start, end, and width 13602 * @param {Number} start 13603 * @param {Number} end 13604 * @param {Number} width 13605 * @returns {{offset: number, scale: number}} conversion 13606 */ 13607 Range.conversion = function (start, end, width, totalHidden) { 13608 if (totalHidden === undefined) { 13609 totalHidden = 0; 13610 } 13611 if (width != 0 && end - start != 0) { 13612 return { 13613 offset: start, 13614 scale: width / (end - start - totalHidden) 13615 }; 13616 } else { 13617 return { 13618 offset: 0, 13619 scale: 1 13620 }; 13621 } 13622 }; 13623 13624 /** 13625 * Start dragging horizontally or vertically 13626 * @param {Event} event 13627 * @private 13628 */ 13629 Range.prototype._onDragStart = function (event) { 13630 this.deltaDifference = 0; 13631 this.previousDelta = 0; 13632 13633 // only allow dragging when configured as movable 13634 if (!this.options.moveable) return; 13635 13636 // only start dragging when the mouse is inside the current range 13637 if (!this._isInsideRange(event)) return; 13638 13639 // refuse to drag when we where pinching to prevent the timeline make a jump 13640 // when releasing the fingers in opposite order from the touch screen 13641 if (!this.props.touch.allowDragging) return; 13642 13643 this.props.touch.start = this.start; 13644 this.props.touch.end = this.end; 13645 this.props.touch.dragging = true; 13646 13647 if (this.body.dom.root) { 13648 this.body.dom.root.style.cursor = 'move'; 13649 } 13650 }; 13651 13652 /** 13653 * Perform dragging operation 13654 * @param {Event} event 13655 * @private 13656 */ 13657 Range.prototype._onDrag = function (event) { 13658 if (!this.props.touch.dragging) return; 13659 13660 // only allow dragging when configured as movable 13661 if (!this.options.moveable) return; 13662 13663 // TODO: this may be redundant in hammerjs2 13664 // refuse to drag when we where pinching to prevent the timeline make a jump 13665 // when releasing the fingers in opposite order from the touch screen 13666 if (!this.props.touch.allowDragging) return; 13667 13668 var direction = this.options.direction; 13669 validateDirection(direction); 13670 var delta = direction == 'horizontal' ? event.deltaX : event.deltaY; 13671 delta -= this.deltaDifference; 13672 var interval = this.props.touch.end - this.props.touch.start; 13673 13674 // normalize dragging speed if cutout is in between. 13675 var duration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end); 13676 interval -= duration; 13677 13678 var width = direction == 'horizontal' ? this.body.domProps.center.width : this.body.domProps.center.height; 13679 var diffRange = -delta / width * interval; 13680 var newStart = this.props.touch.start + diffRange; 13681 var newEnd = this.props.touch.end + diffRange; 13682 13683 // snapping times away from hidden zones 13684 var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, this.previousDelta - delta, true); 13685 var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, this.previousDelta - delta, true); 13686 if (safeStart != newStart || safeEnd != newEnd) { 13687 this.deltaDifference += delta; 13688 this.props.touch.start = safeStart; 13689 this.props.touch.end = safeEnd; 13690 this._onDrag(event); 13691 return; 13692 } 13693 13694 this.previousDelta = delta; 13695 this._applyRange(newStart, newEnd); 13696 13697 // fire a rangechange event 13698 this.body.emitter.emit('rangechange', { 13699 start: new Date(this.start), 13700 end: new Date(this.end), 13701 byUser: true 13702 }); 13703 }; 13704 13705 /** 13706 * Stop dragging operation 13707 * @param {event} event 13708 * @private 13709 */ 13710 Range.prototype._onDragEnd = function (event) { 13711 if (!this.props.touch.dragging) return; 13712 13713 // only allow dragging when configured as movable 13714 if (!this.options.moveable) return; 13715 13716 // TODO: this may be redundant in hammerjs2 13717 // refuse to drag when we where pinching to prevent the timeline make a jump 13718 // when releasing the fingers in opposite order from the touch screen 13719 if (!this.props.touch.allowDragging) return; 13720 13721 this.props.touch.dragging = false;
13722 if (this.body.dom.root) { 13723 this.body.dom.root.style.cursor = 'auto'; 13724 } 13725 13726 // fire a rangechanged event 13727 this.body.emitter.emit('rangechanged', { 13728 start: new Date(this.start), 13729 end: new Date(this.end), 13730 byUser: true 13731 }); 13732 }; 13733 13734 /** 13735 * Event handler for mouse wheel event, used to zoom 13736 * Code from http://adomas.org/javascript-mouse-wheel/ 13737 * @param {Event} event 13738 * @private 13739 */ 13740 Range.prototype._onMouseWheel = function (event) { 13741 // only allow zooming when configured as zoomable and moveable 13742 if (!(this.options.zoomable && this.options.moveable)) return; 13743 13744 // only zoom when the mouse is inside the current range 13745 if (!this._isInsideRange(event)) return; 13746 13747 // only zoom when the according key is pressed and the zoomKey option is set 13748 if (this.options.zoomKey && !event[this.options.zoomKey]) return; 13749 13750 // retrieve delta 13751 var delta = 0; 13752 if (event.wheelDelta) { 13753 /* IE/Opera. */ 13754 delta = event.wheelDelta / 120; 13755 } else if (event.detail) { 13756 /* Mozilla case. */ 13757 // In Mozilla, sign of delta is different than in IE. 13758 // Also, delta is multiple of 3. 13759 delta = -event.detail / 3; 13760 } 13761 13762 // If delta is nonzero, handle it. 13763 // Basically, delta is now positive if wheel was scrolled up, 13764 // and negative, if wheel was scrolled down. 13765 if (delta) { 13766 // perform the zoom action. Delta is normally 1 or -1 13767 13768 // adjust a negative delta such that zooming in with delta 0.1 13769 // equals zooming out with a delta -0.1 13770 var scale; 13771 if (delta < 0) { 13772 scale = 1 - delta / 5; 13773 } else { 13774 scale = 1 / (1 + delta / 5); 13775 } 13776 13777 // calculate center, the date to zoom around 13778 var pointer = getPointer({ x: event.clientX, y: event.clientY }, this.body.dom.center); 13779 var pointerDate = this._pointerToDate(pointer); 13780 13781 this.zoom(scale, pointerDate, delta); 13782 } 13783 13784 // Prevent default actions caused by mouse wheel 13785 // (else the page and timeline both zoom and scroll) 13786 event.preventDefault(); 13787 }; 13788 13789 /** 13790 * Start of a touch gesture 13791 * @private 13792 */ 13793 Range.prototype._onTouch = function (event) { 13794 this.props.touch.start = this.start; 13795 this.props.touch.end = this.end; 13796 this.props.touch.allowDragging = true; 13797 this.props.touch.center = null; 13798 this.scaleOffset = 0; 13799 this.deltaDifference = 0; 13800 }; 13801 13802 /** 13803 * Handle pinch event 13804 * @param {Event} event 13805 * @private 13806 */ 13807 Range.prototype._onPinch = function (event) { 13808 // only allow zooming when configured as zoomable and moveable 13809 if (!(this.options.zoomable && this.options.moveable)) return; 13810 13811 this.props.touch.allowDragging = false; 13812 13813 if (!this.props.touch.center) { 13814 this.props.touch.center = getPointer(event.center, this.body.dom.center); 13815 } 13816 13817 var scale = 1 / (event.scale + this.scaleOffset); 13818 var centerDate = this._pointerToDate(this.props.touch.center); 13819 13820 var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end); 13821 var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.options.moment, this.body.hiddenDates, this, centerDate); 13822 var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore; 13823 13824 // calculate new start and end 13825 var newStart = centerDate - hiddenDurationBefore + (this.props.touch.start - (centerDate - hiddenDurationBefore)) * scale; 13826 var newEnd = centerDate + hiddenDurationAfter + (this.props.touch.end - (centerDate + hiddenDurationAfter)) * scale; 13827 13828 // snapping times away from hidden zones 13829 this.startToFront = 1 - scale <= 0; // used to do the right auto correction with periodic hidden times 13830 this.endToFront = scale - 1 <= 0; // used to do the right auto correction with periodic hidden times 13831 13832 var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, 1 - scale, true); 13833 var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, scale - 1, true); 13834 if (safeStart != newStart || safeEnd != newEnd) { 13835 this.props.touch.start = safeStart; 13836 this.props.touch.end = safeEnd; 13837 this.scaleOffset = 1 - event.scale; 13838 newStart = safeStart; 13839 newEnd = safeEnd; 13840 } 13841 13842 this.setRange(newStart, newEnd, false, true); 13843 13844 this.startToFront = false; // revert to default 13845 this.endToFront = true; // revert to default 13846 }; 13847 13848 /** 13849 * Test whether the mouse from a mouse event is inside the visible window, 13850 * between the current start and end date 13851 * @param {Object} event 13852 * @return {boolean} Returns true when inside the visible window 13853 * @private 13854 */ 13855 Range.prototype._isInsideRange = function (event) { 13856 // calculate the time where the mouse is, check whether inside 13857 // and no scroll action should happen. 13858 var clientX = event.center ? event.center.x : event.clientX; 13859 var x = clientX - util.getAbsoluteLeft(this.body.dom.centerContainer); 13860 var time = this.body.util.toTime(x); 13861 13862 return time >= this.start && time <= this.end; 13863 }; 13864 13865 /** 13866 * Helper function to calculate the center date for zooming 13867 * @param {{x: Number, y: Number}} pointer 13868 * @return {number} date 13869 * @private 13870 */ 13871 Range.prototype._pointerToDate = function (pointer) { 13872 var conversion; 13873 var direction = this.options.direction; 13874 13875 validateDirection(direction); 13876 13877 if (direction == 'horizontal') { 13878 return this.body.util.toTime(pointer.x).valueOf(); 13879 } else { 13880 var height = this.body.domProps.center.height; 13881 conversion = this.conversion(height); 13882 return pointer.y / conversion.scale + conversion.offset; 13883 } 13884 }; 13885 13886 /** 13887 * Get the pointer location relative to the location of the dom element 13888 * @param {{x: Number, y: Number}} touch 13889 * @param {Element} element HTML DOM element 13890 * @return {{x: Number, y: Number}} pointer 13891 * @private 13892 */ 13893 function getPointer(touch, element) { 13894 return { 13895 x: touch.x - util.getAbsoluteLeft(element), 13896 y: touch.y - util.getAbsoluteTop(element) 13897 }; 13898 } 13899 13900 /** 13901 * Zoom the range the given scale in or out. Start and end date will
13902 * be adjusted, and the timeline will be redrawn. You can optionally give a 13903 * date around which to zoom. 13904 * For example, try scale = 0.9 or 1.1 13905 * @param {Number} scale Scaling factor. Values above 1 will zoom out, 13906 * values below 1 will zoom in. 13907 * @param {Number} [center] Value representing a date around which will 13908 * be zoomed. 13909 */ 13910 Range.prototype.zoom = function (scale, center, delta) { 13911 // if centerDate is not provided, take it half between start Date and end Date 13912 if (center == null) { 13913 center = (this.start + this.end) / 2; 13914 } 13915 13916 var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end); 13917 var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.options.moment, this.body.hiddenDates, this, center); 13918 var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore; 13919 13920 // calculate new start and end 13921 var newStart = center - hiddenDurationBefore + (this.start - (center - hiddenDurationBefore)) * scale; 13922 var newEnd = center + hiddenDurationAfter + (this.end - (center + hiddenDurationAfter)) * scale; 13923 13924 // snapping times away from hidden zones 13925 this.startToFront = delta > 0 ? false : true; // used to do the right autocorrection with periodic hidden times 13926 this.endToFront = -delta > 0 ? false : true; // used to do the right autocorrection with periodic hidden times 13927 var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, delta, true); 13928 var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, -delta, true); 13929 if (safeStart != newStart || safeEnd != newEnd) { 13930 newStart = safeStart; 13931 newEnd = safeEnd; 13932 } 13933 13934 this.setRange(newStart, newEnd, false, true); 13935 13936 this.startToFront = false; // revert to default 13937 this.endToFront = true; // revert to default 13938 }; 13939 13940 /** 13941 * Move the range with a given delta to the left or right. Start and end 13942 * value will be adjusted. For example, try delta = 0.1 or -0.1 13943 * @param {Number} delta Moving amount. Positive value will move right, 13944 * negative value will move left 13945 */ 13946 Range.prototype.move = function (delta) { 13947 // zoom start Date and end Date relative to the centerDate 13948 var diff = this.end - this.start; 13949 13950 // apply new values 13951 var newStart = this.start + diff * delta; 13952 var newEnd = this.end + diff * delta; 13953 13954 // TODO: reckon with min and max range 13955 13956 this.start = newStart; 13957 this.end = newEnd; 13958 }; 13959 13960 /** 13961 * Move the range to a new center point 13962 * @param {Number} moveTo New center point of the range 13963 */ 13964 Range.prototype.moveTo = function (moveTo) { 13965 var center = (this.start + this.end) / 2; 13966 13967 var diff = center - moveTo; 13968 13969 // calculate new start and end 13970 var newStart = this.start - diff; 13971 var newEnd = this.end - diff; 13972 13973 this.setRange(newStart, newEnd); 13974 }; 13975 13976 module.exports = Range; 13977 13978/***/ }, 13979/* 25 */ 13980/***/ function(module, exports, __webpack_require__) { 13981 13982 'use strict'; 13983 13984 var Hammer = __webpack_require__(20); 13985 13986 /** 13987 * Register a touch event, taking place before a gesture 13988 * @param {Hammer} hammer A hammer instance 13989 * @param {function} callback Callback, called as callback(event) 13990 */ 13991 exports.onTouch = function (hammer, callback) { 13992 callback.inputHandler = function (event) { 13993 if (event.isFirst && !isTouching) { 13994 callback(event); 13995 13996 isTouching = true; 13997 setTimeout(function () { 13998 isTouching = false; 13999 }, 0); 14000 } 14001 }; 14002 14003 hammer.on('hammer.input', callback.inputHandler); 14004 }; 14005 14006 // isTouching is true while a touch action is being emitted 14007 // this is a hack to prevent `touch` from being fired twice 14008 var isTouching = false; 14009 14010 /** 14011 * Register a release event, taking place after a gesture 14012 * @param {Hammer} hammer A hammer instance 14013 * @param {function} callback Callback, called as callback(event) 14014 */ 14015 exports.onRelease = function (hammer, callback) { 14016 callback.inputHandler = function (event) { 14017 if (event.isFinal && !isReleasing) { 14018 callback(event); 14019 14020 isReleasing = true; 14021 setTimeout(function () { 14022 isReleasing = false;
14023 }, 0); 14024 } 14025 }; 14026 14027 return hammer.on('hammer.input', callback.inputHandler); 14028 }; 14029 14030 // isReleasing is true while a release action is being emitted 14031 // this is a hack to prevent `release` from being fired twice 14032 var isReleasing = false; 14033 14034 /** 14035 * Unregister a touch event, taking place before a gesture 14036 * @param {Hammer} hammer A hammer instance 14037 * @param {function} callback Callback, called as callback(event) 14038 */ 14039 exports.offTouch = function (hammer, callback) { 14040 hammer.off('hammer.input', callback.inputHandler); 14041 }; 14042 14043 /** 14044 * Unregister a release event, taking place before a gesture 14045 * @param {Hammer} hammer A hammer instance 14046 * @param {function} callback Callback, called as callback(event) 14047 */ 14048 exports.offRelease = exports.offTouch; 14049 14050/***/ }, 14051/* 26 */ 14052/***/ function(module, exports) { 14053 14054 /** 14055 * Prototype for visual components 14056 * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} [body] 14057 * @param {Object} [options] 14058 */ 14059 "use strict"; 14060 14061 function Component(body, options) { 14062 this.options = null; 14063 this.props = null; 14064 } 14065 14066 /** 14067 * Set options for the component. The new options will be merged into the 14068 * current options. 14069 * @param {Object} options 14070 */ 14071 Component.prototype.setOptions = function (options) { 14072 if (options) { 14073 util.extend(this.options, options); 14074 } 14075 }; 14076 14077 /** 14078 * Repaint the component 14079 * @return {boolean} Returns true if the component is resized 14080 */ 14081 Component.prototype.redraw = function () { 14082 // should be implemented by the component 14083 return false; 14084 }; 14085 14086 /** 14087 * Destroy the component. Cleanup DOM and event listeners 14088 */ 14089 Component.prototype.destroy = function () {}; 14090 14091 /** 14092 * Test whether the component is resized since the last time _isResized() was 14093 * called. 14094 * @return {Boolean} Returns true if the component is resized 14095 * @protected 14096 */ 14097 Component.prototype._isResized = function () { 14098 var resized = this.props._previousWidth !== this.props.width || this.props._previousHeight !== this.props.height; 14099 14100 this.props._previousWidth = this.props.width; 14101 this.props._previousHeight = this.props.height; 14102 14103 return resized; 14104 }; 14105 14106 module.exports = Component; 14107 14108 // should be implemented by the component 14109 14110/***/ }, 14111/* 27 */ 14112/***/ function(module, exports) { 14113 14114 14115 /** 14116 * used in Core to convert the options into a volatile variable 14117 * 14118 * @param {function} moment 14119 * @param {Object} body 14120 * @param {Array | Object} hiddenDates 14121 */ 14122 "use strict"; 14123 14124 exports.convertHiddenOptions = function (moment, body, hiddenDates) { 14125 if (hiddenDates && !Array.isArray(hiddenDates)) { 14126 return exports.convertHiddenOptions(moment, body, [hiddenDates]); 14127 } 14128 14129 body.hiddenDates = []; 14130 if (hiddenDates) { 14131 if (Array.isArray(hiddenDates) == true) { 14132 for (var i = 0; i < hiddenDates.length; i++) { 14133 if (hiddenDates[i].repeat === undefined) { 14134 var dateItem = {}; 14135 dateItem.start = moment(hiddenDates[i].start).toDate().valueOf(); 14136 dateItem.end = moment(hiddenDates[i].end).toDate().valueOf(); 14137 body.hiddenDates.push(dateItem); 14138 } 14139 } 14140 body.hiddenDates.sort(function (a, b) { 14141 return a.start - b.start; 14142 }); // sort by start time 14143 } 14144 } 14145 }; 14146 14147 /** 14148 * create new entrees for the repeating hidden dates 14149 * @param {function} moment 14150 * @param {Object} body 14151 * @param {Array | Object} hiddenDates 14152 */ 14153 exports.updateHiddenDates = function (moment, body, hiddenDates) { 14154 if (hiddenDates && !Array.isArray(hiddenDates)) { 14155 return exports.updateHiddenDates(moment, body, [hiddenDates]); 14156 } 14157 14158 if (hiddenDates && body.domProps.centerContainer.width !== undefined) { 14159 exports.convertHiddenOptions(moment, body, hiddenDates); 14160 14161 var start = moment(body.range.start); 14162 var end = moment(body.range.end); 14163 14164 var totalRange = body.range.end - body.range.start; 14165 var pixelTime = totalRange / body.domProps.centerContainer.width; 14166 14167 for (var i = 0; i < hiddenDates.length; i++) { 14168 if (hiddenDates[i].repeat !== undefined) { 14169 var startDate = moment(hiddenDates[i].start); 14170 var endDate = moment(hiddenDates[i].end); 14171 14172 if (startDate._d == "Invalid Date") {
14173 throw new Error("Supplied start date is not valid: " + hiddenDates[i].start); 14174 } 14175 if (endDate._d == "Invalid Date") { 14176 throw new Error("Supplied end date is not valid: " + hiddenDates[i].end); 14177 } 14178 14179 var duration = endDate - startDate; 14180 if (duration >= 4 * pixelTime) { 14181 14182 var offset = 0; 14183 var runUntil = end.clone(); 14184 switch (hiddenDates[i].repeat) { 14185 case "daily": 14186 // case of time 14187 if (startDate.day() != endDate.day()) { 14188 offset = 1; 14189 } 14190 startDate.dayOfYear(start.dayOfYear()); 14191 startDate.year(start.year()); 14192 startDate.subtract(7, "days"); 14193 14194 endDate.dayOfYear(start.dayOfYear()); 14195 endDate.year(start.year()); 14196 endDate.subtract(7 - offset, "days"); 14197 14198 runUntil.add(1, "weeks"); 14199 break; 14200 case "weekly": 14201 var dayOffset = endDate.diff(startDate, "days"); 14202 var day = startDate.day(); 14203 14204 // set the start date to the range.start 14205 startDate.date(start.date()); 14206 startDate.month(start.month()); 14207 startDate.year(start.year()); 14208 endDate = startDate.clone(); 14209 14210 // force 14211 startDate.day(day); 14212 endDate.day(day); 14213 endDate.add(dayOffset, "days"); 14214 14215 startDate.subtract(1, "weeks"); 14216 endDate.subtract(1, "weeks"); 14217 14218 runUntil.add(1, "weeks"); 14219 break; 14220 case "monthly": 14221 if (startDate.month() != endDate.month()) { 14222 offset = 1; 14223 } 14224 startDate.month(start.month()); 14225 startDate.year(start.year()); 14226 startDate.subtract(1, "months"); 14227 14228 endDate.month(start.month()); 14229 endDate.year(start.year()); 14230 endDate.subtract(1, "months"); 14231 endDate.add(offset, "months"); 14232 14233 runUntil.add(1, "months"); 14234 break; 14235 case "yearly": 14236 if (startDate.year() != endDate.year()) { 14237 offset = 1; 14238 } 14239 startDate.year(start.year()); 14240 startDate.subtract(1, "years"); 14241 endDate.year(start.year()); 14242 endDate.subtract(1, "years"); 14243 endDate.add(offset, "years"); 14244 14245 runUntil.add(1, "years"); 14246 break; 14247 default: 14248 console.log("Wrong repeat format, allowed are: daily, weekly, monthly, yearly. Given:", hiddenDates[i].repeat); 14249 return; 14250 } 14251 while (startDate < runUntil) { 14252 body.hiddenDates.push({ start: startDate.valueOf(), end: endDate.valueOf() }); 14253 switch (hiddenDates[i].repeat) { 14254 case "daily": 14255 startDate.add(1, "days"); 14256 endDate.add(1, "days"); 14257 break; 14258 case "weekly": 14259 startDate.add(1, "weeks"); 14260 endDate.add(1, "weeks"); 14261 break; 14262 case "monthly": 14263 startDate.add(1, "months"); 14264 endDate.add(1, "months"); 14265 break; 14266 case "yearly": 14267 startDate.add(1, "y"); 14268 endDate.add(1, "y"); 14269 break; 14270 default: 14271 console.log("Wrong repeat format, allowed are: daily, weekly, monthly, yearly. Given:", hiddenDates[i].repeat); 14272 return; 14273 } 14274 } 14275 body.hiddenDates.push({ start: startDate.valueOf(), end: endDate.valueOf() }); 14276 } 14277 } 14278 } 14279 // remove duplicates, merge where possible 14280 exports.removeDuplicates(body); 14281 // ensure the new positions are not on hidden dates 14282 var startHidden = exports.isHidden(body.range.start, body.hiddenDates); 14283 var endHidden = exports.isHidden(body.range.end, body.hiddenDates); 14284 var rangeStart = body.range.start; 14285 var rangeEnd = body.range.end; 14286 if (startHidden.hidden == true) { 14287 rangeStart = body.range.startToFront == true ? startHidden.startDate - 1 : startHidden.endDate + 1; 14288 } 14289 if (endHidden.hidden == true) { 14290 rangeEnd = body.range.endToFront == true ? endHidden.startDate - 1 : endHidden.endDate + 1; 14291 } 14292 if (startHidden.hidden == true || endHidden.hidden == true) { 14293 body.range._applyRange(rangeStart, rangeEnd); 14294 } 14295 } 14296 }; 14297 14298 /** 14299 * remove duplicates from the hidden dates list. Duplicates are evil. They mess everything up. 14300 * Scales with N^2 14301 * @param body 14302 */ 14303 exports.removeDuplicates = function (body) { 14304 var hiddenDates = body.hiddenDates; 14305 var safeDates = []; 14306 for (var i = 0; i < hiddenDates.length; i++) { 14307 for (var j = 0; j < hiddenDates.length; j++) { 14308 if (i != j && hiddenDates[j].remove != true && hiddenDates[i].remove != true) { 14309 // j inside i 14310 if (hiddenDates[j].start >= hiddenDates[i].start && hiddenDates[j].end <= hiddenDates[i].end) { 14311 hiddenDates[j].remove = true; 14312 } 14313 // j start inside i 14314 else if (hiddenDates[j].start >= hiddenDates[i].start && hiddenDates[j].start <= hiddenDates[i].end) { 14315 hiddenDates[i].end = hiddenDates[j].end; 14316 hiddenDates[j].remove = true; 14317 } 14318 // j end inside i 14319 else if (hiddenDates[j].end >= hiddenDates[i].start && hiddenDates[j].end <= hiddenDates[i].end) { 14320 hiddenDates[i].start = hiddenDates[j].start; 14321 hiddenDates[j].remove = true; 14322 } 14323 } 14324 } 14325 } 14326 14327 for (var i = 0; i < hiddenDates.length; i++) { 14328 if (hiddenDates[i].remove !== true) {
vendor: 2,331 bytes, lines 14329-14402
14329 safeDates.push(hiddenDates[i]); 14330 } 14331 } 14332 14333 body.hiddenDates = safeDates; 14334 body.hiddenDates.sort(function (a, b) { 14335 return a.start - b.start; 14336 }); // sort by start time 14337 }; 14338 14339 exports.printDates = function (dates) { 14340 for (var i = 0; i < dates.length; i++) { 14341 console.log(i, new Date(dates[i].start), new Date(dates[i].end), dates[i].start, dates[i].end, dates[i].remove); 14342 } 14343 }; 14344 14345 /** 14346 * Used in TimeStep to avoid the hidden times. 14347 * @param {function} moment 14348 * @param {TimeStep} timeStep 14349 * @param previousTime 14350 */ 14351 exports.stepOverHiddenDates = function (moment, timeStep, previousTime) { 14352 var stepInHidden = false; 14353 var currentValue = timeStep.current.valueOf(); 14354 for (var i = 0; i < timeStep.hiddenDates.length; i++) { 14355 var startDate = timeStep.hiddenDates[i].start; 14356 var endDate = timeStep.hiddenDates[i].end; 14357 if (currentValue >= startDate && currentValue < endDate) { 14358 stepInHidden = true; 14359 break; 14360 } 14361 } 14362 14363 if (stepInHidden == true && currentValue < timeStep._end.valueOf() && currentValue != previousTime) { 14364 var prevValue = moment(previousTime); 14365 var newValue = moment(endDate); 14366 //check if the next step should be major 14367 if (prevValue.year() != newValue.year()) { 14368 timeStep.switchedYear = true; 14369 } else if (prevValue.month() != newValue.month()) { 14370 timeStep.switchedMonth = true; 14371 } else if (prevValue.dayOfYear() != newValue.dayOfYear()) { 14372 timeStep.switchedDay = true; 14373 } 14374 14375 timeStep.current = newValue; 14376 } 14377 }; 14378 14379 ///** 14380 // * Used in TimeStep to avoid the hidden times. 14381 // * @param timeStep 14382 // * @param previousTime 14383 // */ 14384 //exports.checkFirstStep = function(timeStep) { 14385 // var stepInHidden = false; 14386 // var currentValue = timeStep.current.valueOf(); 14387 // for (var i = 0; i < timeStep.hiddenDates.length; i++) { 14388 // var startDate = timeStep.hiddenDates[i].start; 14389 // var endDate = timeStep.hiddenDates[i].end; 14390 // if (currentValue >= startDate && currentValue < endDate) { 14391 // stepInHidden = true; 14392 // break; 14393 // } 14394 // } 14395 // 14396 // if (stepInHidden == true && currentValue <= timeStep._end.valueOf()) { 14397 // var newValue = moment(endDate); 14398 // timeStep.current = newValue.toDate(); 14399 // } 14400 //}; 14401 14402 /**
14403 * replaces the Core toScreen methods 14404 * @param Core 14405 * @param time 14406 * @param width 14407 * @returns {number} 14408 */ 14409 exports.toScreen = function (Core, time, width) { 14410 if (Core.body.hiddenDates.length == 0) { 14411 var conversion = Core.range.conversion(width); 14412 return (time.valueOf() - conversion.offset) * conversion.scale; 14413 } else { 14414 var hidden = exports.isHidden(time, Core.body.hiddenDates); 14415 if (hidden.hidden == true) { 14416 time = hidden.startDate; 14417 } 14418 14419 var duration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end); 14420 time = exports.correctTimeForHidden(Core.options.moment, Core.body.hiddenDates, Core.range, time); 14421 14422 var conversion = Core.range.conversion(width, duration); 14423 return (time.valueOf() - conversion.offset) * conversion.scale; 14424 } 14425 }; 14426 14427 /** 14428 * Replaces the core toTime methods 14429 * @param body 14430 * @param range 14431 * @param x 14432 * @param width 14433 * @returns {Date} 14434 */ 14435 exports.toTime = function (Core, x, width) { 14436 if (Core.body.hiddenDates.length == 0) { 14437 var conversion = Core.range.conversion(width); 14438 return new Date(x / conversion.scale + conversion.offset); 14439 } else { 14440 var hiddenDuration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end); 14441 var totalDuration = Core.range.end - Core.range.start - hiddenDuration; 14442 var partialDuration = totalDuration * x / width; 14443 var accumulatedHiddenDuration = exports.getAccumulatedHiddenDuration(Core.body.hiddenDates, Core.range, partialDuration); 14444 14445 var newTime = new Date(accumulatedHiddenDuration + partialDuration + Core.range.start); 14446 return newTime; 14447 } 14448 }; 14449 14450 /** 14451 * Support function 14452 * 14453 * @param hiddenDates 14454 * @param range 14455 * @returns {number} 14456 */ 14457 exports.getHiddenDurationBetween = function (hiddenDates, start, end) { 14458 var duration = 0; 14459 for (var i = 0; i < hiddenDates.length; i++) { 14460 var startDate = hiddenDates[i].start; 14461 var endDate = hiddenDates[i].end; 14462 // if time after the cutout, and the 14463 if (startDate >= start && endDate < end) { 14464 duration += endDate - startDate; 14465 } 14466 } 14467 return duration; 14468 }; 14469 14470 /** 14471 * Support function 14472 * @param moment 14473 * @param hiddenDates 14474 * @param range 14475 * @param time 14476 * @returns {{duration: number, time: *, offset: number}} 14477 */ 14478 exports.correctTimeForHidden = function (moment, hiddenDates, range, time) { 14479 time = moment(time).toDate().valueOf(); 14480 time -= exports.getHiddenDurationBefore(moment, hiddenDates, range, time); 14481 return time; 14482 }; 14483 14484 exports.getHiddenDurationBefore = function (moment, hiddenDates, range, time) { 14485 var timeOffset = 0; 14486 time = moment(time).toDate().valueOf(); 14487 14488 for (var i = 0; i < hiddenDates.length; i++) { 14489 var startDate = hiddenDates[i].start; 14490 var endDate = hiddenDates[i].end; 14491 // if time after the cutout, and the 14492 if (startDate >= range.start && endDate < range.end) { 14493 if (time >
14493= endDate) { 14494 timeOffset += endDate - startDate; 14495 } 14496 } 14497 } 14498 return timeOffset; 14499 }; 14500 14501 /** 14502 * sum the duration from start to finish, including the hidden duration, 14503 * until the required amount has been reached, return the accumulated hidden duration 14504 * @param hiddenDates 14505 * @param range 14506 * @param time 14507 * @returns {{duration: number, time: *, offset: number}} 14508 */ 14509 exports.getAccumulatedHiddenDuration = function (hiddenDates, range, requiredDuration) { 14510 var hiddenDuration = 0; 14511 var duration = 0; 14512 var previousPoint = range.start; 14513 //exports.printDates(hiddenDates) 14514 for (var i = 0; i < hiddenDates.length; i++) { 14515 var startDate = hiddenDates[i].start; 14516 var endDate = hiddenDates[i].end; 14517 // if time after the cutout, and the 14518 if (startDate >= range.start && endDate < range.end) { 14519 duration += startDate - previousPoint; 14520 previousPoint = endDate; 14521 if (duration >= requiredDuration) { 14522 break; 14523 } else { 14524 hiddenDuration += endDate - startDate; 14525 } 14526 } 14527 } 14528 14529 return hiddenDuration; 14530 }; 14531 14532 /** 14533 * used to step over to either side of a hidden block. Correction is disabled on tablets, might be set to true 14534 * @param hiddenDates 14535 * @param time 14536 * @param direction 14537 * @param correctionEnabled 14538 * @returns {*} 14539 */ 14540 exports.snapAwayFromHidden = function (hiddenDates, time, direction, correctionEnabled) { 14541 var isHidden = exports.isHidden(time, hiddenDates); 14542 if (isHidden.hidden == true) { 14543 if (direction < 0) { 14544 if (correctionEnabled == true) { 14545 return isHidden.startDate - (isHidden.endDate - time) - 1; 14546 } else { 14547 return isHidden.startDate - 1; 14548 } 14549 } else { 14550 if (correctionEnabled == true) { 14551 return isHidden.endDate + (time - isHidden.startDate) + 1; 14552 } else { 14553 return isHidden.endDate + 1; 14554 } 14555 } 14556 } else { 14557 return time; 14558 } 14559 }; 14560 14561 /** 14562 * Check if a time is hidden 14563 * 14564 * @param time 14565 * @param hiddenDates 14566 * @returns {{hidden: boolean, startDate: Window.start, endDate: *}} 14567 */ 14568 exports.isHidden = function (time, hiddenDates) { 14569 for (var i = 0; i < hiddenDates.length; i++) { 14570 var startDate = hiddenDates[i].start; 14571 var endDate = hiddenDates[i].end; 14572 14573 if (time >= startDate && time < endDate) { 14574 // if the start is entering a hidden zone 14575 return { hidden: true, startDate: startDate, endDate: endDate }; 14576 break; 14577 } 14578 } 14579 return { hidden: false, startDate: startDate, endDate: endDate }; 14580 }; 14581 14582/***/ }, 14583/* 28 */ 14584/***/ function(module, exports, __webpack_require__) { 14585 14586 'use strict'; 14587 14588 var Emitter = __webpack_require__(12); 14589 var Hammer = __webpack_require__(20); 14590 var hammerUtil = __webpack_require__(25); 14591 var util = __webpack_require__(1); 14592 var DataSet = __webpack_require__(8); 14593 var DataView = __webpack_require__(10); 14594 var Range = __webpack_require__(24); 14595 var ItemSet = __webpack_require__(29);
14596 var TimeAxis = __webpack_require__(39); 14597 var Activator = __webpack_require__(40); 14598 var DateUtil = __webpack_require__(27); 14599 var CustomTime = __webpack_require__(42); 14600 14601 /** 14602 * Create a timeline visualization 14603 * @constructor 14604 */ 14605 function Core() {} 14606 14607 // turn Core into an event emitter 14608 Emitter(Core.prototype); 14609 14610 /** 14611 * Create the main DOM for the Core: a root panel containing left, right, 14612 * top, bottom, content, and background panel. 14613 * @param {Element} container The container element where the Core will 14614 * be attached. 14615 * @protected 14616 */ 14617 Core.prototype._create = function (container) { 14618 this.dom = {}; 14619 14620 this.dom.container = container; 14621 14622 this.dom.root = document.createElement('div'); 14623 this.dom.background = document.createElement('div'); 14624 this.dom.backgroundVertical = document.createElement('div'); 14625 this.dom.backgroundHorizontal = document.createElement('div'); 14626 this.dom.centerContainer = document.createElement('div'); 14627 this.dom.leftContainer = document.createElement('div'); 14628 this.dom.rightContainer = document.createElement('div'); 14629 this.dom.center = document.createElement('div'); 14630 this.dom.left = document.createElement('div'); 14631 this.dom.right = document.createElement('div'); 14632 this.dom.top = document.createElement('div'); 14633 this.dom.bottom = document.createElement('div'); 14634 this.dom.shadowTop = document.createElement('div'); 14635 this.dom.shadowBottom = document.createElement('div'); 14636 this.dom.shadowTopLeft = document.createElement('div'); 14637 this.dom.shadowBottomLeft = document.createElement('div'); 14638 this.dom.shadowTopRight = document.createElement('div'); 14639 this.dom.shadowBottomRight = document.createElement('div'); 14640 14641 this.dom.root.className = 'vis-timeline'; 14642 this.dom.background.className = 'vis-panel vis-background'; 14643 this.dom.backgroundVertical.className = 'vis-panel vis-background vis-vertical'; 14644 this.dom.backgroundHorizontal.className = 'vis-panel vis-background vis-horizontal'; 14645 this.dom.centerContainer.className = 'vis-panel vis-center'; 14646 this.dom.leftContainer.className = 'vis-panel vis-left'; 14647 this.dom.rightContainer.className = 'vis-panel vis-right'; 14648 this.dom.top.className = 'vis-panel vis-top'; 14649 this.dom.bottom.className = 'vis-panel vis-bottom'; 14650 this.dom.left.className = 'vis-content'; 14651 this.dom.center.className = 'vis-content'; 14652 this.dom.right.className = 'vis-content'; 14653 this.dom.shadowTop.className = 'vis-shadow vis-top'; 14654 this.dom.shadowBottom.className = 'vis-shadow vis-bottom'; 14655 this.dom.shadowTopLeft.className = 'vis-shadow vis-top'; 14656 this.dom.shadowBottomLeft.className = 'vis-shadow vis-bottom'; 14657 this.dom.shadowTopRight.className = 'vis-shadow vis-top'; 14658 this.dom.shadowBottomRight.className = 'vis-shadow vis-bottom'; 14659 14660 this.dom.root.appendChild(this.dom.background); 14661 this.dom.root.appendChild(this.dom.backgroundVertical); 14662 this.dom.root.appendChild(this.dom.backgroundHorizontal); 14663 this.dom.root.appendChild(this.dom.centerContainer); 14664 this.dom.root.appendChild(this.dom.leftContainer); 14665 this.dom.root.appendChild(this.dom.rightContainer); 14666 this.dom.root.appendChild(this.dom.top); 14667 this.dom.root.appendChild(this.dom.bottom); 14668 14669 this.dom.centerContainer.appendChild(this.dom.center); 14670 this.dom.leftContainer.appendChild(this.dom.left); 14671 this.dom.rightContainer.appendChild(this.dom.right); 14672 14673 this.dom.centerContainer.appendChild(this.dom.shadowTop); 14674 this.dom.centerContainer.appendChild(this.dom.shadowBottom); 14675 this.dom.leftContainer.appendChild(this.dom.shadowTopLeft); 14676 this.dom.leftContainer.appendChild(this.dom.shadowBottomLeft); 14677 this.dom.rightContainer.appendChild(this.dom.shadowTopRight); 14678 this.dom.rightContainer.appendChild(this.dom.shadowBottomRight); 14679 14680 this.on('rangechange', (function () { 14681 this._redraw(); // this allows overriding the _redraw method 14682 }).bind(this)); 14683 this.on('touch', this._onTouch.bind(this)); 14684 this.on('pan', this._onDrag.bind(this)); 14685 14686 var me = this; 14687 this.on('change', function (properties) { 14688 if (properties && properties.queue == true) { 14689 // redraw once on next tick 14690 if (!me._redrawTimer) { 14691 me._redrawTimer = setTimeout(function () { 14692 me._redrawTimer = null; 14693 me._redraw(); 14694 }, 0); 14695 } 14696 } else { 14697 // redraw immediately 14698 me._redraw(); 14699 } 14700 }); 14701 14702 // create event listeners for all interesting events, these events will be 14703 // emitted via emitter 14704 this.hammer = new Hammer(this.dom.root); 14705 this.hammer.get('pinch').set({ enable: true }); 14706 this.hammer.get('pan').set({ threshold: 5, direction: 30 }); // 30 is ALL_DIRECTIONS in hammer. 14707 this.listeners = {}; 14708 14709 var events = ['tap', 'doubletap', 'press', 'pinch', 'pan', 'panstart', 'panmove', 'panend' 14710 // TODO: cleanup 14711 //'touch', 'pinch', 14712 //'tap', 'doubletap', 'hold', 14713 //'dragstart', 'drag', 'dragend', 14714 //'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox 14715 ];
14716 events.forEach(function (type) { 14717 var listener = function listener(event) { 14718 if (me.isActive()) { 14719 me.emit(type, event); 14720 } 14721 }; 14722 me.hammer.on(type, listener); 14723 me.listeners[type] = listener; 14724 }); 14725 14726 // emulate a touch event (emitted before the start of a pan, pinch, tap, or press) 14727 hammerUtil.onTouch(this.hammer, (function (event) { 14728 me.emit('touch', event); 14729 }).bind(this)); 14730 14731 // emulate a release event (emitted after a pan, pinch, tap, or press) 14732 hammerUtil.onRelease(this.hammer, (function (event) { 14733 me.emit('release', event); 14734 }).bind(this)); 14735 14736 function onMouseWheel(event) { 14737 if (me.isActive()) { 14738 me.emit('mousewheel', event); 14739 } 14740 } 14741 this.dom.root.addEventListener('mousewheel', onMouseWheel); 14742 this.dom.root.addEventListener('DOMMouseScroll', onMouseWheel); 14743 14744 // size properties of each of the panels 14745 this.props = { 14746 root: {}, 14747 background: {}, 14748 centerContainer: {}, 14749 leftContainer: {}, 14750 rightContainer: {}, 14751 center: {}, 14752 left: {}, 14753 right: {}, 14754 top: {}, 14755 bottom: {}, 14756 border: {}, 14757 scrollTop: 0, 14758 scrollTopMin: 0 14759 }; 14760 14761 this.customTimes = []; 14762 14763 // store state information needed for touch events 14764 this.touch = {}; 14765 14766 this.redrawCount = 0; 14767 14768 // attach the root panel to the provided container 14769 if (!container) throw new Error('No container provided'); 14770 container.appendChild(this.dom.root); 14771 }; 14772 14773 /** 14774 * Set options. Options will be passed to all components loaded in the Timeline. 14775 * @param {Object} [options] 14776 * {String} orientation 14777 * Vertical orientation for the Timeline, 14778 * can be 'bottom' (default) or 'top'. 14779 * {String | Number} width 14780 * Width for the timeline, a number in pixels or 14781 * a css string like '1000px' or '75%'. '100%' by default. 14782 * {String | Number} height 14783 * Fixed height for the Timeline, a number in pixels or 14784 * a css string like '400px' or '75%'. If undefined, 14785 * The Timeline will automatically size such that 14786 * its contents fit. 14787 * {String | Number} minHeight 14788 * Minimum height for the Timeline, a number in pixels or 14789 * a css string like '400px' or '75%'. 14790 * {String | Number} maxHeight 14791 * Maximum height for the Timeline, a number in pixels or 14792 * a css string like '400px' or '75%'. 14793 * {Number | Date | String} start 14794 * Start date for the visible window 14795 * {Number | Date | String} end 14796 * End date for the visible window 14797 */ 14798 Core.prototype.setOptions = function (options) { 14799 if (options) { 14800 // copy the known options 14801 var fields = ['width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'start', 'end', 'clickToUse', 'dataAttributes', 'hiddenDates', 'locale', 'locales', 'moment', 'throttleRedraw']; 14802 util.selectiveExtend(fields, this.options, options); 14803 14804 if ('orientation' in options) { 14805 if (typeof options.orientation === 'string') { 14806 this.options.orientation = { 14807 item: options.orientation, 14808 axis: options.orientation 14809 }; 14810 } else if (typeof options.orientation === 'object') { 14811 if ('item' in options.orientation) { 14812 this.options.orientation.item = options.orientation.item; 14813 } 14814 if ('axis' in options.orientation) { 14815 this.options.orientation.axis = options.orientation.axis; 14816 } 14817 } 14818 } 14819 14820 if (this.options.orientation.axis === 'both') { 14821 if (!this.timeAxis2) { 14822 var timeAxis2 = this.timeAxis2 = new TimeAxis(this.body); 14823 timeAxis2.setOptions = function (options) { 14824 var _options = options ? util.extend({}, options) : {}; 14825 _options.orientation = 'top'; // override the orientation option, always top 14826 TimeAxis.prototype.setOptions.call(timeAxis2, _options); 14827 }; 14828 this.components.push(timeAxis2); 14829 } 14830 } else { 14831 if (this.timeAxis2) { 14832 var index = this.components.indexOf(this.timeAxis2); 14833 if (index !== -1) { 14834 this.components.splice(index, 1); 14835 } 14836 this.timeAxis2.destroy(); 14837 this.timeAxis2 = null; 14838 } 14839 } 14840 14841 // if the graph2d's drawPoints is a function delegate the callback to the onRender property 14842 if (typeof options.drawPoints == 'function') { 14843 options.drawPoints = { 14844 onRender: options.drawPoints 14845 }; 14846 } 14847 14848 if ('hiddenDates' in this.options) { 14849 DateUtil.convertHiddenOptions(this.options.moment, this.body, this.options.hiddenDates); 14850 } 14851
14852 if ('clickToUse' in options) { 14853 if (options.clickToUse) { 14854 if (!this.activator) { 14855 this.activator = new Activator(this.dom.root); 14856 } 14857 } else { 14858 if (this.activator) { 14859 this.activator.destroy(); 14860 delete this.activator; 14861 } 14862 } 14863 } 14864 14865 if ('showCustomTime' in options) { 14866 throw new Error('Option `showCustomTime` is deprecated. Create a custom time bar via timeline.addCustomTime(time [, id])'); 14867 } 14868 14869 // enable/disable autoResize 14870 this._initAutoResize(); 14871 } 14872 14873 // propagate options to all components 14874 this.components.forEach(function (component) { 14875 return component.setOptions(options); 14876 }); 14877 14878 // enable/disable configure 14879 if ('configure' in options) { 14880 if (!this.configurator) { 14881 this.configurator = this._createConfigurator(); 14882 } 14883 14884 this.configurator.setOptions(options.configure); 14885 14886 // collect the settings of all components, and pass them to the configuration system 14887 var appliedOptions = util.deepExtend({}, this.options); 14888 this.components.forEach(function (component) { 14889 util.deepExtend(appliedOptions, component.options); 14890 }); 14891 this.configurator.setModuleOptions({ global: appliedOptions }); 14892 } 14893 14894 // override redraw with a throttled version 14895 if (!this._origRedraw) { 14896 this._origRedraw = this._redraw.bind(this); 14897 } 14898 this._redraw = util.throttle(this._origRedraw, this.options.throttleRedraw); 14899 14900 // redraw everything 14901 this._redraw(); 14902 }; 14903 14904 /** 14905 * Returns true when the Timeline is active. 14906 * @returns {boolean} 14907 */ 14908 Core.prototype.isActive = function () { 14909 return !this.activator || this.activator.active; 14910 }; 14911 14912 /** 14913 * Destroy the Core, clean up all DOM elements and event listeners. 14914 */ 14915 Core.prototype.destroy = function () { 14916 // unbind datasets 14917 this.setItems(null); 14918 this.setGroups(null); 14919 14920 // remove all event listeners 14921 this.off(); 14922 14923 // stop checking for changed size 14924 this._stopAutoResize(); 14925 14926 // remove from DOM 14927 if (this.dom.root.parentNode) { 14928 this.dom.root.parentNode.removeChild(this.dom.root); 14929 } 14930 this.dom = null; 14931 14932 // remove Activator 14933 if (this.activator) { 14934 this.activator.destroy(); 14935 delete this.activator; 14936 } 14937 14938 // cleanup hammer touch events 14939 for (var event in this.listeners) { 14940 if (this.listeners.hasOwnProperty(event)) { 14941 delete this.listeners[event]; 14942 } 14943 } 14944 this.listeners = null; 14945 this.hammer = null; 14946 14947 // give all components the opportunity to cleanup 14948 this.components.forEach(function (component) { 14949 return component.destroy(); 14950 }); 14951 14952 this.body = null; 14953 }; 14954 14955 /** 14956 * Set a custom time bar 14957 * @param {Date} time 14958 * @param {number} [id=undefined] Optional id of the custom time bar to be adjusted. 14959 */ 14960 Core.prototype.setCustomTime = function (time, id) { 14961 var customTimes = this.customTimes.filter(function (component) { 14962 return id === component.options.id; 14963 }); 14964 14965 if (customTimes.length === 0) { 14966 throw new Error('No custom time bar found with id ' + JSON.stringify(id)); 14967 } 14968 14969 if (customTimes.length > 0) { 14970 customTimes[0].setCustomTime(time); 14971 } 14972 }; 14973 14974 /** 14975 * Retrieve the current custom time. 14976 * @param {number} [id=undefined] Id of the custom time bar. 14977 * @return {Date | undefined} customTime 14978 */ 14979 Core.prototype.getCustomTime = function (id) { 14980 var customTimes = this.customTimes.filter(function (component) { 14981 return component.options.id === id; 14982 }); 14983 14984 if (customTimes.length === 0) { 14985 throw new Error('No custom time bar found with id ' + JSON.stringify(id)); 14986 } 14987 return customTimes[0].getCustomTime(); 14988 }; 14989 14990 /** 14991 * Set a custom title for the custom time bar. 14992 * @param {String} [title] Custom title 14993 * @param {number} [id=undefined] Id of the custom time bar. 14994 */ 14995 Core.prototype.setCustomTimeTitle = function (title, id) { 14996 var customTimes = this.customTimes.filter(function (component) { 14997 return component.options.id === id; 14998 }); 14999 15000 if (customTimes.length === 0) { 15001 throw new Error('No custom time bar found with id ' + JSON.stringify(id)); 15002 } 15003 if (customTimes.length > 0) { 15004 return customTimes[0].setCustomTitle(title); 15005 } 15006 }; 15007 15008 /** 15009 * Retrieve meta information from an event. 15010 * Should be overridden by classes extending Core 15011 * @param {Event} event 15012 * @return {Object} An object with related information. 15013 */ 15014 Core.prototype.getEventProperties = function (event) { 15015 return { event: event }; 15016 }; 15017 15018 /** 15019 * Add custom vertical bar 15020 * @param {Date | String | Number} [time] A Date, unix timestamp, or 15021 * ISO date string. Time point where 15022 * the new bar should be placed. 15023 * If not provided, `new Date()` will 15024 * be used. 15025 * @param {Number | String} [id=undefined] Id of the new bar. Optional 15026 * @return {Number | String} Returns the id of the new bar 15027 */ 15028 Core.prototype.addCustomTime = function (time, id) { 15029 var timestamp = time !== undefined ? util.convert(time, 'Date').valueOf() : new Date(); 15030 15031 var exists = this.customTimes.some(function (customTime) { 15032 return customTime.options.id === id; 15033 }); 15034 if (exists) { 15035 throw new Error('A custom time with id ' + JSON.stringify(id) + ' already exists'); 15036 } 15037 15038 var customTime = new CustomTime(this.body, util.extend({}, this.options, { 15039 time: timestamp, 15040 id: id 15041 })); 15042 15043 this.customTimes.push(customTime); 15044 this.components.push(customTime); 15045 this._redraw(); 15046 15047 return id; 15048 }; 15049 15050 /** 15051 * Remove previously added custom bar 15052 * @param {int} id ID of the custom bar to be removed 15053 * @return {boolean} True if the bar exists and is removed, false otherwise 15054 */ 15055 Core.prototype.removeCustomTime = function (id) { 15056 var customTimes = this.customTimes.filter(function (bar) { 15057 return bar.options.id === id; 15058 }); 15059 15060 if (customTimes.length === 0) { 15061 throw new Error('No custom time bar found with id ' + JSON.stringify(id)); 15062 } 15063
15064 customTimes.forEach((function (customTime) { 15065 this.customTimes.splice(this.customTimes.indexOf(customTime), 1); 15066 this.components.splice(this.components.indexOf(customTime), 1); 15067 customTime.destroy(); 15068 }).bind(this)); 15069 }; 15070 15071 /** 15072 * Get the id's of the currently visible items. 15073 * @returns {Array} The ids of the visible items 15074 */ 15075 Core.prototype.getVisibleItems = function () { 15076 return this.itemSet && this.itemSet.getVisibleItems() || []; 15077 }; 15078 15079 /** 15080 * Set Core window such that it fits all items 15081 * @param {Object} [options] Available options: 15082 * `animation: boolean | {duration: number, easingFunction: string}` 15083 * If true (default), the range is animated 15084 * smoothly to the new window. An object can be 15085 * provided to specify duration and easing function. 15086 * Default duration is 500 ms, and default easing 15087 * function is 'easeInOutQuad'. 15088 */ 15089 Core.prototype.fit = function (options) { 15090 var range = this.getDataRange(); 15091 15092 // skip range set if there is no min and max date 15093 if (range.min === null && range.max === null) { 15094 return; 15095 } 15096 15097 // apply a margin of 1% left and right of the data 15098 var interval = range.max - range.min; 15099 var min = new Date(range.min.valueOf() - interval * 0.01); 15100 var max = new Date(range.max.valueOf() + interval * 0.01); 15101 15102 var animation = options && options.animation !== undefined ? options.animation : true; 15103 this.range.setRange(min, max, animation); 15104 }; 15105 15106 /** 15107 * Calculate the data range of the items start and end dates 15108 * @returns {{min: Date | null, max: Date | null}} 15109 * @protected 15110 */ 15111 Core.prototype.getDataRange = function () { 15112 // must be implemented by Timeline and Graph2d 15113 throw new Error('Cannot invoke abstract method getDataRange'); 15114 }; 15115 15116 /** 15117 * Set the visible window. Both parameters are optional, you can change only 15118 * start or only end. Syntax: 15119 * 15120 * TimeLine.setWindow(start, end) 15121 * TimeLine.setWindow(start, end, options) 15122 * TimeLine.setWindow(range) 15123 * 15124 * Where start and end can be a Date, number, or string, and range is an 15125 * object with properties start and end. 15126 * 15127 * @param {Date | Number | String | Object} [start] Start date of visible window 15128 * @param {Date | Number | String} [end] End date of visible window 15129 * @param {Object} [options] Available options: 15130 * `animation: boolean | {duration: number, easingFunction: string}` 15131 * If true (default), the range is animated 15132 * smoothly to the new window. An object can be 15133 * provided to specify duration and easing function. 15134 * Default duration is 500 ms, and default easing 15135 * function is 'easeInOutQuad'. 15136 */ 15137 Core.prototype.setWindow = function (start, end, options) { 15138 var animation; 15139 if (arguments.length == 1) { 15140 var range = arguments[0]; 15141 animation = range.animation !== undefined ? range.animation : true; 15142 this.range.setRange(range.start, range.end, animation); 15143 } else { 15144 animation = options && options.animation !== undefined ? options.animation : true; 15145 this.range.setRange(start, end, animation); 15146 } 15147 }; 15148 15149 /** 15150 * Move the window such that given time is centered on screen. 15151 * @param {Date | Number | String} time 15152 * @param {Object} [options] Available options: 15153 * `animation: boolean | {duration: number, easingFunction: string}` 15154 * If true (default), the range is animated 15155 * smoothly to the new window. An object can be 15156 * provided to specify duration and easing function. 15157 * Default duration is 500 ms, and default easing 15158 * function is 'easeInOutQuad'. 15159 */ 15160 Core.prototype.moveTo = function (time, options) { 15161 var interval = this.range.end - this.range.start; 15162 var t = util.convert(time, 'Date').valueOf(); 15163 15164 var start = t - interval / 2; 15165 var end = t + interval / 2; 15166 var animation = options && options.animation !== undefined ? options.animation : true; 15167 15168 this.range.setRange(start, end, animation); 15169 }; 15170 15171 /** 15172 * Get the visible window 15173 * @return {{start: Date, end: Date}} Visible range 15174 */ 15175 Core.prototype.getWindow = function () { 15176 var range = this.range.getRange(); 15177 return { 15178 start: new Date(range.start), 15179 end: new Date(range.end) 15180 }; 15181 }; 15182 15183 /** 15184 * Force a redraw. Can be overridden by implementations of Core 15185 * 15186 * Note: this function will be overridden on construction with a trottled version 15187 */ 15188 Core.prototype.redraw = function () { 15189 this._redraw(); 15190 }; 15191 15192 /** 15193 * Redraw for internal use. Redraws all components. See also the public 15194 * method redraw. 15195 * @protected 15196 */ 15197 Core.prototype._redraw = function () { 15198 var resized = false;
15199 var options = this.options; 15200 var props = this.props; 15201 var dom = this.dom; 15202 15203 if (!dom) return; // when destroyed 15204 15205 DateUtil.updateHiddenDates(this.options.moment, this.body, this.options.hiddenDates); 15206 15207 // update class names 15208 if (options.orientation == 'top') { 15209 util.addClassName(dom.root, 'vis-top'); 15210 util.removeClassName(dom.root, 'vis-bottom'); 15211 } else { 15212 util.removeClassName(dom.root, 'vis-top'); 15213 util.addClassName(dom.root, 'vis-bottom'); 15214 } 15215 15216 // update root width and height options 15217 dom.root.style.maxHeight = util.option.asSize(options.maxHeight, ''); 15218 dom.root.style.minHeight = util.option.asSize(options.minHeight, ''); 15219 dom.root.style.width = util.option.asSize(options.width, ''); 15220 15221 // calculate border widths 15222 props.border.left = (dom.centerContainer.offsetWidth - dom.centerContainer.clientWidth) / 2; 15223 props.border.right = props.border.left; 15224 props.border.top = (dom.centerContainer.offsetHeight - dom.centerContainer.clientHeight) / 2; 15225 props.border.bottom = props.border.top; 15226 var borderRootHeight = dom.root.offsetHeight - dom.root.clientHeight; 15227 var borderRootWidth = dom.root.offsetWidth - dom.root.clientWidth; 15228 15229 // workaround for a bug in IE: the clientWidth of an element with 15230 // a height:0px and overflow:hidden is not calculated and always has value 0 15231 if (dom.centerContainer.clientHeight === 0) { 15232 props.border.left = props.border.top; 15233 props.border.right = props.border.left; 15234 } 15235 if (dom.root.clientHeight === 0) { 15236 borderRootWidth = borderRootHeight; 15237 } 15238 15239 // calculate the heights. If any of the side panels is empty, we set the height to 15240 // minus the border width, such that the border will be invisible 15241 props.center.height = dom.center.offsetHeight; 15242 props.left.height = dom.left.offsetHeight; 15243 props.right.height = dom.right.offsetHeight; 15244 props.top.height = dom.top.clientHeight || -props.border.top; 15245 props.bottom.height = dom.bottom.clientHeight || -props.border.bottom; 15246 15247 // TODO: compensate borders when any of the panels is empty. 15248 15249 // apply auto height 15250 // TODO: only calculate autoHeight when needed (else we cause an extra reflow/repaint of the DOM) 15251 var contentHeight = Math.max(props.left.height, props.center.height, props.right.height); 15252 var autoHeight = props.top.height + contentHeight + props.bottom.height + borderRootHeight + props.border.top + props.border.bottom; 15253 dom.root.style.height = util.option.asSize(options.height, autoHeight + 'px'); 15254 15255 // calculate heights of the content panels 15256 props.root.height = dom.root.offsetHeight; 15257 props.background.height = props.root.height - borderRootHeight; 15258 var containerHeight = props.root.height - props.top.height - props.bottom.height - borderRootHeight; 15259 props.centerContainer.height = containerHeight; 15260 props.leftContainer.height = containerHeight; 15261 props.rightContainer.height = props.leftContainer.height; 15262 15263 // calculate the widths of the panels 15264 props.root.width = dom.root.offsetWidth; 15265 props.background.width = props.root.width - borderRootWidth; 15266 props.left.width = dom.leftContainer.clientWidth || -props.border.left; 15267 props.leftContainer.width = props.left.width; 15268 props.right.width = dom.rightContainer.clientWidth || -props.border.right; 15269 props.rightContainer.width = props.right.width; 15270 var centerWidth = props.root.width - props.left.width - props.right.width - borderRootWidth; 15271 props.center.width = centerWidth; 15272 props.centerContainer.width = centerWidth; 15273 props.top.width = centerWidth; 15274 props.bottom.width = centerWidth; 15275 15276 // resize the panels 15277 dom.background.style.height = props.background.height + 'px'; 15278 dom.backgroundVertical.style.height = props.background.height + 'px'; 15279 dom.backgroundHorizontal.style.height = props.centerContainer.height + 'px'; 15280 dom.centerContainer.style.height = props.centerContainer.height + 'px'; 15281 dom.leftContainer.style.height = props.leftContainer.height + 'px';
15282 dom.rightContainer.style.height = props.rightContainer.height + 'px'; 15283 15284 dom.background.style.width = props.background.width + 'px'; 15285 dom.backgroundVertical.style.width = props.centerContainer.width + 'px'; 15286 dom.backgroundHorizontal.style.width = props.background.width + 'px'; 15287 dom.centerContainer.style.width = props.center.width + 'px'; 15288 dom.top.style.width = props.top.width + 'px'; 15289 dom.bottom.style.width = props.bottom.width + 'px'; 15290 15291 // reposition the panels 15292 dom.background.style.left = '0'; 15293 dom.background.style.top = '0'; 15294 dom.backgroundVertical.style.left = props.left.width + props.border.left + 'px'; 15295 dom.backgroundVertical.style.top = '0'; 15296 dom.backgroundHorizontal.style.left = '0'; 15297 dom.backgroundHorizontal.style.top = props.top.height + 'px'; 15298 dom.centerContainer.style.left = props.left.width + 'px'; 15299 dom.centerContainer.style.top = props.top.height + 'px'; 15300 dom.leftContainer.style.left = '0'; 15301 dom.leftContainer.style.top = props.top.height + 'px'; 15302 dom.rightContainer.style.left = props.left.width + props.center.width + 'px'; 15303 dom.rightContainer.style.top = props.top.height + 'px'; 15304 dom.top.style.left = props.left.width + 'px'; 15305 dom.top.style.top = '0'; 15306 dom.bottom.style.left = props.left.width + 'px'; 15307 dom.bottom.style.top = props.top.height + props.centerContainer.height + 'px'; 15308 15309 // update the scrollTop, feasible range for the offset can be changed 15310 // when the height of the Core or of the contents of the center changed 15311 this._updateScrollTop(); 15312 15313 // reposition the scrollable contents 15314 var offset = this.props.scrollTop; 15315 if (options.orientation.item != 'top') { 15316 offset += Math.max(this.props.centerContainer.height - this.props.center.height - this.props.border.top - this.props.border.bottom, 0); 15317 } 15318 dom.center.style.left = '0'; 15319 dom.center.style.top = offset + 'px'; 15320 dom.left.style.left = '0'; 15321 dom.left.style.top = offset + 'px'; 15322 dom.right.style.left = '0'; 15323 dom.right.style.top = offset + 'px'; 15324 15325 // show shadows when vertical scrolling is available 15326 var visibilityTop = this.props.scrollTop == 0 ? 'hidden' : ''; 15327 var visibilityBottom = this.props.scrollTop == this.props.scrollTopMin ? 'hidden' : ''; 15328 dom.shadowTop.style.visibility = visibilityTop; 15329 dom.shadowBottom.style.visibility = visibilityBottom; 15330 dom.shadowTopLeft.style.visibility = visibilityTop; 15331 dom.shadowBottomLeft.style.visibility = visibilityBottom; 15332 dom.shadowTopRight.style.visibility = visibilityTop; 15333 dom.shadowBottomRight.style.visibility = visibilityBottom; 15334 15335 // redraw all components 15336 this.components.forEach(function (component) { 15337 resized = component.redraw() || resized; 15338 }); 15339 if (resized) { 15340 // keep repainting until all sizes are settled 15341 var MAX_REDRAWS = 3; // maximum number of consecutive redraws 15342 if (this.redrawCount < MAX_REDRAWS) { 15343 this.redrawCount++; 15344 this._redraw(); 15345 } else { 15346 console.log('WARNING: infinite loop in redraw?'); 15347 } 15348 this.redrawCount = 0; 15349 } 15350 }; 15351 15352 // TODO: deprecated since version 1.1.0, remove some day 15353 Core.prototype.repaint = function () { 15354 throw new Error('Function repaint is deprecated. Use redraw instead.'); 15355 }; 15356 15357 /** 15358 * Set a current time. This can be used for example to ensure that a client's 15359 * time is synchronized with a shared server time. 15360 * Only applicable when option `showCurrentTime` is true. 15361 * @param {Date | String | Number} time A Date, unix timestamp, or 15362 * ISO date string. 15363 */ 15364 Core.prototype.setCurrentTime = function (time) { 15365 if (!this.currentTime) { 15366 throw new Error('Option showCurrentTime must be true'); 15367 } 15368 15369 this.currentTime.setCurrentTime(time); 15370 }; 15371 15372 /** 15373 * Get the current time. 15374 * Only applicable when option `showCurrentTime` is true. 15375 * @return {Date} Returns the current time. 15376 */ 15377 Core.prototype.getCurrentTime = function () { 15378 if (!this.currentTime) { 15379 throw new Error('Option showCurrentTime must be true'); 15380 } 15381 15382 return this.currentTime.getCurrentTime(); 15383 }; 15384 15385 /** 15386 * Convert a position on screen (pixels) to a datetime 15387 * @param {int} x Position on the screen in pixels 15388 * @return {Date} time The datetime the corresponds with given position x 15389 * @protected 15390 */ 15391 // TODO: move this function to Range 15392 Core.prototype._toTime = function (x) { 15393 return DateUtil.toTime(this, x, this.props.center.width); 15394 }; 15395 15396 /** 15397 * Convert a position on the global screen (pixels) to a datetime 15398 * @param {int} x Position on the screen in pixels 15399 * @return {Date} time The datetime the corresponds with given position x 15400 * @protected 15401 */ 15402 // TODO: move this function to Range 15403 Core.prototype._toGlobalTime = function (x) { 15404 return DateUtil.toTime(this, x, this.props.root.width); 15405 //var conversion = this.range.conversion(this.props.root.width); 15406 //return new Date(x / conversion.scale + conversion.offset); 15407 }; 15408 15409 /** 15410 * Convert a datetime (Date object) into a position on the screen 15411 * @param {Date} time A date 15412 * @return {int} x The position on the screen in pixels which corresponds 15413 * with the given date. 15414 * @protected 15415 */ 15416 // TODO: move this function to Range 15417 Core.prototype._toScreen = function (time) { 15418 return DateUtil.toScreen(this, time, this.props.center.width); 15419 }; 15420 15421 /** 15422 * Convert a datetime (Date object) into a position on the root 15423 * This is used to get the pixel density estimate for the screen, not the center panel 15424 * @param {Date} time A date 15425 * @return {int} x The position on root in pixels which corresponds 15426 * with the given date. 15427 * @protected 15428 */ 15429 // TODO: move this function to Range 15430 Core.prototype._toGlobalScreen = function (time) { 15431 return DateUtil.toScreen(this, time, this.props.root.width); 15432 //var conversion = this.range.conversion(this.props.root.width); 15433 //return (time.valueOf() - conversion.offset) * conversion.scale; 15434 }; 15435 15436 /** 15437 * Initialize watching when option autoResize is true 15438 * @private 15439 */ 15440 Core.prototype._initAutoResize = function () { 15441 if (this.options.autoResize == true) { 15442 this._startAutoResize(); 15443 } else { 15444 this._stopAutoResize(); 15445 } 15446 }; 15447 15448 /** 15449 * Watch for changes in the size of the container. On resize, the Panel will 15450 * automatically redraw itself. 15451 * @private 15452 */ 15453 Core.prototype._startAutoResize = function () { 15454 var me = this; 15455 15456 this._stopAutoResize(); 15457 15458 this._onResize = function () { 15459 if (me.options.autoResize != true) { 15460 // stop watching when the option autoResize is changed to fal
15460se 15461 me._stopAutoResize(); 15462 return; 15463 } 15464 15465 if (me.dom.root) { 15466 // check whether the frame is resized 15467 // Note: we compare offsetWidth here, not clientWidth. For some reason, 15468 // IE does not restore the clientWidth from 0 to the actual width after 15469 // changing the timeline's container display style from none to visible 15470 if (me.dom.root.offsetWidth != me.props.lastWidth || me.dom.root.offsetHeight != me.props.lastHeight) { 15471 me.props.lastWidth = me.dom.root.offsetWidth; 15472 me.props.lastHeight = me.dom.root.offsetHeight; 15473 15474 me.emit('change'); 15475 } 15476 } 15477 }; 15478 15479 // add event listener to window resize 15480 util.addEventListener(window, 'resize', this._onResize); 15481 15482 this.watchTimer = setInterval(this._onResize, 1000); 15483 }; 15484 15485 /** 15486 * Stop watching for a resize of the frame. 15487 * @private 15488 */ 15489 Core.prototype._stopAutoResize = function () { 15490 if (this.watchTimer) { 15491 clearInterval(this.watchTimer); 15492 this.watchTimer = undefined; 15493 } 15494 15495 // remove event listener on window.resize 15496 if (this._onResize) { 15497 util.removeEventListener(window, 'resize', this._onResize); 15498 this._onResize = null; 15499 } 15500 }; 15501 15502 /** 15503 * Start moving the timeline vertically 15504 * @param {Event} event 15505 * @private 15506 */ 15507 Core.prototype._onTouch = function (event) { 15508 this.touch.allowDragging = true; 15509 this.touch.initialScrollTop = this.props.scrollTop; 15510 }; 15511 15512 /** 15513 * Start moving the timeline vertically 15514 * @param {Event} event 15515 * @private 15516 */ 15517 Core.prototype._onPinch = function (event) { 15518 this.touch.allowDragging = false; 15519 }; 15520 15521 /** 15522 * Move the timeline vertically 15523 * @param {Event} event 15524 * @private 15525 */ 15526 Core.prototype._onDrag = function (event) { 15527 // refuse to drag when we where pinching to prevent the timeline make a jump 15528 // when releasing the fingers in opposite order from the touch screen 15529 if (!this.touch.allowDragging) return; 15530 15531 var delta = event.deltaY; 15532 15533 var oldScrollTop = this._getScrollTop(); 15534 var newScrollTop = this._setScrollTop(this.touch.initialScrollTop + delta); 15535 15536 if (newScrollTop != oldScrollTop) { 15537 this._redraw(); // TODO: this causes two redraws when dragging, the other is triggered by rangechange already 15538 this.emit('verticalDrag'); 15539 } 15540 }; 15541 15542 /** 15543 * Apply a scrollTop 15544 * @param {Number} scrollTop 15545 * @returns {Number} scrollTop Returns the applied scrollTop 15546 * @private 15547 */ 15548 Core.prototype._setScrollTop = function (scrollTop) { 15549 this.props.scrollTop = scrollTop; 15550 this._updateScrollTop(); 15551 return this.props.scrollTop; 15552 }; 15553 15554 /** 15555 * Update the current scrollTop when the height of the containers has been changed 15556 * @returns {Number} scrollTop Returns the applied scrollTop 15557 * @private 15558 */ 15559 Core.prototype._updateScrollTop = function () { 15560 // recalculate the scrollTopMin 15561 var scrollTopMin = Math.min(this.props.centerContainer.height - this.props.center.height, 0); // is negative or zero 15562 if (scrollTopMin != this.props.scrollTopMin) { 15563 // in case of bottom orientation, change the scrollTop such that the contents 15564 // do not move relative to the time axis at the bottom 15565 if (this.options.orientation.item != 'top') { 15566 this.props.scrollTop += scrollTopMin - this.props.scrollTopMin; 15567 } 15568 this.props.scrollTopMin = scrollTopMin; 15569 } 15570 15571 // limit the scrollTop to the feasible scroll range 15572 if (this.props.scrollTop > 0) this.props.scrollTop = 0; 15573 if (this.props.scrollTop < scrollTopMin) this.props.scrollTop = scrollTopMin; 15574 15575 return this.props.scrollTop; 15576 }; 15577 15578 /** 15579 * Get the current scrollTop 15580 * @returns {number} scrollTop 15581 * @private 15582 */ 15583 Core.prototype._getScrollTop = function () { 15584 return this.props.scrollTop; 15585 }; 15586 15587 /** 15588 * Load a configurator 15589 * @return {Object} 15590 * @private 15591 */ 15592 Core.prototype._createConfigurator = function () { 15593 throw new Error('Cannot invoke abstract method _createConfigurator'); 15594 }; 15595 15596 module.exports = Core; 15597 15598/***/ }, 15599/* 29 */ 15600/***/ function(module, exports, __webpack_require__) { 15601 15602 'use strict'; 15603 15604 var Hammer = __webpack_require__(20); 15605 var util = __webpack_require__(1); 15606 var DataSet = __webpack_require__(8); 15607 var DataView = __webpack_require__(10); 15608 var TimeStep = __webpack_require__(30); 15609 var Component = __webpack_require__(26); 15610 var Group = __webpack_require__(31);
15611 var BackgroundGroup = __webpack_require__(35); 15612 var BoxItem = __webpack_require__(36); 15613 var PointItem = __webpack_require__(37); 15614 var RangeItem = __webpack_require__(33); 15615 var BackgroundItem = __webpack_require__(38); 15616 15617 var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items 15618 var BACKGROUND = '__background__'; // reserved group id for background items without group 15619 15620 /** 15621 * An ItemSet holds a set of items and ranges which can be displayed in a 15622 * range. The width is determined by the parent of the ItemSet, and the height 15623 * is determined by the size of the items. 15624 * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body 15625 * @param {Object} [options] See ItemSet.setOptions for the available options. 15626 * @constructor ItemSet 15627 * @extends Component 15628 */ 15629 function ItemSet(body, options) { 15630 this.body = body; 15631 15632 this.defaultOptions = { 15633 type: null, // 'box', 'point', 'range', 'background' 15634 orientation: { 15635 item: 'bottom' // item orientation: 'top' or 'bottom' 15636 }, 15637 align: 'auto', // alignment of box items 15638 stack: true, 15639 groupOrderSwap: function groupOrderSwap(fromGroup, toGroup, groups) { 15640 var targetOrder = toGroup.order; 15641 toGroup.order = fromGroup.order; 15642 fromGroup.order = targetOrder; 15643 }, 15644 groupOrder: 'order', 15645 15646 selectable: true, 15647 multiselect: false, 15648 15649 editable: { 15650 updateTime: false, 15651 updateGroup: false, 15652 add: false, 15653 remove: false 15654 }, 15655 15656 groupEditable: { 15657 order: false, 15658 add: false, 15659 remove: false 15660 }, 15661 15662 snap: TimeStep.snap, 15663 15664 onAdd: function onAdd(item, callback) { 15665 callback(item); 15666 }, 15667 onUpdate: function onUpdate(item, callback) { 15668 callback(item); 15669 }, 15670 onMove: function onMove(item, callback) { 15671 callback(item); 15672 }, 15673 onRemove: function onRemove(item, callback) { 15674 callback(item); 15675 }, 15676 onMoving: function onMoving(item, callback) { 15677 callback(item); 15678 }, 15679 onAddGroup: function onAddGroup(item, callback) { 15680 callback(item); 15681 }, 15682 onMoveGroup: function onMoveGroup(item, callback) { 15683 callback(item); 15684 }, 15685 onRemoveGroup: function onRemoveGroup(item, callback) { 15686 callback(item); 15687 }, 15688 15689 margin: { 15690 item: { 15691 horizontal: 10, 15692 vertical: 10 15693 }, 15694 axis: 20 15695 } 15696 }; 15697 15698 // options is shared by this ItemSet and all its items 15699 this.options = util.extend({}, this.defaultOptions); 15700 15701 // options for getting items from the DataSet with the correct type 15702 this.itemOptions = { 15703 type: { start: 'Date', end: 'Date' } 15704 }; 15705 15706 this.conversion = { 15707 toScreen: body.util.toScreen, 15708 toTime: body.util.toTime 15709 }; 15710 this.dom = {}; 15711 this.props = {}; 15712 this.hammer = null; 15713 15714 var me = this; 15715 this.itemsData = null; // DataSet 15716 this.groupsData = null; // DataSet 15717 15718 // listeners for the DataSet of the items 15719 this.itemListeners = { 15720 'add': function add(event, params, senderId) { 15721 me._onAdd(params.items); 15722 }, 15723 'update': function update(event, params, senderId) { 15724 me._onUpdate(params.items); 15725 }, 15726 'remove': function remove(event, params, senderId) { 15727 me._onRemove(params.items); 15728 } 15729 }; 15730 15731 // listeners for the DataSet of the groups 15732 this.groupListeners = { 15733 'add': function add(event, params, senderId) { 15734 me._onAddGroups(params.items); 15735 }, 15736 'update': function update(event, params, senderId) { 15737 me._onUpdateGroups(params.items); 15738 }, 15739 'remove': function remove(event, params, senderId) { 15740 me._onRemoveGroups(params.items); 15741 } 15742 }; 15743 15744 this.items = {}; // object with an Item for every data item 15745 this.groups = {}; // Group object for every group 15746 this.groupIds = []; 15747 15748 this.selection = []; // list with the ids of all selected nodes 15749 this.stackDirty = true; // if true, all items will be restacked on next redraw 15750 15751 this.touchParams = {}; // stores properties while dragging 15752 this.groupTouchParams = {}; 15753 // create the HTML DOM 15754 15755 this._create(); 15756 15757 this.setOptions(options); 15758 } 15759 15760 ItemSet.prototype = new Component(); 15761 15762 // available item types will be registered here 15763 ItemSet.types = {
15764 background: BackgroundItem, 15765 box: BoxItem, 15766 range: RangeItem, 15767 point: PointItem 15768 }; 15769 15770 /** 15771 * Create the HTML DOM for the ItemSet 15772 */ 15773 ItemSet.prototype._create = function () { 15774 var frame = document.createElement('div'); 15775 frame.className = 'vis-itemset'; 15776 frame['timeline-itemset'] = this; 15777 this.dom.frame = frame; 15778 15779 // create background panel 15780 var background = document.createElement('div'); 15781 background.className = 'vis-background'; 15782 frame.appendChild(background); 15783 this.dom.background = background; 15784 15785 // create foreground panel 15786 var foreground = document.createElement('div'); 15787 foreground.className = 'vis-foreground'; 15788 frame.appendChild(foreground); 15789 this.dom.foreground = foreground; 15790 15791 // create axis panel 15792 var axis = document.createElement('div'); 15793 axis.className = 'vis-axis'; 15794 this.dom.axis = axis; 15795 15796 // create labelset 15797 var labelSet = document.createElement('div'); 15798 labelSet.className = 'vis-labelset'; 15799 this.dom.labelSet = labelSet; 15800 15801 // create ungrouped Group 15802 this._updateUngrouped(); 15803 15804 // create background Group 15805 var backgroundGroup = new BackgroundGroup(BACKGROUND, null, this); 15806 backgroundGroup.show(); 15807 this.groups[BACKGROUND] = backgroundGroup; 15808 15809 // attach event listeners 15810 // Note: we bind to the centerContainer for the case where the height 15811 // of the center container is larger than of the ItemSet, so we 15812 // can click in the empty area to create a new item or deselect an item. 15813 this.hammer = new Hammer(this.body.dom.centerContainer); 15814 15815 // drag items when selected 15816 this.hammer.on('hammer.input', (function (event) { 15817 if (event.isFirst) { 15818 this._onTouch(event); 15819 } 15820 }).bind(this)); 15821 this.hammer.on('panstart', this._onDragStart.bind(this)); 15822 this.hammer.on('panmove', this._onDrag.bind(this)); 15823 this.hammer.on('panend', this._onDragEnd.bind(this)); 15824 this.hammer.get('pan').set({ threshold: 5, direction: 30 }); // 30 is ALL_DIRECTIONS in hammer. 15825 15826 // single select (or unselect) when tapping an item 15827 this.hammer.on('tap', this._onSelectItem.bind(this)); 15828 15829 // multi select when holding mouse/touch, or on ctrl+click 15830 this.hammer.on('press', this._onMultiSelectItem.bind(this)); 15831 15832 // add item on doubletap 15833 this.hammer.on('doubletap', this._onAddItem.bind(this)); 15834 15835 this.groupHammer = new Hammer(this.body.dom.leftContainer); 15836 this.groupHammer.on('panstart', this._onGroupDragStart.bind(this)); 15837 this.groupHammer.on('panmove', this._onGroupDrag.bind(this)); 15838 this.groupHammer.on('panend', this._onGroupDragEnd.bind(this)); 15839 this.groupHammer.get('pan').set({ threshold: 5, direction: 30 }); 15840 15841 // attach to the DOM 15842 this.show(); 15843 }; 15844 15845 /** 15846 * Set options for the ItemSet. Existing options will be extended/overwritten. 15847 * @param {Object} [options] The following options are available: 15848 * {String} type 15849 * Default type for the items. Choose from 'box' 15850 * (default), 'point', 'range', or 'background'. 15851 * The default style can be overwritten by 15852 * individual items. 15853 * {String} align 15854 * Alignment for the items, only applicable for 15855 * BoxItem. Choose 'center' (default), 'left', or 15856 * 'right'. 15857 * {String} orientation.item 15858 * Orientation of the item set. Choose 'top' or 15859 * 'bottom' (default). 15860 * {Function} groupOrder 15861 * A sorting function for ordering groups 15862 * {Boolean} stack 15863 * If true (default), items will be stacked on 15864 * top of each other. 15865 * {Number} margin.axis 15866 * Margin between the axis and the items in pixels. 15867 * Default is 20. 15868 * {Number} margin.item.horizontal 15869 * Horizontal margin between items in pixels. 15870 * Default is 10. 15871 * {Number} margin.item.vertical 15872 * Vertical Margin between items in pixels. 15873 * Default is 10. 15874 * {Number} margin.item 15875 * Margin between items in pixels in both horizontal 15876 * and vertical direction. Default is 10. 15877 * {Number} margin 15878 * Set margin for both axis and items in pixels. 15879 * {Boolean} selectable 15880 * If true (default), items can be selected. 15881 * {Boolean} multiselect 15882 * If true, multiple items can be selected. 15883 * False by default. 15884 * {Boolean} editable 15885 * Set all editable options to true or false 15886 * {Boolean} editable.updateTime 15887 * Allow dragging an item to an other moment in time 15888 * {Boolean} editable.updateGroup 15889 * Allow dragging an item to an other group 15890 * {Boolean} editable.add 15891 * Allow creating new items on double tap 15892 * {Boolean} editable.remove 15893 * Allow removing items by clicking the delete button 15894 * top right of a selected item. 15895 * {Function(item: Item, callback: Function)} onAdd 15896 * Callback function triggered when an item is about to be added: 15897 * when the user double taps an empty space in the Timeline. 15898 * {Function(item: Item, callback: Function)} onUpdate
15899 * Callback function fired when an item is about to be updated. 15900 * This function typically has to show a dialog where the user 15901 * change the item. If not implemented, nothing happens. 15902 * {Function(item: Item, callback: Function)} onMove 15903 * Fired when an item has been moved. If not implemented, 15904 * the move action will be accepted. 15905 * {Function(item: Item, callback: Function)} onRemove 15906 * Fired when an item is about to be deleted. 15907 * If not implemented, the item will be always removed. 15908 */ 15909 ItemSet.prototype.setOptions = function (options) { 15910 if (options) { 15911 // copy all options that we know 15912 var fields = ['type', 'align', 'order', 'stack', 'selectable', 'multiselect', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'hide', 'snap', 'groupOrderSwap']; 15913 util.selectiveExtend(fields, this.options, options); 15914 15915 if ('orientation' in options) { 15916 if (typeof options.orientation === 'string') { 15917 this.options.orientation.item = options.orientation === 'top' ? 'top' : 'bottom'; 15918 } else if (typeof options.orientation === 'object' && 'item' in options.orientation) { 15919 this.options.orientation.item = options.orientation.item; 15920 } 15921 } 15922 15923 if ('margin' in options) { 15924 if (typeof options.margin === 'number') { 15925 this.options.margin.axis = options.margin; 15926 this.options.margin.item.horizontal = options.margin; 15927 this.options.margin.item.vertical = options.margin; 15928 } else if (typeof options.margin === 'object') { 15929 util.selectiveExtend(['axis'], this.options.margin, options.margin); 15930 if ('item' in options.margin) { 15931 if (typeof options.margin.item === 'number') { 15932 this.options.margin.item.horizontal = options.margin.item; 15933 this.options.margin.item.vertical = options.margin.item; 15934 } else if (typeof options.margin.item === 'object') { 15935 util.selectiveExtend(['horizontal', 'vertical'], this.options.margin.item, options.margin.item); 15936 } 15937 } 15938 } 15939 } 15940 15941 if ('editable' in options) { 15942 if (typeof options.editable === 'boolean') { 15943 this.options.editable.updateTime = options.editable; 15944 this.options.editable.updateGroup = options.editable; 15945 this.options.editable.add = options.editable; 15946 this.options.editable.remove = options.editable; 15947 } else if (typeof options.editable === 'object') { 15948 util.selectiveExtend(['updateTime', 'updateGroup', 'add', 'remove'], this.options.editable, options.editable); 15949 } 15950 } 15951 15952 if ('groupEditable' in options) { 15953 if (typeof options.groupEditable === 'boolean') { 15954 this.options.groupEditable.order = options.groupEditable; 15955 this.options.groupEditable.add = options.groupEditable; 15956 this.options.groupEditable.remove = options.groupEditable; 15957 } else if (typeof options.groupEditable === 'object') { 15958 util.selectiveExtend(['order', 'add', 'remove'], this.options.groupEditable, options.groupEditable); 15959 } 15960 } 15961 15962 // callback functions 15963 var addCallback = (function (name) { 15964 var fn = options[name]; 15965 if (fn) { 15966 if (!(fn instanceof Function)) { 15967 throw new Error('option ' + name + ' must be a function ' + n
15967ame + '(item, callback)'); 15968 } 15969 this.options[name] = fn; 15970 } 15971 }).bind(this); 15972 ['onAdd', 'onUpdate', 'onRemove', 'onMove', 'onMoving', 'onAddGroup', 'onMoveGroup', 'onRemoveGroup'].forEach(addCallback); 15973 15974 // force the itemSet to refresh: options like orientation and margins may be changed 15975 this.markDirty(); 15976 } 15977 }; 15978 15979 /** 15980 * Mark the ItemSet dirty so it will refresh everything with next redraw. 15981 * Optionally, all items can be marked as dirty and be refreshed. 15982 * @param {{refreshItems: boolean}} [options] 15983 */ 15984 ItemSet.prototype.markDirty = function (options) { 15985 this.groupIds = []; 15986 this.stackDirty = true; 15987 15988 if (options && options.refreshItems) { 15989 util.forEach(this.items, function (item) { 15990 item.dirty = true; 15991 if (item.displayed) item.redraw(); 15992 }); 15993 } 15994 }; 15995 15996 /** 15997 * Destroy the ItemSet 15998 */ 15999 ItemSet.prototype.destroy = function () { 16000 this.hide(); 16001 this.setItems(null); 16002 this.setGroups(null); 16003 16004 this.hammer = null; 16005 16006 this.body = null; 16007 this.conversion = null; 16008 }; 16009 16010 /** 16011 * Hide the component from the DOM 16012 */ 16013 ItemSet.prototype.hide = function () { 16014 // remove the frame containing the items 16015 if (this.dom.frame.parentNode) { 16016 this.dom.frame.parentNode.removeChild(this.dom.frame); 16017 } 16018 16019 // remove the axis with dots 16020 if (this.dom.axis.parentNode) { 16021 this.dom.axis.parentNode.removeChild(this.dom.axis); 16022 } 16023 16024 // remove the labelset containing all group labels 16025 if (this.dom.labelSet.parentNode) { 16026 this.dom.labelSet.parentNode.removeChild(this.dom.labelSet); 16027 } 16028 }; 16029 16030 /** 16031 * Show the component in the DOM (when not already visible). 16032 * @return {Boolean} changed 16033 */ 16034 ItemSet.prototype.show = function () { 16035 // show frame containing the items 16036 if (!this.dom.frame.parentNode) { 16037 this.body.dom.center.appendChild(this.dom.frame); 16038 } 16039 16040 // show axis with dots 16041 if (!this.dom.axis.parentNode) { 16042 this.body.dom.backgroundVertical.appendChild(this.dom.axis); 16043 } 16044 16045 // show labelset containing labels 16046 if (!this.dom.labelSet.parentNode) { 16047 this.body.dom.left.appendChild(this.dom.labelSet); 16048 } 16049 }; 16050 16051 /** 16052 * Set selected items by their id. Replaces the current selection 16053 * Unknown id's are silently ignored. 16054 * @param {string[] | string} [ids] An array with zero or more id's of the items to be 16055 * selected, or a single item id. If ids is undefined 16056 * or an empty array, all items will be unselected. 16057 */ 16058 ItemSet.prototype.setSelection = function (ids) { 16059 var i, ii, id, item; 16060 16061 if (ids == undefined) ids = []; 16062 if (!Array.isArray(ids)) ids = [ids]; 16063 16064 // unselect currently selected items 16065 for (i = 0, ii = this.selection.length; i < ii; i++) { 16066 id = this.selection[i]; 16067 item = this.items[id]; 16068 if (item) item.unselect(); 16069 } 16070 16071 // select items 16072 this.selection = []; 16073 for (i = 0, ii = ids.length; i < ii; i++) { 16074 id = ids[i]; 16075 item = this.items[id]; 16076 if (item) { 16077 this.selection.push(id); 16078 item.select(); 16079 } 16080 } 16081 }; 16082 16083 /** 16084 * Get the selected items by their id 16085 * @return {Array} ids The ids of the selected items 16086 */ 16087 ItemSet.prototype.getSelection = function () { 16088 return this.selection.concat([]); 16089 }; 16090 16091 /** 16092 * Get the id's of the currently visible items. 16093 * @returns {Array} The ids of the visible items 16094 */ 16095 ItemSet.prototype.getVisibleItems = function () { 16096 var range = this.body.range.getRange(); 16097 var left = this.body.util.toScreen(range.start); 16098 var right = this.body.util.toScreen(range.end); 16099 16100 var ids = []; 16101 for (var groupId in this.groups) { 16102 if (this.groups.hasOwnProperty(groupId)) { 16103 var group = this.groups[groupId]; 16104 var rawVisibleItems = group.visibleItems; 16105 16106 // filter the "raw" set with visibleItems into a set which is really 16107 // visible by pixels 16108 for (var i = 0; i < rawVisibleItems.length; i++) { 16109 var item = rawVisibleItems[i]; 16110 // TODO: also check whether visible vertically 16111 if (item.left < right && item.left + item.width > left) { 16112 ids.push(item.id); 16113 } 16114 } 16115 } 16116 } 16117 16118 return ids; 16119 }; 16120 16121 /** 16122 * Deselect a selected item 16123 * @param {String | Number} id 16124 * @private 16125 */ 16126 ItemSet.prototype._deselect = function (id) { 16127 var selection = this.selection; 16128 for (var i = 0, ii = selection.length; i < ii; i++) { 16129 if (selection[i] == id) { 16130 // non-strict comparison! 16131 selection.splice(i, 1); 16132 break; 16133 } 16134 } 16135 }; 16136 16137 /** 16138 * Repaint the component 16139 * @return {boolean} Returns true if the component is resized 16140 */ 16141 ItemSet.prototype.redraw = function () { 16142 var margin = this.options.margin, 16143 range = this.body.range, 16144 asSize = util.option.asSize, 16145 options = this.options, 16146 orientation = options.orientation.item, 16147 resized = false, 16148 frame = this.dom.frame; 16149 16150 // recalculate absolute position (before redrawing groups) 16151 this.props.top = this.body.domProps.top.height + this.body.domProps.border.top; 16152 this.props.left = this.body.domProps.left.width + this.body.domProps.border.left; 16153 16154 // update class name 16155 frame.className = 'vis-itemset'; 16156 16157 // reorder the groups (if needed) 16158 resized = this._orderGroups() || resized; 16159 16160 // check whether zoomed (in that case we need to re-stack everything) 16161 // TODO: would be nicer to get this as a trigger from Range 16162 var visibleInterval = range.end - range.start; 16163 var zoomed = visibleInterval != this.lastVisibleInterval || this.props.width != this.props.lastWidth; 16164 if (zoomed) this.stackDirty = true; 16165 this.lastVisibleInterval = visibleInterval; 16166 this.props.lastWidth = this.props.width; 16167 16168 var restack = this.stackDirty; 16169 var firstGroup = this._firstGroup(); 16170 var firstMargin = { 16171 item: margin.item, 16172 axis: margin.axis 16173 }; 16174 var nonFirstMargin = { 16175 item: margin.item, 16176 axis: margin.item.vertical / 2 16177 }; 16178 var height = 0; 16179 var minHeight = margin.axis + margin.item.vertical; 16180 16181 // redraw the background group 16182 this.groups[BACKGROUND].redraw(range, nonFirstMargin, restack); 16183 16184 // redraw all regular groups 16185 util.forEach(this.groups, function (group) { 16186 var groupMargin = group == firstGroup ? firstMargin : nonFirstMargin; 16187 var groupResized = group.redraw(range, groupMargin, restack); 16188 resized = groupResized || resized; 16189 height += group.height; 16190 }); 16191 height = Math.max(height, minHeight); 16192 this.stackDirty = false;
16193 16194 // update frame height 16195 frame.style.height = asSize(height); 16196 16197 // calculate actual size 16198 this.props.width = frame.offsetWidth; 16199 this.props.height = height; 16200 16201 // reposition axis 16202 this.dom.axis.style.top = asSize(orientation == 'top' ? this.body.domProps.top.height + this.body.domProps.border.top : this.body.domProps.top.height + this.body.domProps.centerContainer.height); 16203 this.dom.axis.style.left = '0'; 16204 16205 // check if this component is resized 16206 resized = this._isResized() || resized; 16207 16208 return resized; 16209 }; 16210 16211 /** 16212 * Get the first group, aligned with the axis 16213 * @return {Group | null} firstGroup 16214 * @private 16215 */ 16216 ItemSet.prototype._firstGroup = function () { 16217 var firstGroupIndex = this.options.orientation.item == 'top' ? 0 : this.groupIds.length - 1; 16218 var firstGroupId = this.groupIds[firstGroupIndex]; 16219 var firstGroup = this.groups[firstGroupId] || this.groups[UNGROUPED]; 16220 16221 return firstGroup || null; 16222 }; 16223 16224 /** 16225 * Create or delete the group holding all ungrouped items. This group is used when 16226 * there are no groups specified. 16227 * @protected 16228 */ 16229 ItemSet.prototype._updateUngrouped = function () { 16230 var ungrouped = this.groups[UNGROUPED]; 16231 var background = this.groups[BACKGROUND]; 16232 var item, itemId; 16233 16234 if (this.groupsData) { 16235 // remove the group holding all ungrouped items 16236 if (ungrouped) { 16237 ungrouped.hide(); 16238 delete this.groups[UNGROUPED]; 16239 16240 for (itemId in this.items) { 16241 if (this.items.hasOwnProperty(itemId)) { 16242 item = this.items[itemId]; 16243 item.parent && item.parent.remove(item); 16244 var groupId = this._getGroupId(item.data); 16245 var group = this.groups[groupId]; 16246 group && group.add(item) || item.hide(); 16247 } 16248 } 16249 } 16250 } else { 16251 // create a group holding all (unfiltered) items 16252 if (!ungrouped) { 16253 var id = null; 16254 var data = null; 16255 ungrouped = new Group(id, data, this); 16256 this.groups[UNGROUPED] = ungrouped; 16257 16258 for (itemId in this.items) { 16259 if (this.items.hasOwnProperty(itemId)) { 16260 item = this.items[itemId]; 16261 ungrouped.add(item); 16262 } 16263 } 16264 16265 ungrouped.show(); 16266 } 16267 } 16268 }; 16269 16270 /** 16271 * Get the element for the labelset 16272 * @return {HTMLElement} labelSet 16273 */ 16274 ItemSet.prototype.getLabelSet = function () { 16275 return this.dom.labelSet; 16276 }; 16277 16278 /** 16279 * Set items 16280 * @param {vis.DataSet | null} items 16281 */ 16282 ItemSet.prototype.setItems = function (items) { 16283 var me = this, 16284 ids, 16285 oldItemsData = this.itemsData; 16286 16287 // replace the dataset 16288 if (!items) { 16289 this.itemsData = null; 16290 } else if (items instanceof DataSet || items instanceof DataView) { 16291 this.itemsData = items; 16292 } else { 16293 throw new TypeError('Data must be an instance of DataSet or DataView'); 16294 } 16295 16296 if (oldItemsData) { 16297 // unsubscribe from old dataset 16298 util.forEach(this.itemListeners, function (callback, event) { 16299 oldItemsData.off(event, callback); 16300 }); 16301 16302 // remove all drawn items 16303 ids = oldItemsData.getIds(); 16304 this._onRemove(ids); 16305 } 16306 16307 if (this.itemsData) { 16308 // subscribe to new dataset 16309 var id = this.id; 16310 util.forEach(this.itemListeners, function (callback, event) { 16311 me.itemsData.on(event, callback, id); 16312 }); 16313 16314 // add all new items 16315 ids = this.itemsData.getIds(); 16316 this._onAdd(ids); 16317 16318 // update the group holding all ungrouped items 16319 this._updateUngrouped(); 16320 } 16321 }; 16322 16323 /** 16324 * Get the current items 16325 * @returns {vis.DataSet | null} 16326 */ 16327 ItemSet.prototype.getItems = function () { 16328 return this.itemsData; 16329 }; 16330 16331 /** 16332 * Set groups 16333 * @param {vis.DataSet} groups 16334 */ 16335 ItemSet.prototype.setGroups = function (groups) { 16336 var me = this, 16337 ids; 16338 16339 // unsubscribe from current dataset 16340 if (this.groupsData) { 16341 util.forEach(this.groupListeners, function (callback, event) { 16342 me.groupsData.off(event, callback); 16343 }); 16344 16345 // remove all drawn groups 16346 ids = this.groupsData.getIds(); 16347 this.groupsData = null; 16348 this._onRemoveGroups(ids); // note: this will cause a redraw 16349 } 16350 16351 // replace the dataset 16352 if (!groups) { 16353 this.groupsData = null; 16354 } else if (groups instanceof DataSet || groups instanceof DataView) { 16355 this.groupsData = groups; 16356 } else { 16357 throw new TypeError('Data must be an instance of DataSet or DataView'); 16358 } 16359 16360 if (this.groupsData) { 16361 // subscribe to new dataset 16362 var id = this.id; 16363 util.forEach(this.groupListeners, function (callback, event) { 16364 me.groupsData.on(event, callback, id); 16365 }); 16366 16367 // draw all ms 16368 ids = this.groupsData.getIds(); 16369 this._onAddGroups(ids); 16370 } 16371 16372 // update the group holding all ungrouped items 16373 this._updateUngrouped(); 16374 16375 // update the order of all items in each group 16376 this._order(); 16377 16378 this.body.emitter.emit('change', { queue: true }); 16379 }; 16380 16381 /** 16382 * Get the current groups 16383 * @returns {vis.DataSet | null} groups 16384 */ 16385 ItemSet.prototype.getGroups = function () { 16386 return this.groupsData; 16387 }; 16388 16389 /** 16390 * Remove an item by its id 16391 * @param {String | Number} id 16392 */ 16393 ItemSet.prototype.removeItem = function (id) { 16394 var item = this.itemsData.get(id), 16395 dataset = this.itemsData.getDataSet(); 16396 16397 if (item) {
16398 // confirm deletion 16399 this.options.onRemove(item, function (item) { 16400 if (item) { 16401 // remove by id here, it is possible that an item has no id defined 16402 // itself, so better not delete by the item itself 16403 dataset.remove(id); 16404 } 16405 }); 16406 } 16407 }; 16408 16409 /** 16410 * Get the time of an item based on it's data and options.type 16411 * @param {Object} itemData 16412 * @returns {string} Returns the type 16413 * @private 16414 */ 16415 ItemSet.prototype._getType = function (itemData) { 16416 return itemData.type || this.options.type || (itemData.end ? 'range' : 'box'); 16417 }; 16418 16419 /** 16420 * Get the group id for an item 16421 * @param {Object} itemData 16422 * @returns {string} Returns the groupId 16423 * @private 16424 */ 16425 ItemSet.prototype._getGroupId = function (itemData) { 16426 var type = this._getType(itemData); 16427 if (type == 'background' && itemData.group == undefined) { 16428 return BACKGROUND; 16429 } else { 16430 return this.groupsData ? itemData.group : UNGROUPED; 16431 } 16432 }; 16433 16434 /** 16435 * Handle updated items 16436 * @param {Number[]} ids 16437 * @protected 16438 */ 16439 ItemSet.prototype._onUpdate = function (ids) { 16440 var me = this; 16441 16442 ids.forEach((function (id) { 16443 var itemData = me.itemsData.get(id, me.itemOptions); 16444 var item = me.items[id]; 16445 var type = me._getType(itemData); 16446 16447 var constructor = ItemSet.types[type]; 16448 var selected; 16449 16450 if (item) { 16451 // update item 16452 if (!constructor || !(item instanceof constructor)) { 16453 // item type has changed, delete the item and recreate it 16454 selected = item.selected; // preserve selection of this item 16455 me._removeItem(item); 16456 item = null; 16457 } else { 16458 me._updateItem(item, itemData); 16459 } 16460 } 16461 16462 if (!item) { 16463 // create item 16464 if (constructor) { 16465 item = new constructor(itemData, me.conversion, me.options); 16466 item.id = id; // TODO: not so nice setting id afterwards 16467 me._addItem(item); 16468 if (selected) { 16469 this.selection.push(id); 16470 item.select(); 16471 } 16472 } else if (type == 'rangeoverflow') { 16473 // TODO: deprecated since version 2.1.0 (or 3.0.0?). cleanup some day 16474 throw new TypeError('Item type "rangeoverflow" is deprecated. Use css styling instead: ' + '.vis-item.vis-range .vis-item-content {overflow: visible;}'); 16475 } else { 16476 throw new TypeError('Unknown item type "' + type + '"'); 16477 } 16478 } 16479 }).bind(this)); 16480 16481 this._order(); 16482 this.stackDirty = true; // force re-stacking of all items next redraw 16483 this.body.emitter.emit('change', { queue: true }); 16484 }; 16485 16486 /** 16487 * Handle added items 16488 * @param {Number[]} ids 16489 * @protected 16490 */ 16491 ItemSet.prototype._onAdd = ItemSet.prototype._onUpdate; 16492 16493 /** 16494 * Handle removed items 16495 * @param {Number[]} ids 16496 * @protected 16497 */ 16498 ItemSet.prototype._onRemove = function (ids) { 16499 var count = 0; 16500 var me = this; 16501 ids.forEach(function (id) { 16502 var item = me.items[id]; 16503 if (item) { 16504 count++; 16505 me._removeItem(item); 16506 } 16507 }); 16508 16509 if (count) { 16510 // update order 16511 this._order(); 16512 this.stackDirty = true; // force re-stacking of all items next redraw 16513 this.body.emitter.emit('change', { queue: true }); 16514 } 16515 }; 16516 16517 /** 16518 * Update the order of item in all groups 16519 * @private 16520 */ 16521 ItemSet.prototype._order = function () { 16522 // reorder the items in all groups 16523 // TODO: optimization: only reorder groups affected by the changed items 16524 util.forEach(this.groups, function (group) { 16525 group.order(); 16526 }); 16527 }; 16528 16529 /** 16530 * Handle updated groups 16531 * @param {Number[]} ids 16532 * @private 16533 */ 16534 ItemSet.prototype._onUpdateGroups = function (ids) { 16535 this._onAddGroups(ids); 16536 }; 16537 16538 /** 16539 * Handle changed groups (added or updated) 16540 * @param {Number[]} ids 16541 * @private 16542 */ 16543 ItemSet.prototype._onAddGroups = function (ids) { 16544 var me = this; 16545 16546 ids.forEach(function (id) { 16547 var groupData = me.groupsData.get(id); 16548 var group = me.groups[id]; 16549 16550 if (!group) { 16551 // check for reserved ids 16552 if (id == UNGROUPED || id == BACKGROUND) { 16553 throw new Error('Illegal group id. ' + id + ' is a reserved id.'); 16554 } 16555 16556 var groupOptions = Object.create(me.options); 16557 util.extend(groupOptions, { 16558 height: null 16559 }); 16560 16561 group = new Group(id, groupData, me); 16562 me.groups[id] = group; 16563 16564 // add items with this groupId to the new group 16565 for (var itemId in me.items) { 16566 if (me.items.hasOwnProperty(itemId)) { 16567 var item = me.items[itemId]; 16568 if (item.data.group == id) { 16569 group.add(item); 16570 } 16571 } 16572 } 16573 16574 group.order(); 16575 group.show(); 16576 } else { 16577 // update group 16578 group.setData(groupData); 16579 } 16580 }); 16581 16582 this.body.emitter.emit('change', { queue: true }); 16583 }; 16584 16585 /** 16586 * Handle removed groups 16587 * @param {Number[]} ids 16588 * @private 16589 */ 16590 ItemSet.prototype._onRemoveGroups = function (ids) { 16591 var groups = this.groups; 16592 ids.forEach(function (id) { 16593 var group = groups[id]; 16594 16595 if (group) { 16596 group.hide(); 16597 delete groups[id]; 16598 } 16599 }); 16600 16601 this.markDirty(); 16602 16603 this.body.emitter.emit('change', { queue: true }); 16604 }; 16605 16606 /** 16607 * Reorder the groups if needed 16608 * @return {boolean} changed 16609 * @private 16610 */ 16611 ItemSet.prototype._orderGroups = function () { 16612 if (this.groupsData) { 16613 // reorder the groups 16614 var groupIds = this.groupsData.getIds({ 16615 order: this.options.groupOrder 16616 }); 16617 16618 var changed = !util.equalArray(groupIds, this.groupIds); 16619 if (changed) { 16620 // hide all groups, removes them from the DOM 16621 var groups = this.groups; 16622 groupIds.forEach(function (groupId) { 16623 groups[groupId].hide(); 16624 }); 16625 16626 // show the groups again, attach them to the DOM in correct order 16627 groupIds.forEach(function (groupId) { 16628 groups[groupId].show(); 16629 }); 16630 16631 this.groupIds = groupIds; 16632 } 16633 16634 return changed; 16635 } else { 16636 return false;
16637 } 16638 }; 16639 16640 /** 16641 * Add a new item 16642 * @param {Item} item 16643 * @private 16644 */ 16645 ItemSet.prototype._addItem = function (item) { 16646 this.items[item.id] = item; 16647 16648 // add to group 16649 var groupId = this._getGroupId(item.data); 16650 var group = this.groups[groupId]; 16651 if (group) group.add(item); 16652 }; 16653 16654 /** 16655 * Update an existing item 16656 * @param {Item} item 16657 * @param {Object} itemData 16658 * @private 16659 */ 16660 ItemSet.prototype._updateItem = function (item, itemData) { 16661 var oldGroupId = item.data.group; 16662 var oldSubGroupId = item.data.subgroup; 16663 16664 // update the items data (will redraw the item when displayed) 16665 item.setData(itemData); 16666 16667 // update group 16668 if (oldGroupId != item.data.group || oldSubGroupId != item.data.subgroup) { 16669 var oldGroup = this.groups[oldGroupId]; 16670 if (oldGroup) oldGroup.remove(item); 16671 16672 var groupId = this._getGroupId(item.data); 16673 var group = this.groups[groupId]; 16674 if (group) group.add(item); 16675 } 16676 }; 16677 16678 /** 16679 * Delete an item from the ItemSet: remove it from the DOM, from the map 16680 * with items, and from the map with visible items, and from the selection 16681 * @param {Item} item 16682 * @private 16683 */ 16684 ItemSet.prototype._removeItem = function (item) { 16685 // remove from DOM 16686 item.hide(); 16687 16688 // remove from items 16689 delete this.items[item.id]; 16690 16691 // remove from selection 16692 var index = this.selection.indexOf(item.id); 16693 if (index != -1) this.selection.splice(index, 1); 16694 16695 // remove from group 16696 item.parent && item.parent.remove(item); 16697 }; 16698 16699 /** 16700 * Create an array containing all items being a range (having an end date) 16701 * @param array 16702 * @returns {Array} 16703 * @private 16704 */ 16705 ItemSet.prototype._constructByEndArray = function (array) { 16706 var endArray = []; 16707 16708 for (var i = 0; i < array.length; i++) { 16709 if (array[i] instanceof RangeItem) { 16710 endArray.push(array[i]); 16711 } 16712 } 16713 return endArray; 16714 }; 16715 16716 /** 16717 * Register the clicked item on touch, before dragStart is initiated. 16718 * 16719 * dragStart is initiated from a mousemove event, AFTER the mouse/touch is 16720 * already moving. Therefore, the mouse/touch can sometimes be above an other 16721 * DOM element than the item itself. 16722 * 16723 * @param {Event} event 16724 * @private 16725 */ 16726 ItemSet.prototype._onTouch = function (event) { 16727 // store the touched item, used in _onDragStart 16728 this.touchParams.item = this.itemFromTarget(event); 16729 this.touchParams.dragLeftItem = event.target.dragLeftItem || false; 16730 this.touchParams.dragRightItem = event.target.dragRightItem || false; 16731 this.touchParams.itemProps = null; 16732 }; 16733 16734 /** 16735 * Given an group id, returns the index it has. 16736 * 16737 * @param {Number} groupID 16738 * @private 16739 */ 16740 ItemSet.prototype._getGroupIndex = function (groupId) { 16741 for (var i = 0; i < this.groupIds.length; i++) { 16742 if (groupId == this.groupIds[i]) return i; 16743 } 16744 }; 16745 16746 /** 16747 * Start dragging the selected events 16748 * @param {Event} event 16749 * @private 16750 */ 16751 ItemSet.prototype._onDragStart = function (event) { 16752 var item = this.touchParams.item || null; 16753 var me = this; 16754 var props; 16755 16756 if (item && item.selected) { 16757 16758 if (!this.options.editable.updateTime && !this.options.editable.updateGroup && !item.editable) { 16759 return; 16760 } 16761 16762 // override options.editable 16763 if (item.editable === false) { 16764 return; 16765 } 16766 16767 var dragLeftItem = this.touchParams.dragLeftItem; 16768 var dragRightItem = this.touchParams.dragRightItem; 16769 16770 if (dragLeftItem) { 16771 props = { 16772 item: dragLeftItem, 16773 initialX: event.center.x, 16774 dragLeft: true, 16775 data: this._cloneItemData(item.data) 16776 }; 16777 16778 this.touchParams.itemProps = [props]; 16779 } else if (dragRightItem) { 16780 props = { 16781 item: dragRightItem, 16782 initialX: event.center.x, 16783 dragRight: true, 16784 data: this._cloneItemData(item.data) 16785 }; 16786 16787 this.touchParams.itemProps = [props]; 16788 } else { 16789 this.touchParams.selectedItem = item; 16790 16791 var baseGroupIndex = this._getGroupIndex(item.data.group); 16792
16793 this.touchParams.itemProps = this.getSelection().map((function (id) { 16794 var item = me.items[id]; 16795 var groupIndex = me._getGroupIndex(item.data.group); 16796 return { 16797 item: item, 16798 initialX: event.center.x, 16799 groupOffset: baseGroupIndex - groupIndex, 16800 data: this._cloneItemData(item.data) 16801 }; 16802 }).bind(this)); 16803 } 16804 16805 event.stopPropagation(); 16806 } else if (this.options.editable.add && (event.srcEvent.ctrlKey || event.srcEvent.metaKey)) { 16807 // create a new range item when dragging with ctrl key down 16808 this._onDragStartAddItem(event); 16809 } 16810 }; 16811 16812 /** 16813 * Start creating a new range item by dragging. 16814 * @param {Event} event 16815 * @private 16816 */ 16817 ItemSet.prototype._onDragStartAddItem = function (event) { 16818 var snap = this.options.snap || null; 16819 var xAbs = util.getAbsoluteLeft(this.dom.frame); 16820 var x = event.center.x - xAbs - 10; // minus 10 to compensate for the drag starting as soon as you've moved 10px 16821 var time = this.body.util.toTime(x); 16822 var scale = this.body.util.getScale(); 16823 var step = this.body.util.getStep(); 16824 var start = snap ? snap(time, scale, step) : start; 16825 var end = start; 16826 16827 var itemData = { 16828 type: 'range', 16829 start: start, 16830 end: end, 16831 content: 'new item' 16832 }; 16833 16834 var id = util.randomUUID(); 16835 itemData[this.itemsData._fieldId] = id; 16836 16837 var group = this.groupFromTarget(event); 16838 if (group) { 16839 itemData.group = group.groupId; 16840 } 16841 16842 var newItem = new RangeItem(itemData, this.conversion, this.options); 16843 newItem.id = id; // TODO: not so nice setting id afterwards 16844 newItem.data = this._cloneItemData(itemData); 16845 this._addItem(newItem); 16846 16847 var props = { 16848 item: newItem, 16849 dragRight: true, 16850 initialX: event.center.x, 16851 data: newItem.data 16852 }; 16853 this.touchParams.itemProps = [props]; 16854 16855 event.stopPropagation(); 16856 }; 16857 16858 /** 16859 * Drag selected items 16860 * @param {Event} event 16861 * @private 16862 */ 16863 ItemSet.prototype._onDrag = function (event) { 16864 if (this.touchParams.itemProps) { 16865 event.stopPropagation(); 16866 16867 var me = this; 16868 var snap = this.options.snap || null; 16869 var xOffset = this.body.dom.root.offsetLeft + this.body.domProps.left.width; 16870 var scale = this.body.util.getScale(); 16871 var step = this.body.util.getStep(); 16872 16873 //only calculate the new group for the item that's actually dragged 16874 var selectedItem = this.touchParams.selectedItem; 16875 var updateGroupAllowed = me.options.editable.updateGroup; 16876 var newGroupBase = null; 16877 if (updateGroupAllowed && selectedItem) { 16878 if (selectedItem.data.group != undefined) { 16879 // drag from one group to another 16880 var group = me.groupFromTarget(event); 16881 if (group) { 16882 //we know the offset for all items, so the new group for all items 16883 //will be relative to this one. 16884 newGroupBase = this._getGroupIndex(group.groupId); 16885 } 16886 } 16887 } 16888 16889 // move 16890 this.touchParams.itemProps.forEach((function (props) { 16891 var current = me.body.util.toTime(event.center.x - xOffset); 16892 var initial = me.body.util.toTime(props.initialX - xOffset); 16893 var offset = current - initial; // ms 16894 16895 var itemData = this._cloneItemData(props.item.data); // clone the data 16896 if (props.item.editable === false) { 16897 return; 16898 } 16899 16900 var updateTimeAllowed = me.options.editable.updateTime || props.item.editable === true; 16901 16902 if (updateTimeAllowed) { 16903 if (props.dragLeft) { 16904 // drag left side of a range item 16905 if (itemData.start != undefined) { 16906 var initialStart = util.convert(props.data.start, 'Date'); 16907 var start = new Date(initialStart.valueOf() + offset); 16908 // TODO: pass a Moment instead of a Date to snap(). (Breaking change) 16909 itemData.start = snap ? snap(start, scale, step) : start; 16910 } 16911 } else if (props.dragRight) { 16912 // drag right side of a range item 16913 if (itemData.end != undefined) { 16914 var initialEnd = util.convert(props.data.end, 'Date'); 16915 var end = new Date(initialEnd.valueOf() + offset); 16916 // TODO: pass a Moment instead of a Date to snap(). (Breaking change) 16917 itemData.end = snap ? snap(end, scale, step) : end; 16918 } 16919 } else { 16920 // drag both start and end 16921 if (itemData.start != undefined) { 16922 var initialStart = util.convert(props.data.start, 'Date').valueOf(); 16923 var start = new Date(initialStart + offset); 16924 16925 if (itemData.end != undefined) { 16926 var initialEnd = util.convert(props.data.end, 'Date'); 16927 var duration = initialEnd.valueOf() - initialStart.valueOf(); 16928 16929 // TODO: pass a Moment instead of a Date to snap(). (Breaking change) 16930 itemData.start = snap ? snap(start, scale, step) : start; 16931 itemData.end = new Date(itemData.start.valueOf() + duration); 16932 } else { 16933 // TODO: pass a Moment instead of a Date to snap(). (Breaking change) 16934 itemData.start = snap ? snap(start, scale, step) : start; 16935 } 16936 } 16937 } 16938 } 16939 16940 var updateGroupAllowed = me.options.editable.updateGroup || props.item.editable === true; 16941 16942 if (updateGroupAllowed && (!props.dragLeft && !props.dragRight) && newGroupBase != null) { 16943 if (itemData.group != undefined) { 16944 var newOffset = newGroupBase - props.groupOffset; 16945 16946 //make sure we stay in bounds 16947 newOffset = Math.max(0, newOffset); 16948 newOffset = Math.min(me.groupIds.length - 1, newOffset); 16949 16950 itemData.group = me.groupIds[newOffset]; 16951 } 16952 } 16953 16954 // confirm moving the item 16955 itemData = this._cloneItemData(itemData); // convert start and end to the correct type 16956 me.options.onMoving(itemData, (function (itemData) { 16957 if (itemData) { 16958 props.item.setData(this._cloneItemData(itemData, 'Date')); 16959 } 16960 }).bind(this)); 16961 }).bind(this)); 16962 16963 this.stackDirty = true; // force re-stacking of all items next redraw 16964 this.body.emitter.emit('change'); 16965 } 16966 }; 16967 16968 /** 16969 * Move an item to another group 16970 * @param {Item} item 16971 * @param {String | Number} groupId 16972 * @private 16973 */ 16974 ItemSet.prototype._moveToGroup = function (item, groupId) { 16975 var group = this.groups[groupId]; 16976 if (group && group.groupId != item.data.group) { 16977 var oldGroup = item.parent; 16978 oldGroup.remove(item); 16979 oldGroup.order(); 16980 group.add(item); 16981 group.order(); 16982 16983 item.data.group = group.groupId; 16984 } 16985 }; 16986 16987 /** 16988 * End of dragging selected items 16989 * @param {Event} event 16990 * @private 16991 */ 16992 ItemSet.prototype._onDragEnd = function (event) { 16993 if (this.touchParams.itemProps) { 16994 event.stopPropagation(); 16995 16996 var me = this; 16997 var dataset = this.itemsData.getDataSet(); 16998 var itemProps = this.touchParams.itemProps; 16999 this.touchParams.itemProps = null; 17000 17001 itemProps.forEach((function (props) { 17002 var id = props.item.id; 17003 var exists = me.itemsData.get(id, me.itemOptions) != null; 17004 17005 if (!exists) { 17006 // add a new item 17007 me.options.onAdd(props.item.data, function (itemData) { 17008 me._removeItem(props.item); // remove temporary item 17009 if (itemData) { 17010 me.itemsData.getDataSet().add(itemData); 17011 } 17012 17013 // force re-stacking of all items next redraw 17014 me.stackDirty = true; 17015 me.body.emitter.emit('change'); 17016 }); 17017 } else { 17018 // update existing item 17019 var itemData = this._cloneItemData(props.item.data); // convert start and end to the correct type 17020 me.options.onMove(itemData, function (itemData) { 17021 if (itemData) { 17022 // apply changes 17023 itemData[dataset._fieldId] = id; // ensure the item contains its id (can be undefined) 17024 dataset.update(itemData); 17025 } else { 17026 // restore original values 17027 props.item.setData(props.data); 17028 17029 me.stackDirty = true; // force re-stacking of all items next redraw 17030 me.body.emitter.emit('change'); 17031 } 17032 }); 17033 } 17034 }).bind(this)); 17035 } 17036 }; 17037 17038 ItemSet.prototype._onGroupDragStart = function (event) { 17039 if (this.options.groupEditable.order) { 17040 this.groupTouchParams.group = this.groupFromTarget(event); 17041 17042 if (this.groupTouchParams.group) { 17043 event.stopPropagation(); 17044 17045 this.groupTouchParams.originalOrder = this.groupsData.getIds({ 17046 order: this.options.groupOrder 17047 }); 17048 } 17049 } 17050 }; 17051 17052 ItemSet.prototype._onGroupDrag = function (event) { 17053 if (this.options.groupEditable.order && this.groupTouchParams.group) { 17054 event.stopPropagation(); 17055 17056 // drag from one group to another 17057 var group = this.groupFromTarget(event); 17058 17059 // try to avoid toggling when groups differ in height 17060 if (group && group.height != this.groupTouchParams.group.height) { 17061 var movingUp = group.top < this.groupTouchParams.group.top; 17062 var clientY = event.center ? event.center.y : event.clientY; 17063 var targetGroupTop = util.getAbsoluteTop(group.dom.foreground); 17064 var draggedGroupHeight = this.groupTouchParams.group.height; 17065 if (movingUp) { 17066 // skip swapping the groups when the dragged group is not below clientY afterwards 17067 if (targetGroupTop + draggedGroupHeight < clientY) { 17068 return; 17069 } 17070 } else { 17071 var targetGroupHeight = group.height; 17072 // skip swapping the groups when the dragged group is not below clientY afterwards 17073 if (targetGroupTop + targetGroupHeight - draggedGroupHeight > clientY) { 17074 return; 17075 } 17076 } 17077 } 17078 17079 if (group && group != this.groupTouchParams.group) { 17080 var groupsData = this.groupsData; 17081 var targetGroup = groupsData.get(group.groupId); 17082 var draggedGroup = groupsData.get(this.groupTouchParams.group.groupId); 17083 17084 // switch groups 17085 if (draggedGroup && targetGroup) { 17086 this.options.groupOrderSwap(draggedGroup, targetGroup, this.groupsData); 17087 this.groupsData.update(draggedGroup); 17088 this.groupsData.update(targetGroup); 17089 } 17090 17091 // fetch current order of groups 17092 var newOrder = this.groupsData.getIds({ 17093 order: this.options.groupOrder 17094 }); 17095 17096 // in case of changes since _onGroupDragStart 17097 if (!util.equalArray(newOrder, this.groupTouchParams.originalOrder)) { 17098 var groupsData = this.groupsData; 17099 var origOrder = this.groupTouchParams.originalOrder; 17100 var draggedId = this.groupTouchParams.group.groupId; 17101 var numGroups = Math.min(origOrder.length, newOrder.length); 17102 var curPos = 0; 17103 var newOffset = 0; 17104 var orgOffset = 0; 17105 while (curPos < numGroups) { 17106 // as long as the groups are where they should be step down along the groups order 17107 while (curPos + newOffset < numGroups && curPos + orgOffset < numGroups && newOrder[curPos + newOffset] == origOrder[curPos + orgOffset]) { 17108 curPos++; 17109 } 17110 17111 // all ok 17112 if (curPos + newOffset >= numGroups) { 17113 break; 17114 } 17115 17116 // not all ok 17117 // if dragged group was move upwards everything below should have an offset 17118 if (newOrder[curPos + newOffset] == draggedId) { 17119 newOffset = 1; 17120 continue; 17121 } 17122 // if dragged group was move downwards everything above should have an offset 17123 else if (origOrder[curPos + orgOffset] == draggedId) { 17124 orgOffset = 1; 17125 continue; 17126 } 17127 // found a group (apart from dragged group) that has the wrong position -> switch with the 17128 // group at the position where other one should be, fix index arrays and continue 17129 else { 17130 var slippedPosition = newOrder.indexOf(origOrder[curPos + orgOffset]); 17131 var switchGroup = groupsData.get(newOrder[curPos + newOffset]); 17132 var shouldBeGroup = groupsData.get(origOrder[curPos + orgOffset]); 17133 this.options.groupOrderSwap(switchGroup, shouldBeGroup, groupsData); 17134 groupsData.update(switchGroup); 17135 groupsData.update(shouldBeGroup); 17136 17137 var switchGroupId = newOrder[curPos + newOffset]; 17138 newOrder[curPos + newOffset] = origOrder[curPos + orgOffset]; 17139 newOrder[slippedPosition] = switchGroupId; 17140 17141 curPos++; 17142 } 17143 } 17144 } 17145 } 17146 } 17147 }; 17148 17149 ItemSet.prototype._onGroupDragEnd = function (event) { 17150 if (this.options.groupEditable.order && this.groupTouchParams.group) { 17151 event.stopPropagation(); 17152 17153 // update existing group 17154 var me = this; 17155 var id = me.groupTouchParams.group.groupId; 17156 var dataset = me.groupsData.getDataSet(); 17157 var groupData = util.extend({}, dataset.get(id)); // clone the data 17158 me.options.onMoveGroup(groupData, function (groupData) { 17159 if (groupData) { 17160 // apply changes 17161 groupData[dataset._fieldId] = id; // ensure the group contains its id (can be undefined) 17162 dataset.update(groupData); 17163 } else { 17164 17165 // fetch current order of groups 17166 var newOrder = dataset.getIds({ 17167 order: me.options.groupOrder 17168 }); 17169 17170 // restore original order 17171 if (!util.equalArray(newOrder, me.groupTouchParams.originalOrder)) { 17172 var origOrder = me.groupTouchParams.originalOrder; 17173 var numGroups = Math.min(origOrder.length, newOrder.length); 17174 var curPos = 0; 17175 while (curPos < numGroups) { 17176 // as long as the groups are where they should be step down along the groups order 17177 while (curPos < numGroups && newOrder[curPos] == origOrder[curPos]) { 17178 curPos++; 17179 } 17180 17181 // all ok 17182 if (curPos >= numGroups) { 17183 break; 17184 } 17185 17186 // found a group that has the wrong position -> switch with the 17187 // group at the position where other one should be, fix index arrays and continue 17188 var slippedPosition = newOrder.indexOf(origOrder[curPos]); 17189 var switchGroup = dataset.get(newOrder[curPos]); 17190 var shouldBeGroup = dataset.get(origOrder[curPos]); 17191 me.options.groupOrderSwap(switchGroup, shouldBeGroup, dataset); 17192 groupsData.update(switchGroup); 17193 groupsData.update(shouldBeGroup); 17194 17195 var switchGroupId = newOrder[curPos]; 17196 newOrder[curPos] = origOrder[curPos]; 17197 newOrder[slippedPosition] = switchGroupId; 17198 17199 curPos++; 17200 } 17201 } 17202 } 17203 }); 17204 17205 me.body.emitter.emit('groupDragged', { groupId: id }); 17206 } 17207 }; 17208 17209 /** 17210 * Handle selecting/deselecting an item when tapping it 17211 * @param {Event} event 17212 * @private 17213 */ 17214 ItemSet.prototype._onSelectItem = function (event) { 17215 if (!this.options.selectable) return; 17216 17217 var ctrlKey = event.srcEvent && (event.srcEvent.ctrlKey || event.srcEvent.metaKey); 17218 var shiftKey = event.srcEvent && event.srcEvent.shiftKey;
17219 if (ctrlKey || shiftKey) { 17220 this._onMultiSelectItem(event); 17221 return; 17222 } 17223 17224 var oldSelection = this.getSelection(); 17225 17226 var item = this.itemFromTarget(event); 17227 var selection = item ? [item.id] : []; 17228 this.setSelection(selection); 17229 17230 var newSelection = this.getSelection(); 17231 17232 // emit a select event, 17233 // except when old selection is empty and new selection is still empty 17234 if (newSelection.length > 0 || oldSelection.length > 0) { 17235 this.body.emitter.emit('select', { 17236 items: newSelection, 17237 event: event 17238 }); 17239 } 17240 }; 17241 17242 /** 17243 * Handle creation and updates of an item on double tap 17244 * @param event 17245 * @private 17246 */ 17247 ItemSet.prototype._onAddItem = function (event) { 17248 if (!this.options.selectable) return; 17249 if (!this.options.editable.add) return; 17250 17251 var me = this; 17252 var snap = this.options.snap || null; 17253 var item = this.itemFromTarget(event); 17254 17255 event.stopPropagation(); 17256 17257 if (item) { 17258 // update item 17259 17260 // execute async handler to update the item (or cancel it) 17261 var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset 17262 this.options.onUpdate(itemData, function (itemData) { 17263 if (itemData) { 17264 me.itemsData.getDataSet().update(itemData); 17265 } 17266 }); 17267 } else { 17268 // add item 17269 var xAbs = util.getAbsoluteLeft(this.dom.frame); 17270 var x = event.center.x - xAbs; 17271 var start = this.body.util.toTime(x); 17272 var scale = this.body.util.getScale(); 17273 var step = this.body.util.getStep(); 17274 17275 var newItemData = { 17276 start: snap ? snap(start, scale, step) : start, 17277 content: 'new item' 17278 }; 17279 17280 // when default type is a range, add a default end date to the new item 17281 if (this.options.type === 'range') { 17282 var end = this.body.util.toTime(x + this.props.width / 5); 17283 newItemData.end = snap ? snap(end, scale, step) : end; 17284 } 17285 17286 newItemData[this.itemsData._fieldId] = util.randomUUID(); 17287 17288 var group = this.groupFromTarget(event); 17289 if (group) { 17290 newItemData.group = group.groupId; 17291 } 17292 17293 // execute async handler to customize (or cancel) adding an item 17294 newItemData = this._cloneItemData(newItemData); // convert start and end to the correct type 17295 this.options.onAdd(newItemData, function (item) { 17296 if (item) { 17297 me.itemsData.getDataSet().add(item); 17298 // TODO: need to trigger a redraw? 17299 } 17300 }); 17301 } 17302 }; 17303 17304 /** 17305 * Handle selecting/deselecting multiple items when holding an item 17306 * @param {Event} event 17307 * @private 17308 */ 17309 ItemSet.prototype._onMultiSelectItem = function (event) { 17310 if (!this.options.selectable) return; 17311 17312 var item = this.itemFromTarget(event); 17313 17314 if (item) { 17315 // multi select items (if allowed) 17316
17317 var selection = this.options.multiselect ? this.getSelection() // take current selection 17318 : []; // deselect current selection 17319 17320 var shiftKey = event.srcEvent && event.srcEvent.shiftKey || false; 17321 17322 if (shiftKey && this.options.multiselect) { 17323 // select all items between the old selection and the tapped item 17324 17325 // determine the selection range 17326 selection.push(item.id); 17327 var range = ItemSet._getItemRange(this.itemsData.get(selection, this.itemOptions)); 17328 17329 // select all items within the selection range 17330 selection = []; 17331 for (var id in this.items) { 17332 if (this.items.hasOwnProperty(id)) { 17333 var _item = this.items[id]; 17334 var start = _item.data.start; 17335 var end = _item.data.end !== undefined ? _item.data.end : start; 17336 17337 if (start >= range.min && end <= range.max && !(_item instanceof BackgroundItem)) { 17338 selection.push(_item.id); // do not use id but item.id, id itself is stringified 17339 } 17340 } 17341 } 17342 } else { 17343 // add/remove this item from the current selection 17344 var index = selection.indexOf(item.id); 17345 if (index == -1) { 17346 // item is not yet selected -> select it 17347 selection.push(item.id); 17348 } else { 17349 // item is already selected -> deselect it 17350 selection.splice(index, 1); 17351 } 17352 } 17353 17354 this.setSelection(selection); 17355 17356 this.body.emitter.emit('select', { 17357 items: this.getSelection(), 17358 event: event 17359 }); 17360 } 17361 }; 17362 17363 /** 17364 * Calculate the time range of a list of items 17365 * @param {Array.<Object>} itemsData 17366 * @return {{min: Date, max: Date}} Returns the range of the provided items 17367 * @private 17368 */ 17369 ItemSet._getItemRange = function (itemsData) { 17370 var max = null; 17371 var min = null; 17372 17373 itemsData.forEach(function (data) { 17374 if (min == null || data.start < min) { 17375 min = data.start; 17376 } 17377 17378 if (data.end != undefined) { 17379 if (max == null || data.end > max) { 17380 max = data.end; 17381 } 17382 } else { 17383 if (max == null || data.start > max) { 17384 max = data.start; 17385 } 17386 } 17387 }); 17388 17389 return { 17390 min: min, 17391 max: max 17392 }; 17393 }; 17394 17395 /** 17396 * Find an item from an event target: 17397 * searches for the attribute 'timeline-item' in the event target's element tree 17398 * @param {Event} event 17399 * @return {Item | null} item 17400 */ 17401 ItemSet.prototype.itemFromTarget = function (event) { 17402 var target = event.target; 17403 while (target) { 17404 if (target.hasOwnProperty('timeline-item')) { 17405 return target['timeline-item']; 17406 } 17407 target = target.parentNode; 17408 } 17409 17410 return null; 17411 }; 17412 17413 /** 17414 * Find the Group from an event target: 17415 * searches for the attribute 'timeline-group' in the event target's element tree 17416 * @param {Event} event 17417 * @return {Group | null} group 17418 */ 17419 ItemSet.prototype.groupFromTarget = function (event) { 17420 var clientY = event.center ? event.center.y : event.clientY; 17421 for (var i = 0; i < this.groupIds.length; i++) { 17422 var groupId = this.groupIds[i]; 17423 var group = this.groups[groupId]; 17424 var foreground = group.dom.foreground; 17425 var top = util.getAbsoluteTop(foreground); 17426 if (clientY > top && clientY < top + foreground.offsetHeight) { 17427 return group; 17428 } 17429 17430 if (this.options.orientation.item === 'top') { 17431 if (i === this.groupIds.length - 1 && clientY > top) { 17432 return group; 17433 } 17434 } else { 17435 if (i === 0 && clientY < top + foreground.offset) { 17436 return group; 17437 } 17438 } 17439 } 17440 17441 return null; 17442 }; 17443 17444 /** 17445 * Find the ItemSet from an event target: 17446 * searches for the attribute 'timeline-itemset' in the event target's element tree 17447 * @param {Event} event 17448 * @return {ItemSet | null} item 17449 */ 17450 ItemSet.itemSetFromTarget = function (event) { 17451 var target = event.target; 17452 while (target) { 17453 if (target.hasOwnProperty('timeline-itemset')) { 17454 return target['timeline-itemset']; 17455 } 17456 target = target.parentNode; 17457 } 17458 17459 return null; 17460 }; 17461 17462 /** 17463 * Clone the data of an item, and "normalize" it: convert the start and end date 17464 * to the type (Date, Moment, ...) configured in the DataSet. If not configured, 17465 * start and end are converted to Date. 17466 * @param {Object} itemData, typically `item.data` 17467 * @param {string} [type] Optional Date type. If not provided, the type from the DataSet is taken 17468 * @return {Object} The cloned object 17469 * @private 17470 */ 17471 ItemSet.prototype._cloneItemData = function (itemData, type) { 17472 var clone = util.extend({}, itemData); 17473 17474 if (!type) { 17475 // convert start and end date to the type (Date, Moment, ...) configured in the DataSet 17476 type = this.itemsData.getDataSet()._options.type; 17477 } 17478 17479 if (clone.start != undefined) { 17480 clone.start = util.convert(clone.start, type && type.start || 'Date'); 17481 } 17482 if (clone.end != undefined) { 17483 clone.end = util.convert(clone.end, type && type.end || 'Date'); 17484 } 17485 17486 return clone; 17487 }; 17488 17489 module.exports = ItemSet; 17490 17491/***/ }, 17492/* 30 */ 17493/***/ function(module, exports, __webpack_require__) { 17494 17495 'use strict'; 17496 17497 var moment = __webpack_require__(2); 17498 var DateUtil = __webpack_require__(27); 17499 var util = __webpack_require__(1); 17500 17501 /** 17502 * @constructor TimeStep 17503 * The class TimeStep is an iterator for dates. You provide a start date and an 17504 * end date. The class itself determines the best scale (step size) based on the 17505 * provided start Date, end Date, and minimumStep. 17506 * 17507 * If minimumStep is provided, the step size is chosen as close as possible 17508 * to the minimumStep but larger than minimumStep. If minimumStep is not 17509 * provided, the scale is set to 1 DAY. 17510 * The minimumStep should correspond with the onscreen size of about 6 characters 17511 * 17512 * Alternatively, you can set a scale by hand. 17513 * After creation, you can initialize the class by executing first(). Then you 17514 * can iterate from the start date to the end date via next(). You can check if 17515 * the end date is reached with the function hasNext(). After each step, you can 17516 * retrieve the current date via getCurrent(). 17517 * The TimeStep has scales ranging from milliseconds, seconds, minutes, hours, 17518 * days, to years. 17519 * 17520 * Version: 1.2 17521 * 17522 * @param {Date} [start] The start date, for example new Date(2010, 9, 21) 17523 * or new Date(2010, 9, 21, 23, 45, 00) 17524 * @param {Date} [end] The end date 17525 * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds 17526 */ 17527 function TimeStep(start, end, minimumStep, hiddenDates) { 17528 this.moment = moment; 17529 17530 // variables 17531 this.current = this.moment(); 17532 this._start = this.moment(); 17533 this._end = this.moment(); 17534 17535 this.autoScale = true; 17536 this.scale = 'day'; 17537 this.step = 1; 17538 17539 // initialize the range 17540 this.setRange(start, end, minimumStep); 17541 17542 // hidden Dates options 17543 this.switchedDay = false;
17544 this.switchedMonth = false; 17545 this.switchedYear = false; 17546 if (Array.isArray(hiddenDates)) { 17547 this.hiddenDates = hiddenDates; 17548 } else if (hiddenDates != undefined) { 17549 this.hiddenDates = [hiddenDates]; 17550 } else { 17551 this.hiddenDates = []; 17552 } 17553 17554 this.format = TimeStep.FORMAT; // default formatting 17555 } 17556 17557 // Time formatting 17558 TimeStep.FORMAT = { 17559 minorLabels: { 17560 millisecond: 'SSS', 17561 second: 's', 17562 minute: 'HH:mm', 17563 hour: 'HH:mm', 17564 weekday: 'ddd D', 17565 day: 'D', 17566 month: 'MMM', 17567 year: 'YYYY' 17568 }, 17569 majorLabels: { 17570 millisecond: 'HH:mm:ss', 17571 second: 'D MMMM HH:mm', 17572 minute: 'ddd D MMMM', 17573 hour: 'ddd D MMMM', 17574 weekday: 'MMMM YYYY', 17575 day: 'MMMM YYYY', 17576 month: 'YYYY', 17577 year: '' 17578 } 17579 }; 17580 17581 /** 17582 * Set custom constructor function for moment. Can be used to set dates 17583 * to UTC or to set a utcOffset. 17584 * @param {function} moment 17585 */ 17586 TimeStep.prototype.setMoment = function (moment) { 17587 this.moment = moment; 17588 17589 // update the date properties, can have a new utcOffset 17590 this.current = this.moment(this.current); 17591 this._start = this.moment(this._start); 17592 this._end = this.moment(this._end); 17593 }; 17594 17595 /** 17596 * Set custom formatting for the minor an major labels of the TimeStep. 17597 * Both `minorLabels` and `majorLabels` are an Object with properties: 17598 * 'millisecond', 'second', 'minute', 'hour', 'weekday', 'day', 'month', 'year'. 17599 * @param {{minorLabels: Object, majorLabels: Object}} format 17600 */ 17601 TimeStep.prototype.setFormat = function (format) { 17602 var defaultFormat = util.deepExtend({}, TimeStep.FORMAT); 17603 this.format = util.deepExtend(defaultFormat, format); 17604 }; 17605 17606 /** 17607 * Set a new range 17608 * If minimumStep is provided, the step size is chosen as close as possible 17609 * to the minimumStep but larger than minimumStep. If minimumStep is not 17610 * provided, the scale is set to 1 DAY. 17611 * The minimumStep should correspond with the onscreen size of about 6 characters 17612 * @param {Date} [start] The start date and time. 17613 * @param {Date} [end] The end date and time. 17614 * @param {int} [minimumStep] Optional. Minimum step size in milliseconds 17615 */ 17616 TimeStep.prototype.setRange = function (start, end, minimumStep) { 17617 if (!(start instanceof Date) || !(end instanceof Date)) { 17618 throw 'No legal start or end date in method setRange'; 17619 } 17620 17621 this._start = start != undefined ? this.moment(start.valueOf()) : new Date(); 17622 this._end = end != undefined ? this.moment(end.valueOf()) : new Date(); 17623 17624 if (this.autoScale) { 17625 this.setMinimumStep(minimumStep); 17626 } 17627 }; 17628 17629 /** 17630 * Set the range iterator to the start date. 17631 */ 17632 TimeStep.prototype.start = function () { 17633 this.current = this._start.clone(); 17634 this.roundToMinor(); 17635 }; 17636 17637 /** 17638 * Round the current date to the first minor date value 17639 * This must be executed once when the current date is set to start Date 17640 */ 17641 TimeStep.prototype.roundToMinor = function () { 17642 // round to floor 17643 // IMPORTANT: we have no breaks in this switch! (this is no bug) 17644 // noinspection FallThroughInSwitchStatementJS 17645 switch (this.scale) { 17646 case 'year': 17647 this.current.year(this.step * Math.floor(this.current.year() / this.step)); 17648 this.current.month(0); 17649 case 'month': 17650 this.current.date(1); 17651 case 'day': // intentional fall through 17652 case 'weekday': 17653 this.current.hours(0); 17654 case 'hour': 17655 this.current.minutes(0); 17656 case 'minute': 17657 this.current.seconds(0); 17658 case 'second': 17659 this.current.milliseconds(0); 17660 //case 'millisecond': // nothing to do for milliseconds 17661 } 17662 17663 if (this.step != 1) { 17664 // round down to the first minor value that is a multiple of the current step size 17665 switch (this.scale) { 17666 case 'millisecond': 17667 this.current.subtract(this.current.milliseconds() % this.step, 'milliseconds');break; 17668 case 'second': 17669 this.current.subtract(this.current.seconds() % this.step, 'seconds');break; 17670 case 'minute': 17671 this.current.subtract(this.current.minutes() % this.step, 'minutes');break; 17672 case 'hour': 17673 this.current.subtract(this.current.hours() % this.step, 'hours');break; 17674 case 'weekday': // intentional fall through 17675 case 'day': 17676 this.current.subtract((this.current.date() - 1) % this.step, 'day');break; 17677 case 'month': 17678 this.current.subtract(this.current.month() % this.step, 'month');break; 17679 case 'year': 17680 this.current.subtract(this.current.year() % this.step, 'year');break; 17681 default: 17682 break; 17683 } 17684 } 17685 }; 17686 17687 /** 17688 * Check if the there is a next step 17689 * @return {boolean} true if the current date has not passed the end date 17690 */ 17691 TimeStep.prototype.hasNext = function () { 17692 return this.current.valueOf() <= this._end.valueOf(); 17693 }; 17694 17695 /** 17696 * Do the next step 17697 */ 17698 TimeStep.prototype.next = function () { 17699 var prev = this.current.valueOf(); 17700 17701 // Two cases, needed to prevent issues with switching daylight savings 17702 // (end of March and end of October) 17703 if (this.current.month() < 6) { 17704 switch (this.scale) { 17705 case 'millisecond': 17706 this.current.add(this.step, 'millisecond');break; 17707 case 'second': 17708 this.current.add(this.step, 'second');break; 17709 case 'minute': 17710 this.current.add(this.step, 'minute');break; 17711 case 'hour': 17712 this.current.add(this.step, 'hour'); 17713 // in case of skipping an hour for daylight savings, adjust the hour again (else you get: 0h 5h 9h ... instead of 0h 4h 8h ...) 17714 // TODO: is this still needed now we use the function of moment.js? 17715 this.current.subtract(this.current.hours() % this.step, 'hour'); 17716 break; 17717 case 'weekday': // intentional fall through 17718 case 'day': 17719 this.current.add(this.step, 'day');break; 17720 case 'month': 17721 this.current.add(this.step, 'month');break; 17722 case 'year': 17723 this.current.add(this.step, 'year');break; 17724 default: 17725 break; 17726 } 17727 } else { 17728 switch (this.scale) { 17729 case 'millisecond': 17730 this.current.add(this.step, 'millisecond');break; 17731 case 'second': 17732 this.current.add(this.step, 'second');break; 17733 case 'minute': 17734 this.current.add(this.step, 'minute');break; 17735 case 'hour': 17736 this.current.add(this.step, 'hour');break; 17737 case 'weekday': // intentional fall through 17738 case 'day': 17739 this.current.add(this.step, 'day');break; 17740 case 'month': 17741 this.current.add(this.step, 'month');break; 17742 case 'year': 17743 this.current.add(this.step, 'year');break; 17744 default: 17745 break; 17746 } 17747 } 17748 17749 if (this.step != 1) { 17750 // round down to the correct major value 17751 switch (this.scale) { 17752 case 'millisecond': 17753 if (this.current.milliseconds() < this.step) this.current.milliseconds(0);break; 17754 case 'second': 17755 if (this.current.seconds() < this.step) this.current.seconds(0);break; 17756 case 'minute': 17757 if (this.current.minutes() < this.step) this.current.minutes(0);break; 17758 case 'hour': 17759 if (this.current.hours() < this.step) this.current.hours(0);break; 17760 case 'weekday': // intentional fall through 17761 case 'day': 17762 if (this.current.date() < this.step + 1) this.current.date(1);break; 17763 case 'month': 17764 if (this.current.month() < this.step) this.current.month(0);break; 17765 case 'year': 17766 break; // nothing to do for year 17767 default: 17768 break; 17769 } 17770 } 17771 17772 // safety mechanism: if current time is still unchanged, move to the end 17773 if (this.current.valueOf() == prev) { 17774 this.current = this._end.clone(); 17775 } 17776 17777 DateUtil.stepOverHiddenDates(this.moment, this, prev); 17778 }; 17779 17780 /** 17781 * Get the current datetime 17782 * @return {Moment} current The current date 17783 */ 17784 TimeStep.prototype.getCurrent = function () { 17785 return this.current; 17786 }; 17787 17788 /** 17789 * Set a custom scale. Autoscaling will be disabled. 17790 * For example setScale('minute', 5) will result 17791 * in minor steps of 5 minutes, and major steps of an hour. 17792 *
17793 * @param {{scale: string, step: number}} params 17794 * An object containing two properties: 17795 * - A string 'scale'. Choose from 'millisecond', 'second', 17796 * 'minute', 'hour', 'weekday', 'day', 'month', 'year'. 17797 * - A number 'step'. A step size, by default 1. 17798 * Choose for example 1, 2, 5, or 10. 17799 */ 17800 TimeStep.prototype.setScale = function (params) { 17801 if (params && typeof params.scale == 'string') { 17802 this.scale = params.scale; 17803 this.step = params.step > 0 ? params.step : 1; 17804 this.autoScale = false; 17805 } 17806 }; 17807 17808 /** 17809 * Enable or disable autoscaling 17810 * @param {boolean} enable If true, autoascaling is set true 17811 */ 17812 TimeStep.prototype.setAutoScale = function (enable) { 17813 this.autoScale = enable; 17814 }; 17815 17816 /** 17817 * Automatically determine the scale that bests fits the provided minimum step 17818 * @param {Number} [minimumStep] The minimum step size in milliseconds 17819 */ 17820 TimeStep.prototype.setMinimumStep = function (minimumStep) { 17821 if (minimumStep == undefined) { 17822 return; 17823 } 17824 17825 //var b = asc + ds; 17826 17827 var stepYear = 1000 * 60 * 60 * 24 * 30 * 12; 17828 var stepMonth = 1000 * 60 * 60 * 24 * 30; 17829 var stepDay = 1000 * 60 * 60 * 24; 17830 var stepHour = 1000 * 60 * 60; 17831 var stepMinute = 1000 * 60; 17832 var stepSecond = 1000; 17833 var stepMillisecond = 1; 17834 17835 // find the smallest step that is larger than the provided minimumStep 17836 if (stepYear * 1000 > minimumStep) { 17837 this.scale = 'year';this.step = 1000; 17838 } 17839 if (stepYear * 500 > minimumStep) { 17840 this.scale = 'year';this.step = 500; 17841 } 17842 if (stepYear * 100 > minimumStep) { 17843 this.scale = 'year';this.step = 100; 17844 } 17845 if (stepYear * 50 > minimumStep) { 17846 this.scale = 'year';this.step = 50; 17847 } 17848 if (stepYear * 10 > minimumStep) { 17849 this.scale = 'year';this.step = 10; 17850 } 17851 if (stepYear * 5 > minimumStep) { 17852 this.scale = 'year';this.step = 5; 17853 } 17854 if (stepYear > minimumStep) { 17855 this.scale = 'year';this.step = 1; 17856 } 17857 if (stepMonth * 3 > minimumStep) { 17858 this.scale = 'month';this.step = 3; 17859 } 17860 if (stepMonth > minimumStep) { 17861 this.scale = 'month';this.step = 1; 17862 } 17863 if (stepDay * 5 > minimumStep) { 17864 this.scale = 'day';this.step = 5; 17865 } 17866 if (stepDay * 2 > minimumStep) { 17867 this.scale = 'day';this.step = 2; 17868 } 17869 if (stepDay > minimumStep) { 17870 this.scale = 'day';this.step = 1; 17871 } 17872 if (stepDay / 2 > minimumStep) { 17873 this.scale = 'weekday';this.step = 1; 17874 } 17875 if (stepHour * 4 > minimumStep) { 17876 this.scale = 'hour';this.step = 4; 17877 } 17878 if (stepHour > minimumStep) { 17879 this.scale = 'hour';this.step = 1; 17880 } 17881 if (stepMinute * 15 > minimumStep) { 17882 this.scale = 'minute';this.step = 15; 17883 } 17884 if (stepMinute * 10 > minimumStep) { 17885 this.scale = 'minute';this.step = 10; 17886 } 17887 if (stepMinute * 5 > minimumStep) { 17888 this.scale = 'minute';this.step = 5; 17889 } 17890 if (stepMinute > minimumStep) { 17891 this.scale = 'minute';this.step = 1; 17892 } 17893 if (stepSecond * 15 > minimumStep) { 17894 this.scale = 'second';this.step = 15; 17895 } 17896 if (stepSecond * 10 > minimumStep) { 17897 this.scale = 'second';this.step = 10; 17898 } 17899 if (stepSecond * 5 > minimumStep) { 17900 this.scale = 'second';this.step = 5; 17901 } 17902 if (stepSecond > minimumStep) { 17903 this.scale = 'second';this.step = 1; 17904 } 17905 if (stepMillisecond * 200 > minimumStep) { 17906 this.scale = 'millisecond';this.step = 200; 17907 } 17908 if (stepMillisecond * 100 > minimumStep) { 17909 this.scale = 'millisecond';this.step = 100; 17910 } 17911 if (stepMillisecond * 50 > minimumStep) { 17912 this.scale = 'millisecond';this.step = 50; 17913 } 17914 if (stepMillisecond * 10 > minimumStep) { 17915 this.scale = 'millisecond';this.step = 10; 17916 } 17917 if (stepMillisecond * 5 > minimumStep) { 17918 this.scale = 'millisecond';this.step = 5; 17919 } 17920 if (stepMillisecond > minimumStep) { 17921 this.scale = 'millisecond';this.step = 1; 17922 } 17923 }; 17924 17925 /** 17926 * Snap a date to a rounded value. 17927 * The snap intervals are dependent on the current scale and step. 17928 * Static function 17929 * @param {Date} date the date to be snapped. 17930 * @param {string}
17930 scale Current scale, can be 'millisecond', 'second', 17931 * 'minute', 'hour', 'weekday, 'day', 'month', 'year'. 17932 * @param {number} step Current step (1, 2, 4, 5, ... 17933 * @return {Date} snappedDate 17934 */ 17935 TimeStep.snap = function (date, scale, step) { 17936 var clone = moment(date); 17937 17938 if (scale == 'year') { 17939 var year = clone.year() + Math.round(clone.month() / 12); 17940 clone.year(Math.round(year / step) * step); 17941 clone.month(0); 17942 clone.date(0); 17943 clone.hours(0); 17944 clone.minutes(0); 17945 clone.seconds(0); 17946 clone.milliseconds(0); 17947 } else if (scale == 'month') { 17948 if (clone.date() > 15) { 17949 clone.date(1); 17950 clone.add(1, 'month'); 17951 // important: first set Date to 1, after that change the month. 17952 } else { 17953 clone.date(1); 17954 } 17955 17956 clone.hours(0); 17957 clone.minutes(0); 17958 clone.seconds(0); 17959 clone.milliseconds(0); 17960 } else if (scale == 'day') { 17961 //noinspection FallthroughInSwitchStatementJS 17962 switch (step) { 17963 case 5: 17964 case 2: 17965 clone.hours(Math.round(clone.hours() / 24) * 24);break; 17966 default: 17967 clone.hours(Math.round(clone.hours() / 12) * 12);break; 17968 } 17969 clone.minutes(0); 17970 clone.seconds(0); 17971 clone.milliseconds(0); 17972 } else if (scale == 'weekday') { 17973 //noinspection FallthroughInSwitchStatementJS 17974 switch (step) { 17975 case 5: 17976 case 2: 17977 clone.hours(Math.round(clone.hours() / 12) * 12);break; 17978 default: 17979 clone.hours(Math.round(clone.hours() / 6) * 6);break; 17980 } 17981 clone.minutes(0); 17982 clone.seconds(0); 17983 clone.milliseconds(0); 17984 } else if (scale == 'hour') { 17985 switch (step) { 17986 case 4: 17987 clone.minutes(Math.round(clone.minutes() / 60) * 60);break; 17988 default: 17989 clone.minutes(Math.round(clone.minutes() / 30) * 30);break; 17990 } 17991 clone.seconds(0); 17992 clone.milliseconds(0); 17993 } else if (scale == 'minute') { 17994 //noinspection FallthroughInSwitchStatementJS 17995 switch (step) { 17996 case 15: 17997 case 10: 17998 clone.minutes(Math.round(clone.minutes() / 5) * 5); 17999 clone.seconds(0); 18000 break; 18001 case 5: 18002 clone.seconds(Math.round(clone.seconds() / 60) * 60);break; 18003 default: 18004 clone.seconds(Math.round(clone.seconds() / 30) * 30);break; 18005 } 18006 clone.milliseconds(0); 18007 } else if (scale == 'second') { 18008 //noinspection FallthroughInSwitchStatementJS 18009 switch (step) { 18010 case 15: 18011 case 10: 18012 clone.seconds(Math.round(clone.seconds() / 5) * 5); 18013 clone.milliseconds(0); 18014 break; 18015 case 5: 18016 clone.milliseconds(Math.round(clone.milliseconds() / 1000) * 1000);break; 18017 default: 18018 clone.milliseconds(Math.round(clone.milliseconds() / 500) * 500);break; 18019 } 18020 } else if (scale == 'millisecond') { 18021 var _step = step > 5 ? step / 2 : 1; 18022 clone.milliseconds(Math.round(clone.milliseconds() / _step) * _step); 18023 } 18024 18025 return clone; 18026 }; 18027 18028 /** 18029 * Check if the current value is a major value (for example when the step 18030 * is DAY, a major value is each first day of the MONTH) 18031 * @return {boolean} true if current date is major, else false. 18032 */ 18033 TimeStep.prototype.isMajor = function () { 18034 if (this.switchedYear == true) { 18035 this.switchedYear = false; 18036 switch (this.scale) { 18037 case 'year': 18038 case 'month': 18039 case 'weekday': 18040 case 'day': 18041 case 'hour': 18042 case 'minute': 18043 case 'second': 18044 case 'millisecond': 18045 return true; 18046 default: 18047 return false; 18048 } 18049 } else if (this.switchedMonth == true) { 18050 this.switchedMonth = false; 18051 switch (this.scale) { 18052 case 'weekday': 18053 case 'day': 18054 case 'hour': 18055 case 'minute': 18056 case 'second': 18057 case 'millisecond': 18058 return true; 18059 default: 18060 return false; 18061 } 18062 } else if (this.switchedDay == true) { 18063 this.switchedDay = false; 18064 switch (this.scale) { 18065 case 'millisecond': 18066 case 'second': 18067 case 'minute': 18068 case 'hour': 18069 return true; 18070 default: 18071 return false;
18072 } 18073 } 18074 18075 var date = this.moment(this.current); 18076 switch (this.scale) { 18077 case 'millisecond': 18078 return date.milliseconds() == 0; 18079 case 'second': 18080 return date.seconds() == 0; 18081 case 'minute': 18082 return date.hours() == 0 && date.minutes() == 0; 18083 case 'hour': 18084 return date.hours() == 0; 18085 case 'weekday': // intentional fall through 18086 case 'day': 18087 return date.date() == 1; 18088 case 'month': 18089 return date.month() == 0; 18090 case 'year': 18091 return false; 18092 default: 18093 return false; 18094 } 18095 }; 18096 18097 /** 18098 * Returns formatted text for the minor axislabel, depending on the current 18099 * date and the scale. For example when scale is MINUTE, the current time is 18100 * formatted as "hh:mm". 18101 * @param {Date} [date] custom date. if not provided, current date is taken 18102 */ 18103 TimeStep.prototype.getLabelMinor = function (date) { 18104 if (date == undefined) { 18105 date = this.current; 18106 } 18107 18108 var format = this.format.minorLabels[this.scale]; 18109 return format && format.length > 0 ? this.moment(date).format(format) : ''; 18110 }; 18111 18112 /** 18113 * Returns formatted text for the major axis label, depending on the current 18114 * date and the scale. For example when scale is MINUTE, the major scale is 18115 * hours, and the hour will be formatted as "hh". 18116 * @param {Date} [date] custom date. if not provided, current date is taken 18117 */ 18118 TimeStep.prototype.getLabelMajor = function (date) { 18119 if (date == undefined) { 18120 date = this.current; 18121 } 18122 18123 var format = this.format.majorLabels[this.scale]; 18124 return format && format.length > 0 ? this.moment(date).format(format) : ''; 18125 }; 18126 18127 TimeStep.prototype.getClassName = function () { 18128 var _moment = this.moment; 18129 var m = this.moment(this.current); 18130 var current = m.locale ? m.locale('en') : m.lang('en'); // old versions of moment have .lang() function 18131 var step = this.step; 18132 18133 function even(value) { 18134 return value / step % 2 == 0 ? ' vis-even' : ' vis-odd'; 18135 } 18136 18137 function today(date) { 18138 if (date.isSame(new Date(), 'day')) { 18139 return ' vis-today'; 18140 } 18141 if (date.isSame(_moment().add(1, 'day'), 'day')) { 18142 return ' vis-tomorrow'; 18143 } 18144 if (date.isSame(_moment().add(-1, 'day'), 'day')) { 18145 return ' vis-yesterday'; 18146 } 18147 return ''; 18148 } 18149 18150 function currentWeek(date) { 18151 return date.isSame(new Date(), 'week') ? ' vis-current-week' : ''; 18152 } 18153 18154 function currentMonth(date) { 18155 return date.isSame(new Date(), 'month') ? ' vis-current-month' : ''; 18156 } 18157 18158 function currentYear(date) { 18159 return date.isSame(new Date(), 'year') ? ' vis-current-year' : ''; 18160 } 18161 18162 switch (this.scale) { 18163 case 'millisecond': 18164 return even(current.milliseconds()).trim(); 18165 18166 case 'second': 18167 return even(current.seconds()).trim(); 18168 18169 case 'minute': 18170 return even(current.minutes()).trim(); 18171 18172 case 'hour': 18173 var hours = current.hours(); 18174 if (this.step == 4) { 18175 hours = hours + '-h' + (hours + 4); 18176 } 18177 return 'vis-h' + hours + today(current) + even(current.hours()); 18178 18179 case 'weekday': 18180 return 'vis-' + current.format('dddd').toLowerCase() + today(current) + currentWeek(current) + even(current.date()); 18181 18182 case 'day': 18183 var day = current.date(); 18184 var month = current.format('MMMM').toLowerCase(); 18185 return 'vis-day' + day + ' vis-' + month + currentMonth(current) + even(day - 1); 18186 18187 case 'month': 18188 return 'vis-' + current.format('MMMM').toLowerCase() + currentMonth(current) + even(current.month()); 18189 18190 case 'year': 18191 var year = current.year(); 18192 return 'vis-year' + year + currentYear(current) + even(year); 18193 18194 default: 18195 return ''; 18196 } 18197 }; 18198 18199 module.exports = TimeStep; 18200 18201/***/ }, 18202/* 31 */ 18203/***/ function(module, exports, __webpack_require__) { 18204 18205 'use strict'; 18206 18207 var util = __webpack_require__(1); 18208 var stack = __webpack_require__(32); 18209 var RangeItem = __webpack_require__(33); 18210 18211 /** 18212 * @constructor Group 18213 * @param {Number | String} groupId 18214 * @param {Object} data 18215 * @param {ItemSet} itemSet 18216 */ 18217 function Group(groupId, data, itemSet) { 18218 this.groupId = groupId; 18219 this.subgroups = {}; 18220 this.subgroupIndex = 0; 18221 this.subgroupOrderer = data && data.subgroupOrder; 18222 this.itemSet = itemSet; 18223 18224 this.dom = {}; 18225 this.props = { 18226 label: { 18227 width: 0, 18228 height: 0 18229 } 18230 }; 18231 this.className = null; 18232 18233 this.items = {}; // items filtered by groupId of this group 18234 this.visibleItems = []; // items currently visible in window 18235 this.orderedItems = { 18236 byStart: [], 18237 byEnd: [] 18238 }; 18239 this.checkRangedItems = false; // needed to refresh the ranged items if the window is programatically changed with NO overlap. 18240 var me = this; 18241 this.itemSet.body.emitter.on('checkRangedItems', function () { 18242 me.checkRangedItems = true; 18243 }); 18244 18245 this._create(); 18246 18247 this.setData(data); 18248 } 18249 18250 /** 18251 * Create DOM elements for the group 18252 * @private 18253 */ 18254 Group.prototype._create = function () { 18255 var label = document.createElement('div'); 18256 if (this.itemSet.options.groupEditable.order) { 18257 label.className = 'vis-label draggable'; 18258 } else { 18259 label.className = 'vis-label'; 18260 } 18261 this.dom.label = label; 18262 18263 var inner = document.createElement('div'); 18264 inner.className = 'vis-inner'; 18265 label.appendChild(inner); 18266 this.dom.inner = inner; 18267 18268 var foreground = document.createElement('div'); 18269 foreground.className = 'vis-group'; 18270 foreground['timeline-group'] = this; 18271 this.dom.foreground = foreground; 18272 18273 this.dom.background = document.createElement('div'); 18274 this.dom.background.className = 'vis-group'; 18275 18276 this.dom.axis = document.createElement('div'); 18277 this.dom.axis.className = 'vis-group'; 18278 18279 // create a hidden marker to detect when the Timelines container is attached 18280 // to the DOM, or the style of a parent of the Timeline is changed from 18281 // display:none is changed to visible. 18282 this.dom.marker = document.createElement('div'); 18283 this.dom.marker.style.visibility = 'hidden'; 18284 this.dom.marker.innerHTML = '?'; 18285 this.dom.background.appendChild(this.dom.marker); 18286 }; 18287 18288 /** 18289 * Set the group data for this group 18290 * @param {Object} data Group data, can contain properties content and className 18291 */ 18292 Group.prototype.setData = function (data) { 18293 // update contents 18294 var content; 18295 if (this.itemSet.options && this.itemSet.options.groupTemplate) { 18296 content = this.itemSet.options.groupTemplate(data); 18297 } else { 18298 content = data && data.content; 18299 } 18300 18301 if (content instanceof Element) { 18302 this.dom.inner.appendChild(content); 18303 while (this.dom.inner.firstChild) { 18304 this.dom.inner.removeChild(this.dom.inner.firstChild); 18305 } 18306 this.dom.inner.appendChild(content); 18307 } else if (content !== undefined && content !== null) { 18308 this.dom.inner.innerHTML = content; 18309 } else { 18310 this.dom.inner.innerHTML = this.groupId || ''; // groupId can be null 18311 } 18312 18313 // update title 18314 this.dom.label.title = data && data.title || ''; 18315 18316 if (!this.dom.inner.firstChild) { 18317 util.addClassName(this.dom.inner, 'vis-hidden'); 18318 } else { 18319 util.removeClassName(this.dom.inner, 'vis-hidden'); 18320 } 18321 18322 // update className 18323 var className = data && data.className || null; 18324 if (className != this.className) { 18325 if (this.className) { 18326 util.removeClassName(this.dom.label, this.className); 18327 util.removeClassName(this.dom.foreground, this.className); 18328 util.removeClassName(this.dom.background, this.className); 18329 util.removeClassName(this.dom.axis, this.className); 18330 } 18331 util.addClassName(this.dom.label, className); 18332 util.addClassName(this.dom.foreground, className); 18333 util.addClassName(this.dom.background, className); 18334 util.addClassName(this.dom.axis, className); 18335 this.className = className; 18336 } 18337 18338 // update style 18339 if (this.style) { 18340 util.removeCssText(this.dom.label, this.style); 18341 this.style = null; 18342 } 18343 if (data && data.style) { 18344 util.addCssText(this.dom.label, data.style); 18345 this.style = data.style; 18346 } 18347 }; 18348 18349 /** 18350 * Get the width of the group label 18351 * @return {number} width 18352 */ 18353 Group.prototype.getLabelWidth = function () { 18354 return this.props.label.width; 18355 }; 18356 18357 /** 18358 * Repaint this group 18359 * @param {{start: number, end: number}} range 18360 * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin 18361 * @param {boolean} [restack=false] Force restacking of all items 18362 * @return {boolean} Returns true if the group is resized 18363 */ 18364 Group.prototype.redraw = function (range, margin, restack) { 18365 var resized = false;
18366 18367 // force recalculation of the height of the items when the marker height changed 18368 // (due to the Timeline being attached to the DOM or changed from display:none to visible) 18369 var markerHeight = this.dom.marker.clientHeight; 18370 if (markerHeight != this.lastMarkerHeight) { 18371 this.lastMarkerHeight = markerHeight; 18372 18373 util.forEach(this.items, function (item) { 18374 item.dirty = true; 18375 if (item.displayed) item.redraw(); 18376 }); 18377 18378 restack = true; 18379 } 18380 18381 // reposition visible items vertically 18382 if (typeof this.itemSet.options.order === 'function') { 18383 // a custom order function 18384 18385 if (restack) { 18386 // brute force restack of all items 18387 18388 // show all items 18389 var me = this; 18390 var limitSize = false; 18391 util.forEach(this.items, function (item) { 18392 if (!item.displayed) { 18393 item.redraw(); 18394 me.visibleItems.push(item); 18395 } 18396 item.repositionX(limitSize); 18397 }); 18398 18399 // order all items and force a restacking 18400 var customOrderedItems = this.orderedItems.byStart.slice().sort(function (a, b) { 18401 return me.itemSet.options.order(a.data, b.data); 18402 }); 18403 stack.stack(customOrderedItems, margin, true /* restack=true */); 18404 } 18405 18406 this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range); 18407 } else { 18408 // no custom order function, lazy stacking 18409 this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range); 18410 18411 if (this.itemSet.options.stack) { 18412 // TODO: ugly way to access options... 18413 stack.stack(this.visibleItems, margin, restack); 18414 } else { 18415 // no stacking 18416 stack.nostack(this.visibleItems, margin, this.subgroups); 18417 } 18418 } 18419 18420 // recalculate the height of the group 18421 var height = this._calculateHeight(margin); 18422 18423 // calculate actual size and position 18424 var foreground = this.dom.foreground; 18425 this.top = foreground.offsetTop; 18426 this.left = foreground.offsetLeft; 18427 this.width = foreground.offsetWidth; 18428 resized = util.updateProperty(this, 'height', height) || resized; 18429 18430 // recalculate size of label 18431 resized = util.updateProperty(this.props.label, 'width', this.dom.inner.clientWidth) || resized; 18432 resized = util.updateProperty(this.props.label, 'height', this.dom.inner.clientHeight) || resized; 18433 18434 // apply new height 18435 this.dom.background.style.height = height + 'px'; 18436 this.dom.foreground.style.height = height + 'px'; 18437 this.dom.label.style.height = height + 'px'; 18438 18439 // update vertical position of items after they are re-stacked and the height of the group is calculated 18440 for (var i = 0, ii = this.visibleItems.length; i < ii; i++) { 18441 var item = this.visibleItems[i]; 18442 item.repositionY(margin); 18443 } 18444 18445 return resized; 18446 }; 18447 18448 /** 18449 * recalculate the height of the group 18450 * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin 18451 * @returns {number} Returns the height 18452 * @private 18453 */ 18454 Group.prototype._calculateHeight = function (margin) { 18455 // recalculate the height of the group 18456 var height; 18457 var visibleItems = this.visibleItems; 18458 //var visibleSubgroups = []; 18459 //this.visibleSubgroups = 0; 18460 this.resetSubgroups(); 18461 var me = this; 18462 if (visibleItems.length > 0) { 18463 var min = visibleItems[0].top; 18464 var max = visibleItems[0].top + visibleItems[0].height; 18465 util.forEach(visibleItems, function (item) { 18466 min = Math.min(min, item.top); 18467 max = Math.max(max, item.top + item.height); 18468 if (item.data.subgroup !== undefined) { 18469 me.subgroups[item.data.subgroup].height = Math.max(me.subgroups[item.data.subgroup].height, item.height); 18470 me.subgroups[item.data.subgroup].visible = true; 18471 } 18472 }); 18473 if (min > margin.axis) { 18474 // there is an empty gap between the lowest item and the axis 18475 var offset = min - margin.axis; 18476 max -= offset; 18477 util.forEach(visibleItems, function (item) { 18478 item.top -= offset; 18479 }); 18480 } 18481 height = max + margin.item.vertical / 2; 18482 } else { 18483 height = 0; 18484 } 18485 height = Math.max(height, this.props.label.height); 18486 18487 return height; 18488 }; 18489 18490 /** 18491 * Show this group: attach to the DOM 18492 */ 18493 Group.prototype.show = function () { 18494 if (!this.dom.label.parentNode) { 18495 this.itemSet.dom.labelSet.appendChild(this.dom.label); 18496 } 18497 18498 if (!this.dom.foreground.parentNode) { 18499 this.itemSet.dom.foreground.appendChild(this.dom.foreground); 18500 } 18501 18502 if (!this.dom.background.parentNode) { 18503 this.itemSet.dom.background.appendChild(this.dom.background); 18504 } 18505 18506 if (!this.dom.axis.parentNode) { 18507 this.itemSet.dom.axis.appendChild(this.dom.axis); 18508 } 18509 }; 18510 18511 /** 18512 * Hide this group: remove from the DOM 18513 */ 18514 Group.prototype.hide = function () { 18515 var label = this.dom.label; 18516 if (label.parentNode) { 18517 label.parentNode.removeChild(label); 18518 } 18519 18520 var foreground = this.dom.foreground; 18521 if (foreground.parentNode) { 18522 foreground.parentNode.removeChild(foreground); 18523 } 18524 18525 var background = this.dom.background; 18526 if (background.parentNode) { 18527 background.parentNode.removeChild(background); 18528 } 18529 18530 var axis = this.dom.axis; 18531 if (axis.parentNode) { 18532 axis.parentNode.removeChild(axis); 18533 } 18534 }; 18535 18536 /** 18537 * Add an item to the group 18538 * @param {Item} item 18539 */ 18540 Group.prototype.add = function (item) { 18541 this.items[item.id] = item; 18542 item.setParent(this); 18543 18544 // add to 18545 if (item.data.subgroup !== undefined) { 18546 if (this.subgroups[item.data.subgroup] === undefined) { 18547 this.subgroups[item.data.subgroup] = { height: 0, visible: false, index: this.subgroupIndex, items: [] }; 18548 this.subgroupIndex++; 18549 } 18550 this.subgroups[item.data.subgroup].items.push(item); 18551 } 18552 this.orderSubgroups(); 18553 18554 if (this.visibleItems.indexOf(item) == -1) { 18555 var range = this.itemSet.body.range; // TODO: not nice accessing the range like this 18556 this._checkIfVisible(item, this.visibleItems, range); 18557 } 18558 }; 18559 18560 Group.prototype.orderSubgroups = function () { 18561 if (this.subgroupOrderer !== undefined) { 18562 var sortArray = []; 18563 if (typeof this.subgroupOrderer == 'string') { 18564 for (var subgroup in this.subgroups) { 18565 sortArray.push({ subgroup: subgroup, sortField: this.subgroups[subgroup].items[0].data[this.subgroupOrderer] }); 18566 } 18567 sortArray.sort(function (a, b) { 18568 return a.sortField - b.sortField; 18569 }); 18570 } else if (typeof this.subgroupOrderer == 'function') { 18571 for (var subgroup in this.subgroups) { 18572 sortArray.push(this.subgroups[subgroup].items[0].data); 18573 } 18574 sortArray.sort(this.subgroupOrderer); 18575 } 18576 18577 if (sortArray.length > 0) { 18578 for (var i = 0; i < sortArray.length; i++) { 18579 this.subgroups[sortArray[i].subgroup].index = i; 18580 } 18581 } 18582 } 18583 }; 18584 18585 Group.prototype.resetSubgroups = function () { 18586 for (var subgroup in this.subgroups) { 18587 if (this.subgroups.hasOwnProperty(subgroup)) { 18588 this.subgroups[subgroup].visible = false;
18589 } 18590 } 18591 }; 18592 18593 /** 18594 * Remove an item from the group 18595 * @param {Item} item 18596 */ 18597 Group.prototype.remove = function (item) { 18598 delete this.items[item.id]; 18599 item.setParent(null); 18600 18601 // remove from visible items 18602 var index = this.visibleItems.indexOf(item); 18603 if (index != -1) this.visibleItems.splice(index, 1); 18604 18605 if (item.data.subgroup !== undefined) { 18606 var subgroup = this.subgroups[item.data.subgroup]; 18607 if (subgroup) { 18608 var itemIndex = subgroup.items.indexOf(item); 18609 subgroup.items.splice(itemIndex, 1); 18610 if (!subgroup.items.length) { 18611 delete this.subgroups[item.data.subgroup]; 18612 this.subgroupIndex--; 18613 } 18614 this.orderSubgroups(); 18615 } 18616 } 18617 }; 18618 18619 /** 18620 * Remove an item from the corresponding DataSet 18621 * @param {Item} item 18622 */ 18623 Group.prototype.removeFromDataSet = function (item) { 18624 this.itemSet.removeItem(item.id); 18625 }; 18626 18627 /** 18628 * Reorder the items 18629 */ 18630 Group.prototype.order = function () { 18631 var array = util.toArray(this.items); 18632 var startArray = []; 18633 var endArray = []; 18634 18635 for (var i = 0; i < array.length; i++) { 18636 if (array[i].data.end !== undefined) { 18637 endArray.push(array[i]); 18638 } 18639 startArray.push(array[i]); 18640 } 18641 this.orderedItems = { 18642 byStart: startArray, 18643 byEnd: endArray 18644 }; 18645 18646 stack.orderByStart(this.orderedItems.byStart); 18647 stack.orderByEnd(this.orderedItems.byEnd); 18648 }; 18649 18650 /** 18651 * Update the visible items 18652 * @param {{byStart: Item[], byEnd: Item[]}} orderedItems All items ordered by start date and by end date 18653 * @param {Item[]} visibleItems The previously visible items. 18654 * @param {{start: number, end: number}} range Visible range 18655 * @return {Item[]} visibleItems The new visible items. 18656 * @private 18657 */ 18658 Group.prototype._updateVisibleItems = function (orderedItems, oldVisibleItems, range) { 18659 var visibleItems = []; 18660 var visibleItemsLookup = {}; // we keep this to quickly look up if an item already exists in the list without using indexOf on visibleItems 18661 var interval = (range.end - range.start) / 4; 18662 var lowerBound = range.start - interval; 18663 var upperBound = range.end + interval; 18664 var item, i; 18665 18666 // this function is used to do the binary search. 18667 var searchFunction = function searchFunction(value) { 18668 if (value < lowerBound) { 18669 return -1; 18670 } else if (value <= upperBound) { 18671 return 0; 18672 } else { 18673 return 1; 18674 } 18675 }; 18676 18677 // first check if the items that were in view previously are still in view. 18678 // IMPORTANT: this handles the case for the items with startdate before the window and enddate after the window! 18679 // also cleans up invisible items. 18680 if (oldVisibleItems.length > 0) { 18681 for (i = 0; i < oldVisibleItems.length; i++) { 18682 this._checkIfVisibleWithReference(oldVisibleItems[i], visibleItems, visibleItemsLookup, range); 18683 } 18684 } 18685 18686 // we do a binary search for the items that have only start values. 18687 var initialPosByStart = util.binarySearchCustom(orderedItems.byStart, searchFunction, 'data', 'start'); 18688 18689 // trace the visible items from the inital start pos both ways until an invisible item is found, we only look at the start values. 18690 this._traceVisible(initialPosByStart, orderedItems.byStart, visibleItems, visibleItemsLookup, function (item) { 18691 return item.data.start < lowerBound || item.data.start > upperBound; 18692 }); 18693 18694 // if the window has changed programmatically without overlapping the old window, the ranged items with start < lowerBound and end > upperbound are not shown. 18695 // We therefore have to brute force check all items in the byEnd list 18696 if (this.checkRangedItems == true) { 18697 this.checkRangedItems = false; 18698 for (i = 0; i < orderedItems.byEnd.length; i++) { 18699 this._checkIfVisibleWithReference(orderedItems.byEnd[i], visibleItems, visibleItemsLookup, range); 18700 } 18701 } else { 18702 // we do a binary search for the items that have defined end times. 18703 var initialPosByEnd = util.binarySearchCustom(orderedItems.byEnd, searchFunction, 'data', 'end'); 18704 18705 // trace the visible items from the inital start pos both ways until an invisible item is found, we only look at the end values. 18706 this._traceVisible(initialPosByEnd, orderedItems.byEnd, visibleItems, visibleItemsLookup, function (item) { 18707 return item.data.end < lowerBound || item.data.end > upperBound; 18708 }); 18709 } 18710 18711 // finally, we reposition all the visible items. 18712 for (i = 0; i < visibleItems.length; i++) { 18713 item = visibleItems[i]; 18714 if (!item.displayed) item.show(); 18715 // reposition item horizontally 18716 item.repositionX(); 18717 } 18718 18719 // debug 18720 //console.log("new line") 18721 //if (this.groupId == null) { 18722 // for (i = 0; i < orderedItems.byStart.length; i++) { 18723 // item = orderedItems.byStart[i].data; 18724 // console.log('start',i,initialPosByStart, item.start.valueOf(), item.content, item.start >= lowerBound && item.start <= upperBound,i == initialPosByStart ? "<------------------- HEREEEE" : "") 18725 // } 18726 // for (i = 0; i < orderedItems.byEnd.length; i++) { 18727 // item = orderedItems.byEnd[i].data; 18728 // console.log('rangeEnd',i,initialPosByEnd, item.end.valueOf(), item.content, item.end >= range.start && item.end <= range.end,i == initialPosByEnd ? "<------------------- HEREEEE" : "") 18729 // } 18730 //} 18731 18732 return visibleItems; 18733 }; 18734 18735 Group.prototype._traceVisible = function (initialPos, items, visibleItems, visibleItemsLookup, breakCondition) { 18736 var item; 18737 var i; 18738 18739 if (initialPos != -1) { 18740 for (i = initialPos; i >= 0; i--) { 18741 item = items[i]; 18742 if (breakCondition(item)) { 18743 break; 18744 } else { 18745 if (visibleItemsLookup[item.id] === undefined) { 18746 visibleItemsLookup[item.id] = true; 18747 visibleItems.push(item); 18748 } 18749 } 18750 } 18751 18752 for (i = initialPos + 1; i < items.length; i++) { 18753 item = items[i]; 18754 if (breakCondition(item)) { 18755 break; 18756 } else { 18757 if (visibleItemsLookup[item.id] === undefined) { 18758 visibleItemsLookup[item.id] = true; 18759 visibleItems.push(item); 18760 } 18761 } 18762 } 18763 } 18764 }; 18765 18766 /** 18767 * this function is very similar to the _checkIfInvisible() but it does not 18768 * return booleans, hides the item if it should not be seen and always adds to 18769 * the visibleItems. 18770 * this one is for brute forcing and hiding. 18771 * 18772 * @param {Item} item 18773 * @param {Array} visibleItems 18774 * @param {{start:number, end:number}} range 18775 * @private 18776 */ 18777 Group.prototype._checkIfVisible = function (item, visibleItems, range) { 18778 if (item.isVisible(range)) { 18779 if (!item.displayed) item.show(); 18780 // reposition item horizontally 18781 item.repositionX(); 18782 visibleItems.push(item); 18783 } else { 18784 if (item.displayed) item.hide(); 18785 } 18786 }; 18787 18788 /** 18789 * this function is very similar to the _checkIfInvisible() but it does not 18790 * return booleans, hides the item if it should not be seen and always adds to 18791 * the visibleItems. 18792 * this one is for brute forcing and hiding. 18793 * 18794 * @param {Item} item 18795 * @param {Array} visibleItems 18796 * @param {{start:number, end:number}} range 18797 * @private 18798 */ 18799 Group.prototype._checkIfVisibleWithReference = function (item, visibleItems, visibleItemsLookup, range) { 18800 if (item.isVisible(range)) { 18801 if (visibleItemsLookup[item.id] === undefined) { 18802 visibleItemsLookup[item.id] = true; 18803 visibleItems.push(item); 18804 } 18805 } else { 18806 if (item.displayed) item.hide(); 18807 } 18808 }; 18809 18810 module.exports = Group; 18811 18812/***/ }, 18813/* 32 */
18814/***/ function(module, exports) { 18815 18816 // Utility functions for ordering and stacking of items 18817 'use strict'; 18818 18819 var EPSILON = 0.001; // used when checking collisions, to prevent round-off errors 18820 18821 /** 18822 * Order items by their start data 18823 * @param {Item[]} items 18824 */ 18825 exports.orderByStart = function (items) { 18826 items.sort(function (a, b) { 18827 return a.data.start - b.data.start; 18828 }); 18829 }; 18830 18831 /** 18832 * Order items by their end date. If they have no end date, their start date 18833 * is used. 18834 * @param {Item[]} items 18835 */ 18836 exports.orderByEnd = function (items) { 18837 items.sort(function (a, b) { 18838 var aTime = 'end' in a.data ? a.data.end : a.data.start, 18839 bTime = 'end' in b.data ? b.data.end : b.data.start; 18840 18841 return aTime - bTime; 18842 }); 18843 }; 18844 18845 /** 18846 * Adjust vertical positions of the items such that they don't overlap each 18847 * other. 18848 * @param {Item[]} items 18849 * All visible items 18850 * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin 18851 * Margins between items and between items and the axis. 18852 * @param {boolean} [force=false] 18853 * If true, all items will be repositioned. If false (default), only 18854 * items having a top===null will be re-stacked 18855 */ 18856 exports.stack = function (items, margin, force) { 18857 var i, iMax; 18858 18859 if (force) { 18860 // reset top position of all items 18861 for (i = 0, iMax = items.length; i < iMax; i++) { 18862 items[i].top = null; 18863 } 18864 } 18865 18866 // calculate new, non-overlapping positions 18867 for (i = 0, iMax = items.length; i < iMax; i++) { 18868 var item = items[i]; 18869 if (item.stack && item.top === null) { 18870 // initialize top position 18871 item.top = margin.axis; 18872 18873 do { 18874 // TODO: optimize checking for overlap. when there is a gap without items, 18875 // you only need to check for items from the next item on, not from zero 18876 var collidingItem = null; 18877 for (var j = 0, jj = items.length; j < jj; j++) { 18878 var other = items[j]; 18879 if (other.top !== null && other !== item && other.stack && exports.collision(item, other, margin.item)) { 18880 collidingItem = other; 18881 break; 18882 } 18883 } 18884 18885 if (collidingItem != null) { 18886 // There is a collision. Reposition the items above the colliding element 18887 item.top = collidingItem.top + collidingItem.height + margin.item.vertical; 18888 } 18889 } while (collidingItem); 18890 } 18891 } 18892 }; 18893 18894 /** 18895 * Adjust vertical positions of the items without stacking them 18896 * @param {Item[]} items 18897 * All visible items 18898 * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin 18899 * Margins between items and between items and the axis. 18900 */ 18901 exports.nostack = function (items, margin, subgroups) { 18902 var i, iMax, newTop; 18903 18904 // reset top position of all items 18905 for (i = 0, iMax = items.length; i < iMax; i++) { 18906 if (items[i].data.subgroup !== undefined) { 18907 newTop = margin.axis; 18908 for (var subgroup in subgroups) { 18909 if (subgroups.hasOwnProperty(subgroup)) { 18910 if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroups[items[i].data.subgroup].index) { 18911 newTop += subgroups[subgroup].height + margin.item.vertical; 18912 } 18913 } 18914 } 18915 items[i].top = newTop; 18916 } else { 18917 items[i].top = margin.axis; 18918 } 18919 } 18920 }; 18921 18922 /** 18923 * Test if the two provided items collide 18924 * The items must have parameters left, width, top, and height. 18925 * @param {Item} a The first item 18926 * @param {Item} b The second item 18927 * @param {{horizontal: number, vertical: number}} margin 18928 * An object containing a horizontal and vertical 18929 * minimum required margin. 18930 * @return {boolean} true if a and b collide, else false 18931 */ 18932 exports.collision = function (a, b, margin) { 18933 return a.left - margin.horizontal + EPSILON < b.left + b.width && a.left + a.width + margin.horizontal - EPSILON >
18933 b.left && a.top - margin.vertical + EPSILON < b.top + b.height && a.top + a.height + margin.vertical - EPSILON > b.top; 18934 }; 18935 18936/***/ }, 18937/* 33 */ 18938/***/ function(module, exports, __webpack_require__) { 18939 18940 'use strict'; 18941 18942 var Hammer = __webpack_require__(20); 18943 var Item = __webpack_require__(34); 18944 18945 /** 18946 * @constructor RangeItem 18947 * @extends Item 18948 * @param {Object} data Object containing parameters start, end 18949 * content, className. 18950 * @param {{toScreen: function, toTime: function}} conversion 18951 * Conversion functions from time to screen and vice versa 18952 * @param {Object} [options] Configuration options 18953 * // TODO: describe options 18954 */ 18955 function RangeItem(data, conversion, options) { 18956 this.props = { 18957 content: { 18958 width: 0 18959 } 18960 }; 18961 this.overflow = false; // if contents can overflow (css styling), this flag is set to true 18962 18963 // validate data 18964 if (data) { 18965 if (data.start == undefined) { 18966 throw new Error('Property "start" missing in item ' + data.id); 18967 } 18968 if (data.end == undefined) { 18969 throw new Error('Property "end" missing in item ' + data.id); 18970 } 18971 } 18972 18973 Item.call(this, data, conversion, options); 18974 } 18975 18976 RangeItem.prototype = new Item(null, null, null); 18977 18978 RangeItem.prototype.baseClassName = 'vis-item vis-range'; 18979 18980 /** 18981 * Check whether this item is visible inside given range 18982 * @returns {{start: Number, end: Number}} range with a timestamp for start and end 18983 * @returns {boolean} True if visible 18984 */ 18985 RangeItem.prototype.isVisible = function (range) { 18986 // determine visibility 18987 return this.data.start < range.end && this.data.end > range.start; 18988 }; 18989 18990 /** 18991 * Repaint the item 18992 */ 18993 RangeItem.prototype.redraw = function () { 18994 var dom = this.dom; 18995 if (!dom) { 18996 // create DOM 18997 this.dom = {}; 18998 dom = this.dom; 18999 19000 // background box 19001 dom.box = document.createElement('div'); 19002 // className is updated in redraw() 19003 19004 // frame box (to prevent the item contents from overflowing 19005 dom.frame = document.createElement('div'); 19006 dom.frame.className = 'vis-item-overflow'; 19007 dom.box.appendChild(dom.frame); 19008 19009 // contents box 19010 dom.content = document.createElement('div'); 19011 dom.content.className = 'vis-item-content'; 19012 dom.frame.appendChild(dom.content); 19013 19014 // attach this item as attribute 19015 dom.box['timeline-item'] = this; 19016 19017 this.dirty = true; 19018 } 19019 19020 // append DOM to parent DOM 19021 if (!this.parent) { 19022 throw new Error('Cannot redraw item: no parent attached'); 19023 } 19024 if (!dom.box.parentNode) { 19025 var foreground = this.parent.dom.foreground; 19026 if (!foreground) { 19027 throw new Error('Cannot redraw item: parent has no foreground container element'); 19028 } 19029 foreground.appendChild(dom.box); 19030 } 19031 this.displayed = true; 19032
19033 // Update DOM when item is marked dirty. An item is marked dirty when: 19034 // - the item is not yet rendered 19035 // - the item's data is changed 19036 // - the item is selected/deselected 19037 if (this.dirty) { 19038 this._updateContents(this.dom.content); 19039 this._updateTitle(this.dom.box); 19040 this._updateDataAttributes(this.dom.box); 19041 this._updateStyle(this.dom.box); 19042 19043 var editable = (this.options.editable.updateTime || this.options.editable.updateGroup || this.editable === true) && this.editable !== false; 19044 19045 // update class 19046 var className = (this.data.className ? ' ' + this.data.className : '') + (this.selected ? ' vis-selected' : '') + (editable ? ' vis-editable' : ' vis-readonly'); 19047 dom.box.className = this.baseClassName + className; 19048 19049 // determine from css whether this box has overflow 19050 this.overflow = window.getComputedStyle(dom.frame).overflow !== 'hidden'; 19051 19052 // recalculate size 19053 // turn off max-width to be able to calculate the real width 19054 // this causes an extra browser repaint/reflow, but so be it 19055 this.dom.content.style.maxWidth = 'none'; 19056 this.props.content.width = this.dom.content.offsetWidth; 19057 this.height = this.dom.box.offsetHeight; 19058 this.dom.content.style.maxWidth = ''; 19059 19060 this.dirty = false; 19061 } 19062 19063 this._repaintDeleteButton(dom.box); 19064 this._repaintDragLeft(); 19065 this._repaintDragRight(); 19066 }; 19067 19068 /** 19069 * Show the item in the DOM (when not already visible). The items DOM will 19070 * be created when needed. 19071 */ 19072 RangeItem.prototype.show = function () { 19073 if (!this.displayed) { 19074 this.redraw(); 19075 } 19076 }; 19077 19078 /** 19079 * Hide the item from the DOM (when visible) 19080 * @return {Boolean} changed 19081 */ 19082 RangeItem.prototype.hide = function () { 19083 if (this.displayed) { 19084 var box = this.dom.box; 19085 19086 if (box.parentNode) { 19087 box.parentNode.removeChild(box); 19088 } 19089 19090 this.displayed = false; 19091 } 19092 }; 19093 19094 /** 19095 * Reposition the item horizontally 19096 * @param {boolean} [limitSize=true] If true (default), the width of the range 19097 * item will be limited, as the browser cannot 19098 * display very wide divs. This means though 19099 * that the applied left and width may 19100 * not correspond to the ranges start and end 19101 * @Override 19102 */ 19103 RangeItem.prototype.repositionX = function (limitSize) { 19104 var parentWidth = this.parent.width; 19105 var start = this.conversion.toScreen(this.data.start); 19106 var end = this.conversion.toScreen(this.data.end); 19107 var contentLeft; 19108 var contentWidth; 19109 19110 // limit the width of the range, as browsers cannot draw very wide divs 19111 if (limitSize === undefined || limitSize === true) { 19112 if (start < -parentWidth) { 19113 start = -parentWidth; 19114 } 19115 if (end > 2 * parentWidth) { 19116 end = 2 * parentWidth; 19117 } 19118 } 19119 var boxWidth = Math.max(end - start, 1); 19120 19121 if (this.overflow) { 19122 this.left = start; 19123 this.width = boxWidth + this.props.content.width; 19124 contentWidth = this.props.content.width; 19125 19126 // Note: The calculation of width is an optimistic calculation, giving 19127 // a width which will not change when moving the Timeline 19128 // So no re-stacking needed, which is nicer for the eye; 19129 } else { 19130 this.left = start; 19131 this.width = boxWidth; 19132 contentWidth = Math.min(end - start, this.props.content.width); 19133 } 19134 19135 this.dom.box.style.left = this.left + 'px'; 19136 this.dom.box.style.width = boxWidth + 'px'; 19137 19138 switch (this.options.align) { 19139 case 'left': 19140 this.dom.content.style.left = '0'; 19141 break; 19142 19143 case 'right': 19144 this.dom.content.style.left = Math.max(boxWidth - contentWidth, 0) + 'px'; 19145 break; 19146 19147 case 'center': 19148 this.dom.content.style.left = Math.max((boxWidth - contentWidth) / 2, 0) + 'px'; 19149 break; 19150 19151 default: 19152 // 'auto' 19153 // when range exceeds left of the window, position the contents at the left of the visible area 19154 if (this.overflow) { 19155 if (end > 0) { 19156 contentLeft = Math.max(-start, 0); 19157 } else { 19158 contentLeft = -contentWidth; // ensure it's not visible anymore 19159 } 19160 } else { 19161 if (start < 0) { 19162 contentLeft = -start; 19163 } else { 19164 contentLeft = 0; 19165 } 19166 } 19167 this.dom.content.style.left = contentLeft + 'px'; 19168 } 19169 }; 19170 19171 /** 19172 * Reposition the item vertically 19173 * @Override 19174 */ 19175 RangeItem.prototype.repositionY = function () { 19176 var orientation = this.options.orientation.item; 19177 var box = this.dom.box; 19178 19179 if (orientation == 'top') { 19180 box.style.top = this.top + 'px'; 19181 } else { 19182 box.style.top = this.parent.height - this.top - this.height + 'px'; 19183 } 19184 }; 19185 19186 /** 19187 * Repaint a drag area on the left side of the range when the range is selected 19188 * @protected 19189 */ 19190 RangeItem.prototype._repaintDragLeft = function () { 19191 if (this.selected && this.options.editable.updateTime && !this.dom.dragLeft) { 19192 // create and show drag area 19193 var dragLeft = document.createElement('div'); 19194 dragLeft.className = 'vis-drag-left'; 19195 dragLeft.dragLeftItem = this; 19196 19197 this.dom.box.appendChild(dragLeft); 19198 this.dom.dragLeft = dragLeft; 19199 } else if (!this.selected && this.dom.dragLeft) { 19200 // delete drag area 19201 if (this.dom.dragLeft.parentNode) { 19202 this.dom.dragLeft.parentNode.removeChild(this.dom.dragLeft); 19203 } 19204 this.dom.dragLeft = null; 19205 } 19206 }; 19207 19208 /** 19209 * Repaint a drag area on the right side of the range when the range is selected 19210 * @protected 19211 */ 19212 RangeItem.prototype._repaintDragRight = function () { 19213 if (this.selected && this.options.editable.updateTime && !this.dom.dragRight) { 19214 // create and show drag area 19215 var dragRight = document.createElement('div'); 19216 dragRight.className = 'vis-drag-right'; 19217 dragRight.dragRightItem = this; 19218 19219 this.dom.box.appendChild(dragRight); 19220 this.dom.dragRight = dragRight; 19221 } else if (!this.selected && this.dom.dragRight) { 19222 // delete drag area 19223 if (this.dom.dragRight.parentNode) { 19224 this.dom.dragRight.parentNode.removeChild(this.dom.dragRight); 19225 } 19226 this.dom.dragRight = null; 19227 } 19228 }; 19229 19230 module.exports = RangeItem; 19231 19232/***/ }, 19233/* 34 */ 19234/***/ function(module, exports, __webpack_require__) { 19235 19236 'use strict'; 19237 19238 var Hammer = __webpack_require__(20); 19239 var util = __webpack_require__(1); 19240 19241 /** 19242 * @constructor Item 19243 * @param {Object} data Object containing (optional) parameters type, 19244 * start, end, content, group, className. 19245 * @param {{toScreen: function, toTime: function}} conversion 19246 * Conversion functions from time to screen and vice versa 19247 * @param {Object} options Configuration options 19248 * // TODO: describe available options 19249 */ 19250 function Item(data, conversion, options) { 19251 this.id = null; 19252 this.parent = null; 19253 this.data = data; 19254 this.dom = null; 19255 this.conversion = conversion || {}; 19256 this.options = options || {}; 19257 19258 this.selected = false;
19259 this.displayed = false; 19260 this.dirty = true; 19261 19262 this.top = null; 19263 this.left = null; 19264 this.width = null; 19265 this.height = null; 19266 19267 this.editable = null; 19268 if (this.data && this.data.hasOwnProperty('editable') && typeof this.data.editable === 'boolean') { 19269 this.editable = data.editable; 19270 } 19271 } 19272 19273 Item.prototype.stack = true; 19274 19275 /** 19276 * Select current item 19277 */ 19278 Item.prototype.select = function () { 19279 this.selected = true; 19280 this.dirty = true; 19281 if (this.displayed) this.redraw(); 19282 }; 19283 19284 /** 19285 * Unselect current item 19286 */ 19287 Item.prototype.unselect = function () { 19288 this.selected = false; 19289 this.dirty = true; 19290 if (this.displayed) this.redraw(); 19291 }; 19292 19293 /** 19294 * Set data for the item. Existing data will be updated. The id should not 19295 * be changed. When the item is displayed, it will be redrawn immediately. 19296 * @param {Object} data 19297 */ 19298 Item.prototype.setData = function (data) { 19299 var groupChanged = data.group != undefined && this.data.group != data.group; 19300 if (groupChanged) { 19301 this.parent.itemSet._moveToGroup(this, data.group); 19302 } 19303 19304 if (data.hasOwnProperty('editable') && typeof data.editable === 'boolean') { 19305 this.editable = data.editable; 19306 } 19307 19308 this.data = data; 19309 this.dirty = true; 19310 if (this.displayed) this.redraw(); 19311 }; 19312 19313 /** 19314 * Set a parent for the item 19315 * @param {ItemSet | Group} parent 19316 */ 19317 Item.prototype.setParent = function (parent) { 19318 if (this.displayed) { 19319 this.hide(); 19320 this.parent = parent; 19321 if (this.parent) { 19322 this.show(); 19323 } 19324 } else { 19325 this.parent = parent; 19326 } 19327 }; 19328 19329 /** 19330 * Check whether this item is visible inside given range 19331 * @returns {{start: Number, end: Number}} range with a timestamp for start and end 19332 * @returns {boolean} True if visible 19333 */ 19334 Item.prototype.isVisible = function (range) { 19335 // Should be implemented by Item implementations 19336 return false; 19337 }; 19338 19339 /** 19340 * Show the Item in the DOM (when not already visible) 19341 * @return {Boolean} changed 19342 */ 19343 Item.prototype.show = function () { 19344 return false; 19345 }; 19346 19347 /** 19348 * Hide the Item from the DOM (when visible) 19349 * @return {Boolean} changed 19350 */ 19351 Item.prototype.hide = function () { 19352 return false; 19353 }; 19354 19355 /** 19356 * Repaint the item 19357 */ 19358 Item.prototype.redraw = function () {}; 19359 19360 /** 19361 * Reposition the Item horizontally 19362 */ 19363 Item.prototype.repositionX = function () {}; 19364 19365 /** 19366 * Reposition the Item vertically 19367 */ 19368 Item.prototype.repositionY = function () {}; 19369 19370 /** 19371 * Repaint a delete button on the top right of the item when the item is selected 19372 * @param {HTMLElement} anchor 19373 * @protected 19374 */ 19375 Item.prototype._repaintDeleteButton = function (anchor) { 19376 var editable = (this.options.editable.remove || this.data.editable === true) && this.data.editable !== false; 19377 19378 if (this.selected && editable && !this.dom.deleteButton) { 19379 // create and show button 19380 var me = this; 19381 19382 var deleteButton = document.createElement('div'); 19383 deleteButton.className = 'vis-delete'; 19384 deleteButton.title = 'Delete this item'; 19385 19386 // TODO: be able to destroy the delete button 19387 new Hammer(deleteButton).on('tap', function (event) { 19388 event.stopPropagation(); 19389 me.parent.removeFromDataSet(me); 19390 }); 19391 19392 anchor.appendChild(deleteButton); 19393 this.dom.deleteButton = deleteButton; 19394 } else if (!this.selected && this.dom.deleteButton) { 19395 // remove button 19396 if (this.dom.deleteButton.parentNode) { 19397 this.dom.deleteButton.parentNode.removeChild(this.dom.deleteButton); 19398 } 19399 this.dom.deleteButton = null; 19400 } 19401 }; 19402 19403 /** 19404 * Set HTML contents for the item 19405 * @param {Element} element HTML element to fill with the contents 19406 * @private 19407 */ 19408 Item.prototype._updateContents = function (element) { 19409 var content; 19410 if (this.options.template) { 19411 var itemData = this.parent.itemSet.itemsData.get(this.id); // get a clone of the data from the dataset 19412 content = this.options.template(itemData); 19413 } else { 19414 content = this.data.content; 19415 } 19416 19417 var changed = this._contentToString(this.content) !== this._contentToString(content); 19418 if (changed) { 19419 // only replace the content when changed 19420 if (content instanceof Element) { 19421 element.innerHTML = ''; 19422 element.appendChild(content); 19423 } else if (content != undefined) { 19424 element.innerHTML = content; 19425 } else { 19426 if (!(this.data.type == 'background' && this.data.content === undefined)) { 19427 throw new Error('Property "content" missing in item ' + this.id); 19428 } 19429 } 19430 19431 this.content = content; 19432 } 19433 }; 19434 19435 /** 19436 * Set HTML contents for the item 19437 * @param {Element} element HTML element to fill with the contents 19438 * @private 19439 */ 19440 Item.prototype._updateTitle = function (element) { 19441 if (this.data.title != null) { 19442 element.title = this.data.title || ''; 19443 } else { 19444 element.removeAttribute('vis-title'); 19445 } 19446 }; 19447 19448 /** 19449 * Process dataAttributes timeline option and set as data- attributes on dom.content 19450 * @param {Element} element HTML element to which the attributes will be attached 19451 * @private 19452 */ 19453 Item.prototype._updateDataAttributes = function (element) { 19454 if (this.options.dataAttributes && this.options.dataAttributes.length > 0) { 19455 var attributes = []; 19456 19457 if (Array.isArray(this.options.dataAttributes)) { 19458 attributes = this.options.dataAttributes; 19459 } else if (this.options.dataAttributes == 'all') { 19460 attributes = Object.keys(this.data); 19461 } else { 19462 return; 19463 } 19464 19465 for (var i = 0; i < attributes.length; i++) { 19466 var name = attributes[i]; 19467 var value = this.data[name]; 19468 19469 if (value != null) { 19470 element.setAttribute('data-' + name, value); 19471 } else { 19472 element.removeAttribute('data-' + name); 19473 } 19474 } 19475 } 19476 }; 19477 19478 /** 19479 * Update custom styles of the element 19480 * @param element 19481 * @private 19482 */ 19483 Item.prototype._updateStyle = function (element) { 19484 // remove old styles 19485 if (this.style) { 19486 util.removeCssText(element, this.style); 19487 this.style = null; 19488 } 19489 19490 // append new styles 19491 if (this.data.style) { 19492 util.addCssText(element, this.data.style); 19493 this.style = this.data.style; 19494 } 19495 }; 19496 19497 /** 19498 * Stringify the items contents 19499 * @param {string | Element | undefined} content 19500 * @returns {string | undefined} 19501 * @private 19502 */ 19503 Item.prototype._contentToString = function (content) { 19504 if (typeof content === 'string') return content; 19505 if (content && 'outerHTML' in content) return content.outerHTML; 19506 return content; 19507 }; 19508 19509 /** 19510 * Return the width of the item left from its start date 19511 * @return {number} 19512 */ 19513 Item.prototype.getWidthLeft = function () { 19514 return 0; 19515 }; 19516 19517 /** 19518 * Return the width of the item right from the max of its start and end date 19519 * @return {number} 19520 */ 19521 Item.prototype.getWidthRight = function () { 19522 return 0; 19523 }; 19524 19525 module.exports = Item; 19526 19527 // should be implemented by the item 19528 19529 // should be implemented by the item 19530 19531 // should be implemented by the item 19532 19533/***/ }, 19534/* 35 */ 19535/***/ function(module, exports, __webpack_require__) { 19536 19537 'use strict'; 19538 19539 var util = __webpack_require__(1); 19540 var Group = __webpack_require__(31); 19541 19542 /**
19543 * @constructor BackgroundGroup 19544 * @param {Number | String} groupId 19545 * @param {Object} data 19546 * @param {ItemSet} itemSet 19547 */ 19548 function BackgroundGroup(groupId, data, itemSet) { 19549 Group.call(this, groupId, data, itemSet); 19550 19551 this.width = 0; 19552 this.height = 0; 19553 this.top = 0; 19554 this.left = 0; 19555 } 19556 19557 BackgroundGroup.prototype = Object.create(Group.prototype); 19558 19559 /** 19560 * Repaint this group 19561 * @param {{start: number, end: number}} range 19562 * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin 19563 * @param {boolean} [restack=false] Force restacking of all items 19564 * @return {boolean} Returns true if the group is resized 19565 */ 19566 BackgroundGroup.prototype.redraw = function (range, margin, restack) { 19567 var resized = false; 19568 19569 this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range); 19570 19571 // calculate actual size 19572 this.width = this.dom.background.offsetWidth; 19573 19574 // apply new height (just always zero for BackgroundGroup 19575 this.dom.background.style.height = '0'; 19576 19577 // update vertical position of items after they are re-stacked and the height of the group is calculated 19578 for (var i = 0, ii = this.visibleItems.length; i < ii; i++) { 19579 var item = this.visibleItems[i]; 19580 item.repositionY(margin); 19581 } 19582 19583 return resized; 19584 }; 19585 19586 /** 19587 * Show this group: attach to the DOM 19588 */ 19589 BackgroundGroup.prototype.show = function () { 19590 if (!this.dom.background.parentNode) { 19591 this.itemSet.dom.background.appendChild(this.dom.background); 19592 } 19593 }; 19594 19595 module.exports = BackgroundGroup; 19596 19597/***/ }, 19598/* 36 */ 19599/***/ function(module, exports, __webpack_require__) { 19600 19601 'use strict'; 19602 19603 var Item = __webpack_require__(34); 19604 var util = __webpack_require__(1); 19605 19606 /** 19607 * @constructor BoxItem 19608 * @extends Item 19609 * @param {Object} data Object containing parameters start 19610 * content, className. 19611 * @param {{toScreen: function, toTime: function}} conversion 19612 * Conversion functions from time to screen and vice versa 19613 * @param {Object} [options] Configuration options 19614 * // TODO: describe available options 19615 */ 19616 function BoxItem(data, conversion, options) { 19617 this.props = { 19618 dot: { 19619 width: 0, 19620 height: 0 19621 }, 19622 line: { 19623 width: 0, 19624 height: 0 19625 } 19626 }; 19627 19628 // validate data 19629 if (data) { 19630 if (data.start == undefined) { 19631 throw new Error('Property "start" missing in item ' + data); 19632 } 19633 } 19634 19635 Item.call(this, data, conversion, options); 19636 } 19637 19638 BoxItem.prototype = new Item(null, null, null); 19639 19640 /** 19641 * Check whether this item is visible inside given range 19642 * @returns {{start: Number, end: Number}} range with a timestamp for start and end 19643 * @returns {boolean} True if visible 19644 */ 19645 BoxItem.prototype.isVisible = function (range) { 19646 // determine visibility 19647 // TODO: account for the real width of the item. Right now we just add 1/4 to the window 19648 var interval = (range.end - range.start) / 4; 19649 return this.data.start > range.start - interval && this.data.start < range.end + interval; 19650 }; 19651 19652 /** 19653 * Repaint the item 19654 */ 19655 BoxItem.prototype.redraw = function () { 19656 var dom = this.dom; 19657 if (!dom) { 19658 // create DOM 19659 this.dom = {}; 19660 dom = this.dom; 19661 19662 // create main box 19663 dom.box = document.createElement('DIV'); 19664 19665 // contents box (inside the background box). used for making margins 19666 dom.content = document.createElement('DIV'); 19667 dom.content.className = 'vis-item-content'; 19668 dom.box.appendChild(dom.content); 19669 19670 // line to axis 19671 dom.line = document.createElement('DIV'); 19672 dom.line.className = 'vis-line'; 19673 19674 // dot on axis 19675 dom.dot = document.createElement('DIV'); 19676 dom.dot.className = 'vis-dot'; 19677 19678 // attach this item as attribute 19679 dom.box['timeline-item'] = this; 19680 19681 this.dirty = true; 19682 } 19683 19684 // append DOM to parent DOM 19685 if (!this.parent) { 19686 throw new Error('Cannot redraw item: no parent attached'); 19687 } 19688 if (!dom.box.parentNode) { 19689 var foreground = this.parent.dom.foreground; 19690 if (!foreground) throw new Error('Cannot redraw item: parent has no foreground container element'); 19691 foreground.appendChild(dom.box); 19692 } 19693 if (!dom.line.parentNode) { 19694 var background = this.parent.dom.background; 19695 if (!background) throw new Error('Cannot redraw item: parent has no background container element'); 19696 background.appendChild(dom.line); 19697 } 19698 if (!dom.dot.parentNode) { 19699 var axis = this.parent.dom.axis; 19700 if (!background) throw new Error('Cannot redraw item: parent has no axis container element'); 19701 axis.appendChild(dom.dot); 19702 } 19703 this.displayed = true; 19704
19705 // Update DOM when item is marked dirty. An item is marked dirty when: 19706 // - the item is not yet rendered 19707 // - the item's data is changed 19708 // - the item is selected/deselected 19709 if (this.dirty) { 19710 this._updateContents(this.dom.content); 19711 this._updateTitle(this.dom.box); 19712 this._updateDataAttributes(this.dom.box); 19713 this._updateStyle(this.dom.box); 19714 19715 var editable = (this.options.editable.updateTime || this.options.editable.updateGroup || this.editable === true) && this.editable !== false; 19716 19717 // update class 19718 var className = (this.data.className ? ' ' + this.data.className : '') + (this.selected ? ' vis-selected' : '') + (editable ? ' vis-editable' : ' vis-readonly'); 19719 dom.box.className = 'vis-item vis-box' + className; 19720 dom.line.className = 'vis-item vis-line' + className; 19721 dom.dot.className = 'vis-item vis-dot' + className; 19722 19723 // recalculate size 19724 this.props.dot.height = dom.dot.offsetHeight; 19725 this.props.dot.width = dom.dot.offsetWidth; 19726 this.props.line.width = dom.line.offsetWidth; 19727 this.width = dom.box.offsetWidth; 19728 this.height = dom.box.offsetHeight; 19729 19730 this.dirty = false; 19731 } 19732 19733 this._repaintDeleteButton(dom.box); 19734 }; 19735 19736 /** 19737 * Show the item in the DOM (when not already displayed). The items DOM will 19738 * be created when needed. 19739 */ 19740 BoxItem.prototype.show = function () { 19741 if (!this.displayed) { 19742 this.redraw(); 19743 } 19744 }; 19745 19746 /** 19747 * Hide the item from the DOM (when visible) 19748 */ 19749 BoxItem.prototype.hide = function () { 19750 if (this.displayed) { 19751 var dom = this.dom; 19752 19753 if (dom.box.parentNode) dom.box.parentNode.removeChild(dom.box); 19754 if (dom.line.parentNode) dom.line.parentNode.removeChild(dom.line); 19755 if (dom.dot.parentNode) dom.dot.parentNode.removeChild(dom.dot); 19756 19757 this.displayed = false; 19758 } 19759 }; 19760 19761 /** 19762 * Reposition the item horizontally 19763 * @Override 19764 */ 19765 BoxItem.prototype.repositionX = function () { 19766 var start = this.conversion.toScreen(this.data.start); 19767 var align = this.options.align; 19768 var left; 19769 19770 // calculate left position of the box 19771 if (align == 'right') { 19772 this.left = start - this.width; 19773 } else if (align == 'left') { 19774 this.left = start; 19775 } else { 19776 // default or 'center' 19777 this.left = start - this.width / 2; 19778 } 19779 19780 // reposition box 19781 this.dom.box.style.left = this.left + 'px'; 19782 19783 // reposition line 19784 this.dom.line.style.left = start - this.props.line.width / 2 + 'px'; 19785 19786 // reposition dot 19787 this.dom.dot.style.left = start - this.props.dot.width / 2 + 'px'; 19788 }; 19789 19790 /** 19791 * Reposition the item vertically 19792 * @Override 19793 */ 19794 BoxItem.prototype.repositionY = function () { 19795 var orientation = this.options.orientation.item; 19796 var box = this.dom.box; 19797 var line = this.dom.line; 19798 var dot = this.dom.dot; 19799 19800 if (orientation == 'top') { 19801 box.style.top = (this.top || 0) + 'px'; 19802 19803 line.style.top = '0'; 19804 line.style.height = this.parent.top + this.top + 1 + 'px'; 19805 line.style.bottom = ''; 19806 } else { 19807 // orientation 'bottom' 19808 var itemSetHeight = this.parent.itemSet.props.height; // TODO: this is nasty 19809 var lineHeight = itemSetHeight - this.parent.top - this.parent.height + this.top; 19810 19811 box.style.top = (this.parent.height - this.top - this.height || 0) + 'px'; 19812 line.style.top = itemSetHeight - lineHeight + 'px'; 19813 line.style.bottom = '0'; 19814 } 19815 19816 dot.style.top = -this.props.dot.height / 2 + 'px'; 19817 }; 19818 19819 /** 19820 * Return the width of the item left from its start date 19821 * @return {number} 19822 */ 19823 BoxItem.prototype.getWidthLeft = function () { 19824 return this.width / 2; 19825 }; 19826 19827 /** 19828 * Return the width of the item right from its start date 19829 * @return {number} 19830 */ 19831 BoxItem.prototype.getWidthRight = function () { 19832 return this.width / 2; 19833 }; 19834 19835 module.exports = BoxItem; 19836 19837/***/ }, 19838/* 37 */ 19839/***/ function(module, exports, __webpack_require__) { 19840 19841 'use strict'; 19842 19843 var Item = __webpack_require__(34); 19844 19845 /** 19846 * @constructor PointItem 19847 * @extends Item 19848 * @param {Object} data Object containing parameters start 19849 * content, className. 19850 * @param {{toScreen: function, toTime: function}} conversion 19851 * Conversion functions from time to screen and vice versa 19852 * @param {Object} [options] Configuration options 19853 * // TODO: describe available options 19854 */ 19855 function PointItem(data, conversion, options) { 19856 this.props = { 19857 dot: { 19858 top: 0, 19859 width: 0, 19860 height: 0 19861 }, 19862 content: { 19863 height: 0, 19864 marginLeft: 0 19865 } 19866 }; 19867 19868 // validate data 19869 if (data) { 19870 if (data.start == undefined) { 19871 throw new Error('Property "start" missing in item ' + data); 19872 } 19873 } 19874 19875 Item.call(this, data, conversion, options); 19876 } 19877 19878 PointItem.prototype = new Item(null, null, null); 19879 19880 /** 19881 * Check whether this item is visible inside given range 19882 * @returns {{start: Number, end: Number}} range with a timestamp for start and end 19883 * @returns {boolean} True if visible 19884 */ 19885 PointItem.prototype.isVisible = function (range) { 19886 // determine visibility 19887 // TODO: account for the real width of the item. Right now we just add 1/4 to the window 19888 var interval = (range.end - range.start) / 4; 19889 return this.data.start > range.start - interval && this.data.start < range.end + interval; 19890 }; 19891 19892 /** 19893 * Repaint the item 19894 */ 19895 PointItem.prototype.redraw = function () { 19896 var dom = this.dom; 19897 if (!dom) { 19898 // create DOM 19899 this.dom = {}; 19900 dom = this.dom; 19901 19902 // background box 19903 dom.point = document.createElement('div'); 19904 // className is updated in redraw() 19905 19906 // contents box, right from the dot 19907 dom.content = document.createElement('div'); 19908 dom.content.className = 'vis-item-content';
19909 dom.point.appendChild(dom.content); 19910 19911 // dot at start 19912 dom.dot = document.createElement('div'); 19913 dom.point.appendChild(dom.dot); 19914 19915 // attach this item as attribute 19916 dom.point['timeline-item'] = this; 19917 19918 this.dirty = true; 19919 } 19920 19921 // append DOM to parent DOM 19922 if (!this.parent) { 19923 throw new Error('Cannot redraw item: no parent attached'); 19924 } 19925 if (!dom.point.parentNode) { 19926 var foreground = this.parent.dom.foreground; 19927 if (!foreground) { 19928 throw new Error('Cannot redraw item: parent has no foreground container element'); 19929 } 19930 foreground.appendChild(dom.point); 19931 } 19932 this.displayed = true; 19933 19934 // Update DOM when item is marked dirty. An item is marked dirty when: 19935 // - the item is not yet rendered 19936 // - the item's data is changed 19937 // - the item is selected/deselected 19938 if (this.dirty) { 19939 this._updateContents(this.dom.content); 19940 this._updateTitle(this.dom.point); 19941 this._updateDataAttributes(this.dom.point); 19942 this._updateStyle(this.dom.point); 19943 19944 var editable = (this.options.editable.updateTime || this.options.editable.updateGroup || this.editable === true) && this.editable !== false; 19945 19946 // update class 19947 var className = (this.data.className ? ' ' + this.data.className : '') + (this.selected ? ' vis-selected' : '') + (editable ? ' vis-editable' : ' vis-readonly'); 19948 dom.point.className = 'vis-item vis-point' + className; 19949 dom.dot.className = 'vis-item vis-dot' + className; 19950 19951 // recalculate size of dot and contents 19952 this.props.dot.width = dom.dot.offsetWidth; 19953 this.props.dot.height = dom.dot.offsetHeight; 19954 this.props.content.height = dom.content.offsetHeight; 19955 19956 // resize contents 19957 dom.content.style.marginLeft = 2 * this.props.dot.width + 'px'; 19958 //dom.content.style.marginRight = ... + 'px'; // TODO: margin right 19959 19960 // recalculate size 19961 this.width = dom.point.offsetWidth; 19962 this.height = dom.point.offsetHeight; 19963 19964 // reposition the dot 19965 dom.dot.style.top = (this.height - this.props.dot.height) / 2 + 'px'; 19966 dom.dot.style.left = this.props.dot.width / 2 + 'px'; 19967 19968 this.dirty = false; 19969 } 19970 19971 this._repaintDeleteButton(dom.point); 19972 }; 19973 19974 /** 19975 * Show the item in the DOM (when not already visible). The items DOM will 19976 * be created when needed. 19977 */ 19978 PointItem.prototype.show = function () { 19979 if (!this.displayed) { 19980 this.redraw(); 19981 } 19982 }; 19983 19984 /** 19985 * Hide the item from the DOM (when visible) 19986 */ 19987 PointItem.prototype.hide = function () { 19988 if (this.displayed) { 19989 if (this.dom.point.parentNode) { 19990 this.dom.point.parentNode.removeChild(this.dom.point); 19991 } 19992 19993 this.displayed = false; 19994 } 19995 }; 19996 19997 /** 19998 * Reposition the item horizontally 19999 * @Override 20000 */ 20001 PointItem.prototype.repositionX = function () { 20002 var start = this.conversion.toScreen(this.data.start); 20003 20004 this.left = start - this.props.dot.width; 20005 20006 // reposition point 20007 this.dom.point.style.left = this.left + 'px'; 20008 }; 20009 20010 /** 20011 * Reposition the item vertically 20012 * @Override 20013 */ 20014 PointItem.prototype.repositionY = function () { 20015 var orientation = this.options.orientation.item; 20016 var point = this.dom.point; 20017 20018 if (orientation == 'top') { 20019 point.style.top = this.top + 'px'; 20020 } else { 20021 point.style.top = this.parent.height - this.top - this.height + 'px'; 20022 } 20023 }; 20024 20025 /** 20026 * Return the width of the item left from its start date 20027 * @return {number} 20028 */ 20029 PointItem.prototype.getWidthLeft = function () { 20030 return this.props.dot.width; 20031 }; 20032 20033 /** 20034 * Return the width of the item right from its start date 20035 * @return {number} 20036 */ 20037 PointItem.prototype.getWidthRight = function () { 20038 return this.width - this.props.dot.width; 20039 }; 20040 20041 module.exports = PointItem; 20042 20043/***/ }, 20044/* 38 */ 20045/***/ function(module, exports, __webpack_require__) { 20046 20047 'use strict'; 20048 20049 var Hammer = __webpack_require__(20); 20050 var Item = __webpack_require__(34);
20051 var BackgroundGroup = __webpack_require__(35); 20052 var RangeItem = __webpack_require__(33); 20053 20054 /** 20055 * @constructor BackgroundItem 20056 * @extends Item 20057 * @param {Object} data Object containing parameters start, end 20058 * content, className. 20059 * @param {{toScreen: function, toTime: function}} conversion 20060 * Conversion functions from time to screen and vice versa 20061 * @param {Object} [options] Configuration options 20062 * // TODO: describe options 20063 */ 20064 // TODO: implement support for the BackgroundItem just having a start, then being displayed as a sort of an annotation 20065 function BackgroundItem(data, conversion, options) { 20066 this.props = { 20067 content: { 20068 width: 0 20069 } 20070 }; 20071 this.overflow = false; // if contents can overflow (css styling), this flag is set to true 20072 20073 // validate data 20074 if (data) { 20075 if (data.start == undefined) { 20076 throw new Error('Property "start" missing in item ' + data.id); 20077 } 20078 if (data.end == undefined) { 20079 throw new Error('Property "end" missing in item ' + data.id); 20080 } 20081 } 20082 20083 Item.call(this, data, conversion, options); 20084 } 20085 20086 BackgroundItem.prototype = new Item(null, null, null); 20087 20088 BackgroundItem.prototype.baseClassName = 'vis-item vis-background'; 20089 BackgroundItem.prototype.stack = false; 20090 20091 /** 20092 * Check whether this item is visible inside given range 20093 * @returns {{start: Number, end: Number}} range with a timestamp for start and end 20094 * @returns {boolean} True if visible 20095 */ 20096 BackgroundItem.prototype.isVisible = function (range) { 20097 // determine visibility 20098 return this.data.start < range.end && this.data.end > range.start; 20099 }; 20100 20101 /** 20102 * Repaint the item 20103 */ 20104 BackgroundItem.prototype.redraw = function () { 20105 var dom = this.dom; 20106 if (!dom) { 20107 // create DOM 20108 this.dom = {}; 20109 dom = this.dom; 20110 20111 // background box 20112 dom.box = document.createElement('div'); 20113 // className is updated in redraw() 20114 20115 // frame box (to prevent the item contents from overflowing 20116 dom.frame = document.createElement('div'); 20117 dom.frame.className = 'vis-item-overflow'; 20118 dom.box.appendChild(dom.frame); 20119 20120 // contents box 20121 dom.content = document.createElement('div'); 20122 dom.content.className = 'vis-item-content'; 20123 dom.frame.appendChild(dom.content); 20124 20125 // Note: we do NOT attach this item as attribute to the DOM, 20126 // such that background items cannot be selected 20127 //dom.box['timeline-item'] = this; 20128 20129 this.dirty = true; 20130 } 20131 20132 // append DOM to parent DOM 20133 if (!this.parent) { 20134 throw new Error('Cannot redraw item: no parent attached'); 20135 } 20136 if (!dom.box.parentNode) { 20137 var background = this.parent.dom.background; 20138 if (!background) { 20139 throw new Error('Cannot redraw item: parent has no background container element'); 20140 } 20141 background.appendChild(dom.box); 20142 } 20143 this.displayed = true; 20144
20145 // Update DOM when item is marked dirty. An item is marked dirty when: 20146 // - the item is not yet rendered 20147 // - the item's data is changed 20148 // - the item is selected/deselected 20149 if (this.dirty) { 20150 this._updateContents(this.dom.content); 20151 this._updateTitle(this.dom.content); 20152 this._updateDataAttributes(this.dom.content); 20153 this._updateStyle(this.dom.box); 20154 20155 // update class 20156 var className = (this.data.className ? ' ' + this.data.className : '') + (this.selected ? ' vis-selected' : ''); 20157 dom.box.className = this.baseClassName + className; 20158 20159 // determine from css whether this box has overflow 20160 this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden'; 20161 20162 // recalculate size 20163 this.props.content.width = this.dom.content.offsetWidth; 20164 this.height = 0; // set height zero, so this item will be ignored when stacking items 20165 20166 this.dirty = false; 20167 } 20168 }; 20169 20170 /** 20171 * Show the item in the DOM (when not already visible). The items DOM will 20172 * be created when needed. 20173 */ 20174 BackgroundItem.prototype.show = RangeItem.prototype.show; 20175 20176 /** 20177 * Hide the item from the DOM (when visible) 20178 * @return {Boolean} changed 20179 */ 20180 BackgroundItem.prototype.hide = RangeItem.prototype.hide; 20181 20182 /** 20183 * Reposition the item horizontally 20184 * @Override 20185 */ 20186 BackgroundItem.prototype.repositionX = RangeItem.prototype.repositionX; 20187 20188 /** 20189 * Reposition the item vertically 20190 * @Override 20191 */ 20192 BackgroundItem.prototype.repositionY = function (margin) { 20193 var onTop = this.options.orientation.item === 'top'; 20194 this.dom.content.style.top = onTop ? '' : '0'; 20195 this.dom.content.style.bottom = onTop ? '0' : ''; 20196 var height; 20197 20198 // special positioning for subgroups 20199 if (this.data.subgroup !== undefined) { 20200 // TODO: instead of calculating the top position of the subgroups here for every BackgroundItem, calculate the top of the subgroup once in Itemset 20201 20202 var itemSubgroup = this.data.subgroup; 20203 var subgroups = this.parent.subgroups; 20204 var subgroupIndex = subgroups[itemSubgroup].index; 20205 // if the orientation is top, we need to take the difference in height into account. 20206 if (onTop == true) { 20207 // the first subgroup will have to account for the distance from the top to the first item. 20208 height = this.parent.subgroups[itemSubgroup].height + margin.item.vertical; 20209 height += subgroupIndex == 0 ? margin.axis - 0.5 * margin.item.vertical : 0; 20210 var newTop = this.parent.top; 20211 for (var subgroup in subgroups) { 20212 if (subgroups.hasOwnProperty(subgroup)) { 20213 if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroupIndex) { 20214 newTop += subgroups[subgroup].height + margin.item.vertical; 20215 } 20216 } 20217 } 20218 20219 // the others will have to be offset downwards with this same distance. 20220 newTop += subgroupIndex != 0 ? margin.axis - 0.5 * margin.item.vertical : 0; 20221 this.dom.box.style.top = newTop + 'px'; 20222 this.dom.box.style.bottom = ''; 20223 } 20224 // and when the orientation is bottom: 20225 else { 20226 var newTop = this.parent.top; 20227 var totalHeight = 0; 20228 for (var subgroup in subgroups) { 20229 if (subgroups.hasOwnProperty(subgroup)) { 20230 if (subgroups[subgroup].visible == true) { 20231 var newHeight = subgroups[subgroup].height + margin.item.vertical; 20232 totalHeight += newHeight; 20233 if (subgroups[subgroup].index > subgroupIndex) { 20234 newTop += newHeight; 20235 } 20236 } 20237 } 20238 } 20239 height = this.parent.subgroups[itemSubgroup].height + margin.item.vertical; 20240 this.dom.box.style.top = this.parent.height - totalHeight + newTop + 'px'; 20241 this.dom.box.style.bottom = ''; 20242 } 20243 } 20244 // and in the case of no subgroups: 20245 else { 20246 // we want backgrounds with groups to only show in groups. 20247 if (this.parent instanceof BackgroundGroup) { 20248 // if the item is not in a group: 20249 height = Math.max(this.parent.height, this.parent.itemSet.body.domProps.center.height, this.parent.itemSet.body.domProps.centerContainer.height); 20250 this.dom.box.style.top = onTop ? '0' : ''; 20251 this.dom.box.style.bottom = onTop ? '' : '0'; 20252 } else { 20253 height = this.parent.height; 20254 // same alignment for items when orientation is top or bottom 20255 this.dom.box.style.top = this.parent.top + 'px'; 20256 this.dom.box.style.bottom = ''; 20257 } 20258 } 20259 this.dom.box.style.height = height + 'px'; 20260 }; 20261
vendor: 26,125 bytes, lines 20262-21046
20262 module.exports = BackgroundItem; 20263 20264/***/ }, 20265/* 39 */ 20266/***/ function(module, exports, __webpack_require__) { 20267 20268 'use strict'; 20269 20270 var util = __webpack_require__(1); 20271 var Component = __webpack_require__(26); 20272 var TimeStep = __webpack_require__(30); 20273 var DateUtil = __webpack_require__(27); 20274 var moment = __webpack_require__(2); 20275 20276 /** 20277 * A horizontal time axis 20278 * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body 20279 * @param {Object} [options] See TimeAxis.setOptions for the available 20280 * options. 20281 * @constructor TimeAxis 20282 * @extends Component 20283 */ 20284 function TimeAxis(body, options) { 20285 this.dom = { 20286 foreground: null, 20287 lines: [], 20288 majorTexts: [], 20289 minorTexts: [], 20290 redundant: { 20291 lines: [], 20292 majorTexts: [], 20293 minorTexts: [] 20294 } 20295 }; 20296 this.props = { 20297 range: { 20298 start: 0, 20299 end: 0, 20300 minimumStep: 0 20301 }, 20302 lineTop: 0 20303 }; 20304 20305 this.defaultOptions = { 20306 orientation: { 20307 axis: 'bottom' 20308 }, // axis orientation: 'top' or 'bottom' 20309 showMinorLabels: true, 20310 showMajorLabels: true, 20311 format: TimeStep.FORMAT, 20312 moment: moment, 20313 timeAxis: null 20314 }; 20315 this.options = util.extend({}, this.defaultOptions); 20316 20317 this.body = body; 20318 20319 // create the HTML DOM 20320 this._create(); 20321 20322 this.setOptions(options); 20323 } 20324 20325 TimeAxis.prototype = new Component(); 20326 20327 /** 20328 * Set options for the TimeAxis. 20329 * Parameters will be merged in current options. 20330 * @param {Object} options Available options: 20331 * {string} [orientation.axis] 20332 * {boolean} [showMinorLabels] 20333 * {boolean} [showMajorLabels] 20334 */ 20335 TimeAxis.prototype.setOptions = function (options) { 20336 if (options) { 20337 // copy all options that we know 20338 util.selectiveExtend(['showMinorLabels', 'showMajorLabels', 'hiddenDates', 'timeAxis', 'moment'], this.options, options); 20339 20340 // deep copy the format options 20341 util.selectiveDeepExtend(['format'], this.options, options); 20342 20343 if ('orientation' in options) { 20344 if (typeof options.orientation === 'string') { 20345 this.options.orientation.axis = options.orientation; 20346 } else if (typeof options.orientation === 'object' && 'axis' in options.orientation) { 20347 this.options.orientation.axis = options.orientation.axis; 20348 } 20349 } 20350 20351 // apply locale to moment.js 20352 // TODO: not so nice, this is applied globally to moment.js 20353 if ('locale' in options) { 20354 if (typeof moment.locale === 'function') { 20355 // moment.js 2.8.1+ 20356 moment.locale(options.locale); 20357 } else { 20358 moment.lang(options.locale); 20359 } 20360 } 20361 } 20362 }; 20363 20364 /** 20365 * Create the HTML DOM for the TimeAxis 20366 */ 20367 TimeAxis.prototype._create = function () { 20368 this.dom.foreground = document.createElement('div'); 20369 this.dom.background = document.createElement('div'); 20370 20371 this.dom.foreground.className = 'vis-time-axis vis-foreground'; 20372 this.dom.background.className = 'vis-time-axis vis-background'; 20373 }; 20374 20375 /** 20376 * Destroy the TimeAxis 20377 */ 20378 TimeAxis.prototype.destroy = function () { 20379 // remove from DOM 20380 if (this.dom.foreground.parentNode) { 20381 this.dom.foreground.parentNode.removeChild(this.dom.foreground); 20382 } 20383 if (this.dom.background.parentNode) { 20384 this.dom.background.parentNode.removeChild(this.dom.background); 20385 } 20386 20387 this.body = null; 20388 }; 20389 20390 /** 20391 * Repaint the component 20392 * @return {boolean} Returns true if the component is resized 20393 */ 20394 TimeAxis.prototype.redraw = function () { 20395 var props = this.props; 20396 var foreground = this.dom.foreground; 20397 var background = this.dom.background; 20398 20399 // determine the correct parent DOM element (depending on option orientation) 20400 var parent = this.options.orientation.axis == 'top' ? this.body.dom.top : this.body.dom.bottom; 20401 var parentChanged = foreground.parentNode !== parent; 20402 20403 // calculate character width and height 20404 this._calculateCharSize(); 20405 20406 // TODO: recalculate sizes only needed when parent is resized or options is changed 20407 var showMinorLabels = this.options.showMinorLabels && this.options.orientation.axis !== 'none'; 20408 var showMajorLabels = this.options.showMajorLabels && this.options.orientation.axis !== 'none'; 20409 20410 // determine the width and height of the elemens for the axis 20411 props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0; 20412 props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0; 20413 props.height = props.minorLabelHeight + props.majorLabelHeight; 20414 props.width = foreground.offsetWidth; 20415 20416 props.minorLineHeight = this.body.domProps.root.height - props.majorLabelHeight - (this.options.orientation.axis == 'top' ? this.body.domProps.bottom.height : this.body.domProps.top.height); 20417 props.minorLineWidth = 1; // TODO: really calculate width 20418 props.majorLineHeight = props.minorLineHeight + props.majorLabelHeight; 20419 props.majorLineWidth = 1; // TODO: really calculate width 20420 20421 // take foreground and background offline while updating (is almost twice as fast) 20422 var foregroundNextSibling = foreground.nextSibling; 20423 var backgroundNextSibling = background.nextSibling; 20424 foreground.parentNode && foreground.parentNode.removeChild(foreground); 20425 background.parentNode && background.parentNode.removeChild(background); 20426 20427 foreground.style.height = this.props.height + 'px'; 20428 20429 this._repaintLabels(); 20430 20431 // put DOM online again (at the same place) 20432 if (foregroundNextSibling) { 20433 parent.insertBefore(foreground, foregroundNextSibling); 20434 } else { 20435 parent.appendChild(foreground); 20436 } 20437 if (backgroundNextSibling) { 20438 this.body.dom.backgroundVertical.insertBefore(background, backgroundNextSibling); 20439 } else { 20440 this.body.dom.backgroundVertical.appendChild(background); 20441 } 20442 20443 return this._isResized() || parentChanged; 20444 }; 20445 20446 /** 20447 * Repaint major and minor text labels and vertical grid lines 20448 * @private 20449 */ 20450 TimeAxis.prototype._repaintLabels = function () { 20451 var orientation = this.options.orientation.axis; 20452 20453 // calculate range and step (step such that we have space for 7 characters per label) 20454 var start = util.convert(this.body.range.start, 'Number'); 20455 var end = util.convert(this.body.range.end, 'Number'); 20456 var timeLabelsize = this.body.util.toTime((this.props.minorCharWidth || 10) * 7).valueOf(); 20457 var minimumStep = timeLabelsize - DateUtil.getHiddenDurationBefore(this.options.moment, this.body.hiddenDates, this.body.range, timeLabelsize); 20458 minimumStep -= this.body.util.toTime(0).valueOf(); 20459 20460 var step = new TimeStep(new Date(start), new Date(end), minimumStep, this.body.hiddenDates); 20461 step.setMoment(this.options.moment); 20462 if (this.options.format) { 20463 step.setFormat(this.options.format); 20464 } 20465 if (this.options.timeAxis) { 20466 step.setScale(this.options.timeAxis); 20467 } 20468 this.step = step; 20469 20470 // Move all DOM elements to a "redundant" list, where they 20471 // can be picked for re-use, and clear the lists with lines and texts. 20472 // At the end of the function _repaintLabels, left over elements will be cleaned up 20473 var dom = this.dom; 20474 dom.redundant.lines = dom.lines; 20475 dom.redundant.majorTexts = dom.majorTexts; 20476 dom.redundant.minorTexts = dom.minorTexts; 20477 dom.lines = []; 20478 dom.majorTexts = []; 20479 dom.minorTexts = []; 20480 20481 var current; 20482 var next; 20483 var x; 20484 var xNext; 20485 var isMajor; 20486 var width; 20487 var line; 20488 var labelMinor; 20489 var xFirstMajorLabel = undefined; 20490 var max = 0; 20491 var className; 20492 20493 step.start(); 20494 next = step.getCurrent(); 20495 xNext = this.body.util.toScreen(next); 20496 while (step.hasNext() && max < 1000) { 20497 max++; 20498 20499 isMajor = step.isMajor(); 20500 className = step.getClassName(); 20501 labelMinor = step.getLabelMinor(); 20502 20503 current = next; 20504 x = xNext; 20505 20506 step.next(); 20507 next = step.getCurrent(); 20508 xNext = this.body.util.toScreen(next); 20509 20510 width = xNext - x; 20511 var labelFits = (labelMinor.length + 1) * this.props.minorCharWidth < width; 20512 20513 if (this.options.showMinorLabels && labelFits) { 20514 this._repaintMinorText(x, labelMinor, orientation, className); 20515 } 20516 20517 if (isMajor && this.options.showMajorLabels) { 20518 if (x > 0) { 20519 if (xFirstMajorLabel == undefined) { 20520 xFirstMajorLabel = x; 20521 } 20522 this._repaintMajorText(x, step.getLabelMajor(), orientation, className); 20523 } 20524 line = this._repaintMajorLine(x, width, orientation, className); 20525 } else { 20526 if (labelFits) { 20527 line = this._repaintMinorLine(x, width, orientation, className); 20528 } else { 20529 if (line) { 20530 line.style.width = parseInt(line.style.width) + width + 'px'; 20531 } 20532 } 20533 } 20534 } 20535 20536 // create a major label on the left when needed 20537 if (this.options.showMajorLabels) { 20538 var leftTime = this.body.util.toTime(0), 20539 leftText = step.getLabelMajor(leftTime), 20540 widthText = leftText.length * (this.props.majorCharWidth || 10) + 10; // upper bound estimation 20541 20542 if (xFirstMajorLabel == undefined || widthText < xFirstMajorLabel) { 20543 this._repaintMajorText(0, leftText, orientation, className); 20544 } 20545 } 20546 20547 // Cleanup leftover DOM elements from the redundant list 20548 util.forEach(this.dom.redundant, function (arr) { 20549 while (arr.length) { 20550 var elem = arr.pop(); 20551 if (elem && elem.parentNode) { 20552 elem.parentNode.removeChild(elem); 20553 } 20554 } 20555 }); 20556 }; 20557 20558 /** 20559 * Create a minor label for the axis at position x 20560 * @param {Number} x 20561 * @param {String} text 20562 * @param {String} orientation "top" or "bottom" (default) 20563 * @param {String} className 20564 * @return {Element} Returns the HTML element of the created label 20565 * @private 20566 */ 20567 TimeAxis.prototype._repaintMinorText = function (x, text, orientation, className) { 20568 // reuse redundant label 20569 var label = this.dom.redundant.minorTexts.shift(); 20570 20571 if (!label) { 20572 // create new label 20573 var content = document.createTextNode(''); 20574 label = document.createElement('div'); 20575 label.appendChild(content); 20576 this.dom.foreground.appendChild(label); 20577 } 20578 this.dom.minorTexts.push(label); 20579 20580 label.childNodes[0].nodeValue = text; 20581 20582 label.style.top = orientation == 'top' ? this.props.majorLabelHeight + 'px' : '0'; 20583 label.style.left = x + 'px'; 20584 label.className = 'vis-text vis-minor ' + className; 20585 //label.title = title; // TODO: this is a heavy operation 20586 20587 return label; 20588 }; 20589 20590 /** 20591 * Create a Major label for the axis at position x 20592 * @param {Number} x 20593 * @param {String} text 20594 * @param {String} orientation "top" or "bottom" (default) 20595 * @param {String} className 20596 * @return {Element} Returns the HTML element of the created label 20597 * @private 20598 */ 20599 TimeAxis.prototype._repaintMajorText = function (x, text, orientation, className) { 20600 // reuse redundant label 20601 var label = this.dom.redundant.majorTexts.shift(); 20602 20603 if (!label) { 20604 // create label 20605 var content = document.createTextNode(text); 20606 label = document.createElement('div'); 20607 label.appendChild(content); 20608 this.dom.foreground.appendChild(label); 20609 } 20610 this.dom.majorTexts.push(label); 20611 20612 label.childNodes[0].nodeValue = text; 20613 label.className = 'vis-text vis-major ' + className; 20614 //label.title = title; // TODO: this is a heavy operation 20615 20616 label.style.top = orientation == 'top' ? '0' : this.props.minorLabelHeight + 'px'; 20617 label.style.left = x + 'px'; 20618 20619 return label; 20620 }; 20621 20622 /** 20623 * Create a minor line for the axis at position x 20624 * @param {Number} x 20625 * @param {Number} width 20626 * @param {String} orientation "top" or "bottom" (default) 20627 * @param {String} className 20628 * @return {Element} Returns the created line 20629 * @private 20630 */ 20631 TimeAxis.prototype._repaintMinorLine = function (x, width, orientation, className) { 20632 // reuse redundant line 20633 var line = this.dom.redundant.lines.shift(); 20634 if (!line) { 20635 // create vertical line 20636 line = document.createElement('div'); 20637 this.dom.background.appendChild(line); 20638 } 20639 this.dom.lines.push(line); 20640 20641 var props = this.props; 20642 if (orientation == 'top') { 20643 line.style.top = props.majorLabelHeight + 'px'; 20644 } else { 20645 line.style.top = this.body.domProps.top.height + 'px'; 20646 } 20647 line.style.height = props.minorLineHeight + 'px'; 20648 line.style.left = x - props.minorLineWidth / 2 + 'px'; 20649 line.style.width = width + 'px'; 20650 20651 line.className = 'vis-grid vis-vertical vis-minor ' + className; 20652 20653 return line; 20654 }; 20655 20656 /** 20657 * Create a Major line for the axis at position x 20658 * @param {Number} x 20659 * @param {Number} width 20660 * @param {String} orientation "top" or "bottom" (default) 20661 * @param {String} className 20662 * @return {Element} Returns the created line 20663 * @private 20664 */ 20665 TimeAxis.prototype._repaintMajorLine = function (x, width, orientation, className) { 20666 // reuse redundant line 20667 var line = this.dom.redundant.lines.shift(); 20668 if (!line) { 20669 // create vertical line 20670 line = document.createElement('div'); 20671 this.dom.background.appendChild(line); 20672 } 20673 this.dom.lines.push(line); 20674 20675 var props = this.props; 20676 if (orientation == 'top') { 20677 line.style.top = '0'; 20678 } else { 20679 line.style.top = this.body.domProps.top.height + 'px'; 20680 } 20681 line.style.left = x - props.majorLineWidth / 2 + 'px'; 20682 line.style.height = props.majorLineHeight + 'px'; 20683 line.style.width = width + 'px'; 20684 20685 line.className = 'vis-grid vis-vertical vis-major ' + className; 20686 20687 return line; 20688 }; 20689 20690 /** 20691 * Determine the size of text on the axis (both major and minor axis). 20692 * The size is calculated only once and then cached in this.props. 20693 * @private 20694 */ 20695 TimeAxis.prototype._calculateCharSize = function () { 20696 // Note: We calculate char size with every redraw. Size may change, for 20697 // example when any of the timelines parents had display:none for example. 20698 20699 // determine the char width and height on the minor axis 20700 if (!this.dom.measureCharMinor) { 20701 this.dom.measureCharMinor = document.createElement('DIV'); 20702 this.dom.measureCharMinor.className = 'vis-text vis-minor vis-measure'; 20703 this.dom.measureCharMinor.style.position = 'absolute'; 20704 20705 this.dom.measureCharMinor.appendChild(document.createTextNode('0')); 20706 this.dom.foreground.appendChild(this.dom.measureCharMinor); 20707 } 20708 this.props.minorCharHeight = this.dom.measureCharMinor.clientHeight; 20709 this.props.minorCharWidth = this.dom.measureCharMinor.clientWidth; 20710 20711 // determine the char width and height on the major axis 20712 if (!this.dom.measureCharMajor) { 20713 this.dom.measureCharMajor = document.createElement('DIV'); 20714 this.dom.measureCharMajor.className = 'vis-text vis-major vis-measure'; 20715 this.dom.measureCharMajor.style.position = 'absolute'; 20716 20717 this.dom.measureCharMajor.appendChild(document.createTextNode('0')); 20718 this.dom.foreground.appendChild(this.dom.measureCharMajor); 20719 } 20720 this.props.majorCharHeight = this.dom.measureCharMajor.clientHeight; 20721 this.props.majorCharWidth = this.dom.measureCharMajor.clientWidth; 20722 }; 20723 20724 module.exports = TimeAxis; 20725 20726/***/ }, 20727/* 40 */ 20728/***/ function(module, exports, __webpack_require__) { 20729 20730 'use strict'; 20731 20732 var keycharm = __webpack_require__(41); 20733 var Emitter = __webpack_require__(12); 20734 var Hammer = __webpack_require__(20); 20735 var util = __webpack_require__(1); 20736 20737 /** 20738 * Turn an element into an clickToUse element. 20739 * When not active, the element has a transparent overlay. When the overlay is 20740 * clicked, the mode is changed to active. 20741 * When active, the element is displayed with a blue border around it, and 20742 * the interactive contents of the element can be used. When clicked outside 20743 * the element, the elements mode is changed to inactive. 20744 * @param {Element} container 20745 * @constructor 20746 */ 20747 function Activator(container) { 20748 this.active = false; 20749 20750 this.dom = { 20751 container: container 20752 }; 20753 20754 this.dom.overlay = document.createElement('div'); 20755 this.dom.overlay.className = 'vis-overlay'; 20756 20757 this.dom.container.appendChild(this.dom.overlay); 20758 20759 this.hammer = Hammer(this.dom.overlay); 20760 this.hammer.on('tap', this._onTapOverlay.bind(this)); 20761 20762 // block all touch events (except tap) 20763 var me = this; 20764 var events = ['tap', 'doubletap', 'press', 'pinch', 'pan', 'panstart', 'panmove', 'panend']; 20765 events.forEach(function (event) { 20766 me.hammer.on(event, function (event) { 20767 event.stopPropagation(); 20768 }); 20769 }); 20770 20771 // attach a click event to the window, in order to deactivate when clicking outside the timeline 20772 if (document && document.body) { 20773 this.onClick = function (event) { 20774 if (!_hasParent(event.target, container)) { 20775 me.deactivate(); 20776 } 20777 }; 20778 document.body.addEventListener('click', this.onClick); 20779 } 20780 20781 if (this.keycharm !== undefined) { 20782 this.keycharm.destroy(); 20783 } 20784 this.keycharm = keycharm(); 20785 20786 // keycharm listener only bounded when active) 20787 this.escListener = this.deactivate.bind(this); 20788 } 20789 20790 // turn into an event emitter 20791 Emitter(Activator.prototype); 20792 20793 // The currently active activator 20794 Activator.current = null; 20795 20796 /** 20797 * Destroy the activator. Cleans up all created DOM and event listeners 20798 */ 20799 Activator.prototype.destroy = function () { 20800 this.deactivate(); 20801 20802 // remove dom 20803 this.dom.overlay.parentNode.removeChild(this.dom.overlay); 20804 20805 // remove global event listener 20806 if (this.onClick) { 20807 document.body.removeEventListener('click', this.onClick); 20808 } 20809 20810 // cleanup hammer instances 20811 this.hammer.destroy(); 20812 this.hammer = null; 20813 // FIXME: cleaning up hammer instances doesn't work (Timeline not removed from memory) 20814 }; 20815 20816 /** 20817 * Activate the element 20818 * Overlay is hidden, element is decorated with a blue shadow border 20819 */ 20820 Activator.prototype.activate = function () { 20821 // we allow only one active activator at a time 20822 if (Activator.current) { 20823 Activator.current.deactivate(); 20824 } 20825 Activator.current = this; 20826 20827 this.active = true; 20828 this.dom.overlay.style.display = 'none'; 20829 util.addClassName(this.dom.container, 'vis-active'); 20830 20831 this.emit('change'); 20832 this.emit('activate'); 20833 20834 // ugly hack: bind ESC after emitting the events, as the Network rebinds all 20835 // keyboard events on a 'change' event 20836 this.keycharm.bind('esc', this.escListener); 20837 }; 20838 20839 /** 20840 * Deactivate the element 20841 * Overlay is displayed on top of the element 20842 */ 20843 Activator.prototype.deactivate = function () { 20844 this.active = false; 20845 this.dom.overlay.style.display = ''; 20846 util.removeClassName(this.dom.container, 'vis-active'); 20847 this.keycharm.unbind('esc', this.escListener); 20848 20849 this.emit('change'); 20850 this.emit('deactivate'); 20851 }; 20852 20853 /** 20854 * Handle a tap event: activate the container 20855 * @param event 20856 * @private 20857 */ 20858 Activator.prototype._onTapOverlay = function (event) { 20859 // activate the container 20860 this.activate(); 20861 event.stopPropagation(); 20862 }; 20863 20864 /** 20865 * Test whether the element has the requested parent element somewhere in 20866 * its chain of parent nodes. 20867 * @param {HTMLElement} element 20868 * @param {HTMLElement} parent 20869 * @returns {boolean} Returns true when the parent is found somewhere in the 20870 * chain of parent nodes. 20871 * @private 20872 */ 20873 function _hasParent(element, parent) { 20874 while (element) { 20875 if (element === parent) { 20876 return true; 20877 } 20878 element = element.parentNode; 20879 } 20880 return false; 20881 } 20882 20883 module.exports = Activator; 20884 20885/***/ }, 20886/* 41 */ 20887/***/ function(module, exports, __webpack_require__) { 20888 20889 var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;"use strict"; 20890 /** 20891 * Created by Alex on 11/6/2014. 20892 */ 20893 20894 // https://github.com/umdjs/umd/blob/master/returnExports.js#L40-L60 20895 // if the module has no dependencies, the above pattern can be simplified to 20896 (function (root, factory) { 20897 if (true) { 20898 // AMD. Register as an anonymous module. 20899 !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); 20900 } else if (typeof exports === 'object') { 20901 // Node. Does not work with strict CommonJS, but 20902 // only CommonJS-like environments that support module.exports, 20903 // like Node. 20904 module.exports = factory(); 20905 } else { 20906 // Browser globals (root is window) 20907 root.keycharm = factory(); 20908 } 20909 }(this, function () { 20910 20911 function keycharm(options) { 20912 var preventDefault = options && options.preventDefault || false; 20913 20914 var container = options && options.container || window; 20915 20916 var _exportFunctions = {}; 20917 var _bound = {keydown:{}, keyup:{}}; 20918 var _keys = {}; 20919 var i; 20920 20921 // a - z 20922 for (i = 97; i <= 122; i++) {_keys[String.fromCharCode(i)] = {code:65 + (i - 97), shift: false};} 20923 // A - Z 20924 for (i = 65; i <= 90; i++) {_keys[String.fromCharCode(i)] = {code:i, shift: true};} 20925 // 0 - 9 20926 for (i = 0; i <= 9; i++) {_keys['' + i] = {code:48 + i, shift: false};} 20927 // F1 - F12 20928 for (i = 1; i <= 12; i++) {_keys['F' + i] = {code:111 + i, shift: false};} 20929 // num0 - num9 20930 for (i = 0; i <= 9; i++) {_keys['num' + i] = {code:96 + i, shift: false};} 20931 20932 // numpad misc 20933 _keys['num*'] = {code:106, shift: false}; 20934 _keys['num+'] = {code:107, shift: false}; 20935 _keys['num-'] = {code:109, shift: false}; 20936 _keys['num/'] = {code:111, shift: false}; 20937 _keys['num.'] = {code:110, shift: false}; 20938 // arrows 20939 _keys['left'] = {code:37, shift: false}; 20940 _keys['up'] = {code:38, shift: false}; 20941 _keys['right'] = {code:39, shift: false}; 20942 _keys['down'] = {code:40, shift: false}; 20943 // extra keys 20944 _keys['space'] = {code:32, shift: false}; 20945 _keys['enter'] = {code:13, shift: false}; 20946 _keys['shift'] = {code:16, shift: undefined}; 20947 _keys['esc'] = {code:27, shift: false}; 20948 _keys['backspace'] = {code:8, shift: false}; 20949 _keys['tab'] = {code:9, shift: false}; 20950 _keys['ctrl'] = {code:17, shift: false}; 20951 _keys['alt'] = {code:18, shift: false}; 20952 _keys['delete'] = {code:46, shift: false}; 20953 _keys['pageup'] = {code:33, shift: false}; 20954 _keys['pagedown'] = {code:34, shift: false}; 20955 // symbols 20956 _keys['='] = {code:187, shift: false}; 20957 _keys['-'] = {code:189, shift: false}; 20958 _keys[']'] = {code:221, shift: false}; 20959 _keys['['] = {code:219, shift: false}; 20960 20961 20962 20963 var down = function(event) {handleEvent(event,'keydown');}; 20964 var up = function(event) {handleEvent(event,'keyup');}; 20965 20966 // handle the actualy bound key with the event 20967 var handleEvent = function(event,type) { 20968 if (_bound[type][event.keyCode] !== undefined) { 20969 var bound = _bound[type][event.keyCode]; 20970 for (var i = 0; i < bound.length; i++) { 20971 if (bound[i].shift === undefined) { 20972 bound[i].fn(event); 20973 } 20974 else if (bound[i].shift == true && event.shiftKey == true) { 20975 bound[i].fn(event); 20976 } 20977 else if (bound[i].shift == false && event.shiftKey == false) { 20978 bound[i].fn(event); 20979 } 20980 } 20981 20982 if (preventDefault == true) { 20983 event.preventDefault(); 20984 } 20985 } 20986 }; 20987 20988 // bind a key to a callback 20989 _exportFunctions.bind = function(key, callback, type) { 20990 if (type === undefined) { 20991 type = 'keydown'; 20992 } 20993 if (_keys[key] === undefined) { 20994 throw new Error("unsupported key: " + key); 20995 } 20996 if (_bound[type][_keys[key].code] === undefined) { 20997 _bound[type][_keys[key].code] = []; 20998 } 20999 _bound[type][_keys[key].code].push({fn:callback, shift:_keys[key].shift}); 21000 }; 21001 21002 21003 // bind all keys to a call back (demo purposes) 21004 _exportFunctions.bindAll = function(callback, type) { 21005 if (type === undefined) { 21006 type = 'keydown'; 21007 } 21008 for (var key in _keys) { 21009 if (_keys.hasOwnProperty(key)) { 21010 _exportFunctions.bind(key,callback,type); 21011 } 21012 } 21013 }; 21014 21015 // get the key label from an event 21016 _exportFunctions.getKey = function(event) { 21017 for (var key in _keys) { 21018 if (_keys.hasOwnProperty(key)) { 21019 if (event.shiftKey == true && _keys[key].shift == true && event.keyCode == _keys[key].code) { 21020 return key; 21021 } 21022 else if (event.shiftKey == false && _keys[key].shift == false && event.keyCode == _keys[key].code) { 21023 return key; 21024 } 21025 else if (event.keyCode == _keys[key].code && key == 'shift') { 21026 return key; 21027 } 21028 } 21029 } 21030 return "unknown key, currently not supported"; 21031 }; 21032 21033 // unbind either a specific callback from a key or all of them (by leaving callback undefined) 21034 _exportFunctions.unbind = function(key, callback, type) { 21035 if (type === undefined) { 21036 type = 'keydown'; 21037 } 21038 if (_keys[key] === undefined) { 21039 throw new Error("unsupported key: " + key); 21040 } 21041 if (callback !== undefined) { 21042 var newBindings = []; 21043 var bound = _bound[type][_keys[key].code]; 21044 if (bound !== undefined) { 21045 for (var i = 0; i < bound.length; i++) { 21046 if (!(bound[i].fn == callback && bound[i].shift == _keys[key].shift)) {
21047 newBindings.push(_bound[type][_keys[key].code][i]); 21048 } 21049 } 21050 } 21051 _bound[type][_keys[key].code] = newBindings; 21052 } 21053 else { 21054 _bound[type][_keys[key].code] = []; 21055 } 21056 }; 21057 21058 // reset all bound variables. 21059 _exportFunctions.reset = function() { 21060 _bound = {keydown:{}, keyup:{}}; 21061 }; 21062 21063 // unbind all listeners and reset all variables. 21064 _exportFunctions.destroy = function() { 21065 _bound = {keydown:{}, keyup:{}}; 21066 container.removeEventListener('keydown', down, true); 21067 container.removeEventListener('keyup', up, true); 21068 }; 21069 21070 // create listeners. 21071 container.addEventListener('keydown',down,true); 21072 container.addEventListener('keyup',up,true); 21073 21074 // return the public functions. 21075 return _exportFunctions; 21076 } 21077 21078 return keycharm; 21079 })); 21080 21081 21082 21083 21084/***/ }, 21085/* 42 */ 21086/***/ function(module, exports, __webpack_require__) { 21087 21088 'use strict'; 21089 21090 var Hammer = __webpack_require__(20); 21091 var util = __webpack_require__(1); 21092 var Component = __webpack_require__(26); 21093 var moment = __webpack_require__(2); 21094 var locales = __webpack_require__(43); 21095 21096 /** 21097 * A custom time bar 21098 * @param {{range: Range, dom: Object}} body 21099 * @param {Object} [options] Available parameters: 21100 * {number | string} id 21101 * {string} locales 21102 * {string} locale 21103 * @constructor CustomTime 21104 * @extends Component 21105 */ 21106 21107 function CustomTime(body, options) { 21108 this.body = body; 21109 21110 // default options 21111 this.defaultOptions = { 21112 moment: moment, 21113 locales: locales, 21114 locale: 'en', 21115 id: undefined, 21116 title: undefined 21117 }; 21118 this.options = util.extend({}, this.defaultOptions); 21119 21120 if (options && options.time) { 21121 this.customTime = options.time; 21122 } else { 21123 this.customTime = new Date(); 21124 } 21125 21126 this.eventParams = {}; // stores state parameters while dragging the bar 21127 21128 this.setOptions(options); 21129 21130 // create the DOM 21131 this._create(); 21132 } 21133 21134 CustomTime.prototype = new Component(); 21135 21136 /** 21137 * Set options for the component. Options will be merged in current options. 21138 * @param {Object} options Available parameters: 21139 * {number | string} id 21140 * {string} locales 21141 * {string} locale 21142 */ 21143 CustomTime.prototype.setOptions = function (options) { 21144 if (options) { 21145 // copy all options that we know 21146 util.selectiveExtend(['moment', 'locale', 'locales', 'id'], this.options, options); 21147 } 21148 }; 21149 21150 /** 21151 * Create the DOM for the custom time 21152 * @private 21153 */ 21154 CustomTime.prototype._create = function () { 21155 var bar = document.createElement('div'); 21156 bar['custom-time'] = this; 21157 bar.className = 'vis-custom-time ' + (this.options.id || ''); 21158 bar.style.position = 'absolute'; 21159 bar.style.top = '0px'; 21160 bar.style.height = '100%'; 21161 this.bar = bar; 21162 21163 var drag = document.createElement('div'); 21164 drag.style.position = 'relative'; 21165 drag.style.top = '0px'; 21166 drag.style.left = '-10px'; 21167 drag.style.height = '100%'; 21168 drag.style.width = '20px'; 21169 bar.appendChild(drag); 21170 21171 // attach event listeners 21172 this.hammer = new Hammer(drag); 21173 this.hammer.on('panstart', this._onDragStart.bind(this)); 21174 this.hammer.on('panmove', this._onDrag.bind(this)); 21175 this.hammer.on('panend', this._onDragEnd.bind(this)); 21176 this.hammer.get('pan').set({ threshold: 5, direction: 30 }); // 30 is ALL_DIRECTIONS in hammer. 21177 }; 21178 21179 /** 21180 * Destroy the CustomTime bar 21181 */ 21182 CustomTime.prototype.destroy = function () { 21183 this.hide(); 21184 21185 this.hammer.destroy(); 21186 this.hammer = null; 21187 21188 this.body = null; 21189 }; 21190 21191 /** 21192 * Repaint the component 21193 * @return {boolean} Returns true if the component is resized 21194 */ 21195 CustomTime.prototype.redraw = function () { 21196 var parent = this.body.dom.backgroundVertical; 21197 if (this.bar.parentNode != parent) { 21198 // attach to the dom 21199 if (this.bar.parentNode) { 21200 this.bar.parentNode.removeChild(this.bar); 21201 } 21202 parent.appendChild(this.bar); 21203 } 21204 21205 var x = this.body.util.toScreen(this.customTime); 21206 21207 var locale = this.options.locales[this.options.locale]; 21208 if (!locale) { 21209 if (!this.warned) { 21210 console.log('WARNING: options.locales[\'' + this.options.locale + '\'] not found. See http://visjs.org/docs/timeline.html#Localization'); 21211 this.warned = true; 21212 } 21213 locale = this.options.locales['en']; // fall back on english when not available 21214 } 21215 21216 var title = this.options.title; 21217 // To hide the title completely use empty string ''. 21218 if (title === undefined) { 21219 title = locale.time + ': ' + this.options.moment(this.customTime).format('dddd, MMMM Do YYYY, H:mm:ss'); 21220 title = title.charAt(0).toUpperCase() + title.substring(1); 21221 } 21222 21223 this.bar.style.left = x + 'px'; 21224 this.bar.title = title; 21225 21226 return false;
21227 }; 21228 21229 /** 21230 * Remove the CustomTime from the DOM 21231 */ 21232 CustomTime.prototype.hide = function () { 21233 // remove the line from the DOM 21234 if (this.bar.parentNode) { 21235 this.bar.parentNode.removeChild(this.bar); 21236 } 21237 }; 21238 21239 /** 21240 * Set custom time. 21241 * @param {Date | number | string} time 21242 */ 21243 CustomTime.prototype.setCustomTime = function (time) { 21244 this.customTime = util.convert(time, 'Date'); 21245 this.redraw(); 21246 }; 21247 21248 /** 21249 * Retrieve the current custom time. 21250 * @return {Date} customTime 21251 */ 21252 CustomTime.prototype.getCustomTime = function () { 21253 return new Date(this.customTime.valueOf()); 21254 }; 21255 21256 /** 21257 * Set custom title. 21258 * @param {Date | number | string} title 21259 */ 21260 CustomTime.prototype.setCustomTitle = function (title) { 21261 this.options.title = title; 21262 }; 21263 21264 /** 21265 * Start moving horizontally 21266 * @param {Event} event 21267 * @private 21268 */ 21269 CustomTime.prototype._onDragStart = function (event) { 21270 this.eventParams.dragging = true; 21271 this.eventParams.customTime = this.customTime; 21272 21273 event.stopPropagation(); 21274 }; 21275 21276 /** 21277 * Perform moving operating. 21278 * @param {Event} event 21279 * @private 21280 */ 21281 CustomTime.prototype._onDrag = function (event) { 21282 if (!this.eventParams.dragging) return; 21283 21284 var x = this.body.util.toScreen(this.eventParams.customTime) + event.deltaX; 21285 var time = this.body.util.toTime(x); 21286 21287 this.setCustomTime(time); 21288 21289 // fire a timechange event 21290 this.body.emitter.emit('timechange', { 21291 id: this.options.id, 21292 time: new Date(this.customTime.valueOf()) 21293 }); 21294 21295 event.stopPropagation(); 21296 }; 21297 21298 /** 21299 * Stop moving operating. 21300 * @param {Event} event 21301 * @private 21302 */ 21303 CustomTime.prototype._onDragEnd = function (event) { 21304 if (!this.eventParams.dragging) return; 21305 21306 // fire a timechanged event 21307 this.body.emitter.emit('timechanged', { 21308 id: this.options.id, 21309 time: new Date(this.customTime.valueOf()) 21310 }); 21311 21312 event.stopPropagation(); 21313 }; 21314 21315 /** 21316 * Find a custom time from an event target: 21317 * searches for the attribute 'custom-time' in the event target's element tree 21318 * @param {Event} event 21319 * @return {CustomTime | null} customTime 21320 */ 21321 CustomTime.customTimeFromTarget = function (event) { 21322 var target = event.target; 21323 while (target) { 21324 if (target.hasOwnProperty('custom-time')) { 21325 return target['custom-time']; 21326 } 21327 target = target.parentNode; 21328 } 21329 21330 return null; 21331 }; 21332 21333 module.exports = CustomTime; 21334 21335/***/ }, 21336/* 43 */ 21337/***/ function(module, exports) { 21338 21339 // English 21340 'use strict'; 21341 21342 exports['en'] = { 21343 current: 'current', 21344 time: 'time' 21345 }; 21346 exports['en_EN'] = exports['en']; 21347 exports['en_US'] = exports['en']; 21348 21349 // Dutch 21350 exports['nl'] = { 21351 current: 'huidige', 21352 time: 'tijd' 21353 }; 21354 exports['nl_NL'] = exports['nl']; 21355 exports['nl_BE'] = exports['nl']; 21356 21357/***/ }, 21358/* 44 */ 21359/***/ function(module, exports, __webpack_require__) { 21360 21361 'use strict'; 21362 21363 var util = __webpack_require__(1); 21364 var Component = __webpack_require__(26); 21365 var moment = __webpack_require__(2); 21366 var locales = __webpack_require__(43); 21367 21368 /** 21369 * A current time bar 21370 * @param {{range: Range, dom: Object, domProps: Object}} body 21371 * @param {Object} [options] Available parameters: 21372 * {Boolean} [showCurrentTime] 21373 * @constructor CurrentTime 21374 * @extends Component 21375 */ 21376 function CurrentTime(body, options) { 21377 this.body = body; 21378 21379 // default options 21380 this.defaultOptions = { 21381 showCurrentTime: true, 21382 21383 moment: moment, 21384 locales: locales, 21385 locale: 'en' 21386 }; 21387 this.options = util.extend({}, this.defaultOptions); 21388 this.offset = 0; 21389 21390 this._create(); 21391 21392 this.setOptions(options); 21393 } 21394 21395 CurrentTime.prototype = new Component(); 21396 21397 /** 21398 * Create the HTML DOM for the current time bar 21399 * @private 21400 */ 21401 CurrentTime.prototype._create = function () { 21402 var bar = document.createElement('div'); 21403 bar.className = 'vis-current-time'; 21404 bar.style.position = 'absolute'; 21405 bar.style.top = '0px'; 21406 bar.style.height = '100%'; 21407 21408 this.bar = bar; 21409 }; 21410 21411 /** 21412 * Destroy the CurrentTime bar 21413 */ 21414 CurrentTime.prototype.destroy = function () { 21415 this.options.showCurrentTime = false;
21416 this.redraw(); // will remove the bar from the DOM and stop refreshing 21417 21418 this.body = null; 21419 }; 21420 21421 /** 21422 * Set options for the component. Options will be merged in current options. 21423 * @param {Object} options Available parameters: 21424 * {boolean} [showCurrentTime] 21425 */ 21426 CurrentTime.prototype.setOptions = function (options) { 21427 if (options) { 21428 // copy all options that we know 21429 util.selectiveExtend(['showCurrentTime', 'moment', 'locale', 'locales'], this.options, options); 21430 } 21431 }; 21432 21433 /** 21434 * Repaint the component 21435 * @return {boolean} Returns true if the component is resized 21436 */ 21437 CurrentTime.prototype.redraw = function () { 21438 if (this.options.showCurrentTime) { 21439 var parent = this.body.dom.backgroundVertical; 21440 if (this.bar.parentNode != parent) { 21441 // attach to the dom 21442 if (this.bar.parentNode) { 21443 this.bar.parentNode.removeChild(this.bar); 21444 } 21445 parent.appendChild(this.bar); 21446 21447 this.start(); 21448 } 21449 21450 var now = this.options.moment(new Date().valueOf() + this.offset); 21451 var x = this.body.util.toScreen(now); 21452 21453 var locale = this.options.locales[this.options.locale]; 21454 if (!locale) { 21455 if (!this.warned) { 21456 console.log('WARNING: options.locales[\'' + this.options.locale + '\'] not found. See http://visjs.org/docs/timeline.html#Localization'); 21457 this.warned = true; 21458 } 21459 locale = this.options.locales['en']; // fall back on english when not available 21460 } 21461 var title = locale.current + ' ' + locale.time + ': ' + now.format('dddd, MMMM Do YYYY, H:mm:ss'); 21462 title = title.charAt(0).toUpperCase() + title.substring(1); 21463 21464 this.bar.style.left = x + 'px'; 21465 this.bar.title = title; 21466 } else { 21467 // remove the line from the DOM 21468 if (this.bar.parentNode) { 21469 this.bar.parentNode.removeChild(this.bar); 21470 } 21471 this.stop(); 21472 } 21473 21474 return false; 21475 }; 21476 21477 /** 21478 * Start auto refreshing the current time bar 21479 */ 21480 CurrentTime.prototype.start = function () { 21481 var me = this; 21482 21483 function update() { 21484 me.stop(); 21485 21486 // determine interval to refresh 21487 var scale = me.body.range.conversion(me.body.domProps.center.width).scale; 21488 var interval = 1 / scale / 10; 21489 if (interval < 30) interval = 30; 21490 if (interval > 1000) interval = 1000; 21491 21492 me.redraw(); 21493 21494 // start a renderTimer to adjust for the new time 21495 me.currentTimeTimer = setTimeout(update, interval); 21496 } 21497 21498 update(); 21499 }; 21500 21501 /** 21502 * Stop auto refreshing the current time bar 21503 */ 21504 CurrentTime.prototype.stop = function () { 21505 if (this.currentTimeTimer !== undefined) { 21506 clearTimeout(this.currentTimeTimer); 21507 delete this.currentTimeTimer; 21508 } 21509 }; 21510 21511 /** 21512 * Set a current time. This can be used for example to ensure that a client's 21513 * time is synchronized with a shared server time. 21514 * @param {Date | String | Number} time A Date, unix timestamp, or 21515 * ISO date string. 21516 */ 21517 CurrentTime.prototype.setCurrentTime = function (time) { 21518 var t = util.convert(time, 'Date').valueOf(); 21519 var now = new Date().valueOf(); 21520 this.offset = t - now; 21521 this.redraw(); 21522 }; 21523 21524 /** 21525 * Get the current time. 21526 * @return {Date} Returns the current time. 21527 */ 21528 CurrentTime.prototype.getCurrentTime = function () { 21529 return new Date(new Date().valueOf() + this.offset); 21530 }; 21531 21532 module.exports = CurrentTime; 21533 21534/***/ }, 21535/* 45 */ 21536/***/ function(module, exports, __webpack_require__) { 21537 21538 'use strict'; 21539 21540 Object.defineProperty(exports, '__esModule', { 21541 value: true 21542 }); 21543 21544 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 21545 21546 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 21547 21548 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 21549 21550 var _ColorPicker = __webpack_require__(46); 21551 21552 var _ColorPicker2 = _interopRequireDefault(_ColorPicker); 21553 21554 var util = __webpack_require__(1); 21555 21556 /** 21557 * The way this works is for all properties of this.possible options, you can supply the property name in any form to list the options. 21558 * Boolean options are recognised as Boolean 21559 * Number options should be written as array: [default value, min value, max value, stepsize] 21560 * Colors should be written as array: ['color', '#ffffff'] 21561 * Strings with should be written as array: [option1, option2, option3, ..] 21562 * 21563 * The options are matched with their counterparts in each of the modules and the values used in the configuration are 21564 * 21565 * @param parentModule | the location where parentModule.setOptions() can be called 21566 * @param defaultContainer | the default container of the module 21567 * @param configureOptions | the fully configured and predefined options set found in allOptions.js 21568 * @param pixelRatio | canvas pixel ratio 21569 */ 21570 21571 var Configurator = (function () { 21572 function Configurator(parentModule, defaultContainer, configureOptions) { 21573 var pixelRatio = arguments.length <= 3 || arguments[3] === undefined ? 1 : arguments[3]; 21574 21575 _classCallCheck(this, Configurator); 21576 21577 this.parent = parentModule; 21578 this.changedOptions = []; 21579 this.container = defaultContainer; 21580 this.allowCreation = false;
21581 21582 this.options = {}; 21583 this.initialized = false; 21584 this.popupCounter = 0; 21585 this.defaultOptions = { 21586 enabled: false, 21587 filter: true, 21588 container: undefined, 21589 showButton: true 21590 }; 21591 util.extend(this.options, this.defaultOptions); 21592 21593 this.configureOptions = configureOptions; 21594 this.moduleOptions = {}; 21595 this.domElements = []; 21596 this.popupDiv = {}; 21597 this.popupLimit = 5; 21598 this.popupHistory = {}; 21599 this.colorPicker = new _ColorPicker2['default'](pixelRatio); 21600 this.wrapper = undefined; 21601 } 21602 21603 _createClass(Configurator, [{ 21604 key: 'setOptions', 21605 21606 /** 21607 * refresh all options. 21608 * Because all modules parse their options by themselves, we just use their options. We copy them here. 21609 * 21610 * @param options 21611 */ 21612 value: function setOptions(options) { 21613 if (options !== undefined) { 21614 // reset the popup history because the indices may have been changed. 21615 this.popupHistory = {}; 21616 this._removePopup(); 21617 21618 var enabled = true; 21619 if (typeof options === 'string') { 21620 this.options.filter = options; 21621 } else if (options instanceof Array) { 21622 this.options.filter = options.join(); 21623 } else if (typeof options === 'object') { 21624 if (options.container !== undefined) { 21625 this.options.container = options.container; 21626 } 21627 if (options.filter !== undefined) { 21628 this.options.filter = options.filter; 21629 } 21630 if (options.showButton !== undefined) { 21631 this.options.showButton = options.showButton; 21632 } 21633 if (options.enabled !== undefined) { 21634 enabled = options.enabled; 21635 } 21636 } else if (typeof options === 'boolean') { 21637 this.options.filter = true; 21638 enabled = options; 21639 } else if (typeof options === 'function') { 21640 this.options.filter = options; 21641 enabled = true; 21642 } 21643 if (this.options.filter === false) { 21644 enabled = false; 21645 } 21646 21647 this.options.enabled = enabled; 21648 } 21649 this._clean(); 21650 } 21651 }, { 21652 key: 'setModuleOptions', 21653 value: function setModuleOptions(moduleOptions) { 21654 this.moduleOptions = moduleOptions; 21655 if (this.options.enabled === true) { 21656 this._clean(); 21657 if (this.options.container !== undefined) { 21658 this.container = this.options.container; 21659 } 21660 this._create(); 21661 } 21662 } 21663 }, { 21664 key: '_create', 21665 21666 /** 21667 * Create all DOM elements 21668 * @private 21669 */ 21670 value: function _create() { 21671 var _this = this; 21672 21673 this._clean(); 21674 this.changedOptions = []; 21675 21676 var filter = this.options.filter; 21677 var counter = 0; 21678 var show = false; 21679 for (var option in this.configureOptions) { 21680 if (this.configureOptions.hasOwnProperty(option)) { 21681 this.allowCreation = false; 21682 show = false; 21683 if (typeof filter === 'function') { 21684 show = filter(option, []); 21685 show = show || this._handleObject(this.configureOptions[option], [option], true); 21686 } else if (filter === true || filter.indexOf(option) !== -1) { 21687 show = true; 21688 } 21689 21690 if (show !== false) { 21691 this.allowCreation = true; 21692 21693 // linebreak between categories 21694 if (counter > 0) { 21695 this._makeItem([]); 21696 } 21697 // a header for the category 21698 this._makeHeader(option); 21699 21700 // get the suboptions 21701 this._handleObject(this.configureOptions[option], [option]); 21702 } 21703 counter++; 21704 } 21705 } 21706 21707 if (this.options.showButton === true) { 21708 (function () { 21709 var generateButton = document.createElement('div'); 21710 generateButton.className = 'vis-configuration vis-config-button'; 21711 generateButton.innerHTML = 'generate options'; 21712 generateButton.onclick = function () { 21713 _this._printOptions(); 21714 }; 21715 generateButton.onmouseover = function () { 21716 generateButton.className = 'vis-configuration vis-config-button hover'; 21717 }; 21718 generateButton.onmouseout = function () { 21719 generateButton.className = 'vis-configuration vis-config-button'; 21720 }; 21721 21722 _this.optionsContainer = document.createElement('div'); 21723 _this.optionsContainer.className = 'vis-configuration vis-config-option-container'; 21724 21725 _this.domElements.push(_this.optionsContainer); 21726 _this.domElements.push(generateButton); 21727 })(); 21728 } 21729 21730 this._push(); 21731 this.colorPicker.insertTo(this.container); 21732 } 21733 }, {
21734 key: '_push', 21735 21736 /** 21737 * draw all DOM elements on the screen 21738 * @private 21739 */ 21740 value: function _push() { 21741 this.wrapper = document.createElement('div'); 21742 this.wrapper.className = 'vis-configuration-wrapper'; 21743 this.container.appendChild(this.wrapper); 21744 for (var i = 0; i < this.domElements.length; i++) { 21745 this.wrapper.appendChild(this.domElements[i]); 21746 } 21747 21748 this._showPopupIfNeeded(); 21749 } 21750 }, { 21751 key: '_clean', 21752 21753 /** 21754 * delete all DOM elements 21755 * @private 21756 */ 21757 value: function _clean() { 21758 for (var i = 0; i < this.domElements.length; i++) { 21759 this.wrapper.removeChild(this.domElements[i]); 21760 } 21761 21762 if (this.wrapper !== undefined) { 21763 this.container.removeChild(this.wrapper); 21764 this.wrapper = undefined; 21765 } 21766 this.domElements = []; 21767 21768 this._removePopup(); 21769 } 21770 }, { 21771 key: '_getValue', 21772 21773 /** 21774 * get the value from the actualOptions if it exists 21775 * @param {array} path | where to look for the actual option 21776 * @returns {*} 21777 * @private 21778 */ 21779 value: function _getValue(path) { 21780 var base = this.moduleOptions; 21781 for (var i = 0; i < path.length; i++) { 21782 if (base[path[i]] !== undefined) { 21783 base = base[path[i]]; 21784 } else { 21785 base = undefined; 21786 break; 21787 } 21788 } 21789 return base; 21790 } 21791 }, { 21792 key: '_makeItem', 21793 21794 /** 21795 * all option elements are wrapped in an item 21796 * @param path 21797 * @param domElements 21798 * @private 21799 */ 21800 value: function _makeItem(path) { 21801 var _arguments = arguments, 21802 _this2 = this; 21803 21804 if (this.allowCreation === true) { 21805 var _len, domElements, _key; 21806 21807 var _ret2 = (function () { 21808 var item = document.createElement('div'); 21809 item.className = 'vis-configuration vis-config-item vis-config-s' + path.length; 21810 21811 for (_len = _arguments.length, domElements = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 21812 domElements[_key - 1] = _arguments[_key]; 21813 } 21814 21815 domElements.forEach(function (element) { 21816 item.appendChild(element); 21817 }); 21818 _this2.domElements.push(item); 21819 return { 21820 v: _this2.domElements.length 21821 }; 21822 })(); 21823 21824 if (typeof _ret2 === 'object') return _ret2.v; 21825 } 21826 return 0; 21827 } 21828 }, { 21829 key: '_makeHeader', 21830 21831 /** 21832 * header for major subjects 21833 * @param name 21834 * @private 21835 */ 21836 value: function _makeHeader(name) { 21837 var div = document.createElement('div'); 21838 div.className = 'vis-configuration vis-config-header'; 21839 div.innerHTML = name; 21840 this._makeItem([], div); 21841 } 21842 }, { 21843 key: '_makeLabel', 21844 21845 /** 21846 * make a label, if it is an object label, it gets different styling. 21847 * @param name 21848 * @param path 21849 * @param objectLabel 21850 * @returns {HTMLElement} 21851 * @private 21852 */ 21853 value: function _makeLabel(name, path) { 21854 var objectLabel = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 21855 21856 var div = document.createElement('div'); 21857 div.className = 'vis-configuration vis-config-label vis-config-s' + path.length; 21858 if (objectLabel === true) { 21859 div.innerHTML = '<i><b>' + name + ':</b></i>'; 21860 } else { 21861 div.innerHTML = name + ':'; 21862 } 21863 return div; 21864 } 21865 }, { 21866 key: '_makeDropdown', 21867 21868 /** 21869 * make a dropdown list for multiple possible string optoins 21870 * @param arr 21871 * @param value 21872 * @param path 21873 * @private 21874 */ 21875 value: function _makeDropdown(arr, value, path) { 21876 var select = document.createElement('select'); 21877 select.className = 'vis-configuration vis-config-select'; 21878 var selectedValue = 0; 21879 if (value !== undefined) { 21880 if (arr.indexOf(value) !== -1) { 21881 selectedValue = arr.indexOf(value); 21882 } 21883 } 21884 21885 for (var i = 0; i < arr.length; i++) { 21886 var option = document.createElement('option'); 21887 option.value = arr[i]; 21888 if (i === selectedValue) { 21889 option.selected = 'selected'; 21890 } 21891 option.innerHTML = arr[i]; 21892 select.appendChild(option); 21893 } 21894 21895 var me = this; 21896 select.onchange = function () { 21897 me._update(this.value, path); 21898 }; 21899 21900 var label = this._makeLabel(path[path.length - 1], path); 21901 this._makeItem(path, label, select); 21902 } 21903 }, {
21904 key: '_makeRange', 21905 21906 /** 21907 * make a range object for numeric options 21908 * @param arr 21909 * @param value 21910 * @param path 21911 * @private 21912 */ 21913 value: function _makeRange(arr, value, path) { 21914 var defaultValue = arr[0]; 21915 var min = arr[1]; 21916 var max = arr[2]; 21917 var step = arr[3]; 21918 var range = document.createElement('input'); 21919 range.className = 'vis-configuration vis-config-range'; 21920 try { 21921 range.type = 'range'; // not supported on IE9 21922 range.min = min; 21923 range.max = max; 21924 } catch (err) {} 21925 range.step = step; 21926 21927 // set up the popup settings in case they are needed. 21928 var popupString = ''; 21929 var popupValue = 0; 21930 21931 if (value !== undefined) { 21932 var factor = 1.20; 21933 if (value < 0 && value * factor < min) { 21934 range.min = Math.ceil(value * factor); 21935 popupValue = range.min; 21936 popupString = 'range increased'; 21937 } else if (value / factor < min) { 21938 range.min = Math.ceil(value / factor); 21939 popupValue = range.min; 21940 popupString = 'range increased'; 21941 } 21942 if (value * factor > max && max !== 1) { 21943 range.max = Math.ceil(value * factor); 21944 popupValue = range.max; 21945 popupString = 'range increased'; 21946 } 21947 range.value = value; 21948 } else { 21949 range.value = defaultValue; 21950 } 21951 21952 var input = document.createElement('input'); 21953 input.className = 'vis-configuration vis-config-rangeinput'; 21954 input.value = range.value; 21955 21956 var me = this; 21957 range.onchange = function () { 21958 input.value = this.value;me._update(Number(this.value), path); 21959 }; 21960 range.oninput = function () { 21961 input.value = this.value; 21962 }; 21963 21964 var label = this._makeLabel(path[path.length - 1], path); 21965 var itemIndex = this._makeItem(path, label, range, input); 21966 21967 // if a popup is needed AND it has not been shown for this value, show it. 21968 if (popupString !== '' && this.popupHistory[itemIndex] !== popupValue) { 21969 this.popupHistory[itemIndex] = popupValue; 21970 this._setupPopup(popupString, itemIndex); 21971 } 21972 } 21973 }, { 21974 key: '_setupPopup', 21975 21976 /** 21977 * prepare the popup 21978 * @param string 21979 * @param index 21980 * @private 21981 */ 21982 value: function _setupPopup(string, index) { 21983 var _this3 = this; 21984 21985 if (this.initialized === true && this.allowCreation === true && this.popupCounter < this.popupLimit) { 21986 var div = document.createElement('div'); 21987 div.id = 'vis-configuration-popup'; 21988 div.className = 'vis-configuration-popup'; 21989 div.innerHTML = string; 21990 div.onclick = function () { 21991 _this3._removePopup(); 21992 }; 21993 this.popupCounter += 1; 21994 this.popupDiv = { html: div, index: index }; 21995 } 21996 } 21997 }, { 21998 key: '_removePopup', 21999 22000 /** 22001 * remove the popup from the dom 22002 * @private 22003 */ 22004 value: function _removePopup() { 22005 if (this.popupDiv.html !== undefined) { 22006 this.popupDiv.html.parentNode.removeChild(this.popupDiv.html); 22007 clearTimeout(this.popupDiv.hideTimeout); 22008 clearTimeout(this.popupDiv.deleteTimeout); 22009 this.popupDiv = {}; 22010 } 22011 } 22012 }, { 22013 key: '_showPopupIfNeeded', 22014 22015 /** 22016 * Show the popup if it is needed. 22017 * @private 22018 */ 22019 value: function _showPopupIfNeeded() { 22020 var _this4 = this; 22021 22022 if (this.popupDiv.html !== undefined) { 22023 var correspondingElement = this.domElements[this.popupDiv.index]; 22024 var rect = correspondingElement.getBoundingClientRect(); 22025 this.popupDiv.html.style.left = rect.left + 'px'; 22026 this.popupDiv.html.style.top = rect.top - 30 + 'px'; // 30 is the height; 22027 document.body.appendChild(this.popupDiv.html); 22028 this.popupDiv.hideTimeout = setTimeout(function () { 22029 _this4.popupDiv.html.style.opacity = 0; 22030 }, 1500); 22031 this.popupDiv.deleteTimeout = setTimeout(function () { 22032 _this4._removePopup(); 22033 }, 1800); 22034 } 22035 } 22036 }, {
22037 key: '_makeCheckbox', 22038 22039 /** 22040 * make a checkbox for boolean options. 22041 * @param defaultValue 22042 * @param value 22043 * @param path 22044 * @private 22045 */ 22046 value: function _makeCheckbox(defaultValue, value, path) { 22047 var checkbox = document.createElement('input'); 22048 checkbox.type = 'checkbox'; 22049 checkbox.className = 'vis-configuration vis-config-checkbox'; 22050 checkbox.checked = defaultValue; 22051 if (value !== undefined) { 22052 checkbox.checked = value; 22053 if (value !== defaultValue) { 22054 if (typeof defaultValue === 'object') { 22055 if (value !== defaultValue.enabled) { 22056 this.changedOptions.push({ path: path, value: value }); 22057 } 22058 } else { 22059 this.changedOptions.push({ path: path, value: value }); 22060 } 22061 } 22062 } 22063 22064 var me = this; 22065 checkbox.onchange = function () { 22066 me._update(this.checked, path); 22067 }; 22068 22069 var label = this._makeLabel(path[path.length - 1], path); 22070 this._makeItem(path, label, checkbox); 22071 } 22072 }, { 22073 key: '_makeTextInput', 22074 22075 /** 22076 * make a text input field for string options. 22077 * @param defaultValue 22078 * @param value 22079 * @param path 22080 * @private 22081 */ 22082 value: function _makeTextInput(defaultValue, value, path) { 22083 var checkbox = document.createElement('input'); 22084 checkbox.type = 'text'; 22085 checkbox.className = 'vis-configuration vis-config-text'; 22086 checkbox.value = value; 22087 if (value !== defaultValue) { 22088 this.changedOptions.push({ path: path, value: value }); 22089 } 22090 22091 var me = this; 22092 checkbox.onchange = function () { 22093 me._update(this.value, path); 22094 }; 22095 22096 var label = this._makeLabel(path[path.length - 1], path); 22097 this._makeItem(path, label, checkbox); 22098 } 22099 }, { 22100 key: '_makeColorField', 22101 22102 /** 22103 * make a color field with a color picker for color fields 22104 * @param arr 22105 * @param value 22106 * @param path 22107 * @private 22108 */ 22109 value: function _makeColorField(arr, value, path) { 22110 var _this5 = this; 22111 22112 var defaultColor = arr[1]; 22113 var div = document.createElement('div'); 22114 value = value === undefined ? defaultColor : value; 22115 22116 if (value !== 'none') { 22117 div.className = 'vis-configuration vis-config-colorBlock'; 22118 div.style.backgroundColor = value; 22119 } else { 22120 div.className = 'vis-configuration vis-config-colorBlock none'; 22121 } 22122 22123 value = value === undefined ? defaultColor : value; 22124 div.onclick = function () { 22125 _this5._showColorPicker(value, div, path); 22126 }; 22127 22128 var label = this._makeLabel(path[path.length - 1], path); 22129 this._makeItem(path, label, div); 22130 } 22131 }, { 22132 key: '_showColorPicker', 22133 22134 /** 22135 * used by the color buttons to call the color picker. 22136 * @param event 22137 * @param value 22138 * @param div 22139 * @param path 22140 * @private 22141 */ 22142 value: function _showColorPicker(value, div, path) { 22143 var _this6 = this; 22144 22145 var rect = div.getBoundingClientRect(); 22146 var bodyRect = document.body.getBoundingClientRect(); 22147 var pickerX = rect.left + rect.width + 5; 22148 var pickerY = rect.top - bodyRect.top + rect.height + 2; 22149 this.colorPicker.show(pickerX, pickerY); 22150 this.colorPicker.setColor(value); 22151 this.colorPicker.setCallback(function (color) { 22152 var colorString = 'rgba(' + color.r + ',' + color.g + ',' + color.b + ',' + color.a + ')'; 22153 div.style.backgroundColor = colorString; 22154 _this6._update(colorString, path); 22155 }); 22156 } 22157 }, { 22158 key: '_handleObject', 22159 22160 /** 22161 * parse an object and draw the correct items 22162 * @param obj 22163 * @param path 22164 * @private 22165 */ 22166 value: function _handleObject(obj) { 22167 var path = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; 22168 var checkOnly = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 22169 22170 var show = false;
22171 var filter = this.options.filter; 22172 var visibleInSet = false; 22173 for (var subObj in obj) { 22174 if (obj.hasOwnProperty(subObj)) { 22175 show = true; 22176 var item = obj[subObj]; 22177 var newPath = util.copyAndExtendArray(path, subObj); 22178 if (typeof filter === 'function') { 22179 show = filter(subObj, path); 22180 22181 // if needed we must go deeper into the object. 22182 if (show === false) { 22183 if (!(item instanceof Array) && typeof item !== 'string' && typeof item !== 'boolean' && item instanceof Object) { 22184 this.allowCreation = false; 22185 show = this._handleObject(item, newPath, true); 22186 this.allowCreation = checkOnly === false; 22187 } 22188 } 22189 } 22190 22191 if (show !== false) { 22192 visibleInSet = true; 22193 var value = this._getValue(newPath); 22194 22195 if (item instanceof Array) { 22196 this._handleArray(item, value, newPath); 22197 } else if (typeof item === 'string') { 22198 this._makeTextInput(item, value, newPath); 22199 } else if (typeof item === 'boolean') { 22200 this._makeCheckbox(item, value, newPath); 22201 } else if (item instanceof Object) { 22202 // collapse the physics options that are not enabled 22203 var draw = true; 22204 if (path.indexOf('physics') !== -1) { 22205 if (this.moduleOptions.physics.solver !== subObj) { 22206 draw = false; 22207 } 22208 } 22209 22210 if (draw === true) { 22211 // initially collapse options with an disabled enabled option. 22212 if (item.enabled !== undefined) { 22213 var enabledPath = util.copyAndExtendArray(newPath, 'enabled'); 22214 var enabledValue = this._getValue(enabledPath); 22215 if (enabledValue === true) { 22216 var label = this._makeLabel(subObj, newPath, true); 22217 this._makeItem(newPath, label); 22218 visibleInSet = this._handleObject(item, newPath) || visibleInSet; 22219 } else { 22220 this._makeCheckbox(item, enabledValue, newPath); 22221 } 22222 } else { 22223 var label = this._makeLabel(subObj, newPath, true); 22224 this._makeItem(newPath, label); 22225 visibleInSet = this._handleObject(item, newPath) || visibleInSet; 22226 } 22227 } 22228 } else { 22229 console.error('dont know how to handle', item, subObj, newPath); 22230 } 22231 } 22232 } 22233 } 22234 return visibleInSet; 22235 } 22236 }, { 22237 key: '_handleArray', 22238 22239 /** 22240 * handle the array type of option 22241 * @param optionName 22242 * @param arr 22243 * @param value 22244 * @param path 22245 * @private 22246 */ 22247 value: function _handleArray(arr, value, path) { 22248 if (typeof arr[0] === 'string' && arr[0] === 'color') { 22249 this._makeColorField(arr, value, path); 22250 if (arr[1] !== value) { 22251 this.changedOptions.push({ path: path, value: value }); 22252 } 22253 } else if (typeof arr[0] === 'string') { 22254 this._makeDropdown(arr, value, path); 22255 if (arr[0] !== value) { 22256 this.changedOptions.push({ path: path, value: value }); 22257 } 22258 } else if (typeof arr[0] === 'number') { 22259 this._makeRange(arr, value, path); 22260 if (arr[0] !== value) { 22261 this.changedOptions.push({ path: path, value: Number(value) }); 22262 } 22263 } 22264 } 22265 }, { 22266 key: '_update', 22267 22268 /** 22269 * called to update the network with the new settings. 22270 * @param value 22271 * @param path 22272 * @private 22273 */ 22274 value: function _update(value, path) { 22275 var options = this._constructOptions(value, path); 22276 22277 if (this.parent.body && this.parent.body.emitter && this.parent.body.emitter.emit) { 22278 this.parent.body.emitter.emit('configChange', options); 22279 } 22280 this.initialized = true; 22281 this.parent.setOptions(options); 22282 } 22283 }, {
22284 key: '_constructOptions', 22285 value: function _constructOptions(value, path) { 22286 var optionsObj = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; 22287 22288 var pointer = optionsObj; 22289 22290 // when dropdown boxes can be string or boolean, we typecast it into correct types 22291 value = value === 'true' ? true : value; 22292 value = value === 'false' ? false : value; 22293 22294 for (var i = 0; i < path.length; i++) { 22295 if (path[i] !== 'global') { 22296 if (pointer[path[i]] === undefined) { 22297 pointer[path[i]] = {}; 22298 } 22299 if (i !== path.length - 1) { 22300 pointer = pointer[path[i]]; 22301 } else { 22302 pointer[path[i]] = value; 22303 } 22304 } 22305 } 22306 return optionsObj; 22307 } 22308 }, { 22309 key: '_printOptions', 22310 value: function _printOptions() { 22311 var options = this.getOptions(); 22312 this.optionsContainer.innerHTML = '<pre>var options = ' + JSON.stringify(options, null, 2) + '</pre>'; 22313 } 22314 }, { 22315 key: 'getOptions', 22316 value: function getOptions() { 22317 var options = {}; 22318 for (var i = 0; i < this.changedOptions.length; i++) { 22319 this._constructOptions(this.changedOptions[i].value, this.changedOptions[i].path, options); 22320 } 22321 return options; 22322 } 22323 }]); 22324 22325 return Configurator; 22326 })(); 22327 22328 exports['default'] = Configurator; 22329 module.exports = exports['default']; 22330 22331/***/ }, 22332/* 46 */ 22333/***/ function(module, exports, __webpack_require__) { 22334 22335 'use strict'; 22336 22337 Object.defineProperty(exports, '__esModule', { 22338 value: true 22339 }); 22340 22341 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 22342 22343 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 22344 22345 var Hammer = __webpack_require__(20); 22346 var hammerUtil = __webpack_require__(25); 22347 var util = __webpack_require__(1); 22348 22349 var ColorPicker = (function () { 22350 function ColorPicker() { 22351 var pixelRatio = arguments.length <= 0 || arguments[0] === undefined ? 1 : arguments[0]; 22352 22353 _classCallCheck(this, ColorPicker); 22354 22355 this.pixelRatio = pixelRatio; 22356 this.generated = false; 22357 this.centerCoordinates = { x: 289 / 2, y: 289 / 2 }; 22358 this.r = 289 * 0.49; 22359 this.color = { r: 255, g: 255, b: 255, a: 1.0 }; 22360 this.hueCircle = undefined; 22361 this.initialColor = { r: 255, g: 255, b: 255, a: 1.0 }; 22362 this.previousColor = undefined; 22363 this.applied = false; 22364 22365 // bound by 22366 this.updateCallback = function () {}; 22367 22368 // create all DOM elements 22369 this._create(); 22370 } 22371 22372 _createClass(ColorPicker, [{ 22373 key: 'insertTo', 22374 22375 /** 22376 * this inserts the colorPicker into a div from the DOM 22377 * @param container 22378 */ 22379 value: function insertTo(container) { 22380 if (this.hammer !== undefined) { 22381 this.hammer.destroy(); 22382 this.hammer = undefined; 22383 } 22384 this.container = container; 22385 this.container.appendChild(this.frame); 22386 this._bindHammer(); 22387 22388 this._setSize(); 22389 } 22390 }, { 22391 key: 'setCallback', 22392 22393 /** 22394 * the callback is executed on apply and save. Bind it to the application 22395 * @param callback 22396 */ 22397 value: function setCallback(callback) { 22398 if (typeof callback === 'function') { 22399 this.updateCallback = callback; 22400 } else { 22401 throw new Error('Function attempted to set as colorPicker callback is not a function.'); 22402 } 22403 } 22404 }, {
22405 key: '_isColorString', 22406 value: function _isColorString(color) { 22407 var htmlColors = { black: '#000000', navy: '#000080', darkblue: '#00008B', mediumblue: '#0000CD', blue: '#0000FF', darkgreen: '#006400', green: '#008000', teal: '#008080', darkcyan: '#008B8B', deepskyblue: '#00BFFF', darkturquoise: '#00CED1', mediumspringgreen: '#00FA9A', lime: '#00FF00', springgreen: '#00FF7F', aqua: '#00FFFF', cyan: '#00FFFF', midnightblue: '#191970', dodgerblue: '#1E90FF', lightseagreen: '#20B2AA', forestgreen: '#228B22', seagreen: '#2E8B57', darkslategray: '#2F4F4F', limegreen: '#32CD32', mediumseagreen: '#3CB371', turquoise: '#40E0D0', royalblue: '#4169E1', steelblue: '#4682B4', darkslateblue: '#483D8B', mediumturquoise: '#48D1CC', indigo: '#4B0082', darkolivegreen: '#556B2F', cadetblue: '#5F9EA0', cornflowerblue: '#6495ED', mediumaquamarine: '#66CDAA', dimgray: '#696969', slateblue: '#6A5ACD', olivedrab: '#6B8E23', slategray: '#708090', lightslategray: '#778899', mediumslateblue: '#7B68EE', lawngreen: '#7CFC00', chartreuse: '#7FFF00', aquamarine: '#7FFFD4', maroon: '#800000', purple: '#800080', olive: '#808000', gray: '#808080', skyblue: '#87CEEB', lightskyblue: '#87CEFA', blueviolet: '#8A2BE2', darkred: '#8B0000', darkmagenta: '#8B008B', saddlebrown: '#8B4513', darkseagreen: '#8FBC8F', lightgreen: '#90EE90', mediumpurple: '#9370D8', darkviolet: '#9400D3', palegreen: '#98FB98', darkorchid: '#9932CC', yellowgreen: '#9ACD32', sienna: '#A0522D', brown: '#A52A2A', darkgray: '#A9A9A9', lightblue: '#ADD8E6', greenyellow: '#ADFF2F', paleturquoise: '#AFEEEE', lightsteelblue: '#B0C4DE', powderblue: '#B0E0E6', firebrick: '#B22222', darkgoldenrod: '#B8860B', mediumorchid: '#BA55D3', rosybrown: '#BC8F8F', darkkhaki: '#BDB76B', silver: '#C0C0C0', mediumvioletred: '#C71585', indianred: '#CD5C5C', peru: '#CD853F', chocolate: '#D2691E', tan: '#D2B48C', lightgrey: '#D3D3D3', palevioletred: '#D87093', thistle: '#D8BFD8', orchid: '#DA70D6', goldenrod: '#DAA520', crimson: '#DC143C', gainsboro: '#DCDCDC', plum: '#DDA0DD', burlywood: '#DEB887', lightcyan: '#E0FFFF', lavender: '#E6E6FA', darksalmon: '#E9967A', violet: '#EE82EE', palegoldenrod: '#EEE8AA', lightcoral: '#F08080', khaki: '#F0E68C', aliceblue: '#F0F8FF', honeydew: '#F0FFF0', azure: '#F0FFFF', sandybrown: '#F4A460', wheat: '#F5DEB3', beige: '#F5F5DC', whitesmoke: '#F5F5F5', mintcream: '#F5FFFA', ghostwhite: '#F8F8FF', salmon: '#FA8072', antiquewhite: '#FAEBD7', linen: '#FAF0E6', lightgoldenrodyellow: '#FAFAD2', oldlace: '#FDF5E6', red: '#FF0000', fuchsia: '#FF00FF', magenta: '#FF00FF', deeppink: '#FF1493', orangered: '#FF4500', tomato: '#FF6347', hotpink: '#FF69B4', coral: '#FF7F50', darkorange: '#FF8C00', lightsalmon: '#FFA07A', orange: '#FFA500', lightpink: '#FFB6C1', pink: '#FFC0CB', gold: '#FFD700', peachpuff: '#FFDAB9', navajowhite: '#FFDEAD', moccasin: '#FFE4B5', bisque: '#FFE4C4', mistyrose: '#FFE4E1', blanchedalmond: '#FFEBCD', papayawhip: '#FFEFD5', lavenderblush: '#FFF0F5', seashell: '#FFF5EE', cornsilk: '#FFF8DC', lemonchiffon: '#FFFACD', floralwhite: '#FFFAF0', snow: '#FFFAFA', yellow: '#FFFF00', lightyellow: '#FFFFE0', ivory: '#FFFFF0', white: '#FFFFFF' }; 22408 if (typeof color === 'string') { 22409 return htmlColors[color]; 22410 } 22411 } 22412 }, { 22413 key: 'setColor', 22414 22415 /** 22416 * Set the color of the colorPicker 22417 * Supported formats: 22418 * 'red' --> HTML color string 22419 * '#ffffff' --> hex string 22420 * 'rbg(255,255,255)' --> rgb string 22421 * 'rgba(255,255,255,1.0)' --> rgba string 22422 * {r:255,g:255,b:255} --> rgb object 22423 * {r:255,g:255,b:255,a:1.0} --> rgba object 22424 * @param color 22425 * @param setInitial 22426 */ 22427 value: function setColor(color) { 22428 var setInitial = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; 22429 22430 if (color === 'none') { 22431 return; 22432 } 22433 22434 var rgba = undefined; 22435 22436 // if a html color shorthand is used, convert to hex 22437 var htmlColor = this._isColorString(color); 22438 if (htmlColor !== undefined) { 22439 color = htmlColor; 22440 } 22441 22442 // check format 22443 if (util.isString(color) === true) { 22444 if (util.isValidRGB(color) === true) { 22445 var rgbaArray = color.substr(4).substr(0, color.length - 5).split(','); 22446 rgba = { r: rgbaArray[0], g: rgbaArray[1], b: rgbaArray[2], a: 1.0 }; 22447 } else if (util.isValidRGBA(color) === true) { 22448 var rgbaArray = color.substr(5).substr(0, color.length - 6).split(','); 22449 rgba = { r: rgbaArray[0], g: rgbaArray[1], b: rgbaArray[2], a: rgbaArray[3] }; 22450 } else if (util.isValidHex(color) === true) { 22451 var rgbObj = util.hexToRGB(color); 22452 rgba = { r: rgbObj.r, g: rgbObj.g, b: rgbObj.b, a: 1.0 }; 22453 } 22454 } else { 22455 if (color instanceof Object) { 22456 if (color.r !== undefined && color.g !== undefined && color.b !== undefined) { 22457 var alpha = color.a !== undefined ? color.a : '1.0'; 22458 rgba = { r: color.r, g: color.g, b: color.b, a: alpha }; 22459 } 22460 } 22461 } 22462 22463 // set color 22464 if (rgba === undefined) { 22465 throw new Error('Unknown color passed to the colorPicker. Supported are strings: rgb, hex, rgba. Object: rgb ({r:r,g:g,b:b,[a:a]}). Supplied: ' + JSON.stringify(color)); 22466 } else { 22467 this._setColor(rgba, setInitial); 22468 } 22469 } 22470 }, {
22471 key: 'show', 22472 22473 /** 22474 * this shows the color picker at a location. The hue circle is constructed once and stored. 22475 * @param x 22476 * @param y 22477 */ 22478 value: function show(x, y) { 22479 this.applied = false; 22480 this.frame.style.display = 'block'; 22481 this.frame.style.top = y + 'px'; 22482 this.frame.style.left = x + 'px'; 22483 this._generateHueCircle(); 22484 } 22485 }, { 22486 key: '_hide', 22487 22488 // ------------------------------------------ PRIVATE ----------------------------- // 22489 22490 /** 22491 * Hide the picker. Is called by the cancel button. 22492 * Optional boolean to store the previous color for easy access later on. 22493 * @param storePrevious 22494 * @private 22495 */ 22496 value: function _hide() { 22497 var storePrevious = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; 22498 22499 // store the previous color for next time; 22500 if (storePrevious === true) { 22501 this.previousColor = util.extend({}, this.color); 22502 } 22503 22504 if (this.applied === true) { 22505 this.updateCallback(this.initialColor); 22506 } 22507 22508 this.frame.style.display = 'none'; 22509 } 22510 }, { 22511 key: '_save', 22512 22513 /** 22514 * bound to the save button. Saves and hides. 22515 * @private 22516 */ 22517 value: function _save() { 22518 this.updateCallback(this.color); 22519 this.applied = false; 22520 this._hide(); 22521 } 22522 }, { 22523 key: '_apply', 22524 22525 /** 22526 * Bound to apply button. Saves but does not close. Is undone by the cancel button. 22527 * @private 22528 */ 22529 value: function _apply() { 22530 this.applied = true; 22531 this.updateCallback(this.color); 22532 this._updatePicker(this.color); 22533 } 22534 }, { 22535 key: '_loadLast', 22536 22537 /** 22538 * load the color from the previous session. 22539 * @private 22540 */ 22541 value: function _loadLast() { 22542 if (this.previousColor !== undefined) { 22543 this.setColor(this.previousColor, false); 22544 } else { 22545 alert('There is no last color to load...'); 22546 } 22547 } 22548 }, { 22549 key: '_setColor', 22550 22551 /** 22552 * set the color, place the picker 22553 * @param rgba 22554 * @param setInitial 22555 * @private 22556 */ 22557 value: function _setColor(rgba) { 22558 var setInitial = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; 22559 22560 // store the initial color 22561 if (setInitial === true) { 22562 this.initialColor = util.extend({}, rgba); 22563 } 22564 22565 this.color = rgba; 22566 var hsv = util.RGBToHSV(rgba.r, rgba.g, rgba.b); 22567 22568 var angleConvert = 2 * Math.PI; 22569 var radius = this.r * hsv.s; 22570 var x = this.centerCoordinates.x + radius * Math.sin(angleConvert * hsv.h); 22571 var y = this.centerCoordinates.y + radius * Math.cos(angleConvert * hsv.h); 22572 22573 this.colorPickerSelector.style.left = x - 0.5 * this.colorPickerSelector.clientWidth + 'px'; 22574 this.colorPickerSelector.style.top = y - 0.5 * this.colorPickerSelector.clientHeight + 'px'; 22575 22576 this._updatePicker(rgba); 22577 } 22578 }, { 22579 key: '_setOpacity', 22580 22581 /** 22582 * bound to opacity control 22583 * @param value 22584 * @private 22585 */ 22586 value: function _setOpacity(value) { 22587 this.color.a = value / 100; 22588 this._updatePicker(this.color); 22589 } 22590 }, { 22591 key: '_setBrightness', 22592 22593 /** 22594 * bound to brightness control 22595 * @param value 22596 * @private 22597 */ 22598 value: function _setBrightness(value) { 22599 var hsv = util.RGBToHSV(this.color.r, this.color.g, this.color.b); 22600 hsv.v = value / 100; 22601 var rgba = util.HSVToRGB(hsv.h, hsv.s, hsv.v); 22602 rgba['a'] = this.color.a; 22603 this.color = rgba; 22604 this._updatePicker(); 22605 } 22606 }, { 22607 key: '_updatePicker', 22608 22609 /** 22610 * update the colorpicker. A black circle overlays the hue circle to mimic the brightness decreasing. 22611 * @param rgba 22612 * @private 22613 */ 22614 value: function _updatePicker() { 22615 var rgba = arguments.length <= 0 || arguments[0] === undefined ? this.color : arguments[0]; 22616 22617 var hsv = util.RGBToHSV(rgba.r, rgba.g, rgba.b); 22618 var ctx = this.colorPickerCanvas.getContext('2d'); 22619 if (this.pixelRation === undefined) { 22620 this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); 22621 } 22622 ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); 22623 22624 // clear the canvas 22625 var w = this.colorPickerCanvas.clientWidth; 22626 var h = this.colorPickerCanvas.clientHeight; 22627 ctx.clearRect(0, 0, w, h); 22628 22629 ctx.putImageData(this.hueCircle, 0, 0); 22630 ctx.fillStyle = 'rgba(0,0,0,' + (1 - hsv.v) + ')'; 22631 ctx.circle(this.centerCoordinates.x, this.centerCoordinates.y, this.r); 22632 ctx.fill(); 22633 22634 this.brightnessRange.value = 100 * hsv.v; 22635 this.opacityRange.value = 100 * rgba.a; 22636 22637 this.initialColorDiv.style.backgroundColor = 'rgba(' + this.initialColor.r + ',' + this.initialColor.g + ',' + this.initialColor.b + ',' + this.initialColor.a + ')'; 22638 this.newColorDiv.style.backgroundColor = 'rgba(' + this.color.r + ',' + this.color.g + ',' + this.color.b + ',' + this.color.a + ')'; 22639 } 22640 }, {
vendor: 5,055 bytes, lines 22641-22767
22641 key: '_setSize', 22642 22643 /** 22644 * used by create to set the size of the canvas. 22645 * @private 22646 */ 22647 value: function _setSize() { 22648 this.colorPickerCanvas.style.width = '100%'; 22649 this.colorPickerCanvas.style.height = '100%'; 22650 22651 this.colorPickerCanvas.width = 289 * this.pixelRatio; 22652 this.colorPickerCanvas.height = 289 * this.pixelRatio; 22653 } 22654 }, { 22655 key: '_create', 22656 22657 /** 22658 * create all dom elements 22659 * TODO: cleanup, lots of similar dom elements 22660 * @private 22661 */ 22662 value: function _create() { 22663 this.frame = document.createElement('div'); 22664 this.frame.className = 'vis-color-picker'; 22665 22666 this.colorPickerDiv = document.createElement('div'); 22667 this.colorPickerSelector = document.createElement('div'); 22668 this.colorPickerSelector.className = 'vis-selector'; 22669 this.colorPickerDiv.appendChild(this.colorPickerSelector); 22670 22671 this.colorPickerCanvas = document.createElement('canvas'); 22672 this.colorPickerDiv.appendChild(this.colorPickerCanvas); 22673 22674 if (!this.colorPickerCanvas.getContext) { 22675 var noCanvas = document.createElement('DIV'); 22676 noCanvas.style.color = 'red'; 22677 noCanvas.style.fontWeight = 'bold'; 22678 noCanvas.style.padding = '10px'; 22679 noCanvas.innerHTML = 'Error: your browser does not support HTML canvas'; 22680 this.colorPickerCanvas.appendChild(noCanvas); 22681 } else { 22682 var ctx = this.colorPickerCanvas.getContext('2d'); 22683 this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); 22684 22685 this.colorPickerCanvas.getContext('2d').setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); 22686 } 22687 22688 this.colorPickerDiv.className = 'vis-color'; 22689 22690 this.opacityDiv = document.createElement('div'); 22691 this.opacityDiv.className = 'vis-opacity'; 22692 22693 this.brightnessDiv = document.createElement('div'); 22694 this.brightnessDiv.className = 'vis-brightness'; 22695 22696 this.arrowDiv = document.createElement('div'); 22697 this.arrowDiv.className = 'vis-arrow'; 22698 22699 this.opacityRange = document.createElement('input'); 22700 try { 22701 this.opacityRange.type = 'range'; // Not supported on IE9 22702 this.opacityRange.min = '0'; 22703 this.opacityRange.max = '100'; 22704 } catch (err) {} 22705 this.opacityRange.value = '100'; 22706 this.opacityRange.className = 'vis-range'; 22707 22708 this.brightnessRange = document.createElement('input'); 22709 try { 22710 this.brightnessRange.type = 'range'; // Not supported on IE9 22711 this.brightnessRange.min = '0'; 22712 this.brightnessRange.max = '100'; 22713 } catch (err) {} 22714 this.brightnessRange.value = '100'; 22715 this.brightnessRange.className = 'vis-range'; 22716 22717 this.opacityDiv.appendChild(this.opacityRange); 22718 this.brightnessDiv.appendChild(this.brightnessRange); 22719 22720 var me = this; 22721 this.opacityRange.onchange = function () { 22722 me._setOpacity(this.value); 22723 }; 22724 this.opacityRange.oninput = function () { 22725 me._setOpacity(this.value); 22726 }; 22727 this.brightnessRange.onchange = function () { 22728 me._setBrightness(this.value); 22729 }; 22730 this.brightnessRange.oninput = function () { 22731 me._setBrightness(this.value); 22732 }; 22733 22734 this.brightnessLabel = document.createElement('div'); 22735 this.brightnessLabel.className = 'vis-label vis-brightness'; 22736 this.brightnessLabel.innerHTML = 'brightness:'; 22737 22738 this.opacityLabel = document.createElement('div'); 22739 this.opacityLabel.className = 'vis-label vis-opacity'; 22740 this.opacityLabel.innerHTML = 'opacity:'; 22741 22742 this.newColorDiv = document.createElement('div'); 22743 this.newColorDiv.className = 'vis-new-color'; 22744 this.newColorDiv.innerHTML = 'new'; 22745 22746 this.initialColorDiv = document.createElement('div'); 22747 this.initialColorDiv.className = 'vis-initial-color'; 22748 this.initialColorDiv.innerHTML = 'initial'; 22749 22750 this.cancelButton = document.createElement('div'); 22751 this.cancelButton.className = 'vis-button vis-cancel'; 22752 this.cancelButton.innerHTML = 'cancel'; 22753 this.cancelButton.onclick = this._hide.bind(this, false); 22754 22755 this.applyButton = document.createElement('div'); 22756 this.applyButton.className = 'vis-button vis-apply'; 22757 this.applyButton.innerHTML = 'apply'; 22758 this.applyButton.onclick = this._apply.bind(this); 22759 22760 this.saveButton = document.createElement('div'); 22761 this.saveButton.className = 'vis-button vis-save'; 22762 this.saveButton.innerHTML = 'save'; 22763 this.saveButton.onclick = this._save.bind(this); 22764 22765 this.loadButton = document.createElement('div'); 22766 this.loadButton.className = 'vis-button vis-load'; 22767 this.loadButton.innerHTML = 'load last';
22768 this.loadButton.onclick = this._loadLast.bind(this); 22769 22770 this.frame.appendChild(this.colorPickerDiv); 22771 this.frame.appendChild(this.arrowDiv); 22772 this.frame.appendChild(this.brightnessLabel); 22773 this.frame.appendChild(this.brightnessDiv); 22774 this.frame.appendChild(this.opacityLabel); 22775 this.frame.appendChild(this.opacityDiv); 22776 this.frame.appendChild(this.newColorDiv); 22777 this.frame.appendChild(this.initialColorDiv); 22778 22779 this.frame.appendChild(this.cancelButton); 22780 this.frame.appendChild(this.applyButton); 22781 this.frame.appendChild(this.saveButton); 22782 this.frame.appendChild(this.loadButton); 22783 } 22784 }, { 22785 key: '_bindHammer', 22786 22787 /** 22788 * bind hammer to the color picker 22789 * @private 22790 */ 22791 value: function _bindHammer() { 22792 var _this = this; 22793 22794 this.drag = {}; 22795 this.pinch = {}; 22796 this.hammer = new Hammer(this.colorPickerCanvas); 22797 this.hammer.get('pinch').set({ enable: true }); 22798 22799 hammerUtil.onTouch(this.hammer, function (event) { 22800 _this._moveSelector(event); 22801 }); 22802 this.hammer.on('tap', function (event) { 22803 _this._moveSelector(event); 22804 }); 22805 this.hammer.on('panstart', function (event) { 22806 _this._moveSelector(event); 22807 }); 22808 this.hammer.on('panmove', function (event) { 22809 _this._moveSelector(event); 22810 }); 22811 this.hammer.on('panend', function (event) { 22812 _this._moveSelector(event); 22813 }); 22814 } 22815 }, { 22816 key: '_generateHueCircle', 22817 22818 /** 22819 * generate the hue circle. This is relatively heavy (200ms) and is done only once on the first time it is shown. 22820 * @private 22821 */ 22822 value: function _generateHueCircle() { 22823 if (this.generated === false) { 22824 var ctx = this.colorPickerCanvas.getContext('2d'); 22825 if (this.pixelRation === undefined) { 22826 this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); 22827 } 22828 ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); 22829 22830 // clear the canvas 22831 var w = this.colorPickerCanvas.clientWidth; 22832 var h = this.colorPickerCanvas.clientHeight; 22833 ctx.clearRect(0, 0, w, h); 22834 22835 // draw hue circle 22836 var x = undefined, 22837 y = undefined, 22838 hue = undefined, 22839 sat = undefined; 22840 this.centerCoordinates = { x: w * 0.5, y: h * 0.5 }; 22841 this.r = 0.49 * w; 22842 var angleConvert = 2 * Math.PI / 360; 22843 var hfac = 1 / 360; 22844 var sfac = 1 / this.r; 22845 var rgb = undefined; 22846 for (hue = 0; hue < 360; hue++) { 22847 for (sat = 0; sat < this.r; sat++) { 22848 x = this.centerCoordinates.x + sat * Math.sin(angleConvert * hue); 22849 y = this.centerCoordinates.y + sat * Math.cos(angleConvert * hue); 22850 rgb = util.HSVToRGB(hue * hfac, sat * sfac, 1); 22851 ctx.fillStyle = 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')'; 22852 ctx.fillRect(x - 0.5, y - 0.5, 2, 2); 22853 } 22854 } 22855 ctx.strokeStyle = 'rgba(0,0,0,1)'; 22856 ctx.circle(this.centerCoordinates.x, this.centerCoordinates.y, this.r); 22857 ctx.stroke(); 22858 22859 this.hueCircle = ctx.getImageData(0, 0, w, h); 22860 } 22861 this.generated = true; 22862 } 22863 }, { 22864 key: '_moveSelector', 22865 22866 /** 22867 * move the selector. This is called by hammer functions. 22868 * 22869 * @param event 22870 * @private 22871 */ 22872 value: function _moveSelector(event) { 22873 var rect = this.colorPickerDiv.getBoundingClientRect(); 22874 var left = event.center.x - rect.left; 22875 var top = event.center.y - rect.top; 22876 22877 var centerY = 0.5 * this.colorPickerDiv.clientHeight; 22878 var centerX = 0.5 * this.colorPickerDiv.clientWidth; 22879 22880 var x = left - centerX; 22881 var y = top - centerY; 22882 22883 var angle = Math.atan2(x, y); 22884 var radius = 0.98 * Math.min(Math.sqrt(x * x + y * y), centerX); 22885 22886 var newTop = Math.cos(angle) * radius + centerY; 22887 var newLeft = Math.sin(angle) * radius + centerX; 22888 22889 this.colorPickerSelector.style.top = newTop - 0.5 * this.colorPickerSelector.clientHeight + 'px'; 22890 this.colorPickerSelector.style.left = newLeft - 0.5 * this.colorPickerSelector.clientWidth + 'px'; 22891 22892 // set color 22893 var h = angle / (2 * Math.PI); 22894 h = h < 0 ? h + 1 : h;
22895 var s = radius / this.r; 22896 var hsv = util.RGBToHSV(this.color.r, this.color.g, this.color.b); 22897 hsv.h = h; 22898 hsv.s = s; 22899 var rgba = util.HSVToRGB(hsv.h, hsv.s, hsv.v); 22900 rgba['a'] = this.color.a; 22901 this.color = rgba; 22902 22903 // update previews 22904 this.initialColorDiv.style.backgroundColor = 'rgba(' + this.initialColor.r + ',' + this.initialColor.g + ',' + this.initialColor.b + ',' + this.initialColor.a + ')'; 22905 this.newColorDiv.style.backgroundColor = 'rgba(' + this.color.r + ',' + this.color.g + ',' + this.color.b + ',' + this.color.a + ')'; 22906 } 22907 }]); 22908 22909 return ColorPicker; 22910 })(); 22911 22912 exports['default'] = ColorPicker; 22913 module.exports = exports['default']; 22914 22915/***/ }, 22916/* 47 */ 22917/***/ function(module, exports, __webpack_require__) { 22918 22919 'use strict'; 22920 22921 Object.defineProperty(exports, '__esModule', { 22922 value: true 22923 }); 22924 22925 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 22926 22927 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 22928 22929 var util = __webpack_require__(1); 22930 22931 var errorFound = false; 22932 var allOptions = undefined; 22933 var printStyle = 'background: #FFeeee; color: #dd0000'; 22934 /** 22935 * Used to validate options. 22936 */ 22937 22938 var Validator = (function () { 22939 function Validator() { 22940 _classCallCheck(this, Validator); 22941 } 22942 22943 _createClass(Validator, null, [{ 22944 key: 'validate', 22945 22946 /** 22947 * Main function to be called 22948 * @param options 22949 * @param subObject 22950 * @returns {boolean} 22951 */ 22952 value: function validate(options, referenceOptions, subObject) { 22953 errorFound = false; 22954 allOptions = referenceOptions; 22955 var usedOptions = referenceOptions; 22956 if (subObject !== undefined) { 22957 usedOptions = referenceOptions[subObject]; 22958 } 22959 Validator.parse(options, usedOptions, []); 22960 return errorFound; 22961 } 22962 }, { 22963 key: 'parse', 22964 22965 /** 22966 * Will traverse an object recursively and check every value 22967 * @param options 22968 * @param referenceOptions 22969 * @param path 22970 */ 22971 value: function parse(options, referenceOptions, path) { 22972 for (var option in options) { 22973 if (options.hasOwnProperty(option)) { 22974 Validator.check(option, options, referenceOptions, path); 22975 } 22976 } 22977 } 22978 }, { 22979 key: 'check', 22980 22981 /** 22982 * Check every value. If the value is an object, call the parse function on that object. 22983 * @param option 22984 * @param options 22985 * @param referenceOptions 22986 * @param path 22987 */ 22988 value: function check(option, options, referenceOptions, path) { 22989 if (referenceOptions[option] === undefined && referenceOptions.__any__ === undefined) { 22990 Validator.getSuggestion(option, referenceOptions, path); 22991 } else if (referenceOptions[option] === undefined && referenceOptions.__any__ !== undefined) { 22992 // __any__ is a wildcard. Any value is accepted and will be further analysed by reference. 22993 if (Validator.getType(options[option]) === 'object' && referenceOptions['__any__'].__type__ !== undefined) { 22994 // if the any subgroup is not a predefined object int he configurator we do not look deeper into the object. 22995 Validator.checkFields(option, options, referenceOptions, '__any__', referenceOptions['__any__'].__type__, path); 22996 } else { 22997 Validator.checkFields(option, options, referenceOptions, '__any__', referenceOptions['__any__'], path); 22998 } 22999 } else { 23000 // Since all options in the reference are objects, we can check whether they are supposed to be object to look for the __type__ field. 23001 if (referenceOptions[option].__type__ !== undefined) { 23002 // if this should be an object, we check if the correct type has been supplied to account for shorthand options. 23003 Validator.checkFields(option, options, referenceOptions, option, referenceOptions[option].__type__, path); 23004 } else { 23005 Validator.checkFields(option, options, referenceOptions, option, referenceOptions[option], path); 23006 } 23007 } 23008 } 23009 }, {
23010 key: 'checkFields', 23011 23012 /** 23013 * 23014 * @param {String} option | the option property 23015 * @param {Object} options | The supplied options object 23016 * @param {Object} referenceOptions | The reference options containing all options and their allowed formats 23017 * @param {String} referenceOption | Usually this is the same as option, except when handling an __any__ tag. 23018 * @param {String} refOptionType | This is the type object from the reference options 23019 * @param {Array} path | where in the object is the option 23020 */ 23021 value: function checkFields(option, options, referenceOptions, referenceOption, refOptionObj, path) { 23022 var optionType = Validator.getType(options[option]); 23023 var refOptionType = refOptionObj[optionType]; 23024 if (refOptionType !== undefined) { 23025 // if the type is correct, we check if it is supposed to be one of a few select values 23026 if (Validator.getType(refOptionType) === 'array') { 23027 if (refOptionType.indexOf(options[option]) === -1) { 23028 console.log('%cInvalid option detected in "' + option + '".' + ' Allowed values are:' + Validator.print(refOptionType) + ' not "' + options[option] + '". ' + Validator.printLocation(path, option), printStyle); 23029 errorFound = true; 23030 } else if (optionType === 'object' && referenceOption !== '__any__') { 23031 path = util.copyAndExtendArray(path, option); 23032 Validator.parse(options[option], referenceOptions[referenceOption], path); 23033 } 23034 } else if (optionType === 'object' && referenceOption !== '__any__') { 23035 path = util.copyAndExtendArray(path, option); 23036 Validator.parse(options[option], referenceOptions[referenceOption], path); 23037 } 23038 } else if (refOptionObj['any'] === undefined) { 23039 // type of the field is incorrect and the field cannot be any 23040 console.log('%cInvalid type received for "' + option + '". Expected: ' + Validator.print(Object.keys(refOptionObj)) + '. Received [' + optionType + '] "' + options[option] + '"' + Validator.printLocation(path, option), printStyle); 23041 errorFound = true; 23042 } 23043 } 23044 }, { 23045 key: 'getType', 23046 value: function getType(object) { 23047 var type = typeof object; 23048 23049 if (type === 'object') { 23050 if (object === null) { 23051 return 'null'; 23052 } 23053 if (object instanceof Boolean) { 23054 return 'boolean'; 23055 } 23056 if (object instanceof Number) { 23057 return 'number'; 23058 } 23059 if (object instanceof String) { 23060 return 'string'; 23061 } 23062 if (Array.isArray(object)) { 23063 return 'array'; 23064 } 23065 if (object instanceof Date) { 23066 return 'date'; 23067 } 23068 if (object.nodeType !== undefined) { 23069 return 'dom'; 23070 } 23071 if (object._isAMomentObject === true) { 23072 return 'moment'; 23073 } 23074 return 'object'; 23075 } else if (type === 'number') { 23076 return 'number'; 23077 } else if (type === 'boolean') { 23078 return 'boolean'; 23079 } else if (type === 'string') { 23080 return 'string'; 23081 } else if (type === undefined) { 23082 return 'undefined'; 23083 } 23084 return type; 23085 } 23086 }, { 23087 key: 'getSuggestion', 23088 value: function getSuggestion(option, options, path) { 23089 var localSearch = Validator.findInOptions(option, options, path, false); 23090 var globalSearch = Validator.findInOptions(option, allOptions, [], true); 23091 23092 var localSearchThreshold = 8; 23093 var globalSearchThreshold = 4; 23094 23095 if (localSearch.indexMatch !== undefined) { 23096 console.log('%cUnknown option detected: "' + option + '" in ' + Validator.printLocation(localSearch.path, option, '') + 'Perhaps it was incomplete? Did you mean: "' + localSearch.indexMatch + '"?\n\n', printStyle); 23097 } else if (globalSearch.distance <= globalSearchThreshold && localSearch.distance > globalSearch.distance) { 23098 console.log('%cUnknown option detected: "' + option + '" in ' + Validator.printLocation(localSearch.path, option, '') + 'Perhaps it was misplaced? Matching option found at: ' + Validator.printLocation(globalSearch.path, globalSearch.closestMatch, ''), printStyle); 23099 } else if (localSearch.distance <= localSearchThreshold) { 23100 console.log('%cUnknown option detected: "' + option + '". Did you mean "' + localSearch.closestMatch + '"?' + Validator.printLocation(localSearch.path, option), printStyle); 23101 } else { 23102 console.log('%cUnknown option detected: "' + option + '". Did you mean one of these: ' + Validator.print(Object.keys(options)) + Validator.printLocation(path, option), printStyle); 23103 } 23104 23105 errorFound = true; 23106 } 23107 }, {
23108 key: 'findInOptions', 23109 23110 /** 23111 * traverse the options in search for a match. 23112 * @param option 23113 * @param options 23114 * @param path 23115 * @param recursive 23116 * @returns {{closestMatch: string, path: Array, distance: number}} 23117 */ 23118 value: function findInOptions(option, options, path) { 23119 var recursive = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3]; 23120 23121 var min = 1e9; 23122 var closestMatch = ''; 23123 var closestMatchPath = []; 23124 var lowerCaseOption = option.toLowerCase(); 23125 var indexMatch = undefined; 23126 for (var op in options) { 23127 var distance = undefined; 23128 if (options[op].__type__ !== undefined && recursive === true) { 23129 var result = Validator.findInOptions(option, options[op], util.copyAndExtendArray(path, op)); 23130 if (min > result.distance) { 23131 closestMatch = result.closestMatch; 23132 closestMatchPath = result.path; 23133 min = result.distance; 23134 indexMatch = result.indexMatch; 23135 } 23136 } else { 23137 if (op.toLowerCase().indexOf(lowerCaseOption) !== -1) { 23138 indexMatch = op; 23139 } 23140 distance = Validator.levenshteinDistance(option, op); 23141 if (min > distance) { 23142 closestMatch = op; 23143 closestMatchPath = util.copyArray(path); 23144 min = distance; 23145 } 23146 } 23147 } 23148 return { closestMatch: closestMatch, path: closestMatchPath, distance: min, indexMatch: indexMatch }; 23149 } 23150 }, { 23151 key: 'printLocation', 23152 value: function printLocation(path, option) { 23153 var prefix = arguments.length <= 2 || arguments[2] === undefined ? 'Problem value found at: \n' : arguments[2]; 23154 23155 var str = '\n\n' + prefix + 'options = {\n'; 23156 for (var i = 0; i < path.length; i++) { 23157 for (var j = 0; j < i + 1; j++) { 23158 str += ' '; 23159 } 23160 str += path[i] + ': {\n'; 23161 } 23162 for (var j = 0; j < path.length + 1; j++) { 23163 str += ' '; 23164 } 23165 str += option + '\n'; 23166 for (var i = 0; i < path.length + 1; i++) { 23167 for (var j = 0; j < path.length - i; j++) { 23168 str += ' '; 23169 } 23170 str += '}\n'; 23171 } 23172 return str + '\n\n'; 23173 } 23174 }, { 23175 key: 'print', 23176 value: function print(options) { 23177 return JSON.stringify(options).replace(/(\")|(\[)|(\])|(,"__type__")/g, '').replace(/(\,)/g, ', '); 23178 } 23179 }, { 23180 key: 'levenshteinDistance', 23181 23182 // Compute the edit distance between the two given strings 23183 // http://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#JavaScript 23184 /* 23185 Copyright (c) 2011 Andrei Mackenzie 23186 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 23187 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 23188 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23189 */ 23190 value: function levenshteinDistance(a, b) { 23191 if (a.length === 0) return b.length; 23192 if (b.length === 0) return a.length; 23193 23194 var matrix = []; 23195 23196 // increment along the first column of each row 23197 var i; 23198 for (i = 0; i <= b.length; i++) { 23199 matrix[i] = [i]; 23200 } 23201 23202 // increment each column in the first row 23203 var j; 23204 for (j = 0; j <= a.length; j++) { 23205 matrix[0][j] = j; 23206 } 23207 23208 // Fill in the rest of the matrix 23209 for (i = 1; i <= b.length; i++) { 23210 for (j = 1; j <= a.length; j++) { 23211 if (b.charAt(i - 1) == a.charAt(j - 1)) { 23212 matrix[i][j] = matrix[i - 1][j - 1]; 23213 } else { 23214 matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution 23215 Math.min(matrix[i][j - 1] + 1, // insertion 23216 matrix[i - 1][j] + 1)); // deletion 23217 } 23218 } 23219 } 23220 23221 return matrix[b.length][a.length]; 23222 } 23223 }]); 23224 23225 return Validator; 23226 })(); 23227 23228 exports['default'] = Validator; 23229 exports.printStyle = printStyle; 23230 23231/***/ }, 23232/* 48 */
23233/***/ function(module, exports) { 23234 23235 /** 23236 * This object contains all possible options. It will check if the types are correct, if required if the option is one 23237 * of the allowed values. 23238 * 23239 * __any__ means that the name of the property does not matter. 23240 * __type__ is a required field for all objects and contains the allowed types of all objects 23241 */ 23242 'use strict'; 23243 23244 Object.defineProperty(exports, '__esModule', { 23245 value: true 23246 }); 23247 var string = 'string'; 23248 var boolean = 'boolean'; 23249 var number = 'number'; 23250 var array = 'array'; 23251 var date = 'date'; 23252 var object = 'object'; // should only be in a __type__ property 23253 var dom = 'dom'; 23254 var moment = 'moment'; 23255 var any = 'any'; 23256 23257 var allOptions = { 23258 configure: { 23259 enabled: { boolean: boolean }, 23260 filter: { boolean: boolean, 'function': 'function' }, 23261 container: { dom: dom }, 23262 __type__: { object: object, boolean: boolean, 'function': 'function' } 23263 }, 23264 23265 //globals : 23266 align: { string: string }, 23267 autoResize: { boolean: boolean }, 23268 throttleRedraw: { number: number }, 23269 clickToUse: { boolean: boolean }, 23270 dataAttributes: { string: string, array: array }, 23271 editable: { 23272 add: { boolean: boolean, 'undefined': 'undefined' }, 23273 remove: { boolean: boolean, 'undefined': 'undefined' }, 23274 updateGroup: { boolean: boolean, 'undefined': 'undefined' }, 23275 updateTime: { boolean: boolean, 'undefined': 'undefined' }, 23276 __type__: { boolean: boolean, object: object } 23277 }, 23278 end: { number: number, date: date, string: string, moment: moment }, 23279 format: { 23280 minorLabels: { 23281 millisecond: { string: string, 'undefined': 'undefined' }, 23282 second: { string: string, 'undefined': 'undefined' }, 23283 minute: { string: string, 'undefined': 'undefined' }, 23284 hour: { string: string, 'undefined': 'undefined' },
23285 weekday: { string: string, 'undefined': 'undefined' }, 23286 day: { string: string, 'undefined': 'undefined' }, 23287 month: { string: string, 'undefined': 'undefined' }, 23288 year: { string: string, 'undefined': 'undefined' }, 23289 __type__: { object: object } 23290 }, 23291 majorLabels: { 23292 millisecond: { string: string, 'undefined': 'undefined' }, 23293 second: { string: string, 'undefined': 'undefined' }, 23294 minute: { string: string, 'undefined': 'undefined' }, 23295 hour: { string: string, 'undefined': 'undefined' }, 23296 weekday: { string: string, 'undefined': 'undefined' }, 23297 day: { string: string, 'undefined': 'undefined' }, 23298 month: { string: string, 'undefined': 'undefined' }, 23299 year: { string: string, 'undefined': 'undefined' }, 23300 __type__: { object: object } 23301 }, 23302 __type__: { object: object } 23303 }, 23304 moment: { 'function': 'function' }, 23305 groupOrder: { string: string, 'function': 'function' }, 23306 groupEditable: { 23307 add: { boolean: boolean, 'undefined': 'undefined' }, 23308 remove: { boolean: boolean, 'undefined': 'undefined' }, 23309 order: { boolean: boolean, 'undefined': 'undefined' }, 23310 __type__: { boolean: boolean, object: object } 23311 }, 23312 groupOrderSwap: { 'function': 'function' }, 23313 height: { string: string, number: number }, 23314 hiddenDates: { 23315 start: { date: date, number: number, string: string, moment: moment }, 23316 end: { date: date, number: number, string: string, moment: moment }, 23317 repeat: { string: string }, 23318 __type__: { object: object, array: array } 23319 }, 23320 locale: { string: string }, 23321 locales: { 23322 __type__: { object: object } 23323 }, 23324 margin: { 23325 axis: { number: number }, 23326 item: { 23327 horizontal: { number: number, 'undefined': 'undefined' }, 23328 vertical: { number: number, 'undefined': 'undefined' }, 23329 __type__: { object: object, number: number } 23330 }, 23331 __type__: { object: object, number: number } 23332 }, 23333 max: { date: date, number: number, string: string, moment: moment }, 23334 maxHeight: { number: number, string: string }, 23335 min: { date: date, number: number, string: string, moment: moment }, 23336 minHeight: { number: number, string: string }, 23337 moveable: { boolean: boolean }, 23338 multiselect: { boolean: boolean }, 23339 onAdd: { 'function': 'function' }, 23340 onUpdate: { 'function': 'function' }, 23341 onMove: { 'function': 'function' }, 23342 onMoving: { 'function': 'function' }, 23343 onRemove: { 'function': 'function' }, 23344 onAddGroup: { 'function': 'function' }, 23345 onMoveGroup: { 'function': 'function' }, 23346 onRemoveGroup: { 'function': 'function' }, 23347 order: { 'function': 'function' }, 23348 orientation: { 23349 axis: { string: string, 'undefined': 'undefined' }, 23350 item: { string: string, 'undefined': 'undefined' }, 23351 __type__: { string: string, object: object } 23352 }, 23353 selectable: { boolean: boolean }, 23354 showCurrentTime: { boolean: boolean }, 23355 showMajorLabels: { boolean: boolean }, 23356 showMinorLabels: { boolean: boolean }, 23357 stack: { boolean: boolean }, 23358 snap: { 'function': 'function', 'null': 'null' }, 23359 start: { date: date, number: number, string: string, moment: moment }, 23360 template: { 'function': 'function' }, 23361 groupTemplate: { 'function': 'function' }, 23362 timeAxis: { 23363 scale: { string: string, 'undefined': 'undefined' }, 23364 step: { number: number, 'undefined': 'undefined' }, 23365 __type__: { object: object } 23366 }, 23367 type: { string: string }, 23368 width: { string: string, number: number }, 23369 zoomable: { boolean: boolean }, 23370 zoomKey: { string: ['ctrlKey', 'altKey', 'metaKey', ''] }, 23371 zoomMax: { number: number }, 23372 zoomMin: { number: number }, 23373 23374 __type__: { object: object } 23375 }; 23376 23377 var configureOptions = { 23378 global: { 23379 align: ['center', 'left', 'right'], 23380 autoResize: true, 23381 throttleRedraw: [10, 0, 1000, 10], 23382 clickToUse: false, 23383 // dataAttributes: ['all'], // FIXME: can be 'all' or string[] 23384 editable: { 23385 add: false, 23386 remove: false, 23387 updateGroup: false, 23388 updateTime: false 23389 }, 23390 end: '', 23391 format: { 23392 minorLabels: { 23393 millisecond: 'SSS', 23394 second: 's', 23395 minute: 'HH:mm', 23396 hour: 'HH:mm', 23397 weekday: 'ddd D', 23398 day: 'D', 23399 month: 'MMM', 23400 year: 'YYYY' 23401 }, 23402 majorLabels: { 23403 millisecond: 'HH:mm:ss', 23404 second: 'D MMMM HH:mm', 23405 minute: 'ddd D MMMM', 23406 hour: 'ddd D MMMM', 23407 weekday: 'MMMM YYYY', 23408 day: 'MMMM YYYY', 23409 month: 'YYYY', 23410 year: '' 23411 } 23412 }, 23413 23414 //groupOrder: {string, 'function': 'function'}, 23415 groupsDraggable: false, 23416 height: '', 23417 //hiddenDates: {object, array}, 23418 locale: '', 23419 margin: { 23420 axis: [20, 0, 100, 1], 23421 item: { 23422 horizontal: [10, 0, 100, 1], 23423 vertical: [10, 0, 100, 1] 23424 } 23425 }, 23426 max: '', 23427 maxHeight: '', 23428 min: '', 23429 minHeight: '', 23430 moveable: false, 23431 multiselect: false, 23432 //onAdd: {'function': 'function'}, 23433 //onUpdate: {'function': 'function'}, 23434 //onMove: {'function': 'function'}, 23435 //onMoving: {'function': 'function'}, 23436 //onRename: {'function': 'function'}, 23437 //order: {'function': 'function'}, 23438 orientation: { 23439 axis: ['both', 'bottom', 'top'], 23440 item: ['bottom', 'top'] 23441 }, 23442 selectable: true, 23443 showCurrentTime: false, 23444 showMajorLabels: true, 23445 showMinorLabels: true, 23446 stack: true, 23447 //snap: {'function': 'function', nada}, 23448 start: '', 23449 //template: {'function': 'function'}, 23450 //timeAxis: { 23451 // scale: ['millisecond', 'second', 'minute', 'hour', 'weekday', 'day', 'month', 'year'], 23452 // step: [1, 1, 10, 1] 23453 //}, 23454 type: ['box', 'point', 'range', 'background'], 23455 width: '100%', 23456 zoomable: true, 23457 zoomKey: ['ctrlKey', 'altKey', 'metaKey', ''], 23458 zoomMax: [315360000000000, 10, 315360000000000, 1], 23459 zoomMin: [10, 10, 315360000000000, 1] 23460 } 23461 }; 23462 23463 exports.allOptions = allOptions; 23464 exports.configureOptions = configureOptions; 23465 23466/***/ }, 23467/* 49 */ 23468/***/ function(module, exports, __webpack_require__) { 23469 23470 'use strict'; 23471 23472 var Emitter = __webpack_require__(12); 23473 var Hammer = __webpack_require__(20); 23474 var moment = __webpack_require__(2); 23475 var util = __webpack_require__(1); 23476 var DataSet = __webpack_require__(8); 23477 var DataView = __webpack_require__(10); 23478 var Range = __webpack_require__(24); 23479 var Core = __webpack_require__(28);
23480 var TimeAxis = __webpack_require__(39); 23481 var CurrentTime = __webpack_require__(44); 23482 var CustomTime = __webpack_require__(42); 23483 var LineGraph = __webpack_require__(50); 23484 23485 var Configurator = __webpack_require__(45); 23486 var Validator = __webpack_require__(47)['default']; 23487 var printStyle = __webpack_require__(47).printStyle; 23488 var allOptions = __webpack_require__(58).allOptions; 23489 var configureOptions = __webpack_require__(58).configureOptions; 23490 23491 /** 23492 * Create a timeline visualization 23493 * @param {HTMLElement} container 23494 * @param {vis.DataSet | Array} [items] 23495 * @param {Object} [options] See Graph2d.setOptions for the available options. 23496 * @constructor 23497 * @extends Core 23498 */ 23499 function Graph2d(container, items, groups, options) { 23500 // if the third element is options, the forth is groups (optionally); 23501 if (!(Array.isArray(groups) || groups instanceof DataSet) && groups instanceof Object) { 23502 var forthArgument = options; 23503 options = groups; 23504 groups = forthArgument; 23505 } 23506 23507 var me = this; 23508 this.defaultOptions = { 23509 start: null, 23510 end: null, 23511 23512 autoResize: true, 23513 23514 orientation: { 23515 axis: 'bottom', // axis orientation: 'bottom', 'top', or 'both' 23516 item: 'bottom' // not relevant for Graph2d 23517 }, 23518 23519 moment: moment, 23520 23521 width: null, 23522 height: null, 23523 maxHeight: null, 23524 minHeight: null 23525 }; 23526 this.options = util.deepExtend({}, this.defaultOptions); 23527 23528 // Create the DOM, props, and emitter 23529 this._create(container); 23530 23531 // all components listed here will be repainted automatically 23532 this.components = []; 23533 23534 this.body = { 23535 dom: this.dom, 23536 domProps: this.props, 23537 emitter: { 23538 on: this.on.bind(this), 23539 off: this.off.bind(this), 23540 emit: this.emit.bind(this) 23541 }, 23542 hiddenDates: [], 23543 util: { 23544 toScreen: me._toScreen.bind(me), 23545 toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width 23546 toTime: me._toTime.bind(me), 23547 toGlobalTime: me._toGlobalTime.bind(me) 23548 } 23549 }; 23550 23551 // range 23552 this.range = new Range(this.body); 23553 this.components.push(this.range); 23554 this.body.range = this.range; 23555 23556 // time axis 23557 this.timeAxis = new TimeAxis(this.body); 23558 this.components.push(this.timeAxis); 23559 //this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis); 23560 23561 // current time bar 23562 this.currentTime = new CurrentTime(this.body); 23563 this.components.push(this.currentTime); 23564 23565 // item set 23566 this.linegraph = new LineGraph(this.body); 23567 this.components.push(this.linegraph); 23568 23569 this.itemsData = null; // DataSet 23570 this.groupsData = null; // DataSet 23571 23572 this.on('tap', function (event) { 23573 me.emit('click', me.getEventProperties(event)); 23574 }); 23575 this.on('doubletap', function (event) { 23576 me.emit('doubleClick', me.getEventProperties(event)); 23577 }); 23578 this.dom.root.oncontextmenu = function (event) { 23579 me.emit('contextmenu', me.getEventProperties(event)); 23580 }; 23581 23582 // apply options 23583 if (options) { 23584 this.setOptions(options); 23585 } 23586 23587 // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS! 23588 if (groups) { 23589 this.setGroups(groups); 23590 } 23591 23592 // create itemset 23593 if (items) { 23594 this.setItems(items); 23595 } else { 23596 this._redraw(); 23597 } 23598 } 23599 23600 // Extend the functionality from Core 23601 Graph2d.prototype = new Core(); 23602 23603 Graph2d.prototype.setOptions = function (options) { 23604 // validate options 23605 var errorFound = Validator.validate(options, allOptions); 23606 if (errorFound === true) { 23607 console.log('%cErrors have been found in the supplied options object.', printStyle); 23608 } 23609 23610 Core.prototype.setOptions.call(this, options); 23611 }; 23612 23613 /** 23614 * Set items 23615 * @param {vis.DataSet | Array | null} items 23616 */ 23617 Graph2d.prototype.setItems = function (items) { 23618 var initialLoad = this.itemsData == null; 23619 23620 // convert to type DataSet when needed 23621 var newDataSet; 23622 if (!items) { 23623 newDataSet = null; 23624 } else if (items instanceof DataSet || items instanceof DataView) { 23625 newDataSet = items; 23626 } else { 23627 // turn an array into a dataset 23628 newDataSet = new DataSet(items, { 23629 type: { 23630 start: 'Date', 23631 end: 'Date' 23632 } 23633 }); 23634 } 23635 23636 // set items 23637 this.itemsData = newDataSet; 23638 this.linegraph && this.linegraph.setItems(newDataSet); 23639 23640 if (initialLoad) { 23641 if (this.options.start != undefined || this.options.end != undefined) { 23642 var start = this.options.start != undefined ? this.options.start : null; 23643 var end = this.options.end != undefined ? this.options.end : null; 23644 23645 this.setWindow(start, end, { animation: false }); 23646 } else { 23647 this.fit({ animation: false }); 23648 } 23649 } 23650 }; 23651 23652 /** 23653 * Set groups 23654 * @param {vis.DataSet | Array} groups 23655 */ 23656 Graph2d.prototype.setGroups = function (groups) { 23657 // convert to type DataSet when needed 23658 var newDataSet; 23659 if (!groups) { 23660 newDataSet = null; 23661 } else if (groups instanceof DataSet || groups instanceof DataView) { 23662 newDataSet = groups; 23663 } else { 23664 // turn an array into a dataset 23665 newDataSet = new DataSet(groups); 23666 } 23667 23668 this.groupsData = newDataSet; 23669 this.linegraph.setGroups(newDataSet); 23670 }; 23671 23672 /**
vendor: 12,483 bytes, lines 23673-24043
23673 * Returns an object containing an SVG element with the icon of the group (size determined by iconWidth and iconHeight), the label of the group (content) and the yAxisOrientation of the group (left or right). 23674 * @param groupId 23675 * @param width 23676 * @param height 23677 */ 23678 Graph2d.prototype.getLegend = function (groupId, width, height) { 23679 if (width === undefined) { 23680 width = 15; 23681 } 23682 if (height === undefined) { 23683 height = 15; 23684 } 23685 if (this.linegraph.groups[groupId] !== undefined) { 23686 return this.linegraph.groups[groupId].getLegend(width, height); 23687 } else { 23688 return 'cannot find group:' + groupId; 23689 } 23690 }; 23691 23692 /** 23693 * This checks if the visible option of the supplied group (by ID) is true or false. 23694 * @param groupId 23695 * @returns {*} 23696 */ 23697 Graph2d.prototype.isGroupVisible = function (groupId) { 23698 if (this.linegraph.groups[groupId] !== undefined) { 23699 return this.linegraph.groups[groupId].visible && (this.linegraph.options.groups.visibility[groupId] === undefined || this.linegraph.options.groups.visibility[groupId] == true); 23700 } else { 23701 return false; 23702 } 23703 }; 23704 23705 /** 23706 * Get the data range of the item set. 23707 * @returns {{min: Date, max: Date}} range A range with a start and end Date. 23708 * When no minimum is found, min==null 23709 * When no maximum is found, max==null 23710 */ 23711 Graph2d.prototype.getDataRange = function () { 23712 var min = null; 23713 var max = null; 23714 23715 // calculate min from start filed 23716 for (var groupId in this.linegraph.groups) { 23717 if (this.linegraph.groups.hasOwnProperty(groupId)) { 23718 if (this.linegraph.groups[groupId].visible == true) { 23719 for (var i = 0; i < this.linegraph.groups[groupId].itemsData.length; i++) { 23720 var item = this.linegraph.groups[groupId].itemsData[i]; 23721 var value = util.convert(item.x, 'Date').valueOf(); 23722 min = min == null ? value : min > value ? value : min; 23723 max = max == null ? value : max < value ? value : max; 23724 } 23725 } 23726 } 23727 } 23728 23729 return { 23730 min: min != null ? new Date(min) : null, 23731 max: max != null ? new Date(max) : null 23732 }; 23733 }; 23734 23735 /** 23736 * Generate Timeline related information from an event 23737 * @param {Event} event 23738 * @return {Object} An object with related information, like on which area 23739 * The event happened, whether clicked on an item, etc. 23740 */ 23741 Graph2d.prototype.getEventProperties = function (event) { 23742 var clientX = event.center ? event.center.x : event.clientX; 23743 var clientY = event.center ? event.center.y : event.clientY; 23744 var x = clientX - util.getAbsoluteLeft(this.dom.centerContainer); 23745 var y = clientY - util.getAbsoluteTop(this.dom.centerContainer); 23746 var time = this._toTime(x); 23747 23748 var customTime = CustomTime.customTimeFromTarget(event); 23749 23750 var element = util.getTarget(event); 23751 var what = null; 23752 if (util.hasParent(element, this.timeAxis.dom.foreground)) { 23753 what = 'axis'; 23754 } else if (this.timeAxis2 && util.hasParent(element, this.timeAxis2.dom.foreground)) { 23755 what = 'axis'; 23756 } else if (util.hasParent(element, this.linegraph.yAxisLeft.dom.frame)) { 23757 what = 'data-axis'; 23758 } else if (util.hasParent(element, this.linegraph.yAxisRight.dom.frame)) { 23759 what = 'data-axis'; 23760 } else if (util.hasParent(element, this.linegraph.legendLeft.dom.frame)) { 23761 what = 'legend'; 23762 } else if (util.hasParent(element, this.linegraph.legendRight.dom.frame)) { 23763 what = 'legend'; 23764 } else if (customTime != null) { 23765 what = 'custom-time'; 23766 } else if (util.hasParent(element, this.currentTime.bar)) { 23767 what = 'current-time'; 23768 } else if (util.hasParent(element, this.dom.center)) { 23769 what = 'background'; 23770 } 23771 23772 var value = []; 23773 var yAxisLeft = this.linegraph.yAxisLeft; 23774 var yAxisRight = this.linegraph.yAxisRight; 23775 if (!yAxisLeft.hidden) { 23776 value.push(yAxisLeft.screenToValue(y)); 23777 } 23778 if (!yAxisRight.hidden) { 23779 value.push(yAxisRight.screenToValue(y)); 23780 } 23781 23782 return { 23783 event: event, 23784 what: what, 23785 pageX: event.srcEvent ? event.srcEvent.pageX : event.pageX, 23786 pageY: event.srcEvent ? event.srcEvent.pageY : event.pageY, 23787 x: x, 23788 y: y, 23789 time: time, 23790 value: value 23791 }; 23792 }; 23793 23794 /** 23795 * Load a configurator 23796 * @return {Object} 23797 * @private 23798 */ 23799 Graph2d.prototype._createConfigurator = function () { 23800 return new Configurator(this, this.dom.container, configureOptions); 23801 }; 23802 23803 module.exports = Graph2d; 23804 23805/***/ }, 23806/* 50 */ 23807/***/ function(module, exports, __webpack_require__) { 23808 23809 'use strict'; 23810 23811 var util = __webpack_require__(1); 23812 var DOMutil = __webpack_require__(7); 23813 var DataSet = __webpack_require__(8); 23814 var DataView = __webpack_require__(10); 23815 var Component = __webpack_require__(26); 23816 var DataAxis = __webpack_require__(51); 23817 var GraphGroup = __webpack_require__(53); 23818 var Legend = __webpack_require__(57); 23819 var BarFunctions = __webpack_require__(56); 23820 var LineFunctions = __webpack_require__(54); 23821 23822 var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items 23823 23824 /** 23825 * This is the constructor of the LineGraph. It requires a Timeline body and options. 23826 * 23827 * @param body 23828 * @param options 23829 * @constructor 23830 */ 23831 function LineGraph(body, options) { 23832 this.id = util.randomUUID(); 23833 this.body = body; 23834 23835 this.defaultOptions = { 23836 yAxisOrientation: 'left', 23837 defaultGroup: 'default', 23838 sort: true, 23839 sampling: true, 23840 stack: false, 23841 graphHeight: '400px', 23842 shaded: { 23843 enabled: false, 23844 orientation: 'bottom' // top, bottom 23845 }, 23846 style: 'line', // line, bar 23847 barChart: { 23848 width: 50, 23849 sideBySide: false, 23850 align: 'center' // left, center, right 23851 }, 23852 interpolation: { 23853 enabled: true, 23854 parametrization: 'centripetal', // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5) 23855 alpha: 0.5 23856 }, 23857 drawPoints: { 23858 enabled: true, 23859 size: 6, 23860 style: 'square' // square, circle 23861 }, 23862 dataAxis: { 23863 showMinorLabels: true, 23864 showMajorLabels: true, 23865 icons: false, 23866 width: '40px', 23867 visible: true, 23868 alignZeros: true, 23869 left: { 23870 range: { min: undefined, max: undefined }, 23871 format: function format(value) { 23872 return value; 23873 }, 23874 title: { text: undefined, style: undefined } 23875 }, 23876 right: { 23877 range: { min: undefined, max: undefined }, 23878 format: function format(value) { 23879 return value; 23880 }, 23881 title: { text: undefined, style: undefined } 23882 } 23883 }, 23884 legend: { 23885 enabled: false, 23886 icons: true, 23887 left: { 23888 visible: true, 23889 position: 'top-left' // top/bottom - left,right 23890 }, 23891 right: { 23892 visible: true, 23893 position: 'top-right' // top/bottom - left,right 23894 } 23895 }, 23896 groups: { 23897 visibility: {} 23898 } 23899 }; 23900 23901 // options is shared by this ItemSet and all its items 23902 this.options = util.extend({}, this.defaultOptions); 23903 this.dom = {}; 23904 this.props = {}; 23905 this.hammer = null; 23906 this.groups = {}; 23907 this.abortedGraphUpdate = false; 23908 this.updateSVGheight = false; 23909 this.updateSVGheightOnResize = false; 23910 23911 var me = this; 23912 this.itemsData = null; // DataSet 23913 this.groupsData = null; // DataSet 23914 23915 // listeners for the DataSet of the items 23916 this.itemListeners = { 23917 'add': function add(event, params, senderId) { 23918 me._onAdd(params.items); 23919 }, 23920 'update': function update(event, params, senderId) { 23921 me._onUpdate(params.items); 23922 }, 23923 'remove': function remove(event, params, senderId) { 23924 me._onRemove(params.items); 23925 } 23926 }; 23927 23928 // listeners for the DataSet of the groups 23929 this.groupListeners = { 23930 'add': function add(event, params, senderId) { 23931 me._onAddGroups(params.items); 23932 }, 23933 'update': function update(event, params, senderId) { 23934 me._onUpdateGroups(params.items); 23935 }, 23936 'remove': function remove(event, params, senderId) { 23937 me._onRemoveGroups(params.items); 23938 } 23939 }; 23940 23941 this.items = {}; // object with an Item for every data item 23942 this.selection = []; // list with the ids of all selected nodes 23943 this.lastStart = this.body.range.start; 23944 this.touchParams = {}; // stores properties while dragging 23945 23946 this.svgElements = {}; 23947 this.setOptions(options); 23948 this.groupsUsingDefaultStyles = [0]; 23949 this.COUNTER = 0; 23950 this.body.emitter.on('rangechanged', function () { 23951 me.lastStart = me.body.range.start; 23952 me.svg.style.left = util.option.asSize(-me.props.width); 23953 me.redraw.call(me, true); 23954 }); 23955 23956 // create the HTML DOM 23957 this._create(); 23958 this.framework = { svg: this.svg, svgElements: this.svgElements, options: this.options, groups: this.groups }; 23959 this.body.emitter.emit('change'); 23960 } 23961 23962 LineGraph.prototype = new Component(); 23963 23964 /** 23965 * Create the HTML DOM for the ItemSet 23966 */ 23967 LineGraph.prototype._create = function () { 23968 var frame = document.createElement('div'); 23969 frame.className = 'vis-line-graph'; 23970 this.dom.frame = frame; 23971 23972 // create svg element for graph drawing. 23973 this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 23974 this.svg.style.position = 'relative'; 23975 this.svg.style.height = ('' + this.options.graphHeight).replace('px', '') + 'px'; 23976 this.svg.style.display = 'block'; 23977 frame.appendChild(this.svg); 23978 23979 // data axis 23980 this.options.dataAxis.orientation = 'left'; 23981 this.yAxisLeft = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups); 23982 23983 this.options.dataAxis.orientation = 'right'; 23984 this.yAxisRight = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups); 23985 delete this.options.dataAxis.orientation; 23986 23987 // legends 23988 this.legendLeft = new Legend(this.body, this.options.legend, 'left', this.options.groups); 23989 this.legendRight = new Legend(this.body, this.options.legend, 'right', this.options.groups); 23990 23991 this.show(); 23992 }; 23993 23994 /** 23995 * set the options of the LineGraph. the mergeOptions is used for subObjects that have an enabled element. 23996 * @param {object} options 23997 */ 23998 LineGraph.prototype.setOptions = function (options) { 23999 if (options) { 24000 var fields = ['sampling', 'defaultGroup', 'stack', 'height', 'graphHeight', 'yAxisOrientation', 'style', 'barChart', 'dataAxis', 'sort', 'groups']; 24001 if (options.graphHeight === undefined && options.height !== undefined && this.body.domProps.centerContainer.height !== undefined) { 24002 this.updateSVGheight = true; 24003 this.updateSVGheightOnResize = true; 24004 } else if (this.body.domProps.centerContainer.height !== undefined && options.graphHeight !== undefined) { 24005 if (parseInt((options.graphHeight + '').replace('px', '')) < this.body.domProps.centerContainer.height) { 24006 this.updateSVGheight = true; 24007 } 24008 } 24009 util.selectiveDeepExtend(fields, this.options, options); 24010 util.mergeOptions(this.options, options, 'interpolation'); 24011 util.mergeOptions(this.options, options, 'drawPoints'); 24012 util.mergeOptions(this.options, options, 'shaded'); 24013 util.mergeOptions(this.options, options, 'legend'); 24014 24015 if (options.interpolation) { 24016 if (typeof options.interpolation == 'object') { 24017 if (options.interpolation.parametrization) { 24018 if (options.interpolation.parametrization == 'uniform') { 24019 this.options.interpolation.alpha = 0; 24020 } else if (options.interpolation.parametrization == 'chordal') { 24021 this.options.interpolation.alpha = 1.0; 24022 } else { 24023 this.options.interpolation.parametrization = 'centripetal'; 24024 this.options.interpolation.alpha = 0.5; 24025 } 24026 } 24027 } 24028 } 24029 24030 if (this.yAxisLeft) { 24031 if (options.dataAxis !== undefined) { 24032 this.yAxisLeft.setOptions(this.options.dataAxis); 24033 this.yAxisRight.setOptions(this.options.dataAxis); 24034 } 24035 } 24036 24037 if (this.legendLeft) { 24038 if (options.legend !== undefined) { 24039 this.legendLeft.setOptions(this.options.legend); 24040 this.legendRight.setOptions(this.options.legend); 24041 } 24042 } 24043
24044 if (this.groups.hasOwnProperty(UNGROUPED)) { 24045 this.groups[UNGROUPED].setOptions(options); 24046 } 24047 } 24048 24049 // this is used to redraw the graph if the visibility of the groups is changed. 24050 if (this.dom.frame) { 24051 this.redraw(true); 24052 } 24053 }; 24054 24055 /** 24056 * Hide the component from the DOM 24057 */ 24058 LineGraph.prototype.hide = function () { 24059 // remove the frame containing the items 24060 if (this.dom.frame.parentNode) { 24061 this.dom.frame.parentNode.removeChild(this.dom.frame); 24062 } 24063 }; 24064 24065 /** 24066 * Show the component in the DOM (when not already visible). 24067 * @return {Boolean} changed 24068 */ 24069 LineGraph.prototype.show = function () { 24070 // show frame containing the items 24071 if (!this.dom.frame.parentNode) { 24072 this.body.dom.center.appendChild(this.dom.frame); 24073 } 24074 }; 24075 24076 /** 24077 * Set items 24078 * @param {vis.DataSet | null} items 24079 */ 24080 LineGraph.prototype.setItems = function (items) { 24081 var me = this, 24082 ids, 24083 oldItemsData = this.itemsData; 24084 24085 // replace the dataset 24086 if (!items) { 24087 this.itemsData = null; 24088 } else if (items instanceof DataSet || items instanceof DataView) { 24089 this.itemsData = items; 24090 } else { 24091 throw new TypeError('Data must be an instance of DataSet or DataView'); 24092 } 24093 24094 if (oldItemsData) { 24095 // unsubscribe from old dataset 24096 util.forEach(this.itemListeners, function (callback, event) { 24097 oldItemsData.off(event, callback); 24098 }); 24099 24100 // remove all drawn items 24101 ids = oldItemsData.getIds(); 24102 this._onRemove(ids); 24103 } 24104 24105 if (this.itemsData) { 24106 // subscribe to new dataset 24107 var id = this.id; 24108 util.forEach(this.itemListeners, function (callback, event) { 24109 me.itemsData.on(event, callback, id); 24110 }); 24111 24112 // add all new items 24113 ids = this.itemsData.getIds(); 24114 this._onAdd(ids); 24115 } 24116 this._updateUngrouped(); 24117 //this._updateGraph(); 24118 this.redraw(true); 24119 }; 24120 24121 /** 24122 * Set groups 24123 * @param {vis.DataSet} groups 24124 */ 24125 LineGraph.prototype.setGroups = function (groups) { 24126 var me = this; 24127 var ids; 24128 24129 // unsubscribe from current dataset 24130 if (this.groupsData) { 24131 util.forEach(this.groupListeners, function (callback, event) { 24132 me.groupsData.off(event, callback); 24133 }); 24134 24135 // remove all drawn groups 24136 ids = this.groupsData.getIds(); 24137 this.groupsData = null; 24138 this._onRemoveGroups(ids); // note: this will cause a redraw 24139 } 24140 24141 // replace the dataset 24142 if (!groups) { 24143 this.groupsData = null; 24144 } else if (groups instanceof DataSet || groups instanceof DataView) { 24145 this.groupsData = groups; 24146 } else { 24147 throw new TypeError('Data must be an instance of DataSet or DataView'); 24148 } 24149 24150 if (this.groupsData) { 24151 // subscribe to new dataset 24152 var id = this.id; 24153 util.forEach(this.groupListeners, function (callback, event) { 24154 me.groupsData.on(event, callback, id); 24155 }); 24156 24157 // draw all ms 24158 ids = this.groupsData.getIds(); 24159 this._onAddGroups(ids); 24160 } 24161 this._onUpdate(); 24162 }; 24163 24164 /** 24165 * Update the data 24166 * @param [ids] 24167 * @private 24168 */ 24169 LineGraph.prototype._onUpdate = function (ids) { 24170 this._updateUngrouped(); 24171 this._updateAllGroupData(); 24172 //this._updateGraph(); 24173 this.redraw(true); 24174 }; 24175 LineGraph.prototype._onAdd = function (ids) { 24176 this._onUpdate(ids); 24177 }; 24178 LineGraph.prototype._onRemove = function (ids) { 24179 this._onUpdate(ids); 24180 }; 24181 LineGraph.prototype._onUpdateGroups = function (groupIds) { 24182 for (var i = 0; i < groupIds.length; i++) { 24183 var group = this.groupsData.get(groupIds[i]); 24184 this._updateGroup(group, groupIds[i]); 24185 } 24186 24187 //this._updateGraph(); 24188 this.redraw(true); 24189 }; 24190 LineGraph.prototype._onAddGroups = function (groupIds) { 24191 this._onUpdateGroups(groupIds); 24192 }; 24193 24194 /** 24195 * this cleans the group out off the legends and the dataaxis, updates the ungrouped and updates the graph 24196 * @param {Array} groupIds 24197 * @private 24198 */ 24199 LineGraph.prototype._onRemoveGroups = function (groupIds) { 24200 for (var i = 0; i < groupIds.length; i++) { 24201 if (this.groups.hasOwnProperty(groupIds[i])) {
24202 if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') { 24203 this.yAxisRight.removeGroup(groupIds[i]); 24204 this.legendRight.removeGroup(groupIds[i]); 24205 this.legendRight.redraw(); 24206 } else { 24207 this.yAxisLeft.removeGroup(groupIds[i]); 24208 this.legendLeft.removeGroup(groupIds[i]); 24209 this.legendLeft.redraw(); 24210 } 24211 delete this.groups[groupIds[i]]; 24212 } 24213 } 24214 this._updateUngrouped(); 24215 //this._updateGraph(); 24216 this.redraw(true); 24217 }; 24218 24219 /** 24220 * update a group object with the group dataset entree 24221 * 24222 * @param group 24223 * @param groupId 24224 * @private 24225 */ 24226 LineGraph.prototype._updateGroup = function (group, groupId) { 24227 if (!this.groups.hasOwnProperty(groupId)) { 24228 this.groups[groupId] = new GraphGroup(group, groupId, this.options, this.groupsUsingDefaultStyles); 24229 if (this.groups[groupId].options.yAxisOrientation == 'right') { 24230 this.yAxisRight.addGroup(groupId, this.groups[groupId]); 24231 this.legendRight.addGroup(groupId, this.groups[groupId]); 24232 } else { 24233 this.yAxisLeft.addGroup(groupId, this.groups[groupId]); 24234 this.legendLeft.addGroup(groupId, this.groups[groupId]); 24235 } 24236 } else { 24237 this.groups[groupId].update(group);
24238 if (this.groups[groupId].options.yAxisOrientation == 'right') { 24239 this.yAxisRight.updateGroup(groupId, this.groups[groupId]); 24240 this.legendRight.updateGroup(groupId, this.groups[groupId]); 24241 } else { 24242 this.yAxisLeft.updateGroup(groupId, this.groups[groupId]); 24243 this.legendLeft.updateGroup(groupId, this.groups[groupId]); 24244 } 24245 } 24246 this.legendLeft.redraw(); 24247 this.legendRight.redraw(); 24248 }; 24249 24250 /** 24251 * this updates all groups, it is used when there is an update the the itemset. 24252 * 24253 * @private 24254 */ 24255 LineGraph.prototype._updateAllGroupData = function () { 24256 if (this.itemsData != null) { 24257 var groupsContent = {}; 24258 var groupId; 24259 for (groupId in this.groups) { 24260 if (this.groups.hasOwnProperty(groupId)) { 24261 groupsContent[groupId] = []; 24262 } 24263 } 24264 for (var itemId in this.itemsData._data) { 24265 if (this.itemsData._data.hasOwnProperty(itemId)) { 24266 var item = this.itemsData._data[itemId]; 24267 if (groupsContent[item.group] === undefined) { 24268 throw new Error('Cannot find referenced group ' + item.group + '. Possible reason: items added before groups? Groups need to be added before items, as items refer to groups.'); 24269 } 24270 item.x = util.convert(item.x, 'Date'); 24271 groupsContent[item.group].push(item); 24272 } 24273 } 24274 for (groupId in this.groups) { 24275 if (this.groups.hasOwnProperty(groupId)) { 24276 this.groups[groupId].setItems(groupsContent[groupId]); 24277 } 24278 } 24279 } 24280 }; 24281 24282 /** 24283 * Create or delete the group holding all ungrouped items. This group is used when 24284 * there are no groups specified. This anonymous group is called 'graph'. 24285 * @protected 24286 */ 24287 LineGraph.prototype._updateUngrouped = function () { 24288 if (this.itemsData && this.itemsData != null) { 24289 var ungroupedCounter = 0; 24290 for (var itemId in this.itemsData._data) { 24291 if (this.itemsData._data.hasOwnProperty(itemId)) { 24292 var item = this.itemsData._data[itemId]; 24293 if (item != undefined) { 24294 if (item.hasOwnProperty('group')) { 24295 if (item.group === undefined) { 24296 item.group = UNGROUPED; 24297 } 24298 } else { 24299 item.group = UNGROUPED; 24300 } 24301 ungroupedCounter = item.group == UNGROUPED ? ungroupedCounter + 1 : ungroupedCounter; 24302 } 24303 } 24304 } 24305 24306 if (ungroupedCounter == 0) { 24307 delete this.groups[UNGROUPED]; 24308 this.legendLeft.removeGroup(UNGROUPED); 24309 this.legendRight.removeGroup(UNGROUPED); 24310 this.yAxisLeft.removeGroup(UNGROUPED); 24311 this.yAxisRight.removeGroup(UNGROUPED); 24312 } else { 24313 var group = { id: UNGROUPED, content: this.options.defaultGroup }; 24314 this._updateGroup(group, UNGROUPED); 24315 } 24316 } else { 24317 delete this.groups[UNGROUPED]; 24318 this.legendLeft.removeGroup(UNGROUPED); 24319 this.legendRight.removeGroup(UNGROUPED); 24320 this.yAxisLeft.removeGroup(UNGROUPED); 24321 this.yAxisRight.removeGroup(UNGROUPED); 24322 } 24323 24324 this.legendLeft.redraw(); 24325 this.legendRight.redraw(); 24326 }; 24327 24328 /** 24329 * Redraw the component, mandatory function 24330 * @return {boolean} Returns true if the component is resized 24331 */ 24332 LineGraph.prototype.redraw = function (forceGraphUpdate) { 24333 var resized = false; 24334 24335 // calculate actual size and position 24336 this.props.width = this.dom.frame.offsetWidth; 24337 this.props.height = this.body.domProps.centerContainer.height - this.body.domProps.border.top - this.body.domProps.border.bottom; 24338 24339 // update the graph if there is no lastWidth or with, used for the initial draw 24340 if (this.lastWidth === undefined && this.props.width) { 24341 forceGraphUpdate = true; 24342 } 24343 24344 // check if this component is resized 24345 resized = this._isResized() || resized; 24346 24347 // check whether zoomed (in that case we need to re-stack everything) 24348 var visibleInterval = this.body.range.end - this.body.range.start; 24349 var zoomed = visibleInterval != this.lastVisibleInterval; 24350 this.lastVisibleInterval = visibleInterval; 24351 24352 // the svg element is three times as big as the width, this allows for fully dragging left and right 24353 // without reloading the graph. the controls for this are bound to events in the constructor 24354 if (resized == true) { 24355 this.svg.style.width = util.option.asSize(3 * this.props.width); 24356 this.svg.style.left = util.option.asSize(-this.props.width); 24357 24358 // if the height of the graph is set as proportional, change the height of the svg 24359 if ((this.options.height + '').indexOf('%') != -1 || this.updateSVGheightOnResize == true) { 24360 this.updateSVGheight = true; 24361 } 24362 } 24363 24364 // update the height of the graph on each redraw of the graph. 24365 if (this.updateSVGheight == true) { 24366 if (this.options.graphHeight != this.props.height + 'px') { 24367 this.options.graphHeight = this.props.height + 'px'; 24368 this.svg.style.height = this.props.height + 'px'; 24369 } 24370 this.updateSVGheight = false;
24371 } else { 24372 this.svg.style.height = ('' + this.options.graphHeight).replace('px', '') + 'px'; 24373 } 24374 24375 // zoomed is here to ensure that animations are shown correctly. 24376 if (resized == true || zoomed == true || this.abortedGraphUpdate == true || forceGraphUpdate == true) { 24377 resized = this._updateGraph() || resized; 24378 } else { 24379 // move the whole svg while dragging 24380 if (this.lastStart != 0) { 24381 var offset = this.body.range.start - this.lastStart; 24382 var range = this.body.range.end - this.body.range.start; 24383 if (this.props.width != 0) { 24384 var rangePerPixelInv = this.props.width / range; 24385 var xOffset = offset * rangePerPixelInv; 24386 this.svg.style.left = -this.props.width - xOffset + 'px'; 24387 } 24388 } 24389 } 24390 24391 this.legendLeft.redraw(); 24392 this.legendRight.redraw(); 24393 return resized; 24394 }; 24395 24396 /** 24397 * Update and redraw the graph. 24398 * 24399 */ 24400 LineGraph.prototype._updateGraph = function () { 24401 // reset the svg elements 24402 DOMutil.prepareElements(this.svgElements); 24403 if (this.props.width != 0 && this.itemsData != null) { 24404 var group, i; 24405 var preprocessedGroupData = {}; 24406 var processedGroupData = {}; 24407 var groupRanges = {}; 24408 var changeCalled = false; 24409 24410 // getting group Ids 24411 var groupIds = []; 24412 for (var groupId in this.groups) { 24413 if (this.groups.hasOwnProperty(groupId)) { 24414 group = this.groups[groupId]; 24415 if (group.visible == true && (this.options.groups.visibility[groupId] === undefined || this.options.groups.visibility[groupId] == true)) { 24416 groupIds.push(groupId); 24417 } 24418 } 24419 } 24420 if (groupIds.length > 0) { 24421 // this is the range of the SVG canvas 24422 var minDate = this.body.util.toGlobalTime(-this.body.domProps.root.width); 24423 var maxDate = this.body.util.toGlobalTime(2 * this.body.domProps.root.width); 24424 var groupsData = {}; 24425 // fill groups data, this only loads the data we require based on the timewindow 24426 this._getRelevantData(groupIds, groupsData, minDate, maxDate); 24427 24428 // apply sampling, if disabled, it will pass through this function. 24429 this._applySampling(groupIds, groupsData); 24430 24431 // we transform the X coordinates to detect collisions 24432 for (i = 0; i < groupIds.length; i++) { 24433 preprocessedGroupData[groupIds[i]] = this._convertXcoordinates(groupsData[groupIds[i]]); 24434 } 24435 24436 // now all needed data has been collected we start the processing. 24437 this._getYRanges(groupIds, preprocessedGroupData, groupRanges); 24438 24439 // update the Y axis first, we use this data to draw at the correct Y points 24440 // changeCalled is required to clean the SVG on a change emit. 24441 changeCalled = this._updateYAxis(groupIds, groupRanges); 24442 var MAX_CYCLES = 5; 24443 if (changeCalled == true && this.COUNTER < MAX_CYCLES) { 24444 DOMutil.cleanupElements(this.svgElements); 24445 this.abortedGraphUpdate = true; 24446 this.COUNTER++; 24447 this.body.emitter.emit('change'); 24448 return true; 24449 } else { 24450 if (this.COUNTER > MAX_CYCLES) { 24451 console.log('WARNING: there may be an infinite loop in the _updateGraph emitter cycle.'); 24452 } 24453 this.COUNTER = 0; 24454 this.abortedGraphUpdate = false; 24455
24456 // With the yAxis scaled correctly, use this to get the Y values of the points. 24457 for (i = 0; i < groupIds.length; i++) { 24458 group = this.groups[groupIds[i]]; 24459 processedGroupData[groupIds[i]] = this._convertYcoordinates(groupsData[groupIds[i]], group); 24460 } 24461 24462 // draw the groups 24463 for (i = 0; i < groupIds.length; i++) { 24464 group = this.groups[groupIds[i]]; 24465 if (group.options.style != 'bar') { 24466 // bar needs to be drawn enmasse 24467 group.draw(processedGroupData[groupIds[i]], group, this.framework); 24468 } 24469 } 24470 BarFunctions.draw(groupIds, processedGroupData, this.framework); 24471 } 24472 } 24473 } 24474 24475 // cleanup unused svg elements 24476 DOMutil.cleanupElements(this.svgElements); 24477 return false; 24478 }; 24479 24480 /** 24481 * first select and preprocess the data from the datasets. 24482 * the groups have their preselection of data, we now loop over this data to see 24483 * what data we need to draw. Sorted data is much faster. 24484 * more optimization is possible by doing the sampling before and using the binary search 24485 * to find the end date to determine the increment. 24486 * 24487 * @param {array} groupIds 24488 * @param {object} groupsData 24489 * @param {date} minDate 24490 * @param {date} maxDate 24491 * @private 24492 */ 24493 LineGraph.prototype._getRelevantData = function (groupIds, groupsData, minDate, maxDate) { 24494 var group, i, j, item; 24495 if (groupIds.length > 0) { 24496 for (i = 0; i < groupIds.length; i++) { 24497 group = this.groups[groupIds[i]]; 24498 groupsData[groupIds[i]] = []; 24499 var dataContainer = groupsData[groupIds[i]]; 24500 // optimization for sorted data 24501 if (group.options.sort == true) { 24502 var guess = Math.max(0, util.binarySearchValue(group.itemsData, minDate, 'x', 'before')); 24503 for (j = guess; j < group.itemsData.length; j++) { 24504 item = group.itemsData[j]; 24505 if (item !== undefined) { 24506 if (item.x > maxDate) { 24507 dataContainer.push(item); 24508 break; 24509 } else { 24510 dataContainer.push(item); 24511 } 24512 } 24513 } 24514 } else { 24515 for (j = 0; j < group.itemsData.length; j++) { 24516 item = group.itemsData[j]; 24517 if (item !== undefined) { 24518 if (item.x > minDate && item.x < maxDate) { 24519 dataContainer.push(item); 24520 } 24521 } 24522 } 24523 } 24524 } 24525 } 24526 }; 24527 24528 /** 24529 * 24530 * @param groupIds 24531 * @param groupsData 24532 * @private 24533 */ 24534 LineGraph.prototype._applySampling = function (groupIds, groupsData) { 24535 var group; 24536 if (groupIds.length > 0) { 24537 for (var i = 0; i < groupIds.length; i++) { 24538 group = this.groups[groupIds[i]]; 24539 if (group.options.sampling == true) { 24540 var dataContainer = groupsData[groupIds[i]]; 24541 if (dataContainer.length > 0) { 24542 var increment = 1; 24543 var amountOfPoints = dataContainer.length; 24544 24545 // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop 24546 // of width changing of the yAxis. 24547 var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x); 24548 var pointsPerPixel = amountOfPoints / xDistance; 24549 increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel))); 24550 24551 var sampledData = []; 24552 for (var j = 0; j < amountOfPoints; j += increment) { 24553 sampledData.push(dataContainer[j]); 24554 } 24555 groupsData[groupIds[i]] = sampledData; 24556 } 24557 } 24558 } 24559 } 24560 }; 24561 24562 /** 24563 * 24564 * 24565 * @param {array} groupIds 24566 * @param {object} groupsData 24567 * @param {object} groupRanges | this is being filled here 24568 * @private 24569 */ 24570 LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) { 24571 var groupData, group, i; 24572 var combinedDataLeft = []; 24573 var combinedDataRight = []; 24574 var options; 24575 if (groupIds.length > 0) { 24576 for (i = 0; i < groupIds.length; i++) { 24577 groupData = groupsData[groupIds[i]]; 24578 options = this.groups[groupIds[i]].options; 24579 if (groupData.length > 0) { 24580 group = this.groups[groupIds[i]]; 24581 // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups. 24582 if (options.stack === true && options.style === 'bar') {
vendor: 12,409 bytes, lines 24583-24966
24583 if (options.yAxisOrientation === 'left') { 24584 combinedDataLeft = combinedDataLeft.concat(group.getData(groupData)); 24585 } else { 24586 combinedDataRight = combinedDataRight.concat(group.getData(groupData)); 24587 } 24588 } else { 24589 groupRanges[groupIds[i]] = group.getYRange(groupData, groupIds[i]); 24590 } 24591 } 24592 } 24593 24594 // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups. 24595 BarFunctions.getStackedYRange(combinedDataLeft, groupRanges, groupIds, '__barStackLeft', 'left'); 24596 BarFunctions.getStackedYRange(combinedDataRight, groupRanges, groupIds, '__barStackRight', 'right'); 24597 // if line graphs are stacked, their range need to be handled differently and accumulated over all groups. 24598 //LineFunctions.getStackedYRange(combinedDataLeft , groupRanges, groupIds, '__lineStackLeft' , 'left' ); 24599 //LineFunctions.getStackedYRange(combinedDataRight, groupRanges, groupIds, '__lineStackRight', 'right'); 24600 } 24601 }; 24602 24603 /** 24604 * this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden. 24605 * @param {Array} groupIds 24606 * @param {Object} groupRanges 24607 * @private 24608 */ 24609 LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) { 24610 var resized = false; 24611 var yAxisLeftUsed = false; 24612 var yAxisRightUsed = false; 24613 var minLeft = 1e9, 24614 minRight = 1e9, 24615 maxLeft = -1e9, 24616 maxRight = -1e9, 24617 minVal, 24618 maxVal; 24619 // if groups are present 24620 if (groupIds.length > 0) { 24621 // this is here to make sure that if there are no items in the axis but there are groups, that there is no infinite draw/redraw loop. 24622 for (var i = 0; i < groupIds.length; i++) { 24623 var group = this.groups[groupIds[i]]; 24624 if (group && group.options.yAxisOrientation != 'right') { 24625 yAxisLeftUsed = true; 24626 minLeft = 1e9; 24627 maxLeft = -1e9; 24628 } else if (group && group.options.yAxisOrientation) { 24629 yAxisRightUsed = true; 24630 minRight = 1e9; 24631 maxRight = -1e9; 24632 } 24633 } 24634 24635 // if there are items: 24636 for (var i = 0; i < groupIds.length; i++) { 24637 if (groupRanges.hasOwnProperty(groupIds[i])) { 24638 if (groupRanges[groupIds[i]].ignore !== true) { 24639 minVal = groupRanges[groupIds[i]].min; 24640 maxVal = groupRanges[groupIds[i]].max; 24641 24642 if (groupRanges[groupIds[i]].yAxisOrientation != 'right') { 24643 yAxisLeftUsed = true; 24644 minLeft = minLeft > minVal ? minVal : minLeft; 24645 maxLeft = maxLeft < maxVal ? maxVal : maxLeft; 24646 } else { 24647 yAxisRightUsed = true; 24648 minRight = minRight > minVal ? minVal : minRight; 24649 maxRight = maxRight < maxVal ? maxVal : maxRight; 24650 } 24651 } 24652 } 24653 } 24654 24655 if (yAxisLeftUsed == true) { 24656 this.yAxisLeft.setRange(minLeft, maxLeft); 24657 } 24658 if (yAxisRightUsed == true) { 24659 this.yAxisRight.setRange(minRight, maxRight); 24660 } 24661 } 24662 resized = this._toggleAxisVisiblity(yAxisLeftUsed, this.yAxisLeft) || resized; 24663 resized = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || resized; 24664 24665 if (yAxisRightUsed == true && yAxisLeftUsed == true) { 24666 this.yAxisLeft.drawIcons = true; 24667 this.yAxisRight.drawIcons = true; 24668 } else { 24669 this.yAxisLeft.drawIcons = false; 24670 this.yAxisRight.drawIcons = false; 24671 } 24672 this.yAxisRight.master = !yAxisLeftUsed; 24673 if (this.yAxisRight.master == false) { 24674 if (yAxisRightUsed == true) { 24675 this.yAxisLeft.lineOffset = this.yAxisRight.width; 24676 } else { 24677 this.yAxisLeft.lineOffset = 0; 24678 } 24679 24680 resized = this.yAxisLeft.redraw() || resized; 24681 this.yAxisRight.stepPixels = this.yAxisLeft.stepPixels; 24682 this.yAxisRight.zeroCrossing = this.yAxisLeft.zeroCrossing; 24683 this.yAxisRight.amountOfSteps = this.yAxisLeft.amountOfSteps; 24684 resized = this.yAxisRight.redraw() || resized; 24685 } else { 24686 resized = this.yAxisRight.redraw() || resized; 24687 } 24688 24689 // clean the accumulated lists 24690 var tempGroups = ['__barStackLeft', '__barStackRight', '__lineStackLeft', '__lineStackRight']; 24691 for (var i = 0; i < tempGroups.length; i++) { 24692 if (groupIds.indexOf(tempGroups[i]) != -1) { 24693 groupIds.splice(groupIds.indexOf(tempGroups[i]), 1); 24694 } 24695 } 24696 24697 return resized; 24698 }; 24699 24700 /** 24701 * This shows or hides the Y axis if needed. If there is a change, the changed event is emitted by the updateYAxis function 24702 * 24703 * @param {boolean} axisUsed 24704 * @returns {boolean} 24705 * @private 24706 * @param axis 24707 */ 24708 LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) { 24709 var changed = false; 24710 if (axisUsed == false) { 24711 if (axis.dom.frame.parentNode && axis.hidden == false) { 24712 axis.hide(); 24713 changed = true; 24714 } 24715 } else { 24716 if (!axis.dom.frame.parentNode && axis.hidden == true) { 24717 axis.show(); 24718 changed = true; 24719 } 24720 } 24721 return changed; 24722 }; 24723 24724 /** 24725 * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the 24726 * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for 24727 * the yAxis. 24728 * 24729 * @param datapoints 24730 * @returns {Array} 24731 * @private 24732 */ 24733 LineGraph.prototype._convertXcoordinates = function (datapoints) { 24734 var extractedData = []; 24735 var xValue, yValue; 24736 var toScreen = this.body.util.toScreen; 24737 24738 for (var i = 0; i < datapoints.length; i++) { 24739 xValue = toScreen(datapoints[i].x) + this.props.width; 24740 yValue = datapoints[i].y; 24741 extractedData.push({ x: xValue, y: yValue }); 24742 } 24743 24744 return extractedData; 24745 }; 24746 24747 /** 24748 * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the 24749 * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for 24750 * the yAxis. 24751 * 24752 * @param datapoints 24753 * @param group 24754 * @returns {Array} 24755 * @private 24756 */ 24757 LineGraph.prototype._convertYcoordinates = function (datapoints, group) { 24758 var extractedData = []; 24759 var xValue, yValue; 24760 var toScreen = this.body.util.toScreen; 24761 var axis = this.yAxisLeft; 24762 var svgHeight = Number(this.svg.style.height.replace('px', '')); 24763 if (group.options.yAxisOrientation == 'right') { 24764 axis = this.yAxisRight; 24765 } 24766 24767 for (var i = 0; i < datapoints.length; i++) { 24768 var labelValue = datapoints[i].label ? datapoints[i].label : null; 24769 xValue = toScreen(datapoints[i].x) + this.props.width; 24770 yValue = Math.round(axis.convertValue(datapoints[i].y)); 24771 extractedData.push({ x: xValue, y: yValue, label: labelValue }); 24772 } 24773 24774 group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0))); 24775 24776 return extractedData; 24777 }; 24778 24779 module.exports = LineGraph; 24780 24781/***/ }, 24782/* 51 */ 24783/***/ function(module, exports, __webpack_require__) { 24784 24785 'use strict'; 24786 24787 var util = __webpack_require__(1); 24788 var DOMutil = __webpack_require__(7); 24789 var Component = __webpack_require__(26); 24790 var DataStep = __webpack_require__(52); 24791 24792 /** 24793 * A horizontal time axis 24794 * @param {Object} [options] See DataAxis.setOptions for the available 24795 * options. 24796 * @constructor DataAxis 24797 * @extends Component 24798 * @param body 24799 */ 24800 function DataAxis(body, options, svg, linegraphOptions) { 24801 this.id = util.randomUUID(); 24802 this.body = body; 24803 24804 this.defaultOptions = { 24805 orientation: 'left', // supported: 'left', 'right' 24806 showMinorLabels: true, 24807 showMajorLabels: true, 24808 icons: true, 24809 majorLinesOffset: 7, 24810 minorLinesOffset: 4, 24811 labelOffsetX: 10, 24812 labelOffsetY: 2, 24813 iconWidth: 20, 24814 width: '40px', 24815 visible: true, 24816 alignZeros: true, 24817 left: { 24818 range: { min: undefined, max: undefined }, 24819 format: function format(value) { 24820 return value; 24821 }, 24822 title: { text: undefined, style: undefined } 24823 }, 24824 right: { 24825 range: { min: undefined, max: undefined }, 24826 format: function format(value) { 24827 return value; 24828 }, 24829 title: { text: undefined, style: undefined } 24830 } 24831 }; 24832 24833 this.linegraphOptions = linegraphOptions; 24834 this.linegraphSVG = svg; 24835 this.props = {}; 24836 this.DOMelements = { // dynamic elements 24837 lines: {}, 24838 labels: {}, 24839 title: {} 24840 }; 24841 24842 this.dom = {}; 24843 24844 this.range = { start: 0, end: 0 }; 24845 24846 this.options = util.extend({}, this.defaultOptions); 24847 this.conversionFactor = 1; 24848 24849 this.setOptions(options); 24850 this.width = Number(('' + this.options.width).replace('px', '')); 24851 this.minWidth = this.width; 24852 this.height = this.linegraphSVG.offsetHeight; 24853 this.hidden = false; 24854 24855 this.stepPixels = 25; 24856 this.zeroCrossing = -1; 24857 this.amountOfSteps = -1; 24858 24859 this.lineOffset = 0; 24860 this.master = true; 24861 this.svgElements = {}; 24862 this.iconsRemoved = false; 24863 24864 this.groups = {}; 24865 this.amountOfGroups = 0; 24866 24867 // create the HTML DOM 24868 this._create(); 24869 24870 var me = this; 24871 this.body.emitter.on('verticalDrag', function () { 24872 me.dom.lineContainer.style.top = me.body.domProps.scrollTop + 'px'; 24873 }); 24874 } 24875 24876 DataAxis.prototype = new Component(); 24877 24878 DataAxis.prototype.addGroup = function (label, graphOptions) { 24879 if (!this.groups.hasOwnProperty(label)) { 24880 this.groups[label] = graphOptions; 24881 } 24882 this.amountOfGroups += 1; 24883 }; 24884 24885 DataAxis.prototype.updateGroup = function (label, graphOptions) { 24886 this.groups[label] = graphOptions; 24887 }; 24888 24889 DataAxis.prototype.removeGroup = function (label) { 24890 if (this.groups.hasOwnProperty(label)) { 24891 delete this.groups[label]; 24892 this.amountOfGroups -= 1; 24893 } 24894 }; 24895 24896 DataAxis.prototype.setOptions = function (options) { 24897 if (options) { 24898 var redraw = false; 24899 if (this.options.orientation != options.orientation && options.orientation !== undefined) { 24900 redraw = true; 24901 } 24902 var fields = ['orientation', 'showMinorLabels', 'showMajorLabels', 'icons', 'majorLinesOffset', 'minorLinesOffset', 'labelOffsetX', 'labelOffsetY', 'iconWidth', 'width', 'visible', 'left', 'right', 'alignZeros']; 24903 util.selectiveExtend(fields, this.options, options); 24904 24905 this.minWidth = Number(('' + this.options.width).replace('px', '')); 24906 24907 if (redraw === true && this.dom.frame) { 24908 this.hide(); 24909 this.show(); 24910 } 24911 } 24912 }; 24913 24914 /** 24915 * Create the HTML DOM for the DataAxis 24916 */ 24917 DataAxis.prototype._create = function () { 24918 this.dom.frame = document.createElement('div'); 24919 this.dom.frame.style.width = this.options.width; 24920 this.dom.frame.style.height = this.height; 24921 24922 this.dom.lineContainer = document.createElement('div'); 24923 this.dom.lineContainer.style.width = '100%'; 24924 this.dom.lineContainer.style.height = this.height; 24925 this.dom.lineContainer.style.position = 'relative'; 24926 24927 // create svg element for graph drawing. 24928 this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 24929 this.svg.style.position = 'absolute'; 24930 this.svg.style.top = '0px'; 24931 this.svg.style.height = '100%'; 24932 this.svg.style.width = '100%'; 24933 this.svg.style.display = 'block'; 24934 this.dom.frame.appendChild(this.svg); 24935 }; 24936 24937 DataAxis.prototype._redrawGroupIcons = function () { 24938 DOMutil.prepareElements(this.svgElements); 24939 24940 var x; 24941 var iconWidth = this.options.iconWidth; 24942 var iconHeight = 15; 24943 var iconOffset = 4; 24944 var y = iconOffset + 0.5 * iconHeight; 24945 24946 if (this.options.orientation === 'left') { 24947 x = iconOffset; 24948 } else { 24949 x = this.width - iconWidth - iconOffset; 24950 } 24951 24952 var groupArray = Object.keys(this.groups); 24953 groupArray.sort(function (a, b) { 24954 return a < b ? -1 : 1; 24955 }); 24956 24957 for (var i = 0; i < groupArray.length; i++) { 24958 var groupId = groupArray[i]; 24959 if (this.groups[groupId].visible === true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] === true)) { 24960 this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight); 24961 y += iconHeight + iconOffset; 24962 } 24963 } 24964 24965 DOMutil.cleanupElements(this.svgElements); 24966 this.iconsRemoved = false;
24967 }; 24968 24969 DataAxis.prototype._cleanupIcons = function () { 24970 if (this.iconsRemoved === false) { 24971 DOMutil.prepareElements(this.svgElements); 24972 DOMutil.cleanupElements(this.svgElements); 24973 this.iconsRemoved = true; 24974 } 24975 }; 24976 24977 /** 24978 * Create the HTML DOM for the DataAxis 24979 */ 24980 DataAxis.prototype.show = function () { 24981 this.hidden = false; 24982 if (!this.dom.frame.parentNode) { 24983 if (this.options.orientation === 'left') { 24984 this.body.dom.left.appendChild(this.dom.frame); 24985 } else { 24986 this.body.dom.right.appendChild(this.dom.frame); 24987 } 24988 } 24989 24990 if (!this.dom.lineContainer.parentNode) { 24991 this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer); 24992 } 24993 }; 24994 24995 /** 24996 * Create the HTML DOM for the DataAxis 24997 */ 24998 DataAxis.prototype.hide = function () { 24999 this.hidden = true; 25000 if (this.dom.frame.parentNode) { 25001 this.dom.frame.parentNode.removeChild(this.dom.frame); 25002 } 25003 25004 if (this.dom.lineContainer.parentNode) { 25005 this.dom.lineContainer.parentNode.removeChild(this.dom.lineContainer); 25006 } 25007 }; 25008 25009 /** 25010 * Set a range (start and end) 25011 * @param end 25012 * @param start 25013 * @param end 25014 */ 25015 DataAxis.prototype.setRange = function (start, end) { 25016 if (this.master === false && this.options.alignZeros === true && this.zeroCrossing != -1) { 25017 if (start > 0) { 25018 start = 0; 25019 } 25020 } 25021 this.range.start = start; 25022 this.range.end = end; 25023 }; 25024 25025 /** 25026 * Repaint the component 25027 * @return {boolean} Returns true if the component is resized 25028 */ 25029 DataAxis.prototype.redraw = function () { 25030 var resized = false; 25031 var activeGroups = 0; 25032 25033 // Make sure the line container adheres to the vertical scrolling. 25034 this.dom.lineContainer.style.top = this.body.domProps.scrollTop + 'px'; 25035 25036 for (var groupId in this.groups) { 25037 if (this.groups.hasOwnProperty(groupId)) { 25038 if (this.groups[groupId].visible === true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] === true)) { 25039 activeGroups++; 25040 } 25041 } 25042 } 25043 if (this.amountOfGroups === 0 || activeGroups === 0) { 25044 this.hide(); 25045 } else { 25046 this.show(); 25047 this.height = Number(this.linegraphSVG.style.height.replace('px', '')); 25048 25049 // svg offsetheight did not work in firefox and explorer... 25050 this.dom.lineContainer.style.height = this.height + 'px'; 25051 this.width = this.options.visible === true ? Number(('' + this.options.width).replace('px', '')) : 0; 25052 25053 var props = this.props; 25054 var frame = this.dom.frame; 25055 25056 // update classname 25057 frame.className = 'vis-data-axis'; 25058 25059 // calculate character width and height 25060 this._calculateCharSize(); 25061 25062 var orientation = this.options.orientation; 25063 var showMinorLabels = this.options.showMinorLabels; 25064 var showMajorLabels = this.options.showMajorLabels; 25065 25066 // determine the width and height of the elements for the axis 25067 props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0; 25068 props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0; 25069 25070 props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset; 25071 props.minorLineHeight = 1; 25072 props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset; 25073 props.majorLineHeight = 1; 25074 25075 // take frame offline while updating (is almost twice as fast) 25076 if (orientation === 'left') { 25077 frame.style.top = '0'; 25078 frame.style.left = '0'; 25079 frame.style.bottom = ''; 25080 frame.style.width = this.width + 'px'; 25081 frame.style.height = this.height + 'px'; 25082 this.props.width = this.body.domProps.left.width; 25083 this.props.height = this.body.domProps.left.height; 25084 } else { 25085 // right 25086 frame.style.top = ''; 25087 frame.style.bottom = '0'; 25088 frame.style.left = '0'; 25089 frame.style.width = this.width + 'px'; 25090 frame.style.height = this.height + 'px'; 25091 this.props.width = this.body.domProps.right.width; 25092 this.props.height = this.body.domProps.right.height; 25093 } 25094 25095 resized = this._redrawLabels(); 25096 resized = this._isResized() || resized; 25097 25098 if (this.options.icons === true) { 25099 this._redrawGroupIcons(); 25100 } else { 25101 this._cleanupIcons(); 25102 } 25103 25104 this._redrawTitle(orientation); 25105 } 25106 return resized; 25107 }; 25108 25109 /** 25110 * Repaint major and minor text labels and vertical grid lines 25111 * @private 25112 */ 25113 DataAxis.prototype._redrawLabels = function () { 25114 var resized = false;
25115 DOMutil.prepareElements(this.DOMelements.lines); 25116 DOMutil.prepareElements(this.DOMelements.labels); 25117 var orientation = this.options['orientation']; 25118 25119 // get the range for the slaved axis 25120 var step; 25121 if (this.master === false) { 25122 var stepSize, rangeStart, rangeEnd, minimumStep; 25123 if (this.zeroCrossing !== -1 && this.options.alignZeros === true) { 25124 if (this.range.end > 0) { 25125 stepSize = this.range.end / this.zeroCrossing; // size of one step 25126 rangeStart = this.range.end - this.amountOfSteps * stepSize; 25127 rangeEnd = this.range.end; 25128 } else { 25129 // all of the range (including start) has to be done before the zero crossing. 25130 stepSize = -1 * this.range.start / (this.amountOfSteps - this.zeroCrossing); // absolute size of a step 25131 rangeStart = this.range.start; 25132 rangeEnd = this.range.start + stepSize * this.amountOfSteps; 25133 } 25134 } else { 25135 rangeStart = this.range.start; 25136 rangeEnd = this.range.end; 25137 } 25138 minimumStep = this.stepPixels; 25139 } else { 25140 // calculate range and step (step such that we have space for 7 characters per label) 25141 minimumStep = this.props.majorCharHeight; 25142 rangeStart = this.range.start; 25143 rangeEnd = this.range.end; 25144 } 25145 25146 this.step = step = new DataStep(rangeStart, rangeEnd, minimumStep, this.dom.frame.offsetHeight, this.options[this.options.orientation].range, this.options[this.options.orientation].format, this.master === false && this.options.alignZeros // does the step have to align zeros? only if not master and the options is on 25147 ); 25148 25149 // the slave axis needs to use the same horizontal lines as the master axis. 25150 if (this.master === true) { 25151 this.stepPixels = this.dom.frame.offsetHeight / step.marginRange * step.step; 25152 this.amountOfSteps = Math.ceil(this.dom.frame.offsetHeight / this.stepPixels); 25153 } else { 25154 // align with zero 25155 if (this.options.alignZeros === true && this.zeroCrossing !== -1) { 25156 // distance is the amount of steps away from the zero crossing we are. 25157 var distance = (step.current - this.zeroCrossing * step.step) / step.step; 25158 this.step.shift(distance); 25159 } 25160 } 25161 25162 // value at the bottom of the SVG 25163 this.valueAtBottom = step.marginEnd; 25164 25165 this.maxLabelSize = 0; 25166 var y = 0; // init value 25167 var stepIndex = 0; // init value 25168 var isMajor = false; // init value 25169 while (stepIndex < this.amountOfSteps) { 25170 y = Math.round(stepIndex * this.stepPixels); 25171 isMajor = step.isMajor(); 25172 25173 if (stepIndex > 0 && stepIndex !== this.amountOfSteps) { 25174 if (this.options['showMinorLabels'] && isMajor === false || this.master === false && this.options['showMinorLabels'] === true) { 25175 this._redrawLabel(y - 2, step.getCurrent(), orientation, 'vis-y-axis vis-minor', this.props.minorCharHeight); 25176 } 25177 25178 if (isMajor && this.options['showMajorLabels'] && this.master === true || this.options['showMinorLabels'] === false && this.master === false && isMajor === true) { 25179 if (y >= 0) { 25180 this._redrawLabel(y - 2, step.getCurrent(), orientation, 'vis-y-axis vis-major', this.props.majorCharHeight); 25181 } 25182 this._redrawLine(y, orientation, 'vis-grid vis-horizontal vis-major', this.options.majorLinesOffset, this.props.majorLineWidth); 25183 } else { 25184 this._redrawLine(y, orientation, 'vis-grid vis-horizontal vis-minor', this.options.minorLinesOffset, this.props.minorLineWidth); 25185 } 25186 } 25187 25188 // get zero crossing 25189 if (this.master === true && step.current === 0) { 25190 this.zeroCrossing = stepIndex; 25191 } 25192 25193 step.next(); 25194 stepIndex += 1; 25195 } 25196 25197 // get zero crossing if it's the last step 25198 if (this.master === true && step.current === 0) { 25199 this.zeroCrossing = stepIndex; 25200 } 25201 25202 this.conversionFactor = this.stepPixels / step.step; 25203 25204 // Note that title is rotated, so we're using the height, not width! 25205 var titleWidth = 0; 25206 if (this.options[orientation].title !== undefined && this.options[orientation].title.text !== undefined) { 25207 titleWidth = this.props.titleCharHeight; 25208 } 25209 var offset = this.options.icons === true ? Math.max(this.options.iconWidth, titleWidth) + this.options.labelOffsetX + 15 : titleWidth + this.options.labelOffsetX + 15; 25210 25211 // this will resize the yAxis to accommodate the labels. 25212 if (this.maxLabelSize > this.width - offset && this.options.visible === true) { 25213 this.width = this.maxLabelSize + offset; 25214 this.options.width = this.width + 'px'; 25215 DOMutil.cleanupElements(this.DOMelements.lines); 25216 DOMutil.cleanupElements(this.DOMelements.labels); 25217 this.redraw(); 25218 resized = true; 25219 } 25220 // this will resize the yAxis if it is too big for the labels. 25221 else if (this.maxLabelSize < this.width - offset && this.options.visible === true && this.width > this.minWidth) { 25222 this.width = Math.max(this.minWidth, this.maxLabelSize + offset); 25223 this.options.width = this.width + 'px'; 25224 DOMutil.cleanupElements(this.DOMelements.lines); 25225 DOMutil.cleanupElements(this.DOMelements.labels); 25226 this.redraw(); 25227 resized = true; 25228 } else { 25229 DOMutil.cleanupElements(this.DOMelements.lines); 25230 DOMutil.cleanupElements(this.DOMelements.labels); 25231 resized = false;
25232 } 25233 25234 return resized; 25235 }; 25236 25237 DataAxis.prototype.convertValue = function (value) { 25238 var invertedValue = this.valueAtBottom - value; 25239 var convertedValue = invertedValue * this.conversionFactor; 25240 return convertedValue; 25241 }; 25242 25243 DataAxis.prototype.screenToValue = function (x) { 25244 return this.valueAtBottom - x / this.conversionFactor; 25245 }; 25246 25247 /** 25248 * Create a label for the axis at position x 25249 * @private 25250 * @param y 25251 * @param text 25252 * @param orientation 25253 * @param className 25254 * @param characterHeight 25255 */ 25256 DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) { 25257 // reuse redundant label 25258 var label = DOMutil.getDOMElement('div', this.DOMelements.labels, this.dom.frame); //this.dom.redundant.labels.shift(); 25259 label.className = className; 25260 label.innerHTML = text; 25261 if (orientation === 'left') { 25262 label.style.left = '-' + this.options.labelOffsetX + 'px'; 25263 label.style.textAlign = 'right'; 25264 } else { 25265 label.style.right = '-' + this.options.labelOffsetX + 'px'; 25266 label.style.textAlign = 'left'; 25267 } 25268 25269 label.style.top = y - 0.5 * characterHeight + this.options.labelOffsetY + 'px'; 25270 25271 text += ''; 25272 25273 var largestWidth = Math.max(this.props.majorCharWidth, this.props.minorCharWidth); 25274 if (this.maxLabelSize < text.length * largestWidth) { 25275 this.maxLabelSize = text.length * largestWidth; 25276 } 25277 }; 25278 25279 /** 25280 * Create a minor line for the axis at position y 25281 * @param y 25282 * @param orientation 25283 * @param className 25284 * @param offset 25285 * @param width 25286 */ 25287 DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) { 25288 if (this.master === true) { 25289 var line = DOMutil.getDOMElement('div', this.DOMelements.lines, this.dom.lineContainer); //this.dom.redundant.lines.shift(); 25290 line.className = className; 25291 line.innerHTML = ''; 25292 25293 if (orientation === 'left') { 25294 line.style.left = this.width - offset + 'px'; 25295 } else { 25296 line.style.right = this.width - offset + 'px'; 25297 } 25298 25299 line.style.width = width + 'px'; 25300 line.style.top = y + 'px'; 25301 } 25302 }; 25303 25304 /** 25305 * Create a title for the axis 25306 * @private 25307 * @param orientation 25308 */ 25309 DataAxis.prototype._redrawTitle = function (orientation) { 25310 DOMutil.prepareElements(this.DOMelements.title); 25311 25312 // Check if the title is defined for this axes 25313 if (this.options[orientation].title !== undefined && this.options[orientation].title.text !== undefined) { 25314 var title = DOMutil.getDOMElement('div', this.DOMelements.title, this.dom.frame); 25315 title.className = 'vis-y-axis vis-title vis-' + orientation; 25316 title.innerHTML = this.options[orientation].title.text; 25317 25318 // Add style - if provided 25319 if (this.options[orientation].title.style !== undefined) { 25320 util.addCssText(title, this.options[orientation].title.style); 25321 } 25322 25323 if (orientation === 'left') { 25324 title.style.left = this.props.titleCharHeight + 'px'; 25325 } else { 25326 title.style.right = this.props.titleCharHeight + 'px'; 25327 } 25328 25329 title.style.width = this.height + 'px'; 25330 } 25331 25332 // we need to clean up in case we did not use all elements. 25333 DOMutil.cleanupElements(this.DOMelements.title); 25334 }; 25335 25336 /** 25337 * Determine the size of text on the axis (both major and minor axis). 25338 * The size is calculated only once and then cached in this.props. 25339 * @private 25340 */ 25341 DataAxis.prototype._calculateCharSize = function () { 25342 // determine the char width and height on the minor axis 25343 if (!('minorCharHeight' in this.props)) { 25344 var textMinor = document.createTextNode('0'); 25345 var measureCharMinor = document.createElement('div'); 25346 measureCharMinor.className = 'vis-y-axis vis-minor vis-measure'; 25347 measureCharMinor.appendChild(textMinor); 25348 this.dom.frame.appendChild(measureCharMinor); 25349 25350 this.props.minorCharHeight = measureCharMinor.clientHeight; 25351 this.props.minorCharWidth = measureCharMinor.clientWidth; 25352 25353 this.dom.frame.removeChild(measureCharMinor); 25354 } 25355 25356 if (!('majorCharHeight' in this.props)) { 25357 var textMajor = document.createTextNode('0'); 25358 var measureCharMajor = document.createElement('div'); 25359 measureCharMajor.className = 'vis-y-axis vis-major vis-measure'; 25360 measureCharMajor.appendChild(textMajor); 25361 this.dom.frame.appendChild(measureCharMajor); 25362 25363 this.props.majorCharHeight = measureCharMajor.clientHeight; 25364 this.props.majorCharWidth = measureCharMajor.clientWidth; 25365 25366 this.dom.frame.removeChild(measureCharMajor); 25367 } 25368 25369 if (!('titleCharHeight' in this.props)) { 25370 var textTitle = document.createTextNode('0'); 25371 var measureCharTitle = document.createElement('div'); 25372 measureCharTitle.className = 'vis-y-axis vis-title vis-measure'; 25373 measureCharTitle.appendChild(textTitle); 25374 this.dom.frame.appendChild(measureCharTitle); 25375 25376 this.props.titleCharHeight = measureCharTitle.clientHeight; 25377 this.props.titleCharWidth = measureCharTitle.clientWidth; 25378 25379 this.dom.frame.removeChild(measureCharTitle); 25380 } 25381 }; 25382 25383 module.exports = DataAxis; 25384 25385/***/ }, 25386/* 52 */
25387/***/ function(module, exports) { 25388 25389 /** 25390 * @constructor DataStep 25391 * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an 25392 * end data point. The class itself determines the best scale (step size) based on the 25393 * provided start Date, end Date, and minimumStep. 25394 * 25395 * If minimumStep is provided, the step size is chosen as close as possible 25396 * to the minimumStep but larger than minimumStep. If minimumStep is not 25397 * provided, the scale is set to 1 DAY. 25398 * The minimumStep should correspond with the onscreen size of about 6 characters 25399 * 25400 * Alternatively, you can set a scale by hand. 25401 * After creation, you can initialize the class by executing first(). Then you 25402 * can iterate from the start date to the end date via next(). You can check if 25403 * the end date is reached with the function hasNext(). After each step, you can 25404 * retrieve the current date via getCurrent(). 25405 * The DataStep has scales ranging from milliseconds, seconds, minutes, hours, 25406 * days, to years. 25407 * 25408 * Version: 1.2 25409 * 25410 * @param {Date} [start] The start date, for example new Date(2010, 9, 21) 25411 * or new Date(2010, 9, 21, 23, 45, 00) 25412 * @param {Date} [end] The end date 25413 * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds 25414 */ 25415 'use strict'; 25416 25417 function DataStep(start, end, minimumStep, containerHeight, customRange, formattingFunction, alignZeros) { 25418 // variables 25419 this.current = 0; 25420 25421 this.autoScale = true; 25422 this.stepIndex = 0; 25423 this.step = 1; 25424 this.scale = 1; 25425 this.formattingFunction = formattingFunction; 25426 25427 this.marginStart; 25428 this.marginEnd; 25429 this.deadSpace = 0; 25430 25431 this.majorSteps = [1, 2, 5, 10]; 25432 this.minorSteps = [0.25, 0.5, 1, 2]; 25433 25434 this.alignZeros = alignZeros; 25435 25436 this.setRange(start, end, minimumStep, containerHeight, customRange); 25437 } 25438 25439 /** 25440 * Set a new range 25441 * If minimumStep is provided, the step size is chosen as close as possible 25442 * to the minimumStep but larger than minimumStep. If minimumStep is not 25443 * provided, the scale is set to 1 DAY. 25444 * The minimumStep should correspond with the onscreen size of about 6 characters 25445 * @param {Number} [start] The start date and time. 25446 * @param {Number} [end] The end date and time. 25447 * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds 25448 */ 25449 DataStep.prototype.setRange = function (start, end, minimumStep, containerHeight, customRange) { 25450 this._start = customRange.min === undefined ? start : customRange.min; 25451 this._end = customRange.max === undefined ? end : customRange.max; 25452 if (this._start === this._end) { 25453 this._start = customRange.min === undefined ? this._start - 0.75 : this._start; 25454 this._end = customRange.max === undefined ? this._end + 1 : this._end;; 25455 } 25456 25457 if (this.autoScale === true) { 25458 this.setMinimumStep(minimumStep, containerHeight); 25459 } 25460 25461 this.setFirst(customRange); 25462 }; 25463 25464 /** 25465 * Automatically determine the scale that bests fits the provided minimum step 25466 * @param {Number} [minimumStep] The minimum step size in pixels 25467 */ 25468 DataStep.prototype.setMinimumStep = function (minimumStep, containerHeight) { 25469 // round to floor 25470 var range = this._end - this._start; 25471 var safeRange = range * 1.2; 25472 var minimumStepValue = minimumStep * (safeRange / containerHeight); 25473 var orderOfMagnitude = Math.round(Math.log(safeRange) / Math.LN10); 25474 25475 var minorStepIdx = -1; 25476 var magnitudefactor = Math.pow(10, orderOfMagnitude); 25477 25478 var start = 0; 25479 if (orderOfMagnitude < 0) { 25480 start = orderOfMagnitude; 25481 } 25482 25483 var solutionFound = false; 25484 for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) { 25485 magnitudefactor = Math.pow(10, i); 25486 for (var j = 0; j < this.minorSteps.length; j++) { 25487 var stepSize = magnitudefactor * this.minorSteps[j]; 25488 if (stepSize >= minimumStepValue) { 25489 solutionFound = true; 25490 minorStepIdx = j; 25491 break; 25492 } 25493 } 25494 if (solutionFound === true) { 25495 break; 25496 } 25497 } 25498 this.stepIndex = minorStepIdx; 25499 this.scale = magnitudefactor; 25500 this.step = magnitudefactor * this.minorSteps[minorStepIdx]; 25501 }; 25502 25503 /** 25504 * Round the current date to the first minor date value 25505 * This must be executed once when the current date is set to start Date 25506 */ 25507 DataStep.prototype.setFirst = function (customRange) { 25508 if (customRange === undefined) { 25509 customRange = {}; 25510 } 25511 25512 var niceStart = customRange.min === undefined ? this._start - this.scale * 2 * this.minorSteps[this.stepIndex] : customRange.min; 25513 var niceEnd = customRange.max === undefined ? this._end + this.scale * this.minorSteps[this.stepIndex] : customRange.max; 25514 25515 this.marginEnd = customRange.max === undefined ? this.roundToMinor(niceEnd) : customRange.max; 25516 this.marginStart = customRange.min === undefined ? this.roundToMinor(niceStart) : customRange.min; 25517 25518 // if we need to align the zero's we need to make sure that there is a zero to use. 25519 if (this.alignZeros === true && (this.marginEnd - this.marginStart) % this.step != 0) { 25520 this.marginEnd += this.marginEnd % this.step; 25521 } 25522 25523 this.deadSpace = this.roundToMinor(niceEnd) - niceEnd + this.roundToMinor(niceStart) - niceStart; 25524 this.marginRange = this.marginEnd - this.marginStart; 25525 25526 this.current = this.marginEnd; 25527 }; 25528 25529 DataStep.prototype.roundToMinor = function (value) { 25530 var rounded = value - value % (this.scale * this.minorSteps[this.stepIndex]); 25531 if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) { 25532 return rounded + this.scale * this.minorSteps[this.stepIndex]; 25533 } else { 25534 return rounded; 25535 } 25536 }; 25537 25538 /** 25539 * Check if the there is a next step 25540 * @return {boolean} true if the current date has not passed the end date 25541 */ 25542 DataStep.prototype.hasNext = function () { 25543 return this.current >= this.marginStart; 25544 }; 25545 25546 /** 25547 * Do the next step 25548 */ 25549 DataStep.prototype.next = function () { 25550 var prev = this.current; 25551 this.current -= this.step; 25552 25553 // safety mechanism: if current time is still unchanged, move to the end 25554 if (this.current === prev) { 25555 this.current = this._end; 25556 } 25557 }; 25558 25559 /** 25560 * Do the next step 25561 */ 25562 DataStep.prototype.previous = function () { 25563 this.current += this.step; 25564 this.marginEnd += this.step; 25565 this.marginRange = this.marginEnd - this.marginStart; 25566 }; 25567 25568 /** 25569 * Get the current datetime 25570 * @return {String} current The current date 25571 */ 25572 DataStep.prototype.getCurrent = function () {
vendor: 7,103 bytes, lines 25573-25770
25573 // prevent round-off errors when close to zero 25574 var current = Math.abs(this.current) < this.step / 2 ? 0 : this.current; 25575 var returnValue = current.toPrecision(5); 25576 if (typeof this.formattingFunction === 'function') { 25577 returnValue = this.formattingFunction(current); 25578 } 25579 25580 if (typeof returnValue === 'number') { 25581 return '' + returnValue; 25582 } else if (typeof returnValue === 'string') { 25583 return returnValue; 25584 } else { 25585 return current.toPrecision(5); 25586 } 25587 }; 25588 25589 /** 25590 * Check if the current value is a major value (for example when the step 25591 * is DAY, a major value is each first day of the MONTH) 25592 * @return {boolean} true if current date is major, else false. 25593 */ 25594 DataStep.prototype.isMajor = function () { 25595 return this.current % (this.scale * this.majorSteps[this.stepIndex]) === 0; 25596 }; 25597 25598 DataStep.prototype.shift = function (steps) { 25599 if (steps < 0) { 25600 for (var i = 0; i < -steps; i++) { 25601 this.previous(); 25602 } 25603 } else if (steps > 0) { 25604 for (var i = 0; i < steps; i++) { 25605 this.next(); 25606 } 25607 } 25608 }; 25609 25610 module.exports = DataStep; 25611 25612/***/ }, 25613/* 53 */ 25614/***/ function(module, exports, __webpack_require__) { 25615 25616 'use strict'; 25617 25618 var util = __webpack_require__(1); 25619 var DOMutil = __webpack_require__(7); 25620 var Line = __webpack_require__(54); 25621 var Bar = __webpack_require__(56); 25622 var Points = __webpack_require__(55); 25623 25624 /** 25625 * /** 25626 * @param {object} group | the object of the group from the dataset 25627 * @param {string} groupId | ID of the group 25628 * @param {object} options | the default options 25629 * @param {array} groupsUsingDefaultStyles | this array has one entree. 25630 * It is passed as an array so it is passed by reference. 25631 * It enumerates through the default styles 25632 * @constructor 25633 */ 25634 function GraphGroup(group, groupId, options, groupsUsingDefaultStyles) { 25635 this.id = groupId; 25636 var fields = ['sampling', 'style', 'sort', 'yAxisOrientation', 'barChart', 'drawPoints', 'shaded', 'interpolation']; 25637 this.options = util.selectiveBridgeObject(fields, options); 25638 this.usingDefaultStyle = group.className === undefined; 25639 this.groupsUsingDefaultStyles = groupsUsingDefaultStyles; 25640 this.zeroPosition = 0; 25641 this.update(group); 25642 if (this.usingDefaultStyle == true) { 25643 this.groupsUsingDefaultStyles[0] += 1; 25644 } 25645 this.itemsData = []; 25646 this.visible = group.visible === undefined ? true : group.visible; 25647 } 25648 25649 /** 25650 * this loads a reference to all items in this group into this group. 25651 * @param {array} items 25652 */ 25653 GraphGroup.prototype.setItems = function (items) { 25654 if (items != null) { 25655 this.itemsData = items; 25656 if (this.options.sort == true) { 25657 this.itemsData.sort(function (a, b) { 25658 return a.x - b.x; 25659 }); 25660 } 25661 // typecast all items to numbers. Takes around 10ms for 500.000 items 25662 for (var i = 0; i < this.itemsData.length; i++) { 25663 this.itemsData[i].y = Number(this.itemsData[i].y); 25664 } 25665 } else { 25666 this.itemsData = []; 25667 } 25668 }; 25669 25670 /** 25671 * this is used for plotting barcharts, this way, we only have to calculate it once. 25672 * @param pos 25673 */ 25674 GraphGroup.prototype.setZeroPosition = function (pos) { 25675 this.zeroPosition = pos; 25676 }; 25677 25678 /** 25679 * set the options of the graph group over the default options. 25680 * @param options 25681 */ 25682 GraphGroup.prototype.setOptions = function (options) { 25683 if (options !== undefined) { 25684 var fields = ['sampling', 'style', 'sort', 'yAxisOrientation', 'barChart', 'excludeFromLegend']; 25685 util.selectiveDeepExtend(fields, this.options, options); 25686 25687 // if the group's drawPoints is a function delegate the callback to the onRender property 25688 if (typeof options.drawPoints == 'function') { 25689 options.drawPoints = { 25690 onRender: options.drawPoints 25691 }; 25692 } 25693 25694 util.mergeOptions(this.options, options, 'interpolation'); 25695 util.mergeOptions(this.options, options, 'drawPoints'); 25696 util.mergeOptions(this.options, options, 'shaded'); 25697 25698 if (options.interpolation) { 25699 if (typeof options.interpolation == 'object') { 25700 if (options.interpolation.parametrization) { 25701 if (options.interpolation.parametrization == 'uniform') { 25702 this.options.interpolation.alpha = 0; 25703 } else if (options.interpolation.parametrization == 'chordal') { 25704 this.options.interpolation.alpha = 1.0; 25705 } else { 25706 this.options.interpolation.parametrization = 'centripetal'; 25707 this.options.interpolation.alpha = 0.5; 25708 } 25709 } 25710 } 25711 } 25712 } 25713 25714 if (this.options.style == 'line') { 25715 this.type = new Line(this.id, this.options); 25716 } else if (this.options.style == 'bar') { 25717 this.type = new Bar(this.id, this.options); 25718 } else if (this.options.style == 'points') { 25719 this.type = new Points(this.id, this.options); 25720 } 25721 }; 25722 25723 /** 25724 * this updates the current group class with the latest group dataset entree, used in _updateGroup in linegraph 25725 * @param group 25726 */ 25727 GraphGroup.prototype.update = function (group) { 25728 this.group = group; 25729 this.content = group.content || 'graph'; 25730 this.className = group.className || this.className || 'vis-graph-group' + this.groupsUsingDefaultStyles[0] % 10; 25731 this.visible = group.visible === undefined ? true : group.visible; 25732 this.style = group.style; 25733 this.setOptions(group.options); 25734 }; 25735 25736 /** 25737 * draw the icon for the legend. 25738 * 25739 * @param x 25740 * @param y 25741 * @param JSONcontainer 25742 * @param SVGcontainer 25743 * @param iconWidth 25744 * @param iconHeight 25745 */ 25746 GraphGroup.prototype.drawIcon = function (x, y, JSONcontainer, SVGcontainer, iconWidth, iconHeight) { 25747 var fillHeight = iconHeight * 0.5; 25748 var path, fillPath; 25749 25750 var outline = DOMutil.getSVGElement('rect', JSONcontainer, SVGcontainer); 25751 outline.setAttributeNS(null, 'x', x); 25752 outline.setAttributeNS(null, 'y', y - fillHeight); 25753 outline.setAttributeNS(null, 'width', iconWidth); 25754 outline.setAttributeNS(null, 'height', 2 * fillHeight); 25755 outline.setAttributeNS(null, 'class', 'vis-outline'); 25756 25757 if (this.options.style == 'line') { 25758 path = DOMutil.getSVGElement('path', JSONcontainer, SVGcontainer); 25759 path.setAttributeNS(null, 'class', this.className); 25760 if (this.style !== undefined) { 25761 path.setAttributeNS(null, 'style', this.style); 25762 } 25763 25764 path.setAttributeNS(null, 'd', 'M' + x + ',' + y + ' L' + (x + iconWidth) + ',' + y + ''); 25765 if (this.options.shaded.enabled == true) { 25766 fillPath = DOMutil.getSVGElement('path', JSONcontainer, SVGcontainer); 25767 if (this.options.shaded.orientation == 'top') { 25768 fillPath.setAttributeNS(null, 'd', 'M' + x + ', ' + (y - fillHeight) + 'L' + x + ',' + y + ' L' + (x + iconWidth) + ',' + y + ' L' + (x + iconWidth) + ',' + (y - fillHeight)); 25769 } else { 25770 fillPath.setAttributeNS(null, 'd', 'M' + x + ',' + y + ' ' + '
25770L' + x + ',' + (y + fillHeight) + ' ' + 'L' + (x + iconWidth) + ',' + (y + fillHeight) + 'L' + (x + iconWidth) + ',' + y); 25771 } 25772 fillPath.setAttributeNS(null, 'class', this.className + ' vis-icon-fill'); 25773 } 25774 25775 if (this.options.drawPoints.enabled == true) { 25776 var groupTemplate = { 25777 style: this.options.drawPoints.style, 25778 styles: this.options.drawPoints.styles, 25779 size: this.options.drawPoints.size, 25780 className: this.className 25781 }; 25782 DOMutil.drawPoint(x + 0.5 * iconWidth, y, groupTemplate, JSONcontainer, SVGcontainer); 25783 } 25784 } else { 25785 var barWidth = Math.round(0.3 * iconWidth); 25786 var bar1Height = Math.round(0.4 * iconHeight); 25787 var bar2Height = Math.round(0.75 * iconHeight); 25788 25789 var offset = Math.round((iconWidth - 2 * barWidth) / 3); 25790 25791 DOMutil.drawBar(x + 0.5 * barWidth + offset, y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' vis-bar', JSONcontainer, SVGcontainer, this.style); 25792 DOMutil.drawBar(x + 1.5 * barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' vis-bar', JSONcontainer, SVGcontainer, this.style); 25793 } 25794 }; 25795 25796 /** 25797 * return the legend entree for this group. 25798 * 25799 * @param iconWidth 25800 * @param iconHeight 25801 * @returns {{icon: HTMLElement, label: (group.content|*|string), orientation: (.options.yAxisOr
25801ientation|*)}} 25802 */ 25803 GraphGroup.prototype.getLegend = function (iconWidth, iconHeight) { 25804 var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 25805 this.drawIcon(0, 0.5 * iconHeight, [], svg, iconWidth, iconHeight); 25806 return { icon: svg, label: this.content, orientation: this.options.yAxisOrientation }; 25807 }; 25808 25809 GraphGroup.prototype.getYRange = function (groupData) { 25810 return this.type.getYRange(groupData); 25811 }; 25812 25813 GraphGroup.prototype.getData = function (groupData) { 25814 return this.type.getData(groupData); 25815 }; 25816 25817 GraphGroup.prototype.draw = function (dataset, group, framework) { 25818 this.type.draw(dataset, group, framework); 25819 }; 25820 25821 module.exports = GraphGroup; 25822 25823/***/ }, 25824/* 54 */ 25825/***/ function(module, exports, __webpack_require__) { 25826 25827 'use strict'; 25828 25829 var DOMutil = __webpack_require__(7); 25830 var Points = __webpack_require__(55); 25831 25832 function Line(groupId, options) { 25833 this.groupId = groupId; 25834 this.options = options; 25835 } 25836 25837 Line.prototype.getData = function (groupData) { 25838 var combinedData = []; 25839 for (var j = 0; j < groupData.length; j++) { 25840 combinedData.push({ 25841 x: groupData[j].x, 25842 y: groupData[j].y, 25843 groupId: this.groupId 25844 }); 25845 } 25846 return combinedData; 25847 }; 25848 25849 Line.prototype.getYRange = function (groupData) { 25850 var yMin = groupData[0].y; 25851 var yMax = groupData[0].y; 25852 for (var j = 0; j < groupData.length; j++) { 25853 yMin = yMin > groupData[j].y ? groupData[j].y : yMin; 25854 yMax = yMax < groupData[j].y ? groupData[j].y : yMax; 25855 } 25856 return { min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOr
vendor: 9,780 bytes, lines 25856-26137
25856ientation }; 25857 }; 25858 25859 Line.getStackedYRange = function (combinedData, groupRanges, groupIds, groupLabel, orientation) { 25860 if (combinedData.length > 0) { 25861 // sort by time and by group 25862 combinedData.sort(function (a, b) { 25863 if (a.x === b.x) { 25864 return a.groupId < b.groupId ? -1 : 1; 25865 } else { 25866 return a.x - b.x; 25867 } 25868 }); 25869 var intersections = {}; 25870 25871 Line._getDataIntersections(intersections, combinedData); 25872 groupRanges[groupLabel] = Line._getStackedYRange(intersections, combinedData); 25873 groupRanges[groupLabel].yAxisOrientation = orientation; 25874 groupIds.push(groupLabel); 25875 } 25876 }; 25877 25878 Line._getStackedYRange = function (intersections, combinedData) { 25879 var key; 25880 var yMin = combinedData[0].y; 25881 var yMax = combinedData[0].y; 25882 for (var i = 0; i < combinedData.length; i++) { 25883 key = combinedData[i].x; 25884 if (intersections[key] === undefined) { 25885 yMin = yMin > combinedData[i].y ? combinedData[i].y : yMin; 25886 yMax = yMax < combinedData[i].y ? combinedData[i].y : yMax; 25887 } else { 25888 if (combinedData[i].y < 0) { 25889 intersections[key].accumulatedNegative += combinedData[i].y; 25890 } else { 25891 intersections[key].accumulatedPositive += combinedData[i].y; 25892 } 25893 } 25894 } 25895 for (var xpos in intersections) { 25896 if (intersections.hasOwnProperty(xpos)) { 25897 yMin = yMin > intersections[xpos].accumulatedNegative ? intersections[xpos].accumulatedNegative : yMin; 25898 yMin = yMin > intersections[xpos].accumulatedPositive ? intersections[xpos].accumulatedPositive : yMin; 25899 yMax = yMax < intersections[xpos].accumulatedNegative ? intersections[xpos].accumulatedNegative : yMax; 25900 yMax = yMax < intersections[xpos].accumulatedPositive ? intersections[xpos].accumulatedPositive : yMax; 25901 } 25902 } 25903 25904 return { min: yMin, max: yMax }; 25905 }; 25906 25907 /** 25908 * Fill the intersections object with counters of how many datapoints share the same x coordinates 25909 * @param intersections 25910 * @param combinedData 25911 * @private 25912 */ 25913 Line._getDataIntersections = function (intersections, combinedData) { 25914 // get intersections 25915 var coreDistance; 25916 for (var i = 0; i < combinedData.length; i++) { 25917 if (i + 1 < combinedData.length) { 25918 coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x); 25919 } 25920 if (i > 0) { 25921 coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x)); 25922 } 25923 if (coreDistance === 0) { 25924 if (intersections[combinedData[i].x] === undefined) { 25925 intersections[combinedData[i].x] = { amount: 0, resolved: 0, accumulatedPositive: 0, accumulatedNegative: 0 }; 25926 } 25927 intersections[combinedData[i].x].amount += 1; 25928 } 25929 } 25930 }; 25931 25932 /** 25933 * draw a line graph 25934 * 25935 * @param dataset 25936 * @param group 25937 */ 25938 Line.prototype.draw = function (dataset, group, framework) { 25939 if (dataset != null) { 25940 if (dataset.length > 0) { 25941 var path, d; 25942 var svgHeight = Number(framework.svg.style.height.replace('px', '')); 25943 path = DOMutil.getSVGElement('path', framework.svgElements, framework.svg); 25944 path.setAttributeNS(null, 'class', group.className); 25945 if (group.style !== undefined) { 25946 path.setAttributeNS(null, 'style', group.style); 25947 } 25948 25949 // construct path from dataset 25950 if (group.options.interpolation.enabled == true) { 25951 d = Line._catmullRom(dataset, group); 25952 } else { 25953 d = Line._linear(dataset); 25954 } 25955 25956 // append with points for fill and finalize the path 25957 if (group.options.shaded.enabled == true) { 25958 var fillPath = DOMutil.getSVGElement('path', framework.svgElements, framework.svg); 25959 var dFill; 25960 if (group.options.shaded.orientation == 'top') { 25961 dFill = 'M' + dataset[0].x + ',' + 0 + ' ' + d + 'L' + dataset[dataset.length - 1].x + ',' + 0; 25962 } else { 25963 dFill = 'M' + dataset[0].x + ',' + svgHeight + ' ' + d + 'L' + dataset[dataset.length - 1].x + ',' + svgHeight; 25964 } 25965 fillPath.setAttributeNS(null, 'class', group.className + ' vis-fill'); 25966 if (group.options.shaded.style !== undefined) { 25967 fillPath.setAttributeNS(null, 'style', group.options.shaded.style); 25968 } 25969 fillPath.setAttributeNS(null, 'd', dFill); 25970 } 25971 // copy properties to path for drawing. 25972 path.setAttributeNS(null, 'd', 'M' + d); 25973 25974 // draw points 25975 if (group.options.drawPoints.enabled == true) { 25976 Points.draw(dataset, group, framework); 25977 } 25978 } 25979 } 25980 }; 25981 25982 /** 25983 * This uses an uniform parametrization of the interpolation algorithm: 25984 * 'On the Parameterization of Catmull-Rom Curves' by Cem Yuksel et al. 25985 * @param data 25986 * @returns {string} 25987 * @private 25988 */ 25989 Line._catmullRomUniform = function (data) { 25990 // catmull rom 25991 var p0, p1, p2, p3, bp1, bp2; 25992 var d = Math.round(data[0].x) + ',' + Math.round(data[0].y) + ' '; 25993 var normalization = 1 / 6; 25994 var length = data.length; 25995 for (var i = 0; i < length - 1; i++) { 25996 25997 p0 = i == 0 ? data[0] : data[i - 1]; 25998 p1 = data[i]; 25999 p2 = data[i + 1]; 26000 p3 = i + 2 < length ? data[i + 2] : p2; 26001 26002 // Catmull-Rom to Cubic Bezier conversion matrix 26003 // 0 1 0 0 26004 // -1/6 1 1/6 0 26005 // 0 1/6 1 -1/6 26006 // 0 0 1 0 26007 26008 // bp0 = { x: p1.x, y: p1.y }; 26009 bp1 = { x: (-p0.x + 6 * p1.x + p2.x) * normalization, y: (-p0.y + 6 * p1.y + p2.y) * normalization }; 26010 bp2 = { x: (p1.x + 6 * p2.x - p3.x) * normalization, y: (p1.y + 6 * p2.y - p3.y) * normalization }; 26011 // bp0 = { x: p2.x, y: p2.y }; 26012 26013 d += 'C' + bp1.x + ',' + bp1.y + ' ' + bp2.x + ',' + bp2.y + ' ' + p2.x + ',' + p2.y + ' '; 26014 } 26015 26016 return d; 26017 }; 26018 26019 /** 26020 * This uses either the chordal or centripetal parameterization of the catmull-rom algorithm. 26021 * By default, the centripetal parameterization is used because this gives the nicest results. 26022 * These parameterizations are relatively heavy because the distance between 4 points have to be calculated. 26023 * 26024 * One optimization can be used to reuse distances since this is a sliding window approach. 26025 * @param data 26026 * @param group 26027 * @returns {string} 26028 * @private 26029 */ 26030 Line._catmullRom = function (data, group) { 26031 var alpha = group.options.interpolation.alpha; 26032 if (alpha == 0 || alpha === undefined) { 26033 return this._catmullRomUniform(data); 26034 } else { 26035 var p0, p1, p2, p3, bp1, bp2, d1, d2, d3, A, B, N, M; 26036 var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA; 26037 var d = Math.round(data[0].x) + ',' + Math.round(data[0].y) + ' '; 26038 var length = data.length; 26039 for (var i = 0; i < length - 1; i++) { 26040 26041 p0 = i == 0 ? data[0] : data[i - 1]; 26042 p1 = data[i]; 26043 p2 = data[i + 1]; 26044 p3 = i + 2 < length ? data[i + 2] : p2; 26045 26046 d1 = Math.sqrt(Math.pow(p0.x - p1.x, 2) + Math.pow(p0.y - p1.y, 2)); 26047 d2 = Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); 26048 d3 = Math.sqrt(Math.pow(p2.x - p3.x, 2) + Math.pow(p2.y - p3.y, 2)); 26049 26050 // Catmull-Rom to Cubic Bezier conversion matrix 26051 26052 // A = 2d1^2a + 3d1^a * d2^a + d3^2a 26053 // B = 2d3^2a + 3d3^a * d2^a + d2^2a 26054 26055 // [ 0 1 0 0 ] 26056 // [ -d2^2a /N A/N d1^2a /N 0 ] 26057 // [ 0 d3^2a /M B/M -d2^2a /M ] 26058 // [ 0 0 1 0 ] 26059 26060 d3powA = Math.pow(d3, alpha); 26061 d3pow2A = Math.pow(d3, 2 * alpha); 26062 d2powA = Math.pow(d2, alpha); 26063 d2pow2A = Math.pow(d2, 2 * alpha); 26064 d1powA = Math.pow(d1, alpha); 26065 d1pow2A = Math.pow(d1, 2 * alpha); 26066 26067 A = 2 * d1pow2A + 3 * d1powA * d2powA + d2pow2A; 26068 B = 2 * d3pow2A + 3 * d3powA * d2powA + d2pow2A; 26069 N = 3 * d1powA * (d1powA + d2powA); 26070 if (N > 0) { 26071 N = 1 / N; 26072 } 26073 M = 3 * d3powA * (d3powA + d2powA); 26074 if (M > 0) { 26075 M = 1 / M; 26076 } 26077 26078 bp1 = { x: (-d2pow2A * p0.x + A * p1.x + d1pow2A * p2.x) * N, 26079 y: (-d2pow2A * p0.y + A * p1.y + d1pow2A * p2.y) * N }; 26080 26081 bp2 = { x: (d3pow2A * p1.x + B * p2.x - d2pow2A * p3.x) * M, 26082 y: (d3pow2A * p1.y + B * p2.y - d2pow2A * p3.y) * M }; 26083 26084 if (bp1.x == 0 && bp1.y == 0) { 26085 bp1 = p1; 26086 } 26087 if (bp2.x == 0 && bp2.y == 0) { 26088 bp2 = p2; 26089 } 26090 d += 'C' + bp1.x + ',' + bp1.y + ' ' + bp2.x + ',' + bp2.y + ' ' + p2.x + ',' + p2.y + ' '; 26091 } 26092 26093 return d; 26094 } 26095 }; 26096 26097 /** 26098 * this generates the SVG path for a linear drawing between datapoints. 26099 * @param data 26100 * @returns {string} 26101 * @private 26102 */ 26103 Line._linear = function (data) { 26104 // linear 26105 var d = ''; 26106 for (var i = 0; i < data.length; i++) { 26107 if (i == 0) { 26108 d += data[i].x + ',' + data[i].y; 26109 } else { 26110 d += ' ' + data[i].x + ',' + data[i].y; 26111 } 26112 } 26113 return d; 26114 }; 26115 26116 module.exports = Line; 26117 26118/***/ }, 26119/* 55 */ 26120/***/ function(module, exports, __webpack_require__) { 26121 26122 'use strict'; 26123 26124 var DOMutil = __webpack_require__(7); 26125 26126 function Points(groupId, options) { 26127 this.groupId = groupId; 26128 this.options = options; 26129 } 26130 26131 Points.prototype.getYRange = function (groupData) { 26132 var yMin = groupData[0].y; 26133 var yMax = groupData[0].y; 26134 for (var j = 0; j < groupData.length; j++) { 26135 yMin = yMin > groupData[j].y ? groupData[j].y : yMin; 26136 yMax = yMax < groupData[j].y ? groupData[j].y : yMax; 26137 }
26138 return { min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation }; 26139 }; 26140 26141 Points.prototype.draw = function (dataset, group, framework, offset) { 26142 Points.draw(dataset, group, framework, offset); 26143 }; 26144 26145 /** 26146 * draw the data points 26147 * 26148 * @param {Array} dataset 26149 * @param {Object} JSONcontainer 26150 * @param {Object} svg | SVG DOM element 26151 * @param {GraphGroup} group 26152 * @param {Number} [offset] 26153 */ 26154 Points.draw = function (dataset, group, framework, offset) { 26155 offset = offset || 0; 26156 var callback = getCallback(); 26157 26158 for (var i = 0; i < dataset.length; i++) { 26159 if (!callback) { 26160 // draw the point the simple way. 26161 DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, getGroupTemplate(), framework.svgElements, framework.svg, dataset[i].label); 26162 } else { 26163 var callbackResult = callback(dataset[i], group, framework); // result might be true, false or an object 26164 if (callbackResult === true || typeof callbackResult === 'object') { 26165 DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, getGroupTemplate(callbackResult), framework.svgElements, framework.svg, dataset[i].label); 26166 } 26167 } 26168 } 26169 26170 function getGroupTemplate(callbackResult) { 26171 callbackResult = typeof callbackResult === 'undefined' ? {} : callbackResult; 26172 return { 26173 style: callbackResult.style || group.options.drawPoints.style, 26174 styles: callbackResult.styles || group.options.drawPoints.styles, 26175 size: callbackResult.size || group.options.drawPoints.size, 26176 className: callbackResult.className || group.className 26177 }; 26178 } 26179 26180 function getCallback() { 26181 var callback = undefined; 26182 // check for the graph2d onRender 26183 if (framework.options.drawPoints.onRender && typeof framework.options.drawPoints.onRender == 'function') { 26184 callback = framework.options.drawPoints.onRender; 26185 } 26186 26187 // override it with the group onRender if defined 26188 if (group.group.options && group.group.options.drawPoints && group.group.options.drawPoints.onRender && typeof group.group.options.drawPoints.onRender == 'function') { 26189 callback = group.group.options.drawPoints.onRender; 26190 } 26191 26192 return callback; 26193 } 26194 }; 26195 26196 module.exports = Points; 26197 26198/***/ }, 26199/* 56 */ 26200/***/ function(module, exports, __webpack_require__) { 26201 26202 'use strict'; 26203 26204 var DOMutil = __webpack_require__(7); 26205 var Points = __webpack_require__(55); 26206 26207 function Bargraph(groupId, options) { 26208 this.groupId = groupId; 26209 this.options = options; 26210 } 26211 26212 Bargraph.prototype.getYRange = function (groupData) { 26213 var yMin = groupData[0].y; 26214 var yMax = groupData[0].y; 26215 for (var j = 0; j < groupData.length; j++) { 26216 yMin = yMin > groupData[j].y ? groupData[j].y : yMin; 26217 yMax = yMax < groupData[j].y ? groupData[j].y : yMax; 26218 } 26219 return { min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOr
vendor: 1,725 bytes, lines 26219-26284
26219ientation }; 26220 }; 26221 26222 Bargraph.prototype.getData = function (groupData) { 26223 var combinedData = []; 26224 for (var j = 0; j < groupData.length; j++) { 26225 combinedData.push({ 26226 x: groupData[j].x, 26227 y: groupData[j].y, 26228 groupId: this.groupId 26229 }); 26230 } 26231 return combinedData; 26232 }; 26233 26234 /** 26235 * draw a bar graph 26236 * 26237 * @param groupIds 26238 * @param processedGroupData 26239 */ 26240 Bargraph.draw = function (groupIds, processedGroupData, framework) { 26241 var combinedData = []; 26242 var intersections = {}; 26243 var coreDistance; 26244 var key, drawData; 26245 var group; 26246 var i, j; 26247 var barPoints = 0; 26248 26249 // combine all barchart data 26250 for (i = 0; i < groupIds.length; i++) { 26251 group = framework.groups[groupIds[i]]; 26252 if (group.options.style === 'bar') { 26253 if (group.visible === true && (framework.options.groups.visibility[groupIds[i]] === undefined || framework.options.groups.visibility[groupIds[i]] === true)) { 26254 for (j = 0; j < processedGroupData[groupIds[i]].length; j++) { 26255 combinedData.push({ 26256 x: processedGroupData[groupIds[i]][j].x, 26257 y: processedGroupData[groupIds[i]][j].y, 26258 groupId: groupIds[i], 26259 label: processedGroupData[groupIds[i]][j].label 26260 }); 26261 barPoints += 1; 26262 } 26263 } 26264 } 26265 } 26266 26267 if (barPoints === 0) { 26268 return; 26269 } 26270 26271 // sort by time and by group 26272 combinedData.sort(function (a, b) { 26273 if (a.x === b.x) { 26274 return a.groupId < b.groupId ? -1 : 1; 26275 } else { 26276 return a.x - b.x; 26277 } 26278 }); 26279 26280 // get intersections 26281 Bargraph._getDataIntersections(intersections, combinedData); 26282 26283 // plot barchart 26284 for (i = 0;
26284 i < combinedData.length; i++) { 26285 group = framework.groups[combinedData[i].groupId]; 26286 var minWidth = 0.1 * group.options.barChart.width; 26287 26288 key = combinedData[i].x; 26289 var heightOffset = 0; 26290 if (intersections[key] === undefined) { 26291 if (i + 1 < combinedData.length) { 26292 coreDistance = Math.abs(combinedData[i + 1].x - key); 26293 } 26294 if (i > 0) { 26295 coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - key)); 26296 } 26297 drawData = Bargraph._getSafeDrawData(coreDistance, group, minWidth); 26298 } else { 26299 var nextKey = i + (intersections[key].amount - intersections[key].resolved); 26300 var prevKey = i - (intersections[key].resolved + 1); 26301 if (nextKey < combinedData.length) { 26302 coreDistance = Math.abs(combinedData[nextKey].x - key); 26303 } 26304 if (prevKey > 0) { 26305 coreDistance = Math.min(coreDistance, Math.abs(combinedData[prevKey].x - key)); 26306 } 26307 drawData = Bargraph._getSafeDrawData(coreDistance, group, minWidth); 26308 intersections[key].resolved += 1; 26309 26310 if (group.options.stack === true) { 26311 if (combinedData[i].y < group.zeroPosition) { 26312 heightOffset = intersections[key].accumulatedNegative; 26313 intersections[key].accumulatedNegative += group.zeroPosition - combinedData[i].y; 26314 } else { 26315 heightOffset = intersections[key].accumulatedPositive; 26316 intersections[key].accumulatedPositive += group.zeroPosition - combinedData[i].y; 26317 } 26318 } else if (group.options.barChart.sideBySide === true) { 26319 drawData.width = drawData.width / intersections[key].amount; 26320 drawData.offset += intersections[key].resolved * drawData.width - 0.5 * drawData.width * (intersections[key].amount + 1); 26321 if (group.options.barChart.align === 'left') { 26322 drawData.offset -= 0.5 * drawData.width; 26323 } else if (group.options.barChart.align === 'right') { 26324 drawData.offset += 0.5 * drawData.width; 26325 } 26326 } 26327 } 26328 DOMutil.drawBar(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, drawData.width, group.zeroPosition - combinedData[i].y, group.className + ' vis-bar', framework.svgElements, framework.svg, group.style); 26329 // draw points 26330 if (group.options.drawPoints.enabled === true) { 26331 var pointData = { 26332 x: combinedData[i].x + drawData.offset, 26333 y: combinedData[i].y - heightOffset, 26334 groupId: combinedData[i].groupId, 26335 label: combinedData[i].label 26336 }; 26337 Points.draw([pointData], group, framework, drawData.offset); 26338 //DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y, group, framework.svgElements, framework.svg); 26339 } 26340 } 26341 }; 26342 26343 /** 26344 * Fill the intersections object with counters of how many datapoints share the same x coordinates 26345 * @param intersections 26346 * @param combinedData 26347 * @private 26348 */ 26349 Bargraph._getDataIntersections = function (intersections, combinedData) { 26350 // get intersections 26351 var coreDistance; 26352 for (var i = 0;
vendor: 11,072 bytes, lines 26352-26673
26352 i < combinedData.length; i++) { 26353 if (i + 1 < combinedData.length) { 26354 coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x); 26355 } 26356 if (i > 0) { 26357 coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x)); 26358 } 26359 if (coreDistance === 0) { 26360 if (intersections[combinedData[i].x] === undefined) { 26361 intersections[combinedData[i].x] = { amount: 0, resolved: 0, accumulatedPositive: 0, accumulatedNegative: 0 }; 26362 } 26363 intersections[combinedData[i].x].amount += 1; 26364 } 26365 } 26366 }; 26367 26368 /** 26369 * Get the width and offset for bargraphs based on the coredistance between datapoints 26370 * 26371 * @param coreDistance 26372 * @param group 26373 * @param minWidth 26374 * @returns {{width: Number, offset: Number}} 26375 * @private 26376 */ 26377 Bargraph._getSafeDrawData = function (coreDistance, group, minWidth) { 26378 var width, offset; 26379 if (coreDistance < group.options.barChart.width && coreDistance > 0) { 26380 width = coreDistance < minWidth ? minWidth : coreDistance; 26381 26382 offset = 0; // recalculate offset with the new width; 26383 if (group.options.barChart.align === 'left') { 26384 offset -= 0.5 * coreDistance; 26385 } else if (group.options.barChart.align === 'right') { 26386 offset += 0.5 * coreDistance; 26387 } 26388 } else { 26389 // default settings 26390 width = group.options.barChart.width; 26391 offset = 0; 26392 if (group.options.barChart.align === 'left') { 26393 offset -= 0.5 * group.options.barChart.width; 26394 } else if (group.options.barChart.align === 'right') { 26395 offset += 0.5 * group.options.barChart.width; 26396 } 26397 } 26398 26399 return { width: width, offset: offset }; 26400 }; 26401 26402 Bargraph.getStackedYRange = function (combinedData, groupRanges, groupIds, groupLabel, orientation) { 26403 if (combinedData.length > 0) { 26404 // sort by time and by group 26405 combinedData.sort(function (a, b) { 26406 if (a.x === b.x) { 26407 return a.groupId < b.groupId ? -1 : 1; 26408 } else { 26409 return a.x - b.x; 26410 } 26411 }); 26412 var intersections = {}; 26413 26414 Bargraph._getDataIntersections(intersections, combinedData); 26415 groupRanges[groupLabel] = Bargraph._getStackedYRange(intersections, combinedData); 26416 groupRanges[groupLabel].yAxisOrientation = orientation; 26417 groupIds.push(groupLabel); 26418 } 26419 }; 26420 26421 Bargraph._getStackedYRange = function (intersections, combinedData) { 26422 var key; 26423 var yMin = combinedData[0].y; 26424 var yMax = combinedData[0].y; 26425 for (var i = 0; i < combinedData.length; i++) { 26426 key = combinedData[i].x; 26427 if (intersections[key] === undefined) { 26428 yMin = yMin > combinedData[i].y ? combinedData[i].y : yMin; 26429 yMax = yMax < combinedData[i].y ? combinedData[i].y : yMax; 26430 } else { 26431 if (combinedData[i].y < 0) { 26432 intersections[key].accumulatedNegative += combinedData[i].y; 26433 } else { 26434 intersections[key].accumulatedPositive += combinedData[i].y; 26435 } 26436 } 26437 } 26438 for (var xpos in intersections) { 26439 if (intersections.hasOwnProperty(xpos)) { 26440 yMin = yMin > intersections[xpos].accumulatedNegative ? intersections[xpos].accumulatedNegative : yMin; 26441 yMin = yMin > intersections[xpos].accumulatedPositive ? intersections[xpos].accumulatedPositive : yMin; 26442 yMax = yMax < intersections[xpos].accumulatedNegative ? intersections[xpos].accumulatedNegative : yMax; 26443 yMax = yMax < intersections[xpos].accumulatedPositive ? intersections[xpos].accumulatedPositive : yMax; 26444 } 26445 } 26446 26447 return { min: yMin, max: yMax }; 26448 }; 26449 26450 module.exports = Bargraph; 26451 26452/***/ }, 26453/* 57 */ 26454/***/ function(module, exports, __webpack_require__) { 26455 26456 'use strict'; 26457 26458 var util = __webpack_require__(1); 26459 var DOMutil = __webpack_require__(7); 26460 var Component = __webpack_require__(26); 26461 26462 /** 26463 * Legend for Graph2d 26464 */ 26465 function Legend(body, options, side, linegraphOptions) { 26466 this.body = body; 26467 this.defaultOptions = { 26468 enabled: true, 26469 icons: true, 26470 iconSize: 20, 26471 iconSpacing: 6, 26472 left: { 26473 visible: true, 26474 position: 'top-left' // top/bottom - left,center,right 26475 }, 26476 right: { 26477 visible: true, 26478 position: 'top-left' // top/bottom - left,center,right 26479 } 26480 }; 26481 this.side = side; 26482 this.options = util.extend({}, this.defaultOptions); 26483 this.linegraphOptions = linegraphOptions; 26484 26485 this.svgElements = {}; 26486 this.dom = {}; 26487 this.groups = {}; 26488 this.amountOfGroups = 0; 26489 this._create(); 26490 26491 this.setOptions(options); 26492 } 26493 26494 Legend.prototype = new Component(); 26495 26496 Legend.prototype.clear = function () { 26497 this.groups = {}; 26498 this.amountOfGroups = 0; 26499 }; 26500 26501 Legend.prototype.addGroup = function (label, graphOptions) { 26502 26503 // Include a group only if the group option 'excludeFromLegend: false' is not set. 26504 if (graphOptions.options.excludeFromLegend != true) { 26505 if (!this.groups.hasOwnProperty(label)) { 26506 this.groups[label] = graphOptions; 26507 } 26508 this.amountOfGroups += 1; 26509 } 26510 }; 26511 26512 Legend.prototype.updateGroup = function (label, graphOptions) { 26513 this.groups[label] = graphOptions; 26514 }; 26515 26516 Legend.prototype.removeGroup = function (label) { 26517 if (this.groups.hasOwnProperty(label)) { 26518 delete this.groups[label]; 26519 this.amountOfGroups -= 1; 26520 } 26521 }; 26522 26523 Legend.prototype._create = function () { 26524 this.dom.frame = document.createElement('div'); 26525 this.dom.frame.className = 'vis-legend'; 26526 this.dom.frame.style.position = 'absolute'; 26527 this.dom.frame.style.top = '10px'; 26528 this.dom.frame.style.display = 'block'; 26529 26530 this.dom.textArea = document.createElement('div'); 26531 this.dom.textArea.className = 'vis-legend-text'; 26532 this.dom.textArea.style.position = 'relative'; 26533 this.dom.textArea.style.top = '0px'; 26534 26535 this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 26536 this.svg.style.position = 'absolute'; 26537 this.svg.style.top = 0 + 'px'; 26538 this.svg.style.width = this.options.iconSize + 5 + 'px'; 26539 this.svg.style.height = '100%'; 26540 26541 this.dom.frame.appendChild(this.svg); 26542 this.dom.frame.appendChild(this.dom.textArea); 26543 }; 26544 26545 /** 26546 * Hide the component from the DOM 26547 */ 26548 Legend.prototype.hide = function () { 26549 // remove the frame containing the items 26550 if (this.dom.frame.parentNode) { 26551 this.dom.frame.parentNode.removeChild(this.dom.frame); 26552 } 26553 }; 26554 26555 /** 26556 * Show the component in the DOM (when not already visible). 26557 * @return {Boolean} changed 26558 */ 26559 Legend.prototype.show = function () { 26560 // show frame containing the items 26561 if (!this.dom.frame.parentNode) { 26562 this.body.dom.center.appendChild(this.dom.frame); 26563 } 26564 }; 26565 26566 Legend.prototype.setOptions = function (options) { 26567 var fields = ['enabled', 'orientation', 'icons', 'left', 'right']; 26568 util.selectiveDeepExtend(fields, this.options, options); 26569 }; 26570 26571 Legend.prototype.redraw = function () { 26572 var activeGroups = 0; 26573 var groupArray = Object.keys(this.groups); 26574 groupArray.sort(function (a, b) { 26575 return a < b ? -1 : 1; 26576 }); 26577 26578 for (var i = 0; i < groupArray.length; i++) { 26579 var groupId = groupArray[i]; 26580 if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) { 26581 activeGroups++; 26582 } 26583 } 26584 26585 if (this.options[this.side].visible == false || this.amountOfGroups == 0 || this.options.enabled == false || activeGroups == 0) { 26586 this.hide(); 26587 } else { 26588 this.show(); 26589 if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'bottom-left') { 26590 this.dom.frame.style.left = '4px'; 26591 this.dom.frame.style.textAlign = 'left'; 26592 this.dom.textArea.style.textAlign = 'left'; 26593 this.dom.textArea.style.left = this.options.iconSize + 15 + 'px'; 26594 this.dom.textArea.style.right = ''; 26595 this.svg.style.left = 0 + 'px'; 26596 this.svg.style.right = ''; 26597 } else { 26598 this.dom.frame.style.right = '4px'; 26599 this.dom.frame.style.textAlign = 'right'; 26600 this.dom.textArea.style.textAlign = 'right'; 26601 this.dom.textArea.style.right = this.options.iconSize + 15 + 'px'; 26602 this.dom.textArea.style.left = ''; 26603 this.svg.style.right = 0 + 'px'; 26604 this.svg.style.left = ''; 26605 } 26606 26607 if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'top-right') { 26608 this.dom.frame.style.top = 4 - Number(this.body.dom.center.style.top.replace('px', '')) + 'px'; 26609 this.dom.frame.style.bottom = ''; 26610 } else { 26611 var scrollableHeight = this.body.domProps.center.height - this.body.domProps.centerContainer.height; 26612 this.dom.frame.style.bottom = 4 + scrollableHeight + Number(this.body.dom.center.style.top.replace('px', '')) + 'px'; 26613 this.dom.frame.style.top = ''; 26614 } 26615 26616 if (this.options.icons == false) { 26617 this.dom.frame.style.width = this.dom.textArea.offsetWidth + 10 + 'px'; 26618 this.dom.textArea.style.right = ''; 26619 this.dom.textArea.style.left = ''; 26620 this.svg.style.width = '0px'; 26621 } else { 26622 this.dom.frame.style.width = this.options.iconSize + 15 + this.dom.textArea.offsetWidth + 10 + 'px'; 26623 this.drawLegendIcons(); 26624 } 26625 26626 var content = ''; 26627 for (var i = 0; i < groupArray.length; i++) { 26628 var groupId = groupArray[i]; 26629 if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) { 26630 content += this.groups[groupId].content + '<br />'; 26631 } 26632 } 26633 this.dom.textArea.innerHTML = content; 26634 this.dom.textArea.style.lineHeight = 0.75 * this.options.iconSize + this.options.iconSpacing + 'px'; 26635 } 26636 }; 26637 26638 Legend.prototype.drawLegendIcons = function () { 26639 if (this.dom.frame.parentNode) { 26640 var groupArray = Object.keys(this.groups); 26641 groupArray.sort(function (a, b) { 26642 return a < b ? -1 : 1; 26643 }); 26644 26645 DOMutil.prepareElements(this.svgElements); 26646 var padding = window.getComputedStyle(this.dom.frame).paddingTop; 26647 var iconOffset = Number(padding.replace('px', '')); 26648 var x = iconOffset; 26649 var iconWidth = this.options.iconSize; 26650 var iconHeight = 0.75 * this.options.iconSize; 26651 var y = iconOffset + 0.5 * iconHeight + 3; 26652 26653 this.svg.style.width = iconWidth + 5 + iconOffset + 'px'; 26654 26655 for (var i = 0; i < groupArray.length; i++) { 26656 var groupId = groupArray[i]; 26657 if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) { 26658 this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight); 26659 y += iconHeight + this.options.iconSpacing; 26660 } 26661 } 26662 26663 DOMutil.cleanupElements(this.svgElements); 26664 } 26665 }; 26666 26667 module.exports = Legend; 26668 26669/***/ }, 26670/* 58 */ 26671/***/ function(module, exports) { 26672 26673 /**
26674 * This object contains all possible options. It will check if the types are correct, if required if the option is one 26675 * of the allowed values. 26676 * 26677 * __any__ means that the name of the property does not matter. 26678 * __type__ is a required field for all objects and contains the allowed types of all objects 26679 */ 26680 'use strict'; 26681 26682 Object.defineProperty(exports, '__esModule', { 26683 value: true 26684 }); 26685 var string = 'string'; 26686 var boolean = 'boolean'; 26687 var number = 'number'; 26688 var array = 'array'; 26689 var date = 'date'; 26690 var object = 'object'; // should only be in a __type__ property 26691 var dom = 'dom'; 26692 var moment = 'moment'; 26693 var any = 'any'; 26694 26695 var allOptions = { 26696 configure: { 26697 enabled: { boolean: boolean }, 26698 filter: { boolean: boolean, 'function': 'function' }, 26699 container: { dom: dom }, 26700 __type__: { object: object, boolean: boolean, 'function': 'function' } 26701 }, 26702 26703 //globals : 26704 yAxisOrientation: { string: ['left', 'right'] }, 26705 defaultGroup: { string: string }, 26706 sort: { boolean: boolean }, 26707 sampling: { boolean: boolean }, 26708 stack: { boolean: boolean }, 26709 graphHeight: { string: string, number: number }, 26710 shaded: { 26711 enabled: { boolean: boolean }, 26712 orientation: { string: ['bottom', 'top'] }, // top, bottom 26713 __type__: { boolean: boolean, object: object } 26714 }, 26715 style: { string: ['line', 'bar', 'points'] }, // line, bar 26716 barChart: { 26717 width: { number: number }, 26718 sideBySide: { boolean: boolean }, 26719 align: { string: ['left', 'center', 'right'] }, 26720 __type__: { object: object } 26721 }, 26722 interpolation: { 26723 enabled: { boolean: boolean }, 26724 parametrization: { string: ['centripetal', 'chordal', 'uniform'] }, // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5) 26725 alpha: { number: number }, 26726 __type__: { object: object, boolean: boolean } 26727 }, 26728 drawPoints: { 26729 enabled: { boolean: boolean }, 26730 onRender: { 'function': 'function' }, 26731 size: { number: number }, 26732 style: { string: ['square', 'circle'] }, // square, circle 26733 __type__: { object: object, boolean: boolean, 'function': 'function' } 26734 }, 26735 dataAxis: { 26736 showMinorLabels: { boolean: boolean }, 26737 showMajorLabels: { boolean: boolean }, 26738 icons: { boolean: boolean }, 26739 width: { string: string, number: number }, 26740 visible: { boolean: boolean }, 26741 alignZeros: { boolean: boolean }, 26742 left: { 26743 range: { min: { number: number }, max: { number: number }, __type__: { object: object } }, 26744 format: { 'function': 'function' }, 26745 title: { text: { string: string, number: number }, style: { string: string }, __type__: { object: object } }, 26746 __type__: { object: object } 26747 }, 26748 right: { 26749 range: { min: { number: number }, max: { number: number }, __type__: { object: object } }, 26750 format: { 'function': 'function' }, 26751 title: { text: { string: string, number: number }, style: { string: string }, __type__: { object: object } }, 26752 __type__: { object: object } 26753 }, 26754 __type__: { object: object } 26755 }, 26756 legend: { 26757 enabled: { boolean: boolean }, 26758 icons: { boolean: boolean }, 26759 left: { 26760 visible: { boolean: boolean }, 26761 position: { string: ['top-right', 'bottom-right', 'top-left', 'bottom-left'] }, 26762 __type__: { object: object } 26763 }, 26764 right: { 26765 visible: { boolean: boolean }, 26766 position: { string: ['top-right', 'bottom-right', 'top-left', 'bottom-left'] }, 26767 __type__: { object: object } 26768 }, 26769 __type__: { object: object, boolean: boolean } 26770 }, 26771 groups: { 26772 visibility: { any: any }, 26773 __type__: { object: object } 26774 }, 26775 26776 autoResize: { boolean: boolean }, 26777 throttleRedraw: { number: number }, 26778 clickToUse: { boolean: boolean }, 26779 end: { number: number, date: date, string: string, moment: moment }, 26780 format: { 26781 minorLabels: { 26782 millisecond: { string: string, 'undefined': 'undefined' }, 26783 second: { string: string, 'undefined': 'undefined' }, 26784 minute: { string: string, 'undefined': 'undefined' }, 26785 hour: { string: string, 'undefined': 'undefined' },
vendor: 8,388 bytes, lines 26786-27038
26786 weekday: { string: string, 'undefined': 'undefined' }, 26787 day: { string: string, 'undefined': 'undefined' }, 26788 month: { string: string, 'undefined': 'undefined' }, 26789 year: { string: string, 'undefined': 'undefined' }, 26790 __type__: { object: object } 26791 }, 26792 majorLabels: { 26793 millisecond: { string: string, 'undefined': 'undefined' }, 26794 second: { string: string, 'undefined': 'undefined' }, 26795 minute: { string: string, 'undefined': 'undefined' }, 26796 hour: { string: string, 'undefined': 'undefined' }, 26797 weekday: { string: string, 'undefined': 'undefined' }, 26798 day: { string: string, 'undefined': 'undefined' }, 26799 month: { string: string, 'undefined': 'undefined' }, 26800 year: { string: string, 'undefined': 'undefined' }, 26801 __type__: { object: object } 26802 }, 26803 __type__: { object: object } 26804 }, 26805 moment: { 'function': 'function' }, 26806 height: { string: string, number: number }, 26807 hiddenDates: { 26808 start: { date: date, number: number, string: string, moment: moment }, 26809 end: { date: date, number: number, string: string, moment: moment }, 26810 repeat: { string: string }, 26811 __type__: { object: object, array: array } 26812 }, 26813 locale: { string: string }, 26814 locales: { 26815 __any__: { any: any }, 26816 __type__: { object: object } 26817 }, 26818 max: { date: date, number: number, string: string, moment: moment }, 26819 maxHeight: { number: number, string: string }, 26820 min: { date: date, number: number, string: string, moment: moment }, 26821 minHeight: { number: number, string: string }, 26822 moveable: { boolean: boolean }, 26823 multiselect: { boolean: boolean }, 26824 orientation: { string: string }, 26825 showCurrentTime: { boolean: boolean }, 26826 showMajorLabels: { boolean: boolean }, 26827 showMinorLabels: { boolean: boolean }, 26828 start: { date: date, number: number, string: string, moment: moment }, 26829 timeAxis: { 26830 scale: { string: string, 'undefined': 'undefined' }, 26831 step: { number: number, 'undefined': 'undefined' }, 26832 __type__: { object: object } 26833 }, 26834 width: { string: string, number: number }, 26835 zoomable: { boolean: boolean }, 26836 zoomKey: { string: ['ctrlKey', 'altKey', 'metaKey', ''] }, 26837 zoomMax: { number: number }, 26838 zoomMin: { number: number }, 26839 __type__: { object: object } 26840 }; 26841 26842 var configureOptions = { 26843 global: { 26844 //yAxisOrientation: ['left','right'], // TDOO: enable as soon as Grahp2d doesn't crash when changing this on the fly 26845 sort: true, 26846 sampling: true, 26847 stack: false, 26848 shaded: { 26849 enabled: false, 26850 orientation: ['top', 'bottom'] // top, bottom 26851 }, 26852 style: ['line', 'bar', 'points'], // line, bar 26853 barChart: { 26854 width: [50, 5, 100, 5], 26855 sideBySide: false, 26856 align: ['left', 'center', 'right'] // left, center, right 26857 }, 26858 interpolation: { 26859 enabled: true, 26860 parametrization: ['centripetal', 'chordal', 'uniform'] // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5) 26861 }, 26862 drawPoints: { 26863 enabled: true, 26864 size: [6, 2, 30, 1], 26865 style: ['square', 'circle'] // square, circle 26866 }, 26867 dataAxis: { 26868 showMinorLabels: true, 26869 showMajorLabels: true, 26870 icons: false, 26871 width: [40, 0, 200, 1], 26872 visible: true, 26873 alignZeros: true, 26874 left: { 26875 //range: {min:'undefined': 'undefined'ined,max:'undefined': 'undefined'ined}, 26876 //format: function (value) {return value;}, 26877 title: { text: '', style: '' } 26878 }, 26879 right: { 26880 //range: {min:'undefined': 'undefined'ined,max:'undefined': 'undefined'ined}, 26881 //format: function (value) {return value;}, 26882 title: { text: '', style: '' } 26883 } 26884 }, 26885 legend: { 26886 enabled: false, 26887 icons: true, 26888 left: { 26889 visible: true, 26890 position: ['top-right', 'bottom-right', 'top-left', 'bottom-left'] // top/bottom - left,right 26891 }, 26892 right: { 26893 visible: true, 26894 position: ['top-right', 'bottom-right', 'top-left', 'bottom-left'] // top/bottom - left,right 26895 } 26896 }, 26897 26898 autoResize: true, 26899 throttleRedraw: [10, 0, 1000, 10], 26900 clickToUse: false, 26901 end: '', 26902 format: { 26903 minorLabels: { 26904 millisecond: 'SSS', 26905 second: 's', 26906 minute: 'HH:mm', 26907 hour: 'HH:mm', 26908 weekday: 'ddd D', 26909 day: 'D', 26910 month: 'MMM', 26911 year: 'YYYY' 26912 }, 26913 majorLabels: { 26914 millisecond: 'HH:mm:ss', 26915 second: 'D MMMM HH:mm', 26916 minute: 'ddd D MMMM', 26917 hour: 'ddd D MMMM', 26918 weekday: 'MMMM YYYY', 26919 day: 'MMMM YYYY', 26920 month: 'YYYY', 26921 year: '' 26922 } 26923 }, 26924 26925 height: '', 26926 locale: '', 26927 max: '', 26928 maxHeight: '', 26929 min: '', 26930 minHeight: '', 26931 moveable: true, 26932 orientation: ['both', 'bottom', 'top'], 26933 showCurrentTime: false, 26934 showMajorLabels: true, 26935 showMinorLabels: true, 26936 start: '', 26937 width: '100%', 26938 zoomable: true, 26939 zoomKey: ['ctrlKey', 'altKey', 'metaKey', ''], 26940 zoomMax: [315360000000000, 10, 315360000000000, 1], 26941 zoomMin: [10, 10, 315360000000000, 1] 26942 } 26943 }; 26944 26945 exports.allOptions = allOptions; 26946 exports.configureOptions = configureOptions; 26947 26948/***/ }, 26949/* 59 */ 26950/***/ function(module, exports, __webpack_require__) { 26951 26952 // Load custom shapes into CanvasRenderingContext2D 26953 'use strict'; 26954 26955 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 26956 26957 var _modulesGroups = __webpack_require__(60); 26958 26959 var _modulesGroups2 = _interopRequireDefault(_modulesGroups); 26960 26961 var _modulesNodesHandler = __webpack_require__(61); 26962 26963 var _modulesNodesHandler2 = _interopRequireDefault(_modulesNodesHandler); 26964 26965 var _modulesEdgesHandler = __webpack_require__(81); 26966 26967 var _modulesEdgesHandler2 = _interopRequireDefault(_modulesEdgesHandler); 26968 26969 var _modulesPhysicsEngine = __webpack_require__(90); 26970 26971 var _modulesPhysicsEngine2 = _interopRequireDefault(_modulesPhysicsEngine); 26972 26973 var _modulesClustering = __webpack_require__(99); 26974 26975 var _modulesClustering2 = _interopRequireDefault(_modulesClustering); 26976 26977 var _modulesCanvasRenderer = __webpack_require__(101); 26978 26979 var _modulesCanvasRenderer2 = _interopRequireDefault(_modulesCanvasRenderer); 26980 26981 var _modulesCanvas = __webpack_require__(102); 26982 26983 var _modulesCanvas2 = _interopRequireDefault(_modulesCanvas); 26984 26985 var _modulesView = __webpack_require__(103); 26986 26987 var _modulesView2 = _interopRequireDefault(_modulesView); 26988 26989 var _modulesInteractionHandler = __webpack_require__(105); 26990 26991 var _modulesInteractionHandler2 = _interopRequireDefault(_modulesInteractionHandler); 26992 26993 var _modulesSelectionHandler = __webpack_require__(108); 26994 26995 var _modulesSelectionHandler2 = _interopRequireDefault(_modulesSelectionHandler); 26996 26997 var _modulesLayoutEngine = __webpack_require__(109); 26998 26999 var _modulesLayoutEngine2 = _interopRequireDefault(_modulesLayoutEngine); 27000 27001 var _modulesManipulationSystem = __webpack_require__(110); 27002 27003 var _modulesManipulationSystem2 = _interopRequireDefault(_modulesManipulationSystem); 27004 27005 var _sharedConfigurator = __webpack_require__(45); 27006 27007 var _sharedConfigurator2 = _interopRequireDefault(_sharedConfigurator); 27008 27009 var _sharedValidator = __webpack_require__(47); 27010 27011 var _sharedValidator2 = _interopRequireDefault(_sharedValidator); 27012 27013 var _optionsJs = __webpack_require__(111); 27014 27015 var _modulesKamadaKawaiJs = __webpack_require__(112); 27016 27017 var _modulesKamadaKawaiJs2 = _interopRequireDefault(_modulesKamadaKawaiJs); 27018 27019 __webpack_require__(114); 27020 27021 var Emitter = __webpack_require__(12); 27022 var Hammer = __webpack_require__(20); 27023 var util = __webpack_require__(1); 27024 var DataSet = __webpack_require__(8); 27025 var DataView = __webpack_require__(10); 27026 var dotparser = __webpack_require__(115); 27027 var gephiParser = __webpack_require__(116); 27028 var Images = __webpack_require__(117); 27029 var Activator = __webpack_require__(40); 27030 var locales = __webpack_require__(118); 27031 27032 /** 27033 * @constructor Network 27034 * Create a network visualization, displaying nodes and edges. 27035 * 27036 * @param {Element} container The DOM element in which the Network will 27037 * be created. Normally a div element. 27038 * @param {Object}
27038 data An object containing parameters 27039 * {Array} nodes 27040 * {Array} edges 27041 * @param {Object} options Options 27042 */ 27043 function Network(container, data, options) { 27044 var _this = this; 27045 27046 if (!(this instanceof Network)) { 27047 throw new SyntaxError('Constructor must be called with the new operator'); 27048 } 27049 27050 // set constant values 27051 this.options = {}; 27052 this.defaultOptions = { 27053 locale: 'en', 27054 locales: locales, 27055 clickToUse: false 27056 }; 27057 util.extend(this.options, this.defaultOptions); 27058 27059 // containers for nodes and edges 27060 this.body = { 27061 container: container, 27062 nodes: {}, 27063 nodeIndices: [], 27064 edges: {}, 27065 edgeIndices: [], 27066 emitter: { 27067 on: this.on.bind(this), 27068 off: this.off.bind(this), 27069 emit: this.emit.bind(this), 27070 once: this.once.bind(this) 27071 }, 27072 eventListeners: { 27073 onTap: function onTap() {}, 27074 onTouch: function onTouch() {}, 27075 onDoubleTap: function onDoubleTap() {}, 27076 onHold: function onHold() {}, 27077 onDragStart: function onDragStart() {}, 27078 onDrag: function onDrag() {}, 27079 onDragEnd: function onDragEnd() {}, 27080 onMouseWheel: function onMouseWheel() {}, 27081 onPinch: function onPinch() {}, 27082 onMouseMove: function onMouseMove() {}, 27083 onRelease: function onRelease() {}, 27084 onContext: function onContext() {} 27085 }, 27086 data: { 27087 nodes: null, // A DataSet or DataView 27088 edges: null // A DataSet or DataView 27089 }, 27090 functions: { 27091 createNode: function createNode() {}, 27092 createEdge: function createEdge() {}, 27093 getPointer: function getPointer() {} 27094 }, 27095 modules: {}, 27096 view: { 27097 scale: 1, 27098 translation: { x: 0, y: 0 } 27099 } 27100 }; 27101 27102 // bind the event listeners 27103 this.bindEventListeners(); 27104 27105 // setting up all modules 27106 this.images = new Images(function () { 27107 return _this.body.emitter.emit('_requestRedraw'); 27108 }); // object with images 27109 this.groups = new _modulesGroups2['default'](); // object with groups 27110 this.canvas = new _modulesCanvas2['default'](this.body); // DOM handler 27111 this.selectionHandler = new _modulesSelectionHandler2['default'](this.body, this.canvas); // Selection handler 27112 this.interactionHandler = new _modulesInteractionHandler2['default'](this.body, this.canvas, this.selectionHandler); // Interaction handler handles all the hammer bindings (that are bound by canvas), key 27113 this.view = new _modulesView2['default'](this.body, this.canvas); // camera handler, does animations and zooms 27114 this.renderer = new _modulesCanvasRenderer2['default'](this.body, this.canvas); // renderer, starts renderloop, has events that modules can hook into 27115 this.physics = new _modulesPhysicsEngine2['default'](this.body); // physics engine, does all the simulations 27116 this.layoutEngine = new _modulesLayoutEngine2['default'](this.body); // layout engine for inital layout and hierarchical layout 27117 this.clustering = new _modulesClustering2['default'](this.body); // clustering api 27118 this.manipulation = new _modulesManipulationSystem2['default'](this.body, this.canvas, this.selectionHandler); // data manipulation system 27119 27120 this.nodesHandler = new _modulesNodesHandler2['default'](this.body, this.images, this.groups, this.layoutEngine); // Handle adding, deleting and updating of nodes as well as global options 27121 this.edgesHandler = new _modulesEdgesHandler2['default'](this.body, this.images, this.groups); // Handle adding, deleting and updating of edges as well as global options 27122 27123 this.body.modules['kamadaKawai'] = new _modulesKamadaKawaiJs2['default'](this.body, 150, 0.05); // Layouting algorithm. 27124 this.body.modules['clustering'] = this.clustering; 27125 27126 // create the DOM elements 27127 this.canvas._create(); 27128 27129 // apply options 27130 this.setOptions(options); 27131 27132 // load data (the disable start variable will be the same as the enabled clustering) 27133 this.setData(data); 27134 } 27135 27136 // Extend Network with an Emitter mixin 27137 Emitter(Network.prototype); 27138 27139 /** 27140 * Set options 27141 * @param {Object} options 27142 */ 27143 Network.prototype.setOptions = function (options) { 27144 var _this2 = this; 27145 27146 if (options !== undefined) { 27147 27148 var errorFound = _sharedValidator2['default'].validate(options, _optionsJs.allOptions); 27149 if (errorFound === true) { 27150 console.log('%cErrors have been found in the supplied options object.', _sharedValidator.printStyle); 27151 } 27152 27153 // copy the global fields over 27154 var fields = ['locale', 'locales', 'clickToUse']; 27155 util.selectiveDeepExtend(fields, this.options, options); 27156 27157 // the hierarchical system can adapt the edges and the physics to it's own options because not all c
27157ombinations work with the hierarichical system. 27158 options = this.layoutEngine.setOptions(options.layout, options); 27159 27160 this.canvas.setOptions(options); // options for canvas are in globals 27161 27162 // pass the options to the modules 27163 this.groups.setOptions(options.groups); 27164 this.nodesHandler.setOptions(options.nodes); 27165 this.edgesHandler.setOptions(options.edges); 27166 this.physics.setOptions(options.physics); 27167 this.manipulation.setOptions(options.manipulation, options, this.options); // manipulation uses the locales in the globals 27168 27169 this.interactionHandler.setOptions(options.interaction); 27170 this.renderer.setOptions(options.interaction); // options for rendering are in interaction 27171 this.selectionHandler.setOptions(options.interaction); // options for selection are in interaction 27172 27173 // reload the settings of the nodes to apply changes in groups that are not referenced by pointer. 27174 if (options.groups !== undefined) { 27175 this.body.emitter.emit('refreshNodes'); 27176 } 27177 // these two do not have options at the moment, here for completeness 27178 //this.view.setOptions(options.view); 27179 //this.clustering.setOptions(options.clustering); 27180 27181 if ('configure' in options) { 27182 if (!this.configurator) { 27183 this.configurator = new _sharedConfigurator2['default'](this, this.body.container, _optionsJs.configureOptions, this.canvas.pixelRatio); 27184 } 27185 27186 this.configurator.setOptions(options.configure); 27187 } 27188 27189 // if the configuration system is enabled, copy all options and put them into the config system 27190 if (this.configurator && this.configurator.options.enabled === true) { 27191 var networkOptions = { nodes: {}, edges: {}, layout: {}, interaction: {}, manipulation: {}, physics: {}, global: {} }; 27192 util.deepExtend(networkOptions.nodes, this.nodesHandler.options); 27193 util.deepExtend(networkOptions.edges, this.edgesHandler.options); 27194 util.deepExtend(networkOptions.layout, this.layoutEngine.options); 27195 // load the selectionHandler and render default options in to the interaction group 27196 util.deepExtend(networkOptions.interaction, this.selectionHandler.options); 27197 util.deepExtend(networkOptions.interaction, this.renderer.options); 27198 27199 util.deepExtend(networkOptions.interaction, this.interactionHandler.options); 27200 util.deepExtend(networkOptions.manipulation, this.manipulation.options); 27201 util.deepExtend(networkOptions.physics, this.physics.options); 27202 27203 // load globals into the global object 27204 util.deepExtend(networkOptions.global, this.canvas.options); 27205 util.deepExtend(networkOptions.global, this.options); 27206 27207 this.configurator.setModuleOptions(networkOptions); 27208 } 27209 27210 // handle network global options 27211 if (options.clickToUse !== undefined) { 27212 if (options.clickToUse === true) { 27213 if (this.activator === undefined) { 27214 this.activator = new Activator(this.canvas.frame); 27215 this.activator.on('change', function () { 27216 _this2.body.emitter.emit('activate'); 27217 }); 27218 } 27219 } else { 27220 if (this.activator !== undefined) { 27221 this.activator.destroy(); 27222 delete this.activator; 27223 } 27224 this.body.emitter.emit('activate'); 27225 } 27226 } else { 27227 this.body.emitter.emit('activate'); 27228 } 27229 27230 this.canvas.setSize(); 27231 // start the physics simulation. Can be safely called multiple times. 27232 this.body.emitter.emit('startSimulation'); 27233 } 27234 }; 27235 27236 /** 27237 * Update the this.body.nodeIndices with the most recent node index list 27238 * @private 27239 */ 27240 Network.prototype._updateVisibleIndices = function () { 27241 var nodes = this.body.nodes; 27242 var edges = this.body.edges; 27243 this.body.nodeIndices = []; 27244 this.body.edgeIndices = []; 27245 27246 for (var nodeId in nodes) { 27247 if (nodes.hasOwnProperty(nodeId)) { 27248 if (nodes[nodeId].options.hidden === false) { 27249 this.body.nodeIndices.push(nodeId); 27250 } 27251 } 27252 } 27253 27254 for (var edgeId in edges) { 27255 if (edges.hasOwnProperty(edgeId)) { 27256 if (edges[edgeId].options.hidden === false) { 27257 this.body.edgeIndices.push(edgeId); 27258 } 27259 } 27260 } 27261 }; 27262 27263 /** 27264 * Bind all events 27265 */ 27266 Network.prototype.bindEventListeners = function () { 27267 var _this3 = this; 27268 27269 // this event will trigger a rebuilding of the cache everything. Used when nodes or edges have been added or removed. 27270 this.body.emitter.on('_dataChanged', function () { 27271 // update shortcut lists 27272 _this3._updateVisibleIndices(); 27273 _this3.physics.updatePhysicsData(); 27274 _this3.body.emitter.emit('_requestRedraw'); 27275 // call the dataUpdated event because the only difference between the two is the updating of the indices 27276 _this3.body.emitter.emit('_dataUpdated'); 27277 }); 27278 27279 // this is called when options of EXISTING nodes or edges have changed. 27280 this.body.emitter.on('_dataUpdated', function () { 27281 // update values
27282 _this3._updateValueRange(_this3.body.nodes); 27283 _this3._updateValueRange(_this3.body.edges); 27284 // start simulation (can be called safely, even if already running) 27285 _this3.body.emitter.emit('startSimulation'); 27286 _this3.body.emitter.emit('_requestRedraw'); 27287 }); 27288 }; 27289 27290 /** 27291 * Set nodes and edges, and optionally options as well. 27292 * 27293 * @param {Object} data Object containing parameters: 27294 * {Array | DataSet | DataView} [nodes] Array with nodes 27295 * {Array | DataSet | DataView} [edges] Array with edges 27296 * {String} [dot] String containing data in DOT format 27297 * {String} [gephi] String containing data in gephi JSON format 27298 * {Options} [options] Object with options 27299 */ 27300 Network.prototype.setData = function (data) { 27301 // reset the physics engine. 27302 this.body.emitter.emit('resetPhysics'); 27303 this.body.emitter.emit('_resetData'); 27304 27305 // unselect all to ensure no selections from old data are carried over. 27306 this.selectionHandler.unselectAll(); 27307 27308 if (data && data.dot && (data.nodes || data.edges)) { 27309 throw new SyntaxError('Data must contain either parameter "dot" or ' + ' parameter pair "nodes" and "edges", but not both.'); 27310 } 27311 27312 // set options 27313 this.setOptions(data && data.options); 27314 // set all data 27315 if (data && data.dot) { 27316 console.log('The dot property has been depricated. Please use the static convertDot method to convert DOT into vis.network format and use the normal data format with nodes and edges. This converter is used like this: var data = vis.network.convertDot(dotString);'); 27317 // parse DOT file 27318 var dotData = dotparser.DOTToGraph(data.dot); 27319 this.setData(dotData); 27320 return; 27321 } else if (data && data.gephi) { 27322 // parse DOT file 27323 console.log('The gephi property has been depricated. Please use the static convertGephi method to convert gephi into vis.network format and use the normal data format with nodes and edges. This converter is used like this: var data = vis.network.convertGephi(gephiJson);'); 27324 var gephiData = gephiParser.parseGephi(data.gephi); 27325 this.setData(gephiData); 27326 return; 27327 } else { 27328 this.nodesHandler.setData(data && data.nodes, true); 27329 this.edgesHandler.setData(data && data.edges, true); 27330 } 27331 27332 // emit change in data 27333 this.body.emitter.emit('_dataChanged'); 27334 27335 // emit data loaded 27336 this.body.emitter.emit('_dataLoaded'); 27337 27338 // find a stable position or start animating to a stable position 27339 this.body.emitter.emit('initPhysics'); 27340 }; 27341 27342 /** 27343 * Cleans up all bindings of the network, removing it fully from the memory IF the variable is set to null after calling this function. 27344 * var network = new vis.Network(..); 27345 * network.destroy(); 27346 * network = null; 27347 */ 27348 Network.prototype.destroy = function () { 27349 this.body.emitter.emit('destroy'); 27350 // clear events 27351 this.body.emitter.off(); 27352 this.off(); 27353 27354 // delete modules 27355 delete this.groups; 27356 delete this.canvas; 27357 delete this.selectionHandler; 27358 delete this.interactionHandler; 27359 delete this.view; 27360 delete this.renderer; 27361 delete this.physics; 27362 delete this.layoutEngine; 27363 delete this.clustering; 27364 delete this.manipulation; 27365 delete this.nodesHandler; 27366 delete this.edgesHandler; 27367 delete this.configurator; 27368 delete this.images; 27369 27370 for (var nodeId in this.body.nodes) { 27371 delete this.body.nodes[nodeId]; 27372 } 27373 for (var edgeId in this.body.edges) { 27374 delete this.body.edges[edgeId]; 27375 } 27376 27377 // remove the container and everything inside it recursively 27378 util.recursiveDOMDelete(this.body.container); 27379 }; 27380 27381 /** 27382 * Update the values of all object in the given array according to the current 27383 * value range of the objects in the array. 27384 * @param {Object} obj An object containing a set of Edges or Nodes 27385 * The objects must have a method getValue() and 27386 * setValueRange(min, max). 27387 * @private 27388 */
27389 Network.prototype._updateValueRange = function (obj) { 27390 var id; 27391 27392 // determine the range of the objects 27393 var valueMin = undefined; 27394 var valueMax = undefined; 27395 var valueTotal = 0; 27396 for (id in obj) { 27397 if (obj.hasOwnProperty(id)) { 27398 var value = obj[id].getValue(); 27399 if (value !== undefined) { 27400 valueMin = valueMin === undefined ? value : Math.min(value, valueMin); 27401 valueMax = valueMax === undefined ? value : Math.max(value, valueMax); 27402 valueTotal += value; 27403 } 27404 } 27405 } 27406 27407 // adjust the range of all objects 27408 if (valueMin !== undefined && valueMax !== undefined) { 27409 for (id in obj) { 27410 if (obj.hasOwnProperty(id)) { 27411 obj[id].setValueRange(valueMin, valueMax, valueTotal); 27412 } 27413 } 27414 } 27415 }; 27416 27417 /** 27418 * Returns true when the Network is active. 27419 * @returns {boolean} 27420 */ 27421 Network.prototype.isActive = function () { 27422 return !this.activator || this.activator.active; 27423 }; 27424 27425 Network.prototype.setSize = function () { 27426 return this.canvas.setSize.apply(this.canvas, arguments); 27427 }; 27428 Network.prototype.canvasToDOM = function () { 27429 return this.canvas.canvasToDOM.apply(this.canvas, arguments); 27430 }; 27431 Network.prototype.DOMtoCanvas = function () { 27432 return this.canvas.DOMtoCanvas.apply(this.canvas, arguments); 27433 }; 27434 Network.prototype.findNode = function () { 27435 return this.clustering.findNode.apply(this.clustering, arguments); 27436 }; 27437 Network.prototype.isCluster = function () { 27438 return this.clustering.isCluster.apply(this.clustering, arguments); 27439 }; 27440 Network.prototype.openCluster = function () { 27441 return this.clustering.openCluster.apply(this.clustering, arguments); 27442 }; 27443 Network.prototype.cluster = function () { 27444 return this.clustering.cluster.apply(this.clustering, arguments); 27445 }; 27446 Network.prototype.getNodesInCluster = function () { 27447 return this.clustering.getNodesInCluster.apply(this.clustering, arguments); 27448 }; 27449 Network.prototype.clusterByConnection = function () { 27450 return this.clustering.clusterByConnection.apply(this.clustering, arguments); 27451 }; 27452 Network.prototype.clusterByHubsize = function () { 27453 return this.clustering.clusterByHubsize.apply(this.clustering, arguments); 27454 }; 27455 Network.prototype.clusterOutliers = function () { 27456 return this.clustering.clusterOutliers.apply(this.clustering, arguments); 27457 }; 27458 Network.prototype.getSeed = function () { 27459 return this.layoutEngine.getSeed.apply(this.layoutEngine, arguments); 27460 }; 27461 Network.prototype.enableEditMode = function () { 27462 return this.manipulation.enableEditMode.apply(this.manipulation, arguments); 27463 }; 27464 Network.prototype.disableEditMode = function () { 27465 return this.manipulation.disableEditMode.apply(this.manipulation, arguments); 27466 }; 27467 Network.prototype.addNodeMode = function () { 27468 return this.manipulation.addNodeMode.apply(this.manipulation, arguments); 27469 }; 27470 Network.prototype.editNode = function () { 27471 return this.manipulation.editNode.apply(this.manipulation, arguments); 27472 }; 27473 Network.prototype.editNodeMode = function () { 27474 console.log('Deprecated: Please use editNode instead of editNodeMode.');return this.manipulation.editNode.apply(this.manipulation, arguments); 27475 }; 27476 Network.prototype.addEdgeMode = function () { 27477 return this.manipulation.addEdgeMode.apply(this.manipulation, arguments); 27478 }; 27479 Network.prototype.editEdgeMode = function () { 27480 return this.manipulation.editEdgeMode.apply(this.manipulation, arguments); 27481 }; 27482 Network.prototype.deleteSelected = function () { 27483 return this.manipulation.deleteSelected.apply(this.manipulation, arguments); 27484 }; 27485 Network.prototype.getPositions = function () { 27486 return this.nodesHandler.getPositions.apply(this.nodesHandler, arguments); 27487 }; 27488 Network.prototype.storePositions = function () { 27489 return this.nodesHandler.storePositions.apply(this.nodesHandler, arguments); 27490 }; 27491 Network.prototype.moveNode = function () { 27492 return this.nodesHandler.moveNode.apply(this.nodesHandler, arguments); 27493 }; 27494 Network.prototype.getBoundingBox = function () { 27495 return this.nodesHandler.getBoundingBox.apply(this.nodesHandler, arguments); 27496 }; 27497 Network.prototype.getConnectedNodes = function (objectId) { 27498 if (this.body.nodes[objectId] !== undefined) { 27499 return this.nodesHandler.getConnectedNodes.apply(this.nodesHandler, arguments); 27500 } else { 27501 return this.edgesHandler.getConnectedNodes.apply(this.edgesHandler, arguments); 27502 } 27503 }; 27504 Network.prototype.getConnectedEdges = function () { 27505 return this.nodesHandler.getConnectedEdges.apply(this.nodesHandler, arguments); 27506 }; 27507 Network.prototype.startSimulation = function () { 27508 return this.physics.startSimulation.apply(this.physics, arguments); 27509 }; 27510 Network.prototype.stopSimulation = function () { 27511 return this.physics.stopSimulation.apply(this.physics, arguments); 27512 }; 27513 Network.prototype.stabilize = function () { 27514 return this.physics.stabilize.apply(this.physics, arguments); 27515 }; 27516 Network.prototype.getSelection = function () { 27517 return this.selectionHandler.getSelection.apply(this.selectionHandler, arguments); 27518 }; 27519 Network.prototype.getSelectedNodes = function () { 27520 return this.selectionHandler.getSelectedNodes.apply(this.selectionHandler, arguments); 27521 }; 27522 Network.prototype.getSelectedEdges = function () { 27523 return this.selectionHandler.getSelectedEdges.apply(this.selectionHandler, arguments); 27524 }; 27525 Network.prototype.getNodeAt = function () { 27526 var node = this.selectionHandler.getNodeAt.apply(this.selectionHandler, arguments); 27527 if (node !== undefined && node.id !== undefined) { 27528 return node.id; 27529 } 27530 return node; 27531 }; 27532 Network.prototype.getEdgeAt = function () { 27533 var edge = this.selectionHandler.getEdgeAt.apply(this.selectionHandler, arguments); 27534 if (edge !== undefined && edge.id !== undefined) { 27535 return edge.id; 27536 } 27537 return edge; 27538 }; 27539 Network.prototype.selectNodes = function () { 27540 return this.selectionHandler.selectNodes.apply(this.selectionHandler, arguments); 27541 }; 27542 Network.prototype.selectEdges = function () { 27543 return this.selectionHandler.selectEdges.apply(this.selectionHandler, arguments); 27544 }; 27545 Network.prototype.unselectAll = function () { 27546 this.selectionHandler.unselectAll.apply(this.selectionHandler, arguments); 27547 this.redraw(); 27548 }; 27549 Network.prototype.redraw = function () { 27550 return this.renderer.redraw.apply(this.renderer, arguments); 27551 }; 27552 Network.prototype.getScale = function () { 27553 return this.view.getScale.apply(this.view, arguments); 27554 }; 27555 Network.prototype.getViewPosition = function () { 27556 return this.view.getViewPosition.apply(this.view, arguments); 27557 }; 27558 Network.prototype.fit = function () { 27559 return this.view.fit.apply(this.view, arguments); 27560 }; 27561 Network.prototype.moveTo = function () { 27562 return this.view.moveTo.apply(this.view, arguments); 27563 };
27564 Network.prototype.focus = function () { 27565 return this.view.focus.apply(this.view, arguments); 27566 }; 27567 Network.prototype.releaseNode = function () { 27568 return this.view.releaseNode.apply(this.view, arguments); 27569 }; 27570 Network.prototype.getOptionsFromConfigurator = function () { 27571 var options = {}; 27572 if (this.configurator) { 27573 options = this.configurator.getOptions.apply(this.configurator); 27574 } 27575 return options; 27576 }; 27577 27578 module.exports = Network; 27579 27580/***/ }, 27581/* 60 */ 27582/***/ function(module, exports, __webpack_require__) { 27583 27584 "use strict"; 27585 27586 Object.defineProperty(exports, "__esModule", { 27587 value: true 27588 }); 27589 27590 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 27591 27592 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 27593 27594 var util = __webpack_require__(1); 27595 27596 /** 27597 * @class Groups 27598 * This class can store groups and options specific for groups. 27599 */ 27600 27601 var Groups = (function () { 27602 function Groups() { 27603 _classCallCheck(this, Groups); 27604 27605 this.clear(); 27606 this.defaultIndex = 0; 27607 this.groupsArray = []; 27608 this.groupIndex = 0; 27609 27610 this.defaultGroups = [{ border: "#2B7CE9", background: "#97C2FC", highlight: { border: "#2B7CE9", background: "#D2E5FF" }, hover: { border: "#2B7CE9", background: "#D2E5FF" } }, // 0: blue 27611 { border: "#FFA500", background: "#FFFF00", highlight: { border: "#FFA500", background: "#FFFFA3" }, hover: { border: "#FFA500", background: "#FFFFA3" } }, // 1: yellow 27612 { border: "#FA0A10", background: "#FB7E81", highlight: { border: "#FA0A10", background: "#FFAFB1" }, hover: { border: "#FA0A10", background: "#FFAFB1" } }, // 2: red 27613 { border: "#41A906", background: "#7BE141", highlight: { border: "#41A906", background: "#A1EC76" }, hover: { border: "#41A906", background: "#A1EC76" } }, // 3: green 27614 { border: "#E129F0", background: "#EB7DF4", highlight: { border: "#E129F0", background: "#F0B3F5" }, hover: { border: "#E129F0", background: "#F0B3F5" } }, // 4: magenta 27615 { border: "#7C29F0", background: "#AD85E4", highlight: { border: "#7C29F0", background: "#D3BDF0" }, hover: { border: "#7C29F0", background: "#D3BDF0" } }, // 5: purple 27616 { border: "#C37F00", background: "#FFA807", highlight: { border: "#C37F00", background: "#FFCA66" }, hover: { border: "#C37F00", background: "#FFCA66" } }, // 6: orange 27617 { border: "#4220FB", background: "#6E6EFD", highlight: { border: "#4220FB", background: "#9B9BFD" }, hover: { border: "#4220FB", background: "#9B9BFD" } }, // 7: darkblue 27618 { border: "#FD5A77", background: "#FFC0CB", highlight: { border: "#FD5A77", background: "#FFD1D9" }, hover: { border: "#FD5A77", background: "#FFD1D9" } }, // 8: pink 27619 { border: "#4AD63A", background: "#C2FABC", highlight: { border: "#4AD63A", background: "#E6FFE3" }, hover: { border: "#4AD63A", background: "#E6FFE3" } }, // 9: mint 27620 27621 { border: "#990000", background: "#EE0000", highlight: { border: "#BB0000", background: "#FF3333" }, hover: { border: "#BB0000", background: "#FF3333" } }, // 10:bright red 27622 27623 { border: "#FF6000", background: "#FF6000", highlight: { border: "#FF6000", background: "#FF6000" }, hover: { border: "#FF6000", background: "#FF6000" } }, // 12: real orange 27624 { border: "#97C2FC", background: "#2B7CE9", highlight: { border: "#D2E5FF", background: "#2B7CE9" }, hover: { border: "#D2E5FF", background: "#2B7CE9" } }, // 13: blue 27625 { border: "#399605", background: "#255C03", highlight: { border: "#399605", background: "#255C03" }, hover: { border: "#399605", background: "#255C03" } }, // 14: green 27626 { border: "#B70054", background: "#FF007E", highlight: { border: "#B70054", background: "#FF007E" }, hover: { border: "#B70054", background: "#FF007E" } }, // 15: magenta 27627 { border: "#AD85E4", background: "#7C29F0", highlight: { border: "#D3BDF0", background: "#7C29F0" }, hover: { border: "#D3BDF0", background: "#7C29F0" } }, // 16: purple 27628 { border: "#4557FA", background: "#000EA1", highlight: { border: "#6E6EFD", background: "#000EA1" }, hover: { border: "#6E6EFD", background: "#000EA1" } }, // 17: darkblue 27629 { border: "#FFC0CB", background: "#FD5A77", highlight: { border: "#FFD1D9", background: "#FD5A77" }, hover: { border: "#FFD1D9", background: "#FD5A77" } }, // 18: pink 27630 { border: "#C2FABC", background: "#74D66A", highlight: { border: "#E6FFE3", background: "#74D66A" }, hover: { border: "#E6FFE3", background: "#74D66A" } }, // 19: mint 27631 27632 { border: "#EE0000", background: "#990000", highlight: { border: "#FF3333", background: "#BB0000" }, hover: { border: "#FF3333", background: "#BB0000" } } // 20:bright red 27633 ]; 27634 27635 this.options = {}; 27636 this.defaultOptions = { 27637 useDefaultGroups: true 27638 }; 27639 util.extend(this.options, this.defaultOptions); 27640 } 27641 27642 _createClass(Groups, [{ 27643 key: "setOptions", 27644 value: function setOptions(options) { 27645 var optionFields = ["useDefaultGroups"]; 27646 27647 if (options !== undefined) { 27648 for (var groupName in options) { 27649 if (options.hasOwnProperty(groupName)) { 27650 if (optionFields.indexOf(groupName) === -1) { 27651 var group = options[groupName]; 27652 this.add(groupName, group); 27653 } 27654 } 27655 } 27656 } 27657 } 27658 }, {
27659 key: "clear", 27660 27661 /** 27662 * Clear all groups 27663 */ 27664 value: function clear() { 27665 this.groups = {}; 27666 this.groupsArray = []; 27667 } 27668 }, { 27669 key: "get", 27670 27671 /** 27672 * get group options of a groupname. If groupname is not found, a new group 27673 * is added. 27674 * @param {*} groupname Can be a number, string, Date, etc. 27675 * @return {Object} group The created group, containing all group options 27676 */ 27677 value: function get(groupname) { 27678 var group = this.groups[groupname]; 27679 if (group === undefined) { 27680 if (this.options.useDefaultGroups === false && this.groupsArray.length > 0) { 27681 // create new group 27682 var index = this.groupIndex % this.groupsArray.length; 27683 this.groupIndex++; 27684 group = {}; 27685 group.color = this.groups[this.groupsArray[index]]; 27686 this.groups[groupname] = group; 27687 } else { 27688 // create new group 27689 var index = this.defaultIndex % this.defaultGroups.length; 27690 this.defaultIndex++; 27691 group = {}; 27692 group.color = this.defaultGroups[index]; 27693 this.groups[groupname] = group; 27694 } 27695 } 27696 27697 return group; 27698 } 27699 }, { 27700 key: "add", 27701 27702 /** 27703 * Add a custom group style 27704 * @param {String} groupName 27705 * @param {Object} style An object containing borderColor, 27706 * backgroundColor, etc. 27707 * @return {Object} group The created group object 27708 */ 27709 value: function add(groupName, style) { 27710 this.groups[groupName] = style; 27711 this.groupsArray.push(groupName); 27712 return style; 27713 } 27714 }]); 27715 27716 return Groups; 27717 })(); 27718 27719 exports["default"] = Groups; 27720 module.exports = exports["default"]; 27721 27722/***/ }, 27723/* 61 */ 27724/***/ function(module, exports, __webpack_require__) { 27725 27726 'use strict'; 27727 27728 Object.defineProperty(exports, '__esModule', { 27729 value: true 27730 }); 27731 27732 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 27733 27734 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 27735 27736 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 27737 27738 var _componentsNode = __webpack_require__(62); 27739 27740 var _componentsNode2 = _interopRequireDefault(_componentsNode); 27741 27742 var _componentsSharedLabel = __webpack_require__(63); 27743 27744 var _componentsSharedLabel2 = _interopRequireDefault(_componentsSharedLabel); 27745 27746 var util = __webpack_require__(1); 27747 var DataSet = __webpack_require__(8); 27748 var DataView = __webpack_require__(10); 27749 27750 var NodesHandler = (function () { 27751 function NodesHandler(body, images, groups, layoutEngine) { 27752 var _this = this; 27753 27754 _classCallCheck(this, NodesHandler); 27755 27756 this.body = body; 27757 this.images = images; 27758 this.groups = groups; 27759 this.layoutEngine = layoutEngine; 27760 27761 // create the node API in the body container 27762 this.body.functions.createNode = this.create.bind(this); 27763 27764 this.nodesListeners = { 27765 add: function add(event, params) { 27766 _this.add(params.items); 27767 }, 27768 update: function update(event, params) { 27769 _this.update(params.items, params.data); 27770 }, 27771 remove: function remove(event, params) { 27772 _this.remove(params.items); 27773 } 27774 }; 27775 27776 this.options = {}; 27777 this.defaultOptions = { 27778 borderWidth: 1, 27779 borderWidthSelected: 2, 27780 brokenImage: undefined, 27781 color: { 27782 border: '#2B7CE9', 27783 background: '#97C2FC', 27784 highlight: { 27785 border: '#2B7CE9', 27786 background: '#D2E5FF' 27787 }, 27788 hover: { 27789 border: '#2B7CE9', 27790 background: '#D2E5FF' 27791 } 27792 }, 27793 fixed: { 27794 x: false, 27795 y: false 27796 }, 27797 font: { 27798 color: '#343434', 27799 size: 14, // px 27800 face: 'arial', 27801 background: 'none', 27802 strokeWidth: 0, // px 27803 strokeColor: '#ffffff', 27804 align: 'horizontal' 27805 }, 27806 group: undefined, 27807 hidden: false, 27808 icon: { 27809 face: 'FontAwesome', //'FontAwesome', 27810 code: undefined, //'\uf007', 27811 size: 50, //50, 27812 color: '#2B7CE9' //'#aa00ff' 27813 }, 27814 image: undefined, // --> URL 27815 label: undefined, 27816 labelHighlightBold: true, 27817 level: undefined, 27818 mass: 1, 27819 physics: true, 27820 scaling: { 27821 min: 10, 27822 max: 30, 27823 label: { 27824 enabled: false, 27825 min: 14, 27826 max: 30, 27827 maxVisible: 30, 27828 drawThreshold: 5 27829 }, 27830 customScalingFunction: function customScalingFunction(min, max, total, value) { 27831 if (max === min) { 27832 return 0.5; 27833 } else { 27834 var scale = 1 / (max - min); 27835 return Math.max(0, (value - min) * scale); 27836 } 27837 } 27838 }, 27839 shadow: { 27840 enabled: false, 27841 size: 10, 27842 x: 5, 27843 y: 5 27844 }, 27845 shape: 'ellipse', 27846 shapeProperties: { 27847 borderDashes: false, // only for borders 27848 borderRadius: 6, // only for box shape 27849 useImageSize: false, // only for image and circularImage shapes 27850 useBorderWithImage: false // only for image shape 27851 }, 27852 size: 25, 27853 title: undefined, 27854 value: undefined, 27855 x: undefined, 27856 y: undefined 27857 }; 27858 util.extend(this.options, this.defaultOptions); 27859 27860 this.bindEventListeners(); 27861 } 27862 27863 _createClass(NodesHandler, [{ 27864 key: 'bindEventListeners', 27865 value: function bindEventListeners() { 27866 var _this2 = this; 27867 27868 // refresh the nodes. Used when reverting from hierarchical layout 27869 this.body.emitter.on('refreshNodes', this.refresh.bind(this)); 27870 this.body.emitter.on('refresh', this.refresh.bind(this)); 27871 this.body.emitter.on('destroy', function () { 27872 delete _this2.body.functions.createNode; 27873 delete _this2.nodesListeners.add; 27874 delete _this2.nodesListeners.update; 27875 delete _this2.nodesListeners.remove; 27876 delete _this2.nodesListeners; 27877 }); 27878 } 27879 }, {
27880 key: 'setOptions', 27881 value: function setOptions(options) { 27882 if (options !== undefined) { 27883 _componentsNode2['default'].parseOptions(this.options, options); 27884 27885 // update the shape in all nodes 27886 if (options.shape !== undefined) { 27887 for (var nodeId in this.body.nodes) { 27888 if (this.body.nodes.hasOwnProperty(nodeId)) { 27889 this.body.nodes[nodeId].updateShape(); 27890 } 27891 } 27892 } 27893 27894 // update the font in all nodes 27895 if (options.font !== undefined) { 27896 _componentsSharedLabel2['default'].parseOptions(this.options.font, options); 27897 for (var nodeId in this.body.nodes) { 27898 if (this.body.nodes.hasOwnProperty(nodeId)) { 27899 this.body.nodes[nodeId].updateLabelModule(); 27900 this.body.nodes[nodeId]._reset(); 27901 } 27902 } 27903 } 27904 27905 // update the shape size in all nodes 27906 if (options.size !== undefined) { 27907 for (var nodeId in this.body.nodes) { 27908 if (this.body.nodes.hasOwnProperty(nodeId)) { 27909 this.body.nodes[nodeId]._reset(); 27910 } 27911 } 27912 } 27913 27914 // update the state of the letiables if needed 27915 if (options.hidden !== undefined || options.physics !== undefined) { 27916 this.body.emitter.emit('_dataChanged'); 27917 } 27918 } 27919 } 27920 }, { 27921 key: 'setData', 27922 27923 /** 27924 * Set a data set with nodes for the network 27925 * @param {Array | DataSet | DataView} nodes The data containing the nodes. 27926 * @private 27927 */ 27928 value: function setData(nodes) { 27929 var _this3 = this; 27930 27931 var doNotEmit = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 27932 27933 var oldNodesData = this.body.data.nodes; 27934 27935 if (nodes instanceof DataSet || nodes instanceof DataView) { 27936 this.body.data.nodes = nodes; 27937 } else if (Array.isArray(nodes)) { 27938 this.body.data.nodes = new DataSet(); 27939 this.body.data.nodes.add(nodes); 27940 } else if (!nodes) { 27941 this.body.data.nodes = new DataSet(); 27942 } else { 27943 throw new TypeError('Array or DataSet expected'); 27944 } 27945 27946 if (oldNodesData) { 27947 // unsubscribe from old dataset 27948 util.forEach(this.nodesListeners, function (callback, event) { 27949 oldNodesData.off(event, callback); 27950 }); 27951 } 27952 27953 // remove drawn nodes 27954 this.body.nodes = {}; 27955 27956 if (this.body.data.nodes) { 27957 (function () { 27958 // subscribe to new dataset 27959 var me = _this3; 27960 util.forEach(_this3.nodesListeners, function (callback, event) { 27961 me.body.data.nodes.on(event, callback); 27962 }); 27963 27964 // draw all new nodes 27965 var ids = _this3.body.data.nodes.getIds(); 27966 _this3.add(ids, true); 27967 })(); 27968 } 27969 27970 if (doNotEmit === false) { 27971 this.body.emitter.emit('_dataChanged'); 27972 } 27973 } 27974 }, { 27975 key: 'add', 27976 27977 /** 27978 * Add nodes 27979 * @param {Number[] | String[]} ids 27980 * @private 27981 */ 27982 value: function add(ids) { 27983 var doNotEmit = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 27984 27985 var id = undefined; 27986 var newNodes = []; 27987 for (var i = 0; i < ids.length; i++) { 27988 id = ids[i]; 27989 var properties = this.body.data.nodes.get(id); 27990 var node = this.create(properties); 27991 newNodes.push(node); 27992 this.body.nodes[id] = node; // note: this may replace an existing node 27993 } 27994 27995 this.layoutEngine.positionInitially(newNodes); 27996 27997 if (doNotEmit === false) { 27998 this.body.emitter.emit('_dataChanged'); 27999 } 28000 } 28001 }, { 28002 key: 'update', 28003 28004 /** 28005 * Update existing nodes, or create them when not yet existing 28006 * @param {Number[] | String[]} ids 28007 * @private 28008 */ 28009 value: function update(ids, changedData) { 28010 var nodes = this.body.nodes; 28011 var dataChanged = false;
28012 for (var i = 0; i < ids.length; i++) { 28013 var id = ids[i]; 28014 var node = nodes[id]; 28015 var data = changedData[i]; 28016 if (node !== undefined) { 28017 // update node 28018 dataChanged = node.setOptions(data); 28019 } else { 28020 dataChanged = true; 28021 // create node 28022 node = this.create(data); 28023 nodes[id] = node; 28024 } 28025 } 28026 if (dataChanged === true) { 28027 this.body.emitter.emit('_dataChanged'); 28028 } else { 28029 this.body.emitter.emit('_dataUpdated'); 28030 } 28031 } 28032 }, { 28033 key: 'remove', 28034 28035 /** 28036 * Remove existing nodes. If nodes do not exist, the method will just ignore it. 28037 * @param {Number[] | String[]} ids 28038 * @private 28039 */ 28040 value: function remove(ids) { 28041 var nodes = this.body.nodes; 28042 28043 for (var i = 0; i < ids.length; i++) { 28044 var id = ids[i]; 28045 delete nodes[id]; 28046 } 28047 28048 this.body.emitter.emit('_dataChanged'); 28049 } 28050 }, { 28051 key: 'create', 28052 28053 /** 28054 * create a node 28055 * @param properties 28056 * @param constructorClass 28057 */ 28058 value: function create(properties) { 28059 var constructorClass = arguments.length <= 1 || arguments[1] === undefined ? _componentsNode2['default'] : arguments[1]; 28060 28061 return new constructorClass(properties, this.body, this.images, this.groups, this.options); 28062 } 28063 }, { 28064 key: 'refresh', 28065 value: function refresh() { 28066 var clearPositions = arguments.length <= 0 || arguments[0] === undefined ? false : arguments[0]; 28067 28068 var nodes = this.body.nodes; 28069 for (var nodeId in nodes) { 28070 var node = undefined; 28071 if (nodes.hasOwnProperty(nodeId)) { 28072 node = nodes[nodeId]; 28073 } 28074 var data = this.body.data.nodes._data[nodeId]; 28075 if (node !== undefined && data !== undefined) { 28076 if (clearPositions === true) { 28077 node.setOptions({ x: null, y: null }); 28078 } 28079 node.setOptions({ fixed: false }); 28080 node.setOptions(data); 28081 } 28082 } 28083 } 28084 }, { 28085 key: 'getPositions', 28086 28087 /** 28088 * Returns the positions of the nodes. 28089 * @param ids --> optional, can be array of nodeIds, can be string 28090 * @returns {{}} 28091 */ 28092 value: function getPositions(ids) { 28093 var dataArray = {}; 28094 if (ids !== undefined) { 28095 if (Array.isArray(ids) === true) { 28096 for (var i = 0; i < ids.length; i++) { 28097 if (this.body.nodes[ids[i]] !== undefined) { 28098 var node = this.body.nodes[ids[i]]; 28099 dataArray[ids[i]] = { x: Math.round(node.x), y: Math.round(node.y) }; 28100 } 28101 } 28102 } else { 28103 if (this.body.nodes[ids] !== undefined) { 28104 var node = this.body.nodes[ids]; 28105 dataArray[ids] = { x: Math.round(node.x), y: Math.round(node.y) }; 28106 } 28107 } 28108 } else { 28109 for (var i = 0; i < this.body.nodeIndices.length; i++) { 28110 var node = this.body.nodes[this.body.nodeIndices[i]]; 28111 dataArray[this.body.nodeIndices[i]] = { x: Math.round(node.x), y: Math.round(node.y) }; 28112 } 28113 } 28114 return dataArray; 28115 } 28116 }, { 28117 key: 'storePositions', 28118 28119 /** 28120 * Load the XY positions of the nodes into the dataset. 28121 */ 28122 value: function storePositions() { 28123 // todo: add support for clusters and hierarchical. 28124 var dataArray = []; 28125 var dataset = this.body.data.nodes.getDataSet(); 28126 28127 for (var nodeId in dataset._data) { 28128 if (dataset._data.hasOwnProperty(nodeId)) { 28129 var node = this.body.nodes[nodeId]; 28130 if (dataset._data[nodeId].x != Math.round(node.x) || dataset._data[nodeId].y != Math.round(node.y)) { 28131 dataArray.push({ id: nodeId, x: Math.round(node.x), y: Math.round(node.y) }); 28132 } 28133 } 28134 } 28135 dataset.update(dataArray); 28136 } 28137 }, { 28138 key: 'getBoundingBox', 28139 28140 /** 28141 * get the bounding box of a node. 28142 * @param nodeId 28143 * @returns {j|*} 28144 */ 28145 value: function getBoundingBox(nodeId) { 28146 if (this.body.nodes[nodeId] !== undefined) { 28147 return this.body.nodes[nodeId].shape.boundingBox; 28148 } 28149 } 28150 }, {
28151 key: 'getConnectedNodes', 28152 28153 /** 28154 * Get the Ids of nodes connected to this node. 28155 * @param nodeId 28156 * @returns {Array} 28157 */ 28158 value: function getConnectedNodes(nodeId) { 28159 var nodeList = []; 28160 if (this.body.nodes[nodeId] !== undefined) { 28161 var node = this.body.nodes[nodeId]; 28162 var nodeObj = {}; // used to quickly check if node already exists 28163 for (var i = 0; i < node.edges.length; i++) { 28164 var edge = node.edges[i]; 28165 if (edge.toId == nodeId) { 28166 // these are double equals since ids can be numeric or string 28167 if (nodeObj[edge.fromId] === undefined) { 28168 nodeList.push(edge.fromId); 28169 nodeObj[edge.fromId] = true; 28170 } 28171 } else if (edge.fromId == nodeId) { 28172 // these are double equals since ids can be numeric or string 28173 if (nodeObj[edge.toId] === undefined) { 28174 nodeList.push(edge.toId); 28175 nodeObj[edge.toId] = true; 28176 } 28177 } 28178 } 28179 } 28180 return nodeList; 28181 } 28182 }, { 28183 key: 'getConnectedEdges', 28184 28185 /** 28186 * Get the ids of the edges connected to this node. 28187 * @param nodeId 28188 * @returns {*} 28189 */ 28190 value: function getConnectedEdges(nodeId) { 28191 var edgeList = []; 28192 if (this.body.nodes[nodeId] !== undefined) { 28193 var node = this.body.nodes[nodeId]; 28194 for (var i = 0; i < node.edges.length; i++) { 28195 edgeList.push(node.edges[i].id); 28196 } 28197 } else { 28198 console.log('NodeId provided for getConnectedEdges does not exist. Provided: ', nodeId); 28199 } 28200 return edgeList; 28201 } 28202 }, { 28203 key: 'moveNode', 28204 28205 /** 28206 * Move a node. 28207 * @param String nodeId 28208 * @param Number x 28209 * @param Number y 28210 */ 28211 value: function moveNode(nodeId, x, y) { 28212 var _this4 = this; 28213 28214 if (this.body.nodes[nodeId] !== undefined) { 28215 this.body.nodes[nodeId].x = Number(x); 28216 this.body.nodes[nodeId].y = Number(y); 28217 setTimeout(function () { 28218 _this4.body.emitter.emit('startSimulation'); 28219 }, 0); 28220 } else { 28221 console.log('Node id supplied to moveNode does not exist. Provided: ', nodeId); 28222 } 28223 } 28224 }]); 28225 28226 return NodesHandler; 28227 })(); 28228 28229 exports['default'] = NodesHandler; 28230 module.exports = exports['default']; 28231 28232/***/ }, 28233/* 62 */ 28234/***/ function(module, exports, __webpack_require__) { 28235 28236 'use strict'; 28237 28238 Object.defineProperty(exports, '__esModule', { 28239 value: true 28240 }); 28241 28242 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 28243 28244 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 28245 28246 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 28247 28248 var _sharedLabel = __webpack_require__(63); 28249 28250 var _sharedLabel2 = _interopRequireDefault(_sharedLabel); 28251 28252 var _nodesShapesBox = __webpack_require__(64); 28253 28254 var _nodesShapesBox2 = _interopRequireDefault(_nodesShapesBox); 28255 28256 var _nodesShapesCircle = __webpack_require__(66); 28257 28258 var _nodesShapesCircle2 = _interopRequireDefault(_nodesShapesCircle); 28259 28260 var _nodesShapesCircularImage = __webpack_require__(68); 28261 28262 var _nodesShapesCircularImage2 = _interopRequireDefault(_nodesShapesCircularImage); 28263 28264 var _nodesShapesDatabase = __webpack_require__(69); 28265 28266 var _nodesShapesDatabase2 = _interopRequireDefault(_nodesShapesDatabase); 28267 28268 var _nodesShapesDiamond = __webpack_require__(70); 28269 28270 var _nodesShapesDiamond2 = _interopRequireDefault(_nodesShapesDiamond); 28271 28272 var _nodesShapesDot = __webpack_require__(72); 28273 28274 var _nodesShapesDot2 = _interopRequireDefault(_nodesShapesDot); 28275 28276 var _nodesShapesEllipse = __webpack_require__(73); 28277 28278 var _nodesShapesEllipse2 = _interopRequireDefault(_nodesShapesEllipse); 28279 28280 var _nodesShapesIcon = __webpack_require__(74); 28281 28282 var _nodesShapesIcon2 = _interopRequireDefault(_nodesShapesIcon); 28283 28284 var _nodesShapesImage = __webpack_require__(75); 28285 28286 var _nodesShapesImage2 = _interopRequireDefault(_nodesShapesImage); 28287 28288 var _nodesShapesSquare = __webpack_require__(76); 28289 28290 var _nodesShapesSquare2 = _interopRequireDefault(_nodesShapesSquare); 28291 28292 var _nodesShapesStar = __webpack_require__(77); 28293 28294 var _nodesShapesStar2 = _interopRequireDefault(_nodesShapesStar); 28295 28296 var _nodesShapesText = __webpack_require__(78); 28297 28298 var _nodesShapesText2 = _interopRequireDefault(_nodesShapesText); 28299 28300 var _nodesShapesTriangle = __webpack_require__(79); 28301 28302 var _nodesShapesTriangle2 = _interopRequireDefault(_nodesShapesTriangle); 28303 28304 var _nodesShapesTriangleDown = __webpack_require__(80); 28305 28306 var _nodesShapesTriangleDown2 = _interopRequireDefault(_nodesShapesTriangleDown); 28307 28308 var _sharedValidator = __webpack_require__(47); 28309 28310 var _sharedValidator2 = _interopRequireDefault(_sharedValidator); 28311 28312 var util = __webpack_require__(1); 28313 28314 /** 28315 * @class Node 28316 * A node. A node can be connected to other nodes via one or multiple edges. 28317 * @param {object}
28317 options An object containing options for the node. All 28318 * options are optional, except for the id. 28319 * {number} id Id of the node. Required 28320 * {string} label Text label for the node 28321 * {number} x Horizontal position of the node 28322 * {number} y Vertical position of the node 28323 * {string} shape Node shape, available: 28324 * "database", "circle", "ellipse", 28325 * "box", "image", "text", "dot", 28326 * "star", "triangle", "triangleDown", 28327 * "square", "icon" 28328 * {string} image An image url 28329 * {string} title An title text, can be HTML 28330 * {anytype} group A group name or number 28331 * @param {Network.Images} imagelist A list with images. Only needed 28332 * when the node has an image 28333 * @param {Network.Groups} grouplist A list with groups. Needed for 28334 * retrieving group options 28335 * @param {Object} constants An object with default values for 28336 * example for the color 28337 * 28338 */ 28339 28340 var Node = (function () { 28341 function Node(options, body, imagelist, grouplist, globalOptions) { 28342 _classCallCheck(this, Node); 28343 28344 this.options = util.bridgeObject(globalOptions); 28345 this.globalOptions = globalOptions; 28346 this.body = body; 28347 28348 this.edges = []; // all edges connected to this node 28349 28350 // set defaults for the options 28351 this.id = undefined; 28352 this.imagelist = imagelist; 28353 this.grouplist = grouplist; 28354 28355 // state options 28356 this.x = undefined; 28357 this.y = undefined; 28358 this.baseSize = this.options.size; 28359 this.baseFontSize = this.options.font.size; 28360 this.predefinedPosition = false; // used to check if initial fit should just take the range or approximate 28361 this.selected = false; 28362 this.hover = false; 28363 28364 this.labelModule = new _sharedLabel2['default'](this.body, this.options); 28365 this.setOptions(options); 28366 } 28367 28368 _createClass(Node, [{ 28369 key: 'attachEdge', 28370 28371 /** 28372 * Attach a edge to the node 28373 * @param {Edge} edge 28374 */ 28375 value: function attachEdge(edge) { 28376 if (this.edges.indexOf(edge) === -1) { 28377 this.edges.push(edge); 28378 } 28379 } 28380 }, { 28381 key: 'detachEdge', 28382 28383 /** 28384 * Detach a edge from the node 28385 * @param {Edge} edge 28386 */ 28387 value: function detachEdge(edge) { 28388 var index = this.edges.indexOf(edge); 28389 if (index != -1) { 28390 this.edges.splice(index, 1); 28391 } 28392 } 28393 }, { 28394 key: 'setOptions', 28395 28396 /** 28397 * Set or overwrite options for the node 28398 * @param {Object} options an object with options 28399 * @param {Object} constants and object with default, global options 28400 */ 28401 value: function setOptions(options) { 28402 var currentShape = this.options.shape; 28403 if (!options) { 28404 return; 28405 } 28406 // basic options 28407 if (options.id !== undefined) { 28408 this.id = options.id; 28409 } 28410 28411 if (this.id === undefined) { 28412 throw 'Node must have an id'; 28413 } 28414 28415 // set these options locally 28416 // clear x and y positions 28417 if (options.x !== undefined) { 28418 if (options.x === null) { 28419 this.x = undefined;this.predefinedPosition = false; 28420 } else { 28421 this.x = parseInt(options.x);this.predefinedPosition = true; 28422 } 28423 } 28424 if (options.y !== undefined) { 28425 if (options.y === null) { 28426 this.y = undefined;this.predefinedPosition = false; 28427 } else { 28428 this.y = parseInt(options.y);this.predefinedPosition = true; 28429 } 28430 } 28431 if (options.size !== undefined) { 28432 this.baseSize = options.size; 28433 } 28434 if (options.value !== undefined) { 28435 options.value = parseFloat(options.value); 28436 } 28437 28438 // copy group options 28439 if (typeof options.group === 'number' || typeof options.group === 'string' && options.group != '') { 28440 var groupObj = this.grouplist.get(options.group); 28441 util.deepExtend(this.options, groupObj); 28442 // the color object needs to be completely defined. Since groups can partially overwrite the colors, we parse it again, just in case. 28443 this.options.color = util.parseColor(this.options.color); 28444 } 28445 28446 // this transforms all shorthands into fully defined options 28447 Node.parseOptions(this.options, options, true, this.globalOptions); 28448 28449 // load the images 28450 if (this.options.image !== undefined) { 28451 if (this.imagelist) { 28452 this.imageObj = this.imagelist.load(this.options.image, this.options.brokenImage, this.id); 28453 } else { 28454 throw 'No imagelist provided'; 28455 } 28456 } 28457
28458 this.updateLabelModule(); 28459 this.updateShape(currentShape); 28460 28461 if (options.hidden !== undefined || options.physics !== undefined) { 28462 return true; 28463 } 28464 return false; 28465 } 28466 }, { 28467 key: 'updateLabelModule', 28468 value: function updateLabelModule() { 28469 if (this.options.label === undefined || this.options.label === null) { 28470 this.options.label = ''; 28471 } 28472 this.labelModule.setOptions(this.options, true); 28473 if (this.labelModule.baseSize !== undefined) { 28474 this.baseFontSize = this.labelModule.baseSize; 28475 } 28476 } 28477 }, { 28478 key: 'updateShape', 28479 value: function updateShape(currentShape) { 28480 if (currentShape === this.options.shape && this.shape) { 28481 this.shape.setOptions(this.options, this.imageObj); 28482 } else { 28483 // choose draw method depending on the shape 28484 switch (this.options.shape) { 28485 case 'box': 28486 this.shape = new _nodesShapesBox2['default'](this.options, this.body, this.labelModule); 28487 break; 28488 case 'circle': 28489 this.shape = new _nodesShapesCircle2['default'](this.options, this.body, this.labelModule); 28490 break; 28491 case 'circularImage': 28492 this.shape = new _nodesShapesCircularImage2['default'](this.options, this.body, this.labelModule, this.imageObj); 28493 break; 28494 case 'database': 28495 this.shape = new _nodesShapesDatabase2['default'](this.options, this.body, this.labelModule); 28496 break; 28497 case 'diamond': 28498 this.shape = new _nodesShapesDiamond2['default'](this.options, this.body, this.labelModule); 28499 break; 28500 case 'dot': 28501 this.shape = new _nodesShapesDot2['default'](this.options, this.body, this.labelModule); 28502 break; 28503 case 'ellipse': 28504 this.shape = new _nodesShapesEllipse2['default'](this.options, this.body, this.labelModule); 28505 break; 28506 case 'icon': 28507 this.shape = new _nodesShapesIcon2['default'](this.options, this.body, this.labelModule); 28508 break; 28509 case 'image': 28510 this.shape = new _nodesShapesImage2['default'](this.options, this.body, this.labelModule, this.imageObj); 28511 break; 28512 case 'square': 28513 this.shape = new _nodesShapesSquare2['default'](this.options, this.body, this.labelModule); 28514 break; 28515 case 'star': 28516 this.shape = new _nodesShapesStar2['default'](this.options, this.body, this.labelModule); 28517 break; 28518 case 'text': 28519 this.shape = new _nodesShapesText2['default'](this.options, this.body, this.labelModule); 28520 break; 28521 case 'triangle': 28522 this.shape = new _nodesShapesTriangle2['default'](this.options, this.body, this.labelModule); 28523 break; 28524 case 'triangleDown': 28525 this.shape = new _nodesShapesTriangleDown2['default'](this.options, this.body, this.labelModule); 28526 break; 28527 default: 28528 this.shape = new _nodesShapesEllipse2['default'](this.options, this.body, this.labelModule); 28529 break; 28530 } 28531 } 28532 this._reset(); 28533 } 28534 }, { 28535 key: 'select', 28536 28537 /** 28538 * select this node 28539 */ 28540 value: function select() { 28541 this.selected = true; 28542 this._reset(); 28543 } 28544 }, { 28545 key: 'unselect', 28546 28547 /** 28548 * unselect this node 28549 */ 28550 value: function unselect() { 28551 this.selected = false; 28552 this._reset(); 28553 } 28554 }, { 28555 key: '_reset', 28556 28557 /** 28558 * Reset the calculated size of the node, forces it to recalculate its size 28559 * @private 28560 */ 28561 value: function _reset() { 28562 this.shape.width = undefined; 28563 this.shape.height = undefined; 28564 } 28565 }, { 28566 key: 'getTitle', 28567 28568 /** 28569 * get the title of this node. 28570 * @return {string} title The title of the node, or undefined when no title 28571 * has been set. 28572 */ 28573 value: function getTitle() { 28574 return this.options.title; 28575 } 28576 }, {
28577 key: 'distanceToBorder', 28578 28579 /** 28580 * Calculate the distance to the border of the Node 28581 * @param {CanvasRenderingContext2D} ctx 28582 * @param {Number} angle Angle in radians 28583 * @returns {number} distance Distance to the border in pixels 28584 */ 28585 value: function distanceToBorder(ctx, angle) { 28586 return this.shape.distanceToBorder(ctx, angle); 28587 } 28588 }, { 28589 key: 'isFixed', 28590 28591 /** 28592 * Check if this node has a fixed x and y position 28593 * @return {boolean} true if fixed, false if not 28594 */ 28595 value: function isFixed() { 28596 return this.options.fixed.x && this.options.fixed.y; 28597 } 28598 }, { 28599 key: 'isSelected', 28600 28601 /** 28602 * check if this node is selecte 28603 * @return {boolean} selected True if node is selected, else false 28604 */ 28605 value: function isSelected() { 28606 return this.selected; 28607 } 28608 }, { 28609 key: 'getValue', 28610 28611 /** 28612 * Retrieve the value of the node. Can be undefined 28613 * @return {Number} value 28614 */ 28615 value: function getValue() { 28616 return this.options.value; 28617 } 28618 }, { 28619 key: 'setValueRange', 28620 28621 /** 28622 * Adjust the value range of the node. The node will adjust it's size 28623 * based on its value. 28624 * @param {Number} min 28625 * @param {Number} max 28626 */ 28627 value: function setValueRange(min, max, total) { 28628 if (this.options.value !== undefined) { 28629 var scale = this.options.scaling.customScalingFunction(min, max, total, this.options.value); 28630 var sizeDiff = this.options.scaling.max - this.options.scaling.min; 28631 if (this.options.scaling.label.enabled === true) { 28632 var fontDiff = this.options.scaling.label.max - this.options.scaling.label.min; 28633 this.options.font.size = this.options.scaling.label.min + scale * fontDiff; 28634 } 28635 this.options.size = this.options.scaling.min + scale * sizeDiff; 28636 } else { 28637 this.options.size = this.baseSize; 28638 this.options.font.size = this.baseFontSize; 28639 } 28640 } 28641 }, { 28642 key: 'draw', 28643 28644 /** 28645 * Draw this node in the given canvas 28646 * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); 28647 * @param {CanvasRenderingContext2D} ctx 28648 */ 28649 value: function draw(ctx) { 28650 this.shape.draw(ctx, this.x, this.y, this.selected, this.hover); 28651 } 28652 }, { 28653 key: 'updateBoundingBox', 28654 28655 /** 28656 * Update the bounding box of the shape 28657 */ 28658 value: function updateBoundingBox(ctx) { 28659 this.shape.updateBoundingBox(this.x, this.y, ctx); 28660 } 28661 }, { 28662 key: 'resize', 28663 28664 /** 28665 * Recalculate the size of this node in the given canvas 28666 * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); 28667 * @param {CanvasRenderingContext2D} ctx 28668 */ 28669 value: function resize(ctx) { 28670 this.shape.resize(ctx, this.selected); 28671 } 28672 }, { 28673 key: 'isOverlappingWith', 28674 28675 /** 28676 * Check if this object is overlapping with the provided object 28677 * @param {Object} obj an object with parameters left, top, right, bottom 28678 * @return {boolean} True if location is located on node 28679 */ 28680 value: function isOverlappingWith(obj) { 28681 return this.shape.left < obj.right && this.shape.left + this.shape.width > obj.left && this.shape.top < obj.bottom && this.shape.top + this.shape.height > obj.top; 28682 } 28683 }, { 28684 key: 'isBoundingBoxOverlappingWith', 28685 28686 /** 28687 * Check if this object is overlapping with the provided object 28688 * @param {Object} obj an object with parameters left, top, right, bottom 28689 * @return {boolean} True if location is located on node 28690 */ 28691 value: function isBoundingBoxOverlappingWith(obj) { 28692 return this.shape.boundingBox.left < obj.right && this.shape.boundingBox.right > obj.left && this.shape.boundingBox.top < obj.bottom && this.shape.boundingBox.bottom > obj.top; 28693 } 28694 }], [{ 28695 key: 'parseOptions', 28696 28697 /** 28698 * This process all possible shorthands in the new options and makes sure that the parentOptions are fully defined. 28699 * Static so it can also be used by the handler. 28700 * @param parentOptions 28701 * @param newOptions 28702 */ 28703 value: function parseOptions(parentOptions, newOptions) { 28704 var allowDeletion = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 28705 var globalOptions = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3]; 28706 28707 var fields = ['color', 'font', 'fixed', 'shadow']; 28708 util.selectiveNotDeepExtend(fields, parentOptions, newOptions, allowDeletion); 28709 28710 // merge the shadow options into the parent. 28711 util.mergeOptions(parentOptions, newOptions, 'shadow', allowDeletion, globalOptions); 28712 28713 // individual shape newOptions 28714 if (newOptions.color !== undefined && newOptions.color !== null) { 28715 var parsedColor = util.parseColor(newOptions.color); 28716 util.fillIfDefined(parentOptions.color, parsedColor); 28717 } else if (allowDeletion === true && newOptions.color === null) { 28718 parentOptions.color = Object.create(globalOptions.color); // this sets the pointer of the option back to the global option. 28719 } 28720 28721 // handle the fixed options 28722 if (newOptions.fixed !== undefined && newOptions.fixed !== null) { 28723 if (typeof newOptions.fixed === 'boolean') { 28724 parentOptions.fixed.x = newOptions.fixed; 28725 parentOptions.fixed.y = newOptions.fixed; 28726 } else { 28727 if (newOptions.fixed.x !== undefined && typeof newOptions.fixed.x === 'boolean') { 28728 parentOptions.fixed.x = newOptions.fixed.x; 28729 } 28730 if (newOptions.fixed.y !== undefined && typeof newOptions.fixed.y === 'boolean') { 28731 parentOptions.fixed.y = newOptions.fixed.y; 28732 } 28733 } 28734 } 28735 28736 // handle the font options 28737 if (newOptions.font !== undefined && newOptions.font !== null) { 28738 _sharedLabel2['default'].parseOptions(parentOptions.font, newOptions); 28739 } else if (allowDeletion === true && newOptions.font === null) { 28740 parentOptions.font = Object.create(globalOptions.font); // this sets the pointer of the option back to the global option. 28741 } 28742 28743 // handle the scaling options, specifically the label part 28744 if (newOptions.scaling !== undefined) { 28745 util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label', allowDeletion, globalOptions.scaling); 28746 } 28747 } 28748 }]); 28749 28750 return Node; 28751 })(); 28752 28753 exports['default'] = Node; 28754 module.exports = exports['default']; 28755 28756/***/ }, 28757/* 63 */ 28758/***/ function(module, exports, __webpack_require__) { 28759 28760 'use strict'; 28761 28762 Object.defineProperty(exports, '__esModule', { 28763 value: true 28764 }); 28765 28766 var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); }
28766 else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })(); 28767 28768 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 28769 28770 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 28771 28772 var util = __webpack_require__(1); 28773 28774 var Label = (function () { 28775 function Label(body, options) { 28776 _classCallCheck(this, Label); 28777 28778 this.body = body; 28779 28780 this.pointToSelf = false; 28781 this.baseSize = undefined; 28782 this.fontOptions = {}; 28783 this.setOptions(options); 28784 this.size = { top: 0, left: 0, width: 0, height: 0, yLine: 0 }; // could be cached 28785 } 28786 28787 _createClass(Label, [{ 28788 key: 'setOptions', 28789 value: function setOptions(options) { 28790 var allowDeletion = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 28791 28792 this.nodeOptions = options; 28793 28794 // We want to keep the font options seperated from the node options. 28795 // The node options have to mirror the globals when they are not overruled. 28796 this.fontOptions = util.deepExtend({}, options.font, true); 28797 28798 if (options.label !== undefined) { 28799 this.labelDirty = true; 28800 } 28801 28802 if (options.font !== undefined) { 28803 Label.parseOptions(this.fontOptions, options, allowDeletion); 28804 if (typeof options.font === 'string') { 28805 this.baseSize = this.fontOptions.size; 28806 } else if (typeof options.font === 'object') { 28807 if (options.font.size !== undefined) { 28808 this.baseSize = options.font.size; 28809 } 28810 } 28811 } 28812 } 28813 }, { 28814 key: 'draw', 28815 28816 /** 28817 * Main function. This is called from anything that wants to draw a label. 28818 * @param ctx 28819 * @param x 28820 * @param y 28821 * @param selected 28822 * @param baseline 28823 */ 28824 value: function draw(ctx, x, y, selected) { 28825 var baseline = arguments.length <= 4 || arguments[4] === undefined ? 'middle' : arguments[4]; 28826 28827 // if no label, return 28828 if (this.nodeOptions.label === undefined) return; 28829 28830 // check if we have to render the label 28831 var viewFontSize = this.fontOptions.size * this.body.view.scale; 28832 if (this.nodeOptions.label && viewFontSize < this.nodeOptions.scaling.label.drawThreshold - 1) return; 28833 28834 // update the size cache if required 28835 this.calculateLabelSize(ctx, selected, x, y, baseline); 28836 28837 // create the fontfill background 28838 this._drawBackground(ctx); 28839 // draw text 28840 this._drawText(ctx, selected, x, y, baseline); 28841 } 28842 }, { 28843 key: '_drawBackground', 28844 28845 /** 28846 * Draws the label background 28847 * @param {CanvasRenderingContext2D} ctx 28848 * @private 28849 */ 28850 value: function _drawBackground(ctx) { 28851 if (this.fontOptions.background !== undefined && this.fontOptions.background !== 'none') { 28852 ctx.fillStyle = this.fontOptions.background; 28853 28854 var lineMargin = 2; 28855 28856 switch (this.fontOptions.align) { 28857 case 'middle': 28858 ctx.fillRect(-this.size.width * 0.5, -this.size.height * 0.5, this.size.width, this.size.height); 28859 break; 28860 case 'top': 28861 ctx.fillRect(-this.size.width * 0.5, -(this.size.height + lineMargin), this.size.width, this.size.height); 28862 break; 28863 case 'bottom': 28864 ctx.fillRect(-this.size.width * 0.5, lineMargin, this.size.width, this.size.height); 28865 break; 28866 default: 28867 ctx.fillRect(this.size.left, this.size.top - 0.5 * lineMargin, this.size.width, this.size.height); 28868 break; 28869 } 28870 } 28871 } 28872 }, {
28873 key: '_drawText', 28874 28875 /** 28876 * 28877 * @param ctx 28878 * @param x 28879 * @param baseline 28880 * @private 28881 */ 28882 value: function _drawText(ctx, selected, x, y) { 28883 var baseline = arguments.length <= 4 || arguments[4] === undefined ? 'middle' : arguments[4]; 28884 28885 var fontSize = this.fontOptions.size; 28886 var viewFontSize = fontSize * this.body.view.scale; 28887 // this ensures that there will not be HUGE letters on screen by setting an upper limit on the visible text size (regardless of zoomLevel) 28888 if (viewFontSize >= this.nodeOptions.scaling.label.maxVisible) { 28889 fontSize = Number(this.nodeOptions.scaling.label.maxVisible) / this.body.view.scale; 28890 } 28891 28892 var yLine = this.size.yLine; 28893 28894 var _getColor2 = this._getColor(viewFontSize); 28895 28896 var _getColor22 = _slicedToArray(_getColor2, 2); 28897 28898 var fontColor = _getColor22[0]; 28899 var strokeColor = _getColor22[1]; 28900 28901 var _setAlignment2 = this._setAlignment(ctx, x, yLine, baseline); 28902 28903 var _setAlignment22 = _slicedToArray(_setAlignment2, 2); 28904 28905 x = _setAlignment22[0]; 28906 yLine = _setAlignment22[1]; 28907 28908 // configure context for drawing the text 28909 ctx.font = (selected && this.nodeOptions.labelHighlightBold ? 'bold ' : '') + fontSize + 'px ' + this.fontOptions.face; 28910 ctx.fillStyle = fontColor; 28911 ctx.textAlign = 'center'; 28912 28913 // set the strokeWidth 28914 if (this.fontOptions.strokeWidth > 0) { 28915 ctx.lineWidth = this.fontOptions.strokeWidth; 28916 ctx.strokeStyle = strokeColor; 28917 ctx.lineJoin = 'round'; 28918 } 28919 28920 // draw the text 28921 for (var i = 0; i < this.lineCount; i++) { 28922 if (this.fontOptions.strokeWidth > 0) { 28923 ctx.strokeText(this.lines[i], x, yLine); 28924 } 28925 ctx.fillText(this.lines[i], x, yLine); 28926 yLine += fontSize; 28927 } 28928 } 28929 }, { 28930 key: '_setAlignment', 28931 value: function _setAlignment(ctx, x, yLine, baseline) { 28932 // check for label alignment (for edges) 28933 // TODO: make alignment for nodes 28934 if (this.fontOptions.align !== 'horizontal' && this.pointToSelf === false) { 28935 x = 0; 28936 yLine = 0; 28937 28938 var lineMargin = 2; 28939 if (this.fontOptions.align === 'top') { 28940 ctx.textBaseline = 'alphabetic'; 28941 yLine -= 2 * lineMargin; // distance from edge, required because we use alphabetic. Alphabetic has less difference between browsers 28942 } else if (this.fontOptions.align === 'bottom') { 28943 ctx.textBaseline = 'hanging'; 28944 yLine += 2 * lineMargin; // distance from edge, required because we use hanging. Hanging has less difference between browsers 28945 } else { 28946 ctx.textBaseline = 'middle'; 28947 } 28948 } else { 28949 ctx.textBaseline = baseline; 28950 } 28951 28952 return [x, yLine]; 28953 } 28954 }, { 28955 key: '_getColor', 28956 28957 /** 28958 * fade in when relative scale is between threshold and threshold - 1. 28959 * If the relative scale would be smaller than threshold -1 the draw function would have returned before coming here. 28960 * 28961 * @param viewFontSize 28962 * @returns {*[]} 28963 * @private 28964 */ 28965 value: function _getColor(viewFontSize) { 28966 var fontColor = this.fontOptions.color || '#000000'; 28967 var strokeColor = this.fontOptions.strokeColor || '#ffffff'; 28968 if (viewFontSize <= this.nodeOptions.scaling.label.drawThreshold) { 28969 var opacity = Math.max(0, Math.min(1, 1 - (this.nodeOptions.scaling.label.drawThreshold - viewFontSize))); 28970 fontColor = util.overrideOpacity(fontColor, opacity); 28971 strokeColor = util.overrideOpacity(strokeColor, opacity); 28972 } 28973 return [fontColor, strokeColor]; 28974 } 28975 }, { 28976 key: 'getTextSize', 28977 28978 /** 28979 * 28980 * @param ctx 28981 * @param selected 28982 * @returns {{width: number, height: number}} 28983 */ 28984 value: function getTextSize(ctx) { 28985 var selected = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 28986 28987 var size = { 28988 width: this._processLabel(ctx, selected), 28989 height: this.fontOptions.size * this.lineCount, 28990 lineCount: this.lineCount 28991 }; 28992 return size; 28993 } 28994 }, {
28995 key: 'calculateLabelSize', 28996 28997 /** 28998 * 28999 * @param ctx 29000 * @param selected 29001 * @param x 29002 * @param y 29003 * @param baseline 29004 */ 29005 value: function calculateLabelSize(ctx, selected) { 29006 var x = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2]; 29007 var y = arguments.length <= 3 || arguments[3] === undefined ? 0 : arguments[3]; 29008 var baseline = arguments.length <= 4 || arguments[4] === undefined ? 'middle' : arguments[4]; 29009 29010 if (this.labelDirty === true) { 29011 this.size.width = this._processLabel(ctx, selected); 29012 } 29013 this.size.height = this.fontOptions.size * this.lineCount; 29014 this.size.left = x - this.size.width * 0.5; 29015 this.size.top = y - this.size.height * 0.5; 29016 this.size.yLine = y + (1 - this.lineCount) * 0.5 * this.fontOptions.size; 29017 if (baseline === 'hanging') { 29018 this.size.top += 0.5 * this.fontOptions.size; 29019 this.size.top += 4; // distance from node, required because we use hanging. Hanging has less difference between browsers 29020 this.size.yLine += 4; // distance from node 29021 } 29022 29023 this.labelDirty = false; 29024 } 29025 }, { 29026 key: '_processLabel', 29027 29028 /** 29029 * This calculates the width as well as explodes the label string and calculates the amount of lines. 29030 * @param ctx 29031 * @param selected 29032 * @returns {number} 29033 * @private 29034 */ 29035 value: function _processLabel(ctx, selected) { 29036 var width = 0; 29037 var lines = ['']; 29038 var lineCount = 0; 29039 if (this.nodeOptions.label !== undefined) { 29040 lines = String(this.nodeOptions.label).split('\n'); 29041 lineCount = lines.length; 29042 ctx.font = (selected && this.nodeOptions.labelHighlightBold ? 'bold ' : '') + this.fontOptions.size + 'px ' + this.fontOptions.face; 29043 width = ctx.measureText(lines[0]).width; 29044 for (var i = 1; i < lineCount; i++) { 29045 var lineWidth = ctx.measureText(lines[i]).width; 29046 width = lineWidth > width ? lineWidth : width; 29047 } 29048 } 29049 this.lines = lines; 29050 this.lineCount = lineCount; 29051 29052 return width; 29053 } 29054 }], [{ 29055 key: 'parseOptions', 29056 value: function parseOptions(parentOptions, newOptions) { 29057 var allowDeletion = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 29058 29059 if (typeof newOptions.font === 'string') { 29060 var newOptionsArray = newOptions.font.split(' '); 29061 parentOptions.size = newOptionsArray[0].replace('px', ''); 29062 parentOptions.face = newOptionsArray[1]; 29063 parentOptions.color = newOptionsArray[2]; 29064 } else if (typeof newOptions.font === 'object') { 29065 util.fillIfDefined(parentOptions, newOptions.font, allowDeletion); 29066 } 29067 parentOptions.size = Number(parentOptions.size); 29068 } 29069 }]); 29070 29071 return Label; 29072 })(); 29073 29074 exports['default'] = Label; 29075 module.exports = exports['default']; 29076 29077/***/ }, 29078/* 64 */ 29079/***/ function(module, exports, __webpack_require__) { 29080 29081 'use strict'; 29082 29083 Object.defineProperty(exports, '__esModule', { 29084 value: true 29085 }); 29086 29087 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 29088 29089 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 29090 29091 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 29092 29093 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 29094 29095 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
29095onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 29096 29097 var _utilNodeBase = __webpack_require__(65); 29098 29099 var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); 29100 29101 var Box = (function (_NodeBase) { 29102 _inherits(Box, _NodeBase); 29103 29104 function Box(options, body, labelModule) { 29105 _classCallCheck(this, Box); 29106 29107 _get(Object.getPrototypeOf(Box.prototype), 'constructor', this).call(this, options, body, labelModule); 29108 } 29109 29110 _createClass(Box, [{ 29111 key: 'resize', 29112 value: function resize(ctx, selected) { 29113 if (this.width === undefined) { 29114 var margin = 5; 29115 var textSize = this.labelModule.getTextSize(ctx, selected); 29116 this.width = textSize.width + 2 * margin; 29117 this.height = textSize.height + 2 * margin; 29118 this.radius = 0.5 * this.width; 29119 } 29120 } 29121 }, { 29122 key: 'draw', 29123 value: function draw(ctx, x, y, selected, hover) { 29124 this.resize(ctx, selected); 29125 this.left = x - this.width / 2; 29126 this.top = y - this.height / 2; 29127 29128 var borderWidth = this.options.borderWidth; 29129 var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth; 29130 29131 ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border; 29132 ctx.lineWidth = selected ? selectionLineWidth : borderWidth; 29133 ctx.lineWidth /= this.body.view.scale; 29134 ctx.lineWidth = Math.min(this.width, ctx.lineWidth); 29135 29136 ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background; 29137 29138 var borderRadius = this.options.shapeProperties.borderRadius; // only effective for box 29139 ctx.roundRect(this.left, this.top, this.width, this.height, borderRadius); 29140 29141 // draw shadow if enabled 29142 this.enableShadow(ctx); 29143 // draw the background 29144 ctx.fill(); 29145 // disable shadows for other elements. 29146 this.disableShadow(ctx); 29147 29148 //draw dashed border if enabled, save and restore is required for firefox not to crash on unix. 29149 ctx.save(); 29150 this.enableBorderDashes(ctx); 29151 //draw the border 29152 ctx.stroke(); 29153 //disable dashed border for other elements 29154 this.disableBorderDashes(ctx); 29155 ctx.restore(); 29156 29157 this.updateBoundingBox(x, y, ctx, selected); 29158 this.labelModule.draw(ctx, x, y, selected); 29159 } 29160 }, { 29161 key: 'updateBoundingBox', 29162 value: function updateBoundingBox(x, y, ctx, selected) { 29163 this.resize(ctx, selected); 29164 this.left = x - this.width * 0.5; 29165 this.top = y - this.height * 0.5; 29166 29167 this.boundingBox.left = this.left; 29168 this.boundingBox.top = this.top; 29169 this.boundingBox.bottom = this.top + this.height; 29170 this.boundingBox.right = this.left + this.width; 29171 } 29172 }, { 29173 key: 'distanceToBorder', 29174 value: function distanceToBorder(ctx, angle) { 29175 this.resize(ctx); 29176 var a = this.width / 2; 29177 var b = this.height / 2; 29178 var w = Math.sin(angle) * a; 29179 var h = Math.cos(angle) * b; 29180 return a * b / Math.sqrt(w * w + h * h); 29181 } 29182 }]); 29183 29184 return Box; 29185 })(_utilNodeBase2['default']); 29186 29187 exports['default'] = Box; 29188 module.exports = exports['default']; 29189 29190/***/ }, 29191/* 65 */ 29192/***/ function(module, exports) { 29193 29194 'use strict'; 29195 29196 Object.defineProperty(exports, '__esModule', { 29197 value: true 29198 }); 29199 29200 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 29201 29202 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 29203 29204 var NodeBase = (function () { 29205 function NodeBase(options, body, labelModule) { 29206 _classCallCheck(this, NodeBase); 29207 29208 this.body = body; 29209 this.labelModule = labelModule; 29210 this.setOptions(options); 29211 this.top = undefined; 29212 this.left = undefined; 29213 this.height = undefined; 29214 this.width = undefined; 29215 this.radius = undefined; 29216 this.boundingBox = { top: 0, left: 0, right: 0, bottom: 0 }; 29217 } 29218 29219 _createClass(NodeBase, [{ 29220 key: 'setOptions', 29221 value: function setOptions(options) { 29222 this.options = options; 29223 } 29224 }, {
29225 key: '_distanceToBorder', 29226 value: function _distanceToBorder(ctx, angle) { 29227 var borderWidth = 1; 29228 this.resize(ctx); 29229 return Math.min(Math.abs(this.width / 2 / Math.cos(angle)), Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth; 29230 } 29231 }, { 29232 key: 'enableShadow', 29233 value: function enableShadow(ctx) { 29234 if (this.options.shadow.enabled === true) { 29235 ctx.shadowColor = 'rgba(0,0,0,0.5)'; 29236 ctx.shadowBlur = this.options.shadow.size; 29237 ctx.shadowOffsetX = this.options.shadow.x; 29238 ctx.shadowOffsetY = this.options.shadow.y; 29239 } 29240 } 29241 }, { 29242 key: 'disableShadow', 29243 value: function disableShadow(ctx) { 29244 if (this.options.shadow.enabled === true) { 29245 ctx.shadowColor = 'rgba(0,0,0,0)'; 29246 ctx.shadowBlur = 0; 29247 ctx.shadowOffsetX = 0; 29248 ctx.shadowOffsetY = 0; 29249 } 29250 } 29251 }, { 29252 key: 'enableBorderDashes', 29253 value: function enableBorderDashes(ctx) { 29254 if (this.options.shapeProperties.borderDashes !== false) { 29255 if (ctx.setLineDash !== undefined) { 29256 var dashes = this.options.shapeProperties.borderDashes; 29257 if (dashes === true) { 29258 dashes = [5, 15]; 29259 } 29260 ctx.setLineDash(dashes); 29261 } else { 29262 console.warn('setLineDash is not supported in this browser. The dashed borders cannot be used.'); 29263 this.options.shapeProperties.borderDashes = false; 29264 } 29265 } 29266 } 29267 }, { 29268 key: 'disableBorderDashes', 29269 value: function disableBorderDashes(ctx) { 29270 if (this.options.shapeProperties.borderDashes !== false) { 29271 if (ctx.setLineDash !== undefined) { 29272 ctx.setLineDash([0]); 29273 } else { 29274 console.warn('setLineDash is not supported in this browser. The dashed borders cannot be used.'); 29275 this.options.shapeProperties.borderDashes = false; 29276 } 29277 } 29278 } 29279 }]); 29280 29281 return NodeBase; 29282 })(); 29283 29284 exports['default'] = NodeBase; 29285 module.exports = exports['default']; 29286 29287/***/ }, 29288/* 66 */ 29289/***/ function(module, exports, __webpack_require__) { 29290 29291 'use strict'; 29292 29293 Object.defineProperty(exports, '__esModule', { 29294 value: true 29295 }); 29296 29297 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 29298 29299 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 29300 29301 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 29302 29303 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 29304 29305 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 29306 29307 var _utilCircleImageBase = __webpack_require__(67); 29308 29309 var _utilCircleImageBase2 = _interopRequireDefault(_utilCircleImageBase); 29310 29311 var Circle = (function (_CircleImageBase) { 29312 _inherits(Circle, _CircleImageBase); 29313 29314 function Circle(options, body, labelModule) { 29315 _classCallCheck(this, Circle); 29316 29317 _get(Object.getPrototypeOf(Circle.prototype), 'constructor', this).call(this, options, body, labelModule); 29318 } 29319 29320 _createClass(Circle, [{ 29321 key: 'resize', 29322 value: function resize(ctx, selected) { 29323 if (this.width === undefined) { 29324 var margin = 5; 29325 var textSize = this.labelModule.getTextSize(ctx, selected); 29326 var diameter = Math.max(textSize.width, textSize.height) + 2 * margin; 29327 this.options.size = diameter / 2; 29328 29329 this.width = diameter; 29330 this.height = diameter; 29331 this.radius = 0.5 * this.width; 29332 } 29333 } 29334 }, {
29335 key: 'draw', 29336 value: function draw(ctx, x, y, selected, hover) { 29337 this.resize(ctx, selected); 29338 this.left = x - this.width / 2; 29339 this.top = y - this.height / 2; 29340 29341 this._drawRawCircle(ctx, x, y, selected, hover, this.options.size); 29342 29343 this.boundingBox.top = y - this.options.size; 29344 this.boundingBox.left = x - this.options.size; 29345 this.boundingBox.right = x + this.options.size; 29346 this.boundingBox.bottom = y + this.options.size; 29347 29348 this.updateBoundingBox(x, y); 29349 this.labelModule.draw(ctx, x, y, selected); 29350 } 29351 }, { 29352 key: 'updateBoundingBox', 29353 value: function updateBoundingBox(x, y) { 29354 this.boundingBox.top = y - this.options.size; 29355 this.boundingBox.left = x - this.options.size; 29356 this.boundingBox.right = x + this.options.size; 29357 this.boundingBox.bottom = y + this.options.size; 29358 } 29359 }, { 29360 key: 'distanceToBorder', 29361 value: function distanceToBorder(ctx, angle) { 29362 this.resize(ctx); 29363 var a = this.width / 2; 29364 var b = this.height / 2; 29365 var w = Math.sin(angle) * a; 29366 var h = Math.cos(angle) * b; 29367 return a * b / Math.sqrt(w * w + h * h); 29368 } 29369 }]); 29370 29371 return Circle; 29372 })(_utilCircleImageBase2['default']); 29373 29374 exports['default'] = Circle; 29375 module.exports = exports['default']; 29376 29377/***/ }, 29378/* 67 */ 29379/***/ function(module, exports, __webpack_require__) { 29380 29381 'use strict'; 29382 29383 Object.defineProperty(exports, '__esModule', { 29384 value: true 29385 }); 29386 29387 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 29388 29389 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 29390 29391 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 29392 29393 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 29394 29395 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 29396 29397 var _utilNodeBase = __webpack_require__(65); 29398 29399 var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); 29400 29401 var CircleImageBase = (function (_NodeBase) { 29402 _inherits(CircleImageBase, _NodeBase); 29403 29404 function CircleImageBase(options, body, labelModule) { 29405 _classCallCheck(this, CircleImageBase); 29406 29407 _get(Object.getPrototypeOf(CircleImageBase.prototype), 'constructor', this).call(this, options, body, labelModule); 29408 this.labelOffset = 0; 29409 this.imageLoaded = false; 29410 } 29411 29412 _createClass(CircleImageBase, [{ 29413 key: 'setOptions', 29414 value: function setOptions(options, imageObj) { 29415 this.options = options; 29416 if (imageObj) { 29417 this.imageObj = imageObj; 29418 } 29419 } 29420 }, {
29421 key: '_resizeImage', 29422 29423 /** 29424 * This function resizes the image by the options size when the image has not yet loaded. If the image has loaded, we 29425 * force the update of the size again. 29426 * 29427 * @private 29428 */ 29429 value: function _resizeImage() { 29430 var force = false; 29431 if (!this.imageObj.width || !this.imageObj.height) { 29432 // undefined or 0 29433 this.imageLoaded = false; 29434 } else if (this.imageLoaded === false) { 29435 this.imageLoaded = true; 29436 force = true; 29437 } 29438 29439 if (!this.width || !this.height || force === true) { 29440 // undefined or 0 29441 var width, height, ratio; 29442 if (this.imageObj.width && this.imageObj.height) { 29443 // not undefined or 0 29444 width = 0; 29445 height = 0; 29446 } 29447 if (this.options.shapeProperties.useImageSize === false) { 29448 if (this.imageObj.width > this.imageObj.height) { 29449 ratio = this.imageObj.width / this.imageObj.height; 29450 width = this.options.size * 2 * ratio || this.imageObj.width; 29451 height = this.options.size * 2 || this.imageObj.height; 29452 } else { 29453 if (this.imageObj.width && this.imageObj.height) { 29454 // not undefined or 0 29455 ratio = this.imageObj.height / this.imageObj.width; 29456 } else { 29457 ratio = 1; 29458 } 29459 width = this.options.size * 2; 29460 height = this.options.size * 2 * ratio; 29461 } 29462 } else { 29463 // when not using the size property, we use the image size 29464 width = this.imageObj.width; 29465 height = this.imageObj.height; 29466 } 29467 this.width = width; 29468 this.height = height; 29469 this.radius = 0.5 * this.width; 29470 } 29471 } 29472 }, { 29473 key: '_drawRawCircle', 29474 value: function _drawRawCircle(ctx, x, y, selected, hover, size) { 29475 var borderWidth = this.options.borderWidth; 29476 var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth; 29477 29478 ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border; 29479 29480 ctx.lineWidth = selected ? selectionLineWidth : borderWidth; 29481 ctx.lineWidth *= this.networkScaleInv; 29482 ctx.lineWidth = Math.min(this.width, ctx.lineWidth); 29483 ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background; 29484 ctx.circle(x, y, size); 29485 29486 // draw shadow if enabled 29487 this.enableShadow(ctx); 29488 // draw the background 29489 ctx.fill(); 29490 // disable shadows for other elements. 29491 this.disableShadow(ctx); 29492 29493 //draw dashed border if enabled, save and restore is required for firefox not to crash on unix. 29494 ctx.save(); 29495 this.enableBorderDashes(ctx); 29496 //draw the border 29497 ctx.stroke(); 29498 //disable dashed border for other elements 29499 this.disableBorderDashes(ctx); 29500 ctx.restore(); 29501 } 29502 }, { 29503 key: '_drawImageAtPosition', 29504 value: function _drawImageAtPosition(ctx) { 29505 if (this.imageObj.width != 0) { 29506 // draw the image 29507 ctx.globalAlpha = 1.0; 29508 29509 // draw shadow if enabled 29510 this.enableShadow(ctx); 29511 29512 // draw image 29513 ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height); 29514 29515 // disable shadows for other elements. 29516 this.disableShadow(ctx); 29517 } 29518 } 29519 }, { 29520 key: '_drawImageLabel', 29521 value: function _drawImageLabel(ctx, x, y, selected) { 29522 var yLabel; 29523 var offset = 0; 29524 29525 if (this.height !== undefined) { 29526 offset = this.height * 0.5; 29527 var labelDimensions = this.labelModule.getTextSize(ctx); 29528 if (labelDimensions.lineCount >= 1) { 29529 offset += labelDimensions.height / 2; 29530 } 29531 } 29532 29533 yLabel = y + offset; 29534 29535 if (this.options.label) { 29536 this.labelOffset = offset; 29537 } 29538 this.labelModule.draw(ctx, x, yLabel, selected, 'hanging'); 29539 } 29540 }]); 29541 29542 return CircleImageBase; 29543 })(_utilNodeBase2['default']); 29544 29545 exports['default'] = CircleImageBase; 29546 module.exports = exports['default']; 29547 29548/***/ }, 29549/* 68 */ 29550/***/ function(module, exports, __webpack_require__) { 29551 29552 'use strict'; 29553 29554 Object.defineProperty(exports, '__esModule', { 29555 value: true 29556 }); 29557 29558 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 29559 29560 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 29561 29562 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 29563 29564 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 29565 29566 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
29566onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 29567 29568 var _utilCircleImageBase = __webpack_require__(67); 29569 29570 var _utilCircleImageBase2 = _interopRequireDefault(_utilCircleImageBase); 29571 29572 var CircularImage = (function (_CircleImageBase) { 29573 _inherits(CircularImage, _CircleImageBase); 29574 29575 function CircularImage(options, body, labelModule, imageObj) { 29576 _classCallCheck(this, CircularImage); 29577 29578 _get(Object.getPrototypeOf(CircularImage.prototype), 'constructor', this).call(this, options, body, labelModule); 29579 this.imageObj = imageObj; 29580 this._swapToImageResizeWhenImageLoaded = true; 29581 } 29582 29583 _createClass(CircularImage, [{ 29584 key: 'resize', 29585 value: function resize() { 29586 if (this.imageObj.src === undefined || this.imageObj.width === undefined || this.imageObj.height === undefined) { 29587 if (!this.width) { 29588 var diameter = this.options.size * 2; 29589 this.width = diameter; 29590 this.height = diameter; 29591 this._swapToImageResizeWhenImageLoaded = true; 29592 this.radius = 0.5 * this.width; 29593 } 29594 } else { 29595 if (this._swapToImageResizeWhenImageLoaded) { 29596 this.width = undefined; 29597 this.height = undefined; 29598 this._swapToImageResizeWhenImageLoaded = false; 29599 } 29600 this._resizeImage(); 29601 } 29602 } 29603 }, { 29604 key: 'draw', 29605 value: function draw(ctx, x, y, selected, hover) { 29606 this.resize(); 29607 29608 this.left = x - this.width / 2; 29609 this.top = y - this.height / 2; 29610 29611 var size = Math.min(0.5 * this.height, 0.5 * this.width); 29612 29613 // draw the backgroun circle. IMPORTANT: the stroke in this method is used by the clip method below. 29614 this._drawRawCircle(ctx, x, y, selected, hover, size); 29615 29616 // now we draw in the cicle, we save so we can revert the clip operation after drawing. 29617 ctx.save(); 29618 // clip is used to use the stroke in drawRawCircle as an area that we can draw in. 29619 ctx.clip(); 29620 // draw the image 29621 this._drawImageAtPosition(ctx); 29622 // restore so we can again draw on the full canvas 29623 ctx.restore(); 29624 29625 this._drawImageLabel(ctx, x, y, selected); 29626 29627 this.updateBoundingBox(x, y); 29628 } 29629 }, { 29630 key: 'updateBoundingBox', 29631 value: function updateBoundingBox(x, y) { 29632 this.boundingBox.top = y - this.options.size; 29633 this.boundingBox.left = x - this.options.size; 29634 this.boundingBox.right = x + this.options.size; 29635 this.boundingBox.bottom = y + this.options.size; 29636 this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left); 29637 this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width); 29638 this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelOffset); 29639 } 29640 }, { 29641 key: 'distanceToBorder', 29642 value: function distanceToBorder(ctx, angle) { 29643 this.resize(ctx); 29644 return this._distanceToBorder(ctx, angle); 29645 } 29646 }]); 29647 29648 return CircularImage; 29649 })(_utilCircleImageBase2['default']); 29650 29651 exports['default'] = CircularImage; 29652 module.exports = exports['default']; 29653 29654/***/ }, 29655/* 69 */ 29656/***/ function(module, exports, __webpack_require__) { 29657 29658 'use strict'; 29659 29660 Object.defineProperty(exports, '__esModule', { 29661 value: true 29662 }); 29663 29664 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 29665 29666 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 29667 29668 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 29669 29670 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 29671 29672 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
29672onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 29673 29674 var _utilNodeBase = __webpack_require__(65); 29675 29676 var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); 29677 29678 var Database = (function (_NodeBase) { 29679 _inherits(Database, _NodeBase); 29680 29681 function Database(options, body, labelModule) { 29682 _classCallCheck(this, Database); 29683 29684 _get(Object.getPrototypeOf(Database.prototype), 'constructor', this).call(this, options, body, labelModule); 29685 } 29686 29687 _createClass(Database, [{ 29688 key: 'resize', 29689 value: function resize(ctx, selected) { 29690 if (this.width === undefined) { 29691 var margin = 5; 29692 var textSize = this.labelModule.getTextSize(ctx, selected); 29693 var size = textSize.width + 2 * margin; 29694 this.width = size; 29695 this.height = size; 29696 this.radius = 0.5 * this.width; 29697 } 29698 } 29699 }, { 29700 key: 'draw', 29701 value: function draw(ctx, x, y, selected, hover) { 29702 this.resize(ctx, selected); 29703 this.left = x - this.width / 2; 29704 this.top = y - this.height / 2; 29705 29706 var borderWidth = this.options.borderWidth; 29707 var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth; 29708 29709 ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border; 29710 ctx.lineWidth = this.selected ? selectionLineWidth : borderWidth; 29711 ctx.lineWidth *= this.networkScaleInv; 29712 ctx.lineWidth = Math.min(this.width, ctx.lineWidth); 29713 29714 ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background; 29715 ctx.database(x - this.width / 2, y - this.height * 0.5, this.width, this.height); 29716 29717 // draw shadow if enabled 29718 this.enableShadow(ctx); 29719 // draw the background 29720 ctx.fill(); 29721 // disable shadows for other elements. 29722 this.disableShadow(ctx); 29723 29724 //draw dashed border if enabled, save and restore is required for firefox not to crash on unix. 29725 ctx.save(); 29726 this.enableBorderDashes(ctx); 29727 //draw the border 29728 ctx.stroke(); 29729 //disable dashed border for other elements 29730 this.disableBorderDashes(ctx); 29731 ctx.restore(); 29732 29733 this.updateBoundingBox(x, y, ctx, selected); 29734 this.labelModule.draw(ctx, x, y, selected); 29735 } 29736 }, { 29737 key: 'updateBoundingBox', 29738 value: function updateBoundingBox(x, y, ctx, selected) { 29739 this.resize(ctx, selected); 29740 29741 this.left = x - this.width * 0.5; 29742 this.top = y - this.height * 0.5; 29743 29744 this.boundingBox.left = this.left; 29745 this.boundingBox.top = this.top; 29746 this.boundingBox.bottom = this.top + this.height; 29747 this.boundingBox.right = this.left + this.width; 29748 } 29749 }, { 29750 key: 'distanceToBorder', 29751 value: function distanceToBorder(ctx, angle) { 29752 this.resize(ctx); 29753 var a = this.width / 2; 29754 var b = this.height / 2; 29755 var w = Math.sin(angle) * a; 29756 var h = Math.cos(angle) * b; 29757 return a * b / Math.sqrt(w * w + h * h); 29758 } 29759 }]); 29760 29761 return Database; 29762 })(_utilNodeBase2['default']); 29763 29764 exports['default'] = Database; 29765 module.exports = exports['default']; 29766 29767/***/ }, 29768/* 70 */ 29769/***/ function(module, exports, __webpack_require__) { 29770 29771 'use strict'; 29772 29773 Object.defineProperty(exports, '__esModule', { 29774 value: true 29775 }); 29776 29777 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 29778 29779 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 29780 29781 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 29782 29783 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 29784 29785 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
29785onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 29786 29787 var _utilShapeBase = __webpack_require__(71); 29788 29789 var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); 29790 29791 var Diamond = (function (_ShapeBase) { 29792 _inherits(Diamond, _ShapeBase); 29793 29794 function Diamond(options, body, labelModule) { 29795 _classCallCheck(this, Diamond); 29796 29797 _get(Object.getPrototypeOf(Diamond.prototype), 'constructor', this).call(this, options, body, labelModule); 29798 } 29799 29800 _createClass(Diamond, [{ 29801 key: 'resize', 29802 value: function resize(ctx) { 29803 this._resizeShape(); 29804 } 29805 }, { 29806 key: 'draw', 29807 value: function draw(ctx, x, y, selected, hover) { 29808 this._drawShape(ctx, 'diamond', 4, x, y, selected, hover); 29809 } 29810 }, { 29811 key: 'distanceToBorder', 29812 value: function distanceToBorder(ctx, angle) { 29813 return this._distanceToBorder(ctx, angle); 29814 } 29815 }]); 29816 29817 return Diamond; 29818 })(_utilShapeBase2['default']); 29819 29820 exports['default'] = Diamond; 29821 module.exports = exports['default']; 29822 29823/***/ }, 29824/* 71 */ 29825/***/ function(module, exports, __webpack_require__) { 29826 29827 'use strict'; 29828 29829 Object.defineProperty(exports, '__esModule', { 29830 value: true 29831 }); 29832 29833 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 29834 29835 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 29836 29837 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 29838 29839 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 29840 29841 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 29842 29843 var _utilNodeBase = __webpack_require__(65); 29844 29845 var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); 29846 29847 var ShapeBase = (function (_NodeBase) { 29848 _inherits(ShapeBase, _NodeBase); 29849 29850 function ShapeBase(options, body, labelModule) { 29851 _classCallCheck(this, ShapeBase); 29852 29853 _get(Object.getPrototypeOf(ShapeBase.prototype), 'constructor', this).call(this, options, body, labelModule); 29854 } 29855 29856 _createClass(ShapeBase, [{ 29857 key: '_resizeShape', 29858 value: function _resizeShape() { 29859 if (this.width === undefined) { 29860 var size = 2 * this.options.size; 29861 this.width = size; 29862 this.height = size; 29863 this.radius = 0.5 * this.width; 29864 } 29865 } 29866 }, { 29867 key: '_drawShape', 29868 value: function _drawShape(ctx, shape, sizeMultiplier, x, y, selected, hover) { 29869 this._resizeShape(); 29870 29871 this.left = x - this.width / 2; 29872 this.top = y - this.height / 2; 29873 29874 var borderWidth = this.options.borderWidth; 29875 var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth; 29876 29877 ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border; 29878 ctx.lineWidth = selected ? selectionLineWidth : borderWidth; 29879 ctx.lineWidth /= this.body.view.scale; 29880 ctx.lineWidth = Math.min(this.width, ctx.lineWidth); 29881 ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background; 29882 ctx[shape](x, y, this.options.size); 29883 29884 // draw shadow if enabled 29885 this.enableShadow(ctx); 29886 // draw the background 29887 ctx.fill(); 29888 // disable shadows for other elements. 29889 this.disableShadow(ctx); 29890 29891 //draw dashed border if enabled, save and restore is required for firefox not to crash on unix. 29892 ctx.save(); 29893 this.enableBorderDashes(ctx); 29894 //draw the border 29895 ctx.stroke(); 29896 //disable dashed border for other elements 29897 this.disableBorderDashes(ctx);
29898 ctx.restore(); 29899 29900 if (this.options.label !== undefined) { 29901 var yLabel = y + 0.5 * this.height + 3; // the + 3 is to offset it a bit below the node. 29902 this.labelModule.draw(ctx, x, yLabel, selected, 'hanging'); 29903 } 29904 29905 this.updateBoundingBox(x, y); 29906 } 29907 }, { 29908 key: 'updateBoundingBox', 29909 value: function updateBoundingBox(x, y) { 29910 this.boundingBox.top = y - this.options.size; 29911 this.boundingBox.left = x - this.options.size; 29912 this.boundingBox.right = x + this.options.size; 29913 this.boundingBox.bottom = y + this.options.size; 29914 29915 if (this.options.label !== undefined && this.labelModule.size.width > 0) { 29916 this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left); 29917 this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width); 29918 this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelModule.size.height + 3); 29919 } 29920 } 29921 }]); 29922 29923 return ShapeBase; 29924 })(_utilNodeBase2['default']); 29925 29926 exports['default'] = ShapeBase; 29927 module.exports = exports['default']; 29928 29929/***/ }, 29930/* 72 */ 29931/***/ function(module, exports, __webpack_require__) { 29932 29933 'use strict'; 29934 29935 Object.defineProperty(exports, '__esModule', { 29936 value: true 29937 }); 29938 29939 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 29940 29941 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 29942 29943 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 29944 29945 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 29946 29947 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 29948 29949 var _utilShapeBase = __webpack_require__(71); 29950 29951 var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); 29952 29953 var Dot = (function (_ShapeBase) { 29954 _inherits(Dot, _ShapeBase); 29955 29956 function Dot(options, body, labelModule) { 29957 _classCallCheck(this, Dot); 29958 29959 _get(Object.getPrototypeOf(Dot.prototype), 'constructor', this).call(this, options, body, labelModule); 29960 } 29961 29962 _createClass(Dot, [{ 29963 key: 'resize', 29964 value: function resize(ctx) { 29965 this._resizeShape(); 29966 } 29967 }, { 29968 key: 'draw', 29969 value: function draw(ctx, x, y, selected, hover) { 29970 this._drawShape(ctx, 'circle', 2, x, y, selected, hover); 29971 } 29972 }, { 29973 key: 'distanceToBorder', 29974 value: function distanceToBorder(ctx, angle) { 29975 this.resize(ctx); 29976 return this.options.size + this.options.borderWidth; 29977 } 29978 }]); 29979 29980 return Dot; 29981 })(_utilShapeBase2['default']); 29982 29983 exports['default'] = Dot; 29984 module.exports = exports['default']; 29985 29986/***/ }, 29987/* 73 */ 29988/***/ function(module, exports, __webpack_require__) { 29989 29990 'use strict'; 29991 29992 Object.defineProperty(exports, '__esModule', { 29993 value: true 29994 }); 29995 29996 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 29997 29998 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 29999 30000 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 30001 30002 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 30003 30004 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
30004onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 30005 30006 var _utilNodeBase = __webpack_require__(65); 30007 30008 var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); 30009 30010 var Ellipse = (function (_NodeBase) { 30011 _inherits(Ellipse, _NodeBase); 30012 30013 function Ellipse(options, body, labelModule) { 30014 _classCallCheck(this, Ellipse); 30015 30016 _get(Object.getPrototypeOf(Ellipse.prototype), 'constructor', this).call(this, options, body, labelModule); 30017 } 30018 30019 _createClass(Ellipse, [{ 30020 key: 'resize', 30021 value: function resize(ctx, selected) { 30022 if (this.width === undefined) { 30023 var textSize = this.labelModule.getTextSize(ctx, selected); 30024 30025 this.width = textSize.width * 1.5; 30026 this.height = textSize.height * 2; 30027 if (this.width < this.height) { 30028 this.width = this.height; 30029 } 30030 this.radius = 0.5 * this.width; 30031 } 30032 } 30033 }, { 30034 key: 'draw', 30035 value: function draw(ctx, x, y, selected, hover) { 30036 this.resize(ctx, selected); 30037 this.left = x - this.width * 0.5; 30038 this.top = y - this.height * 0.5; 30039 30040 var borderWidth = this.options.borderWidth; 30041 var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth; 30042 30043 ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border; 30044 30045 ctx.lineWidth = selected ? selectionLineWidth : borderWidth; 30046 ctx.lineWidth /= this.body.view.scale; 30047 ctx.lineWidth = Math.min(this.width, ctx.lineWidth); 30048 30049 ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background; 30050 ctx.ellipse(this.left, this.top, this.width, this.height); 30051 30052 // draw shadow if enabled 30053 this.enableShadow(ctx); 30054 // draw the background 30055 ctx.fill(); 30056 // disable shadows for other elements. 30057 this.disableShadow(ctx); 30058 30059 //draw dashed border if enabled, save and restore is required for firefox not to crash on unix. 30060 ctx.save(); 30061 this.enableBorderDashes(ctx); 30062 //draw the border 30063 ctx.stroke(); 30064 //disable dashed border for other elements 30065 this.disableBorderDashes(ctx); 30066 ctx.restore(); 30067 30068 this.updateBoundingBox(x, y, ctx, selected); 30069 this.labelModule.draw(ctx, x, y, selected); 30070 } 30071 }, { 30072 key: 'updateBoundingBox', 30073 value: function updateBoundingBox(x, y, ctx, selected) { 30074 this.resize(ctx, selected); // just in case 30075 30076 this.left = x - this.width * 0.5; 30077 this.top = y - this.height * 0.5; 30078 30079 this.boundingBox.left = this.left; 30080 this.boundingBox.top = this.top; 30081 this.boundingBox.bottom = this.top + this.height; 30082 this.boundingBox.right = this.left + this.width; 30083 } 30084 }, { 30085 key: 'distanceToBorder', 30086 value: function distanceToBorder(ctx, angle) { 30087 this.resize(ctx); 30088 var a = this.width * 0.5; 30089 var b = this.height * 0.5; 30090 var w = Math.sin(angle) * a; 30091 var h = Math.cos(angle) * b; 30092 return a * b / Math.sqrt(w * w + h * h); 30093 } 30094 }]); 30095 30096 return Ellipse; 30097 })(_utilNodeBase2['default']); 30098 30099 exports['default'] = Ellipse; 30100 module.exports = exports['default']; 30101 30102/***/ }, 30103/* 74 */ 30104/***/ function(module, exports, __webpack_require__) { 30105 30106 'use strict'; 30107 30108 Object.defineProperty(exports, '__esModule', { 30109 value: true 30110 }); 30111 30112 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 30113 30114 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 30115 30116 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 30117 30118 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 30119 30120 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
30120onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 30121 30122 var _utilNodeBase = __webpack_require__(65); 30123 30124 var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); 30125 30126 var Icon = (function (_NodeBase) { 30127 _inherits(Icon, _NodeBase); 30128 30129 function Icon(options, body, labelModule) { 30130 _classCallCheck(this, Icon); 30131 30132 _get(Object.getPrototypeOf(Icon.prototype), 'constructor', this).call(this, options, body, labelModule); 30133 } 30134 30135 _createClass(Icon, [{ 30136 key: 'resize', 30137 value: function resize(ctx) { 30138 if (this.width === undefined) { 30139 var margin = 5; 30140 var iconSize = { 30141 width: Number(this.options.icon.size), 30142 height: Number(this.options.icon.size) 30143 }; 30144 this.width = iconSize.width + 2 * margin; 30145 this.height = iconSize.height + 2 * margin; 30146 this.radius = 0.5 * this.width; 30147 } 30148 } 30149 }, { 30150 key: 'draw', 30151 value: function draw(ctx, x, y, selected, hover) { 30152 this.resize(ctx); 30153 this.options.icon.size = this.options.icon.size || 50; 30154 30155 this.left = x - this.width * 0.5; 30156 this.top = y - this.height * 0.5; 30157 this._icon(ctx, x, y, selected); 30158 30159 if (this.options.label !== undefined) { 30160 var iconTextSpacing = 5; 30161 this.labelModule.draw(ctx, x, y + this.height * 0.5 + iconTextSpacing, selected); 30162 } 30163 30164 this.updateBoundingBox(x, y); 30165 } 30166 }, { 30167 key: 'updateBoundingBox', 30168 value: function updateBoundingBox(x, y) { 30169 this.boundingBox.top = y - this.options.icon.size * 0.5; 30170 this.boundingBox.left = x - this.options.icon.size * 0.5; 30171 this.boundingBox.right = x + this.options.icon.size * 0.5; 30172 this.boundingBox.bottom = y + this.options.icon.size * 0.5; 30173 30174 if (this.options.label !== undefined && this.labelModule.size.width > 0) { 30175 var iconTextSpacing = 5; 30176 this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left); 30177 this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width); 30178 this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelModule.size.height + iconTextSpacing); 30179 } 30180 } 30181 }, { 30182 key: '_icon', 30183 value: function _icon(ctx, x, y, selected) { 30184 var iconSize = Number(this.options.icon.size); 30185 30186 if (this.options.icon.code !== undefined) { 30187 ctx.font = (selected ? 'bold ' : '') + iconSize + 'px ' + this.options.icon.face; 30188 30189 // draw icon 30190 ctx.fillStyle = this.options.icon.color || 'black'; 30191 ctx.textAlign = 'center'; 30192 ctx.textBaseline = 'middle'; 30193 30194 // draw shadow if enabled 30195 this.enableShadow(ctx); 30196 ctx.fillText(this.options.icon.code, x, y); 30197 30198 // disable shadows for other elements. 30199 this.disableShadow(ctx); 30200 } else { 30201 console.error('When using the icon shape, you need to define the code in the icon options object. This can be done per node or globally.'); 30202 } 30203 } 30204 }, { 30205 key: 'distanceToBorder', 30206 value: function distanceToBorder(ctx, angle) { 30207 return this._distanceToBorder(ctx, angle); 30208 } 30209 }]); 30210 30211 return Icon; 30212 })(_utilNodeBase2['default']); 30213 30214 exports['default'] = Icon; 30215 module.exports = exports['default']; 30216 30217/***/ }, 30218/* 75 */ 30219/***/ function(module, exports, __webpack_require__) { 30220 30221 'use strict'; 30222 30223 Object.defineProperty(exports, '__esModule', { 30224 value: true 30225 }); 30226 30227 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 30228 30229 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 30230 30231 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 30232 30233 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 30234 30235 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
30235onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 30236 30237 var _utilCircleImageBase = __webpack_require__(67); 30238 30239 var _utilCircleImageBase2 = _interopRequireDefault(_utilCircleImageBase); 30240 30241 var Image = (function (_CircleImageBase) { 30242 _inherits(Image, _CircleImageBase); 30243 30244 function Image(options, body, labelModule, imageObj) { 30245 _classCallCheck(this, Image); 30246 30247 _get(Object.getPrototypeOf(Image.prototype), 'constructor', this).call(this, options, body, labelModule); 30248 this.imageObj = imageObj; 30249 } 30250 30251 _createClass(Image, [{ 30252 key: 'resize', 30253 value: function resize() { 30254 this._resizeImage(); 30255 } 30256 }, { 30257 key: 'draw', 30258 value: function draw(ctx, x, y, selected, hover) { 30259 this.resize(); 30260 this.left = x - this.width / 2; 30261 this.top = y - this.height / 2; 30262 30263 if (this.options.shapeProperties.useBorderWithImage === true) { 30264 var borderWidth = this.options.borderWidth; 30265 30266 var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth; 30267 30268 ctx.beginPath(); 30269 30270 // setup the line properties. 30271 ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border; 30272 ctx.lineWidth = selected ? selectionLineWidth : borderWidth; 30273 ctx.lineWidth /= this.body.view.scale; 30274 ctx.lineWidth = Math.min(this.width, ctx.lineWidth); 30275 30276 // set a fillstyle 30277 ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background; 30278 30279 // draw a rectangle to form the border around. This rectangle is filled so the opacity of a picture (in future vis releases?) can be used to tint the image 30280 ctx.rect(this.left - 0.5 * ctx.lineWidth, this.top - 0.5 * ctx.lineWidth, this.width + ctx.lineWidth, this.height + ctx.lineWidth); 30281 ctx.fill(); 30282 30283 //draw dashed border if enabled, save and restore is required for firefox not to crash on unix. 30284 ctx.save(); 30285 this.enableBorderDashes(ctx); 30286 //draw the border 30287 ctx.stroke(); 30288 //disable dashed border for other elements 30289 this.disableBorderDashes(ctx); 30290 ctx.restore(); 30291 30292 ctx.closePath(); 30293 } 30294 30295 this._drawImageAtPosition(ctx); 30296 30297 this._drawImageLabel(ctx, x, y, selected || hover); 30298 30299 this.updateBoundingBox(x, y); 30300 } 30301 }, { 30302 key: 'updateBoundingBox', 30303 value: function updateBoundingBox(x, y) { 30304 this.resize(); 30305 this.left = x - this.width / 2; 30306 this.top = y - this.height / 2; 30307 30308 this.boundingBox.top = this.top; 30309 this.boundingBox.left = this.left; 30310 this.boundingBox.right = this.left + this.width; 30311 this.boundingBox.bottom = this.top + this.height; 30312 30313 if (this.options.label !== undefined && this.labelModule.size.width > 0) { 30314 this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left); 30315 this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width); 30316 this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelOffset); 30317 } 30318 } 30319 }, { 30320 key: 'distanceToBorder', 30321 value: function distanceToBorder(ctx, angle) { 30322 this.resize(ctx); 30323 var a = this.width / 2; 30324 var b = this.height / 2; 30325 var w = Math.sin(angle) * a; 30326 var h = Math.cos(angle) * b; 30327 return a * b / Math.sqrt(w * w + h * h); 30328 } 30329 }]); 30330 30331 return Image; 30332 })(_utilCircleImageBase2['default']); 30333 30334 exports['default'] = Image; 30335 module.exports = exports['default']; 30336 30337/***/ }, 30338/* 76 */ 30339/***/ function(module, exports, __webpack_require__) { 30340 30341 'use strict'; 30342 30343 Object.defineProperty(exports, '__esModule', { 30344 value: true 30345 }); 30346 30347 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 30348 30349 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 30350 30351 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 30352 30353 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 30354 30355 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
vendor: 6,091 bytes, lines 30355-30467
30355onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 30356 30357 var _utilShapeBase = __webpack_require__(71); 30358 30359 var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); 30360 30361 var Square = (function (_ShapeBase) { 30362 _inherits(Square, _ShapeBase); 30363 30364 function Square(options, body, labelModule) { 30365 _classCallCheck(this, Square); 30366 30367 _get(Object.getPrototypeOf(Square.prototype), 'constructor', this).call(this, options, body, labelModule); 30368 } 30369 30370 _createClass(Square, [{ 30371 key: 'resize', 30372 value: function resize() { 30373 this._resizeShape(); 30374 } 30375 }, { 30376 key: 'draw', 30377 value: function draw(ctx, x, y, selected, hover) { 30378 this._drawShape(ctx, 'square', 2, x, y, selected, hover); 30379 } 30380 }, { 30381 key: 'distanceToBorder', 30382 value: function distanceToBorder(ctx, angle) { 30383 return this._distanceToBorder(ctx, angle); 30384 } 30385 }]); 30386 30387 return Square; 30388 })(_utilShapeBase2['default']); 30389 30390 exports['default'] = Square; 30391 module.exports = exports['default']; 30392 30393/***/ }, 30394/* 77 */ 30395/***/ function(module, exports, __webpack_require__) { 30396 30397 'use strict'; 30398 30399 Object.defineProperty(exports, '__esModule', { 30400 value: true 30401 }); 30402 30403 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 30404 30405 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 30406 30407 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 30408 30409 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 30410 30411 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 30412 30413 var _utilShapeBase = __webpack_require__(71); 30414 30415 var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); 30416 30417 var Star = (function (_ShapeBase) { 30418 _inherits(Star, _ShapeBase); 30419 30420 function Star(options, body, labelModule) { 30421 _classCallCheck(this, Star); 30422 30423 _get(Object.getPrototypeOf(Star.prototype), 'constructor', this).call(this, options, body, labelModule); 30424 } 30425 30426 _createClass(Star, [{ 30427 key: 'resize', 30428 value: function resize(ctx) { 30429 this._resizeShape(); 30430 } 30431 }, { 30432 key: 'draw', 30433 value: function draw(ctx, x, y, selected, hover) { 30434 this._drawShape(ctx, 'star', 4, x, y, selected, hover); 30435 } 30436 }, { 30437 key: 'distanceToBorder', 30438 value: function distanceToBorder(ctx, angle) { 30439 return this._distanceToBorder(ctx, angle); 30440 } 30441 }]); 30442 30443 return Star; 30444 })(_utilShapeBase2['default']); 30445 30446 exports['default'] = Star; 30447 module.exports = exports['default']; 30448 30449/***/ }, 30450/* 78 */ 30451/***/ function(module, exports, __webpack_require__) { 30452 30453 'use strict'; 30454 30455 Object.defineProperty(exports, '__esModule', { 30456 value: true 30457 }); 30458 30459 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 30460 30461 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 30462 30463 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 30464 30465 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 30466 30467 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
30467onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 30468 30469 var _utilNodeBase = __webpack_require__(65); 30470 30471 var _utilNodeBase2 = _interopRequireDefault(_utilNodeBase); 30472 30473 var Text = (function (_NodeBase) { 30474 _inherits(Text, _NodeBase); 30475 30476 function Text(options, body, labelModule) { 30477 _classCallCheck(this, Text); 30478 30479 _get(Object.getPrototypeOf(Text.prototype), 'constructor', this).call(this, options, body, labelModule); 30480 } 30481 30482 _createClass(Text, [{ 30483 key: 'resize', 30484 value: function resize(ctx, selected) { 30485 if (this.width === undefined) { 30486 var margin = 5; 30487 var textSize = this.labelModule.getTextSize(ctx, selected); 30488 this.width = textSize.width + 2 * margin; 30489 this.height = textSize.height + 2 * margin; 30490 this.radius = 0.5 * this.width; 30491 } 30492 } 30493 }, { 30494 key: 'draw', 30495 value: function draw(ctx, x, y, selected, hover) { 30496 this.resize(ctx, selected || hover); 30497 this.left = x - this.width / 2; 30498 this.top = y - this.height / 2; 30499 30500 // draw shadow if enabled 30501 this.enableShadow(ctx); 30502 this.labelModule.draw(ctx, x, y, selected || hover); 30503 30504 // disable shadows for other elements. 30505 this.disableShadow(ctx); 30506 30507 this.updateBoundingBox(x, y, ctx, selected); 30508 } 30509 }, { 30510 key: 'updateBoundingBox', 30511 value: function updateBoundingBox(x, y, ctx, selected) { 30512 this.resize(ctx, selected); 30513 30514 this.left = x - this.width / 2; 30515 this.top = y - this.height / 2; 30516 30517 this.boundingBox.top = this.top; 30518 this.boundingBox.left = this.left; 30519 this.boundingBox.right = this.left + this.width; 30520 this.boundingBox.bottom = this.top + this.height; 30521 } 30522 }, { 30523 key: 'distanceToBorder', 30524 value: function distanceToBorder(ctx, angle) { 30525 return this._distanceToBorder(ctx, angle); 30526 } 30527 }]); 30528 30529 return Text; 30530 })(_utilNodeBase2['default']); 30531 30532 exports['default'] = Text; 30533 module.exports = exports['default']; 30534 30535/***/ }, 30536/* 79 */ 30537/***/ function(module, exports, __webpack_require__) { 30538 30539 'use strict'; 30540 30541 Object.defineProperty(exports, '__esModule', { 30542 value: true 30543 }); 30544 30545 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 30546 30547 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 30548 30549 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 30550 30551 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 30552 30553 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 30554 30555 var _utilShapeBase = __webpack_require__(71); 30556 30557 var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); 30558
vendor: 5,088 bytes, lines 30559-30666
30559 var Triangle = (function (_ShapeBase) { 30560 _inherits(Triangle, _ShapeBase); 30561 30562 function Triangle(options, body, labelModule) { 30563 _classCallCheck(this, Triangle); 30564 30565 _get(Object.getPrototypeOf(Triangle.prototype), 'constructor', this).call(this, options, body, labelModule); 30566 } 30567 30568 _createClass(Triangle, [{ 30569 key: 'resize', 30570 value: function resize(ctx) { 30571 this._resizeShape(); 30572 } 30573 }, { 30574 key: 'draw', 30575 value: function draw(ctx, x, y, selected, hover) { 30576 this._drawShape(ctx, 'triangle', 3, x, y, selected, hover); 30577 } 30578 }, { 30579 key: 'distanceToBorder', 30580 value: function distanceToBorder(ctx, angle) { 30581 return this._distanceToBorder(ctx, angle); 30582 } 30583 }]); 30584 30585 return Triangle; 30586 })(_utilShapeBase2['default']); 30587 30588 exports['default'] = Triangle; 30589 module.exports = exports['default']; 30590 30591/***/ }, 30592/* 80 */ 30593/***/ function(module, exports, __webpack_require__) { 30594 30595 'use strict'; 30596 30597 Object.defineProperty(exports, '__esModule', { 30598 value: true 30599 }); 30600 30601 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 30602 30603 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 30604 30605 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 30606 30607 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 30608 30609 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 30610 30611 var _utilShapeBase = __webpack_require__(71); 30612 30613 var _utilShapeBase2 = _interopRequireDefault(_utilShapeBase); 30614 30615 var TriangleDown = (function (_ShapeBase) { 30616 _inherits(TriangleDown, _ShapeBase); 30617 30618 function TriangleDown(options, body, labelModule) { 30619 _classCallCheck(this, TriangleDown); 30620 30621 _get(Object.getPrototypeOf(TriangleDown.prototype), 'constructor', this).call(this, options, body, labelModule); 30622 } 30623 30624 _createClass(TriangleDown, [{ 30625 key: 'resize', 30626 value: function resize(ctx) { 30627 this._resizeShape(); 30628 } 30629 }, { 30630 key: 'draw', 30631 value: function draw(ctx, x, y, selected, hover) { 30632 this._drawShape(ctx, 'triangleDown', 3, x, y, selected, hover); 30633 } 30634 }, { 30635 key: 'distanceToBorder', 30636 value: function distanceToBorder(ctx, angle) { 30637 return this._distanceToBorder(ctx, angle); 30638 } 30639 }]); 30640 30641 return TriangleDown; 30642 })(_utilShapeBase2['default']); 30643 30644 exports['default'] = TriangleDown; 30645 module.exports = exports['default']; 30646 30647/***/ }, 30648/* 81 */ 30649/***/ function(module, exports, __webpack_require__) { 30650 30651 'use strict'; 30652 30653 Object.defineProperty(exports, '__esModule', { 30654 value: true 30655 }); 30656 30657 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 30658 30659 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 30660 30661 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 30662 30663 var _componentsEdge = __webpack_require__(82); 30664 30665 var _componentsEdge2 = _interopRequireDefault(_componentsEdge); 30666
30667 var _componentsSharedLabel = __webpack_require__(63); 30668 30669 var _componentsSharedLabel2 = _interopRequireDefault(_componentsSharedLabel); 30670 30671 var util = __webpack_require__(1); 30672 var DataSet = __webpack_require__(8); 30673 var DataView = __webpack_require__(10); 30674 30675 var EdgesHandler = (function () { 30676 function EdgesHandler(body, images, groups) { 30677 var _this = this; 30678 30679 _classCallCheck(this, EdgesHandler); 30680 30681 this.body = body; 30682 this.images = images; 30683 this.groups = groups; 30684 30685 // create the edge API in the body container 30686 this.body.functions.createEdge = this.create.bind(this); 30687 30688 this.edgesListeners = { 30689 add: function add(event, params) { 30690 _this.add(params.items); 30691 }, 30692 update: function update(event, params) { 30693 _this.update(params.items); 30694 }, 30695 remove: function remove(event, params) { 30696 _this.remove(params.items); 30697 } 30698 }; 30699 30700 this.options = {}; 30701 this.defaultOptions = { 30702 arrows: { 30703 to: { enabled: false, scaleFactor: 1 }, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1} 30704 middle: { enabled: false, scaleFactor: 1 }, 30705 from: { enabled: false, scaleFactor: 1 } 30706 }, 30707 color: { 30708 color: '#848484', 30709 highlight: '#848484', 30710 hover: '#848484', 30711 inherit: 'from', 30712 opacity: 1.0 30713 }, 30714 dashes: false, 30715 font: { 30716 color: '#343434', 30717 size: 14, // px 30718 face: 'arial', 30719 background: 'none', 30720 strokeWidth: 2, // px 30721 strokeColor: '#ffffff', 30722 align: 'horizontal' 30723 }, 30724 hidden: false, 30725 hoverWidth: 1.5, 30726 label: undefined, 30727 labelHighlightBold: true, 30728 length: undefined, 30729 physics: true, 30730 scaling: { 30731 min: 1, 30732 max: 15, 30733 label: { 30734 enabled: true, 30735 min: 14, 30736 max: 30, 30737 maxVisible: 30, 30738 drawThreshold: 5 30739 }, 30740 customScalingFunction: function customScalingFunction(min, max, total, value) { 30741 if (max === min) { 30742 return 0.5; 30743 } else { 30744 var scale = 1 / (max - min); 30745 return Math.max(0, (value - min) * scale); 30746 } 30747 } 30748 }, 30749 selectionWidth: 1.5, 30750 selfReferenceSize: 20, 30751 shadow: { 30752 enabled: false, 30753 size: 10, 30754 x: 5, 30755 y: 5 30756 }, 30757 smooth: { 30758 enabled: true, 30759 type: 'dynamic', 30760 forceDirection: 'none', 30761 roundness: 0.5 30762 }, 30763 title: undefined, 30764 width: 1, 30765 value: undefined 30766 }; 30767 30768 util.extend(this.options, this.defaultOptions); 30769 30770 this.bindEventListeners(); 30771 } 30772 30773 _createClass(EdgesHandler, [{ 30774 key: 'bindEventListeners', 30775 value: function bindEventListeners() { 30776 var _this2 = this; 30777 30778 // this allows external modules to force all dynamic curves to turn static. 30779 this.body.emitter.on('_forceDisableDynamicCurves', function (type) { 30780 if (type === 'dynamic') { 30781 type = 'continuous'; 30782 } 30783 var emitChange = false; 30784 for (var edgeId in _this2.body.edges) { 30785 if (_this2.body.edges.hasOwnProperty(edgeId)) { 30786 var edge = _this2.body.edges[edgeId]; 30787 var edgeData = _this2.body.data.edges._data[edgeId]; 30788 30789 // only forcilby remove the smooth curve if the data has been set of the edge has the smooth curves defined. 30790 // this is because a change in the global would not affect these curves. 30791 if (edgeData !== undefined) { 30792 var edgeOptions = edgeData.smooth; 30793 if (edgeOptions !== undefined) { 30794 if (edgeOptions.enabled === true && edgeOptions.type === 'dynamic') { 30795 if (type === undefined) { 30796 edge.setOptions({ smooth: false }); 30797 } else { 30798 edge.setOptions({ smooth: { type: type } }); 30799 } 30800 emitChange = true; 30801 } 30802 } 30803 } 30804 } 30805 } 30806 if (emitChange === true) { 30807 _this2.body.emitter.emit('_dataChanged'); 30808 } 30809 }); 30810 30811 // this is called when options of EXISTING nodes or edges have changed. 30812 this.body.emitter.on('_dataUpdated', function () { 30813 _this2.reconnectEdges(); 30814 _this2.markAllEdgesAsDirty(); 30815 }); 30816 30817 // refresh the edges. Used when reverting from hierarchical layout 30818 this.body.emitter.on('refreshEdges', this.refresh.bind(this)); 30819 this.body.emitter.on('refresh', this.refresh.bind(this)); 30820 this.body.emitter.on('destroy', function () { 30821 delete _this2.body.functions.createEdge; 30822 delete _this2.edgesListeners.add; 30823 delete _this2.edgesListeners.update; 30824 delete _this2.edgesListeners.remove; 30825 delete _this2.edgesListeners; 30826 }); 30827 } 30828 }, {
30829 key: 'setOptions', 30830 value: function setOptions(options) { 30831 if (options !== undefined) { 30832 // use the parser from the Edge class to fill in all shorthand notations 30833 _componentsEdge2['default'].parseOptions(this.options, options); 30834 30835 // hanlde multiple input cases for color 30836 if (options.color !== undefined) { 30837 this.markAllEdgesAsDirty(); 30838 } 30839 30840 // update smooth settings in all edges 30841 var dataChanged = false; 30842 if (options.smooth !== undefined) { 30843 for (var edgeId in this.body.edges) { 30844 if (this.body.edges.hasOwnProperty(edgeId)) { 30845 dataChanged = this.body.edges[edgeId].updateEdgeType() || dataChanged; 30846 } 30847 } 30848 } 30849 30850 // update fonts in all edges 30851 if (options.font !== undefined) { 30852 // use the parser from the Label class to fill in all shorthand notations 30853 _componentsSharedLabel2['default'].parseOptions(this.options.font, options); 30854 for (var edgeId in this.body.edges) { 30855 if (this.body.edges.hasOwnProperty(edgeId)) { 30856 this.body.edges[edgeId].updateLabelModule(); 30857 } 30858 } 30859 } 30860 30861 // update the state of the variables if needed 30862 if (options.hidden !== undefined || options.physics !== undefined || dataChanged === true) { 30863 this.body.emitter.emit('_dataChanged'); 30864 } 30865 } 30866 } 30867 }, { 30868 key: 'setData', 30869 30870 /** 30871 * Load edges by reading the data table 30872 * @param {Array | DataSet | DataView} edges The data containing the edges. 30873 * @private 30874 * @private 30875 */ 30876 value: function setData(edges) { 30877 var _this3 = this; 30878 30879 var doNotEmit = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 30880 30881 var oldEdgesData = this.body.data.edges; 30882 30883 if (edges instanceof DataSet || edges instanceof DataView) { 30884 this.body.data.edges = edges; 30885 } else if (Array.isArray(edges)) { 30886 this.body.data.edges = new DataSet(); 30887 this.body.data.edges.add(edges); 30888 } else if (!edges) { 30889 this.body.data.edges = new DataSet(); 30890 } else { 30891 throw new TypeError('Array or DataSet expected'); 30892 } 30893 30894 // TODO: is this null or undefined or false? 30895 if (oldEdgesData) { 30896 // unsubscribe from old dataset 30897 util.forEach(this.edgesListeners, function (callback, event) { 30898 oldEdgesData.off(event, callback); 30899 }); 30900 } 30901 30902 // remove drawn edges 30903 this.body.edges = {}; 30904 30905 // TODO: is this null or undefined or false? 30906 if (this.body.data.edges) { 30907 // subscribe to new dataset 30908 util.forEach(this.edgesListeners, function (callback, event) { 30909 _this3.body.data.edges.on(event, callback); 30910 }); 30911 30912 // draw all new nodes 30913 var ids = this.body.data.edges.getIds(); 30914 this.add(ids, true); 30915 } 30916 30917 if (doNotEmit === false) { 30918 this.body.emitter.emit('_dataChanged'); 30919 } 30920 } 30921 }, { 30922 key: 'add', 30923 30924 /** 30925 * Add edges 30926 * @param {Number[] | String[]} ids 30927 * @private 30928 */ 30929 value: function add(ids) { 30930 var doNotEmit = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 30931 30932 var edges = this.body.edges; 30933 var edgesData = this.body.data.edges; 30934 30935 for (var i = 0; i < ids.length; i++) { 30936 var id = ids[i]; 30937 30938 var oldEdge = edges[id]; 30939 if (oldEdge) { 30940 oldEdge.disconnect(); 30941 } 30942 30943 var data = edgesData.get(id, { 'showInternalIds': true }); 30944 edges[id] = this.create(data); 30945 } 30946 30947 if (doNotEmit === false) { 30948 this.body.emitter.emit('_dataChanged'); 30949 } 30950 } 30951 }, { 30952 key: 'update', 30953 30954 /** 30955 * Update existing edges, or create them when not yet existing 30956 * @param {Number[] | String[]} ids 30957 * @private 30958 */ 30959 value: function update(ids) { 30960 var edges = this.body.edges; 30961 var edgesData = this.body.data.edges; 30962 var dataChanged = false;
30963 for (var i = 0; i < ids.length; i++) { 30964 var id = ids[i]; 30965 var data = edgesData.get(id); 30966 var edge = edges[id]; 30967 if (edge !== undefined) { 30968 // update edge 30969 edge.disconnect(); 30970 dataChanged = edge.setOptions(data) || dataChanged; // if a support node is added, data can be changed. 30971 edge.connect(); 30972 } else { 30973 // create edge 30974 this.body.edges[id] = this.create(data); 30975 dataChanged = true; 30976 } 30977 } 30978 30979 if (dataChanged === true) { 30980 this.body.emitter.emit('_dataChanged'); 30981 } else { 30982 this.body.emitter.emit('_dataUpdated'); 30983 } 30984 } 30985 }, { 30986 key: 'remove', 30987 30988 /** 30989 * Remove existing edges. Non existing ids will be ignored 30990 * @param {Number[] | String[]} ids 30991 * @private 30992 */ 30993 value: function remove(ids) { 30994 var edges = this.body.edges; 30995 for (var i = 0; i < ids.length; i++) { 30996 var id = ids[i]; 30997 var edge = edges[id]; 30998 if (edge !== undefined) { 30999 edge.cleanup(); 31000 edge.disconnect(); 31001 delete edges[id]; 31002 } 31003 } 31004 31005 this.body.emitter.emit('_dataChanged'); 31006 } 31007 }, { 31008 key: 'refresh', 31009 value: function refresh() { 31010 var edges = this.body.edges; 31011 for (var edgeId in edges) { 31012 var edge = undefined; 31013 if (edges.hasOwnProperty(edgeId)) { 31014 edge = edges[edgeId]; 31015 } 31016 var data = this.body.data.edges._data[edgeId]; 31017 if (edge !== undefined && data !== undefined) { 31018 edge.setOptions(data); 31019 } 31020 } 31021 } 31022 }, { 31023 key: 'create', 31024 value: function create(properties) { 31025 return new _componentsEdge2['default'](properties, this.body, this.options); 31026 } 31027 }, { 31028 key: 'markAllEdgesAsDirty', 31029 value: function markAllEdgesAsDirty() { 31030 for (var edgeId in this.body.edges) { 31031 this.body.edges[edgeId].edgeType.colorDirty = true; 31032 } 31033 } 31034 }, { 31035 key: 'reconnectEdges', 31036 31037 /** 31038 * Reconnect all edges 31039 * @private 31040 */ 31041 value: function reconnectEdges() { 31042 var id; 31043 var nodes = this.body.nodes; 31044 var edges = this.body.edges; 31045 31046 for (id in nodes) { 31047 if (nodes.hasOwnProperty(id)) { 31048 nodes[id].edges = []; 31049 } 31050 } 31051 31052 for (id in edges) { 31053 if (edges.hasOwnProperty(id)) { 31054 var edge = edges[id]; 31055 edge.from = null; 31056 edge.to = null; 31057 edge.connect(); 31058 } 31059 } 31060 } 31061 }, { 31062 key: 'getConnectedNodes', 31063 value: function getConnectedNodes(edgeId) { 31064 var nodeList = []; 31065 if (this.body.edges[edgeId] !== undefined) { 31066 var edge = this.body.edges[edgeId]; 31067 if (edge.fromId) { 31068 nodeList.push(edge.fromId); 31069 } 31070 if (edge.toId) { 31071 nodeList.push(edge.toId); 31072 } 31073 } 31074 return nodeList; 31075 } 31076 }]); 31077 31078 return EdgesHandler; 31079 })(); 31080 31081 exports['default'] = EdgesHandler; 31082 module.exports = exports['default']; 31083 31084/***/ }, 31085/* 82 */ 31086/***/ function(module, exports, __webpack_require__) { 31087 31088 'use strict'; 31089 31090 Object.defineProperty(exports, '__esModule', { 31091 value: true 31092 }); 31093 31094 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 31095 31096 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 31097 31098 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 31099 31100 var _sharedLabel = __webpack_require__(63); 31101 31102 var _sharedLabel2 = _interopRequireDefault(_sharedLabel); 31103 31104 var _edgesCubicBezierEdge = __webpack_require__(83); 31105 31106 var _edgesCubicBezierEdge2 = _interopRequireDefault(_edgesCubicBezierEdge); 31107 31108 var _edgesBezierEdgeDynamic = __webpack_require__(87); 31109 31110 var _edgesBezierEdgeDynamic2 = _interopRequireDefault(_edgesBezierEdgeDynamic); 31111 31112 var _edgesBezierEdgeStatic = __webpack_require__(88); 31113 31114 var _edgesBezierEdgeStatic2 = _interopRequireDefault(_edgesBezierEdgeStatic); 31115 31116 var _edgesStraightEdge = __webpack_require__(89); 31117 31118 var _edgesStraightEdge2 = _interopRequireDefault(_edgesStraightEdge); 31119 31120 var util = __webpack_require__(1); 31121 31122 /** 31123 * @class Edge 31124 * 31125 * A edge connects two nodes 31126 * @param {Object} properties Object with options. Must contain 31127 * At least options from and to. 31128 * Available options: from (number), 31129 * to (number), label (string, color (string), 31130 * width (number), style (string), 31131 * length (number), title (string) 31132 * @param {Network} network A Network object, used to find and edge to 31133 * nodes. 31134 * @param {Object} constants An object with default values for 31135 * example for the color 31136 */ 31137 31138 var Edge = (function () { 31139 function Edge(options, body, globalOptions) { 31140 _classCallCheck(this, Edge); 31141 31142 if (body === undefined) { 31143 throw 'No body provided'; 31144 } 31145 this.options = util.bridgeObject(globalOptions); 31146 this.globalOptions = globalOptions; 31147 this.body = body; 31148 31149 // initialize variables 31150 this.id = undefined; 31151 this.fromId = undefined; 31152 this.toId = undefined; 31153 this.selected = false;
31154 this.hover = false; 31155 this.labelDirty = true; 31156 this.colorDirty = true; 31157 31158 this.baseWidth = this.options.width; 31159 this.baseFontSize = this.options.font.size; 31160 31161 this.from = undefined; // a node 31162 this.to = undefined; // a node 31163 31164 this.edgeType = undefined; 31165 31166 this.connected = false; 31167 31168 this.labelModule = new _sharedLabel2['default'](this.body, this.options); 31169 31170 this.setOptions(options); 31171 } 31172 31173 _createClass(Edge, [{ 31174 key: 'setOptions', 31175 31176 /** 31177 * Set or overwrite options for the edge 31178 * @param {Object} options an object with options 31179 * @param doNotEmit 31180 */ 31181 value: function setOptions(options) { 31182 if (!options) { 31183 return; 31184 } 31185 this.colorDirty = true; 31186 31187 Edge.parseOptions(this.options, options, true, this.globalOptions); 31188 31189 if (options.id !== undefined) { 31190 this.id = options.id; 31191 } 31192 if (options.from !== undefined) { 31193 this.fromId = options.from; 31194 } 31195 if (options.to !== undefined) { 31196 this.toId = options.to; 31197 } 31198 if (options.title !== undefined) { 31199 this.title = options.title; 31200 } 31201 if (options.value !== undefined) { 31202 options.value = parseFloat(options.value); 31203 } 31204 31205 // update label Module 31206 this.updateLabelModule(); 31207 31208 var dataChanged = this.updateEdgeType(); 31209 31210 // if anything has been updates, reset the selection width and the hover width 31211 this._setInteractionWidths(); 31212 31213 // A node is connected when it has a from and to node that both exist in the network.body.nodes. 31214 this.connect(); 31215 31216 if (options.hidden !== undefined || options.physics !== undefined) { 31217 dataChanged = true; 31218 } 31219 31220 return dataChanged; 31221 } 31222 }, { 31223 key: 'updateLabelModule', 31224 31225 /** 31226 * update the options in the label module 31227 */ 31228 value: function updateLabelModule() { 31229 this.labelModule.setOptions(this.options, true); 31230 if (this.labelModule.baseSize !== undefined) { 31231 this.baseFontSize = this.labelModule.baseSize; 31232 } 31233 } 31234 }, { 31235 key: 'updateEdgeType', 31236 31237 /** 31238 * update the edge type, set the options 31239 * @returns {boolean} 31240 */ 31241 value: function updateEdgeType() { 31242 var dataChanged = false; 31243 var changeInType = true; 31244 var smooth = this.options.smooth; 31245 if (this.edgeType !== undefined) { 31246 if (this.edgeType instanceof _edgesBezierEdgeDynamic2['default'] && smooth.enabled === true && smooth.type === 'dynamic') { 31247 changeInType = false;
31248 } 31249 if (this.edgeType instanceof _edgesCubicBezierEdge2['default'] && smooth.enabled === true && smooth.type === 'cubicBezier') { 31250 changeInType = false; 31251 } 31252 if (this.edgeType instanceof _edgesBezierEdgeStatic2['default'] && smooth.enabled === true && smooth.type !== 'dynamic' && smooth.type !== 'cubicBezier') { 31253 changeInType = false; 31254 } 31255 if (this.edgeType instanceof _edgesStraightEdge2['default'] && smooth.enabled === false) { 31256 changeInType = false; 31257 } 31258 31259 if (changeInType === true) { 31260 dataChanged = this.cleanup(); 31261 } 31262 } 31263 31264 if (changeInType === true) { 31265 if (this.options.smooth.enabled === true) { 31266 if (this.options.smooth.type === 'dynamic') { 31267 dataChanged = true; 31268 this.edgeType = new _edgesBezierEdgeDynamic2['default'](this.options, this.body, this.labelModule); 31269 } else if (this.options.smooth.type === 'cubicBezier') { 31270 this.edgeType = new _edgesCubicBezierEdge2['default'](this.options, this.body, this.labelModule); 31271 } else { 31272 this.edgeType = new _edgesBezierEdgeStatic2['default'](this.options, this.body, this.labelModule); 31273 } 31274 } else { 31275 this.edgeType = new _edgesStraightEdge2['default'](this.options, this.body, this.labelModule); 31276 } 31277 } else { 31278 // if nothing changes, we just set the options. 31279 this.edgeType.setOptions(this.options); 31280 } 31281 31282 return dataChanged; 31283 } 31284 }, { 31285 key: 'connect', 31286 31287 /** 31288 * Connect an edge to its nodes 31289 */ 31290 value: function connect() { 31291 this.disconnect(); 31292 31293 this.from = this.body.nodes[this.fromId] || undefined; 31294 this.to = this.body.nodes[this.toId] || undefined; 31295 this.connected = this.from !== undefined && this.to !== undefined; 31296 31297 if (this.connected === true) { 31298 this.from.attachEdge(this); 31299 this.to.attachEdge(this); 31300 } else { 31301 if (this.from) { 31302 this.from.detachEdge(this); 31303 } 31304 if (this.to) { 31305 this.to.detachEdge(this); 31306 } 31307 } 31308 31309 this.edgeType.connect(); 31310 } 31311 }, { 31312 key: 'disconnect', 31313 31314 /** 31315 * Disconnect an edge from its nodes 31316 */ 31317 value: function disconnect() { 31318 if (this.from) { 31319 this.from.detachEdge(this); 31320 this.from = undefined; 31321 } 31322 if (this.to) { 31323 this.to.detachEdge(this); 31324 this.to = undefined; 31325 } 31326 31327 this.connected = false; 31328 } 31329 }, { 31330 key: 'getTitle', 31331 31332 /** 31333 * get the title of this edge. 31334 * @return {string} title The title of the edge, or undefined when no title 31335 * has been set. 31336 */ 31337 value: function getTitle() { 31338 return this.title; 31339 } 31340 }, { 31341 key: 'isSelected', 31342 31343 /** 31344 * check if this node is selecte 31345 * @return {boolean} selected True if node is selected, else false 31346 */ 31347 value: function isSelected() { 31348 return this.selected; 31349 } 31350 }, { 31351 key: 'getValue', 31352 31353 /** 31354 * Retrieve the value of the edge. Can be undefined 31355 * @return {Number} value 31356 */ 31357 value: function getValue() { 31358 return this.options.value; 31359 } 31360 }, { 31361 key: 'setValueRange', 31362 31363 /** 31364 * Adjust the value range of the edge. The edge will adjust it's width 31365 * based on its value. 31366 * @param {Number} min 31367 * @param {Number} max 31368 * @param total 31369 */ 31370 value: function setValueRange(min, max, total) { 31371 if (this.options.value !== undefined) { 31372 var scale = this.options.scaling.customScalingFunction(min, max, total, this.options.value); 31373 var widthDiff = this.options.scaling.max - this.options.scaling.min; 31374 if (this.options.scaling.label.enabled === true) { 31375 var fontDiff = this.options.scaling.label.max - this.options.scaling.label.min; 31376 this.options.font.size = this.options.scaling.label.min + scale * fontDiff; 31377 } 31378 this.options.width = this.options.scaling.min + scale * widthDiff; 31379 } else { 31380 this.options.width = this.baseWidth; 31381 this.options.font.size = this.baseFontSize; 31382 } 31383 31384 this._setInteractionWidths(); 31385 } 31386 }, {
31387 key: '_setInteractionWidths', 31388 value: function _setInteractionWidths() { 31389 if (typeof this.options.hoverWidth === 'function') { 31390 this.edgeType.hoverWidth = this.options.hoverWidth(this.options.width); 31391 } else { 31392 this.edgeType.hoverWidth = this.options.hoverWidth + this.options.width; 31393 } 31394 31395 if (typeof this.options.selectionWidth === 'function') { 31396 this.edgeType.selectionWidth = this.options.selectionWidth(this.options.width); 31397 } else { 31398 this.edgeType.selectionWidth = this.options.selectionWidth + this.options.width; 31399 } 31400 } 31401 }, { 31402 key: 'draw', 31403 31404 /** 31405 * Redraw a edge 31406 * Draw this edge in the given canvas 31407 * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); 31408 * @param {CanvasRenderingContext2D} ctx 31409 */ 31410 value: function draw(ctx) { 31411 var via = this.edgeType.drawLine(ctx, this.selected, this.hover); 31412 this.drawArrows(ctx, via); 31413 this.drawLabel(ctx, via); 31414 } 31415 }, { 31416 key: 'drawArrows', 31417 value: function drawArrows(ctx, viaNode) { 31418 if (this.options.arrows.from.enabled === true) { 31419 this.edgeType.drawArrowHead(ctx, 'from', viaNode, this.selected, this.hover); 31420 } 31421 if (this.options.arrows.middle.enabled === true) { 31422 this.edgeType.drawArrowHead(ctx, 'middle', viaNode, this.selected, this.hover); 31423 } 31424 if (this.options.arrows.to.enabled === true) { 31425 this.edgeType.drawArrowHead(ctx, 'to', viaNode, this.selected, this.hover); 31426 } 31427 } 31428 }, { 31429 key: 'drawLabel', 31430 value: function drawLabel(ctx, viaNode) { 31431 if (this.options.label !== undefined) { 31432 // set style 31433 var node1 = this.from; 31434 var node2 = this.to; 31435 var selected = this.from.selected || this.to.selected || this.selected; 31436 if (node1.id != node2.id) { 31437 this.labelModule.pointToSelf = false; 31438 var point = this.edgeType.getPoint(0.5, viaNode); 31439 ctx.save(); 31440 31441 // if the label has to be rotated: 31442 if (this.options.font.align !== 'horizontal') { 31443 this.labelModule.calculateLabelSize(ctx, selected, point.x, point.y); 31444 ctx.translate(point.x, this.labelModule.size.yLine); 31445 this._rotateForLabelAlignment(ctx); 31446 } 31447 31448 // draw the label 31449 this.labelModule.draw(ctx, point.x, point.y, selected); 31450 ctx.restore(); 31451 } else { 31452 // Ignore the orientations. 31453 this.labelModule.pointToSelf = true; 31454 var x, y; 31455 var radius = this.options.selfReferenceSize; 31456 if (node1.shape.width > node1.shape.height) { 31457 x = node1.x + node1.shape.width * 0.5; 31458 y = node1.y - radius; 31459 } else { 31460 x = node1.x + radius; 31461 y = node1.y - node1.shape.height * 0.5; 31462 } 31463 point = this._pointOnCircle(x, y, radius, 0.125); 31464 this.labelModule.draw(ctx, point.x, point.y, selected); 31465 } 31466 } 31467 } 31468 }, { 31469 key: 'isOverlappingWith', 31470 31471 /** 31472 * Check if this object is overlapping with the provided object 31473 * @param {Object} obj an object with parameters left, top 31474 * @return {boolean} True if location is located on the edge 31475 */ 31476 value: function isOverlappingWith(obj) { 31477 if (this.connected) { 31478 var distMax = 10; 31479 var xFrom = this.from.x; 31480 var yFrom = this.from.y; 31481 var xTo = this.to.x; 31482 var yTo = this.to.y; 31483 var xObj = obj.left; 31484 var yObj = obj.top; 31485 31486 var dist = this.edgeType.getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj); 31487 31488 return dist < distMax; 31489 } else { 31490 return false; 31491 } 31492 } 31493 }, { 31494 key: '_rotateForLabelAlignment', 31495 31496 /** 31497 * Rotates the canvas so the text is most readable 31498 * @param {CanvasRenderingContext2D} ctx 31499 * @private 31500 */ 31501 value: function _rotateForLabelAlignment(ctx) { 31502 var dy = this.from.y - this.to.y; 31503 var dx = this.from.x - this.to.x; 31504 var angleInDegrees = Math.atan2(dy, dx); 31505 31506 // rotate so label it is readable 31507 if (angleInDegrees < -1 && dx < 0 || angleInDegrees > 0 && dx < 0) { 31508 angleInDegrees = angleInDegrees + Math.PI; 31509 } 31510 31511 ctx.rotate(angleInDegrees); 31512 } 31513 }, {
31514 key: '_pointOnCircle', 31515 31516 /** 31517 * Get a point on a circle 31518 * @param {Number} x 31519 * @param {Number} y 31520 * @param {Number} radius 31521 * @param {Number} percentage. Value between 0 (line start) and 1 (line end) 31522 * @return {Object} point 31523 * @private 31524 */ 31525 value: function _pointOnCircle(x, y, radius, percentage) { 31526 var angle = percentage * 2 * Math.PI; 31527 return { 31528 x: x + radius * Math.cos(angle), 31529 y: y - radius * Math.sin(angle) 31530 }; 31531 } 31532 }, { 31533 key: 'select', 31534 value: function select() { 31535 this.selected = true; 31536 } 31537 }, { 31538 key: 'unselect', 31539 value: function unselect() { 31540 this.selected = false; 31541 } 31542 }, { 31543 key: 'cleanup', 31544 31545 /** 31546 * cleans all required things on delete 31547 * @returns {*} 31548 */ 31549 value: function cleanup() { 31550 return this.edgeType.cleanup(); 31551 } 31552 }], [{ 31553 key: 'parseOptions', 31554 value: function parseOptions(parentOptions, newOptions) { 31555 var allowDeletion = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 31556 var globalOptions = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3]; 31557 31558 var fields = ['id', 'from', 'hidden', 'hoverWidth', 'label', 'labelHighlightBold', 'length', 'line', 'opacity', 'physics', 'scaling', 'selectionWidth', 'selfReferenceSize', 'to', 'title', 'value', 'width']; 31559 31560 // only deep extend the items in the field array. These do not have shorthand. 31561 util.selectiveDeepExtend(fields, parentOptions, newOptions, allowDeletion); 31562 31563 util.mergeOptions(parentOptions, newOptions, 'smooth', allowDeletion, globalOptions); 31564 util.mergeOptions(parentOptions, newOptions, 'shadow', allowDeletion, globalOptions); 31565 31566 if (newOptions.dashes !== undefined && newOptions.dashes !== null) { 31567 parentOptions.dashes = newOptions.dashes; 31568 } else if (allowDeletion === true && newOptions.dashes === null) { 31569 parentOptions.dashes = Object.create(globalOptions.dashes); // this sets the pointer of the option back to the global option. 31570 } 31571 31572 // set the scaling newOptions 31573 if (newOptions.scaling !== undefined && newOptions.scaling !== null) { 31574 if (newOptions.scaling.min !== undefined) { 31575 parentOptions.scaling.min = newOptions.scaling.min; 31576 } 31577 if (newOptions.scaling.max !== undefined) { 31578 parentOptions.scaling.max = newOptions.scaling.max; 31579 } 31580 util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label', allowDeletion, globalOptions.scaling); 31581 } else if (allowDeletion === true && newOptions.scaling === null) { 31582 parentOptions.scaling = Object.create(globalOptions.scaling); // this sets the pointer of the option back to the global option. 31583 } 31584 31585 // hanlde multiple input cases for arrows 31586 if (newOptions.arrows !== undefined && newOptions.arrows !== null) { 31587 if (typeof newOptions.arrows === 'string') { 31588 var arrows = newOptions.arrows.toLowerCase(); 31589 if (arrows.indexOf('to') != -1) { 31590 parentOptions.arrows.to.enabled = true; 31591 } 31592 if (arrows.indexOf('middle') != -1) { 31593 parentOptions.arrows.middle.enabled = true; 31594 } 31595 if (arrows.indexOf('from') != -1) { 31596 parentOptions.arrows.from.enabled = true; 31597 } 31598 } else if (typeof newOptions.arrows === 'object') { 31599 util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'to', allowDeletion, globalOptions.arrows); 31600 util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'middle', allowDeletion, globalOptions.arrows); 31601 util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'from', allowDeletion, globalOptions.arrows); 31602 } else { 31603 throw new Error('The arrow newOptions can only be an object or a string. Refer to the documentation. You used:' + JSON.stringify(newOptions.arrows)); 31604 } 31605 } else if (allowDeletion === true && newOptions.arrows === null) { 31606 parentOptions.arrows = Object.create(globalOptions.arrows); // this sets the pointer of the option back to the global option. 31607 } 31608 31609 // hanlde multiple input cases for color 31610 if (newOptions.color !== undefined && newOptions.color !== null) { 31611 if (util.isString(newOptions.color)) { 31612 parentOptions.color.color = newOptions.color; 31613 parentOptions.color.highlight = newOptions.color; 31614 parentOptions.color.hover = newOptions.color; 31615 parentOptions.color.inherit = false;
31616 } else { 31617 var colorsDefined = false; 31618 if (newOptions.color.color !== undefined) { 31619 parentOptions.color.color = newOptions.color.color;colorsDefined = true; 31620 } 31621 if (newOptions.color.highlight !== undefined) { 31622 parentOptions.color.highlight = newOptions.color.highlight;colorsDefined = true; 31623 } 31624 if (newOptions.color.hover !== undefined) { 31625 parentOptions.color.hover = newOptions.color.hover;colorsDefined = true; 31626 } 31627 if (newOptions.color.inherit !== undefined) { 31628 parentOptions.color.inherit = newOptions.color.inherit; 31629 } 31630 if (newOptions.color.opacity !== undefined) { 31631 parentOptions.color.opacity = Math.min(1, Math.max(0, newOptions.color.opacity)); 31632 } 31633 31634 if (newOptions.color.inherit === undefined && colorsDefined === true) { 31635 parentOptions.color.inherit = false; 31636 } 31637 } 31638 } else if (allowDeletion === true && newOptions.color === null) { 31639 parentOptions.color = Object.create(globalOptions.color); // this sets the pointer of the option back to the global option. 31640 } 31641 31642 // handle the font settings 31643 if (newOptions.font !== undefined && newOptions.font !== null) { 31644 _sharedLabel2['default'].parseOptions(parentOptions.font, newOptions); 31645 } else if (allowDeletion === true && newOptions.font === null) { 31646 parentOptions.font = Object.create(globalOptions.font); // this sets the pointer of the option back to the global option. 31647 } 31648 } 31649 }]); 31650 31651 return Edge; 31652 })(); 31653 31654 exports['default'] = Edge; 31655 module.exports = exports['default']; 31656 31657/***/ }, 31658/* 83 */ 31659/***/ function(module, exports, __webpack_require__) { 31660 31661 'use strict'; 31662 31663 Object.defineProperty(exports, '__esModule', { 31664 value: true 31665 }); 31666 31667 var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })(); 31668 31669 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 31670 31671 var _get = function get(_x3, _x4, _x5) { var _again = true; _function: while (_again) { var object = _x3, property = _x4, receiver = _x5; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x3 = parent; _x4 = property; _x5 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 31672 31673 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 31674 31675 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 31676 31677 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
31677onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 31678 31679 var _utilCubicBezierEdgeBase = __webpack_require__(84); 31680 31681 var _utilCubicBezierEdgeBase2 = _interopRequireDefault(_utilCubicBezierEdgeBase); 31682 31683 var CubicBezierEdge = (function (_CubicBezierEdgeBase) { 31684 _inherits(CubicBezierEdge, _CubicBezierEdgeBase); 31685 31686 function CubicBezierEdge(options, body, labelModule) { 31687 _classCallCheck(this, CubicBezierEdge); 31688 31689 _get(Object.getPrototypeOf(CubicBezierEdge.prototype), 'constructor', this).call(this, options, body, labelModule); 31690 } 31691 31692 _createClass(CubicBezierEdge, [{ 31693 key: '_line', 31694 31695 /** 31696 * Draw a line between two nodes 31697 * @param {CanvasRenderingContext2D} ctx 31698 * @private 31699 */ 31700 value: function _line(ctx) { 31701 // get the coordinates of the support points. 31702 31703 var _getViaCoordinates2 = this._getViaCoordinates(); 31704 31705 var _getViaCoordinates22 = _slicedToArray(_getViaCoordinates2, 2); 31706 31707 var via1 = _getViaCoordinates22[0]; 31708 var via2 = _getViaCoordinates22[1]; 31709 31710 var returnValue = [via1, via2]; 31711 31712 // start drawing the line. 31713 ctx.beginPath(); 31714 ctx.moveTo(this.from.x, this.from.y); 31715 31716 // fallback to normal straight edges 31717 if (via1.x === undefined) { 31718 ctx.lineTo(this.to.x, this.to.y); 31719 returnValue = undefined; 31720 } else { 31721 ctx.bezierCurveTo(via1.x, via1.y, via2.x, via2.y, this.to.x, this.to.y); 31722 } 31723 // draw shadow if enabled 31724 this.enableShadow(ctx); 31725 ctx.stroke(); 31726 this.disableShadow(ctx); 31727 return returnValue; 31728 } 31729 }, { 31730 key: '_getViaCoordinates', 31731 value: function _getViaCoordinates() { 31732 var dx = this.from.x - this.to.x; 31733 var dy = this.from.y - this.to.y; 31734 31735 var x1 = undefined, 31736 y1 = undefined, 31737 x2 = undefined, 31738 y2 = undefined; 31739 var roundness = this.options.smooth.roundness;; 31740 31741 // horizontal if x > y or if direction is forced or if direction is horizontal 31742 if ((Math.abs(dx) > Math.abs(dy) || this.options.smooth.forceDirection === true || this.options.smooth.forceDirection === 'horizontal') && this.options.smooth.forceDirection !== 'vertical') { 31743 y1 = this.from.y; 31744 y2 = this.to.y; 31745 x1 = this.from.x - roundness * dx; 31746 x2 = this.to.x + roundness * dx; 31747 } else { 31748 y1 = this.from.y - roundness * dy; 31749 y2 = this.to.y + roundness * dy; 31750 x1 = this.from.x; 31751 x2 = this.to.x; 31752 } 31753 31754 return [{ x: x1, y: y1 }, { x: x2, y: y2 }]; 31755 } 31756 }, { 31757 key: '_findBorderPosition', 31758 value: function _findBorderPosition(nearNode, ctx) { 31759 return this._findBorderPositionBezier(nearNode, ctx); 31760 } 31761 }, { 31762 key: '_getDistanceToEdge', 31763 value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { 31764 var _ref = arguments.length <= 6 || arguments[6] === undefined ? this._getViaCoordinates() : arguments[6]; 31765 31766 var _ref2 = _slicedToArray(_ref, 2); 31767 31768 var via1 = _ref2[0]; 31769 var via2 = _ref2[1]; 31770 // x3,y3 is the point 31771 return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via1, via2); 31772 } 31773 }, { 31774 key: 'getPoint', 31775 31776 /** 31777 * Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way 31778 * @param percentage 31779 * @param via 31780 * @returns {{x: number, y: number}} 31781 * @private 31782 */ 31783 value: function getPoint(percentage) { 31784 var _ref3 = arguments.length <= 1 || arguments[1] === undefined ? this._getViaCoordinates() : arguments[1]; 31785 31786 var _ref32 = _slicedToArray(_ref3, 2); 31787 31788 var via1 = _ref32[0]; 31789 var via2 = _ref32[1]; 31790 31791 var t = percentage; 31792 var vec = []; 31793 vec[0] = Math.pow(1 - t, 3); 31794 vec[1] = 3 * t * Math.pow(1 - t, 2); 31795 vec[2] = 3 * Math.pow(t, 2) * (1 - t); 31796 vec[3] = Math.pow(t, 3); 31797 var x = vec[0] * this.from.x + vec[1] * via1.x + vec[2] * via2.x + vec[3] * this.to.x; 31798 var y = vec[0] * this.from.y + vec[1] * via1.y + vec[2] * via2.y + vec[3] * this.to.y; 31799 31800 return { x: x, y: y }; 31801 } 31802 }]); 31803 31804 return CubicBezierEdge; 31805 })(_utilCubicBezierEdgeBase2['default']); 31806 31807 exports['default'] = CubicBezierEdge; 31808 module.exports = exports['default']; 31809 31810/***/ }, 31811/* 84 */ 31812/***/ function(module, exports, __webpack_require__) { 31813 31814 'use strict'; 31815 31816 Object.defineProperty(exports, '__esModule', { 31817 value: true 31818 }); 31819 31820 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 31821 31822 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 31823 31824 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 31825 31826 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 31827 31828 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
31828onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 31829 31830 var _BezierEdgeBase2 = __webpack_require__(85); 31831 31832 var _BezierEdgeBase3 = _interopRequireDefault(_BezierEdgeBase2); 31833 31834 var CubicBezierEdgeBase = (function (_BezierEdgeBase) { 31835 _inherits(CubicBezierEdgeBase, _BezierEdgeBase); 31836 31837 function CubicBezierEdgeBase(options, body, labelModule) { 31838 _classCallCheck(this, CubicBezierEdgeBase); 31839 31840 _get(Object.getPrototypeOf(CubicBezierEdgeBase.prototype), 'constructor', this).call(this, options, body, labelModule); 31841 } 31842 31843 _createClass(CubicBezierEdgeBase, [{ 31844 key: '_getDistanceToBezierEdge', 31845 31846 /** 31847 * Calculate the distance between a point (x3,y3) and a line segment from 31848 * (x1,y1) to (x2,y2). 31849 * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment 31850 * https://en.wikipedia.org/wiki/B%C3%A9zier_curve 31851 * @param {number} x1 from x 31852 * @param {number} y1 from y 31853 * @param {number} x2 to x 31854 * @param {number} y2 to y 31855 * @param {number} x3 point to check x 31856 * @param {number} y3 point to check y 31857 * @private 31858 */ 31859 value: function _getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via1, via2) { 31860 // x3,y3 is the point 31861 var minDistance = 1e9; 31862 var distance = undefined; 31863 var i = undefined, 31864 t = undefined, 31865 x = undefined, 31866 y = undefined; 31867 var lastX = x1; 31868 var lastY = y1; 31869 var vec = [0, 0, 0, 0]; 31870 for (i = 1; i < 10; i++) { 31871 t = 0.1 * i; 31872 vec[0] = Math.pow(1 - t, 3); 31873 vec[1] = 3 * t * Math.pow(1 - t, 2); 31874 vec[2] = 3 * Math.pow(t, 2) * (1 - t); 31875 vec[3] = Math.pow(t, 3); 31876 x = vec[0] * x1 + vec[1] * via1.x + vec[2] * via2.x + vec[3] * x2; 31877 y = vec[0] * y1 + vec[1] * via1.y + vec[2] * via2.y + vec[3] * y2; 31878 if (i > 0) { 31879 distance = this._getDistanceToLine(lastX, lastY, x, y, x3, y3); 31880 minDistance = distance < minDistance ? distance : minDistance; 31881 } 31882 lastX = x; 31883 lastY = y; 31884 } 31885 31886 return minDistance; 31887 } 31888 }]); 31889 31890 return CubicBezierEdgeBase; 31891 })(_BezierEdgeBase3['default']); 31892 31893 exports['default'] = CubicBezierEdgeBase; 31894 module.exports = exports['default']; 31895 31896/***/ }, 31897/* 85 */ 31898/***/ function(module, exports, __webpack_require__) { 31899 31900 'use strict'; 31901 31902 Object.defineProperty(exports, '__esModule', { 31903 value: true 31904 }); 31905 31906 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 31907 31908 var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 31909 31910 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 31911 31912 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 31913 31914 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
31914onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 31915 31916 var _EdgeBase2 = __webpack_require__(86); 31917 31918 var _EdgeBase3 = _interopRequireDefault(_EdgeBase2); 31919 31920 var BezierEdgeBase = (function (_EdgeBase) { 31921 _inherits(BezierEdgeBase, _EdgeBase); 31922 31923 function BezierEdgeBase(options, body, labelModule) { 31924 _classCallCheck(this, BezierEdgeBase); 31925 31926 _get(Object.getPrototypeOf(BezierEdgeBase.prototype), 'constructor', this).call(this, options, body, labelModule); 31927 } 31928 31929 _createClass(BezierEdgeBase, [{ 31930 key: '_findBorderPositionBezier', 31931 31932 /** 31933 * This function uses binary search to look for the point where the bezier curve crosses the border of the node. 31934 * 31935 * @param nearNode 31936 * @param ctx 31937 * @param viaNode 31938 * @param nearNode 31939 * @param ctx 31940 * @param viaNode 31941 * @param nearNode 31942 * @param ctx 31943 * @param viaNode 31944 */ 31945 value: function _findBorderPositionBezier(nearNode, ctx) { 31946 var viaNode = arguments.length <= 2 || arguments[2] === undefined ? this._getViaCoordinates() : arguments[2]; 31947 31948 var maxIterations = 10; 31949 var iteration = 0; 31950 var low = 0; 31951 var high = 1; 31952 var pos, angle, distanceToBorder, distanceToPoint, difference; 31953 var threshold = 0.2; 31954 var node = this.to; 31955 var from = false; 31956 if (nearNode.id === this.from.id) { 31957 node = this.from; 31958 from = true; 31959 } 31960 31961 while (low <= high && iteration < maxIterations) { 31962 var middle = (low + high) * 0.5; 31963 31964 pos = this.getPoint(middle, viaNode); 31965 angle = Math.atan2(node.y - pos.y, node.x - pos.x); 31966 distanceToBorder = node.distanceToBorder(ctx, angle); 31967 distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2)); 31968 difference = distanceToBorder - distanceToPoint; 31969 if (Math.abs(difference) < threshold) { 31970 break; // found 31971 } else if (difference < 0) { 31972 // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node. 31973 if (from === false) { 31974 low = middle; 31975 } else { 31976 high = middle; 31977 } 31978 } else { 31979 if (from === false) { 31980 high = middle; 31981 } else { 31982 low = middle; 31983 } 31984 } 31985 31986 iteration++; 31987 } 31988 pos.t = middle; 31989 31990 return pos; 31991 } 31992 }, { 31993 key: '_getDistanceToBezierEdge', 31994 31995 /** 31996 * Calculate the distance between a point (x3,y3) and a line segment from 31997 * (x1,y1) to (x2,y2). 31998 * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment 31999 * @param {number} x1 from x 32000 * @param {number} y1 from y 32001 * @param {number} x2 to x 32002 * @param {number} y2 to y 32003 * @param {number} x3 point to check x 32004 * @param {number} y3 point to check y 32005 * @private 32006 */ 32007 value: function _getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via) { 32008 // x3,y3 is the point 32009 var minDistance = 1e9; 32010 var distance = undefined; 32011 var i = undefined, 32012 t = undefined, 32013 x = undefined, 32014 y = undefined; 32015 var lastX = x1; 32016 var lastY = y1; 32017 for (i = 1; i < 10; i++) { 32018 t = 0.1 * i; 32019 x = Math.pow(1 - t, 2) * x1 + 2 * t * (1 - t) * via.x + Math.pow(t, 2) * x2; 32020 y = Math.pow(1 - t, 2) * y1 + 2 * t * (1 - t) * via.y + Math.pow(t, 2) * y2; 32021 if (i > 0) { 32022 distance = this._getDistanceToLine(lastX, lastY, x, y, x3, y3); 32023 minDistance = distance < minDistance ? distance : minDistance; 32024 } 32025 lastX = x; 32026 lastY = y; 32027 } 32028 32029 return minDistance; 32030 } 32031 }]); 32032 32033 return BezierEdgeBase; 32034 })(_EdgeBase3['default']); 32035 32036 exports['default'] = BezierEdgeBase; 32037 module.exports = exports['default']; 32038 32039/***/ }, 32040/* 86 */ 32041/***/ function(module, exports, __webpack_require__) { 32042 32043 'use strict'; 32044 32045 Object.defineProperty(exports, '__esModule', { 32046 value: true 32047 }); 32048 32049 var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); }
32049 else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })(); 32050 32051 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 32052 32053 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 32054 32055 var util = __webpack_require__(1); 32056 32057 var EdgeBase = (function () { 32058 function EdgeBase(options, body, labelModule) { 32059 _classCallCheck(this, EdgeBase); 32060 32061 this.body = body; 32062 this.labelModule = labelModule; 32063 this.setOptions(options); 32064 this.colorDirty = true; 32065 this.color = {}; 32066 this.selectionWidth = 2; 32067 this.hoverWidth = 1.5; 32068 } 32069 32070 _createClass(EdgeBase, [{ 32071 key: 'connect', 32072 value: function connect() { 32073 this.from = this.body.nodes[this.options.from]; 32074 this.to = this.body.nodes[this.options.to]; 32075 } 32076 }, { 32077 key: 'cleanup', 32078 value: function cleanup() { 32079 return false; 32080 } 32081 }, { 32082 key: 'setOptions', 32083 value: function setOptions(options) { 32084 this.options = options; 32085 this.from = this.body.nodes[this.options.from]; 32086 this.to = this.body.nodes[this.options.to]; 32087 this.id = this.options.id; 32088 } 32089 }, { 32090 key: 'drawLine', 32091 32092 /** 32093 * Redraw a edge as a line 32094 * Draw this edge in the given canvas 32095 * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); 32096 * @param {CanvasRenderingContext2D} ctx 32097 * @private 32098 */ 32099 value: function drawLine(ctx, selected, hover) { 32100 // set style 32101 ctx.strokeStyle = this.getColor(ctx, selected, hover); 32102 ctx.lineWidth = this.getLineWidth(selected, hover); 32103 var via = undefined; 32104 if (this.options.dashes !== false) { 32105 via = this._drawDashedLine(ctx); 32106 } else { 32107 via = this._drawLine(ctx); 32108 } 32109 return via; 32110 } 32111 }, { 32112 key: '_drawLine', 32113 value: function _drawLine(ctx) { 32114 var via = undefined; 32115 if (this.from != this.to) { 32116 // draw line 32117 via = this._line(ctx); 32118 } else { 32119 var _getCircleData2 = this._getCircleData(ctx); 32120 32121 var _getCircleData22 = _slicedToArray(_getCircleData2, 3); 32122 32123 var x = _getCircleData22[0]; 32124 var y = _getCircleData22[1]; 32125 var radius = _getCircleData22[2]; 32126 32127 this._circle(ctx, x, y, radius); 32128 } 32129 return via; 32130 } 32131 }, { 32132 key: '_drawDashedLine', 32133 value: function _drawDashedLine(ctx) { 32134 var via = undefined; 32135 ctx.lineCap = 'round'; 32136 var pattern = [5, 5]; 32137 if (Array.isArray(this.options.dashes) === true) { 32138 pattern = this.options.dashes; 32139 } 32140 32141 // only firefox and chrome support this method, else we use the legacy one. 32142 if (ctx.setLineDash !== undefined) { 32143 ctx.save(); 32144 32145 // set dash settings for chrome or firefox 32146 ctx.setLineDash(pattern); 32147 ctx.lineDashOffset = 0; 32148 32149 // draw the line 32150 if (this.from != this.to) { 32151 // draw line 32152 via = this._line(ctx); 32153 } else { 32154 var _getCircleData3 = this._getCircleData(ctx); 32155 32156 var _getCircleData32 = _slicedToArray(_getCircleData3, 3); 32157 32158 var x = _getCircleData32[0]; 32159 var y = _getCircleData32[1]; 32160 var radius = _getCircleData32[2]; 32161 32162 this._circle(ctx, x, y, radius); 32163 } 32164 32165 // restore the dash settings. 32166 ctx.setLineDash([0]); 32167 ctx.lineDashOffset = 0;
32168 ctx.restore(); 32169 } else { 32170 // unsupporting smooth lines 32171 if (this.from != this.to) { 32172 // draw line 32173 ctx.dashedLine(this.from.x, this.from.y, this.to.x, this.to.y, pattern); 32174 } else { 32175 var _getCircleData4 = this._getCircleData(ctx); 32176 32177 var _getCircleData42 = _slicedToArray(_getCircleData4, 3); 32178 32179 var x = _getCircleData42[0]; 32180 var y = _getCircleData42[1]; 32181 var radius = _getCircleData42[2]; 32182 32183 this._circle(ctx, x, y, radius); 32184 } 32185 // draw shadow if enabled 32186 this.enableShadow(ctx); 32187 32188 ctx.stroke(); 32189 32190 // disable shadows for other elements. 32191 this.disableShadow(ctx); 32192 } 32193 return via; 32194 } 32195 }, { 32196 key: 'findBorderPosition', 32197 value: function findBorderPosition(nearNode, ctx, options) { 32198 if (this.from != this.to) { 32199 return this._findBorderPosition(nearNode, ctx, options); 32200 } else { 32201 return this._findBorderPositionCircle(nearNode, ctx, options); 32202 } 32203 } 32204 }, { 32205 key: 'findBorderPositions', 32206 value: function findBorderPositions(ctx) { 32207 var from = {}; 32208 var to = {}; 32209 if (this.from != this.to) { 32210 from = this._findBorderPosition(this.from, ctx); 32211 to = this._findBorderPosition(this.to, ctx); 32212 } else { 32213 var _getCircleData5 = this._getCircleData(ctx); 32214 32215 var _getCircleData52 = _slicedToArray(_getCircleData5, 3); 32216 32217 var x = _getCircleData52[0]; 32218 var y = _getCircleData52[1]; 32219 var radius = _getCircleData52[2]; 32220 32221 from = this._findBorderPositionCircle(this.from, ctx, { x: x, y: y, low: 0.25, high: 0.6, direction: -1 }); 32222 to = this._findBorderPositionCircle(this.from, ctx, { x: x, y: y, low: 0.6, high: 0.8, direction: 1 }); 32223 } 32224 return { from: from, to: to }; 32225 } 32226 }, { 32227 key: '_getCircleData', 32228 value: function _getCircleData(ctx) { 32229 var x = undefined, 32230 y = undefined; 32231 var node = this.from; 32232 var radius = this.options.selfReferenceSize; 32233 32234 if (ctx !== undefined) { 32235 if (node.shape.width === undefined) { 32236 node.shape.resize(ctx); 32237 } 32238 } 32239 32240 // get circle coordinates 32241 if (node.shape.width > node.shape.height) { 32242 x = node.x + node.shape.width * 0.5; 32243 y = node.y - radius; 32244 } else { 32245 x = node.x + radius; 32246 y = node.y - node.shape.height * 0.5; 32247 } 32248 return [x, y, radius]; 32249 } 32250 }, { 32251 key: '_pointOnCircle', 32252 32253 /** 32254 * Get a point on a circle 32255 * @param {Number} x 32256 * @param {Number} y 32257 * @param {Number} radius 32258 * @param {Number} percentage. Value between 0 (line start) and 1 (line end) 32259 * @return {Object} point 32260 * @private 32261 */ 32262 value: function _pointOnCircle(x, y, radius, percentage) { 32263 var angle = percentage * 2 * Math.PI; 32264 return { 32265 x: x + radius * Math.cos(angle), 32266 y: y - radius * Math.sin(angle) 32267 }; 32268 } 32269 }, { 32270 key: '_findBorderPositionCircle', 32271 32272 /** 32273 * This function uses binary search to look for the point where the circle crosses the border of the node. 32274 * @param node 32275 * @param ctx 32276 * @param options 32277 * @returns {*} 32278 * @private 32279 */ 32280 value: function _findBorderPositionCircle(node, ctx, options) { 32281 var x = options.x; 32282 var y = options.y; 32283 var low = options.low; 32284 var high = options.high; 32285 var direction = options.direction; 32286 32287 var maxIterations = 10; 32288 var iteration = 0; 32289 var radius = this.options.selfReferenceSize; 32290 var pos = undefined, 32291 angle = undefined, 32292 distanceToBorder = undefined, 32293 distanceToPoint = undefined, 32294 difference = undefined; 32295 var threshold = 0.05; 32296 var middle = (low + high) * 0.5; 32297 32298 while (low <= high && iteration < maxIterations) { 32299 middle = (low + high) * 0.5; 32300 32301 pos = this._pointOnCircle(x, y, radius, middle); 32302 angle = Math.atan2(node.y - pos.y, node.x - pos.x); 32303 distanceToBorder = node.distanceToBorder(ctx, angle); 32304 distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2)); 32305 difference = distanceToBorder - distanceToPoint; 32306 if (Math.abs(difference) < threshold) { 32307 break; // found 32308 } else if (difference > 0) { 32309 // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node. 32310 if (direction > 0) { 32311 low = middle; 32312 } else { 32313 high = middle; 32314 } 32315 } else { 32316 if (direction > 0) { 32317 high = middle; 32318 } else { 32319 low = middle; 32320 } 32321 } 32322 iteration++; 32323 } 32324 pos.t = middle; 32325 32326 return pos; 32327 } 32328 }, {
32329 key: 'getLineWidth', 32330 32331 /** 32332 * Get the line width of the edge. Depends on width and whether one of the 32333 * connected nodes is selected. 32334 * @return {Number} width 32335 * @private 32336 */ 32337 value: function getLineWidth(selected, hover) { 32338 if (selected === true) { 32339 return Math.max(this.selectionWidth, 0.3 / this.body.view.scale); 32340 } else { 32341 if (hover === true) { 32342 return Math.max(this.hoverWidth, 0.3 / this.body.view.scale); 32343 } else { 32344 return Math.max(this.options.width, 0.3 / this.body.view.scale); 32345 } 32346 } 32347 } 32348 }, { 32349 key: 'getColor', 32350 value: function getColor(ctx, selected, hover) { 32351 var colorOptions = this.options.color; 32352 if (colorOptions.inherit !== false) { 32353 // when this is a loop edge, just use the 'from' method 32354 if (colorOptions.inherit === 'both' && this.from.id !== this.to.id) { 32355 var grd = ctx.createLinearGradient(this.from.x, this.from.y, this.to.x, this.to.y); 32356 var fromColor = undefined, 32357 toColor = undefined; 32358 fromColor = this.from.options.color.highlight.border; 32359 toColor = this.to.options.color.highlight.border; 32360 32361 if (this.from.selected === false && this.to.selected === false) { 32362 fromColor = util.overrideOpacity(this.from.options.color.border, this.options.color.opacity); 32363 toColor = util.overrideOpacity(this.to.options.color.border, this.options.color.opacity); 32364 } else if (this.from.selected === true && this.to.selected === false) { 32365 toColor = this.to.options.color.border; 32366 } else if (this.from.selected === false && this.to.selected === true) { 32367 fromColor = this.from.options.color.border; 32368 } 32369 grd.addColorStop(0, fromColor); 32370 grd.addColorStop(1, toColor); 32371 32372 // -------------------- this returns -------------------- // 32373 return grd; 32374 } 32375 32376 if (this.colorDirty === true) { 32377 if (colorOptions.inherit === 'to') { 32378 this.color.highlight = this.to.options.color.highlight.border; 32379 this.color.hover = this.to.options.color.hover.border; 32380 this.color.color = util.overrideOpacity(this.to.options.color.border, colorOptions.opacity); 32381 } else { 32382 // (this.options.color.inherit.source === "from") { 32383 this.color.highlight = this.from.options.color.highlight.border; 32384 this.color.hover = this.from.options.color.hover.border; 32385 this.color.color = util.overrideOpacity(this.from.options.color.border, colorOptions.opacity); 32386 } 32387 } 32388 } else if (this.colorDirty === true) { 32389 this.color.highlight = colorOptions.highlight; 32390 this.color.hover = colorOptions.hover; 32391 this.color.color = util.overrideOpacity(colorOptions.color, colorOptions.opacity); 32392 } 32393 32394 // if color inherit is on and gradients are used, the function has already returned by now. 32395 this.colorDirty = false; 32396 32397 if (selected === true) { 32398 return this.color.highlight; 32399 } else if (hover === true) { 32400 return this.color.hover; 32401 } else { 32402 return this.color.color; 32403 } 32404 } 32405 }, { 32406 key: '_circle', 32407 32408 /** 32409 * Draw a line from a node to itself, a circle 32410 * @param {CanvasRenderingContext2D} ctx 32411 * @param {Number} x 32412 * @param {Number} y 32413 * @param {Number} radius 32414 * @private 32415 */ 32416 value: function _circle(ctx, x, y, radius) { 32417 // draw shadow if enabled 32418 this.enableShadow(ctx); 32419 32420 // draw a circle 32421 ctx.beginPath(); 32422 ctx.arc(x, y, radius, 0, 2 * Math.PI, false); 32423 ctx.stroke(); 32424 32425 // disable shadows for other elements. 32426 this.disableShadow(ctx); 32427 } 32428 }, { 32429 key: 'getDistanceToEdge', 32430 32431 /** 32432 * Calculate the distance between a point (x3,y3) and a line segment from 32433 * (x1,y1) to (x2,y2). 32434 * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment 32435 * @param {number} x1 32436 * @param {number} y1 32437 * @param {number} x2 32438 * @param {number} y2 32439 * @param {number} x3 32440 * @param {number} y3 32441 * @private 32442 */ 32443 value: function getDistanceToEdge(x1, y1, x2, y2, x3, y3, via) { 32444 // x3,y3 is the point 32445 var returnValue = 0; 32446 if (this.from != this.to) { 32447 returnValue = this._getDistanceToEdge(x1, y1, x2, y2, x3, y3, via); 32448 } else { 32449 var _getCircleData6 = this._getCircleData(); 32450 32451 var _getCircleData62 = _slicedToArray(_getCircleData6, 3); 32452 32453 var x = _getCircleData62[0]; 32454 var y = _getCircleData62[1]; 32455 var radius = _getCircleData62[2]; 32456 32457 var dx = x - x3; 32458 var dy = y - y3; 32459 returnValue = Math.abs(Math.sqrt(dx * dx + dy * dy) - radiu
32459s); 32460 } 32461 32462 if (this.labelModule.size.left < x3 && this.labelModule.size.left + this.labelModule.size.width > x3 && this.labelModule.size.top < y3 && this.labelModule.size.top + this.labelModule.size.height > y3) { 32463 return 0; 32464 } else { 32465 return returnValue; 32466 } 32467 } 32468 }, { 32469 key: '_getDistanceToLine', 32470 value: function _getDistanceToLine(x1, y1, x2, y2, x3, y3) { 32471 var px = x2 - x1; 32472 var py = y2 - y1; 32473 var something = px * px + py * py; 32474 var u = ((x3 - x1) * px + (y3 - y1) * py) / something; 32475 32476 if (u > 1) { 32477 u = 1; 32478 } else if (u < 0) { 32479 u = 0; 32480 } 32481 32482 var x = x1 + u * px; 32483 var y = y1 + u * py; 32484 var dx = x - x3; 32485 var dy = y - y3; 32486 32487 //# Note: If the actual distance does not matter, 32488 //# if you only want to compare what this function 32489 //# returns to other results of this function, you 32490 //# can just return the squared distance instead 32491 //# (i.e. remove the sqrt) to gain a little performance 32492 32493 return Math.sqrt(dx * dx + dy * dy); 32494 } 32495 }, { 32496 key: 'drawArrowHead', 32497 32498 /** 32499 * 32500 * @param ctx 32501 * @param position 32502 * @param viaNode 32503 */ 32504 value: function drawArrowHead(ctx, position, viaNode, selected, hover) { 32505 // set style 32506 ctx.strokeStyle = this.getColor(ctx, selected, hover); 32507 ctx.fillStyle = ctx.strokeStyle; 32508 ctx.lineWidth = this.getLineWidth(selected, hover); 32509 32510 // set lets 32511 var angle = undefined; 32512 var length = undefined; 32513 var arrowPos = undefined; 32514 var node1 = undefined; 32515 var node2 = undefined; 32516 var guideOffset = undefined; 32517 var scaleFactor = undefined; 32518 32519 if (position === 'from') { 32520 node1 = this.from; 32521 node2 = this.to; 32522 guideOffset = 0.1; 32523 scaleFactor = this.options.arrows.from.scaleFactor; 32524 } else if (position === 'to') { 32525 node1 = this.to; 32526 node2 = this.from; 32527 guideOffset = -0.1; 32528 scaleFactor = this.options.arrows.to.scaleFactor; 32529 } else { 32530 node1 = this.to; 32531 node2 = this.from; 32532 scaleFactor = this.options.arrows.middle.scaleFactor; 32533 } 32534 32535 // if not connected to itself 32536 if (node1 != node2) { 32537 if (position !== 'middle') { 32538 // draw arrow head 32539 if (this.options.smooth.enabled === true) { 32540 arrowPos = this.findBorderPosition(node1, ctx, { via: viaNode }); 32541 var guidePos = this.getPoint(Math.max(0.0, Math.min(1.0, arrowPos.t + guideOffset)), viaNode); 32542 angle = Math.atan2(arrowPos.y - guidePos.y, arrowPos.x - guidePos.x); 32543 } else { 32544 angle = Math.atan2(node1.y - node2.y, node1.x - node2.x); 32545 arrowPos = this.findBorderPosition(node1, ctx); 32546 } 32547 } else { 32548 angle = Math.atan2(node1.y - node2.y, node1.x - node2.x); 32549 arrowPos = this.getPoint(0.6, viaNode); // this is 0.6 to account for the size of the arrow. 32550 } 32551 // draw arrow at the end of the line 32552 length = (10 + 5 * this.options.width) * scaleFactor; 32553 ctx.arrow(arrowPos.x, arrowPos.y, angle, length); 32554 32555 // draw shadow if enabled 32556 this.enableShadow(ctx); 32557 ctx.fill(); 32558 32559 // disable shadows for other elements. 32560 this.disableShadow(ctx); 32561 ctx.stroke(); 32562 } else { 32563 // draw circle 32564 var _angle = undefined, 32565 point = undefined; 32566 32567 var _getCircleData7 = this._getCircleData(ctx); 32568 32569 var _getCircleData72 = _slicedToArray(_getCircleData7, 3); 32570 32571 var x = _getCircleData72[0]; 32572 var y = _getCircleData72[1]; 32573 var radius = _getCircleData72[2]; 32574 32575 if (position === 'from') { 32576 point = this.findBorderPosition(this.from, ctx, { x: x, y: y, low: 0.25, high: 0.6, direction: -1 }); 32577 _angle = point.t * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI; 32578 } else if (position === 'to') { 32579 point = this.findBorderPosition(this.from, ctx, { x: x, y: y, low: 0.6, high: 1.0, direction: 1 }); 32580 _angle = point.t * -2 * Math.PI + 1.5 * Math.PI - 1.1 * Math.PI; 32581 } else { 32582 point = this._pointOnCircle(x, y, radius, 0.175); 32583 _angle = 3.9269908169872414; // === 0.175 * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI; 32584 } 32585 32586 // draw the arrowhead 32587 var _length = (10 + 5 * this.options.width) * scaleFactor; 32588 ctx.arrow(point.x, point.y, _angle, _length); 32589 32590 // draw shadow if enabled 32591 this.enableShadow(ctx); 32592 ctx.fill(); 32593 32594 // disable shadows for other elements. 32595 this.disableShadow(ctx); 32596 ctx.stroke(); 32597 } 32598 } 32599 }, {
32600 key: 'enableShadow', 32601 value: function enableShadow(ctx) { 32602 if (this.options.shadow.enabled === true) { 32603 ctx.shadowColor = 'rgba(0,0,0,0.5)'; 32604 ctx.shadowBlur = this.options.shadow.size; 32605 ctx.shadowOffsetX = this.options.shadow.x; 32606 ctx.shadowOffsetY = this.options.shadow.y; 32607 } 32608 } 32609 }, { 32610 key: 'disableShadow', 32611 value: function disableShadow(ctx) { 32612 if (this.options.shadow.enabled === true) { 32613 ctx.shadowColor = 'rgba(0,0,0,0)'; 32614 ctx.shadowBlur = 0; 32615 ctx.shadowOffsetX = 0; 32616 ctx.shadowOffsetY = 0; 32617 } 32618 } 32619 }]); 32620 32621 return EdgeBase; 32622 })(); 32623 32624 exports['default'] = EdgeBase; 32625 module.exports = exports['default']; 32626 32627/***/ }, 32628/* 87 */ 32629/***/ function(module, exports, __webpack_require__) { 32630 32631 "use strict"; 32632 32633 Object.defineProperty(exports, "__esModule", { 32634 value: true 32635 }); 32636 32637 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 32638 32639 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 32640 32641 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 32642 32643 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 32644 32645 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 32646 32647 var _utilBezierEdgeBase = __webpack_require__(85); 32648 32649 var _utilBezierEdgeBase2 = _interopRequireDefault(_utilBezierEdgeBase); 32650 32651 var BezierEdgeDynamic = (function (_BezierEdgeBase) { 32652 _inherits(BezierEdgeDynamic, _BezierEdgeBase); 32653 32654 function BezierEdgeDynamic(options, body, labelModule) { 32655 var _this = this; 32656 32657 _classCallCheck(this, BezierEdgeDynamic); 32658 32659 //this.via = undefined; // Here for completeness but not allowed to defined before super() is invoked. 32660 _get(Object.getPrototypeOf(BezierEdgeDynamic.prototype), "constructor", this).call(this, options, body, labelModule); // --> this calls the setOptions below 32661 this._boundFunction = function () { 32662 _this.positionBezierNode(); 32663 }; 32664 this.body.emitter.on("_repositionBezierNodes", this._boundFunction); 32665 } 32666 32667 _createClass(BezierEdgeDynamic, [{ 32668 key: "setOptions", 32669 value: function setOptions(options) { 32670 this.options = options; 32671 this.id = this.options.id; 32672 this.setupSupportNode(); 32673 32674 // when we change the physics state of the edge, we reposition the support node. 32675 if (this.options.physics !== options.physics) { 32676 this.via.setOptions({ physics: this.options.physics }); 32677 this.positionBezierNode(); 32678 } 32679 this.connect(); 32680 } 32681 }, {
32682 key: "connect", 32683 value: function connect() { 32684 this.from = this.body.nodes[this.options.from]; 32685 this.to = this.body.nodes[this.options.to]; 32686 if (this.from === undefined || this.to === undefined || this.options.physics === false) { 32687 this.via.setOptions({ physics: false }); 32688 } else { 32689 // fix weird behaviour where a selfreferencing node has physics enabled 32690 if (this.from.id === this.to.id) { 32691 this.via.setOptions({ physics: false }); 32692 } else { 32693 this.via.setOptions({ physics: true }); 32694 } 32695 } 32696 } 32697 }, { 32698 key: "cleanup", 32699 32700 /** 32701 * remove the support nodes 32702 * @returns {boolean} 32703 */ 32704 value: function cleanup() { 32705 this.body.emitter.off("_repositionBezierNodes", this._boundFunction); 32706 if (this.via !== undefined) { 32707 delete this.body.nodes[this.via.id]; 32708 this.via = undefined; 32709 return true; 32710 } 32711 return false; 32712 } 32713 }, { 32714 key: "setupSupportNode", 32715 32716 /** 32717 * Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but 32718 * are used for the force calculation. 32719 * 32720 * The changed data is not called, if needed, it is returned by the main edge constructor. 32721 * @private 32722 */ 32723 value: function setupSupportNode() { 32724 if (this.via === undefined) { 32725 var nodeId = "edgeId:" + this.id; 32726 var node = this.body.functions.createNode({ 32727 id: nodeId, 32728 shape: "circle", 32729 physics: true, 32730 hidden: true 32731 }); 32732 this.body.nodes[nodeId] = node; 32733 this.via = node; 32734 this.via.parentEdgeId = this.id; 32735 this.positionBezierNode(); 32736 } 32737 } 32738 }, { 32739 key: "positionBezierNode", 32740 value: function positionBezierNode() { 32741 if (this.via !== undefined && this.from !== undefined && this.to !== undefined) { 32742 this.via.x = 0.5 * (this.from.x + this.to.x); 32743 this.via.y = 0.5 * (this.from.y + this.to.y); 32744 } else if (this.via !== undefined) { 32745 this.via.x = 0; 32746 this.via.y = 0; 32747 } 32748 } 32749 }, { 32750 key: "_line", 32751 32752 /** 32753 * Draw a line between two nodes 32754 * @param {CanvasRenderingContext2D} ctx 32755 * @private 32756 */ 32757 value: function _line(ctx) { 32758 // draw a straight line 32759 ctx.beginPath(); 32760 ctx.moveTo(this.from.x, this.from.y); 32761 ctx.quadraticCurveTo(this.via.x, this.via.y, this.to.x, this.to.y); 32762 // draw shadow if enabled 32763 this.enableShadow(ctx); 32764 ctx.stroke(); 32765 this.disableShadow(ctx); 32766 return this.via; 32767 } 32768 }, { 32769 key: "getPoint", 32770 32771 /** 32772 * Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way 32773 * @param percentage 32774 * @param via 32775 * @returns {{x: number, y: number}} 32776 * @private 32777 */ 32778 value: function getPoint(percentage) { 32779 var t = percentage; 32780 var x = Math.pow(1 - t, 2) * this.from.x + 2 * t * (1 - t) * this.via.x + Math.pow(t, 2) * this.to.x; 32781 var y = Math.pow(1 - t, 2) * this.from.y + 2 * t * (1 - t) * this.via.y + Math.pow(t, 2) * this.to.y; 32782 32783 return { x: x, y: y }; 32784 } 32785 }, { 32786 key: "_findBorderPosition", 32787 value: function _findBorderPosition(nearNode, ctx) { 32788 return this._findBorderPositionBezier(nearNode, ctx, this.via); 32789 } 32790 }, { 32791 key: "_getDistanceToEdge", 32792 value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { 32793 // x3,y3 is the point 32794 return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, this.via); 32795 } 32796 }]); 32797 32798 return BezierEdgeDynamic; 32799 })(_utilBezierEdgeBase2["default"]); 32800 32801 exports["default"] = BezierEdgeDynamic; 32802 module.exports = exports["default"]; 32803 32804/***/ }, 32805/* 88 */ 32806/***/ function(module, exports, __webpack_require__) { 32807 32808 'use strict'; 32809 32810 Object.defineProperty(exports, '__esModule', { 32811 value: true 32812 }); 32813 32814 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 32815 32816 var _get = function get(_x4, _x5, _x6) { var _again = true; _function: while (_again) { var object = _x4, property = _x5, receiver = _x6; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x4 = parent; _x5 = property; _x6 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 32817 32818 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 32819 32820 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 32821 32822 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
32822onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 32823 32824 var _utilBezierEdgeBase = __webpack_require__(85); 32825 32826 var _utilBezierEdgeBase2 = _interopRequireDefault(_utilBezierEdgeBase); 32827 32828 var BezierEdgeStatic = (function (_BezierEdgeBase) { 32829 _inherits(BezierEdgeStatic, _BezierEdgeBase); 32830 32831 function BezierEdgeStatic(options, body, labelModule) { 32832 _classCallCheck(this, BezierEdgeStatic); 32833 32834 _get(Object.getPrototypeOf(BezierEdgeStatic.prototype), 'constructor', this).call(this, options, body, labelModule); 32835 } 32836 32837 _createClass(BezierEdgeStatic, [{ 32838 key: '_line', 32839 32840 /** 32841 * Draw a line between two nodes 32842 * @param {CanvasRenderingContext2D} ctx 32843 * @private 32844 */ 32845 value: function _line(ctx) { 32846 // draw a straight line 32847 ctx.beginPath(); 32848 ctx.moveTo(this.from.x, this.from.y); 32849 var via = this._getViaCoordinates(); 32850 var returnValue = via; 32851 32852 // fallback to normal straight edges 32853 if (via.x === undefined) { 32854 ctx.lineTo(this.to.x, this.to.y); 32855 returnValue = undefined; 32856 } else { 32857 ctx.quadraticCurveTo(via.x, via.y, this.to.x, this.to.y); 32858 } 32859 // draw shadow if enabled 32860 this.enableShadow(ctx); 32861 ctx.stroke(); 32862 this.disableShadow(ctx); 32863 return returnValue; 32864 } 32865 }, { 32866 key: '_getViaCoordinates', 32867 value: function _getViaCoordinates() { 32868 var xVia = undefined; 32869 var yVia = undefined; 32870 var factor = this.options.smooth.roundness; 32871 var type = this.options.smooth.type; 32872 var dx = Math.abs(this.from.x - this.to.x); 32873 var dy = Math.abs(this.from.y - this.to.y); 32874 if (type === 'discrete' || type === 'diagonalCross') { 32875 if (Math.abs(this.from.x - this.to.x) <= Math.abs(this.from.y - this.to.y)) { 32876 if (this.from.y >= this.to.y) { 32877 if (this.from.x <= this.to.x) { 32878 xVia = this.from.x + factor * dy; 32879 yVia = this.from.y - factor * dy; 32880 } else if (this.from.x > this.to.x) { 32881 xVia = this.from.x - factor * dy; 32882 yVia = this.from.y - factor * dy; 32883 } 32884 } else if (this.from.y < this.to.y) { 32885 if (this.from.x <= this.to.x) { 32886 xVia = this.from.x + factor * dy; 32887 yVia = this.from.y + factor * dy; 32888 } else if (this.from.x > this.to.x) { 32889 xVia = this.from.x - factor * dy; 32890 yVia = this.from.y + factor * dy; 32891 } 32892 } 32893 if (type === 'discrete') { 32894 xVia = dx < factor * dy ? this.from.x : xVia; 32895 } 32896 } else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { 32897 if (this.from.y >= this.to.y) { 32898 if (this.from.x <= this.to.x) { 32899 xVia = this.from.x + factor * dx; 32900 yVia = this.from.y - factor * dx; 32901 } else if (this.from.x > this.to.x) { 32902 xVia = this.from.x - factor * dx; 32903 yVia = this.from.y - factor * dx; 32904 } 32905 } else if (this.from.y < this.to.y) { 32906 if (this.from.x <= this.to.x) { 32907 xVia = this.from.x + factor * dx; 32908 yVia = this.from.y + factor * dx; 32909 } else if (this.from.x > this.to.x) { 32910 xVia = this.from.x - factor * dx; 32911 yVia = this.from.y + factor * dx; 32912 } 32913 } 32914 if (type === 'discrete') { 32915 yVia = dy < factor * dx ? this.from.y : yVia; 32916 } 32917 } 32918 } else if (type === 'straightCross') { 32919 if (Math.abs(this.from.x - this.to.x) <= Math.abs(this.from.y - this.to.y)) { 32920 // up - down 32921 xVia = this.from.x; 32922 if (this.from.y < this.to.y) { 32923 yVia = this.to.y - (1 - factor) * dy; 32924 } else { 32925 yVia = this.to.y + (1 - factor) * dy; 32926 } 32927 } else if (Math.abs(this.from.x - this.to.x) >
32927 Math.abs(this.from.y - this.to.y)) { 32928 // left - right 32929 if (this.from.x < this.to.x) { 32930 xVia = this.to.x - (1 - factor) * dx; 32931 } else { 32932 xVia = this.to.x + (1 - factor) * dx; 32933 } 32934 yVia = this.from.y; 32935 } 32936 } else if (type === 'horizontal') { 32937 if (this.from.x < this.to.x) { 32938 xVia = this.to.x - (1 - factor) * dx; 32939 } else { 32940 xVia = this.to.x + (1 - factor) * dx; 32941 } 32942 yVia = this.from.y; 32943 } else if (type === 'vertical') { 32944 xVia = this.from.x; 32945 if (this.from.y < this.to.y) { 32946 yVia = this.to.y - (1 - factor) * dy; 32947 } else { 32948 yVia = this.to.y + (1 - factor) * dy; 32949 } 32950 } else if (type === 'curvedCW') { 32951 dx = this.to.x - this.from.x; 32952 dy = this.from.y - this.to.y; 32953 var radius = Math.sqrt(dx * dx + dy * dy); 32954 var pi = Math.PI; 32955 32956 var originalAngle = Math.atan2(dy, dx); 32957 var myAngle = (originalAngle + (factor * 0.5 + 0.5) * pi) % (2 * pi); 32958 32959 xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle); 32960 yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle); 32961 } else if (type === 'curvedCCW') { 32962 dx = this.to.x - this.from.x; 32963 dy = this.from.y - this.to.y; 32964 var radius = Math.sqrt(dx * dx + dy * dy); 32965 var pi = Math.PI; 32966 32967 var originalAngle = Math.atan2(dy, dx); 32968 var myAngle = (originalAngle + (-factor * 0.5 + 0.5) * pi) % (2 * pi); 32969 32970 xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle); 32971 yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle); 32972 } else { 32973 // continuous 32974 if (Math.abs(this.from.x - this.to.x) <= Math.abs(this.from.y - this.to.y)) { 32975 if (this.from.y >= this.to.y) { 32976 if (this.from.x <= this.to.x) { 32977 xVia = this.from.x + factor * dy; 32978 yVia = this.from.y - factor * dy; 32979 xVia = this.to.x < xVia ? this.to.x : xVia; 32980 } else if (this.from.x > this.to.x) { 32981 xVia = this.from.x - factor * dy; 32982 yVia = this.from.y - factor * dy; 32983 xVia = this.to.x > xVia ? this.to.x : xVia; 32984 } 32985 } else if (this.from.y < this.to.y) { 32986 if (this.from.x <= this.to.x) { 32987 xVia = this.from.x + factor * dy; 32988 yVia = this.from.y + factor * dy; 32989 xVia = this.to.x < xVia ? this.to.x : xVia; 32990 } else if (this.from.x > this.to.x) { 32991 xVia = this.from.x - factor * dy; 32992 yVia = this.from.y + factor * dy; 32993 xVia = this.to.x > xVia ? this.to.x : xVia; 32994 } 32995 } 32996 } else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { 32997 if (this.from.y >= this.to.y) { 32998 if (this.from.x <= this.to.x) { 32999 xVia = this.from.x + factor * dx; 33000 yVia = this.from.y - factor * dx; 33001 yVia = this.to.y > yVia ? this.to.y : yVia; 33002 } else if (this.from.x > this.to.x) { 33003 xVia = this.from.x - factor * dx; 33004 yVia = this.from.y - factor * dx; 33005 yVia = this.to.y > yVia ? this.to.y : yVia; 33006 } 33007 } else if (this.from.y < this.to.y) { 33008 if (this.from.x <= this.to.x) { 33009 xVia = this.from.x + factor * dx; 33010 yVia = this.from.y + factor * dx; 33011 yVia = this.to.y < yVia ? this.to.y : yVia; 33012 } else if (this.from.x > this.to.x) { 33013 xVia = this.from.x - factor * dx; 33014 yVia = this.from.y + factor * dx; 33015 yVia = this.to.y < yVia ? this.to.y : yVia; 33016 } 33017 } 33018 } 33019 } 33020 return { x: xVia, y: yVia }; 33021 } 33022 }, { 33023 key: '_findBorderPosition', 33024 value: function _findBorderPosition(nearNode, ctx) { 33025 var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; 33026 33027 return this._findBorderPositionBezier(nearNode, ctx, options.via); 33028 } 33029 }, {
33030 key: '_getDistanceToEdge', 33031 value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { 33032 var via = arguments.length <= 6 || arguments[6] === undefined ? this._getViaCoordinates() : arguments[6]; 33033 // x3,y3 is the point 33034 return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via); 33035 } 33036 }, { 33037 key: 'getPoint', 33038 33039 /** 33040 * Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way 33041 * @param percentage 33042 * @param via 33043 * @returns {{x: number, y: number}} 33044 * @private 33045 */ 33046 value: function getPoint(percentage) { 33047 var via = arguments.length <= 1 || arguments[1] === undefined ? this._getViaCoordinates() : arguments[1]; 33048 33049 var t = percentage; 33050 var x = Math.pow(1 - t, 2) * this.from.x + 2 * t * (1 - t) * via.x + Math.pow(t, 2) * this.to.x; 33051 var y = Math.pow(1 - t, 2) * this.from.y + 2 * t * (1 - t) * via.y + Math.pow(t, 2) * this.to.y; 33052 33053 return { x: x, y: y }; 33054 } 33055 }]); 33056 33057 return BezierEdgeStatic; 33058 })(_utilBezierEdgeBase2['default']); 33059 33060 exports['default'] = BezierEdgeStatic; 33061 module.exports = exports['default']; 33062 33063/***/ }, 33064/* 89 */ 33065/***/ function(module, exports, __webpack_require__) { 33066 33067 'use strict'; 33068 33069 Object.defineProperty(exports, '__esModule', { 33070 value: true 33071 }); 33072 33073 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 33074 33075 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 33076 33077 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 33078 33079 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 33080 33081 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 33082 33083 var _utilEdgeBase = __webpack_require__(86); 33084 33085 var _utilEdgeBase2 = _interopRequireDefault(_utilEdgeBase); 33086 33087 var StraightEdge = (function (_EdgeBase) { 33088 _inherits(StraightEdge, _EdgeBase); 33089 33090 function StraightEdge(options, body, labelModule) { 33091 _classCallCheck(this, StraightEdge); 33092 33093 _get(Object.getPrototypeOf(StraightEdge.prototype), 'constructor', this).call(this, options, body, labelModule); 33094 } 33095 33096 _createClass(StraightEdge, [{ 33097 key: '_line', 33098 33099 /** 33100 * Draw a line between two nodes 33101 * @param {CanvasRenderingContext2D} ctx 33102 * @private 33103 */ 33104 value: function _line(ctx) { 33105 // draw a straight line 33106 ctx.beginPath(); 33107 ctx.moveTo(this.from.x, this.from.y); 33108 ctx.lineTo(this.to.x, this.to.y); 33109 // draw shadow if enabled 33110 this.enableShadow(ctx); 33111 ctx.stroke(); 33112 this.disableShadow(ctx); 33113 return undefined; 33114 } 33115 }, {
33116 key: 'getPoint', 33117 33118 /** 33119 * Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way 33120 * @param percentage 33121 * @param via 33122 * @returns {{x: number, y: number}} 33123 * @private 33124 */ 33125 value: function getPoint(percentage) { 33126 return { 33127 x: (1 - percentage) * this.from.x + percentage * this.to.x, 33128 y: (1 - percentage) * this.from.y + percentage * this.to.y 33129 }; 33130 } 33131 }, { 33132 key: '_findBorderPosition', 33133 value: function _findBorderPosition(nearNode, ctx) { 33134 var node1 = this.to; 33135 var node2 = this.from; 33136 if (nearNode.id === this.from.id) { 33137 node1 = this.from; 33138 node2 = this.to; 33139 } 33140 33141 var angle = Math.atan2(node1.y - node2.y, node1.x - node2.x); 33142 var dx = node1.x - node2.x; 33143 var dy = node1.y - node2.y; 33144 var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy); 33145 var toBorderDist = nearNode.distanceToBorder(ctx, angle); 33146 var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength; 33147 33148 var borderPos = {}; 33149 borderPos.x = (1 - toBorderPoint) * node2.x + toBorderPoint * node1.x; 33150 borderPos.y = (1 - toBorderPoint) * node2.y + toBorderPoint * node1.y; 33151 33152 return borderPos; 33153 } 33154 }, { 33155 key: '_getDistanceToEdge', 33156 value: function _getDistanceToEdge(x1, y1, x2, y2, x3, y3) { 33157 // x3,y3 is the point 33158 return this._getDistanceToLine(x1, y1, x2, y2, x3, y3); 33159 } 33160 }]); 33161 33162 return StraightEdge; 33163 })(_utilEdgeBase2['default']); 33164 33165 exports['default'] = StraightEdge; 33166 module.exports = exports['default']; 33167 33168/***/ }, 33169/* 90 */ 33170/***/ function(module, exports, __webpack_require__) { 33171 33172 'use strict'; 33173 33174 Object.defineProperty(exports, '__esModule', { 33175 value: true 33176 }); 33177 33178 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 33179 33180 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 33181 33182 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 33183 33184 var _componentsPhysicsBarnesHutSolver = __webpack_require__(91); 33185 33186 var _componentsPhysicsBarnesHutSolver2 = _interopRequireDefault(_componentsPhysicsBarnesHutSolver); 33187 33188 var _componentsPhysicsRepulsionSolver = __webpack_require__(92); 33189 33190 var _componentsPhysicsRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsRepulsionSolver); 33191 33192 var _componentsPhysicsHierarchicalRepulsionSolver = __webpack_require__(93); 33193 33194 var _componentsPhysicsHierarchicalRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalRepulsionSolver); 33195 33196 var _componentsPhysicsSpringSolver = __webpack_require__(94); 33197 33198 var _componentsPhysicsSpringSolver2 = _interopRequireDefault(_componentsPhysicsSpringSolver); 33199 33200 var _componentsPhysicsHierarchicalSpringSolver = __webpack_require__(95); 33201 33202 var _componentsPhysicsHierarchicalSpringSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalSpringSolver); 33203 33204 var _componentsPhysicsCentralGravitySolver = __webpack_require__(96); 33205 33206 var _componentsPhysicsCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsCentralGravitySolver); 33207 33208 var _componentsPhysicsFA2BasedRepulsionSolver = __webpack_require__(97); 33209 33210 var _componentsPhysicsFA2BasedRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedRepulsionSolver); 33211 33212 var _componentsPhysicsFA2BasedCentralGravitySolver = __webpack_require__(98); 33213 33214 var _componentsPhysicsFA2BasedCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedCentralGravitySolver); 33215 33216 var util = __webpack_require__(1); 33217 33218 var PhysicsEngine = (function () { 33219 function PhysicsEngine(body) { 33220 _classCallCheck(this, PhysicsEngine); 33221 33222 this.body = body; 33223 this.physicsBody = { physicsNodeIndices: [], physicsEdgeIndices: [], forces: {}, velocities: {} }; 33224 33225 this.physicsEnabled = true; 33226 this.simulationInterval = 1000 / 60; 33227 this.requiresTimeout = true; 33228 this.previousStates = {}; 33229 this.referenceState = {}; 33230 this.freezeCache = {}; 33231 this.renderTimer = undefined; 33232 33233 // parameters for the adaptive timestep 33234 this.adaptiveTimestep = false;
33235 this.adaptiveTimestepEnabled = false; 33236 this.adaptiveCounter = 0; 33237 this.adaptiveInterval = 3; 33238 33239 this.stabilized = false; 33240 this.startedStabilization = false; 33241 this.stabilizationIterations = 0; 33242 this.ready = false; // will be set to true if the stabilize 33243 33244 // default options 33245 this.options = {}; 33246 this.defaultOptions = { 33247 enabled: true, 33248 barnesHut: { 33249 theta: 0.5, 33250 gravitationalConstant: -2000, 33251 centralGravity: 0.3, 33252 springLength: 95, 33253 springConstant: 0.04, 33254 damping: 0.09, 33255 avoidOverlap: 0 33256 }, 33257 forceAtlas2Based: { 33258 theta: 0.5, 33259 gravitationalConstant: -50, 33260 centralGravity: 0.01, 33261 springConstant: 0.08, 33262 springLength: 100, 33263 damping: 0.4, 33264 avoidOverlap: 0 33265 }, 33266 repulsion: { 33267 centralGravity: 0.2, 33268 springLength: 200, 33269 springConstant: 0.05, 33270 nodeDistance: 100, 33271 damping: 0.09, 33272 avoidOverlap: 0 33273 }, 33274 hierarchicalRepulsion: { 33275 centralGravity: 0.0, 33276 springLength: 100, 33277 springConstant: 0.01, 33278 nodeDistance: 120, 33279 damping: 0.09 33280 }, 33281 maxVelocity: 50, 33282 minVelocity: 0.75, // px/s 33283 solver: 'barnesHut', 33284 stabilization: { 33285 enabled: true, 33286 iterations: 1000, // maximum number of iteration to stabilize 33287 updateInterval: 50, 33288 onlyDynamicEdges: false, 33289 fit: true 33290 }, 33291 timestep: 0.5, 33292 adaptiveTimestep: true 33293 }; 33294 util.extend(this.options, this.defaultOptions); 33295 this.timestep = 0.5; 33296 this.layoutFailed = false; 33297 33298 this.bindEventListeners(); 33299 } 33300 33301 _createClass(PhysicsEngine, [{ 33302 key: 'bindEventListeners', 33303 value: function bindEventListeners() { 33304 var _this = this; 33305 33306 this.body.emitter.on('initPhysics', function () { 33307 _this.initPhysics(); 33308 }); 33309 this.body.emitter.on('_layoutFailed', function () { 33310 _this.layoutFailed = true; 33311 }); 33312 this.body.emitter.on('resetPhysics', function () { 33313 _this.stopSimulation();_this.ready = false; 33314 }); 33315 this.body.emitter.on('disablePhysics', function () { 33316 _this.physicsEnabled = false;_this.stopSimulation(); 33317 }); 33318 this.body.emitter.on('restorePhysics', function () { 33319 _this.setOptions(_this.options); 33320 if (_this.ready === true) { 33321 _this.startSimulation(); 33322 } 33323 }); 33324 this.body.emitter.on('startSimulation', function () { 33325 if (_this.ready === true) { 33326 _this.startSimulation(); 33327 } 33328 }); 33329 this.body.emitter.on('stopSimulation', function () { 33330 _this.stopSimulation(); 33331 }); 33332 this.body.emitter.on('destroy', function () { 33333 _this.stopSimulation(false); 33334 _this.body.emitter.off(); 33335 }); 33336 } 33337 }, { 33338 key: 'setOptions', 33339 33340 /** 33341 * set the physics options 33342 * @param options 33343 */ 33344 value: function setOptions(options) { 33345 if (options !== undefined) { 33346 if (options === false) { 33347 this.options.enabled = false; 33348 this.physicsEnabled = false; 33349 this.stopSimulation(); 33350 } else { 33351 this.physicsEnabled = true; 33352 util.selectiveNotDeepExtend(['stabilization'], this.options, options); 33353 util.mergeOptions(this.options, options, 'stabilization'); 33354 33355 if (options.enabled === undefined) { 33356 this.options.enabled = true; 33357 } 33358 33359 if (this.options.enabled === false) { 33360 this.physicsEnabled = false; 33361 this.stopSimulation(); 33362 } 33363 33364 // set the timestep 33365 this.timestep = this.options.timestep; 33366 } 33367 } 33368 this.init(); 33369 } 33370 }, { 33371 key: 'init', 33372 33373 /** 33374 * configure the engine. 33375 */ 33376 value: function init() { 33377 var options; 33378 if (this.options.solver === 'forceAtlas2Based') { 33379 options = this.options.forceAtlas2Based; 33380 this.nodesSolver = new _componentsPhysicsFA2BasedRepulsionSolver2['default'](this.body, this.physicsBody, options); 33381 this.edgesSolver = new _componentsPhysicsSpringSolver2['default'](this.body, this.physicsBody, options); 33382 this.gravitySolver = new _componentsPhysicsFA2BasedCentralGravitySolver2['default'](this.body, this.physicsBody, options); 33383 } else if (this.options.solver === 'repulsion') { 33384 options = this.options.repulsion; 33385 this.nodesSolver = new _componentsPhysicsRepulsionSolver2['default'](this.body, this.physicsBody, options); 33386 this.edgesSolver = new _componentsPhysicsSpringSolver2['default'](this.body, this.physicsBody, options); 33387 this.gravitySolver = new _componentsPhysicsCentralGravitySolver2['default'](this.body, this.physicsBody, options); 33388 } else if (this.options.solver === 'hierarchicalRepulsion') { 33389 options = this.options.hierarchicalRepulsion; 33390 this.nodesSolver = new _componentsPhysicsHierarchicalRepulsionSolver2['default'](this.body, this.physicsBody, options); 33391 this.edgesSolver = new _componentsPhysicsHierarchicalSpringSolver2['default'](this.body, this.physicsBody, options); 33392 this.gravitySolver = new _componentsPhysicsCentralGravitySolver2['default'](this.body, this.physicsBody, options); 33393 } else { 33394 // barnesHut 33395 options = this.options.barnesHut; 33396 this.nodesSolver = new _componentsPhysicsBarnesHutSolver2['default'](this.body, this.physicsBody, options); 33397 this.edgesSolver = new _componentsPhysicsSpringSolver2['default'](this.body, this.physicsBody, options); 33398 this.gravitySolver = new _componentsPhysicsCentralGravitySolver2['default'](this.body, this.physicsBody, options); 33399 } 33400 33401 this.modelOptions = options; 33402 } 33403 }, {
33404 key: 'initPhysics', 33405 33406 /** 33407 * initialize the engine 33408 */ 33409 value: function initPhysics() { 33410 if (this.physicsEnabled === true && this.options.enabled === true) { 33411 if (this.options.stabilization.enabled === true) { 33412 this.stabilize(); 33413 } else { 33414 this.stabilized = false; 33415 this.ready = true; 33416 this.body.emitter.emit('fit', {}, this.layoutFailed); // if the layout failed, we use the approximation for the zoom 33417 this.startSimulation(); 33418 } 33419 } else { 33420 this.ready = true; 33421 this.body.emitter.emit('fit'); 33422 } 33423 } 33424 }, { 33425 key: 'startSimulation', 33426 33427 /** 33428 * Start the simulation 33429 */ 33430 value: function startSimulation() { 33431 if (this.physicsEnabled === true && this.options.enabled === true) { 33432 this.stabilized = false; 33433 33434 // when visible, adaptivity is disabled. 33435 this.adaptiveTimestep = false; 33436 33437 // this sets the width of all nodes initially which could be required for the avoidOverlap 33438 this.body.emitter.emit('_resizeNodes'); 33439 if (this.viewFunction === undefined) { 33440 this.viewFunction = this.simulationStep.bind(this); 33441 this.body.emitter.on('initRedraw', this.viewFunction); 33442 this.body.emitter.emit('_startRendering'); 33443 } 33444 } else { 33445 this.body.emitter.emit('_redraw'); 33446 } 33447 } 33448 }, { 33449 key: 'stopSimulation', 33450 33451 /** 33452 * Stop the simulation, force stabilization. 33453 */ 33454 value: function stopSimulation() { 33455 var emit = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; 33456 33457 this.stabilized = true; 33458 if (emit === true) { 33459 this._emitStabilized(); 33460 } 33461 if (this.viewFunction !== undefined) { 33462 this.body.emitter.off('initRedraw', this.viewFunction); 33463 this.viewFunction = undefined; 33464 if (emit === true) { 33465 this.body.emitter.emit('_stopRendering'); 33466 } 33467 } 33468 } 33469 }, { 33470 key: 'simulationStep', 33471 33472 /** 33473 * The viewFunction inserts this step into each renderloop. It calls the physics tick and handles the cleanup at stabilized. 33474 * 33475 */ 33476 value: function simulationStep() { 33477 // check if the physics have settled 33478 var startTime = Date.now(); 33479 this.physicsTick(); 33480 var physicsTime = Date.now() - startTime; 33481 33482 // run double speed if it is a little graph 33483 if ((physicsTime < 0.4 * this.simulationInterval || this.runDoubleSpeed === true) && this.stabilized === false) { 33484 this.physicsTick(); 33485 33486 // this makes sure there is no jitter. The decision is taken once to run it at double speed. 33487 this.runDoubleSpeed = true; 33488 } 33489 33490 if (this.stabilized === true) { 33491 this.stopSimulation(); 33492 } 33493 } 33494 }, { 33495 key: '_emitStabilized', 33496 33497 /** 33498 * trigger the stabilized event. 33499 * @private 33500 */ 33501 value: function _emitStabilized() { 33502 var _this2 = this; 33503 33504 var amountOfIterations = arguments.length <= 0 || arguments[0] === undefined ? this.stabilizationIterations : arguments[0]; 33505 33506 if (this.stabilizationIterations > 1 || this.startedStabilization === true) { 33507 setTimeout(function () { 33508 _this2.body.emitter.emit('stabilized', { iterations: amountOfIterations }); 33509 _this2.startedStabilization = false; 33510 _this2.stabilizationIterations = 0; 33511 }, 0); 33512 } 33513 } 33514 }, { 33515 key: 'physicsTick', 33516 33517 /** 33518 * A single simulation step (or 'tick') in the physics simulation 33519 * 33520 * @private 33521 */ 33522 value: function physicsTick() { 33523 // this is here to ensure that there is no start event when the network is already stable. 33524 if (this.startedStabilization === false) { 33525 this.body.emitter.emit('startStabilizing'); 33526 this.startedStabilization = true; 33527 } 33528 33529 if (this.stabilized === false) { 33530 // adaptivity means the timestep adapts to the situation, only applicable for stabilization 33531 if (this.adaptiveTimestep === true && this.adaptiveTimestepEnabled === true) { 33532 // this is the factor for increasing the timestep on success. 33533 var factor = 1.2; 33534 33535 // we assume the adaptive interval is 33536 if (this.adaptiveCounter % this.adaptiveInterval === 0) { 33537 // we leave the timestep stable for "interval" iterations. 33538 // first the big step and revert. Revert saves the reference state. 33539 this.timestep = 2 * this.timestep; 33540 this.calculateForces(); 33541 this.moveNodes(); 33542 this.revert(); 33543 33544 // now the normal step. Since this is the last step, it is the more stable one and we will take this. 33545 this.timestep = 0.5 * this.timestep; 33546 33547 // since it's half the step, we do it twice. 33548 this.calculateForces(); 33549 this.moveNodes(); 33550 this.calculateForces(); 33551 this.moveNodes(); 33552 33553 // we compare the two steps. if it is acceptable we double the step. 33554 if (this._evaluateStepQuality() === true) { 33555 this.timestep = factor * this.timestep; 33556 } else { 33557 // if not, we decrease the step to a minimum of the options timestep. 33558 // if the decreased timestep is smaller than the options step, we do not reset the counter 33559 // we assume that the options timestep is stable enough. 33560 if (this.timestep / factor < this.options.timestep) { 33561 this.timestep = this.options.timestep; 33562 } else { 33563 // if the timestep was larger than 2 times the option one we check the adaptivity again to ensure 33564 // that large instabilities do not form. 33565 this.adaptiveCounter = -1; // check again next iteration 33566 this.timestep = Math.max(this.options.timestep, this.timestep / factor); 33567 } 33568 } 33569 } else { 33570 // normal step, keeping timestep constant 33571 this.calculateForces(); 33572 this.moveNodes(); 33573 } 33574 33575 // increment the counter 33576 this.adaptiveCounter += 1; 33577 } else { 33578 // case for the static timestep, we reset it to the one in options and take a normal step. 33579 this.timestep = this.options.timestep; 33580 this.calculateForces(); 33581 this.moveNodes(); 33582 } 33583 33584 // determine if the network has stabilzied 33585 if (this.stabilized === true) { 33586 this.revert(); 33587 } 33588 33589 this.stabilizationIterations++; 33590 } 33591 } 33592 }, {
33593 key: 'updatePhysicsData', 33594 33595 /** 33596 * Nodes and edges can have the physics toggles on or off. A collection of indices is created here so we can skip the check all the time. 33597 * 33598 * @private 33599 */ 33600 value: function updatePhysicsData() { 33601 this.physicsBody.forces = {}; 33602 this.physicsBody.physicsNodeIndices = []; 33603 this.physicsBody.physicsEdgeIndices = []; 33604 var nodes = this.body.nodes; 33605 var edges = this.body.edges; 33606 33607 // get node indices for physics 33608 for (var nodeId in nodes) { 33609 if (nodes.hasOwnProperty(nodeId)) { 33610 if (nodes[nodeId].options.physics === true) { 33611 this.physicsBody.physicsNodeIndices.push(nodeId); 33612 } 33613 } 33614 } 33615 33616 // get edge indices for physics 33617 for (var edgeId in edges) { 33618 if (edges.hasOwnProperty(edgeId)) { 33619 if (edges[edgeId].options.physics === true) { 33620 this.physicsBody.physicsEdgeIndices.push(edgeId); 33621 } 33622 } 33623 } 33624 33625 // get the velocity and the forces vector 33626 for (var i = 0; i < this.physicsBody.physicsNodeIndices.length; i++) { 33627 var nodeId = this.physicsBody.physicsNodeIndices[i]; 33628 this.physicsBody.forces[nodeId] = { x: 0, y: 0 }; 33629 33630 // forces can be reset because they are recalculated. Velocities have to persist. 33631 if (this.physicsBody.velocities[nodeId] === undefined) { 33632 this.physicsBody.velocities[nodeId] = { x: 0, y: 0 }; 33633 } 33634 } 33635 33636 // clean deleted nodes from the velocity vector 33637 for (var nodeId in this.physicsBody.velocities) { 33638 if (nodes[nodeId] === undefined) { 33639 delete this.physicsBody.velocities[nodeId]; 33640 } 33641 } 33642 } 33643 }, { 33644 key: 'revert', 33645 33646 /** 33647 * Revert the simulation one step. This is done so after stabilization, every new start of the simulation will also say stabilized. 33648 */ 33649 value: function revert() { 33650 var nodeIds = Object.keys(this.previousStates); 33651 var nodes = this.body.nodes; 33652 var velocities = this.physicsBody.velocities; 33653 this.referenceState = {}; 33654 33655 for (var i = 0; i < nodeIds.length; i++) { 33656 var nodeId = nodeIds[i]; 33657 if (nodes[nodeId] !== undefined) { 33658 if (nodes[nodeId].options.physics === true) { 33659 this.referenceState[nodeId] = { 33660 positions: { x: nodes[nodeId].x, y: nodes[nodeId].y } 33661 }; 33662 velocities[nodeId].x = this.previousStates[nodeId].vx; 33663 velocities[nodeId].y = this.previousStates[nodeId].vy; 33664 nodes[nodeId].x = this.previousStates[nodeId].x; 33665 nodes[nodeId].y = this.previousStates[nodeId].y; 33666 } 33667 } else { 33668 delete this.previousStates[nodeId]; 33669 } 33670 } 33671 } 33672 }, { 33673 key: '_evaluateStepQuality', 33674 33675 /** 33676 * This compares the reference state to the current state 33677 */ 33678 value: function _evaluateStepQuality() { 33679 var dx = undefined, 33680 dy = undefined, 33681 dpos = undefined; 33682 var nodes = this.body.nodes; 33683 var reference = this.referenceState; 33684 var posThreshold = 0.3; 33685 33686 for (var nodeId in this.referenceState) { 33687 if (this.referenceState.hasOwnProperty(nodeId) && nodes[nodeId] !== undefined) { 33688 dx = nodes[nodeId].x - reference[nodeId].positions.x; 33689 dy = nodes[nodeId].y - reference[nodeId].positions.y; 33690 33691 dpos = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); 33692 33693 if (dpos > posThreshold) { 33694 return false; 33695 } 33696 } 33697 } 33698 return true; 33699 } 33700 }, { 33701 key: 'moveNodes', 33702 33703 /** 33704 * move the nodes one timestap and check if they are stabilized 33705 * @returns {boolean} 33706 */ 33707 value: function moveNodes() { 33708 var nodeIndices = this.physicsBody.physicsNodeIndices; 33709 var maxVelocity = this.options.maxVelocity ? this.options.maxVelocity : 1e9; 33710 var maxNodeVelocity = 0; 33711 var averageNodeVelocity = 0; 33712 33713 // the velocity threshold (energy in the system) for the adaptivity toggle 33714 var velocityAdaptiveThreshold = 5; 33715 33716 for (var i = 0; i < nodeIndices.length; i++) { 33717 var nodeId = nodeIndices[i]; 33718 var nodeVelocity = this._performStep(nodeId, maxVelocity); 33719 // stabilized is true if stabilized is true and velocity is smaller than vmin --> all nodes must be stabilized 33720 maxNodeVelocity = Math.max(maxNodeVelocity, nodeVelocity); 33721 averageNodeVelocity += nodeVelocity; 33722 } 33723 33724 // evaluating the stabilized and adaptiveTimestepEnabled conditions 33725 this.adaptiveTimestepEnabled = averageNodeVelocity / nodeIndices.length < velocityAdaptiveThreshold; 33726 this.stabilized = maxNodeVelocity < this.options.minVelocity; 33727 } 33728 }, {
33729 key: '_performStep', 33730 33731 /** 33732 * Perform the actual step 33733 * 33734 * @param nodeId 33735 * @param maxVelocity 33736 * @returns {number} 33737 * @private 33738 */ 33739 value: function _performStep(nodeId, maxVelocity) { 33740 var node = this.body.nodes[nodeId]; 33741 var timestep = this.timestep; 33742 var forces = this.physicsBody.forces; 33743 var velocities = this.physicsBody.velocities; 33744 33745 // store the state so we can revert 33746 this.previousStates[nodeId] = { x: node.x, y: node.y, vx: velocities[nodeId].x, vy: velocities[nodeId].y }; 33747 33748 if (node.options.fixed.x === false) { 33749 var dx = this.modelOptions.damping * velocities[nodeId].x; // damping force 33750 var ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration 33751 velocities[nodeId].x += ax * timestep; // velocity 33752 velocities[nodeId].x = Math.abs(velocities[nodeId].x) > maxVelocity ? velocities[nodeId].x > 0 ? maxVelocity : -maxVelocity : velocities[nodeId].x; 33753 node.x += velocities[nodeId].x * timestep; // position 33754 } else { 33755 forces[nodeId].x = 0; 33756 velocities[nodeId].x = 0; 33757 } 33758 33759 if (node.options.fixed.y === false) { 33760 var dy = this.modelOptions.damping * velocities[nodeId].y; // damping force 33761 var ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration 33762 velocities[nodeId].y += ay * timestep; // velocity 33763 velocities[nodeId].y = Math.abs(velocities[nodeId].y) > maxVelocity ? velocities[nodeId].y > 0 ? maxVelocity : -maxVelocity : velocities[nodeId].y; 33764 node.y += velocities[nodeId].y * timestep; // position 33765 } else { 33766 forces[nodeId].y = 0; 33767 velocities[nodeId].y = 0; 33768 } 33769 33770 var totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x, 2) + Math.pow(velocities[nodeId].y, 2)); 33771 return totalVelocity; 33772 } 33773 }, { 33774 key: 'calculateForces', 33775 33776 /** 33777 * calculate the forces for one physics iteration. 33778 */ 33779 value: function calculateForces() { 33780 this.gravitySolver.solve(); 33781 this.nodesSolver.solve(); 33782 this.edgesSolver.solve(); 33783 } 33784 }, { 33785 key: '_freezeNodes', 33786 33787 /** 33788 * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization 33789 * because only the supportnodes for the smoothCurves have to settle. 33790 * 33791 * @private 33792 */ 33793 value: function _freezeNodes() { 33794 var nodes = this.body.nodes; 33795 for (var id in nodes) { 33796 if (nodes.hasOwnProperty(id)) { 33797 if (nodes[id].x && nodes[id].y) { 33798 this.freezeCache[id] = { x: nodes[id].options.fixed.x, y: nodes[id].options.fixed.y }; 33799 nodes[id].options.fixed.x = true; 33800 nodes[id].options.fixed.y = true; 33801 } 33802 } 33803 } 33804 } 33805 }, { 33806 key: '_restoreFrozenNodes', 33807 33808 /** 33809 * Unfreezes the nodes that have been frozen by _freezeDefinedNodes. 33810 * 33811 * @private 33812 */ 33813 value: function _restoreFrozenNodes() { 33814 var nodes = this.body.nodes; 33815 for (var id in nodes) { 33816 if (nodes.hasOwnProperty(id)) { 33817 if (this.freezeCache[id] !== undefined) { 33818 nodes[id].options.fixed.x = this.freezeCache[id].x; 33819 nodes[id].options.fixed.y = this.freezeCache[id].y; 33820 } 33821 } 33822 } 33823 this.freezeCache = {}; 33824 } 33825 }, { 33826 key: 'stabilize', 33827 33828 /** 33829 * Find a stable position for all nodes 33830 * @private 33831 */ 33832 value: function stabilize() { 33833 var _this3 = this; 33834 33835 var iterations = arguments.length <= 0 || arguments[0] === undefined ? this.options.stabilization.iterations : arguments[0]; 33836 33837 if (typeof iterations !== 'number') { 33838 console.log('The stabilize method needs a numeric amount of iterations. Switching to default: ', this.options.stabilization.iterations); 33839 iterations = this.options.stabilization.iterations; 33840 } 33841 33842 if (this.physicsBody.physicsNodeIndices.length === 0) { 33843 this.ready = true; 33844 return; 33845 } 33846 33847 // enable adaptive timesteps 33848 this.adaptiveTimestep = true && this.options.adaptiveTimestep; 33849 33850 // this sets the width of all nodes initially which could be required for the avoidOverlap 33851 this.body.emitter.emit('_resizeNodes'); 33852 33853 // stop the render loop 33854 this.stopSimulation(); 33855 33856 // set stabilze to false 33857 this.stabilized = false;
33858 33859 // block redraw requests 33860 this.body.emitter.emit('_blockRedraw'); 33861 this.targetIterations = iterations; 33862 33863 // start the stabilization 33864 if (this.options.stabilization.onlyDynamicEdges === true) { 33865 this._freezeNodes(); 33866 } 33867 this.stabilizationIterations = 0; 33868 33869 setTimeout(function () { 33870 return _this3._stabilizationBatch(); 33871 }, 0); 33872 } 33873 }, { 33874 key: '_stabilizationBatch', 33875 33876 /** 33877 * One batch of stabilization 33878 * @private 33879 */ 33880 value: function _stabilizationBatch() { 33881 // this is here to ensure that there is at least one start event. 33882 if (this.startedStabilization === false) { 33883 this.body.emitter.emit('startStabilizing'); 33884 this.startedStabilization = true; 33885 } 33886 33887 var count = 0; 33888 while (this.stabilized === false && count < this.options.stabilization.updateInterval && this.stabilizationIterations < this.targetIterations) { 33889 this.physicsTick(); 33890 count++; 33891 } 33892 33893 if (this.stabilized === false && this.stabilizationIterations < this.targetIterations) { 33894 this.body.emitter.emit('stabilizationProgress', { iterations: this.stabilizationIterations, total: this.targetIterations }); 33895 setTimeout(this._stabilizationBatch.bind(this), 0); 33896 } else { 33897 this._finalizeStabilization(); 33898 } 33899 } 33900 }, { 33901 key: '_finalizeStabilization', 33902 33903 /** 33904 * Wrap up the stabilization, fit and emit the events. 33905 * @private 33906 */ 33907 value: function _finalizeStabilization() { 33908 this.body.emitter.emit('_allowRedraw'); 33909 if (this.options.stabilization.fit === true) { 33910 this.body.emitter.emit('fit'); 33911 } 33912 33913 if (this.options.stabilization.onlyDynamicEdges === true) { 33914 this._restoreFrozenNodes(); 33915 } 33916 33917 this.body.emitter.emit('stabilizationIterationsDone'); 33918 this.body.emitter.emit('_requestRedraw'); 33919 33920 if (this.stabilized === true) { 33921 this._emitStabilized(); 33922 } else { 33923 this.startSimulation(); 33924 } 33925 33926 this.ready = true; 33927 } 33928 }]); 33929 33930 return PhysicsEngine; 33931 })(); 33932 33933 exports['default'] = PhysicsEngine; 33934 module.exports = exports['default']; 33935 33936/***/ }, 33937/* 91 */ 33938/***/ function(module, exports) { 33939 33940 "use strict"; 33941 33942 Object.defineProperty(exports, "__esModule", { 33943 value: true 33944 }); 33945 33946 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 33947 33948 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 33949 33950 var BarnesHutSolver = (function () { 33951 function BarnesHutSolver(body, physicsBody, options) { 33952 _classCallCheck(this, BarnesHutSolver); 33953 33954 this.body = body; 33955 this.physicsBody = physicsBody; 33956 this.barnesHutTree; 33957 this.setOptions(options); 33958 this.randomSeed = 5; 33959 } 33960 33961 _createClass(BarnesHutSolver, [{ 33962 key: "setOptions", 33963 value: function setOptions(options) { 33964 this.options = options; 33965 this.thetaInversed = 1 / this.options.theta; 33966 this.overlapAvoidanceFactor = 1 - Math.max(0, Math.min(1, this.options.avoidOverlap)); // if 1 then min distance = 0.5, if 0.5 then min distance = 0.5 + 0.5*node.shape.radius 33967 } 33968 }, { 33969 key: "seededRandom", 33970 value: function seededRandom() { 33971 var x = Math.sin(this.randomSeed++) * 10000; 33972 return x - Math.floor(x); 33973 } 33974 }, { 33975 key: "solve", 33976 33977 /** 33978 * This function calculates the forces the nodes apply on eachother based on a gravitational model. 33979 * The Barnes Hut method is used to speed up this N-body simulation. 33980 * 33981 * @private 33982 */ 33983 value: function solve() { 33984 if (this.options.gravitationalConstant !== 0 && this.physicsBody.physicsNodeIndices.length > 0) { 33985 var node = undefined; 33986 var nodes = this.body.nodes; 33987 var nodeIndices = this.physicsBody.physicsNodeIndices; 33988 var nodeCount = nodeIndices.length; 33989 33990 // create the tree 33991 var barnesHutTree = this._formBarnesHutTree(nodes, nodeIndices); 33992 33993 // for debugging 33994 this.barnesHutTree = barnesHutTree; 33995 33996 // place the nodes one by one recursively 33997 for (var i = 0; i < nodeCount; i++) { 33998 node = nodes[nodeIndices[i]]; 33999 if (node.options.mass > 0) { 34000 // starting with root is irrelevant, it never passes the BarnesHutSolver condition 34001 this._getForceContribution(barnesHutTree.root.children.NW, node); 34002 this._getForceContribution(barnesHutTree.root.children.NE, node); 34003 this._getForceContribution(barnesHutTree.root.children.SW, node); 34004 this._getForceContribution(barnesHutTree.root.children.SE, node); 34005 } 34006 } 34007 } 34008 } 34009 }, {
34010 key: "_getForceContribution", 34011 34012 /** 34013 * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass. 34014 * If a region contains a single node, we check if it is not itself, then we apply the force. 34015 * 34016 * @param parentBranch 34017 * @param node 34018 * @private 34019 */ 34020 value: function _getForceContribution(parentBranch, node) { 34021 // we get no force contribution from an empty region 34022 if (parentBranch.childrenCount > 0) { 34023 var dx = undefined, 34024 dy = undefined, 34025 distance = undefined; 34026 34027 // get the distance from the center of mass to the node. 34028 dx = parentBranch.centerOfMass.x - node.x; 34029 dy = parentBranch.centerOfMass.y - node.y; 34030 distance = Math.sqrt(dx * dx + dy * dy); 34031 34032 // BarnesHutSolver condition 34033 // original condition : s/d < theta = passed === d/s > 1/theta = passed 34034 // calcSize = 1/s --> d * 1/s > 1/theta = passed 34035 if (distance * parentBranch.calcSize > this.thetaInversed) { 34036 this._calculateForces(distance, dx, dy, node, parentBranch); 34037 } else { 34038 // Did not pass the condition, go into children if available 34039 if (parentBranch.childrenCount === 4) { 34040 this._getForceContribution(parentBranch.children.NW, node); 34041 this._getForceContribution(parentBranch.children.NE, node); 34042 this._getForceContribution(parentBranch.children.SW, node); 34043 this._getForceContribution(parentBranch.children.SE, node); 34044 } else { 34045 // parentBranch must have only one node, if it was empty we wouldnt be here 34046 if (parentBranch.children.data.id != node.id) { 34047 // if it is not self 34048 this._calculateForces(distance, dx, dy, node, parentBranch); 34049 } 34050 } 34051 } 34052 } 34053 } 34054 }, { 34055 key: "_calculateForces", 34056 34057 /** 34058 * Calculate the forces based on the distance. 34059 * 34060 * @param distance 34061 * @param dx 34062 * @param dy 34063 * @param node 34064 * @param parentBranch 34065 * @private 34066 */ 34067 value: function _calculateForces(distance, dx, dy, node, parentBranch) { 34068 if (distance === 0) { 34069 distance = 0.1; 34070 dx = distance; 34071 } 34072 34073 if (this.overlapAvoidanceFactor < 1) { 34074 distance = Math.max(0.1 + this.overlapAvoidanceFactor * node.shape.radius, distance - node.shape.radius); 34075 } 34076 34077 // the dividing by the distance cubed instead of squared allows us to get the fx and fy components without sines and cosines 34078 // it is shorthand for gravityforce with distance squared and fx = dx/distance * gravityForce 34079 var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / Math.pow(distance, 3); 34080 var fx = dx * gravityForce; 34081 var fy = dy * gravityForce; 34082 34083 this.physicsBody.forces[node.id].x += fx; 34084 this.physicsBody.forces[node.id].y += fy; 34085 } 34086 }, { 34087 key: "_formBarnesHutTree", 34088 34089 /** 34090 * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes. 34091 * 34092 * @param nodes 34093 * @param nodeIndices 34094 * @private 34095 */ 34096 value: function _formBarnesHutTree(nodes, nodeIndices) { 34097 var node = undefined; 34098 var nodeCount = nodeIndices.length; 34099 34100 var minX = nodes[nodeIndices[0]].x; 34101 var minY = nodes[nodeIndices[0]].y; 34102 var maxX = nodes[nodeIndices[0]].x; 34103 var maxY = nodes[nodeIndices[0]].y; 34104 34105 // get the range of the nodes 34106 for (var i = 1; i < nodeCount; i++) { 34107 var x = nodes[nodeIndices[i]].x; 34108 var y = nodes[nodeIndices[i]].y; 34109 if (nodes[nodeIndices[i]].options.mass > 0) { 34110 if (x < minX) { 34111 minX = x; 34112 } 34113 if (x > maxX) { 34114 maxX = x; 34115 } 34116 if (y < minY) { 34117 minY = y; 34118 } 34119 if (y > maxY) { 34120 maxY = y; 34121 } 34122 } 34123 } 34124 // make the range a square 34125 var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y 34126 if (sizeDiff > 0) { 34127 minY -= 0.5 * sizeDiff; 34128 maxY += 0.5 * sizeDiff; 34129 } // xSize > ySize 34130 else { 34131 minX += 0.5 * sizeDiff; 34132 maxX -= 0.5 * sizeDiff; 34133 } // xSize < ySize 34134 34135 var minimumTreeSize = 1e-5; 34136 var rootSize = Math.max(minimumTreeSize, Math.abs(maxX - minX)); 34137 var halfRootSize = 0.5 * rootSize; 34138 var centerX = 0.5 * (minX + maxX), 34139 centerY = 0.5 * (minY + maxY); 34140 34141 // construct the barnesHutTree 34142 var barnesHutTree = { 34143 root: { 34144 centerOfMass: { x: 0, y: 0 }, 34145 mass: 0, 34146 range: { 34147 minX: centerX - halfRootSize, maxX: centerX + halfRootSize, 34148 minY: centerY - halfRootSize, maxY: centerY + halfRootSize 34149 }, 34150 size: rootSize, 34151 calcSize: 1 / rootSize, 34152 children: { data: null }, 34153 maxWidth: 0, 34154 level: 0, 34155 childrenCount: 4 34156 } 34157 }; 34158 this._splitBranch(barnesHutTree.root); 34159 34160 // place the nodes one by one recursively 34161 for (var i = 0; i < nodeCount; i++) { 34162 node = nodes[nodeIndices[i]]; 34163 if (node.options.mass > 0) { 34164 this._placeInTree(barnesHutTree.root, node); 34165 } 34166 } 34167 34168 // make global 34169 return barnesHutTree; 34170 } 34171 }, {
34172 key: "_updateBranchMass", 34173 34174 /** 34175 * this updates the mass of a branch. this is increased by adding a node. 34176 * 34177 * @param parentBranch 34178 * @param node 34179 * @private 34180 */ 34181 value: function _updateBranchMass(parentBranch, node) { 34182 var totalMass = parentBranch.mass + node.options.mass; 34183 var totalMassInv = 1 / totalMass; 34184 34185 parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass; 34186 parentBranch.centerOfMass.x *= totalMassInv; 34187 34188 parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass; 34189 parentBranch.centerOfMass.y *= totalMassInv; 34190 34191 parentBranch.mass = totalMass; 34192 var biggestSize = Math.max(Math.max(node.height, node.radius), node.width); 34193 parentBranch.maxWidth = parentBranch.maxWidth < biggestSize ? biggestSize : parentBranch.maxWidth; 34194 } 34195 }, { 34196 key: "_placeInTree", 34197 34198 /** 34199 * determine in which branch the node will be placed. 34200 * 34201 * @param parentBranch 34202 * @param node
34203 * @param skipMassUpdate 34204 * @private 34205 */ 34206 value: function _placeInTree(parentBranch, node, skipMassUpdate) { 34207 if (skipMassUpdate != true || skipMassUpdate === undefined) { 34208 // update the mass of the branch. 34209 this._updateBranchMass(parentBranch, node); 34210 } 34211 34212 if (parentBranch.children.NW.range.maxX > node.x) { 34213 // in NW or SW 34214 if (parentBranch.children.NW.range.maxY > node.y) { 34215 // in NW 34216 this._placeInRegion(parentBranch, node, "NW"); 34217 } else { 34218 // in SW 34219 this._placeInRegion(parentBranch, node, "SW"); 34220 } 34221 } else { 34222 // in NE or SE 34223 if (parentBranch.children.NW.range.maxY > node.y) { 34224 // in NE 34225 this._placeInRegion(parentBranch, node, "NE"); 34226 } else { 34227 // in SE 34228 this._placeInRegion(parentBranch, node, "SE"); 34229 } 34230 } 34231 } 34232 }, { 34233 key: "_placeInRegion", 34234 34235 /** 34236 * actually place the node in a region (or branch) 34237 * 34238 * @param parentBranch 34239 * @param node 34240 * @param region 34241 * @private 34242 */ 34243 value: function _placeInRegion(parentBranch, node, region) { 34244 switch (parentBranch.children[region].childrenCount) { 34245 case 0: 34246 // place node here 34247 parentBranch.children[region].children.data = node; 34248 parentBranch.children[region].childrenCount = 1; 34249 this._updateBranchMass(parentBranch.children[region], node); 34250 break; 34251 case 1: 34252 // convert into children 34253 // if there are two nodes exactly overlapping (on init, on opening of cluster etc.) 34254 // we move one node a pixel and we do not put it in the tree. 34255 if (parentBranch.children[region].children.data.x === node.x && parentBranch.children[region].children.data.y === node.y) { 34256 node.x += this.seededRandom(); 34257 node.y += this.seededRandom(); 34258 } else { 34259 this._splitBranch(parentBranch.children[region]); 34260 this._placeInTree(parentBranch.children[region], node); 34261 } 34262 break; 34263 case 4: 34264 // place in branch 34265 this._placeInTree(parentBranch.children[region], node); 34266 break; 34267 } 34268 } 34269 }, { 34270 key: "_splitBranch", 34271 34272 /** 34273 * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch 34274 * after the split is complete. 34275 * 34276 * @param parentBranch 34277 * @private 34278 */ 34279 value: function _splitBranch(parentBranch) { 34280 // if the branch is shaded with a node, replace the node in the new subset. 34281 var containedNode = null; 34282 if (parentBranch.childrenCount === 1) { 34283 containedNode = parentBranch.children.data; 34284 parentBranch.mass = 0; 34285 parentBranch.centerOfMass.x = 0; 34286 parentBranch.centerOfMass.y = 0; 34287 } 34288 parentBranch.childrenCount = 4; 34289 parentBranch.children.data = null; 34290 this._insertRegion(parentBranch, "NW"); 34291 this._insertRegion(parentBranch, "NE"); 34292 this._insertRegion(parentBranch, "SW"); 34293 this._insertRegion(parentBranch, "SE"); 34294 34295 if (containedNode != null) { 34296 this._placeInTree(parentBranch, containedNode); 34297 } 34298 } 34299 }, { 34300 key: "_insertRegion", 34301 34302 /** 34303 * This function subdivides the region into four new segments. 34304 * Specifically, this inserts a single new segment. 34305 * It fills the children section of the parentBranch 34306 * 34307 * @param parentBranch 34308 * @param region 34309 * @param parentRange 34310 * @private 34311 */ 34312 value: function _insertRegion(parentBranch, region) { 34313 var minX = undefined, 34314 maxX = undefined, 34315 minY = undefined, 34316 maxY = undefined; 34317 var childSize = 0.5 * parentBranch.size; 34318 switch (region) { 34319 case "NW": 34320 minX = parentBranch.range.minX; 34321 maxX = parentBranch.range.minX + childSize; 34322 minY = parentBranch.range.minY; 34323 maxY = parentBranch.range.minY + childSize; 34324 break; 34325 case "NE": 34326 minX = parentBranch.range.minX + childSize; 34327 maxX = parentBranch.range.maxX; 34328 minY = parentBranch.range.minY; 34329 maxY = parentBranch.range.minY + childSize; 34330 break; 34331 case "SW": 34332 minX = parentBranch.range.minX; 34333 maxX = parentBranch.range.minX + childSize; 34334 minY = parentBranch.range.minY + childSize; 34335 maxY = parentBranch.range.maxY; 34336 break; 34337 case "SE": 34338 minX = parentBranch.range.minX + childSize; 34339 maxX = parentBranch.range.maxX; 34340 minY = parentBranch.range.minY + childSize; 34341 maxY = parentBranch.range.maxY; 34342 break; 34343 } 34344 34345 parentBranch.children[region] = { 34346 centerOfMass: { x: 0, y: 0 }, 34347 mass: 0, 34348 range: { minX: minX, maxX: maxX, minY: minY, maxY: maxY }, 34349 size: 0.5 * parentBranch.size, 34350 calcSize: 2 * parentBranch.calcSize, 34351 children: { data: null }, 34352 maxWidth: 0,
34353 level: parentBranch.level + 1, 34354 childrenCount: 0 34355 }; 34356 } 34357 }, { 34358 key: "_debug", 34359 34360 //--------------------------- DEBUGGING BELOW ---------------------------// 34361 34362 /** 34363 * This function is for debugging purposed, it draws the tree. 34364 * 34365 * @param ctx 34366 * @param color 34367 * @private 34368 */ 34369 value: function _debug(ctx, color) { 34370 if (this.barnesHutTree !== undefined) { 34371 34372 ctx.lineWidth = 1; 34373 34374 this._drawBranch(this.barnesHutTree.root, ctx, color); 34375 } 34376 } 34377 }, { 34378 key: "_drawBranch", 34379 34380 /** 34381 * This function is for debugging purposes. It draws the branches recursively. 34382 * 34383 * @param branch 34384 * @param ctx 34385 * @param color 34386 * @private 34387 */ 34388 value: function _drawBranch(branch, ctx, color) { 34389 if (color === undefined) { 34390 color = "#FF0000"; 34391 } 34392 34393 if (branch.childrenCount === 4) { 34394 this._drawBranch(branch.children.NW, ctx); 34395 this._drawBranch(branch.children.NE, ctx); 34396 this._drawBranch(branch.children.SE, ctx); 34397 this._drawBranch(branch.children.SW, ctx); 34398 } 34399 ctx.strokeStyle = color; 34400 ctx.beginPath(); 34401 ctx.moveTo(branch.range.minX, branch.range.minY); 34402 ctx.lineTo(branch.range.maxX, branch.range.minY); 34403 ctx.stroke(); 34404 34405 ctx.beginPath(); 34406 ctx.moveTo(branch.range.maxX, branch.range.minY); 34407 ctx.lineTo(branch.range.maxX, branch.range.maxY); 34408 ctx.stroke(); 34409 34410 ctx.beginPath(); 34411 ctx.moveTo(branch.range.maxX, branch.range.maxY); 34412 ctx.lineTo(branch.range.minX, branch.range.maxY); 34413 ctx.stroke(); 34414 34415 ctx.beginPath(); 34416 ctx.moveTo(branch.range.minX, branch.range.maxY); 34417 ctx.lineTo(branch.range.minX, branch.range.minY); 34418 ctx.stroke(); 34419 34420 /* 34421 if (branch.mass > 0) { 34422 ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass); 34423 ctx.stroke(); 34424 } 34425 */ 34426 } 34427 }]); 34428 34429 return BarnesHutSolver; 34430 })(); 34431 34432 exports["default"] = BarnesHutSolver; 34433 module.exports = exports["default"]; 34434 34435/***/ }, 34436/* 92 */ 34437/***/ function(module, exports) { 34438 34439 "use strict"; 34440 34441 Object.defineProperty(exports, "__esModule", { 34442 value: true 34443 }); 34444 34445 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 34446 34447 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 34448 34449 var RepulsionSolver = (function () { 34450 function RepulsionSolver(body, physicsBody, options) { 34451 _classCallCheck(this, RepulsionSolver); 34452 34453 this.body = body; 34454 this.physicsBody = physicsBody; 34455 this.setOptions(options); 34456 } 34457 34458 _createClass(RepulsionSolver, [{ 34459 key: "setOptions", 34460 value: function setOptions(options) { 34461 this.options = options; 34462 } 34463 }, { 34464 key: "solve", 34465 34466 /** 34467 * Calculate the forces the nodes apply on each other based on a repulsion field. 34468 * This field is linearly approximated. 34469 * 34470 * @private 34471 */ 34472 value: function solve() { 34473 var dx, dy, distance, fx, fy, repulsingForce, node1, node2; 34474 34475 var nodes = this.body.nodes; 34476 var nodeIndices = this.physicsBody.physicsNodeIndices; 34477 var forces = this.physicsBody.forces; 34478 34479 // repulsing forces between nodes 34480 var nodeDistance = this.options.nodeDistance; 34481 34482 // approximation constants 34483 var a = -2 / 3 / nodeDistance; 34484 var b = 4 / 3; 34485 34486 // we loop from i over all but the last entree in the array 34487 // j loops from i+1 to the last. This way we do not double count any of the indices, nor i === j 34488 for (var i = 0; i < nodeIndices.length - 1; i++) { 34489 node1 = nodes[nodeIndices[i]]; 34490 for (var j = i + 1; j < nodeIndices.length; j++) { 34491 node2 = nodes[nodeIndices[j]]; 34492 34493 dx = node2.x - node1.x; 34494 dy = node2.y - node1.y; 34495 distance = Math.sqrt(dx * dx + dy * dy); 34496 34497 // same condition as BarnesHutSolver, making sure nodes are never 100% overlapping. 34498 if (distance === 0) { 34499 distance = 0.1 * Math.random(); 34500 dx = distance; 34501 } 34502 34503 if (distance < 2 * nodeDistance) { 34504 if (distance < 0.5 * nodeDistance) { 34505 repulsingForce = 1.0; 34506 } else { 34507 repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / nodeDistance - 1) * steepne
34507ss)) 34508 } 34509 repulsingForce = repulsingForce / distance; 34510 34511 fx = dx * repulsingForce; 34512 fy = dy * repulsingForce; 34513 34514 forces[node1.id].x -= fx; 34515 forces[node1.id].y -= fy; 34516 forces[node2.id].x += fx; 34517 forces[node2.id].y += fy; 34518 } 34519 } 34520 } 34521 } 34522 }]); 34523 34524 return RepulsionSolver; 34525 })(); 34526 34527 exports["default"] = RepulsionSolver; 34528 module.exports = exports["default"]; 34529 34530/***/ }, 34531/* 93 */ 34532/***/ function(module, exports) { 34533 34534 "use strict"; 34535 34536 Object.defineProperty(exports, "__esModule", { 34537 value: true 34538 }); 34539 34540 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 34541 34542 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 34543 34544 var HierarchicalRepulsionSolver = (function () { 34545 function HierarchicalRepulsionSolver(body, physicsBody, options) { 34546 _classCallCheck(this, HierarchicalRepulsionSolver); 34547 34548 this.body = body; 34549 this.physicsBody = physicsBody; 34550 this.setOptions(options); 34551 } 34552 34553 _createClass(HierarchicalRepulsionSolver, [{ 34554 key: "setOptions", 34555 value: function setOptions(options) { 34556 this.options = options; 34557 } 34558 }, { 34559 key: "solve", 34560 34561 /** 34562 * Calculate the forces the nodes apply on each other based on a repulsion field. 34563 * This field is linearly approximated. 34564 * 34565 * @private 34566 */ 34567 value: function solve() { 34568 var dx, dy, distance, fx, fy, repulsingForce, node1, node2, i, j; 34569 34570 var nodes = this.body.nodes; 34571 var nodeIndices = this.physicsBody.physicsNodeIndices; 34572 var forces = this.physicsBody.forces; 34573 34574 // repulsing forces between nodes 34575 var nodeDistance = this.options.nodeDistance; 34576 34577 // we loop from i over all but the last entree in the array 34578 // j loops from i+1 to the last. This way we do not double count any of the indices, nor i === j 34579 for (i = 0; i < nodeIndices.length - 1; i++) { 34580 node1 = nodes[nodeIndices[i]]; 34581 for (j = i + 1; j < nodeIndices.length; j++) { 34582 node2 = nodes[nodeIndices[j]]; 34583 34584 // nodes only affect nodes on their level 34585 if (node1.level === node2.level) { 34586 dx = node2.x - node1.x; 34587 dy = node2.y - node1.y; 34588 distance = Math.sqrt(dx * dx + dy * dy); 34589 34590 var steepness = 0.05; 34591 if (distance < nodeDistance) { 34592 repulsingForce = -Math.pow(steepness * distance, 2) + Math.pow(steepness * nodeDistance, 2); 34593 } else { 34594 repulsingForce = 0; 34595 } 34596 // normalize force with 34597 if (distance === 0) { 34598 distance = 0.01; 34599 } else { 34600 repulsingForce = repulsingForce / distance; 34601 } 34602 fx = dx * repulsingForce; 34603 fy = dy * repulsingForce; 34604 34605 forces[node1.id].x -= fx; 34606 forces[node1.id].y -= fy; 34607 forces[node2.id].x += fx; 34608 forces[node2.id].y += fy; 34609 } 34610 } 34611 } 34612 } 34613 }]); 34614 34615 return HierarchicalRepulsionSolver; 34616 })(); 34617 34618 exports["default"] = HierarchicalRepulsionSolver; 34619 module.exports = exports["default"]; 34620 34621/***/ }, 34622/* 94 */ 34623/***/ function(module, exports) { 34624 34625 "use strict"; 34626 34627 Object.defineProperty(exports, "__esModule", { 34628 value: true 34629 }); 34630 34631 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 34632 34633 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 34634 34635 var SpringSolver = (function () { 34636 function SpringSolver(body, physicsBody, options) { 34637 _classCallCheck(this, SpringSolver); 34638 34639 this.body = body; 34640 this.physicsBody = physicsBody; 34641 this.setOptions(options); 34642 } 34643
34644 _createClass(SpringSolver, [{ 34645 key: "setOptions", 34646 value: function setOptions(options) { 34647 this.options = options; 34648 } 34649 }, { 34650 key: "solve", 34651 34652 /** 34653 * This function calculates the springforces on the nodes, accounting for the support nodes. 34654 * 34655 * @private 34656 */ 34657 value: function solve() { 34658 var edgeLength = undefined, 34659 edge = undefined; 34660 var edgeIndices = this.physicsBody.physicsEdgeIndices; 34661 var edges = this.body.edges; 34662 var node1 = undefined, 34663 node2 = undefined, 34664 node3 = undefined; 34665 34666 // forces caused by the edges, modelled as springs 34667 for (var i = 0; i < edgeIndices.length; i++) { 34668 edge = edges[edgeIndices[i]]; 34669 if (edge.connected === true && edge.toId !== edge.fromId) { 34670 // only calculate forces if nodes are in the same sector 34671 if (this.body.nodes[edge.toId] !== undefined && this.body.nodes[edge.fromId] !== undefined) { 34672 if (edge.edgeType.via !== undefined) { 34673 edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length; 34674 node1 = edge.to; 34675 node2 = edge.edgeType.via; 34676 node3 = edge.from; 34677 34678 this._calculateSpringForce(node1, node2, 0.5 * edgeLength); 34679 this._calculateSpringForce(node2, node3, 0.5 * edgeLength); 34680 } else { 34681 // the * 1.5 is here so the edge looks as large as a smooth edge. It does not initially because the smooth edges use 34682 // the support nodes which exert a repulsive force on the to and from nodes, making the edge appear larger. 34683 edgeLength = edge.options.length === undefined ? this.options.springLength * 1.5 : edge.options.length; 34684 this._calculateSpringForce(edge.from, edge.to, edgeLength); 34685 } 34686 } 34687 } 34688 } 34689 } 34690 }, { 34691 key: "_calculateSpringForce", 34692 34693 /** 34694 * This is the code actually performing the calculation for the function above. 34695 * 34696 * @param node1 34697 * @param node2 34698 * @param edgeLength 34699 * @private 34700 */ 34701 value: function _calculateSpringForce(node1, node2, edgeLength) { 34702 var dx = node1.x - node2.x; 34703 var dy = node1.y - node2.y; 34704 var distance = Math.max(Math.sqrt(dx * dx + dy * dy), 0.01); 34705 34706 // the 1/distance is so the fx and fy can be calculated without sine or cosine. 34707 var springForce = this.options.springConstant * (edgeLength - distance) / distance; 34708
34709 var fx = dx * springForce; 34710 var fy = dy * springForce; 34711 34712 // handle the case where one node is not part of the physcis 34713 if (this.physicsBody.forces[node1.id] !== undefined) { 34714 this.physicsBody.forces[node1.id].x += fx; 34715 this.physicsBody.forces[node1.id].y += fy; 34716 } 34717 34718 if (this.physicsBody.forces[node2.id] !== undefined) { 34719 this.physicsBody.forces[node2.id].x -= fx; 34720 this.physicsBody.forces[node2.id].y -= fy; 34721 } 34722 } 34723 }]); 34724 34725 return SpringSolver; 34726 })(); 34727 34728 exports["default"] = SpringSolver; 34729 module.exports = exports["default"]; 34730 34731/***/ }, 34732/* 95 */ 34733/***/ function(module, exports) { 34734 34735 "use strict"; 34736 34737 Object.defineProperty(exports, "__esModule", { 34738 value: true 34739 }); 34740 34741 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 34742 34743 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 34744 34745 var HierarchicalSpringSolver = (function () { 34746 function HierarchicalSpringSolver(body, physicsBody, options) { 34747 _classCallCheck(this, HierarchicalSpringSolver); 34748 34749 this.body = body; 34750 this.physicsBody = physicsBody; 34751 this.setOptions(options); 34752 } 34753 34754 _createClass(HierarchicalSpringSolver, [{ 34755 key: "setOptions", 34756 value: function setOptions(options) { 34757 this.options = options; 34758 } 34759 }, { 34760 key: "solve", 34761 34762 /** 34763 * This function calculates the springforces on the nodes, accounting for the support nodes. 34764 * 34765 * @private 34766 */ 34767 value: function solve() { 34768 var edgeLength, edge; 34769 var dx, dy, fx, fy, springForce, distance; 34770 var edges = this.body.edges; 34771 var factor = 0.5; 34772 34773 var edgeIndices = this.physicsBody.physicsEdgeIndices; 34774 var nodeIndices = this.physicsBody.physicsNodeIndices; 34775 var forces = this.physicsBody.forces; 34776 34777 // initialize the spring force counters 34778 for (var i = 0; i < nodeIndices.length; i++) { 34779 var nodeId = nodeIndices[i]; 34780 forces[nodeId].springFx = 0; 34781 forces[nodeId].springFy = 0; 34782 } 34783 34784 // forces caused by the edges, modelled as springs 34785 for (var i = 0; i < edgeIndices.length; i++) { 34786 edge = edges[edgeIndices[i]]; 34787 if (edge.connected === true) { 34788 edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length; 34789 34790 dx = edge.from.x - edge.to.x; 34791 dy = edge.from.y - edge.to.y; 34792 distance = Math.sqrt(dx * dx + dy * dy); 34793 distance = distance === 0 ? 0.01 : distance; 34794 34795 // the 1/distance is so the fx and fy can be calculated without sine or cosine. 34796 springForce = this.options.springConstant * (edgeLength - distance) / distance; 34797
34798 fx = dx * springForce; 34799 fy = dy * springForce; 34800 34801 if (edge.to.level != edge.from.level) { 34802 if (forces[edge.toId] !== undefined) { 34803 forces[edge.toId].springFx -= fx; 34804 forces[edge.toId].springFy -= fy; 34805 } 34806 if (forces[edge.fromId] !== undefined) { 34807 forces[edge.fromId].springFx += fx; 34808 forces[edge.fromId].springFy += fy; 34809 } 34810 } else { 34811 if (forces[edge.toId] !== undefined) { 34812 forces[edge.toId].x -= factor * fx; 34813 forces[edge.toId].y -= factor * fy; 34814 } 34815 if (forces[edge.fromId] !== undefined) { 34816 forces[edge.fromId].x += factor * fx; 34817 forces[edge.fromId].y += factor * fy; 34818 } 34819 } 34820 } 34821 } 34822 34823 // normalize spring forces 34824 var springForce = 1; 34825 var springFx, springFy; 34826 for (var i = 0; i < nodeIndices.length; i++) { 34827 var nodeId = nodeIndices[i]; 34828 springFx = Math.min(springForce, Math.max(-springForce, forces[nodeId].springFx)); 34829 springFy = Math.min(springForce, Math.max(-springForce, forces[nodeId].springFy)); 34830 34831 forces[nodeId].x += springFx; 34832 forces[nodeId].y += springFy; 34833 } 34834 34835 // retain energy balance 34836 var totalFx = 0; 34837 var totalFy = 0; 34838 for (var i = 0; i < nodeIndices.length; i++) { 34839 var nodeId = nodeIndices[i]; 34840 totalFx += forces[nodeId].x; 34841 totalFy += forces[nodeId].y; 34842 } 34843 var correctionFx = totalFx / nodeIndices.length; 34844 var correctionFy = totalFy / nodeIndices.length; 34845 34846 for (var i = 0; i < nodeIndices.length; i++) { 34847 var nodeId = nodeIndices[i]; 34848 forces[nodeId].x -= correctionFx; 34849 forces[nodeId].y -= correctionFy; 34850 } 34851 } 34852 }]); 34853 34854 return HierarchicalSpringSolver; 34855 })(); 34856 34857 exports["default"] = HierarchicalSpringSolver; 34858 module.exports = exports["default"]; 34859 34860/***/ }, 34861/* 96 */ 34862/***/ function(module, exports) { 34863 34864 "use strict"; 34865 34866 Object.defineProperty(exports, "__esModule", { 34867 value: true 34868 }); 34869 34870 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 34871 34872 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 34873 34874 var CentralGravitySolver = (function () { 34875 function CentralGravitySolver(body, physicsBody, options) { 34876 _classCallCheck(this, CentralGravitySolver); 34877 34878 this.body = body; 34879 this.physicsBody = physicsBody; 34880 this.setOptions(options); 34881 } 34882 34883 _createClass(CentralGravitySolver, [{ 34884 key: "setOptions", 34885 value: function setOptions(options) { 34886 this.options = options; 34887 } 34888 }, { 34889 key: "solve", 34890 value: function solve() { 34891 var dx = undefined, 34892 dy = undefined, 34893 distance = undefined, 34894 node = undefined; 34895 var nodes = this.body.nodes; 34896 var nodeIndices = this.physicsBody.physicsNodeIndices; 34897 var forces = this.physicsBody.forces; 34898 34899 for (var i = 0; i < nodeIndices.length; i++) { 34900 var nodeId = nodeIndices[i]; 34901 node = nodes[nodeId]; 34902 dx = -node.x; 34903 dy = -node.y; 34904 distance = Math.sqrt(dx * dx + dy * dy); 34905 34906 this._calculateForces(distance, dx, dy, forces, node); 34907 } 34908 } 34909 }, { 34910 key: "_calculateForces", 34911 34912 /** 34913 * Calculate the forces based on the distance. 34914 * @private 34915 */ 34916 value: function _calculateForces(distance, dx, dy, forces, node) { 34917 var gravityForce = distance === 0 ? 0 : this.options.centralGravity / distance; 34918 forces[node.id].x = dx * gravityForce; 34919 forces[node.id].y = dy * gravityForce; 34920 } 34921 }]); 34922 34923 return CentralGravitySolver; 34924 })(); 34925 34926 exports["default"] = CentralGravitySolver; 34927 module.exports = exports["default"]; 34928 34929/***/ }, 34930/* 97 */ 34931/***/ function(module, exports, __webpack_require__) { 34932 34933 "use strict"; 34934 34935 Object.defineProperty(exports, "__esModule", { 34936 value: true 34937 }); 34938 34939 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 34940 34941 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 34942 34943 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 34944 34945 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 34946 34947 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
34947onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 34948 34949 var _BarnesHutSolver2 = __webpack_require__(91); 34950 34951 var _BarnesHutSolver3 = _interopRequireDefault(_BarnesHutSolver2); 34952 34953 var ForceAtlas2BasedRepulsionSolver = (function (_BarnesHutSolver) { 34954 _inherits(ForceAtlas2BasedRepulsionSolver, _BarnesHutSolver); 34955 34956 function ForceAtlas2BasedRepulsionSolver(body, physicsBody, options) { 34957 _classCallCheck(this, ForceAtlas2BasedRepulsionSolver); 34958 34959 _get(Object.getPrototypeOf(ForceAtlas2BasedRepulsionSolver.prototype), "constructor", this).call(this, body, physicsBody, options); 34960 } 34961 34962 _createClass(ForceAtlas2BasedRepulsionSolver, [{ 34963 key: "_calculateForces", 34964 34965 /** 34966 * Calculate the forces based on the distance. 34967 * 34968 * @param distance 34969 * @param dx 34970 * @param dy 34971 * @param node 34972 * @param parentBranch 34973 * @private 34974 */ 34975 value: function _calculateForces(distance, dx, dy, node, parentBranch) { 34976 if (distance === 0) { 34977 distance = 0.1 * Math.random(); 34978 dx = distance; 34979 } 34980 34981 if (this.overlapAvoidanceFactor < 1) { 34982 distance = Math.max(0.1 + this.overlapAvoidanceFactor * node.shape.radius, distance - node.shape.radius); 34983 } 34984 34985 var degree = node.edges.length + 1; 34986 // the dividing by the distance cubed instead of squared allows us to get the fx and fy components without sines and cosines 34987 // it is shorthand for gravityforce with distance squared and fx = dx/distance * gravityForce 34988 var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass * degree / Math.pow(distance, 2); 34989 var fx = dx * gravityForce; 34990 var fy = dy * gravityForce; 34991 34992 this.physicsBody.forces[node.id].x += fx; 34993 this.physicsBody.forces[node.id].y += fy; 34994 } 34995 }]); 34996 34997 return ForceAtlas2BasedRepulsionSolver; 34998 })(_BarnesHutSolver3["default"]); 34999 35000 exports["default"] = ForceAtlas2BasedRepulsionSolver; 35001 module.exports = exports["default"]; 35002 35003/***/ }, 35004/* 98 */ 35005/***/ function(module, exports, __webpack_require__) { 35006 35007 "use strict"; 35008 35009 Object.defineProperty(exports, "__esModule", { 35010 value: true 35011 }); 35012 35013 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 35014 35015 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 35016 35017 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 35018 35019 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 35020 35021 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 35022 35023 var _CentralGravitySolver2 = __webpack_require__(96); 35024 35025 var _CentralGravitySolver3 = _interopRequireDefault(_CentralGravitySolver2); 35026 35027 var ForceAtlas2BasedCentralGravitySolver = (function (_CentralGravitySolver) { 35028 _inherits(ForceAtlas2BasedCentralGravitySolver, _CentralGravitySolver); 35029 35030 function ForceAtlas2BasedCentralGravitySolver(body, physicsBody, options) { 35031 _classCallCheck(this, ForceAtlas2BasedCentralGravitySolver); 35032 35033 _get(Object.getPrototypeOf(ForceAtlas2BasedCentralGravitySolver.prototype), "constructor", this).call(this, body, physicsBody, options); 35034 } 35035 35036 _createClass(ForceAtlas2BasedCentralGravitySolver, [{ 35037 key: "_calculateForces", 35038 35039 /** 35040 * Calculate the forces based on the distance. 35041 * @private 35042 */ 35043 value: function _calculateForces(distance, dx, dy, forces, node) { 35044 if (distance > 0) { 35045 var degree = node.edges.length + 1; 35046 var gravityForce = this.options.centralGravity * degree * node.options.mass; 35047 forces[node.id].x = dx * gravityForce; 35048 forces[node.id].y = dy * gravityForce; 35049 } 35050 } 35051 }]); 35052 35053 return ForceAtlas2BasedCentralGravitySolver; 35054 })(_CentralGravitySolver3["default"]); 35055 35056 exports["default"] = ForceAtlas2BasedCentralGravitySolver; 35057 module.exports = exports["default"]; 35058 35059/***/ }, 35060/* 99 */ 35061/***/ function(module, exports, __webpack_require__) { 35062 35063 'use strict'; 35064 35065 Object.defineProperty(exports, '__esModule', { 35066 value: true 35067 }); 35068 35069 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 35070 35071 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 35072 35073 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 35074 35075 var _componentsNodesCluster = __webpack_require__(100); 35076 35077 var _componentsNodesCluster2 = _interopRequireDefault(_componentsNodesCluster); 35078 35079 var util = __webpack_require__(1); 35080 35081 var ClusterEngine = (function () { 35082 function ClusterEngine(body) { 35083 var _this = this; 35084 35085 _classCallCheck(this, ClusterEngine); 35086 35087 this.body = body; 35088 this.clusteredNodes = {}; 35089 35090 this.options = {}; 35091 this.defaultOptions = {}; 35092 util.extend(this.options, this.defaultOptions); 35093 35094 this.body.emitter.on('_resetData', function () { 35095 _this.clusteredNodes = {}; 35096 }); 35097 } 35098 35099 _createClass(ClusterEngine, [{ 35100 key: 'setOptions', 35101 value: function setOptions(options) { 35102 if (options !== undefined) {} 35103 } 35104 }, {
35105 key: 'clusterByHubsize', 35106 35107 /** 35108 * 35109 * @param hubsize 35110 * @param options 35111 */ 35112 value: function clusterByHubsize(hubsize, options) { 35113 if (hubsize === undefined) { 35114 hubsize = this._getHubSize(); 35115 } else if (typeof hubsize === 'object') { 35116 options = this._checkOptions(hubsize); 35117 hubsize = this._getHubSize(); 35118 } 35119 35120 var nodesToCluster = []; 35121 for (var i = 0; i < this.body.nodeIndices.length; i++) { 35122 var node = this.body.nodes[this.body.nodeIndices[i]]; 35123 if (node.edges.length >= hubsize) { 35124 nodesToCluster.push(node.id); 35125 } 35126 } 35127 35128 for (var i = 0; i < nodesToCluster.length; i++) { 35129 this.clusterByConnection(nodesToCluster[i], options, true); 35130 } 35131 35132 this.body.emitter.emit('_dataChanged'); 35133 } 35134 }, { 35135 key: 'cluster', 35136 35137 /** 35138 * loop over all nodes, check if they adhere to the condition and cluster if needed. 35139 * @param options 35140 * @param refreshData 35141 */ 35142 value: function cluster() { 35143 var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 35144 var refreshData = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; 35145 35146 if (options.joinCondition === undefined) { 35147 throw new Error('Cannot call clusterByNodeData without a joinCondition function in the options.'); 35148 } 35149 35150 // check if the options object is fine, append if needed 35151 options = this._checkOptions(options); 35152 35153 var childNodesObj = {}; 35154 var childEdgesObj = {}; 35155 35156 // collect the nodes that will be in the cluster 35157 for (var i = 0; i < this.body.nodeIndices.length; i++) { 35158 var nodeId = this.body.nodeIndices[i]; 35159 var node = this.body.nodes[nodeId]; 35160 var clonedOptions = this._cloneOptions(node); 35161 if (options.joinCondition(clonedOptions) === true) { 35162 childNodesObj[nodeId] = this.body.nodes[nodeId]; 35163 35164 // collect the nodes that will be in the cluster 35165 for (var _i = 0; _i < node.edges.length; _i++) { 35166 var edge = node.edges[_i]; 35167 if (edge.hiddenByCluster !== true) { 35168 childEdgesObj[edge.id] = edge; 35169 } 35170 } 35171 } 35172 } 35173 35174 this._cluster(childNodesObj, childEdgesObj, options, refreshData); 35175 } 35176 }, { 35177 key: 'clusterByEdgeCount', 35178 35179 /** 35180 * Cluster all nodes in the network that have only X edges 35181 * @param edgeCount 35182 * @param options 35183 * @param refreshData 35184 */ 35185 value: function clusterByEdgeCount(edgeCount, options) { 35186 var refreshData = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2]; 35187 35188 options = this._checkOptions(options); 35189 var clusters = []; 35190 var usedNodes = {}; 35191 var edge = undefined, 35192 edges = undefined, 35193 node = undefined, 35194 nodeId = undefined, 35195 relevantEdgeCount = undefined; 35196 // collect the nodes that will be in the cluster 35197 for (var i = 0; i < this.body.nodeIndices.length; i++) { 35198 var childNodesObj = {}; 35199 var childEdgesObj = {}; 35200 nodeId = this.body.nodeIndices[i]; 35201 35202 // if this node is already used in another cluster this session, we do not have to re-evaluate it. 35203 if (usedNodes[nodeId] === undefined) { 35204 relevantEdgeCount = 0; 35205 node = this.body.nodes[nodeId]; 35206 edges = []; 35207 for (var j = 0; j < node.edges.length; j++) { 35208 edge = node.edges[j]; 35209 if (edge.hiddenByCluster !== true) { 35210 if (edge.toId !== edge.fromId) { 35211 relevantEdgeCount++; 35212 } 35213 edges.push(edge); 35214 } 35215 } 35216 35217 // this node qualifies, we collect its neighbours to start the clustering process. 35218 if (relevantEdgeCount === edgeCount) { 35219 var gatheringSuccessful = true; 35220 for (var j = 0; j < edges.length; j++) { 35221 edge = edges[j]; 35222 var childNodeId = this._getConnectedId(edge, nodeId); 35223 // add the nodes to the list by the join condition. 35224 if (options.joinCondition === undefined) { 35225 childEdgesObj[edge.id] = edge; 35226 childNodesObj[nodeId] = this.body.nodes[nodeId]; 35227 childNodesObj[childNodeId] = this.body.nodes[childNodeId]; 35228 usedNodes[nodeId] = true; 35229 } else { 35230 var clonedOptions = this._cloneOptions(this.body.nodes[nodeId]); 35231 if (options.joinCondition(clonedOptions) === true) { 35232 childEdgesObj[edge.id] = edge; 35233 childNodesObj[nodeId] = this.body.nodes[nodeId]; 35234 usedNodes[nodeId] = true; 35235 } else { 35236 // this node does not qualify after all. 35237 gatheringSuccessful = false;
35238 break; 35239 } 35240 } 35241 } 35242 35243 // add to the cluster queue 35244 if (Object.keys(childNodesObj).length > 0 && Object.keys(childEdgesObj).length > 0 && gatheringSuccessful === true) { 35245 clusters.push({ nodes: childNodesObj, edges: childEdgesObj }); 35246 } 35247 } 35248 } 35249 } 35250 35251 for (var i = 0; i < clusters.length; i++) { 35252 this._cluster(clusters[i].nodes, clusters[i].edges, options, false); 35253 } 35254 35255 if (refreshData === true) { 35256 this.body.emitter.emit('_dataChanged'); 35257 } 35258 } 35259 }, { 35260 key: 'clusterOutliers', 35261 35262 /** 35263 * Cluster all nodes in the network that have only 1 edge 35264 * @param options 35265 * @param refreshData 35266 */ 35267 value: function clusterOutliers(options) { 35268 var refreshData = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; 35269 35270 this.clusterByEdgeCount(1, options, refreshData); 35271 } 35272 }, { 35273 key: 'clusterBridges', 35274 35275 /** 35276 * Cluster all nodes in the network that have only 2 edge 35277 * @param options 35278 * @param refreshData 35279 */ 35280 value: function clusterBridges(options) { 35281 var refreshData = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; 35282 35283 this.clusterByEdgeCount(2, options, refreshData); 35284 } 35285 }, { 35286 key: 'clusterByConnection', 35287 35288 /** 35289 * suck all connected nodes of a node into the node. 35290 * @param nodeId 35291 * @param options 35292 * @param refreshData 35293 */ 35294 value: function clusterByConnection(nodeId, options) { 35295 var refreshData = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2]; 35296 35297 // kill conditions 35298 if (nodeId === undefined) { 35299 throw new Error('No nodeId supplied to clusterByConnection!'); 35300 } 35301 if (this.body.nodes[nodeId] === undefined) { 35302 throw new Error('The nodeId given to clusterByConnection does not exist!'); 35303 } 35304 35305 var node = this.body.nodes[nodeId]; 35306 options = this._checkOptions(options, node); 35307 if (options.clusterNodeProperties.x === undefined) { 35308 options.clusterNodeProperties.x = node.x; 35309 } 35310 if (options.clusterNodeProperties.y === undefined) { 35311 options.clusterNodeProperties.y = node.y; 35312 } 35313 if (options.clusterNodeProperties.fixed === undefined) { 35314 options.clusterNodeProperties.fixed = {}; 35315 options.clusterNodeProperties.fixed.x = node.options.fixed.x; 35316 options.clusterNodeProperties.fixed.y = node.options.fixed.y; 35317 } 35318 35319 var childNodesObj = {}; 35320 var childEdgesObj = {}; 35321 var parentNodeId = node.id; 35322 var parentClonedOptions = this._cloneOptions(node); 35323 childNodesObj[parentNodeId] = node; 35324 35325 // collect the nodes that will be in the cluster 35326 for (var i = 0; i < node.edges.length; i++) { 35327 var edge = node.edges[i]; 35328 if (edge.hiddenByCluster !== true) { 35329 var childNodeId = this._getConnectedId(edge, parentNodeId); 35330 35331 // if the child node is not in a cluster (may not be needed now with the edge.hiddenByCluster check) 35332 if (this.clusteredNodes[childNodeId] === undefined) { 35333 if (childNodeId !== parentNodeId) { 35334 if (options.joinCondition === undefined) { 35335 childEdgesObj[edge.id] = edge; 35336 childNodesObj[childNodeId] = this.body.nodes[childNodeId]; 35337 } else { 35338 // clone the options and insert some additional parameters that could be interesting. 35339 var childClonedOptions = this._cloneOptions(this.body.nodes[childNodeId]); 35340 if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) { 35341 childEdgesObj[edge.id] = edge; 35342 childNodesObj[childNodeId] = this.body.nodes[childNodeId]; 35343 } 35344 } 35345 } else { 35346 // swallow the edge if it is self-referencing. 35347 childEdgesObj[edge.id] = edge; 35348 } 35349 } 35350 } 35351 } 35352 35353 this._cluster(childNodesObj, childEdgesObj, options, refreshData); 35354 } 35355 }, {
35356 key: '_cloneOptions', 35357 35358 /** 35359 * This returns a clone of the options or options of the edge or node to be used for construction of new edges or check functions for new nodes. 35360 * @param objId 35361 * @param type 35362 * @returns {{}} 35363 * @private 35364 */ 35365 value: function _cloneOptions(item, type) { 35366 var clonedOptions = {}; 35367 if (type === undefined || type === 'node') { 35368 util.deepExtend(clonedOptions, item.options, true); 35369 clonedOptions.x = item.x; 35370 clonedOptions.y = item.y; 35371 clonedOptions.amountOfConnections = item.edges.length; 35372 } else { 35373 util.deepExtend(clonedOptions, item.options, true); 35374 } 35375 return clonedOptions; 35376 } 35377 }, { 35378 key: '_createClusterEdges', 35379 35380 /** 35381 * This function creates the edges that will be attached to the cluster 35382 * It looks for edges that are connected to the nodes from the "outside' of the cluster. 35383 * 35384 * @param childNodesObj 35385 * @param newEdges 35386 * @param options 35387 * @private 35388 */ 35389 value: function _createClusterEdges(childNodesObj, childEdgesObj, clusterNodeProperties, clusterEdgeProperties) { 35390 var edge = undefined, 35391 childNodeId = undefined, 35392 childNode = undefined, 35393 toId = undefined, 35394 fromId = undefined, 35395 otherNodeId = undefined; 35396 35397 // loop over all child nodes and their edges to find edges going out of the cluster 35398 // these edges will be replaced by clusterEdges. 35399 var childKeys = Object.keys(childNodesObj); 35400 var createEdges = []; 35401 for (var i = 0; i < childKeys.length; i++) { 35402 childNodeId = childKeys[i]; 35403 childNode = childNodesObj[childNodeId]; 35404 35405 // construct new edges from the cluster to others 35406 for (var j = 0; j < childNode.edges.length; j++) { 35407 edge = childNode.edges[j]; 35408 // we only handle edges that are visible to the system, not the disabled ones from the clustering process. 35409 if (edge.hiddenByCluster !== true) { 35410 // self-referencing edges will be added to the "hidden" list 35411 if (edge.toId == edge.fromId) { 35412 childEdgesObj[edge.id] = edge; 35413 } else { 35414 // set up the from and to. 35415 if (edge.toId == childNodeId) { 35416 // this is a double equals because ints and strings can be interchanged here. 35417 toId = clusterNodeProperties.id; 35418 fromId = edge.fromId; 35419 otherNodeId = fromId; 35420 } else { 35421 toId = edge.toId; 35422 fromId = clusterNodeProperties.id; 35423 otherNodeId = toId; 35424 } 35425 } 35426 35427 // Only edges from the cluster outwards are being replaced. 35428 if (childNodesObj[otherNodeId] === undefined) { 35429 createEdges.push({ edge: edge, fromId: fromId, toId: toId }); 35430 } 35431 } 35432 } 35433 } 35434 35435 // here we actually create the replacement edges. We could not do this in the loop above as the creation process 35436 // would add an edge to the edges array we are iterating over. 35437 for (var j = 0; j < createEdges.length; j++) { 35438 var _edge = createEdges[j].edge; 35439 // copy the options of the edge we will replace 35440 var clonedOptions = this._cloneOptions(_edge, 'edge'); 35441 // make sure the properties of clusterEdges are superimposed on it 35442 util.deepExtend(clonedOptions, clusterEdgeProperties); 35443 35444 // set up the edge 35445 clonedOptions.from = createEdges[j].fromId; 35446 clonedOptions.to = createEdges[j].toId; 35447 clonedOptions.id = 'clusterEdge:' + util.randomUUID(); 35448 //clonedOptions.id = '(cf: ' + createEdges[j].fromId + " to: " + createEdges[j].toId + ")" + Math.random(); 35449 35450 // create the edge and give a reference to the one it replaced. 35451 var newEdge = this.body.functions.createEdge(clonedOptions); 35452 newEdge.clusteringEdgeReplacingId = _edge.id; 35453 35454 // connect the edge. 35455 this.body.edges[newEdge.id] = newEdge; 35456 newEdge.connect(); 35457 35458 // hide the replaced edge 35459 _edge.setOptions({ physics: false, hidden: true }); 35460 _edge.hiddenByCluster = true; 35461 } 35462 } 35463 }, {
35464 key: '_checkOptions', 35465 35466 /** 35467 * This function checks the options that can be supplied to the different cluster functions 35468 * for certain fields and inserts defaults if needed 35469 * @param options 35470 * @returns {*} 35471 * @private 35472 */ 35473 value: function _checkOptions() { 35474 var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 35475 35476 if (options.clusterEdgeProperties === undefined) { 35477 options.clusterEdgeProperties = {}; 35478 } 35479 if (options.clusterNodeProperties === undefined) { 35480 options.clusterNodeProperties = {}; 35481 } 35482 35483 return options; 35484 } 35485 }, { 35486 key: '_cluster', 35487 35488 /** 35489 * 35490 * @param {Object} childNodesObj | object with node objects, id as keys, same as childNodes except it also contains a source node 35491 * @param {Object} childEdgesObj | object with edge objects, id as keys 35492 * @param {Array} options | object with {clusterNodeProperties, clusterEdgeProperties, processProperties} 35493 * @param {Boolean} refreshData | when true, do not wrap up 35494 * @private 35495 */ 35496 value: function _cluster(childNodesObj, childEdgesObj, options) { 35497 var refreshData = arguments.length <= 3 || arguments[3] === undefined ? true : arguments[3]; 35498 35499 // kill condition: no children so can't cluster or only one node in the cluster, dont bother 35500 if (Object.keys(childNodesObj).length < 2) { 35501 return; 35502 } 35503 35504 // check if this cluster call is not trying to cluster anything that is in another cluster. 35505 for (var nodeId in childNodesObj) { 35506 if (childNodesObj.hasOwnProperty(nodeId)) { 35507 if (this.clusteredNodes[nodeId] !== undefined) { 35508 return; 35509 } 35510 } 35511 } 35512 35513 var clusterNodeProperties = util.deepExtend({}, options.clusterNodeProperties); 35514 35515 // construct the clusterNodeProperties 35516 if (options.processProperties !== undefined) { 35517 // get the childNode options 35518 var childNodesOptions = []; 35519 for (var nodeId in childNodesObj) { 35520 if (childNodesObj.hasOwnProperty(nodeId)) { 35521 var clonedOptions = this._cloneOptions(childNodesObj[nodeId]); 35522 childNodesOptions.push(clonedOptions); 35523 } 35524 } 35525 35526 // get clusterproperties based on childNodes 35527 var childEdgesOptions = []; 35528 for (var edgeId in childEdgesObj) { 35529 if (childEdgesObj.hasOwnProperty(edgeId)) { 35530 // these cluster edges will be removed on creation of the cluster. 35531 if (edgeId.substr(0, 12) !== 'clusterEdge:') { 35532 var clonedOptions = this._cloneOptions(childEdgesObj[edgeId], 'edge'); 35533 childEdgesOptions.push(clonedOptions); 35534 } 35535 } 35536 } 35537 35538 clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions); 35539 if (!clusterNodeProperties) { 35540 throw new Error('The processProperties function does not return properties!'); 35541 } 35542 } 35543 35544 // check if we have an unique id; 35545 if (clusterNodeProperties.id === undefined) { 35546 clusterNodeProperties.id = 'cluster:' + util.randomUUID(); 35547 } 35548 var clusterId = clusterNodeProperties.id; 35549 35550 if (clusterNodeProperties.label === undefined) { 35551 clusterNodeProperties.label = 'cluster'; 35552 } 35553 35554 // give the clusterNode a postion if it does not have one. 35555 var pos = undefined; 35556 if (clusterNodeProperties.x === undefined) { 35557 pos = this._getClusterPosition(childNodesObj); 35558 clusterNodeProperties.x = pos.x; 35559 } 35560 if (clusterNodeProperties.y === undefined) { 35561 if (pos === undefined) { 35562 pos = this._getClusterPosition(childNodesObj); 35563 } 35564 clusterNodeProperties.y = pos.y; 35565 } 35566 35567 // force the ID to remain the same 35568 clusterNodeProperties.id = clusterId; 35569 35570 // create the clusterNode 35571 var clusterNode = this.body.functions.createNode(clusterNodeProperties, _componentsNodesCluster2['default']); 35572 clusterNode.isCluster = true; 35573 clusterNode.containedNodes = childNodesObj; 35574 clusterNode.containedEdges = childEdgesObj; 35575 // cache a copy from the cluster edge properties if we have to reconnect others later on 35576 clusterNode.clusterEdgeProperties = options.clusterEdgeProperties; 35577 35578 // finally put the cluster node into global 35579 this.body.nodes[clusterNodeProperties.id] = clusterNode; 35580 35581 // create the new edges that will connect to the cluster, all self-referencing edges will be added to childEdgesObject here. 35582 this._createClusterEdges(childNodesObj, childEdgesObj, clusterNodeProperties, options.clusterEdgeProperties); 35583 35584 // disable the childEdges 35585 for (var edgeId in childEdgesObj) { 35586 if (childEdgesObj.hasOwnProperty(edgeId)) { 35587 if (this.body.edges[edgeId] !== undefined) { 35588 var edge = this.body.edges[edgeId]; 35589 edge.setOptions({ physics: false, hidden: true }); 35590 edge.hiddenByCluster = true; 35591 } 35592 } 35593 } 35594 35595 // disable the childNodes 35596 for (var nodeId in childNodesObj) { 35597 if (childNodesObj.hasOwnProperty(nodeId)) { 35598 this.clusteredNodes[nodeId] = { clusterId: clusterNodeProperties.id, node: this.body.nodes[nodeId] }; 35599 this.body.nodes[nodeId].setOptions({ hidden: true, physics: false }); 35600 } 35601 } 35602 35603 // set ID to undefined so no duplicates arise 35604 clusterNodeProperties.id = undefined; 35605 35606 // wrap up 35607 if (refreshData === true) { 35608 this.body.emitter.emit('_dataChanged'); 35609 } 35610 } 35611 }, {
35612 key: 'isCluster', 35613 35614 /** 35615 * Check if a node is a cluster. 35616 * @param nodeId 35617 * @returns {*} 35618 */ 35619 value: function isCluster(nodeId) { 35620 if (this.body.nodes[nodeId] !== undefined) { 35621 return this.body.nodes[nodeId].isCluster === true; 35622 } else { 35623 console.log('Node does not exist.'); 35624 return false; 35625 } 35626 } 35627 }, { 35628 key: '_getClusterPosition', 35629 35630 /** 35631 * get the position of the cluster node based on what's inside 35632 * @param {object} childNodesObj | object with node objects, id as keys 35633 * @returns {{x: number, y: number}} 35634 * @private 35635 */ 35636 value: function _getClusterPosition(childNodesObj) { 35637 var childKeys = Object.keys(childNodesObj); 35638 var minX = childNodesObj[childKeys[0]].x; 35639 var maxX = childNodesObj[childKeys[0]].x; 35640 var minY = childNodesObj[childKeys[0]].y; 35641 var maxY = childNodesObj[childKeys[0]].y; 35642 var node = undefined; 35643 for (var i = 1; i < childKeys.length; i++) { 35644 node = childNodesObj[childKeys[i]]; 35645 minX = node.x < minX ? node.x : minX; 35646 maxX = node.x >
35646 maxX ? node.x : maxX; 35647 minY = node.y < minY ? node.y : minY; 35648 maxY = node.y > maxY ? node.y : maxY; 35649 } 35650 35651 return { x: 0.5 * (minX + maxX), y: 0.5 * (minY + maxY) }; 35652 } 35653 }, { 35654 key: 'openCluster', 35655 35656 /** 35657 * Open a cluster by calling this function. 35658 * @param {String} clusterNodeId | the ID of the cluster node 35659 * @param {Boolean} refreshData | wrap up afterwards if not true 35660 */ 35661 value: function openCluster(clusterNodeId, options) { 35662 var refreshData = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2]; 35663 35664 // kill conditions 35665 if (clusterNodeId === undefined) { 35666 throw new Error('No clusterNodeId supplied to openCluster.'); 35667 } 35668 if (this.body.nodes[clusterNodeId] === undefined) { 35669 throw new Error('The clusterNodeId supplied to openCluster does not exist.'); 35670 } 35671 if (this.body.nodes[clusterNodeId].containedNodes === undefined) { 35672 console.log('The node:' + clusterNodeId + ' is not a cluster.'); 35673 return; 35674 } 35675 var clusterNode = this.body.nodes[clusterNodeId]; 35676 var containedNodes = clusterNode.containedNodes; 35677 var containedEdges = clusterNode.containedEdges; 35678 35679 // allow the user to position the nodes after release. 35680 if (options !== undefined && options.releaseFunction !== undefined && typeof options.releaseFunction === 'function') { 35681 var positions = {}; 35682 var clusterPosition = { x: clusterNode.x, y: clusterNode.y }; 35683 for (var nodeId in containedNodes) { 35684 if (containedNodes.hasOwnProperty(nodeId)) { 35685 var containedNode = this.body.nodes[nodeId]; 35686 positions[nodeId] = { x: containedNode.x, y: containedNode.y }; 35687 } 35688 } 35689 var newPositions = options.releaseFunction(clusterPosition, positions); 35690 35691 for (var nodeId in containedNodes) { 35692 if (containedNodes.hasOwnProperty(nodeId)) { 35693 var containedNode = this.body.nodes[nodeId]; 35694 if (newPositions[nodeId] !== undefined) { 35695 containedNode.x = newPositions[nodeId].x === undefined ? clusterNode.x : newPositions[nodeId].x; 35696 containedNode.y = newPositions[nodeId].y === undefined ? clusterNode.y : newPositions[nodeId].y; 35697 } 35698 } 35699 } 35700 } else { 35701 // copy the position from the cluster 35702 for (var nodeId in containedNodes) { 35703 if (containedNodes.hasOwnProperty(nodeId)) { 35704 var containedNode = this.body.nodes[nodeId]; 35705 containedNode = containedNodes[nodeId]; 35706 // inherit position 35707 containedNode.x = clusterNode.x; 35708 containedNode.y = clusterNode.y; 35709 } 35710 } 35711 } 35712 35713 // release nodes 35714 for (var nodeId in containedNodes) { 35715 if (containedNodes.hasOwnProperty(nodeId)) { 35716 var containedNode = this.body.nodes[nodeId]; 35717 35718 // inherit speed 35719 containedNode.vx = clusterNode.vx; 35720 containedNode.vy = clusterNode.vy; 35721 35722 // we use these methods to avoid reinstantiating the shape, which happens with setOptions. 35723 containedNode.setOptions({ hidden: false, physics: true }); 35724 35725 delete this.clusteredNodes[nodeId]; 35726 } 35727 } 35728 35729 // copy the clusterNode edges because we cannot iterate over an object that we add or remove from. 35730 var edgesToBeDeleted = []; 35731 for (var i = 0; i < clusterNode.edges.length; i++) { 35732 edgesToBeDeleted.push(clusterNode.edges[i]); 35733 } 35734 35735 // actually handling the deleting. 35736 for (var i = 0; i < edgesToBeDeleted.length; i++) { 35737 var edge = edgesToBeDeleted[i]; 35738 35739 var otherNodeId = this._getConnectedId(edge, clusterNodeId); 35740 // if the other node is in another cluster, we transfer ownership of this edge to the other cluster 35741 if (this.clusteredNodes[otherNodeId] !== undefined) { 35742 // transfer ownership: 35743 var otherCluster = this.body.nodes[this.clusteredNodes[otherNodeId].clusterId]; 35744 var transferEdge = this.body.edges[edge.clusteringEdgeReplacingId]; 35745 if (transferEdge !== undefined) { 35746 otherCluster.containedEdges[transferEdge.id] = transferEdge; 35747 35748 // delete local reference 35749 delete containedEdges[transferEdge.id]; 35750 35751 // create new cluster edge from the otherCluster: 35752 // get to and from 35753 var fromId = transferEdge.fromId; 35754 var toId = transferEdge.toId; 35755 if (transferEdge.toId == otherNodeId) { 35756 toId = this.clusteredNodes[otherNodeId].clusterId; 35757 } else { 35758 fromId = this.clusteredNodes[otherNodeId].clusterId; 35759 } 35760 35761 // clone the options and apply the cluster options to them 35762 var clonedOptions = this._cloneOptions(transferEdge, 'edge'); 35763 util.deepExtend(clonedOptions, otherCluster.clusterEdgeProperties); 35764 35765 // apply the edge specific options to it. 35766 var id = 'clusterEdge:' + util.randomUUID(); 35767 util.deepExtend(clonedOptions, { from: fromId, to: toId, hidden: false, physics: true, id: id }); 35768 35769 // create it 35770 var newEdge = this.body.functions.createEdge(clonedOptions); 35771 newEdge.clusteringEdgeReplacingId = transferEdge.id; 35772 this.body.edges[id] = newEdge; 35773 this.body.edges[id].connect(); 35774 } 35775 } else { 35776 var replacedEdge = this.body.edges[edge.clusteringEdgeReplacingId]; 35777 if (replacedEdge !== undefined) { 35778 replacedEdge.setOptions({ physics: true, hidden: false }); 35779 replacedEdge.hiddenByCluster = false;
35780 } 35781 } 35782 edge.cleanup(); 35783 // this removes the edge from node.edges, which is why edgeIds is formed 35784 edge.disconnect(); 35785 delete this.body.edges[edge.id]; 35786 } 35787 35788 // handle the releasing of the edges 35789 for (var edgeId in containedEdges) { 35790 if (containedEdges.hasOwnProperty(edgeId)) { 35791 var edge = containedEdges[edgeId]; 35792 edge.setOptions({ physics: true, hidden: false }); 35793 edge.hiddenByCluster = undefined; 35794 delete edge.hiddenByCluster; 35795 } 35796 } 35797 35798 // remove clusterNode 35799 delete this.body.nodes[clusterNodeId]; 35800 35801 if (refreshData === true) { 35802 this.body.emitter.emit('_dataChanged'); 35803 } 35804 } 35805 }, { 35806 key: 'getNodesInCluster', 35807 value: function getNodesInCluster(clusterId) { 35808 var nodesArray = []; 35809 if (this.isCluster(clusterId) === true) { 35810 var containedNodes = this.body.nodes[clusterId].containedNodes; 35811 for (var nodeId in containedNodes) { 35812 if (containedNodes.hasOwnProperty(nodeId)) { 35813 nodesArray.push(nodeId); 35814 } 35815 } 35816 } 35817 35818 return nodesArray; 35819 } 35820 }, { 35821 key: 'findNode', 35822 35823 /** 35824 * Get the stack clusterId's that a certain node resides in. cluster A -> cluster B -> cluster C -> node 35825 * @param nodeId 35826 * @returns {Array} 35827 * @private 35828 */ 35829 value: function findNode(nodeId) { 35830 var stack = []; 35831 var max = 100; 35832 var counter = 0; 35833 35834 while (this.clusteredNodes[nodeId] !== undefined && counter < max) { 35835 stack.push(this.clusteredNodes[nodeId].node); 35836 nodeId = this.clusteredNodes[nodeId].clusterId; 35837 counter++; 35838 } 35839 stack.push(this.body.nodes[nodeId]); 35840 return stack; 35841 } 35842 }, { 35843 key: '_getConnectedId', 35844 35845 /** 35846 * Get the Id the node is connected to 35847 * @param edge 35848 * @param nodeId 35849 * @returns {*} 35850 * @private 35851 */ 35852 value: function _getConnectedId(edge, nodeId) { 35853 if (edge.toId != nodeId) { 35854 return edge.toId; 35855 } else if (edge.fromId != nodeId) { 35856 return edge.fromId; 35857 } else { 35858 return edge.fromId; 35859 } 35860 } 35861 }, { 35862 key: '_getHubSize', 35863 35864 /** 35865 * We determine how many connections denote an important hub. 35866 * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%) 35867 * 35868 * @private 35869 */ 35870 value: function _getHubSize() { 35871 var average = 0; 35872 var averageSquared = 0; 35873 var hubCounter = 0; 35874 var largestHub = 0; 35875 35876 for (var i = 0; i < this.body.nodeIndices.length; i++) { 35877 var node = this.body.nodes[this.body.nodeIndices[i]]; 35878 if (node.edges.length > largestHub) { 35879 largestHub = node.edges.length; 35880 } 35881 average += node.edges.length; 35882 averageSquared += Math.pow(node.edges.length, 2); 35883 hubCounter += 1; 35884 } 35885 average = average / hubCounter; 35886 averageSquared = averageSquared / hubCounter; 35887 35888 var variance = averageSquared - Math.pow(average, 2); 35889 var standardDeviation = Math.sqrt(variance); 35890 35891 var hubThreshold = Math.floor(average + 2 * standardDeviation); 35892 35893 // always have at least one to cluster 35894 if (hubThreshold > largestHub) { 35895 hubThreshold = largestHub; 35896 } 35897 35898 return hubThreshold; 35899 } 35900 }]); 35901 35902 return ClusterEngine; 35903 })(); 35904 35905 exports['default'] = ClusterEngine; 35906 module.exports = exports['default']; 35907 35908/***/ }, 35909/* 100 */ 35910/***/ function(module, exports, __webpack_require__) { 35911 35912 'use strict'; 35913 35914 Object.defineProperty(exports, '__esModule', { 35915 value: true 35916 }); 35917 35918 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 35919 35920 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 35921 35922 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 35923 35924 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
35924onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 35925 35926 var _Node2 = __webpack_require__(62); 35927 35928 var _Node3 = _interopRequireDefault(_Node2); 35929 35930 /** 35931 * 35932 */ 35933 35934 var Cluster = (function (_Node) { 35935 _inherits(Cluster, _Node); 35936 35937 function Cluster(options, body, imagelist, grouplist, globalOptions) { 35938 _classCallCheck(this, Cluster); 35939 35940 _get(Object.getPrototypeOf(Cluster.prototype), 'constructor', this).call(this, options, body, imagelist, grouplist, globalOptions); 35941 35942 this.isCluster = true; 35943 this.containedNodes = {}; 35944 this.containedEdges = {}; 35945 } 35946 35947 return Cluster; 35948 })(_Node3['default']); 35949 35950 exports['default'] = Cluster; 35951 module.exports = exports['default']; 35952 35953/***/ }, 35954/* 101 */ 35955/***/ function(module, exports, __webpack_require__) { 35956 35957 'use strict'; 35958 35959 Object.defineProperty(exports, '__esModule', { 35960 value: true 35961 }); 35962 35963 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 35964 35965 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 35966 35967 if (typeof window !== 'undefined') { 35968 window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; 35969 } 35970 35971 var util = __webpack_require__(1); 35972 35973 var CanvasRenderer = (function () { 35974 function CanvasRenderer(body, canvas) { 35975 _classCallCheck(this, CanvasRenderer); 35976 35977 this.body = body; 35978 this.canvas = canvas; 35979 35980 this.redrawRequested = false; 35981 this.renderTimer = undefined; 35982 this.requiresTimeout = true; 35983 this.renderingActive = false; 35984 this.renderRequests = 0; 35985 this.pixelRatio = undefined; 35986 this.allowRedraw = true; 35987 35988 this.dragging = false; 35989 this.options = {}; 35990 this.defaultOptions = { 35991 hideEdgesOnDrag: false, 35992 hideNodesOnDrag: false 35993 }; 35994 util.extend(this.options, this.defaultOptions); 35995 35996 this._determineBrowserMethod(); 35997 this.bindEventListeners(); 35998 } 35999 36000 _createClass(CanvasRenderer, [{ 36001 key: 'bindEventListeners', 36002 value: function bindEventListeners() { 36003 var _this = this; 36004 36005 this.body.emitter.on('dragStart', function () { 36006 _this.dragging = true; 36007 }); 36008 this.body.emitter.on('dragEnd', function () { 36009 return _this.dragging = false; 36010 }); 36011 this.body.emitter.on('_resizeNodes', function () { 36012 return _this._resizeNodes(); 36013 }); 36014 this.body.emitter.on('_redraw', function () { 36015 if (_this.renderingActive === false) { 36016 _this._redraw(); 36017 } 36018 }); 36019 this.body.emitter.on('_blockRedraw', function () { 36020 _this.allowRedraw = false; 36021 }); 36022 this.body.emitter.on('_allowRedraw', function () { 36023 _this.allowRedraw = true;_this.redrawRequested = false; 36024 }); 36025 this.body.emitter.on('_requestRedraw', this._requestRedraw.bind(this)); 36026 this.body.emitter.on('_startRendering', function () { 36027 _this.renderRequests += 1; 36028 _this.renderingActive = true; 36029 _this._startRendering(); 36030 }); 36031 this.body.emitter.on('_stopRendering', function () { 36032 _this.renderRequests -= 1; 36033 _this.renderingActive = _this.renderRequests > 0; 36034 _this.renderTimer = undefined; 36035 }); 36036 this.body.emitter.on('destroy', function () { 36037 _this.renderRequests = 0; 36038 _this.allowRedraw = false;
36039 _this.renderingActive = false; 36040 if (_this.requiresTimeout === true) { 36041 clearTimeout(_this.renderTimer); 36042 } else { 36043 cancelAnimationFrame(_this.renderTimer); 36044 } 36045 _this.body.emitter.off(); 36046 }); 36047 } 36048 }, { 36049 key: 'setOptions', 36050 value: function setOptions(options) { 36051 if (options !== undefined) { 36052 var fields = ['hideEdgesOnDrag', 'hideNodesOnDrag']; 36053 util.selectiveDeepExtend(fields, this.options, options); 36054 } 36055 } 36056 }, { 36057 key: '_startRendering', 36058 value: function _startRendering() { 36059 if (this.renderingActive === true) { 36060 if (this.renderTimer === undefined) { 36061 if (this.requiresTimeout === true) { 36062 this.renderTimer = window.setTimeout(this._renderStep.bind(this), this.simulationInterval); // wait this.renderTimeStep milliseconds and perform the animation step function 36063 } else { 36064 this.renderTimer = window.requestAnimationFrame(this._renderStep.bind(this)); // wait this.renderTimeStep milliseconds and perform the animation step function 36065 } 36066 } 36067 } 36068 } 36069 }, { 36070 key: '_renderStep', 36071 value: function _renderStep() { 36072 if (this.renderingActive === true) { 36073 // reset the renderTimer so a new scheduled animation step can be set 36074 this.renderTimer = undefined; 36075 36076 if (this.requiresTimeout === true) { 36077 // this schedules a new simulation step 36078 this._startRendering(); 36079 } 36080 36081 this._redraw(); 36082 36083 if (this.requiresTimeout === false) { 36084 // this schedules a new simulation step 36085 this._startRendering(); 36086 } 36087 } 36088 } 36089 }, { 36090 key: 'redraw', 36091 36092 /** 36093 * Redraw the network with the current data 36094 * chart will be resized too. 36095 */ 36096 value: function redraw() { 36097 this.body.emitter.emit('setSize'); 36098 this._redraw(); 36099 } 36100 }, { 36101 key: '_requestRedraw', 36102 36103 /** 36104 * Redraw the network with the current data 36105 * @param hidden | used to get the first estimate of the node sizes. only the nodes are drawn after which they are quickly drawn over. 36106 * @private 36107 */ 36108 value: function _requestRedraw() { 36109 var _this2 = this; 36110 36111 if (this.redrawRequested !== true && this.renderingActive === false && this.allowRedraw === true) { 36112 this.redrawRequested = true; 36113 if (this.requiresTimeout === true) { 36114 window.setTimeout(function () { 36115 _this2._redraw(false); 36116 }, 0); 36117 } else { 36118 window.requestAnimationFrame(function () { 36119 _this2._redraw(false); 36120 }); 36121 } 36122 } 36123 } 36124 }, { 36125 key: '_redraw', 36126 value: function _redraw() { 36127 var hidden = arguments.length <= 0 || arguments[0] === undefined ? false : arguments[0]; 36128 36129 if (this.allowRedraw === true) { 36130 this.body.emitter.emit('initRedraw'); 36131 36132 this.redrawRequested = false; 36133 var ctx = this.canvas.frame.canvas.getContext('2d'); 36134 36135 // when the container div was hidden, this fixes it back up! 36136 if (this.canvas.frame.canvas.width === 0 || this.canvas.frame.canvas.height === 0) { 36137 this.canvas.setSize(); 36138 } 36139 36140 this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); 36141 36142 ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); 36143 36144 // clear the canvas 36145 var w = this.canvas.frame.canvas.clientWidth; 36146 var h = this.canvas.frame.canvas.clientHeight; 36147 ctx.clearRect(0, 0, w, h); 36148 36149 // if the div is hidden, we stop the redraw here for performance. 36150 if (this.canvas.frame.clientWidth === 0) { 36151 return; 36152 } 36153 36154 // set scaling and translation 36155 ctx.save(); 36156 ctx.translate(this.body.view.translation.x, this.body.view.translation.y); 36157 ctx.scale(this.body.view.scale, this.body.view.scale); 36158 36159 ctx.beginPath(); 36160 this.body.emitter.emit('beforeDrawing', ctx); 36161 ctx.closePath(); 36162 36163 if (hidden === false) { 36164 if (this.dragging === false || this.dragging === true && this.options.hideEdgesOnDrag === false) { 36165 this._drawEdges(ctx); 36166 } 36167 } 36168 36169 if (this.dragging === false || this.dragging === true && this.options.hideNodesOnDrag === false) { 36170 this._drawNodes(ctx, hidden); 36171 } 36172 36173 if (this.controlNodesActive === true) { 36174 this._drawControlNodes(ctx); 36175 } 36176 36177 ctx.beginPath(); 36178 //this.physics.nodesSolver._debug(ctx,"#F00F0F"); 36179 this.body.emitter.emit('afterDrawing', ctx); 36180 ctx.closePath(); 36181 // restore original scaling and translation
36182 ctx.restore(); 36183 36184 if (hidden === true) { 36185 ctx.clearRect(0, 0, w, h); 36186 } 36187 } 36188 } 36189 }, { 36190 key: '_resizeNodes', 36191 36192 /** 36193 * Redraw all nodes 36194 * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); 36195 * @param {CanvasRenderingContext2D} ctx 36196 * @param {Boolean} [alwaysShow] 36197 * @private 36198 */ 36199 value: function _resizeNodes() { 36200 var ctx = this.canvas.frame.canvas.getContext('2d'); 36201 if (this.pixelRatio === undefined) { 36202 this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); 36203 } 36204 ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); 36205 ctx.save(); 36206 ctx.translate(this.body.view.translation.x, this.body.view.translation.y); 36207 ctx.scale(this.body.view.scale, this.body.view.scale); 36208 36209 var nodes = this.body.nodes; 36210 var node = undefined; 36211 36212 // resize all nodes 36213 for (var nodeId in nodes) { 36214 if (nodes.hasOwnProperty(nodeId)) { 36215 node = nodes[nodeId]; 36216 node.resize(ctx); 36217 node.updateBoundingBox(ctx, node.selected); 36218 } 36219 } 36220 36221 // restore original scaling and translation 36222 ctx.restore(); 36223 } 36224 }, { 36225 key: '_drawNodes', 36226 36227 /** 36228 * Redraw all nodes 36229 * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); 36230 * @param {CanvasRenderingContext2D} ctx 36231 * @param {Boolean} [alwaysShow] 36232 * @private 36233 */ 36234 value: function _drawNodes(ctx) { 36235 var alwaysShow = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 36236 36237 var nodes = this.body.nodes; 36238 var nodeIndices = this.body.nodeIndices; 36239 var node = undefined; 36240 var selected = []; 36241 var margin = 20; 36242 var topLeft = this.canvas.DOMtoCanvas({ x: -margin, y: -margin }); 36243 var bottomRight = this.canvas.DOMtoCanvas({ 36244 x: this.canvas.frame.canvas.clientWidth + margin, 36245 y: this.canvas.frame.canvas.clientHeight + margin 36246 }); 36247 var viewableArea = { top: topLeft.y, left: topLeft.x, bottom: bottomRight.y, right: bottomRight.x }; 36248 36249 // draw unselected nodes; 36250 for (var i = 0; i < nodeIndices.length; i++) { 36251 node = nodes[nodeIndices[i]]; 36252 // set selected nodes aside 36253 if (node.isSelected()) { 36254 selected.push(nodeIndices[i]); 36255 } else { 36256 if (alwaysShow === true) { 36257 node.draw(ctx); 36258 } else if (node.isBoundingBoxOverlappingWith(viewableArea) === true) { 36259 node.draw(ctx); 36260 } else { 36261 node.updateBoundingBox(ctx, node.selected); 36262 } 36263 } 36264 } 36265 36266 // draw the selected nodes on top 36267 for (var i = 0; i < selected.length; i++) { 36268 node = nodes[selected[i]]; 36269 node.draw(ctx); 36270 } 36271 } 36272 }, { 36273 key: '_drawEdges', 36274 36275 /** 36276 * Redraw all edges 36277 * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); 36278 * @param {CanvasRenderingContext2D} ctx 36279 * @private 36280 */ 36281 value: function _drawEdges(ctx) { 36282 var edges = this.body.edges; 36283 var edgeIndices = this.body.edgeIndices; 36284 var edge = undefined; 36285 36286 for (var i = 0; i < edgeIndices.length; i++) { 36287 edge = edges[edgeIndices[i]]; 36288 if (edge.connected === true) { 36289 edge.draw(ctx); 36290 } 36291 } 36292 } 36293 }, { 36294 key: '_drawControlNodes', 36295 36296 /** 36297 * Redraw all edges 36298 * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); 36299 * @param {CanvasRenderingContext2D} ctx 36300 * @private 36301 */ 36302 value: function _drawControlNodes(ctx) { 36303 var edges = this.body.edges; 36304 var edgeIndices = this.body.edgeIndices; 36305 var edge = undefined; 36306 36307 for (var i = 0; i < edgeIndices.length; i++) { 36308 edge = edges[edgeIndices[i]]; 36309 edge._drawControlNodes(ctx); 36310 } 36311 } 36312 }, {
36313 key: '_determineBrowserMethod', 36314 36315 /** 36316 * Determine if the browser requires a setTimeout or a requestAnimationFrame. This was required because 36317 * some implementations (safari and IE9) did not support requestAnimationFrame 36318 * @private 36319 */ 36320 value: function _determineBrowserMethod() { 36321 if (typeof window !== 'undefined') { 36322 var browserType = navigator.userAgent.toLowerCase(); 36323 this.requiresTimeout = false; 36324 if (browserType.indexOf('msie 9.0') != -1) { 36325 // IE 9 36326 this.requiresTimeout = true; 36327 } else if (browserType.indexOf('safari') != -1) { 36328 // safari 36329 if (browserType.indexOf('chrome') <= -1) { 36330 this.requiresTimeout = true; 36331 } 36332 } 36333 } else { 36334 this.requiresTimeout = true; 36335 } 36336 } 36337 }]); 36338 36339 return CanvasRenderer; 36340 })(); 36341 36342 exports['default'] = CanvasRenderer; 36343 module.exports = exports['default']; 36344 36345/***/ }, 36346/* 102 */ 36347/***/ function(module, exports, __webpack_require__) { 36348 36349 'use strict'; 36350 36351 Object.defineProperty(exports, '__esModule', { 36352 value: true 36353 }); 36354 36355 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 36356 36357 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 36358 36359 var Hammer = __webpack_require__(20); 36360 var hammerUtil = __webpack_require__(25); 36361 36362 var util = __webpack_require__(1); 36363 36364 /** 36365 * Create the main frame for the Network. 36366 * This function is executed once when a Network object is created. The frame 36367 * contains a canvas, and this canvas contains all objects like the axis and 36368 * nodes. 36369 * @private 36370 */ 36371 36372 var Canvas = (function () { 36373 function Canvas(body) { 36374 _classCallCheck(this, Canvas); 36375 36376 this.body = body; 36377 this.pixelRatio = 1; 36378 this.resizeTimer = undefined; 36379 this.resizeFunction = this._onResize.bind(this); 36380 this.cameraState = {}; 36381 36382 this.options = {}; 36383 this.defaultOptions = { 36384 autoResize: true, 36385 height: '100%', 36386 width: '100%' 36387 }; 36388 util.extend(this.options, this.defaultOptions); 36389 36390 this.bindEventListeners(); 36391 } 36392 36393 _createClass(Canvas, [{ 36394 key: 'bindEventListeners', 36395 value: function bindEventListeners() { 36396 var _this = this; 36397 36398 // bind the events 36399 this.body.emitter.once('resize', function (obj) { 36400 if (obj.width !== 0) { 36401 _this.body.view.translation.x = obj.width * 0.5; 36402 } 36403 if (obj.height !== 0) { 36404 _this.body.view.translation.y = obj.height * 0.5; 36405 } 36406 }); 36407 this.body.emitter.on('setSize', this.setSize.bind(this)); 36408 this.body.emitter.on('destroy', function () { 36409 _this.hammerFrame.destroy(); 36410 _this.hammer.destroy(); 36411 _this._cleanUp(); 36412 }); 36413 } 36414 }, { 36415 key: 'setOptions', 36416 value: function setOptions(options) { 36417 var _this2 = this; 36418 36419 if (options !== undefined) { 36420 var fields = ['width', 'height', 'autoResize']; 36421 util.selectiveDeepExtend(fields, this.options, options); 36422 } 36423 36424 if (this.options.autoResize === true) { 36425 // automatically adapt to a changing size of the browser. 36426 this._cleanUp(); 36427 this.resizeTimer = setInterval(function () { 36428 var changed = _this2.setSize(); 36429 if (changed === true) { 36430 _this2.body.emitter.emit('_requestRedraw'); 36431 } 36432 }, 1000); 36433 this.resizeFunction = this._onResize.bind(this); 36434 util.addEventListener(window, 'resize', this.resizeFunction); 36435 } 36436 } 36437 }, {
36438 key: '_cleanUp', 36439 value: function _cleanUp() { 36440 // automatically adapt to a changing size of the browser. 36441 if (this.resizeTimer !== undefined) { 36442 clearInterval(this.resizeTimer); 36443 } 36444 util.removeEventListener(window, 'resize', this.resizeFunction); 36445 this.resizeFunction = undefined; 36446 } 36447 }, { 36448 key: '_onResize', 36449 value: function _onResize() { 36450 this.setSize(); 36451 this.body.emitter.emit('_redraw'); 36452 } 36453 }, { 36454 key: '_getCameraState', 36455 36456 /** 36457 * Get and store the cameraState 36458 * @private 36459 */ 36460 value: function _getCameraState() { 36461 var pixelRatio = arguments.length <= 0 || arguments[0] === undefined ? this.pixelRatio : arguments[0]; 36462 36463 this.cameraState.previousWidth = this.frame.canvas.width / pixelRatio; 36464 this.cameraState.previousHeight = this.frame.canvas.height / pixelRatio; 36465 this.cameraState.scale = this.body.view.scale; 36466 this.cameraState.position = this.DOMtoCanvas({ x: 0.5 * this.frame.canvas.width / pixelRatio, y: 0.5 * this.frame.canvas.height / pixelRatio }); 36467 } 36468 }, { 36469 key: '_setCameraState', 36470 36471 /** 36472 * Set the cameraState 36473 * @private 36474 */ 36475 value: function _setCameraState() { 36476 if (this.cameraState.scale !== undefined && this.frame.canvas.clientWidth !== 0 && this.frame.canvas.clientHeight !== 0 && this.pixelRatio !== 0 && this.cameraState.previousWidth > 0) { 36477 36478 this.body.view.scale = this.cameraState.scale * Math.min(this.frame.canvas.width / this.pixelRatio / this.cameraState.previousWidth, this.frame.canvas.height / this.pixelRatio / this.cameraState.previousHeight); 36479 36480 // this comes from the view module. 36481 var currentViewCenter = this.DOMtoCanvas({ 36482 x: 0.5 * this.frame.canvas.clientWidth, 36483 y: 0.5 * this.frame.canvas.clientHeight 36484 }); 36485 36486 var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node 36487 x: currentViewCenter.x - this.cameraState.position.x, 36488 y: currentViewCenter.y - this.cameraState.position.y 36489 }; 36490 this.body.view.translation.x += distanceFromCenter.x * this.body.view.scale; 36491 this.body.view.translation.y += distanceFromCenter.y * this.body.view.scale; 36492 } 36493 } 36494 }, { 36495 key: '_prepareValue', 36496 value: function _prepareValue(value) { 36497 if (typeof value === 'number') { 36498 return value + 'px'; 36499 } else if (typeof value === 'string') { 36500 if (value.indexOf('%') !== -1 || value.indexOf('px') !== -1) { 36501 return value; 36502 } else if (value.indexOf('%') === -1) { 36503 return value + 'px'; 36504 } 36505 } 36506 throw new Error('Could not use the value supplied for width or height:' + value); 36507 } 36508 }, { 36509 key: '_create', 36510 36511 /** 36512 * Create the HTML 36513 */ 36514 value: function _create() { 36515 // remove all elements from the container element. 36516 while (this.body.container.hasChildNodes()) { 36517 this.body.container.removeChild(this.body.container.firstChild); 36518 } 36519 36520 this.frame = document.createElement('div'); 36521 this.frame.className = 'vis-network'; 36522 this.frame.style.position = 'relative'; 36523 this.frame.style.overflow = 'hidden'; 36524 this.frame.tabIndex = 900; // tab index is required for keycharm to bind keystrokes to the div instead of the window 36525
36526 ////////////////////////////////////////////////////////////////// 36527 36528 this.frame.canvas = document.createElement('canvas'); 36529 this.frame.canvas.style.position = 'relative'; 36530 this.frame.appendChild(this.frame.canvas); 36531 36532 if (!this.frame.canvas.getContext) { 36533 var noCanvas = document.createElement('DIV'); 36534 noCanvas.style.color = 'red'; 36535 noCanvas.style.fontWeight = 'bold'; 36536 noCanvas.style.padding = '10px'; 36537 noCanvas.innerHTML = 'Error: your browser does not support HTML canvas'; 36538 this.frame.canvas.appendChild(noCanvas); 36539 } else { 36540 var ctx = this.frame.canvas.getContext('2d'); 36541 this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); 36542 36543 this.frame.canvas.getContext('2d').setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); 36544 } 36545 36546 // add the frame to the container element 36547 this.body.container.appendChild(this.frame); 36548 36549 this.body.view.scale = 1; 36550 this.body.view.translation = { x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight }; 36551 36552 this._bindHammer(); 36553 } 36554 }, { 36555 key: '_bindHammer', 36556 36557 /** 36558 * This function binds hammer, it can be repeated over and over due to the uniqueness check. 36559 * @private 36560 */ 36561 value: function _bindHammer() { 36562 var _this3 = this; 36563 36564 if (this.hammer !== undefined) { 36565 this.hammer.destroy(); 36566 } 36567 this.drag = {}; 36568 this.pinch = {}; 36569 36570 // init hammer 36571 this.hammer = new Hammer(this.frame.canvas); 36572 this.hammer.get('pinch').set({ enable: true }); 36573 // enable to get better response, todo: test on mobile. 36574 this.hammer.get('pan').set({ threshold: 5, direction: 30 }); // 30 is ALL_DIRECTIONS in hammer. 36575 36576 hammerUtil.onTouch(this.hammer, function (event) { 36577 _this3.body.eventListeners.onTouch(event); 36578 }); 36579 this.hammer.on('tap', function (event) { 36580 _this3.body.eventListeners.onTap(event); 36581 }); 36582 this.hammer.on('doubletap', function (event) { 36583 _this3.body.eventListeners.onDoubleTap(event); 36584 }); 36585 this.hammer.on('press', function (event) { 36586 _this3.body.eventListeners.onHold(event); 36587 }); 36588 this.hammer.on('panstart', function (event) { 36589 _this3.body.eventListeners.onDragStart(event); 36590 }); 36591 this.hammer.on('panmove', function (event) { 36592 _this3.body.eventListeners.onDrag(event); 36593 }); 36594 this.hammer.on('panend', function (event) { 36595 _this3.body.eventListeners.onDragEnd(event); 36596 }); 36597 this.hammer.on('pinch', function (event) { 36598 _this3.body.eventListeners.onPinch(event); 36599 }); 36600 36601 // TODO: neatly cleanup these handlers when re-creating the Canvas, IF these are done with hammer, event.stopPropagation will not work? 36602 this.frame.canvas.addEventListener('mousewheel', function (event) { 36603 _this3.body.eventListeners.onMouseWheel(event); 36604 }); 36605 this.frame.canvas.addEventListener('DOMMouseScroll', function (event) { 36606 _this3.body.eventListeners.onMouseWheel(event); 36607 }); 36608 36609 this.frame.canvas.addEventListener('mousemove', function (event) { 36610 _this3.body.eventListeners.onMouseMove(event); 36611 }); 36612 this.frame.canvas.addEventListener('contextmenu', function (event) { 36613 _this3.body.eventListeners.onContext(event); 36614 }); 36615 36616 this.hammerFrame = new Hammer(this.frame); 36617 hammerUtil.onRelease(this.hammerFrame, function (event) { 36618 _this3.body.eventListeners.onRelease(event); 36619 }); 36620 } 36621 }, { 36622 key: 'setSize', 36623 36624 /** 36625 * Set a new size for the network 36626 * @param {string} width Width in pixels or percentage (for example '800px' 36627 * or '50%') 36628 * @param {string} height Height in pixels or percentage (for example '400px'
36629 * or '30%') 36630 */ 36631 value: function setSize() { 36632 var width = arguments.length <= 0 || arguments[0] === undefined ? this.options.width : arguments[0]; 36633 var height = arguments.length <= 1 || arguments[1] === undefined ? this.options.height : arguments[1]; 36634 36635 width = this._prepareValue(width); 36636 height = this._prepareValue(height); 36637 36638 var emitEvent = false; 36639 var oldWidth = this.frame.canvas.width; 36640 var oldHeight = this.frame.canvas.height; 36641 36642 // update the pixelratio 36643 var ctx = this.frame.canvas.getContext('2d'); 36644 var previousRation = this.pixelRatio; // we cache this because the camera state storage needs the old value 36645 this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1); 36646 36647 if (width != this.options.width || height != this.options.height || this.frame.style.width != width || this.frame.style.height != height) { 36648 this._getCameraState(previousRation); 36649 36650 this.frame.style.width = width; 36651 this.frame.style.height = height; 36652 36653 this.frame.canvas.style.width = '100%'; 36654 this.frame.canvas.style.height = '100%'; 36655 36656 this.frame.canvas.width = Math.round(this.frame.canvas.clientWidth * this.pixelRatio); 36657 this.frame.canvas.height = Math.round(this.frame.canvas.clientHeight * this.pixelRatio); 36658 36659 this.options.width = width; 36660 this.options.height = height; 36661 36662 emitEvent = true; 36663 } else { 36664 // this would adapt the width of the canvas to the width from 100% if and only if 36665 // there is a change. 36666 36667 // store the camera if there is a change in size. 36668 if (this.frame.canvas.width != Math.round(this.frame.canvas.clientWidth * this.pixelRatio) || this.frame.canvas.height != Math.round(this.frame.canvas.clientHeight * this.pixelRatio)) { 36669 this._getCameraState(previousRation); 36670 } 36671 36672 if (this.frame.canvas.width != Math.round(this.frame.canvas.clientWidth * this.pixelRatio)) { 36673 this.frame.canvas.width = Math.round(this.frame.canvas.clientWidth * this.pixelRatio); 36674 emitEvent = true; 36675 } 36676 if (this.frame.canvas.height != Math.round(this.frame.canvas.clientHeight * this.pixelRatio)) { 36677 this.frame.canvas.height = Math.round(this.frame.canvas.clientHeight * this.pixelRatio); 36678 emitEvent = true; 36679 } 36680 } 36681 36682 if (emitEvent === true) { 36683 this.body.emitter.emit('resize', { 36684 width: Math.round(this.frame.canvas.width / this.pixelRatio), 36685 height: Math.round(this.frame.canvas.height / this.pixelRatio), 36686 oldWidth: Math.round(oldWidth / this.pixelRatio), 36687 oldHeight: Math.round(oldHeight / this.pixelRatio) 36688 }); 36689 36690 // restore the camera on change. 36691 this._setCameraState(); 36692 } 36693 36694 return emitEvent; 36695 } 36696 }, { 36697 key: '_XconvertDOMtoCanvas', 36698 36699 /** 36700 * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to 36701 * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) 36702 * @param {number} x 36703 * @returns {number} 36704 * @private 36705 */ 36706 value: function _XconvertDOMtoCanvas(x) { 36707 return (x - this.body.view.translation.x) / this.body.view.scale; 36708 } 36709 }, { 36710 key: '_XconvertCanvasToDOM', 36711 36712 /** 36713 * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to 36714 * the X coordinate in DOM-space (coordinate point in browser relative to the container div) 36715 * @param {number} x 36716 * @returns {number} 36717 * @private 36718 */ 36719 value: function _XconvertCanvasToDOM(x) { 36720 return x * this.body.view.scale + this.body.view.translation.x; 36721 } 36722 }, { 36723 key: '_YconvertDOMtoCanvas', 36724 36725 /** 36726 * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to 36727 * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) 36728 * @param {number} y 36729 * @returns {number} 36730 * @private 36731 */ 36732 value: function _YconvertDOMtoCanvas(y) { 36733 return (y - this.body.view.translation.y) / this.body.view.scale; 36734 } 36735 }, {
36736 key: '_YconvertCanvasToDOM', 36737 36738 /** 36739 * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to 36740 * the Y coordinate in DOM-space (coordinate point in browser relative to the container div) 36741 * @param {number} y 36742 * @returns {number} 36743 * @private 36744 */ 36745 value: function _YconvertCanvasToDOM(y) { 36746 return y * this.body.view.scale + this.body.view.translation.y; 36747 } 36748 }, { 36749 key: 'canvasToDOM', 36750 36751 /** 36752 * 36753 * @param {object} pos = {x: number, y: number} 36754 * @returns {{x: number, y: number}} 36755 * @constructor 36756 */ 36757 value: function canvasToDOM(pos) { 36758 return { x: this._XconvertCanvasToDOM(pos.x), y: this._YconvertCanvasToDOM(pos.y) }; 36759 } 36760 }, { 36761 key: 'DOMtoCanvas', 36762 36763 /** 36764 * 36765 * @param {object} pos = {x: number, y: number} 36766 * @returns {{x: number, y: number}} 36767 * @constructor 36768 */ 36769 value: function DOMtoCanvas(pos) { 36770 return { x: this._XconvertDOMtoCanvas(pos.x), y: this._YconvertDOMtoCanvas(pos.y) }; 36771 } 36772 }]); 36773 36774 return Canvas; 36775 })(); 36776 36777 exports['default'] = Canvas; 36778 module.exports = exports['default']; 36779 36780/***/ }, 36781/* 103 */ 36782/***/ function(module, exports, __webpack_require__) { 36783 36784 'use strict'; 36785 36786 Object.defineProperty(exports, '__esModule', { 36787 value: true 36788 }); 36789 36790 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 36791 36792 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 36793 36794 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 36795 36796 var _NetworkUtil = __webpack_require__(104); 36797 36798 var _NetworkUtil2 = _interopRequireDefault(_NetworkUtil); 36799 36800 var util = __webpack_require__(1); 36801 36802 var View = (function () { 36803 function View(body, canvas) { 36804 var _this = this; 36805 36806 _classCallCheck(this, View); 36807 36808 this.body = body; 36809 this.canvas = canvas; 36810 36811 this.animationSpeed = 1 / this.renderRefreshRate; 36812 this.animationEasingFunction = 'easeInOutQuint'; 36813 this.easingTime = 0; 36814 this.sourceScale = 0; 36815 this.targetScale = 0; 36816 this.sourceTranslation = 0; 36817 this.targetTranslation = 0; 36818 this.lockedOnNodeId = undefined; 36819 this.lockedOnNodeOffset = undefined; 36820 this.touchTime = 0; 36821 36822 this.viewFunction = undefined; 36823 36824 this.body.emitter.on('fit', this.fit.bind(this)); 36825 this.body.emitter.on('animationFinished', function () { 36826 _this.body.emitter.emit('_stopRendering'); 36827 }); 36828 this.body.emitter.on('unlockNode', this.releaseNode.bind(this)); 36829 } 36830 36831 _createClass(View, [{ 36832 key: 'setOptions', 36833 value: function setOptions() { 36834 var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 36835 36836 this.options = options; 36837 } 36838 }, { 36839 key: 'fit', 36840 36841 /** 36842 * This function zooms out to fit all data on screen based on amount of nodes 36843 * @param {Object} Options 36844 * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
36845 */ 36846 value: function fit() { 36847 var options = arguments.length <= 0 || arguments[0] === undefined ? { nodes: [] } : arguments[0]; 36848 var initialZoom = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 36849 36850 var range = undefined; 36851 var zoomLevel = undefined; 36852 if (options.nodes === undefined || options.nodes.length === 0) { 36853 options.nodes = this.body.nodeIndices; 36854 } 36855 36856 if (initialZoom === true) { 36857 // check if more than half of the nodes have a predefined position. If so, we use the range, not the approximation. 36858 var positionDefined = 0; 36859 for (var nodeId in this.body.nodes) { 36860 if (this.body.nodes.hasOwnProperty(nodeId)) { 36861 var node = this.body.nodes[nodeId]; 36862 if (node.predefinedPosition === true) { 36863 positionDefined += 1; 36864 } 36865 } 36866 } 36867 if (positionDefined > 0.5 * this.body.nodeIndices.length) { 36868 this.fit(options, false); 36869 return; 36870 } 36871 36872 range = _NetworkUtil2['default']._getRange(this.body.nodes, options.nodes); 36873 36874 var numberOfNodes = this.body.nodeIndices.length; 36875 zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good. 36876 36877 // correct for larger canvasses. 36878 var factor = Math.min(this.canvas.frame.canvas.clientWidth / 600, this.canvas.frame.canvas.clientHeight / 600); 36879 zoomLevel *= factor; 36880 } else { 36881 this.body.emitter.emit('_resizeNodes'); 36882 range = _NetworkUtil2['default']._getRange(this.body.nodes, options.nodes); 36883 36884 var xDistance = Math.abs(range.maxX - range.minX) * 1.1; 36885 var yDistance = Math.abs(range.maxY - range.minY) * 1.1; 36886 36887 var xZoomLevel = this.canvas.frame.canvas.clientWidth / xDistance; 36888 var yZoomLevel = this.canvas.frame.canvas.clientHeight / yDistance; 36889 36890 zoomLevel = xZoomLevel <= yZoomLevel ? xZoomLevel : yZoomLevel; 36891 } 36892 36893 if (zoomLevel > 1.0) { 36894 zoomLevel = 1.0; 36895 } else if (zoomLevel === 0) { 36896 zoomLevel = 1.0; 36897 } 36898 36899 var center = _NetworkUtil2['default']._findCenter(range); 36900 var animationOptions = { position: center, scale: zoomLevel, animation: options.animation }; 36901 this.moveTo(animationOptions); 36902 } 36903 }, { 36904 key: 'focus', 36905 36906 // animation 36907 36908 /** 36909 * Center a node in view. 36910 * 36911 * @param {Number} nodeId 36912 * @param {Number} [options] 36913 */ 36914 value: function focus(nodeId) { 36915 var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; 36916 36917 if (this.body.nodes[nodeId] !== undefined) { 36918 var nodePosition = { x: this.body.nodes[nodeId].x, y: this.body.nodes[nodeId].y }; 36919 options.position = nodePosition; 36920 options.lockedOnNode = nodeId; 36921 36922 this.moveTo(options); 36923 } else { 36924 console.log('Node: ' + nodeId + ' cannot be found.'); 36925 } 36926 } 36927 }, { 36928 key: 'moveTo', 36929 36930 /** 36931 * 36932 * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels 36933 * | options.scale = Number // scale to move to 36934 * | options.position = {x:Number, y:Number} // position to move to 36935 * | options.animation = {duration:Number, easingFunction:String} || Boolean // position to move to 36936 */ 36937 value: function moveTo(options) { 36938 if (options === undefined) { 36939 options = {}; 36940 return; 36941 } 36942 if (options.offset === undefined) { 36943 options.offset = { x: 0, y: 0 }; 36944 } 36945 if (options.offset.x === undefined) { 36946 options.offset.x = 0; 36947 } 36948 if (options.offset.y === undefined) { 36949 options.offset.y = 0; 36950 } 36951 if (options.scale === undefined) { 36952 options.scale = this.body.view.scale; 36953 } 36954 if (options.position === undefined) { 36955 options.position = this.getViewPosition(); 36956 } 36957 if (options.animation === undefined) { 36958 options.animation = { duration: 0 }; 36959 } 36960 if (options.animation === false) { 36961 options.animation = { duration: 0 }; 36962 } 36963 if (options.animation === true) { 36964 options.animation = {}; 36965 } 36966 if (options.animation.duration === undefined) { 36967 options.animation.duration = 1000; 36968 } // default duration 36969 if (options.animation.easingFunction === undefined) { 36970 options.animation.easingFunction = 'easeInOutQuad'; 36971 } // default easing function 36972 36973 this.animateView(options); 36974 } 36975 }, {
36976 key: 'animateView', 36977 36978 /** 36979 * 36980 * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels 36981 * | options.time = Number // animation time in milliseconds 36982 * | options.scale = Number // scale to animate to 36983 * | options.position = {x:Number, y:Number} // position to animate to 36984 * | options.easingFunction = String // linear, easeInQuad, easeOutQuad, easeInOutQuad, 36985 * // easeInCubic, easeOutCubic, easeInOutCubic, 36986 * // easeInQuart, easeOutQuart, easeInOutQuart, 36987 * // easeInQuint, easeOutQuint, easeInOutQuint 36988 */ 36989 value: function animateView(options) { 36990 if (options === undefined) { 36991 return; 36992 } 36993 this.animationEasingFunction = options.animation.easingFunction; 36994 // release if something focussed on the node 36995 this.releaseNode(); 36996 if (options.locked === true) { 36997 this.lockedOnNodeId = options.lockedOnNode; 36998 this.lockedOnNodeOffset = options.offset; 36999 } 37000 37001 // forcefully complete the old animation if it was still running 37002 if (this.easingTime != 0) { 37003 this._transitionRedraw(true); // by setting easingtime to 1, we finish the animation. 37004 } 37005 37006 this.sourceScale = this.body.view.scale; 37007 this.sourceTranslation = this.body.view.translation; 37008 this.targetScale = options.scale; 37009 37010 // set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw 37011 // but at least then we'll have the target transition 37012 this.body.view.scale = this.targetScale; 37013 var viewCenter = this.canvas.DOMtoCanvas({ x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight }); 37014 37015 var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node 37016 x: viewCenter.x - options.position.x, 37017 y: viewCenter.y - options.position.y 37018 }; 37019 this.targetTranslation = { 37020 x: this.sourceTranslation.x + distanceFromCenter.x * this.targetScale + options.offset.x, 37021 y: this.sourceTranslation.y + distanceFromCenter.y * this.targetScale + options.offset.y 37022 }; 37023 37024 // if the time is set to 0, don't do an animation 37025 if (options.animation.duration === 0) { 37026 if (this.lockedOnNodeId != undefined) { 37027 this.viewFunction = this._lockedRedraw.bind(this); 37028 this.body.emitter.on('initRedraw', this.viewFunction); 37029 } else { 37030 this.body.view.scale = this.targetScale; 37031 this.body.view.translation = this.targetTranslation; 37032 this.body.emitter.emit('_requestRedraw'); 37033 } 37034 } else { 37035 this.animationSpeed = 1 / (60 * options.animation.duration * 0.001) || 1 / 60; // 60 for 60 seconds, 0.001 for milli's 37036 this.animationEasingFunction = options.animation.easingFunction; 37037 37038 this.viewFunction = this._transitionRedraw.bind(this); 37039 this.body.emitter.on('initRedraw', this.viewFunction); 37040 this.body.emitter.emit('_startRendering'); 37041 } 37042 } 37043 }, { 37044 key: '_lockedRedraw', 37045 37046 /** 37047 * used to animate smoothly by hijacking the redraw function. 37048 * @private 37049 */ 37050 value: function _lockedRedraw() { 37051 var nodePosition = { x: this.body.nodes[this.lockedOnNodeId].x, y: this.body.nodes[this.lockedOnNodeId].y }; 37052 var viewCenter = this.canvas.DOMtoCanvas({ x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight }); 37053 var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node 37054 x: viewCenter.x - nodePosition.x, 37055 y: viewCenter.y - nodePosition.y 37056 }; 37057 var sourceTranslation = this.body.view.translation; 37058 var targetTranslation = { 37059 x: sourceTranslation.x + distanceFromCenter.x * this.body.view.scale + this.lockedOnNodeOffset.x, 37060 y: sourceTranslation.y + distanceFromCenter.y * this.body.view.scale + this.lockedOnNodeOffset.y 37061 }; 37062 37063 this.body.view.translation = targetTranslation; 37064 } 37065 }, {
37066 key: 'releaseNode', 37067 value: function releaseNode() { 37068 if (this.lockedOnNodeId !== undefined && this.viewFunction !== undefined) { 37069 this.body.emitter.off('initRedraw', this.viewFunction); 37070 this.lockedOnNodeId = undefined; 37071 this.lockedOnNodeOffset = undefined; 37072 } 37073 } 37074 }, { 37075 key: '_transitionRedraw', 37076 37077 /** 37078 * 37079 * @param easingTime 37080 * @private 37081 */ 37082 value: function _transitionRedraw() { 37083 var finished = arguments.length <= 0 || arguments[0] === undefined ? false : arguments[0]; 37084 37085 this.easingTime += this.animationSpeed; 37086 this.easingTime = finished === true ? 1.0 : this.easingTime; 37087 37088 var progress = util.easingFunctions[this.animationEasingFunction](this.easingTime); 37089 37090 this.body.view.scale = this.sourceScale + (this.targetScale - this.sourceScale) * progress; 37091 this.body.view.translation = { 37092 x: this.sourceTranslation.x + (this.targetTranslation.x - this.sourceTranslation.x) * progress, 37093 y: this.sourceTranslation.y + (this.targetTranslation.y - this.sourceTranslation.y) * progress 37094 }; 37095 37096 // cleanup 37097 if (this.easingTime >= 1.0) { 37098 this.body.emitter.off('initRedraw', this.viewFunction); 37099 this.easingTime = 0; 37100 if (this.lockedOnNodeId != undefined) { 37101 this.viewFunction = this._lockedRedraw.bind(this); 37102 this.body.emitter.on('initRedraw', this.viewFunction); 37103 } 37104 this.body.emitter.emit('animationFinished'); 37105 } 37106 } 37107 }, { 37108 key: 'getScale', 37109 value: function getScale() { 37110 return this.body.view.scale; 37111 } 37112 }, { 37113 key: 'getViewPosition', 37114 value: function getViewPosition() { 37115 return this.canvas.DOMtoCanvas({ x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight }); 37116 } 37117 }]); 37118 37119 return View; 37120 })(); 37121 37122 exports['default'] = View; 37123 module.exports = exports['default']; 37124 37125/***/ }, 37126/* 104 */ 37127/***/ function(module, exports) { 37128 37129 "use strict"; 37130 37131 Object.defineProperty(exports, "__esModule", { 37132 value: true 37133 }); 37134 37135 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 37136 37137 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 37138 37139 var NetworkUtil = (function () { 37140 function NetworkUtil() { 37141 _classCallCheck(this, NetworkUtil); 37142 } 37143 37144 _createClass(NetworkUtil, null, [{ 37145 key: "_getRange", 37146 37147 /** 37148 * Find the center position of the network considering the bounding boxes 37149 * @private 37150 */ 37151 value: function _getRange(allNodes) { 37152 var specificNodes = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; 37153 37154 var minY = 1e9, 37155 maxY = -1e9, 37156 minX = 1e9, 37157 maxX = -1e9, 37158 node; 37159 if (specificNodes.length > 0) { 37160 for (var i = 0; i < specificNodes.length; i++) { 37161 node = allNodes[specificNodes[i]]; 37162 if (minX > node.shape.boundingBox.left) { 37163 minX = node.shape.boundingBox.left; 37164 } 37165 if (maxX < node.shape.boundingBox.right) { 37166 maxX = node.shape.boundingBox.right; 37167 } 37168 if (minY > node.shape.boundingBox.top) { 37169 minY = node.shape.boundingBox.top; 37170 } // top is negative, bottom is positive 37171 if (maxY < node.shape.boundingBox.bottom) { 37172 maxY = node.shape.boundingBox.bottom; 37173 } // top is negative, bottom is positive 37174 } 37175 } 37176 37177 if (minX === 1e9 && maxX === -1e9 && minY === 1e9 && maxY === -1e9) { 37178 minY = 0, maxY = 0, minX = 0, maxX = 0; 37179 } 37180 return { minX: minX, maxX: maxX, minY: minY, maxY: maxY }; 37181 } 37182 }, {
37183 key: "_getRangeCore", 37184 37185 /** 37186 * Find the center position of the network 37187 * @private 37188 */ 37189 value: function _getRangeCore(allNodes) { 37190 var specificNodes = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; 37191 37192 var minY = 1e9, 37193 maxY = -1e9, 37194 minX = 1e9, 37195 maxX = -1e9, 37196 node; 37197 if (specificNodes.length > 0) { 37198 for (var i = 0; i < specificNodes.length; i++) { 37199 node = allNodes[specificNodes[i]]; 37200 if (minX > node.x) { 37201 minX = node.x; 37202 } 37203 if (maxX < node.x) { 37204 maxX = node.x; 37205 } 37206 if (minY > node.y) { 37207 minY = node.y; 37208 } // top is negative, bottom is positive 37209 if (maxY < node.y) { 37210 maxY = node.y; 37211 } // top is negative, bottom is positive 37212 } 37213 } 37214 37215 if (minX === 1e9 && maxX === -1e9 && minY === 1e9 && maxY === -1e9) { 37216 minY = 0, maxY = 0, minX = 0, maxX = 0; 37217 } 37218 return { minX: minX, maxX: maxX, minY: minY, maxY: maxY }; 37219 } 37220 }, { 37221 key: "_findCenter", 37222 37223 /** 37224 * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY}; 37225 * @returns {{x: number, y: number}} 37226 * @private 37227 */ 37228 value: function _findCenter(range) { 37229 return { x: 0.5 * (range.maxX + range.minX), 37230 y: 0.5 * (range.maxY + range.minY) }; 37231 } 37232 }]); 37233 37234 return NetworkUtil; 37235 })(); 37236 37237 exports["default"] = NetworkUtil; 37238 module.exports = exports["default"]; 37239 37240/***/ }, 37241/* 105 */ 37242/***/ function(module, exports, __webpack_require__) { 37243 37244 'use strict'; 37245 37246 Object.defineProperty(exports, '__esModule', { 37247 value: true 37248 }); 37249 37250 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 37251 37252 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 37253 37254 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 37255 37256 var _componentsNavigationHandler = __webpack_require__(106); 37257 37258 var _componentsNavigationHandler2 = _interopRequireDefault(_componentsNavigationHandler); 37259 37260 var _componentsPopup = __webpack_require__(107); 37261 37262 var _componentsPopup2 = _interopRequireDefault(_componentsPopup); 37263 37264 var util = __webpack_require__(1); 37265 37266 var InteractionHandler = (function () { 37267 function InteractionHandler(body, canvas, selectionHandler) { 37268 _classCallCheck(this, InteractionHandler); 37269 37270 this.body = body; 37271 this.canvas = canvas; 37272 this.selectionHandler = selectionHandler; 37273 this.navigationHandler = new _componentsNavigationHandler2['default'](body, canvas); 37274 37275 // bind the events from hammer to functions in this object 37276 this.body.eventListeners.onTap = this.onTap.bind(this); 37277 this.body.eventListeners.onTouch = this.onTouch.bind(this); 37278 this.body.eventListeners.onDoubleTap = this.onDoubleTap.bind(this); 37279 this.body.eventListeners.onHold = this.onHold.bind(this); 37280 this.body.eventListeners.onDragStart = this.onDragStart.bind(this); 37281 this.body.eventListeners.onDrag = this.onDrag.bind(this); 37282 this.body.eventListeners.onDragEnd = this.onDragEnd.bind(this); 37283 this.body.eventListeners.onMouseWheel = this.onMouseWheel.bind(this); 37284 this.body.eventListeners.onPinch = this.onPinch.bind(this); 37285 this.body.eventListeners.onMouseMove = this.onMouseMove.bind(this); 37286 this.body.eventListeners.onRelease = this.onRelease.bind(this); 37287 this.body.eventListeners.onContext = this.onContext.bind(this); 37288 37289 this.touchTime = 0; 37290 this.drag = {}; 37291 this.pinch = {}; 37292 this.popup = undefined; 37293 this.popupObj = undefined; 37294 this.popupTimer = undefined; 37295 37296 this.body.functions.getPointer = this.getPointer.bind(this); 37297 37298 this.options = {}; 37299 this.defaultOptions = { 37300 dragNodes: true, 37301 dragView: true, 37302 hover: false, 37303 keyboard: { 37304 enabled: false, 37305 speed: { x: 10, y: 10, zoom: 0.02 }, 37306 bindToWindow: true 37307 },
37308 navigationButtons: false, 37309 tooltipDelay: 300, 37310 zoomView: true 37311 }; 37312 util.extend(this.options, this.defaultOptions); 37313 37314 this.bindEventListeners(); 37315 } 37316 37317 _createClass(InteractionHandler, [{ 37318 key: 'bindEventListeners', 37319 value: function bindEventListeners() { 37320 var _this = this; 37321 37322 this.body.emitter.on('destroy', function () { 37323 clearTimeout(_this.popupTimer); 37324 delete _this.body.functions.getPointer; 37325 }); 37326 } 37327 }, { 37328 key: 'setOptions', 37329 value: function setOptions(options) { 37330 if (options !== undefined) { 37331 // extend all but the values in fields 37332 var fields = ['hideEdgesOnDrag', 'hideNodesOnDrag', 'keyboard', 'multiselect', 'selectable', 'selectConnectedEdges']; 37333 util.selectiveNotDeepExtend(fields, this.options, options); 37334 37335 // merge the keyboard options in. 37336 util.mergeOptions(this.options, options, 'keyboard'); 37337 37338 if (options.tooltip) { 37339 util.extend(this.options.tooltip, options.tooltip); 37340 if (options.tooltip.color) { 37341 this.options.tooltip.color = util.parseColor(options.tooltip.color); 37342 } 37343 } 37344 } 37345 37346 this.navigationHandler.setOptions(this.options); 37347 } 37348 }, { 37349 key: 'getPointer', 37350 37351 /** 37352 * Get the pointer location from a touch location 37353 * @param {{x: Number, y: Number}} touch 37354 * @return {{x: Number, y: Number}} pointer 37355 * @private 37356 */ 37357 value: function getPointer(touch) { 37358 return { 37359 x: touch.x - util.getAbsoluteLeft(this.canvas.frame.canvas), 37360 y: touch.y - util.getAbsoluteTop(this.canvas.frame.canvas) 37361 }; 37362 } 37363 }, { 37364 key: 'onTouch', 37365 37366 /** 37367 * On start of a touch gesture, store the pointer 37368 * @param event 37369 * @private 37370 */ 37371 value: function onTouch(event) { 37372 if (new Date().valueOf() - this.touchTime > 50) { 37373 this.drag.pointer = this.getPointer(event.center); 37374 this.drag.pinched = false; 37375 this.pinch.scale = this.body.view.scale; 37376 // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame) 37377 this.touchTime = new Date().valueOf(); 37378 } 37379 } 37380 }, { 37381 key: 'onTap', 37382 37383 /** 37384 * handle tap/click event: select/unselect a node 37385 * @private 37386 */ 37387 value: function onTap(event) { 37388 var pointer = this.getPointer(event.center); 37389 var multiselect = this.selectionHandler.options.multiselect && (event.changedPointers[0].ctrlKey || event.changedPointers[0].metaKey); 37390 37391 this.checkSelectionChanges(pointer, event, multiselect); 37392 this.selectionHandler._generateClickEvent('click', event, pointer); 37393 } 37394 }, { 37395 key: 'onDoubleTap', 37396 37397 /** 37398 * handle doubletap event 37399 * @private 37400 */ 37401 value: function onDoubleTap(event) { 37402 var pointer = this.getPointer(event.center); 37403 this.selectionHandler._generateClickEvent('doubleClick', event, pointer); 37404 } 37405 }, { 37406 key: 'onHold', 37407 37408 /** 37409 * handle long tap event: multi select nodes 37410 * @private 37411 */ 37412 value: function onHold(event) { 37413 var pointer = this.getPointer(event.center); 37414 var multiselect = this.selectionHandler.options.multiselect; 37415 37416 this.checkSelectionChanges(pointer, event, multiselect); 37417 37418 this.selectionHandler._generateClickEvent('click', event, pointer); 37419 this.selectionHandler._generateClickEvent('hold', event, pointer); 37420 } 37421 }, { 37422 key: 'onRelease', 37423 37424 /** 37425 * handle the release of the screen 37426 * 37427 * @private 37428 */ 37429 value: function onRelease(event) { 37430 if (new Date().valueOf() - this.touchTime > 10) { 37431 var pointer = this.getPointer(event.center); 37432 this.selectionHandler._generateClickEvent('release', event, pointer); 37433 // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame) 37434 this.touchTime = new Date().valueOf(); 37435 } 37436 } 37437 }, {
37438 key: 'onContext', 37439 value: function onContext(event) { 37440 var pointer = this.getPointer({ x: event.clientX, y: event.clientY }); 37441 this.selectionHandler._generateClickEvent('oncontext', event, pointer); 37442 } 37443 }, { 37444 key: 'checkSelectionChanges', 37445 37446 /** 37447 * 37448 * @param pointer 37449 * @param add 37450 */ 37451 value: function checkSelectionChanges(pointer, event) { 37452 var add = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 37453 37454 var previouslySelectedEdgeCount = this.selectionHandler._getSelectedEdgeCount(); 37455 var previouslySelectedNodeCount = this.selectionHandler._getSelectedNodeCount(); 37456 var previousSelection = this.selectionHandler.getSelection(); 37457 var selected = undefined; 37458 if (add === true) { 37459 selected = this.selectionHandler.selectAdditionalOnPoint(pointer); 37460 } else { 37461 selected = this.selectionHandler.selectOnPoint(pointer); 37462 } 37463 var selectedEdgesCount = this.selectionHandler._getSelectedEdgeCount(); 37464 var selectedNodesCount = this.selectionHandler._getSelectedNodeCount(); 37465 var currentSelection = this.selectionHandler.getSelection(); 37466 37467 var _determineIfDifferent2 = this._determineIfDifferent(previousSelection, currentSelection); 37468 37469 var nodesChanges = _determineIfDifferent2.nodesChanges; 37470 var edgesChanges = _determineIfDifferent2.edgesChanges; 37471 37472 var nodeSelected = false; 37473 37474 if (selectedNodesCount - previouslySelectedNodeCount > 0) { 37475 // node was selected 37476 this.selectionHandler._generateClickEvent('selectNode', event, pointer); 37477 selected = true; 37478 nodeSelected = true; 37479 } else if (selectedNodesCount - previouslySelectedNodeCount < 0) { 37480 // node was deselected 37481 this.selectionHandler._generateClickEvent('deselectNode', event, pointer, previousSelection); 37482 selected = true; 37483 } else if (selectedNodesCount === previouslySelectedNodeCount && nodesChanges === true) { 37484 this.selectionHandler._generateClickEvent('deselectNode', event, pointer, previousSelection); 37485 this.selectionHandler._generateClickEvent('selectNode', event, pointer); 37486 nodeSelected = true; 37487 selected = true; 37488 } 37489 37490 // handle the selected edges 37491 if (selectedEdgesCount - previouslySelectedEdgeCount > 0 && nodeSelected === false) { 37492 // edge was selected 37493 this.selectionHandler._generateClickEvent('selectEdge', event, pointer); 37494 selected = true; 37495 } else if (selectedEdgesCount - previouslySelectedEdgeCount < 0) { 37496 // edge was deselected 37497 this.selectionHandler._generateClickEvent('deselectEdge', event, pointer, previousSelection); 37498 selected = true; 37499 } else if (selectedEdgesCount === previouslySelectedEdgeCount && edgesChanges === true) { 37500 this.selectionHandler._generateClickEvent('deselectEdge', event, pointer, previousSelection); 37501 this.selectionHandler._generateClickEvent('selectEdge', event, pointer); 37502 selected = true; 37503 } 37504 37505 // fire the select event if anything has been selected or deselected 37506 if (selected === true) { 37507 // select or unselect 37508 this.selectionHandler._generateClickEvent('select', event, pointer); 37509 } 37510 } 37511 }, { 37512 key: '_determineIfDifferent', 37513 37514 /** 37515 * This function checks if the nodes and edges previously selected have changed. 37516 * @param previousSelection 37517 * @param currentSelection 37518 * @returns {{nodesChanges: boolean, edgesChanges: boolean}} 37519 * @private 37520 */ 37521 value: function _determineIfDifferent(previousSelection, currentSelection) { 37522 var nodesChanges = false; 37523 var edgesChanges = false; 37524 37525 for (var i = 0; i < previousSelection.nodes.length; i++) { 37526 if (currentSelection.nodes.indexOf(previousSelection.nodes[i]) === -1) { 37527 nodesChanges = true; 37528 } 37529 } 37530 for (var i = 0; i < currentSelection.nodes.length; i++) { 37531 if (previousSelection.nodes.indexOf(previousSelection.nodes[i]) === -1) { 37532 nodesChanges = true; 37533 } 37534 } 37535 for (var i = 0; i < previousSelection.edges.length; i++) { 37536 if (currentSelection.edges.indexOf(previousSelection.edges[i]) === -1) { 37537 edgesChanges = true; 37538 } 37539 } 37540 for (var i = 0; i < currentSelection.edges.length; i++) { 37541 if (previousSelection.edges.indexOf(previousSelection.edges[i]) === -1) { 37542 edgesChanges = true; 37543 } 37544 } 37545 37546 return { nodesChanges: nodesChanges, edgesChanges: edgesChanges }; 37547 } 37548 }, {
37549 key: 'onDragStart', 37550 37551 /** 37552 * This function is called by onDragStart. 37553 * It is separated out because we can then overload it for the datamanipulation system. 37554 * 37555 * @private 37556 */ 37557 value: function onDragStart(event) { 37558 //in case the touch event was triggered on an external div, do the initial touch now. 37559 if (this.drag.pointer === undefined) { 37560 this.onTouch(event); 37561 } 37562 37563 // note: drag.pointer is set in onTouch to get the initial touch location 37564 var node = this.selectionHandler.getNodeAt(this.drag.pointer); 37565 37566 this.drag.dragging = true; 37567 this.drag.selection = []; 37568 this.drag.translation = util.extend({}, this.body.view.translation); // copy the object 37569 this.drag.nodeId = undefined; 37570 37571 if (node !== undefined && this.options.dragNodes === true) { 37572 this.drag.nodeId = node.id; 37573 // select the clicked node if not yet selected 37574 if (node.isSelected() === false) { 37575 this.selectionHandler.unselectAll(); 37576 this.selectionHandler.selectObject(node); 37577 } 37578 37579 // after select to contain the node 37580 this.selectionHandler._generateClickEvent('dragStart', event, this.drag.pointer); 37581 37582 var selection = this.selectionHandler.selectionObj.nodes; 37583 // create an array with the selected nodes and their original location and status 37584 for (var nodeId in selection) { 37585 if (selection.hasOwnProperty(nodeId)) { 37586 var object = selection[nodeId]; 37587 var s = { 37588 id: object.id, 37589 node: object, 37590 37591 // store original x, y, xFixed and yFixed, make the node temporarily Fixed 37592 x: object.x, 37593 y: object.y, 37594 xFixed: object.options.fixed.x, 37595 yFixed: object.options.fixed.y 37596 }; 37597 37598 object.options.fixed.x = true; 37599 object.options.fixed.y = true; 37600 37601 this.drag.selection.push(s); 37602 } 37603 } 37604 } else { 37605 // fallback if no node is selected and thus the view is dragged. 37606 this.selectionHandler._generateClickEvent('dragStart', event, this.drag.pointer, undefined, true); 37607 } 37608 } 37609 }, { 37610 key: 'onDrag', 37611 37612 /** 37613 * handle drag event 37614 * @private 37615 */ 37616 value: function onDrag(event) { 37617 var _this2 = this; 37618 37619 if (this.drag.pinched === true) { 37620 return; 37621 } 37622 37623 // remove the focus on node if it is focussed on by the focusOnNode 37624 this.body.emitter.emit('unlockNode'); 37625 37626 var pointer = this.getPointer(event.center); 37627 37628 var selection = this.drag.selection; 37629 if (selection && selection.length && this.options.dragNodes === true) { 37630 (function () { 37631 _this2.selectionHandler._generateClickEvent('dragging', event, pointer); 37632 37633 // calculate delta's and new location 37634 var deltaX = pointer.x - _this2.drag.pointer.x; 37635 var deltaY = pointer.y - _this2.drag.pointer.y; 37636 37637 // update position of all selected nodes 37638 selection.forEach(function (selection) { 37639 var node = selection.node; 37640 // only move the node if it was not fixed initially 37641 if (selection.xFixed === false) { 37642 node.x = _this2.canvas._XconvertDOMtoCanvas(_this2.canvas._XconvertCanvasToDOM(selection.x) + deltaX); 37643 } 37644 // only move the node if it was not fixed initially 37645 if (selection.yFixed === false) { 37646 node.y = _this2.canvas._YconvertDOMtoCanvas(_this2.canvas._YconvertCanvasToDOM(selection.y) + deltaY); 37647 } 37648 }); 37649 37650 // start the simulation of the physics 37651 _this2.body.emitter.emit('startSimulation'); 37652 })(); 37653 } else { 37654 // move the network 37655 if (this.options.dragView === true) { 37656 this.selectionHandler._generateClickEvent('dragging', event, pointer, undefined, true); 37657 37658 // if the drag was not started properly because the click started outside the network div, start it now. 37659 if (this.drag.pointer === undefined) { 37660 this.onDragStart(event); 37661 return; 37662 } 37663 var diffX = pointer.x - this.drag.pointer.x; 37664 var diffY = pointer.y - this.drag.pointer.y; 37665 37666 this.body.view.translation = { x: this.drag.translation.x + diffX, y: this.drag.translation.y + diffY }; 37667 this.body.emitter.emit('_redraw'); 37668 } 37669 } 37670 } 37671 }, {
37672 key: 'onDragEnd', 37673 37674 /** 37675 * handle drag start event 37676 * @private 37677 */ 37678 value: function onDragEnd(event) { 37679 this.drag.dragging = false; 37680 var selection = this.drag.selection; 37681 if (selection && selection.length) { 37682 selection.forEach(function (s) { 37683 // restore original xFixed and yFixed 37684 s.node.options.fixed.x = s.xFixed; 37685 s.node.options.fixed.y = s.yFixed; 37686 }); 37687 this.selectionHandler._generateClickEvent('dragEnd', event, this.getPointer(event.center)); 37688 this.body.emitter.emit('startSimulation'); 37689 } else { 37690 this.selectionHandler._generateClickEvent('dragEnd', event, this.getPointer(event.center), undefined, true); 37691 this.body.emitter.emit('_requestRedraw'); 37692 } 37693 } 37694 }, { 37695 key: 'onPinch', 37696 37697 /** 37698 * Handle pinch event 37699 * @param event 37700 * @private 37701 */ 37702 value: function onPinch(event) { 37703 var pointer = this.getPointer(event.center); 37704 37705 this.drag.pinched = true; 37706 if (this.pinch['scale'] === undefined) { 37707 this.pinch.scale = 1; 37708 } 37709 37710 // TODO: enabled moving while pinching? 37711 var scale = this.pinch.scale * event.scale; 37712 this.zoom(scale, pointer); 37713 } 37714 }, { 37715 key: 'zoom', 37716 37717 /** 37718 * Zoom the network in or out 37719 * @param {Number} scale a number around 1, and between 0.01 and 10 37720 * @param {{x: Number, y: Number}} pointer Position on screen 37721 * @return {Number} appliedScale scale is limited within the boundaries 37722 * @private 37723 */ 37724 value: function zoom(scale, pointer) { 37725 if (this.options.zoomView === true) { 37726 var scaleOld = this.body.view.scale; 37727 if (scale < 0.00001) { 37728 scale = 0.00001; 37729 } 37730 if (scale > 10) { 37731 scale = 10; 37732 } 37733 37734 var preScaleDragPointer = undefined; 37735 if (this.drag !== undefined) { 37736 if (this.drag.dragging === true) { 37737 preScaleDragPointer = this.canvas.DOMtoCanvas(this.drag.pointer); 37738 } 37739 } 37740 // + this.canvas.frame.canvas.clientHeight / 2 37741 var translation = this.body.view.translation; 37742 37743 var scaleFrac = scale / scaleOld; 37744 var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac; 37745 var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac; 37746 37747 this.body.view.scale = scale; 37748 this.body.view.translation = { x: tx, y: ty }; 37749 37750 if (preScaleDragPointer != undefined) { 37751 var postScaleDragPointer = this.canvas.canvasToDOM(preScaleDragPointer); 37752 this.drag.pointer.x = postScaleDragPointer.x; 37753 this.drag.pointer.y = postScaleDragPointer.y; 37754 } 37755 37756 this.body.emitter.emit('_requestRedraw'); 37757 37758 if (scaleOld < scale) { 37759 this.body.emitter.emit('zoom', { direction: '+', scale: this.body.view.scale }); 37760 } else { 37761 this.body.emitter.emit('zoom', { direction: '-', scale: this.body.view.scale }); 37762 } 37763 } 37764 } 37765 }, { 37766 key: 'onMouseWheel', 37767 37768 /** 37769 * Event handler for mouse wheel event, used to zoom the timeline 37770 * See http://adomas.org/javascript-mouse-wheel/ 37771 * https://github.com/EightMedia/hammer.js/issues/256 37772 * @param {MouseEvent} event 37773 * @private 37774 */ 37775 value: function onMouseWheel(event) { 37776 // retrieve delta 37777 var delta = 0; 37778 if (event.wheelDelta) { 37779 /* IE/Opera. */ 37780 delta = event.wheelDelta / 120; 37781 } else if (event.detail) { 37782 /* Mozilla case. */ 37783 // In Mozilla, sign of delta is different than in IE. 37784 // Also, delta is multiple of 3. 37785 delta = -event.detail / 3; 37786 } 37787 37788 // If delta is nonzero, handle it. 37789 // Basically, delta is now positive if wheel was scrolled up, 37790 // and negative, if wheel was scrolled down. 37791 if (delta !== 0) { 37792 37793 // calculate the new scale 37794 var scale = this.body.view.scale; 37795 var zoom = delta / 10; 37796 if (delta < 0) { 37797 zoom = zoom / (1 - zoom); 37798 } 37799 scale *= 1 + zoom; 37800 37801 // calculate the pointer location 37802 var pointer = this.getPointer({ x: event.clientX, y: event.clientY }); 37803 37804 // apply the new scale 37805 this.zoom(scale, pointer); 37806 } 37807 37808 // Prevent default actions caused by mouse wheel. 37809 event.preventDefault(); 37810 } 37811 }, {
37812 key: 'onMouseMove', 37813 37814 /** 37815 * Mouse move handler for checking whether the title moves over a node with a title. 37816 * @param {Event} event 37817 * @private 37818 */ 37819 value: function onMouseMove(event) { 37820 var _this3 = this; 37821 37822 var pointer = this.getPointer({ x: event.clientX, y: event.clientY }); 37823 var popupVisible = false; 37824 37825 // check if the previously selected node is still selected 37826 if (this.popup !== undefined) { 37827 if (this.popup.hidden === false) { 37828 this._checkHidePopup(pointer); 37829 } 37830 37831 // if the popup was not hidden above 37832 if (this.popup.hidden === false) { 37833 popupVisible = true; 37834 this.popup.setPosition(pointer.x + 3, pointer.y - 5); 37835 this.popup.show(); 37836 } 37837 } 37838 37839 // if we bind the keyboard to the div, we have to highlight it to use it. This highlights it on mouse over. 37840 if (this.options.keyboard.bindToWindow === false && this.options.keyboard.enabled === true) { 37841 this.canvas.frame.focus(); 37842 } 37843 37844 // start a timeout that will check if the mouse is positioned above an element 37845 if (popupVisible === false) { 37846 if (this.popupTimer !== undefined) { 37847 clearInterval(this.popupTimer); // stop any running calculationTimer 37848 this.popupTimer = undefined; 37849 } 37850 if (!this.drag.dragging) { 37851 this.popupTimer = setTimeout(function () { 37852 return _this3._checkShowPopup(pointer); 37853 }, this.options.tooltipDelay); 37854 } 37855 } 37856 37857 /** 37858 * Adding hover highlights 37859 */ 37860 if (this.options.hover === true) { 37861 // adding hover highlights 37862 var obj = this.selectionHandler.getNodeAt(pointer); 37863 if (obj === undefined) { 37864 obj = this.selectionHandler.getEdgeAt(pointer); 37865 } 37866 this.selectionHandler.hoverObject(obj); 37867 } 37868 } 37869 }, { 37870 key: '_checkShowPopup', 37871 37872 /** 37873 * Check if there is an element on the given position in the network 37874 * (a node or edge). If so, and if this element has a title, 37875 * show a popup window with its title. 37876 * 37877 * @param {{x:Number, y:Number}} pointer 37878 * @private 37879 */ 37880 value: function _checkShowPopup(pointer) { 37881 var x = this.canvas._XconvertDOMtoCanvas(pointer.x); 37882 var y = this.canvas._YconvertDOMtoCanvas(pointer.y); 37883 var pointerObj = { 37884 left: x, 37885 top: y, 37886 right: x, 37887 bottom: y 37888 }; 37889 37890 var previousPopupObjId = this.popupObj === undefined ? undefined : this.popupObj.id; 37891 var nodeUnderCursor = false; 37892 var popupType = 'node'; 37893 37894 // check if a node is under the cursor. 37895 if (this.popupObj === undefined) { 37896 // search the nodes for overlap, select the top one in case of multiple nodes 37897 var nodeIndices = this.body.nodeIndices; 37898 var nodes = this.body.nodes; 37899 var node = undefined; 37900 var overlappingNodes = []; 37901 for (var i = 0; i < nodeIndices.length; i++) { 37902 node = nodes[nodeIndices[i]]; 37903 if (node.isOverlappingWith(pointerObj) === true) { 37904 if (node.getTitle() !== undefined) { 37905 overlappingNodes.push(nodeIndices[i]); 37906 } 37907 } 37908 } 37909 37910 if (overlappingNodes.length > 0) { 37911 // if there are overlapping nodes, select the last one, this is the one which is drawn on top of the others 37912 this.popupObj = nodes[overlappingNodes[overlappingNodes.length - 1]]; 37913 // if you hover over a node, the title of the edge is not supposed to be shown. 37914 nodeUnderCursor = true; 37915 } 37916 } 37917 37918 if (this.popupObj === undefined && nodeUnderCursor === false) { 37919 // search the edges for overlap 37920 var edgeIndices = this.body.edgeIndices; 37921 var edges = this.body.edges; 37922 var edge = undefined; 37923 var overlappingEdges = []; 37924 for (var i = 0; i < edgeIndices.length; i++) { 37925 edge = edges[edgeIndices[i]]; 37926 if (edge.isOverlappingWith(pointerObj) === true) { 37927 if (edge.connected === true && edge.getTitle() !== undefined) { 37928 overlappingEdges.push(edgeIndices[i]); 37929 } 37930 } 37931 } 37932 37933 if (overlappingEdges.length > 0) { 37934 this.popupObj = edges[overlappingEdges[overlappingEdges.length - 1]]; 37935 popupType = 'edge'; 37936 } 37937 } 37938 37939 if (this.popupObj !== undefined) { 37940 // show popup message window 37941 if (this.popupObj.id !== previousPopupObjId) { 37942 if (this.popup === undefined) { 37943 this.popup = new _componentsPopup2['default'](this.canvas.frame); 37944 } 37945 37946 this.popup.popupTargetType = popupType; 37947 this.popup.popupTargetId = this.popupObj.id; 37948
37949 // adjust a small offset such that the mouse cursor is located in the 37950 // bottom left location of the popup, and you can easily move over the 37951 // popup area 37952 this.popup.setPosition(pointer.x + 3, pointer.y - 5); 37953 this.popup.setText(this.popupObj.getTitle()); 37954 this.popup.show(); 37955 this.body.emitter.emit('showPopup', this.popupObj.id); 37956 } 37957 } else { 37958 if (this.popup !== undefined) { 37959 this.popup.hide(); 37960 this.body.emitter.emit('hidePopup'); 37961 } 37962 } 37963 } 37964 }, { 37965 key: '_checkHidePopup', 37966 37967 /** 37968 * Check if the popup must be hidden, which is the case when the mouse is no 37969 * longer hovering on the object 37970 * @param {{x:Number, y:Number}} pointer 37971 * @private 37972 */ 37973 value: function _checkHidePopup(pointer) { 37974 var pointerObj = this.selectionHandler._pointerToPositionObject(pointer); 37975 37976 var stillOnObj = false; 37977 if (this.popup.popupTargetType === 'node') { 37978 if (this.body.nodes[this.popup.popupTargetId] !== undefined) { 37979 stillOnObj = this.body.nodes[this.popup.popupTargetId].isOverlappingWith(pointerObj); 37980 37981 // if the mouse is still one the node, we have to check if it is not also on one that is drawn on top of it. 37982 // we initially only check stillOnObj because this is much faster. 37983 if (stillOnObj === true) { 37984 var overNode = this.selectionHandler.getNodeAt(pointer); 37985 stillOnObj = overNode.id === this.popup.popupTargetId; 37986 } 37987 } 37988 } else { 37989 if (this.selectionHandler.getNodeAt(pointer) === undefined) { 37990 if (this.body.edges[this.popup.popupTargetId] !== undefined) { 37991 stillOnObj = this.body.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj); 37992 } 37993 } 37994 } 37995 37996 if (stillOnObj === false) { 37997 this.popupObj = undefined; 37998 this.popup.hide(); 37999 this.body.emitter.emit('hidePopup'); 38000 } 38001 } 38002 }]); 38003 38004 return InteractionHandler; 38005 })(); 38006 38007 exports['default'] = InteractionHandler; 38008 module.exports = exports['default']; 38009 38010/***/ }, 38011/* 106 */ 38012/***/ function(module, exports, __webpack_require__) { 38013 38014 'use strict'; 38015 38016 Object.defineProperty(exports, '__esModule', { 38017 value: true 38018 }); 38019 38020 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 38021 38022 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 38023 38024 var util = __webpack_require__(1); 38025 var Hammer = __webpack_require__(20); 38026 var hammerUtil = __webpack_require__(25); 38027 var keycharm = __webpack_require__(41); 38028 38029 var NavigationHandler = (function () { 38030 function NavigationHandler(body, canvas) { 38031 var _this = this; 38032 38033 _classCallCheck(this, NavigationHandler); 38034 38035 this.body = body; 38036 this.canvas = canvas; 38037 38038 this.iconsCreated = false; 38039 this.navigationHammers = []; 38040 this.boundFunctions = {}; 38041 this.touchTime = 0; 38042 this.activated = false; 38043 38044 this.body.emitter.on('activate', function () { 38045 _this.activated = true;_this.configureKeyboardBindings(); 38046 }); 38047 this.body.emitter.on('deactivate', function () { 38048 _this.activated = false;_this.configureKeyboardBindings(); 38049 }); 38050 this.body.emitter.on('destroy', function () { 38051 if (_this.keycharm !== undefined) { 38052 _this.keycharm.destroy(); 38053 } 38054 }); 38055 38056 this.options = {}; 38057 } 38058 38059 _createClass(NavigationHandler, [{ 38060 key: 'setOptions', 38061 value: function setOptions(options) { 38062 if (options !== undefined) { 38063 this.options = options; 38064 this.create(); 38065 } 38066 } 38067 }, {
38068 key: 'create', 38069 value: function create() { 38070 if (this.options.navigationButtons === true) { 38071 if (this.iconsCreated === false) { 38072 this.loadNavigationElements(); 38073 } 38074 } else if (this.iconsCreated === true) { 38075 this.cleanNavigation(); 38076 } 38077 38078 this.configureKeyboardBindings(); 38079 } 38080 }, { 38081 key: 'cleanNavigation', 38082 value: function cleanNavigation() { 38083 // clean hammer bindings 38084 if (this.navigationHammers.length != 0) { 38085 for (var i = 0; i < this.navigationHammers.length; i++) { 38086 this.navigationHammers[i].destroy(); 38087 } 38088 this.navigationHammers = []; 38089 } 38090 38091 // clean up previous navigation items 38092 if (this.navigationDOM && this.navigationDOM['wrapper'] && this.navigationDOM['wrapper'].parentNode) { 38093 this.navigationDOM['wrapper'].parentNode.removeChild(this.navigationDOM['wrapper']); 38094 } 38095 38096 this.iconsCreated = false; 38097 } 38098 }, { 38099 key: 'loadNavigationElements', 38100 38101 /** 38102 * Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation 38103 * they have a triggerFunction which is called on click. If the position of the navigation controls is dependent 38104 * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false. 38105 * This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas. 38106 * 38107 * @private 38108 */ 38109 value: function loadNavigationElements() { 38110 var _this2 = this; 38111 38112 this.cleanNavigation(); 38113 38114 this.navigationDOM = {}; 38115 var navigationDivs = ['up', 'down', 'left', 'right', 'zoomIn', 'zoomOut', 'zoomExtends']; 38116 var navigationDivActions = ['_moveUp', '_moveDown', '_moveLeft', '_moveRight', '_zoomIn', '_zoomOut', '_fit']; 38117 38118 this.navigationDOM['wrapper'] = document.createElement('div'); 38119 this.navigationDOM['wrapper'].className = 'vis-navigation'; 38120 this.canvas.frame.appendChild(this.navigationDOM['wrapper']); 38121 38122 for (var i = 0; i < navigationDivs.length; i++) { 38123 this.navigationDOM[navigationDivs[i]] = document.createElement('div'); 38124 this.navigationDOM[navigationDivs[i]].className = 'vis-button vis-' + navigationDivs[i]; 38125 this.navigationDOM['wrapper'].appendChild(this.navigationDOM[navigationDivs[i]]); 38126 38127 var hammer = new Hammer(this.navigationDOM[navigationDivs[i]]); 38128 if (navigationDivActions[i] === '_fit') { 38129 hammerUtil.onTouch(hammer, this._fit.bind(this)); 38130 } else { 38131 hammerUtil.onTouch(hammer, this.bindToRedraw.bind(this, navigationDivActions[i])); 38132 } 38133 38134 this.navigationHammers.push(hammer); 38135 } 38136 38137 // use a hammer for the release so we do not require the one used in the rest of the network 38138 // the one the rest uses can be overloaded by the manipulation system. 38139 var hammerFrame = new Hammer(this.canvas.frame); 38140 hammerUtil.onRelease(hammerFrame, function () { 38141 _this2._stopMovement(); 38142 }); 38143 this.navigationHammers.push(hammerFrame); 38144 38145 this.iconsCreated = true; 38146 } 38147 }, { 38148 key: 'bindToRedraw', 38149 value: function bindToRedraw(action) { 38150 if (this.boundFunctions[action] === undefined) { 38151 this.boundFunctions[action] = this[action].bind(this); 38152 this.body.emitter.on('initRedraw', this.boundFunctions[action]); 38153 this.body.emitter.emit('_startRendering'); 38154 } 38155 } 38156 }, { 38157 key: 'unbindFromRedraw', 38158 value: function unbindFromRedraw(action) { 38159 if (this.boundFunctions[action] !== undefined) { 38160 this.body.emitter.off('initRedraw', this.boundFunctions[action]); 38161 this.body.emitter.emit('_stopRendering'); 38162 delete this.boundFunctions[action]; 38163 } 38164 } 38165 }, { 38166 key: '_fit', 38167 38168 /** 38169 * this stops all movement induced by the navigation buttons 38170 * 38171 * @private 38172 */ 38173 value: function _fit() { 38174 if (new Date().valueOf() - this.touchTime > 700) {
38175 // TODO: fix ugly hack to avoid hammer's double fireing of event (because we use release?) 38176 this.body.emitter.emit('fit', { duration: 700 }); 38177 this.touchTime = new Date().valueOf(); 38178 } 38179 } 38180 }, { 38181 key: '_stopMovement', 38182 38183 /** 38184 * this stops all movement induced by the navigation buttons 38185 * 38186 * @private 38187 */ 38188 value: function _stopMovement() { 38189 for (var boundAction in this.boundFunctions) { 38190 if (this.boundFunctions.hasOwnProperty(boundAction)) { 38191 this.body.emitter.off('initRedraw', this.boundFunctions[boundAction]); 38192 this.body.emitter.emit('_stopRendering'); 38193 } 38194 } 38195 this.boundFunctions = {}; 38196 } 38197 }, { 38198 key: '_moveUp', 38199 value: function _moveUp() { 38200 this.body.view.translation.y += this.options.keyboard.speed.y; 38201 } 38202 }, { 38203 key: '_moveDown', 38204 value: function _moveDown() { 38205 this.body.view.translation.y -= this.options.keyboard.speed.y; 38206 } 38207 }, { 38208 key: '_moveLeft', 38209 value: function _moveLeft() { 38210 this.body.view.translation.x += this.options.keyboard.speed.x; 38211 } 38212 }, { 38213 key: '_moveRight', 38214 value: function _moveRight() { 38215 this.body.view.translation.x -= this.options.keyboard.speed.x; 38216 } 38217 }, { 38218 key: '_zoomIn', 38219 value: function _zoomIn() { 38220 this.body.view.scale *= 1 + this.options.keyboard.speed.zoom; 38221 this.body.emitter.emit('zoom', { direction: '+', scale: this.body.view.scale }); 38222 } 38223 }, { 38224 key: '_zoomOut', 38225 value: function _zoomOut() { 38226 this.body.view.scale /= 1 + this.options.keyboard.speed.zoom; 38227 this.body.emitter.emit('zoom', { direction: '-', scale: this.body.view.scale }); 38228 } 38229 }, { 38230 key: 'configureKeyboardBindings', 38231 38232 /** 38233 * bind all keys using keycharm. 38234 */ 38235 value: function configureKeyboardBindings() { 38236 var _this3 = this; 38237 38238 if (this.keycharm !== undefined) { 38239 this.keycharm.destroy(); 38240 } 38241 38242 if (this.options.keyboard.enabled === true) { 38243 if (this.options.keyboard.bindToWindow === true) { 38244 this.keycharm = keycharm({ container: window, preventDefault: true }); 38245 } else { 38246 this.keycharm = keycharm({ container: this.canvas.frame, preventDefault: true }); 38247 } 38248 38249 this.keycharm.reset(); 38250 38251 if (this.activated === true) { 38252 this.keycharm.bind('up', function () { 38253 _this3.bindToRedraw('_moveUp'); 38254 }, 'keydown'); 38255 this.keycharm.bind('down', function () { 38256 _this3.bindToRedraw('_moveDown'); 38257 }, 'keydown'); 38258 this.keycharm.bind('left', function () { 38259 _this3.bindToRedraw('_moveLeft'); 38260 }, 'keydown'); 38261 this.keycharm.bind('right', function () { 38262 _this3.bindToRedraw('_moveRight'); 38263 }, 'keydown'); 38264 this.keycharm.bind('=', function () { 38265 _this3.bindToRedraw('_zoomIn'); 38266 }, 'keydown'); 38267 this.keycharm.bind('num+', function () { 38268 _this3.bindToRedraw('_zoomIn'); 38269 }, 'keydown'); 38270 this.keycharm.bind('num-', function () { 38271 _this3.bindToRedraw('_zoomOut'); 38272 }, 'keydown'); 38273 this.keycharm.bind('-', function () { 38274 _this3.bindToRedraw('_zoomOut'); 38275 }, 'keydown'); 38276 this.keycharm.bind('[', function () { 38277 _this3.bindToRedraw('_zoomOut'); 38278 }, 'keydown'); 38279 this.keycharm.bind(']', function () { 38280 _this3.bindToRedraw('_zoomIn'); 38281 }, 'keydown'); 38282 this.keycharm.bind('pageup', function () { 38283 _this3.bindToRedraw('_zoomIn'); 38284 }, 'keydown'); 38285 this.keycharm.bind('pagedown', function () { 38286 _this3.bindToRedraw('_zoomOut'); 38287 }, 'keydown'); 38288 38289 this.keycharm.bind('up', function () { 38290 _this3.unbindFromRedraw('_moveUp'); 38291 }, 'keyup'); 38292 this.keycharm.bind('down', function () { 38293 _this3.unbindFromRedraw('_moveDown'); 38294 }, 'keyup'); 38295 this.keycharm.bind('left', function () { 38296 _this3.unbindFromRedraw('_moveLeft'); 38297 }, 'keyup'); 38298 this.keycharm.bind('right', function () { 38299 _this3.unbindFromRedraw('_moveRight'); 38300 }, 'keyup'); 38301 this.keycharm.bind('=', function () { 38302 _this3.unbindFromRedraw('_zoomIn'); 38303 }, 'keyup'); 38304 this.keycharm.bind('num+', function () { 38305 _this3.unbindFromRedraw('_zoomIn'); 38306 }, 'keyup'); 38307 this.keycharm.bind('num-', function () { 38308 _this3.unbindFromRedraw('_zoomOut'); 38309 }, 'keyup'); 38310 this.keycharm.bind('-', function () { 38311 _this3.unbindFromRedraw('_zoomOut'); 38312 }, 'keyup'); 38313 this.keycharm.bind('[', function () { 38314 _this3.unbindFromRedraw('_zoomOut'); 38315 }, 'keyup'); 38316 this.keycharm.bind(']', function () { 38317 _this3.unbindFromRedraw('_zoomIn'); 38318 }, 'keyup'); 38319 this.keycharm.bind('pageup', function () { 38320 _this3.unbindFromRedraw('_zoomIn'); 38321 }, 'keyup'); 38322 this.keycharm.bind('pagedown', function () { 38323 _this3.unbindFromRedraw('_zoomOut'); 38324 }, 'keyup'); 38325 } 38326 } 38327 } 38328 }]); 38329 38330 return NavigationHandler; 38331 })(); 38332 38333 exports['default'] = NavigationHandler; 38334 module.exports = exports['default']; 38335 38336/***/ }, 38337/* 107 */
38338/***/ function(module, exports) { 38339 38340 /** 38341 * Popup is a class to create a popup window with some text 38342 * @param {Element} container The container object. 38343 * @param {Number} [x] 38344 * @param {Number} [y] 38345 * @param {String} [text] 38346 * @param {Object} [style] An object containing borderColor, 38347 * backgroundColor, etc. 38348 */ 38349 'use strict'; 38350 38351 Object.defineProperty(exports, '__esModule', { 38352 value: true 38353 }); 38354 38355 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 38356 38357 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 38358 38359 var Popup = (function () { 38360 function Popup(container) { 38361 _classCallCheck(this, Popup); 38362 38363 this.container = container; 38364 38365 this.x = 0; 38366 this.y = 0; 38367 this.padding = 5; 38368 this.hidden = false; 38369 38370 // create the frame 38371 this.frame = document.createElement('div'); 38372 this.frame.className = 'vis-network-tooltip'; 38373 this.container.appendChild(this.frame); 38374 } 38375 38376 _createClass(Popup, [{ 38377 key: 'setPosition', 38378 38379 /** 38380 * @param {number} x Horizontal position of the popup window 38381 * @param {number} y Vertical position of the popup window 38382 */ 38383 value: function setPosition(x, y) { 38384 this.x = parseInt(x); 38385 this.y = parseInt(y); 38386 } 38387 }, { 38388 key: 'setText', 38389 38390 /** 38391 * Set the content for the popup window. This can be HTML code or text. 38392 * @param {string | Element} content 38393 */ 38394 value: function setText(content) { 38395 if (content instanceof Element) { 38396 this.frame.innerHTML = ''; 38397 this.frame.appendChild(content); 38398 } else { 38399 this.frame.innerHTML = content; // string containing text or HTML 38400 } 38401 } 38402 }, { 38403 key: 'show', 38404 38405 /** 38406 * Show the popup window 38407 * @param {boolean} [doShow] Show or hide the window 38408 */ 38409 value: function show(doShow) { 38410 if (doShow === undefined) { 38411 doShow = true; 38412 } 38413 38414 if (doShow === true) { 38415 var height = this.frame.clientHeight; 38416 var width = this.frame.clientWidth; 38417 var maxHeight = this.frame.parentNode.clientHeight; 38418 var maxWidth = this.frame.parentNode.clientWidth; 38419 38420 var top = this.y - height; 38421 if (top + height + this.padding > maxHeight) { 38422 top = maxHeight - height - this.padding; 38423 } 38424 if (top < this.padding) { 38425 top = this.padding; 38426 } 38427 38428 var left = this.x; 38429 if (left + width + this.padding > maxWidth) { 38430 left = maxWidth - width - this.padding; 38431 } 38432 if (left < this.padding) { 38433 left = this.padding; 38434 } 38435 38436 this.frame.style.left = left + 'px'; 38437 this.frame.style.top = top + 'px'; 38438 this.frame.style.visibility = 'visible'; 38439 this.hidden = false; 38440 } else { 38441 this.hide(); 38442 } 38443 } 38444 }, { 38445 key: 'hide', 38446 38447 /** 38448 * Hide the popup window 38449 */ 38450 value: function hide() { 38451 this.hidden = true; 38452 this.frame.style.visibility = 'hidden'; 38453 } 38454 }]); 38455 38456 return Popup; 38457 })(); 38458 38459 exports['default'] = Popup; 38460 module.exports = exports['default']; 38461 38462/***/ }, 38463/* 108 */ 38464/***/ function(module, exports, __webpack_require__) { 38465 38466 "use strict"; 38467 38468 Object.defineProperty(exports, "__esModule", { 38469 value: true 38470 }); 38471 38472 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 38473 38474 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 38475 38476 var Node = __webpack_require__(62); 38477 var Edge = __webpack_require__(82); 38478 var util = __webpack_require__(1); 38479 38480 var SelectionHandler = (function () { 38481 function SelectionHandler(body, canvas) { 38482 var _this = this; 38483 38484 _classCallCheck(this, SelectionHandler); 38485 38486 this.body = body; 38487 this.canvas = canvas; 38488 this.selectionObj = { nodes: [], edges: [] }; 38489 this.hoverObj = { nodes: {}, edges: {} }; 38490 38491 this.options = {}; 38492 this.defaultOptions = { 38493 multiselect: false, 38494 selectable: true, 38495 selectConnectedEdges: true, 38496 hoverConnectedEdges: true 38497 }; 38498 util.extend(this.options, this.defaultOptions); 38499 38500 this.body.emitter.on("_dataChanged", function () { 38501 _this.updateSelection(); 38502 }); 38503 } 38504 38505 _createClass(SelectionHandler, [{ 38506 key: "setOptions", 38507 value: function setOptions(options) { 38508 if (options !== undefined) { 38509 var fields = ["multiselect", "hoverConnectedEdges", "selectable", "selectConnectedEdges"]; 38510 util.selectiveDeepExtend(fields, this.options, options); 38511 } 38512 } 38513 }, {
38514 key: "selectOnPoint", 38515 38516 /** 38517 * handles the selection part of the tap; 38518 * 38519 * @param {Object} pointer 38520 * @private 38521 */ 38522 value: function selectOnPoint(pointer) { 38523 var selected = false; 38524 if (this.options.selectable === true) { 38525 var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer); 38526 38527 // unselect after getting the objects in order to restore width and height. 38528 this.unselectAll(); 38529 38530 if (obj !== undefined) { 38531 selected = this.selectObject(obj); 38532 } 38533 this.body.emitter.emit("_requestRedraw"); 38534 } 38535 return selected; 38536 } 38537 }, { 38538 key: "selectAdditionalOnPoint", 38539 value: function selectAdditionalOnPoint(pointer) { 38540 var selectionChanged = false; 38541 if (this.options.selectable === true) { 38542 var obj = this.getNodeAt(pointer) || this.getEdgeAt(pointer); 38543 38544 if (obj !== undefined) { 38545 selectionChanged = true; 38546 if (obj.isSelected() === true) { 38547 this.deselectObject(obj); 38548 } else { 38549 this.selectObject(obj); 38550 } 38551 38552 this.body.emitter.emit("_requestRedraw"); 38553 } 38554 } 38555 return selectionChanged; 38556 } 38557 }, { 38558 key: "_generateClickEvent", 38559 value: function _generateClickEvent(eventType, event, pointer, oldSelection) { 38560 var emptySelection = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4]; 38561 38562 var properties = undefined; 38563 if (emptySelection === true) { 38564 properties = { nodes: [], edges: [] }; 38565 } else { 38566 properties = this.getSelection(); 38567 } 38568 properties["pointer"] = { 38569 DOM: { x: pointer.x, y: pointer.y }, 38570 canvas: this.canvas.DOMtoCanvas(pointer) 38571 }; 38572 properties["event"] = event; 38573 38574 if (oldSelection !== undefined) { 38575 properties["previousSelection"] = oldSelection; 38576 } 38577 this.body.emitter.emit(eventType, properties); 38578 } 38579 }, { 38580 key: "selectObject", 38581 value: function selectObject(obj) { 38582 var highlightEdges = arguments.length <= 1 || arguments[1] === undefined ? this.options.selectConnectedEdges : arguments[1]; 38583 38584 if (obj !== undefined) { 38585 if (obj instanceof Node) { 38586 if (highlightEdges === true) { 38587 this._selectConnectedEdges(obj); 38588 } 38589 } 38590 obj.select(); 38591 this._addToSelection(obj); 38592 return true; 38593 } 38594 return false; 38595 } 38596 }, { 38597 key: "deselectObject", 38598 value: function deselectObject(obj) { 38599 if (obj.isSelected() === true) { 38600 obj.selected = false; 38601 this._removeFromSelection(obj); 38602 } 38603 } 38604 }, { 38605 key: "_getAllNodesOverlappingWith", 38606 38607 /** 38608 * retrieve all nodes overlapping with given object 38609 * @param {Object} object An object with parameters left, top, right, bottom 38610 * @return {Number[]} An array with id's of the overlapping nodes 38611 * @private 38612 */ 38613 value: function _getAllNodesOverlappingWith(object) { 38614 var overlappingNodes = []; 38615 var nodes = this.body.nodes; 38616 for (var i = 0; i < this.body.nodeIndices.length; i++) { 38617 var nodeId = this.body.nodeIndices[i]; 38618 if (nodes[nodeId].isOverlappingWith(object)) { 38619 overlappingNodes.push(nodeId); 38620 } 38621 } 38622 return overlappingNodes; 38623 } 38624 }, { 38625 key: "_pointerToPositionObject", 38626 38627 /** 38628 * Return a position object in canvasspace from a single point in screenspace 38629 * 38630 * @param pointer 38631 * @returns {{left: number, top: number, right: number, bottom: number}} 38632 * @private 38633 */ 38634 value: function _pointerToPositionObject(pointer) { 38635 var canvasPos = this.canvas.DOMtoCanvas(pointer); 38636 return { 38637 left: canvasPos.x - 1, 38638 top: canvasPos.y + 1, 38639 right: canvasPos.x + 1, 38640 bottom: canvasPos.y - 1 38641 }; 38642 } 38643 }, { 38644 key: "getNodeAt", 38645 38646 /** 38647 * Get the top node at the a specific point (like a click) 38648 * 38649 * @param {{x: Number, y: Number}} pointer 38650 * @return {Node | undefined} node 38651 * @private 38652 */ 38653 value: function getNodeAt(pointer) { 38654 var returnNode = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; 38655 38656 // we first check if this is an navigation controls element 38657 var positionObject = this._pointerToPositionObject(pointer); 38658 var overlappingNodes = this._getAllNodesOverlappingWith(positionObject); 38659 // if there are overlapping nodes, select the last one, this is the 38660 // one which is drawn on top of the others 38661 if (overlappingNodes.length > 0) { 38662 if (returnNode === true) { 38663 return this.body.nodes[overlappingNodes[overlappingNodes.length - 1]]; 38664 } else { 38665 return overlappingNodes[overlappingNodes.length - 1]; 38666 } 38667 } else { 38668 return undefined; 38669 } 38670 } 38671 }, {
38672 key: "_getEdgesOverlappingWith", 38673 38674 /** 38675 * retrieve all edges overlapping with given object, selector is around center 38676 * @param {Object} object An object with parameters left, top, right, bottom 38677 * @return {Number[]} An array with id's of the overlapping nodes 38678 * @private 38679 */ 38680 value: function _getEdgesOverlappingWith(object, overlappingEdges) { 38681 var edges = this.body.edges; 38682 for (var i = 0; i < this.body.edgeIndices.length; i++) { 38683 var edgeId = this.body.edgeIndices[i]; 38684 if (edges[edgeId].isOverlappingWith(object)) { 38685 overlappingEdges.push(edgeId); 38686 } 38687 } 38688 } 38689 }, { 38690 key: "_getAllEdgesOverlappingWith", 38691 38692 /** 38693 * retrieve all nodes overlapping with given object 38694 * @param {Object} object An object with parameters left, top, right, bottom 38695 * @return {Number[]} An array with id's of the overlapping nodes 38696 * @private 38697 */ 38698 value: function _getAllEdgesOverlappingWith(object) { 38699 var overlappingEdges = []; 38700 this._getEdgesOverlappingWith(object, overlappingEdges); 38701 return overlappingEdges; 38702 } 38703 }, { 38704 key: "getEdgeAt", 38705 38706 /** 38707 * Place holder. To implement change the getNodeAt to a _getObjectAt. Have the _getObjectAt call 38708 * getNodeAt and _getEdgesAt, then priortize the selection to user preferences. 38709 * 38710 * @param pointer 38711 * @returns {undefined} 38712 * @private 38713 */ 38714 value: function getEdgeAt(pointer) { 38715 var returnEdge = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; 38716 38717 var positionObject = this._pointerToPositionObject(pointer); 38718 var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject); 38719 38720 if (overlappingEdges.length > 0) { 38721 if (returnEdge === true) { 38722 return this.body.edges[overlappingEdges[overlappingEdges.length - 1]]; 38723 } else { 38724 return overlappingEdges[overlappingEdges.length - 1]; 38725 } 38726 } else { 38727 return undefined; 38728 } 38729 } 38730 }, { 38731 key: "_addToSelection", 38732 38733 /** 38734 * Add object to the selection array. 38735 * 38736 * @param obj 38737 * @private 38738 */ 38739 value: function _addToSelection(obj) { 38740 if (obj instanceof Node) { 38741 this.selectionObj.nodes[obj.id] = obj; 38742 } else { 38743 this.selectionObj.edges[obj.id] = obj; 38744 } 38745 } 38746 }, { 38747 key: "_addToHover", 38748 38749 /** 38750 * Add object to the selection array. 38751 * 38752 * @param obj 38753 * @private 38754 */ 38755 value: function _addToHover(obj) { 38756 if (obj instanceof Node) { 38757 this.hoverObj.nodes[obj.id] = obj; 38758 } else { 38759 this.hoverObj.edges[obj.id] = obj; 38760 } 38761 } 38762 }, { 38763 key: "_removeFromSelection", 38764 38765 /** 38766 * Remove a single option from selection. 38767 * 38768 * @param {Object} obj 38769 * @private 38770 */ 38771 value: function _removeFromSelection(obj) { 38772 if (obj instanceof Node) { 38773 delete this.selectionObj.nodes[obj.id]; 38774 } else { 38775 delete this.selectionObj.edges[obj.id]; 38776 } 38777 } 38778 }, { 38779 key: "unselectAll", 38780 38781 /** 38782 * Unselect all. The selectionObj is useful for this. 38783 * 38784 * @private 38785 */ 38786 value: function unselectAll() { 38787 for (var nodeId in this.selectionObj.nodes) { 38788 if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { 38789 this.selectionObj.nodes[nodeId].unselect(); 38790 } 38791 } 38792 for (var edgeId in this.selectionObj.edges) { 38793 if (this.selectionObj.edges.hasOwnProperty(edgeId)) { 38794 this.selectionObj.edges[edgeId].unselect(); 38795 } 38796 } 38797 38798 this.selectionObj = { nodes: {}, edges: {} }; 38799 } 38800 }, { 38801 key: "_getSelectedNodeCount", 38802 38803 /** 38804 * return the number of selected nodes 38805 * 38806 * @returns {number} 38807 * @private 38808 */ 38809 value: function _getSelectedNodeCount() { 38810 var count = 0; 38811 for (var nodeId in this.selectionObj.nodes) { 38812 if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { 38813 count += 1; 38814 } 38815 } 38816 return count; 38817 } 38818 }, {
38819 key: "_getSelectedNode", 38820 38821 /** 38822 * return the selected node 38823 * 38824 * @returns {number} 38825 * @private 38826 */ 38827 value: function _getSelectedNode() { 38828 for (var nodeId in this.selectionObj.nodes) { 38829 if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { 38830 return this.selectionObj.nodes[nodeId]; 38831 } 38832 } 38833 return undefined; 38834 } 38835 }, { 38836 key: "_getSelectedEdge", 38837 38838 /** 38839 * return the selected edge 38840 * 38841 * @returns {number} 38842 * @private 38843 */ 38844 value: function _getSelectedEdge() { 38845 for (var edgeId in this.selectionObj.edges) { 38846 if (this.selectionObj.edges.hasOwnProperty(edgeId)) { 38847 return this.selectionObj.edges[edgeId]; 38848 } 38849 } 38850 return undefined; 38851 } 38852 }, { 38853 key: "_getSelectedEdgeCount", 38854 38855 /** 38856 * return the number of selected edges 38857 * 38858 * @returns {number} 38859 * @private 38860 */ 38861 value: function _getSelectedEdgeCount() { 38862 var count = 0; 38863 for (var edgeId in this.selectionObj.edges) { 38864 if (this.selectionObj.edges.hasOwnProperty(edgeId)) { 38865 count += 1; 38866 } 38867 } 38868 return count; 38869 } 38870 }, { 38871 key: "_getSelectedObjectCount", 38872 38873 /** 38874 * return the number of selected objects. 38875 * 38876 * @returns {number} 38877 * @private 38878 */ 38879 value: function _getSelectedObjectCount() { 38880 var count = 0; 38881 for (var nodeId in this.selectionObj.nodes) { 38882 if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { 38883 count += 1; 38884 } 38885 } 38886 for (var edgeId in this.selectionObj.edges) { 38887 if (this.selectionObj.edges.hasOwnProperty(edgeId)) { 38888 count += 1; 38889 } 38890 } 38891 return count; 38892 } 38893 }, { 38894 key: "_selectionIsEmpty", 38895 38896 /** 38897 * Check if anything is selected 38898 * 38899 * @returns {boolean} 38900 * @private 38901 */ 38902 value: function _selectionIsEmpty() { 38903 for (var nodeId in this.selectionObj.nodes) { 38904 if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { 38905 return false; 38906 } 38907 } 38908 for (var edgeId in this.selectionObj.edges) { 38909 if (this.selectionObj.edges.hasOwnProperty(edgeId)) { 38910 return false; 38911 } 38912 } 38913 return true; 38914 } 38915 }, { 38916 key: "_clusterInSelection", 38917 38918 /** 38919 * check if one of the selected nodes is a cluster. 38920 * 38921 * @returns {boolean} 38922 * @private 38923 */ 38924 value: function _clusterInSelection() { 38925 for (var nodeId in this.selectionObj.nodes) { 38926 if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { 38927 if (this.selectionObj.nodes[nodeId].clusterSize > 1) { 38928 return true; 38929 } 38930 } 38931 } 38932 return false; 38933 } 38934 }, { 38935 key: "_selectConnectedEdges", 38936 38937 /** 38938 * select the edges connected to the node that is being selected 38939 * 38940 * @param {Node} node 38941 * @private 38942 */ 38943 value: function _selectConnectedEdges(node) { 38944 for (var i = 0; i < node.edges.length; i++) { 38945 var edge = node.edges[i]; 38946 edge.select(); 38947 this._addToSelection(edge); 38948 } 38949 } 38950 }, { 38951 key: "_hoverConnectedEdges", 38952 38953 /** 38954 * select the edges connected to the node that is being selected 38955 * 38956 * @param {Node} node 38957 * @private 38958 */ 38959 value: function _hoverConnectedEdges(node) { 38960 for (var i = 0; i < node.edges.length; i++) { 38961 var edge = node.edges[i]; 38962 edge.hover = true; 38963 this._addToHover(edge); 38964 } 38965 } 38966 }, { 38967 key: "_unselectConnectedEdges", 38968 38969 /** 38970 * unselect the edges connected to the node that is being selected 38971 * 38972 * @param {Node} node 38973 * @private 38974 */ 38975 value: function _unselectConnectedEdges(node) { 38976 for (var i = 0; i < node.edges.length; i++) { 38977 var edge = node.edges[i]; 38978 edge.unselect(); 38979 this._removeFromSelection(edge); 38980 } 38981 } 38982 }, {
38983 key: "blurObject", 38984 38985 /** 38986 * This is called when someone clicks on a node. either select or deselect it. 38987 * If there is an existing selection and we don't want to append to it, clear the existing selection 38988 * 38989 * @param {Node || Edge} object 38990 * @private 38991 */ 38992 value: function blurObject(object) { 38993 if (object.hover === true) { 38994 object.hover = false; 38995 if (object instanceof Node) { 38996 this.body.emitter.emit("blurNode", { node: object.id }); 38997 } else { 38998 this.body.emitter.emit("blurEdge", { edge: object.id }); 38999 } 39000 } 39001 } 39002 }, { 39003 key: "hoverObject", 39004 39005 /** 39006 * This is called when someone clicks on a node. either select or deselect it. 39007 * If there is an existing selection and we don't want to append to it, clear the existing selection 39008 * 39009 * @param {Node || Edge} object 39010 * @private 39011 */ 39012 value: function hoverObject(object) { 39013 var hoverChanged = false; 39014 // remove all node hover highlights 39015 for (var nodeId in this.hoverObj.nodes) { 39016 if (this.hoverObj.nodes.hasOwnProperty(nodeId)) { 39017 if (object === undefined || object instanceof Node && object.id != nodeId || object instanceof Edge) { 39018 this.blurObject(this.hoverObj.nodes[nodeId]); 39019 delete this.hoverObj.nodes[nodeId]; 39020 hoverChanged = true; 39021 } 39022 } 39023 } 39024 39025 // removing all edge hover highlights 39026 for (var edgeId in this.hoverObj.edges) { 39027 if (this.hoverObj.edges.hasOwnProperty(edgeId)) { 39028 // if the hover has been changed here it means that the node has been hovered over or off 39029 // we then do not use the blurObject method here. 39030 if (hoverChanged === true) { 39031 this.hoverObj.edges[edgeId].hover = false; 39032 delete this.hoverObj.edges[edgeId]; 39033 } 39034 // if the blur remains the same and the object is undefined (mouse off), we blur the edge 39035 else if (object === undefined) { 39036 this.blurObject(this.hoverObj.edges[edgeId]); 39037 delete this.hoverObj.edges[edgeId]; 39038 hoverChanged = true; 39039 } 39040 } 39041 } 39042 39043 if (object !== undefined) { 39044 if (object.hover === false) { 39045 object.hover = true; 39046 this._addToHover(object); 39047 hoverChanged = true; 39048 if (object instanceof Node) { 39049 this.body.emitter.emit("hoverNode", { node: object.id }); 39050 } else { 39051 this.body.emitter.emit("hoverEdge", { edge: object.id }); 39052 } 39053 } 39054 if (object instanceof Node && this.options.hoverConnectedEdges === true) { 39055 this._hoverConnectedEdges(object); 39056 } 39057 } 39058 39059 if (hoverChanged === true) { 39060 this.body.emitter.emit("_requestRedraw"); 39061 } 39062 } 39063 }, { 39064 key: "getSelection", 39065 39066 /** 39067 * 39068 * retrieve the currently selected objects 39069 * @return {{nodes: Array.<String>, edges: Array.<String>}} selection 39070 */ 39071 value: function getSelection() { 39072 var nodeIds = this.getSelectedNodes(); 39073 var edgeIds = this.getSelectedEdges(); 39074 return { nodes: nodeIds, edges: edgeIds }; 39075 } 39076 }, { 39077 key: "getSelectedNodes", 39078 39079 /** 39080 * 39081 * retrieve the currently selected nodes 39082 * @return {String[]} selection An array with the ids of the 39083 * selected nodes. 39084 */ 39085 value: function getSelectedNodes() { 39086 var idArray = []; 39087 if (this.options.selectable === true) { 39088 for (var nodeId in this.selectionObj.nodes) { 39089 if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { 39090 idArray.push(nodeId); 39091 } 39092 } 39093 } 39094 return idArray; 39095 } 39096 }, { 39097 key: "getSelectedEdges", 39098 39099 /** 39100 * 39101 * retrieve the currently selected edges 39102 * @return {Array} selection An array with the ids of the 39103 * selected nodes. 39104 */ 39105 value: function getSelectedEdges() { 39106 var idArray = []; 39107 if (this.options.selectable === true) { 39108 for (var edgeId in this.selectionObj.edges) { 39109 if (this.selectionObj.edges.hasOwnProperty(edgeId)) { 39110 idArray.push(edgeId); 39111 } 39112 } 39113 } 39114 return idArray; 39115 } 39116 }, {
39117 key: "selectNodes", 39118 39119 /** 39120 * select zero or more nodes with the option to highlight edges 39121 * @param {Number[] | String[]} selection An array with the ids of the 39122 * selected nodes. 39123 * @param {boolean} [highlightEdges] 39124 */ 39125 value: function selectNodes(selection) { 39126 var highlightEdges = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; 39127 39128 var i = undefined, 39129 id = undefined; 39130 39131 if (!selection || selection.length === undefined) throw "Selection must be an array with ids"; 39132 39133 // first unselect any selected node 39134 this.unselectAll(); 39135 39136 for (i = 0; i < selection.length; i++) { 39137 id = selection[i]; 39138 39139 var node = this.body.nodes[id]; 39140 if (!node) { 39141 throw new RangeError("Node with id \"" + id + "\" not found"); 39142 } 39143 this.selectObject(node, highlightEdges); 39144 } 39145 this.body.emitter.emit("_requestRedraw"); 39146 } 39147 }, { 39148 key: "selectEdges", 39149 39150 /** 39151 * select zero or more edges 39152 * @param {Number[] | String[]} selection An array with the ids of the 39153 * selected nodes. 39154 */ 39155 value: function selectEdges(selection) { 39156 var i = undefined, 39157 id = undefined; 39158 39159 if (!selection || selection.length === undefined) throw "Selection must be an array with ids"; 39160 39161 // first unselect any selected objects 39162 this.unselectAll(); 39163 39164 for (i = 0; i < selection.length; i++) { 39165 id = selection[i]; 39166 39167 var edge = this.body.edges[id]; 39168 if (!edge) { 39169 throw new RangeError("Edge with id \"" + id + "\" not found"); 39170 } 39171 this.selectObject(edge); 39172 } 39173 this.body.emitter.emit("_requestRedraw"); 39174 } 39175 }, { 39176 key: "updateSelection", 39177 39178 /** 39179 * Validate the selection: remove ids of nodes which no longer exist 39180 * @private 39181 */ 39182 value: function updateSelection() { 39183 for (var nodeId in this.selectionObj.nodes) { 39184 if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { 39185 if (!this.body.nodes.hasOwnProperty(nodeId)) { 39186 delete this.selectionObj.nodes[nodeId]; 39187 } 39188 } 39189 } 39190 for (var edgeId in this.selectionObj.edges) { 39191 if (this.selectionObj.edges.hasOwnProperty(edgeId)) { 39192 if (!this.body.edges.hasOwnProperty(edgeId)) { 39193 delete this.selectionObj.edges[edgeId]; 39194 } 39195 } 39196 } 39197 } 39198 }]); 39199 39200 return SelectionHandler; 39201 })(); 39202 39203 exports["default"] = SelectionHandler; 39204 module.exports = exports["default"]; 39205 39206/***/ }, 39207/* 109 */ 39208/***/ function(module, exports, __webpack_require__) { 39209 39210 'use strict'; 39211 39212 Object.defineProperty(exports, '__esModule', { 39213 value: true 39214 }); 39215 39216 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 39217 39218 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 39219 39220 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 39221 39222 var _NetworkUtil = __webpack_require__(104); 39223 39224 var _NetworkUtil2 = _interopRequireDefault(_NetworkUtil); 39225 39226 var util = __webpack_require__(1); 39227 39228 var LayoutEngine = (function () { 39229 function LayoutEngine(body) { 39230 _classCallCheck(this, LayoutEngine); 39231 39232 this.body = body; 39233 39234 this.initialRandomSeed = Math.round(Math.random() * 1000000); 39235 this.randomSeed = this.initialRandomSeed; 39236 this.options = {}; 39237 this.optionsBackup = {}; 39238 39239 this.defaultOptions = { 39240 randomSeed: undefined, 39241 improvedLayout: true, 39242 hierarchical: { 39243 enabled: false, 39244 levelSeparation: 150, 39245 direction: 'UD', // UD, DU, LR, RL 39246 sortMethod: 'hubsize' // hubsize, directed 39247 } 39248 }; 39249 util.extend(this.options, this.defaultOptions); 39250 39251 this.hierarchicalLevels = {}; 39252 39253 this.bindEventListeners(); 39254 } 39255 39256 _createClass(LayoutEngine, [{ 39257 key: 'bindEventListeners', 39258 value: function bindEventListeners() { 39259 var _this = this; 39260 39261 this.body.emitter.on('_dataChanged', function () { 39262 _this.setupHierarchicalLayout(); 39263 }); 39264 this.body.emitter.on('_dataLoaded', function () { 39265 _this.layoutNetwork(); 39266 }); 39267 this.body.emitter.on('_resetHierarchicalLayout', function () { 39268 _this.setupHierarchicalLayout(); 39269 }); 39270 } 39271 }, {
39272 key: 'setOptions', 39273 value: function setOptions(options, allOptions) { 39274 if (options !== undefined) { 39275 var prevHierarchicalState = this.options.hierarchical.enabled; 39276 util.selectiveDeepExtend(['randomSeed', 'improvedLayout'], this.options, options); 39277 util.mergeOptions(this.options, options, 'hierarchical'); 39278 if (options.randomSeed !== undefined) { 39279 this.initialRandomSeed = options.randomSeed; 39280 } 39281 39282 if (this.options.hierarchical.enabled === true) { 39283 if (prevHierarchicalState === true) { 39284 // refresh the overridden options for nodes and edges. 39285 this.body.emitter.emit('refresh', true); 39286 } 39287 39288 // make sure the level seperation is the right way up 39289 if (this.options.hierarchical.direction === 'RL' || this.options.hierarchical.direction === 'DU') { 39290 if (this.options.hierarchical.levelSeparation > 0) { 39291 this.options.hierarchical.levelSeparation *= -1; 39292 } 39293 } else { 39294 if (this.options.hierarchical.levelSeparation < 0) { 39295 this.options.hierarchical.levelSeparation *= -1; 39296 } 39297 } 39298 39299 this.body.emitter.emit('_resetHierarchicalLayout'); 39300 // because the hierarchical system needs it's own physics and smooth curve settings, we adapt the other options if needed. 39301 return this.adaptAllOptions(allOptions); 39302 } else { 39303 if (prevHierarchicalState === true) { 39304 // refresh the overridden options for nodes and edges. 39305 this.body.emitter.emit('refresh'); 39306 return util.deepExtend(allOptions, this.optionsBackup); 39307 } 39308 } 39309 } 39310 return allOptions; 39311 } 39312 }, { 39313 key: 'adaptAllOptions', 39314 value: function adaptAllOptions(allOptions) { 39315 if (this.options.hierarchical.enabled === true) { 39316 // set the physics 39317 if (allOptions.physics === undefined || allOptions.physics === true) { 39318 allOptions.physics = { solver: 'hierarchicalRepulsion' }; 39319 this.optionsBackup.physics = { solver: 'barnesHut' }; 39320 } else if (typeof allOptions.physics === 'object') { 39321 this.optionsBackup.physics = { solver: 'barnesHut' }; 39322 if (allOptions.physics.solver !== undefined) { 39323 this.optionsBackup.physics = { solver: allOptions.physics.solver }; 39324 } 39325 allOptions.physics['solver'] = 'hierarchicalRepulsion'; 39326 } else if (allOptions.physics !== false) { 39327 this.optionsBackup.physics = { solver: 'barnesHut' }; 39328 allOptions.physics['solver'] = 'hierarchicalRepulsion'; 39329 } 39330 39331 // get the type of static smooth curve in case it is required 39332 var type = 'horizontal'; 39333 if (this.options.hierarchical.direction === 'RL' || this.options.hierarchical.direction === 'LR') { 39334 type = 'vertical'; 39335 } 39336 39337 // disable smooth curves if nothing is defined. If smooth curves have been turned on, turn them into static smooth curves. 39338 if (allOptions.edges === undefined) { 39339 this.optionsBackup.edges = { smooth: { enabled: true, type: 'dynamic' } }; 39340 allOptions.edges = { smooth: false }; 39341 } else if (allOptions.edges.smooth === undefined) { 39342 this.optionsBackup.edges = { smooth: { enabled: true, type: 'dynamic' } }; 39343 allOptions.edges.smooth = false; 39344 } else { 39345 if (typeof allOptions.edges.smooth === 'boolean') { 39346 this.optionsBackup.edges = { smooth: allOptions.edges.smooth }; 39347 allOptions.edges.smooth = { enabled: allOptions.edges.smooth, type: type }; 39348 } else { 39349 // allow custom types except for dynamic 39350 if (allOptions.edges.smooth.type !== undefined && allOptions.edges.smooth.type !== 'dynamic') { 39351 type = allOptions.edges.smooth.type; 39352 } 39353 39354 this.optionsBackup.edges = { 39355 smooth: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled, 39356 type: allOptions.edges.smooth.type === undefined ? 'dynamic' : allOptions.edges.smooth.type, 39357 roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness, 39358 forceDirection: allOptions.edges.smooth.forceDirection === undefined ? false : allOptions.edges.smooth.forceDirection 39359 }; 39360 allOptions.edges.smooth = { 39361 enabled: allOptions.edges.smooth.enabled === undefined ? true : allOptions.edges.smooth.enabled, 39362 type: type, 39363 roundness: allOptions.edges.smooth.roundness === undefined ? 0.5 : allOptions.edges.smooth.roundness, 39364 forceDirection: allOptions.edges.smooth.forceDirection === undefined ? false : allOptions.edges.smooth.forceDirection 39365 }; 39366 } 39367 } 39368
39369 // force all edges into static smooth curves. Only applies to edges that do not use the global options for smooth. 39370 this.body.emitter.emit('_forceDisableDynamicCurves', type); 39371 } 39372 return allOptions; 39373 } 39374 }, { 39375 key: 'seededRandom', 39376 value: function seededRandom() { 39377 var x = Math.sin(this.randomSeed++) * 10000; 39378 return x - Math.floor(x); 39379 } 39380 }, { 39381 key: 'positionInitially', 39382 value: function positionInitially(nodesArray) { 39383 if (this.options.hierarchical.enabled !== true) { 39384 this.randomSeed = this.initialRandomSeed; 39385 for (var i = 0; i < nodesArray.length; i++) { 39386 var node = nodesArray[i]; 39387 var radius = 10 * 0.1 * nodesArray.length + 10; 39388 var angle = 2 * Math.PI * this.seededRandom(); 39389 if (node.x === undefined) { 39390 node.x = radius * Math.cos(angle); 39391 } 39392 if (node.y === undefined) { 39393 node.y = radius * Math.sin(angle); 39394 } 39395 } 39396 } 39397 } 39398 }, { 39399 key: 'layoutNetwork', 39400 39401 /** 39402 * Use KamadaKawai to position nodes. This is quite a heavy algorithm so if there are a lot of nodes we 39403 * cluster them first to reduce the amount. 39404 */ 39405 value: function layoutNetwork() { 39406 if (this.options.hierarchical.enabled !== true && this.options.improvedLayout === true) { 39407 // first check if we should KamadaKawai to layout. The threshold is if less than half of the visible 39408 // nodes have predefined positions we use this. 39409 var positionDefined = 0; 39410 for (var i = 0; i < this.body.nodeIndices.length; i++) { 39411 var node = this.body.nodes[this.body.nodeIndices[i]]; 39412 if (node.predefinedPosition === true) { 39413 positionDefined += 1; 39414 } 39415 } 39416 39417 // if less than half of the nodes have a predefined position we continue 39418 if (positionDefined < 0.5 * this.body.nodeIndices.length) { 39419 var MAX_LEVELS = 10; 39420 var level = 0; 39421 var clusterThreshold = 100; 39422 // if there are a lot of nodes, we cluster before we run the algorithm. 39423 if (this.body.nodeIndices.length > clusterThreshold) { 39424 var startLength = this.body.nodeIndices.length; 39425 while (this.body.nodeIndices.length > clusterThreshold) { 39426 //console.time("clustering") 39427 level += 1; 39428 var before = this.body.nodeIndices.length; 39429 // if there are many nodes we do a hubsize cluster 39430 if (level % 3 === 0) { 39431 this.body.modules.clustering.clusterBridges(); 39432 } else { 39433 this.body.modules.clustering.clusterOutliers(); 39434 } 39435 var after = this.body.nodeIndices.length; 39436 if (before == after && level % 3 !== 0 || level > MAX_LEVELS) { 39437 this._declusterAll(); 39438 this.body.emitter.emit('_layoutFailed'); 39439 console.info('This network could not be positioned by this version of the improved layout algorithm. Please disable improvedLayout for better performance.'); 39440 return; 39441 } 39442 //console.timeEnd("clustering") 39443 //console.log(level,after) 39444 } 39445 // increase the size of the edges 39446 this.body.modules.kamadaKawai.setOptions({ springLength: Math.max(150, 2 * startLength) }); 39447 } 39448 39449 // position the system for these nodes and edges 39450 this.body.modules.kamadaKawai.solve(this.body.nodeIndices, this.body.edgeIndices, true); 39451 39452 // shift to center point 39453 this._shiftToCenter(); 39454 39455 // perturb the nodes a little bit to force the physics to kick in 39456 var offset = 70; 39457 for (var i = 0; i < this.body.nodeIndices.length; i++) { 39458 this.body.nodes[this.body.nodeIndices[i]].x += (0.5 - this.seededRandom()) * offset; 39459 this.body.nodes[this.body.nodeIndices[i]].y += (0.5 - this.seededRandom()) * offset; 39460 } 39461
39462 // uncluster all clusters 39463 this._declusterAll(); 39464 39465 // reposition all bezier nodes. 39466 this.body.emitter.emit('_repositionBezierNodes'); 39467 } 39468 } 39469 } 39470 }, { 39471 key: '_shiftToCenter', 39472 39473 /** 39474 * Move all the nodes towards to the center so gravitational pull wil not move the nodes away from view 39475 * @private 39476 */ 39477 value: function _shiftToCenter() { 39478 var range = _NetworkUtil2['default']._getRangeCore(this.body.nodes, this.body.nodeIndices); 39479 var center = _NetworkUtil2['default']._findCenter(range); 39480 for (var i = 0; i < this.body.nodeIndices.length; i++) { 39481 this.body.nodes[this.body.nodeIndices[i]].x -= center.x; 39482 this.body.nodes[this.body.nodeIndices[i]].y -= center.y; 39483 } 39484 } 39485 }, { 39486 key: '_declusterAll', 39487 value: function _declusterAll() { 39488 var clustersPresent = true; 39489 while (clustersPresent === true) { 39490 clustersPresent = false; 39491 for (var i = 0; i < this.body.nodeIndices.length; i++) { 39492 if (this.body.nodes[this.body.nodeIndices[i]].isCluster === true) { 39493 clustersPresent = true; 39494 this.body.modules.clustering.openCluster(this.body.nodeIndices[i], {}, false); 39495 } 39496 } 39497 if (clustersPresent === true) { 39498 this.body.emitter.emit('_dataChanged'); 39499 } 39500 } 39501 } 39502 }, { 39503 key: 'getSeed', 39504 value: function getSeed() { 39505 return this.initialRandomSeed; 39506 } 39507 }, { 39508 key: 'setupHierarchicalLayout', 39509 39510 /** 39511 * This is the main function to layout the nodes in a hierarchical way. 39512 * It checks if the node details are supplied correctly 39513 * 39514 * @private 39515 */ 39516 value: function setupHierarchicalLayout() { 39517 if (this.options.hierarchical.enabled === true && this.body.nodeIndices.length > 0) { 39518 // get the size of the largest hubs and check if the user has defined a level for a node. 39519 var node = undefined, 39520 nodeId = undefined; 39521 var definedLevel = false; 39522 var undefinedLevel = false; 39523 this.hierarchicalLevels = {}; 39524 this.nodeSpacing = 100; 39525 39526 for (nodeId in this.body.nodes) { 39527 if (this.body.nodes.hasOwnProperty(nodeId)) { 39528 node = this.body.nodes[nodeId]; 39529 if (node.options.level !== undefined) { 39530 definedLevel = true; 39531 this.hierarchicalLevels[nodeId] = node.options.level; 39532 } else { 39533 undefinedLevel = true; 39534 } 39535 } 39536 } 39537 39538 // if the user defined some levels but not all, alert and run without hierarchical layout 39539 if (undefinedLevel === true && definedLevel === true) { 39540 throw new Error('To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.'); 39541 return; 39542 } else { 39543 // setup the system to use hierarchical method. 39544 //this._changeConstants(); 39545 39546 // define levels if undefined by the users. Based on hubsize 39547 if (undefinedLevel === true) { 39548 if (this.options.hierarchical.sortMethod === 'hubsize') { 39549 this._determineLevelsByHubsize(); 39550 } else if (this.options.hierarchical.sortMethod === 'directed' || 'direction') { 39551 this._determineLevelsDirected(); 39552 } 39553 } 39554 39555 // check the distribution of the nodes per level. 39556 var distribution = this._getDistribution(); 39557 39558 // place the nodes on the canvas. 39559 this._placeNodesByHierarchy(distribution); 39560 } 39561 } 39562 } 39563 }, { 39564 key: '_placeNodesByHierarchy', 39565 39566 /** 39567 * This function places the nodes on the canvas based on the hierarchial distribution. 39568 * 39569 * @param {Object} distribution | obtained by the function this._getDistribution() 39570 * @private 39571 */ 39572 value: function _placeNodesByHierarchy(distribution) { 39573 var nodeId = undefined, 39574 node = undefined; 39575 this.positionedNodes = {}; 39576 // start placing all the level 0 nodes first. Then recursively position their branches. 39577 for (var level in distribution) { 39578 if (distribution.hasOwnProperty(level)) { 39579 for (nodeId in distribution[level].nodes) { 39580 if (distribution[level].nodes.hasOwnProperty(nodeId)) { 39581 39582 node = distribution[level].nodes[nodeId]; 39583 39584 if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') { 39585 if (node.x === undefined) { 39586 node.x = distribution[level].distance; 39587 } 39588 distribution[level].distance = node.x + this.nodeSpacing; 39589 } else { 39590 if (node.y === undefined) { 39591 node.y = distribution[level].distance; 39592 } 39593 distribution[level].distance = node.y + this.nodeSpacing; 39594 } 39595 39596 this.positionedNodes[nodeId] = true; 39597 this._placeBranchNodes(node.edges, node.id, distribution, level); 39598 } 39599 } 39600 } 39601 } 39602 } 39603 }, {
39604 key: '_getDistribution', 39605 39606 /** 39607 * This function get the distribution of levels based on hubsize 39608 * 39609 * @returns {Object} 39610 * @private 39611 */ 39612 value: function _getDistribution() { 39613 var distribution = {}; 39614 var nodeId = undefined, 39615 node = undefined; 39616 39617 // 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. 39618 // the fix of X is removed after the x value has been set. 39619 for (nodeId in this.body.nodes) { 39620 if (this.body.nodes.hasOwnProperty(nodeId)) { 39621 node = this.body.nodes[nodeId]; 39622 var level = this.hierarchicalLevels[nodeId] === undefined ? 0 : this.hierarchicalLevels[nodeId]; 39623 if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') { 39624 node.y = this.options.hierarchical.levelSeparation * level; 39625 node.options.fixed.y = true; 39626 } else { 39627 node.x = this.options.hierarchical.levelSeparation * level; 39628 node.options.fixed.x = true; 39629 } 39630 if (distribution[level] === undefined) { 39631 distribution[level] = { amount: 0, nodes: {}, distance: 0 }; 39632 } 39633 distribution[level].amount += 1; 39634 distribution[level].nodes[nodeId] = node; 39635 } 39636 } 39637 return distribution; 39638 } 39639 }, { 39640 key: '_getHubSize', 39641 39642 /** 39643 * Get the hubsize from all remaining unlevelled nodes. 39644 * 39645 * @returns {number} 39646 * @private 39647 */ 39648 value: function _getHubSize() { 39649 var hubSize = 0; 39650 for (var nodeId in this.body.nodes) { 39651 if (this.body.nodes.hasOwnProperty(nodeId)) { 39652 var node = this.body.nodes[nodeId]; 39653 if (this.hierarchicalLevels[nodeId] === undefined) { 39654 hubSize = node.edges.length < hubSize ? hubSize : node.edges.length; 39655 } 39656 } 39657 } 39658 return hubSize; 39659 } 39660 }, { 39661 key: '_determineLevelsByHubsize', 39662 39663 /** 39664 * this function allocates nodes in levels based on the recursive branching from the largest hubs. 39665 * 39666 * @param hubsize 39667 * @private 39668 */ 39669 value: function _determineLevelsByHubsize() { 39670 var nodeId = undefined, 39671 node = undefined; 39672 var hubSize = 1; 39673 39674 while (hubSize > 0) { 39675 // determine hubs 39676 hubSize = this._getHubSize(); 39677 if (hubSize === 0) break; 39678 39679 for (nodeId in this.body.nodes) { 39680 if (this.body.nodes.hasOwnProperty(nodeId)) { 39681 node = this.body.nodes[nodeId]; 39682 if (node.edges.length === hubSize) { 39683 this._setLevelByHubsize(0, node); 39684 } 39685 } 39686 } 39687 } 39688 } 39689 }, { 39690 key: '_setLevelByHubsize', 39691 39692 /** 39693 * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level. 39694 * 39695 * @param level 39696 * @param edges 39697 * @param parentId 39698 * @private 39699 */ 39700 value: function _setLevelByHubsize(level, node) { 39701 if (this.hierarchicalLevels[node.id] !== undefined) return; 39702 39703 var childNode = undefined; 39704 this.hierarchicalLevels[node.id] = level; 39705 for (var i = 0; i < node.edges.length; i++) { 39706 if (node.edges[i].toId === node.id) { 39707 childNode = node.edges[i].from; 39708 } else { 39709 childNode = node.edges[i].to; 39710 } 39711 this._setLevelByHubsize(level + 1, childNode); 39712 } 39713 } 39714 }, { 39715 key: '_determineLevelsDirected', 39716 39717 /** 39718 * this function allocates nodes in levels based on the direction of the edges 39719 * 39720 * @param hubsize 39721 * @private 39722 */ 39723 value: function _determineLevelsDirected() { 39724 var nodeId = undefined, 39725 node = undefined; 39726 var minLevel = 10000; 39727 39728 // set first node to source 39729 for (nodeId in this.body.nodes) { 39730 if (this.body.nodes.hasOwnProperty(nodeId)) { 39731 node = this.body.nodes[nodeId]; 39732 this._setLevelDirected(minLevel, node); 39733 } 39734 } 39735 39736 // get the minimum level 39737 for (nodeId in this.body.nodes) { 39738 if (this.body.nodes.hasOwnProperty(nodeId)) { 39739 minLevel = this.hierarchicalLevels[nodeId] < minLevel ? this.hierarchicalLevels[nodeId] : minLevel; 39740 } 39741 } 39742 39743 // subtract the minimum from the set so we have a range starting from 0 39744 for (nodeId in this.body.nodes) { 39745 if (this.body.nodes.hasOwnProperty(nodeId)) { 39746 this.hierarchicalLevels[nodeId] -= minLevel; 39747 } 39748 } 39749 } 39750 }, {
39751 key: '_setLevelDirected', 39752 39753 /** 39754 * this function is called recursively to enumerate the branched of the first node and give each node a level based on edge direction 39755 * 39756 * @param level 39757 * @param edges 39758 * @param parentId 39759 * @private 39760 */ 39761 value: function _setLevelDirected(level, node) { 39762 if (this.hierarchicalLevels[node.id] !== undefined) return; 39763 39764 var childNode = undefined; 39765 this.hierarchicalLevels[node.id] = level; 39766 39767 for (var i = 0; i < node.edges.length; i++) { 39768 if (node.edges[i].toId === node.id) { 39769 childNode = node.edges[i].from; 39770 this._setLevelDirected(level - 1, childNode); 39771 } else { 39772 childNode = node.edges[i].to; 39773 this._setLevelDirected(level + 1, childNode); 39774 } 39775 } 39776 } 39777 }, { 39778 key: '_placeBranchNodes', 39779 39780 /** 39781 * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes 39782 * on a X position that ensures there will be no overlap. 39783 * 39784 * @param edges 39785 * @param parentId 39786 * @param distribution 39787 * @param parentLevel 39788 * @private 39789 */ 39790 value: function _placeBranchNodes(edges, parentId, distribution, parentLevel) { 39791 for (var i = 0; i < edges.length; i++) { 39792 var childNode = undefined; 39793 var parentNode = undefined; 39794 if (edges[i].toId === parentId) { 39795 childNode = edges[i].from; 39796 parentNode = edges[i].to; 39797 } else { 39798 childNode = edges[i].to; 39799 parentNode = edges[i].from; 39800 } 39801 var childNodeLevel = this.hierarchicalLevels[childNode.id]; 39802 39803 if (this.positionedNodes[childNode.id] === undefined) { 39804 // if a node is conneceted to another node on the same level (
39804or higher (means lower level))!, this is not handled here. 39805 if (childNodeLevel > parentLevel) { 39806 if (this.options.hierarchical.direction === 'UD' || this.options.hierarchical.direction === 'DU') { 39807 if (childNode.x === undefined) { 39808 childNode.x = Math.max(distribution[childNodeLevel].distance, parentNode.x); 39809 } 39810 distribution[childNodeLevel].distance = childNode.x + this.nodeSpacing; 39811 this.positionedNodes[childNode.id] = true; 39812 } else { 39813 if (childNode.y === undefined) { 39814 childNode.y = Math.max(distribution[childNodeLevel].distance, parentNode.y); 39815 } 39816 distribution[childNodeLevel].distance = childNode.y + this.nodeSpacing; 39817 } 39818 this.positionedNodes[childNode.id] = true; 39819 39820 if (childNode.edges.length > 1) { 39821 this._placeBranchNodes(childNode.edges, childNode.id, distribution, childNodeLevel); 39822 } 39823 } 39824 } 39825 } 39826 } 39827 }]); 39828 39829 return LayoutEngine; 39830 })(); 39831 39832 exports['default'] = LayoutEngine; 39833 module.exports = exports['default']; 39834 39835/***/ }, 39836/* 110 */ 39837/***/ function(module, exports, __webpack_require__) { 39838 39839 'use strict'; 39840 39841 Object.defineProperty(exports, '__esModule', { 39842 value: true 39843 }); 39844 39845 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 39846 39847 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 39848 39849 var util = __webpack_require__(1); 39850 var Hammer = __webpack_require__(20); 39851 var hammerUtil = __webpack_require__(25); 39852 39853 /** 39854 * clears the toolbar div element of children 39855 * 39856 * @private 39857 */ 39858 39859 var ManipulationSystem = (function () { 39860 function ManipulationSystem(body, canvas, selectionHandler) { 39861 var _this = this; 39862 39863 _classCallCheck(this, ManipulationSystem); 39864 39865 this.body = body; 39866 this.canvas = canvas; 39867 this.selectionHandler = selectionHandler; 39868 39869 this.editMode = false; 39870 this.manipulationDiv = undefined; 39871 this.editModeDiv = undefined; 39872 this.closeDiv = undefined; 39873 39874 this.manipulationHammers = []; 39875 this.temporaryUIFunctions = {}; 39876 this.temporaryEventFunctions = []; 39877 39878 this.touchTime = 0; 39879 this.temporaryIds = { nodes: [], edges: [] }; 39880 this.guiEnabled = false; 39881 this.inMode = false; 39882 this.selectedControlNode = undefined; 39883 39884 this.options = {}; 39885 this.defaultOptions = { 39886 enabled: false, 39887 initiallyActive: false, 39888 addNode: true, 39889 addEdge: true, 39890 editNode: undefined, 39891 editEdge: true, 39892 deleteNode: true, 39893 deleteEdge: true, 39894 controlNodeStyle: { 39895 shape: 'dot', 39896 size: 6, 39897 color: { background: '#ff0000', border: '#3c3c3c', highlight: { background: '#07f968', border: '#3c3c3c' } }, 39898 borderWidth: 2, 39899 borderWidthSelected: 2 39900 } 39901 }; 39902 util.extend(this.options, this.defaultOptions); 39903 39904 this.body.emitter.on('destroy', function () { 39905 _this._clean(); 39906 }); 39907 this.body.emitter.on('_dataChanged', this._restore.bind(this)); 39908 this.body.emitter.on('_resetData', this._restore.bind(this)); 39909 } 39910 39911 _createClass(ManipulationSystem, [{ 39912 key: '_restore', 39913 39914 /** 39915 * If something changes in the data during editing, switch back to the initial datamanipulation state and close all edit modes. 39916 * @private 39917 */
39918 value: function _restore() { 39919 if (this.inMode !== false) { 39920 if (this.options.initiallyActive === true) { 39921 this.enableEditMode(); 39922 } else { 39923 this.disableEditMode(); 39924 } 39925 } 39926 } 39927 }, { 39928 key: 'setOptions', 39929 39930 /** 39931 * Set the Options 39932 * @param options 39933 */ 39934 value: function setOptions(options, allOptions, globalOptions) { 39935 if (allOptions !== undefined) { 39936 if (allOptions.locale !== undefined) { 39937 this.options.locale = allOptions.locale; 39938 } else { 39939 this.options.locale = globalOptions.locale; 39940 } 39941 if (allOptions.locales !== undefined) { 39942 this.options.locales = allOptions.locales; 39943 } else { 39944 this.options.locales = globalOptions.locales; 39945 } 39946 } 39947 39948 if (options !== undefined) { 39949 if (typeof options === 'boolean') { 39950 this.options.enabled = options; 39951 } else { 39952 this.options.enabled = true; 39953 util.deepExtend(this.options, options); 39954 } 39955 if (this.options.initiallyActive === true) { 39956 this.editMode = true; 39957 } 39958 this._setup(); 39959 } 39960 } 39961 }, { 39962 key: 'toggleEditMode', 39963 39964 /** 39965 * Enable or disable edit-mode. Draws the DOM required and cleans up after itself. 39966 * 39967 * @private 39968 */ 39969 value: function toggleEditMode() { 39970 if (this.editMode === true) { 39971 this.disableEditMode(); 39972 } else { 39973 this.enableEditMode(); 39974 } 39975 } 39976 }, { 39977 key: 'enableEditMode', 39978 value: function enableEditMode() { 39979 this.editMode = true; 39980 39981 this._clean(); 39982 if (this.guiEnabled === true) { 39983 this.manipulationDiv.style.display = 'block'; 39984 this.closeDiv.style.display = 'block'; 39985 this.editModeDiv.style.display = 'none'; 39986 this.showManipulatorToolbar(); 39987 } 39988 } 39989 }, { 39990 key: 'disableEditMode', 39991 value: function disableEditMode() { 39992 this.editMode = false; 39993 39994 this._clean(); 39995 if (this.guiEnabled === true) { 39996 this.manipulationDiv.style.display = 'none'; 39997 this.closeDiv.style.display = 'none'; 39998 this.editModeDiv.style.display = 'block'; 39999 this._createEditButton(); 40000 } 40001 } 40002 }, { 40003 key: 'showManipulatorToolbar', 40004 40005 /** 40006 * Creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar. 40007 * 40008 * @private 40009 */ 40010 value: function showManipulatorToolbar() { 40011 // restore the state of any bound functions or events, remove control nodes, restore physics 40012 this._clean(); 40013 40014 // reset global letiables 40015 this.manipulationDOM = {}; 40016 40017 // if the gui is enabled, draw all elements. 40018 if (this.guiEnabled === true) { 40019 // a _restore will hide these menus 40020 this.editMode = true; 40021 this.manipulationDiv.style.display = 'block'; 40022 this.closeDiv.style.display = 'block'; 40023 40024 var selectedNodeCount = this.selectionHandler._getSelectedNodeCount(); 40025 var selectedEdgeCount = this.selectionHandler._getSelectedEdgeCount(); 40026 var selectedTotalCount = selectedNodeCount + selectedEdgeCount; 40027 var locale = this.options.locales[this.options.locale]; 40028 var needSeperator = false; 40029 40030 if (this.options.addNode !== false) { 40031 this._createAddNodeButton(locale); 40032 needSeperator = true; 40033 } 40034 if (this.options.addEdge !== false) { 40035 if (needSeperator === true) { 40036 this._createSeperator(1); 40037 } else { 40038 needSeperator = true; 40039 } 40040 this._createAddEdgeButton(locale); 40041 } 40042 40043 if (selectedNodeCount === 1 && typeof this.options.editNode === 'function') { 40044 if (needSeperator === true) { 40045 this._createSeperator(2); 40046 } else { 40047 needSeperator = true; 40048 } 40049 this._createEditNodeButton(locale); 40050 } else if (selectedEdgeCount === 1 && selectedNodeCount === 0 && this.options.editEdge !== false) { 40051 if (needSeperator === true) { 40052 this._createSeperator(3); 40053 } else { 40054 needSeperator = true; 40055 } 40056 this._createEditEdgeButton(locale); 40057 } 40058 40059 // remove buttons 40060 if (selectedTotalCount !== 0) { 40061 if (selectedNodeCount > 0 && this.options.deleteNode !== false) { 40062 if (needSeperator === true) { 40063 this._createSeperator(4); 40064 } 40065 this._createDeleteButton(locale); 40066 } else if (selectedNodeCount === 0 && this.options.deleteEdge !== false) { 40067 if (needSeperator === true) { 40068 this._createSeperator(4); 40069 } 40070 this._createDeleteButton(locale); 40071 } 40072 } 40073 40074 // bind the close button 40075 this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); 40076 40077 // refresh this bar based on what has been selected 40078 this._temporaryBindEvent('select', this.showManipulatorToolbar.bind(this)); 40079 } 40080 40081 // redraw to show any possible changes 40082 this.body.emitter.emit('_redraw'); 40083 } 40084 }, {
40085 key: 'addNodeMode', 40086 40087 /** 40088 * Create the toolbar for adding Nodes 40089 * 40090 * @private 40091 */ 40092 value: function addNodeMode() { 40093 // when using the gui, enable edit mode if it wasnt already. 40094 if (this.editMode !== true) { 40095 this.enableEditMode(); 40096 } 40097 40098 // restore the state of any bound functions or events, remove control nodes, restore physics 40099 this._clean(); 40100 40101 this.inMode = 'addNode'; 40102 if (this.guiEnabled === true) { 40103 var locale = this.options.locales[this.options.locale]; 40104 this.manipulationDOM = {}; 40105 this._createBackButton(locale); 40106 this._createSeperator(); 40107 this._createDescription(locale['addDescription'] || this.options.locales['en']['addDescription']); 40108 40109 // bind the close button 40110 this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); 40111 } 40112 40113 this._temporaryBindEvent('click', this._performAddNode.bind(this)); 40114 } 40115 }, { 40116 key: 'editNode', 40117 40118 /** 40119 * call the bound function to handle the editing of the node. The node has to be selected. 40120 * 40121 * @private 40122 */ 40123 value: function editNode() { 40124 var _this2 = this; 40125 40126 // when using the gui, enable edit mode if it wasnt already. 40127 if (this.editMode !== true) { 40128 this.enableEditMode(); 40129 } 40130 40131 // restore the state of any bound functions or events, remove control nodes, restore physics 40132 this._clean(); 40133 var node = this.selectionHandler._getSelectedNode(); 40134 if (node !== undefined) { 40135 this.inMode = 'editNode'; 40136 if (typeof this.options.editNode === 'function') { 40137 if (node.isCluster !== true) { 40138 var data = util.deepExtend({}, node.options, true); 40139 data.x = node.x; 40140 data.y = node.y; 40141 40142 if (this.options.editNode.length === 2) { 40143 this.options.editNode(data, function (finalizedData) { 40144 if (finalizedData !== null && finalizedData !== undefined && _this2.inMode === 'editNode') { 40145 // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { 40146 _this2.body.data.nodes.getDataSet().update(finalizedData); 40147 } 40148 _this2.showManipulatorToolbar(); 40149 }); 40150 } else { 40151 throw new Error('The function for edit does not support two arguments (data, callback)'); 40152 } 40153 } else { 40154 alert(this.options.locales[this.options.locale]['editClusterError'] || this.options.locales['en']['editClusterError']); 40155 } 40156 } else { 40157 throw new Error('No function has been configured to handle the editing of nodes.'); 40158 } 40159 } else { 40160 this.showManipulatorToolbar(); 40161 } 40162 } 40163 }, { 40164 key: 'addEdgeMode', 40165 40166 /** 40167 * create the toolbar to connect nodes 40168 * 40169 * @private 40170 */ 40171 value: function addEdgeMode() { 40172 // when using the gui, enable edit mode if it wasnt already. 40173 if (this.editMode !== true) { 40174 this.enableEditMode(); 40175 } 40176 40177 // restore the state of any bound functions or events, remove control nodes, restore physics 40178 this._clean(); 40179 40180 this.inMode = 'addEdge'; 40181 if (this.guiEnabled === true) { 40182 var locale = this.options.locales[this.options.locale]; 40183 this.manipulationDOM = {}; 40184 this._createBackButton(locale); 40185 this._createSeperator(); 40186 this._createDescription(locale['edgeDescription'] || this.options.locales['en']['edgeDescription']); 40187 40188 // bind the close button 40189 this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); 40190 } 40191 40192 // temporarily overload functions 40193 this._temporaryBindUI('onTouch', this._handleConnect.bind(this)); 40194 this._temporaryBindUI('onDragEnd', this._finishConnect.bind(this)); 40195 this._temporaryBindUI('onDrag', this._dragControlNode.bind(this)); 40196 this._temporaryBindUI('onRelease', this._finishConnect.bind(this)); 40197 40198 this._temporaryBindUI('onDragStart', function () {}); 40199 this._temporaryBindUI('onHold', function () {}); 40200 } 40201 }, {
40202 key: 'editEdgeMode', 40203 40204 /** 40205 * create the toolbar to edit edges 40206 * 40207 * @private 40208 */ 40209 value: function editEdgeMode() { 40210 var _this3 = this; 40211 40212 // when using the gui, enable edit mode if it wasnt already. 40213 if (this.editMode !== true) { 40214 this.enableEditMode(); 40215 } 40216 40217 // restore the state of any bound functions or events, remove control nodes, restore physics 40218 this._clean(); 40219 40220 this.inMode = 'editEdge'; 40221 if (this.guiEnabled === true) { 40222 var locale = this.options.locales[this.options.locale]; 40223 this.manipulationDOM = {}; 40224 this._createBackButton(locale); 40225 this._createSeperator(); 40226 this._createDescription(locale['editEdgeDescription'] || this.options.locales['en']['editEdgeDescription']); 40227 40228 // bind the close button 40229 this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); 40230 } 40231 40232 this.edgeBeingEditedId = this.selectionHandler.getSelectedEdges()[0]; 40233 if (this.edgeBeingEditedId !== undefined) { 40234 (function () { 40235 var edge = _this3.body.edges[_this3.edgeBeingEditedId]; 40236 40237 // create control nodes 40238 var controlNodeFrom = _this3._getNewTargetNode(edge.from.x, edge.from.y); 40239 var controlNodeTo = _this3._getNewTargetNode(edge.to.x, edge.to.y); 40240 40241 _this3.temporaryIds.nodes.push(controlNodeFrom.id); 40242 _this3.temporaryIds.nodes.push(controlNodeTo.id); 40243 40244 _this3.body.nodes[controlNodeFrom.id] = controlNodeFrom; 40245 _this3.body.nodeIndices.push(controlNodeFrom.id); 40246 _this3.body.nodes[controlNodeTo.id] = controlNodeTo; 40247 _this3.body.nodeIndices.push(controlNodeTo.id); 40248 40249 // temporarily overload UI functions, cleaned up automatically because of _temporaryBindUI 40250 _this3._temporaryBindUI('onTouch', _this3._controlNodeTouch.bind(_this3)); // used to get the position 40251 _this3._temporaryBindUI('onTap', function () {}); // disabled 40252 _this3._temporaryBindUI('onHold', function () {}); // disabled 40253 _this3._temporaryBindUI('onDragStart', _this3._controlNodeDragStart.bind(_this3)); // used to select control node 40254 _this3._temporaryBindUI('onDrag', _this3._controlNodeDrag.bind(_this3)); // used to drag control node 40255 _this3._temporaryBindUI('onDragEnd', _this3._controlNodeDragEnd.bind(_this3)); // used to connect or revert control nodes 40256 _this3._temporaryBindUI('onMouseMove', function () {}); // disabled 40257 40258 // create function to position control nodes correctly on movement 40259 // automatically cleaned up because we use the temporary bind 40260 _this3._temporaryBindEvent('beforeDrawing', function (ctx) { 40261 var positions = edge.edgeType.findBorderPositions(ctx); 40262 if (controlNodeFrom.selected === false) { 40263 controlNodeFrom.x = positions.from.x; 40264 controlNodeFrom.y = positions.from.y; 40265 } 40266 if (controlNodeTo.selected === false) { 40267 controlNodeTo.x = positions.to.x; 40268 controlNodeTo.y = positions.to.y; 40269 } 40270 }); 40271 40272 _this3.body.emitter.emit('_redraw'); 40273 })(); 40274 } else { 40275 this.showManipulatorToolbar(); 40276 } 40277 } 40278 }, { 40279 key: 'deleteSelected', 40280 40281 /** 40282 * delete everything in the selection 40283 * 40284 * @private 40285 */ 40286 value: function deleteSelected() { 40287 var _this4 = this; 40288 40289 // when using the gui, enable edit mode if it wasnt already. 40290 if (this.editMode !== true) { 40291 this.enableEditMode(); 40292 } 40293 40294 // restore the state of any bound functions or events, remove control nodes, restore physics 40295 this._clean(); 40296 40297 this.inMode = 'delete'; 40298 var selectedNodes = this.selectionHandler.getSelectedNodes(); 40299 var selectedEdges = this.selectionHandler.getSelectedEdges(); 40300 var deleteFunction = undefined; 40301 if (selectedNodes.length > 0) { 40302 for (var i = 0; i < selectedNodes.length; i++) { 40303 if (this.body.nodes[selectedNodes[i]].isCluster === true) { 40304 alert(this.options.locales[this.options.locale]['deleteClusterError'] || this.options.locales['en']['deleteClusterError']); 40305 return; 40306 } 40307 } 40308 40309 if (typeof this.options.deleteNode === 'function') { 40310 deleteFunction = this.options.deleteNode; 40311 } 40312 } else if (selectedEdges.length > 0) { 40313 if (typeof this.options.deleteEdge === 'function') { 40314 deleteFunction = this.options.deleteEdge; 40315 } 40316 } 40317 40318 if (typeof deleteFunction === 'function') { 40319 var data = { nodes: selectedNodes, edges: selectedEdges }; 40320 if (deleteFunction.length === 2) { 40321 deleteFunction(data, function (finalizedData) { 40322 if (finalizedData !== null && finalizedData !== undefined && _this4.inMode === 'delete') { 40323 // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { 40324 _this4.body.data.edges.getDataSet().remove(finalizedData.edges); 40325 _this4.body.data.nodes.getDataSet().remove(finalizedData.nodes); 40326 _this4.body.emitter.emit('startSimulation'); 40327 _this4.showManipulatorToolbar(); 40328 } else { 40329 _this4.body.emitter.emit('startSimulation'); 40330 _this4.showManipulatorToolbar(); 40331 } 40332 }); 40333 } else { 40334 throw new Error('The function for delete does not support two arguments (data, callback)'); 40335 } 40336 } else { 40337 this.body.data.edges.getDataSet().remove(selectedEdges); 40338 this.body.data.nodes.getDataSet().remove(selectedNodes); 40339 this.body.emitter.emit('startSimulation'); 40340 this.showManipulatorToolbar(); 40341 } 40342 } 40343 }, {
40344 key: '_setup', 40345 40346 //********************************************** PRIVATE ***************************************// 40347 40348 /** 40349 * draw or remove the DOM 40350 * @private 40351 */ 40352 value: function _setup() { 40353 if (this.options.enabled === true) { 40354 // Enable the GUI 40355 this.guiEnabled = true; 40356 40357 this._createWrappers(); 40358 if (this.editMode === false) { 40359 this._createEditButton(); 40360 } else { 40361 this.showManipulatorToolbar(); 40362 } 40363 } else { 40364 this._removeManipulationDOM(); 40365 40366 // disable the gui 40367 this.guiEnabled = false; 40368 } 40369 } 40370 }, { 40371 key: '_createWrappers', 40372 40373 /** 40374 * create the div overlays that contain the DOM 40375 * @private 40376 */ 40377 value: function _createWrappers() { 40378 // load the manipulator HTML elements. All styling done in css. 40379 if (this.manipulationDiv === undefined) { 40380 this.manipulationDiv = document.createElement('div'); 40381 this.manipulationDiv.className = 'vis-manipulation'; 40382 if (this.editMode === true) { 40383 this.manipulationDiv.style.display = 'block'; 40384 } else { 40385 this.manipulationDiv.style.display = 'none'; 40386 } 40387 this.canvas.frame.appendChild(this.manipulationDiv); 40388 } 40389 40390 // container for the edit button. 40391 if (this.editModeDiv === undefined) { 40392 this.editModeDiv = document.createElement('div'); 40393 this.editModeDiv.className = 'vis-edit-mode'; 40394 if (this.editMode === true) { 40395 this.editModeDiv.style.display = 'none'; 40396 } else { 40397 this.editModeDiv.style.display = 'block'; 40398 } 40399 this.canvas.frame.appendChild(this.editModeDiv); 40400 } 40401 40402 // container for the close div button 40403 if (this.closeDiv === undefined) { 40404 this.closeDiv = document.createElement('div'); 40405 this.closeDiv.className = 'vis-close'; 40406 this.closeDiv.style.display = this.manipulationDiv.style.display; 40407 this.canvas.frame.appendChild(this.closeDiv); 40408 } 40409 } 40410 }, { 40411 key: '_getNewTargetNode', 40412 40413 /** 40414 * generate a new target node. Used for creating new edges and editing edges 40415 * @param x 40416 * @param y 40417 * @returns {*} 40418 * @private 40419 */ 40420 value: function _getNewTargetNode(x, y) { 40421 var controlNodeStyle = util.deepExtend({}, this.options.controlNodeStyle); 40422 40423 controlNodeStyle.id = 'targetNode' + util.randomUUID(); 40424 controlNodeStyle.hidden = false; 40425 controlNodeStyle.physics = false; 40426 controlNodeStyle.x = x; 40427 controlNodeStyle.y = y; 40428 40429 return this.body.functions.createNode(controlNodeStyle); 40430 } 40431 }, { 40432 key: '_createEditButton', 40433 40434 /** 40435 * Create the edit button 40436 */ 40437 value: function _createEditButton() { 40438 // restore everything to it's original state (if applicable) 40439 this._clean(); 40440 40441 // reset the manipulationDOM 40442 this.manipulationDOM = {}; 40443 40444 // empty the editModeDiv 40445 util.recursiveDOMDelete(this.editModeDiv); 40446 40447 // create the contents for the editMode button 40448 var locale = this.options.locales[this.options.locale]; 40449 var button = this._createButton('editMode', 'vis-button vis-edit vis-edit-mode', locale['edit'] || this.options.locales['en']['edit']); 40450 this.editModeDiv.appendChild(button); 40451 40452 // bind a hammer listener to the button, calling the function toggleEditMode. 40453 this._bindHammerToDiv(button, this.toggleEditMode.bind(this)); 40454 } 40455 }, { 40456 key: '_clean', 40457 40458 /** 40459 * this function cleans up after everything this module does. Temporary elements, functions and events are removed, physics restored, hammers removed. 40460 * @private 40461 */ 40462 value: function _clean() { 40463 // not in mode 40464 this.inMode = false; 40465 40466 // _clean the divs 40467 if (this.guiEnabled === true) { 40468 util.recursiveDOMDelete(this.editModeDiv); 40469 util.recursiveDOMDelete(this.manipulationDiv); 40470 40471 // removes all the bindings and overloads 40472 this._cleanManipulatorHammers(); 40473 } 40474 40475 // remove temporary nodes and edges 40476 this._cleanupTemporaryNodesAndEdges(); 40477 40478 // restore overloaded UI functions 40479 this._unbindTemporaryUIs(); 40480 40481 // remove the temporaryEventFunctions 40482 this._unbindTemporaryEvents(); 40483 40484 // restore the physics if required 40485 this.body.emitter.emit('restorePhysics'); 40486 } 40487 }, {
40488 key: '_cleanManipulatorHammers', 40489 40490 /** 40491 * Each dom element has it's own hammer. They are stored in this.manipulationHammers. This cleans them up. 40492 * @private 40493 */ 40494 value: function _cleanManipulatorHammers() { 40495 // _clean hammer bindings 40496 if (this.manipulationHammers.length != 0) { 40497 for (var i = 0; i < this.manipulationHammers.length; i++) { 40498 this.manipulationHammers[i].destroy(); 40499 } 40500 this.manipulationHammers = []; 40501 } 40502 } 40503 }, { 40504 key: '_removeManipulationDOM', 40505 40506 /** 40507 * Remove all DOM elements created by this module. 40508 * @private 40509 */ 40510 value: function _removeManipulationDOM() { 40511 // removes all the bindings and overloads 40512 this._clean(); 40513 40514 // empty the manipulation divs 40515 util.recursiveDOMDelete(this.manipulationDiv); 40516 util.recursiveDOMDelete(this.editModeDiv); 40517 util.recursiveDOMDelete(this.closeDiv); 40518 40519 // remove the manipulation divs 40520 if (this.manipulationDiv) { 40521 this.canvas.frame.removeChild(this.manipulationDiv); 40522 } 40523 if (this.editModeDiv) { 40524 this.canvas.frame.removeChild(this.editModeDiv); 40525 } 40526 if (this.closeDiv) { 40527 this.canvas.frame.removeChild(this.manipulationDiv); 40528 } 40529 40530 // set the references to undefined 40531 this.manipulationDiv = undefined; 40532 this.editModeDiv = undefined; 40533 this.closeDiv = undefined; 40534 } 40535 }, { 40536 key: '_createSeperator', 40537 40538 /** 40539 * create a seperator line. the index is to differentiate in the manipulation dom 40540 * @param index 40541 * @private 40542 */ 40543 value: function _createSeperator() { 40544 var index = arguments.length <= 0 || arguments[0] === undefined ? 1 : arguments[0]; 40545 40546 this.manipulationDOM['seperatorLineDiv' + index] = document.createElement('div'); 40547 this.manipulationDOM['seperatorLineDiv' + index].className = 'vis-separator-line'; 40548 this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv' + index]); 40549 } 40550 }, { 40551 key: '_createAddNodeButton', 40552 40553 // ---------------------- DOM functions for buttons --------------------------// 40554 40555 value: function _createAddNodeButton(locale) { 40556 var button = this._createButton('addNode', 'vis-button vis-add', locale['addNode'] || this.options.locales['en']['addNode']); 40557 this.manipulationDiv.appendChild(button); 40558 this._bindHammerToDiv(button, this.addNodeMode.bind(this)); 40559 } 40560 }, { 40561 key: '_createAddEdgeButton', 40562 value: function _createAddEdgeButton(locale) { 40563 var button = this._createButton('addEdge', 'vis-button vis-connect', locale['addEdge'] || this.options.locales['en']['addEdge']); 40564 this.manipulationDiv.appendChild(button); 40565 this._bindHammerToDiv(button, this.addEdgeMode.bind(this)); 40566 } 40567 }, { 40568 key: '_createEditNodeButton', 40569 value: function _createEditNodeButton(locale) { 40570 var button = this._createButton('editNode', 'vis-button vis-edit', locale['editNode'] || this.options.locales['en']['editNode']); 40571 this.manipulationDiv.appendChild(button); 40572 this._bindHammerToDiv(button, this.editNode.bind(this)); 40573 } 40574 }, { 40575 key: '_createEditEdgeButton', 40576 value: function _createEditEdgeButton(locale) { 40577 var button = this._createButton('editEdge', 'vis-button vis-edit', locale['editEdge'] || this.options.locales['en']['editEdge']); 40578 this.manipulationDiv.appendChild(button); 40579 this._bindHammerToDiv(button, this.editEdgeMode.bind(this)); 40580 } 40581 }, { 40582 key: '_createDeleteButton', 40583 value: function _createDeleteButton(locale) { 40584 var button = this._createButton('delete', 'vis-button vis-delete', locale['del'] || this.options.locales['en']['del']); 40585 this.manipulationDiv.appendChild(button); 40586 this._bindHammerToDiv(button, this.deleteSelected.bind(this)); 40587 } 40588 }, { 40589 key: '_createBackButton', 40590 value: function _createBackButton(locale) { 40591 var button = this._createButton('back', 'vis-button vis-back', locale['back'] || this.options.locales['en']['back']); 40592 this.manipulationDiv.appendChild(button); 40593 this._bindHammerToDiv(button, this.showManipulatorToolbar.bind(this)); 40594 } 40595 }, {
40596 key: '_createButton', 40597 value: function _createButton(id, className, label) { 40598 var labelClassName = arguments.length <= 3 || arguments[3] === undefined ? 'vis-label' : arguments[3]; 40599 40600 this.manipulationDOM[id + 'Div'] = document.createElement('div'); 40601 this.manipulationDOM[id + 'Div'].className = className; 40602 this.manipulationDOM[id + 'Label'] = document.createElement('div'); 40603 this.manipulationDOM[id + 'Label'].className = labelClassName; 40604 this.manipulationDOM[id + 'Label'].innerHTML = label; 40605 this.manipulationDOM[id + 'Div'].appendChild(this.manipulationDOM[id + 'Label']); 40606 return this.manipulationDOM[id + 'Div']; 40607 } 40608 }, { 40609 key: '_createDescription', 40610 value: function _createDescription(label) { 40611 this.manipulationDiv.appendChild(this._createButton('description', 'vis-button vis-none', label)); 40612 } 40613 }, { 40614 key: '_temporaryBindEvent', 40615 40616 // -------------------------- End of DOM functions for buttons ------------------------------// 40617 40618 /** 40619 * this binds an event until cleanup by the clean functions. 40620 * @param event 40621 * @param newFunction 40622 * @private 40623 */ 40624 value: function _temporaryBindEvent(event, newFunction) { 40625 this.temporaryEventFunctions.push({ event: event, boundFunction: newFunction }); 40626 this.body.emitter.on(event, newFunction); 40627 } 40628 }, { 40629 key: '_temporaryBindUI', 40630 40631 /** 40632 * this overrides an UI function until cleanup by the clean function 40633 * @param UIfunctionName 40634 * @param newFunction 40635 * @private 40636 */ 40637 value: function _temporaryBindUI(UIfunctionName, newFunction) { 40638 if (this.body.eventListeners[UIfunctionName] !== undefined) { 40639 this.temporaryUIFunctions[UIfunctionName] = this.body.eventListeners[UIfunctionName]; 40640 this.body.eventListeners[UIfunctionName] = newFunction; 40641 } else { 40642 throw new Error('This UI function does not exist. Typo? You tried: ' + UIfunctionName + ' possible are: ' + JSON.stringify(Object.keys(this.body.eventListeners))); 40643 } 40644 } 40645 }, { 40646 key: '_unbindTemporaryUIs', 40647 40648 /** 40649 * Restore the overridden UI functions to their original state. 40650 * 40651 * @private 40652 */ 40653 value: function _unbindTemporaryUIs() { 40654 for (var functionName in this.temporaryUIFunctions) { 40655 if (this.temporaryUIFunctions.hasOwnProperty(functionName)) { 40656 this.body.eventListeners[functionName] = this.temporaryUIFunctions[functionName]; 40657 delete this.temporaryUIFunctions[functionName]; 40658 } 40659 } 40660 this.temporaryUIFunctions = {}; 40661 } 40662 }, { 40663 key: '_unbindTemporaryEvents', 40664 40665 /** 40666 * Unbind the events created by _temporaryBindEvent 40667 * @private 40668 */ 40669 value: function _unbindTemporaryEvents() { 40670 for (var i = 0; i < this.temporaryEventFunctions.length; i++) { 40671 var eventName = this.temporaryEventFunctions[i].event; 40672 var boundFunction = this.temporaryEventFunctions[i].boundFunction; 40673 this.body.emitter.off(eventName, boundFunction); 40674 } 40675 this.temporaryEventFunctions = []; 40676 } 40677 }, { 40678 key: '_bindHammerToDiv', 40679 40680 /** 40681 * Bind an hammer instance to a DOM element. 40682 * @param domElement 40683 * @param funct 40684 */ 40685 value: function _bindHammerToDiv(domElement, boundFunction) { 40686 var hammer = new Hammer(domElement, {}); 40687 hammerUtil.onTouch(hammer, boundFunction); 40688 this.manipulationHammers.push(hammer); 40689 } 40690 }, { 40691 key: '_cleanupTemporaryNodesAndEdges', 40692 40693 /** 40694 * Neatly clean up temporary edges and nodes 40695 * @private 40696 */ 40697 value: function _cleanupTemporaryNodesAndEdges() { 40698 // _clean temporary edges 40699 for (var i = 0; i < this.temporaryIds.edges.length; i++) { 40700 this.body.edges[this.temporaryIds.edges[i]].disconnect(); 40701 delete this.body.edges[this.temporaryIds.edges[i]]; 40702 var indexTempEdge = this.body.edgeIndices.indexOf(this.temporaryIds.edges[i]); 40703 if (indexTempEdge !== -1) { 40704 this.body.edgeIndices.splice(indexTempEdge, 1); 40705 } 40706 } 40707 40708 // _clean temporary nodes 40709 for (var i = 0; i < this.temporaryIds.nodes.length; i++) { 40710 delete this.body.nodes[this.temporaryIds.nodes[i]]; 40711 var indexTempNode = this.body.nodeIndices.indexOf(this.temporaryIds.nodes[i]); 40712 if (indexTempNode !== -1) { 40713 this.body.nodeIndices.splice(indexTempNode, 1); 40714 } 40715 } 40716 40717 this.temporaryIds = { nodes: [], edges: [] }; 40718 } 40719 }, {
40720 key: '_controlNodeTouch', 40721 40722 // ------------------------------------------ EDIT EDGE FUNCTIONS -----------------------------------------// 40723 40724 /** 40725 * the touch is used to get the position of the initial click 40726 * @param event 40727 * @private 40728 */ 40729 value: function _controlNodeTouch(event) { 40730 this.selectionHandler.unselectAll(); 40731 this.lastTouch = this.body.functions.getPointer(event.center); 40732 this.lastTouch.translation = util.extend({}, this.body.view.translation); // copy the object 40733 } 40734 }, { 40735 key: '_controlNodeDragStart', 40736 40737 /** 40738 * the drag start is used to mark one of the control nodes as selected. 40739 * @param event 40740 * @private 40741 */ 40742 value: function _controlNodeDragStart(event) { 40743 var pointer = this.lastTouch; 40744 var pointerObj = this.selectionHandler._pointerToPositionObject(pointer); 40745 var from = this.body.nodes[this.temporaryIds.nodes[0]]; 40746 var to = this.body.nodes[this.temporaryIds.nodes[1]]; 40747 var edge = this.body.edges[this.edgeBeingEditedId]; 40748 this.selectedControlNode = undefined; 40749 40750 var fromSelect = from.isOverlappingWith(pointerObj); 40751 var toSelect = to.isOverlappingWith(pointerObj); 40752 40753 if (fromSelect === true) { 40754 this.selectedControlNode = from; 40755 edge.edgeType.from = from; 40756 } else if (toSelect === true) { 40757 this.selectedControlNode = to; 40758 edge.edgeType.to = to; 40759 } 40760 40761 this.body.emitter.emit('_redraw'); 40762 } 40763 }, { 40764 key: '_controlNodeDrag', 40765 40766 /** 40767 * dragging the control nodes or the canvas 40768 * @param event 40769 * @private 40770 */ 40771 value: function _controlNodeDrag(event) { 40772 this.body.emitter.emit('disablePhysics'); 40773 var pointer = this.body.functions.getPointer(event.center); 40774 var pos = this.canvas.DOMtoCanvas(pointer); 40775 40776 if (this.selectedControlNode !== undefined) { 40777 this.selectedControlNode.x = pos.x; 40778 this.selectedControlNode.y = pos.y; 40779 } else { 40780 // if the drag was not started properly because the click started outside the network div, start it now. 40781 var diffX = pointer.x - this.lastTouch.x; 40782 var diffY = pointer.y - this.lastTouch.y; 40783 this.body.view.translation = { x: this.lastTouch.translation.x + diffX, y: this.lastTouch.translation.y + diffY }; 40784 } 40785 this.body.emitter.emit('_redraw'); 40786 } 40787 }, { 40788 key: '_controlNodeDragEnd', 40789 40790 /** 40791 * connecting or restoring the control nodes. 40792 * @param event 40793 * @private 40794 */ 40795 value: function _controlNodeDragEnd(event) { 40796 var pointer = this.body.functions.getPointer(event.center); 40797 var pointerObj = this.selectionHandler._pointerToPositionObject(pointer); 40798 var edge = this.body.edges[this.edgeBeingEditedId]; 40799 40800 // if the node that was dragged is not a control node, return 40801 if (this.selectedControlNode === undefined) { 40802 return; 40803 } 40804 40805 var overlappingNodeIds = this.selectionHandler._getAllNodesOverlappingWith(pointerObj); 40806 var node = undefined; 40807 for (var i = overlappingNodeIds.length - 1; i >= 0; i--) { 40808 if (overlappingNodeIds[i] !== this.selectedControlNode.id) { 40809 node = this.body.nodes[overlappingNodeIds[i]]; 40810 break; 40811 } 40812 } 40813 40814 // perform the connection 40815 if (node !== undefined && this.selectedControlNode !== undefined) { 40816 if (node.isCluster === true) { 40817 alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError']); 40818 } else { 40819 var from = this.body.nodes[this.temporaryIds.nodes[0]]; 40820 if (this.selectedControlNode.id === from.id) { 40821 this._performEditEdge(node.id, edge.to.id); 40822 } else { 40823 this._performEditEdge(edge.from.id, node.id); 40824 } 40825 } 40826 } else { 40827 edge.updateEdgeType(); 40828 this.body.emitter.emit('restorePhysics'); 40829 } 40830 this.body.emitter.emit('_redraw'); 40831 } 40832 }, {
40833 key: '_handleConnect', 40834 40835 // ------------------------------------ END OF EDIT EDGE FUNCTIONS -----------------------------------------// 40836 40837 // ------------------------------------------- ADD EDGE FUNCTIONS -----------------------------------------// 40838 /** 40839 * the function bound to the selection event. It checks if you want to connect a cluster and changes the description 40840 * to walk the user through the process. 40841 * 40842 * @private 40843 */ 40844 value: function _handleConnect(event) { 40845 // check to avoid double fireing of this function. 40846 if (new Date().valueOf() - this.touchTime > 100) { 40847 this.lastTouch = this.body.functions.getPointer(event.center); 40848 this.lastTouch.translation = util.extend({}, this.body.view.translation); // copy the object 40849 40850 var pointer = this.lastTouch; 40851 var node = this.selectionHandler.getNodeAt(pointer); 40852 40853 if (node !== undefined) { 40854 if (node.isCluster === true) { 40855 alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError']); 40856 } else { 40857 // create a node the temporary line can look at 40858 var targetNode = this._getNewTargetNode(node.x, node.y); 40859 this.body.nodes[targetNode.id] = targetNode; 40860 this.body.nodeIndices.push(targetNode.id); 40861 40862 // create a temporary edge 40863 var connectionEdge = this.body.functions.createEdge({ 40864 id: 'connectionEdge' + util.randomUUID(), 40865 from: node.id, 40866 to: targetNode.id, 40867 physics: false, 40868 smooth: { 40869 enabled: true, 40870 type: 'continuous', 40871 roundness: 0.5 40872 } 40873 }); 40874 this.body.edges[connectionEdge.id] = connectionEdge; 40875 this.body.edgeIndices.push(connectionEdge.id); 40876 40877 this.temporaryIds.nodes.push(targetNode.id); 40878 this.temporaryIds.edges.push(connectionEdge.id); 40879 } 40880 } 40881 this.touchTime = new Date().valueOf(); 40882 } 40883 } 40884 }, { 40885 key: '_dragControlNode', 40886 value: function _dragControlNode(event) { 40887 var pointer = this.body.functions.getPointer(event.center); 40888 if (this.temporaryIds.nodes[0] !== undefined) { 40889 var targetNode = this.body.nodes[this.temporaryIds.nodes[0]]; // there is only one temp node in the add edge mode. 40890 targetNode.x = this.canvas._XconvertDOMtoCanvas(pointer.x); 40891 targetNode.y = this.canvas._YconvertDOMtoCanvas(pointer.y); 40892 this.body.emitter.emit('_redraw'); 40893 } else { 40894 var diffX = pointer.x - this.lastTouch.x; 40895 var diffY = pointer.y - this.lastTouch.y; 40896 this.body.view.translation = { x: this.lastTouch.translation.x + diffX, y: this.lastTouch.translation.y + diffY }; 40897 } 40898 } 40899 }, { 40900 key: '_finishConnect', 40901 40902 /** 40903 * Connect the new edge to the target if one exists, otherwise remove temp line 40904 * @param event 40905 * @private 40906 */ 40907 value: function _finishConnect(event) { 40908 var pointer = this.body.functions.getPointer(event.center); 40909 var pointerObj = this.selectionHandler._pointerToPositionObject(pointer); 40910 40911 // remember the edge id 40912 var connectFromId = undefined; 40913 if (this.temporaryIds.edges[0] !== undefined) { 40914 connectFromId = this.body.edges[this.temporaryIds.edges[0]].fromId; 40915 } 40916 40917 // get the overlapping node but NOT the temporary node; 40918 var overlappingNodeIds = this.selectionHandler._getAllNodesOverlappingWith(pointerObj); 40919 var node = undefined; 40920 for (var i = overlappingNodeIds.length - 1; i >= 0; i--) { 40921 // if the node id is NOT a temporary node, accept the node. 40922 if (this.temporaryIds.nodes.indexOf(overlappingNodeIds[i]) === -1) { 40923 node = this.body.nodes[overlappingNodeIds[i]]; 40924 break; 40925 } 40926 } 40927 40928 // clean temporary nodes and edges. 40929 this._cleanupTemporaryNodesAndEdges(); 40930 40931 // perform the connection 40932 if (node !== undefined) { 40933 if (node.isCluster === true) { 40934 alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError']); 40935 } else { 40936 if (this.body.nodes[connectFromId] !== undefined && this.body.nodes[node.id] !== undefined) { 40937 this._performAddEdge(connectFromId, node.id); 40938 } 40939 } 40940 } 40941 this.body.emitter.emit('_redraw'); 40942 } 40943 }, {
40944 key: '_performAddNode', 40945 40946 // --------------------------------------- END OF ADD EDGE FUNCTIONS -------------------------------------// 40947 40948 // ------------------------------ Performing all the actual data manipulation ------------------------// 40949 40950 /** 40951 * Adds a node on the specified location 40952 */ 40953 value: function _performAddNode(clickData) { 40954 var _this5 = this; 40955 40956 var defaultData = { 40957 id: util.randomUUID(), 40958 x: clickData.pointer.canvas.x, 40959 y: clickData.pointer.canvas.y, 40960 label: 'new' 40961 }; 40962 40963 if (typeof this.options.addNode === 'function') { 40964 if (this.options.addNode.length === 2) { 40965 this.options.addNode(defaultData, function (finalizedData) { 40966 if (finalizedData !== null && finalizedData !== undefined && _this5.inMode === 'addNode') { 40967 // if for whatever reason the mode has changes (due to dataset change) disregard the callback 40968 _this5.body.data.nodes.getDataSet().add(finalizedData); 40969 _this5.showManipulatorToolbar(); 40970 } 40971 }); 40972 } else { 40973 throw new Error('The function for add does not support two arguments (data,callback)'); 40974 this.showManipulatorToolbar(); 40975 } 40976 } else { 40977 this.body.data.nodes.getDataSet().add(defaultData); 40978 this.showManipulatorToolbar(); 40979 } 40980 } 40981 }, { 40982 key: '_performAddEdge', 40983 40984 /** 40985 * connect two nodes with a new edge. 40986 * 40987 * @private 40988 */ 40989 value: function _performAddEdge(sourceNodeId, targetNodeId) { 40990 var _this6 = this; 40991 40992 var defaultData = { from: sourceNodeId, to: targetNodeId }; 40993 if (typeof this.options.addEdge === 'function') { 40994 if (this.options.addEdge.length === 2) { 40995 this.options.addEdge(defaultData, function (finalizedData) { 40996 if (finalizedData !== null && finalizedData !== undefined && _this6.inMode === 'addEdge') { 40997 // if for whatever reason the mode has changes (due to dataset change) disregard the callback 40998 _this6.body.data.edges.getDataSet().add(finalizedData); 40999 _this6.selectionHandler.unselectAll(); 41000 _this6.showManipulatorToolbar(); 41001 } 41002 }); 41003 } else { 41004 throw new Error('The function for connect does not support two arguments (data,callback)'); 41005 } 41006 } else { 41007 this.body.data.edges.getDataSet().add(defaultData); 41008 this.selectionHandler.unselectAll(); 41009 this.showManipulatorToolbar(); 41010 } 41011 } 41012 }, { 41013 key: '_performEditEdge', 41014 41015 /** 41016 * connect two nodes with a new edge. 41017 * 41018 * @private 41019 */ 41020 value: function _performEditEdge(sourceNodeId, targetNodeId) { 41021 var _this7 = this; 41022 41023 var defaultData = { id: this.edgeBeingEditedId, from: sourceNodeId, to: targetNodeId }; 41024 if (typeof this.options.editEdge === 'function') { 41025 if (this.options.editEdge.length === 2) { 41026 this.options.editEdge(defaultData, function (finalizedData) { 41027 if (finalizedData === null || finalizedData === undefined || _this7.inMode !== 'editEdge') { 41028 // if for whatever reason the mode has changes (due to dataset change) disregard the callback) { 41029 _this7.body.edges[defaultData.id].updateEdgeType(); 41030 _this7.body.emitter.emit('_redraw'); 41031 } else { 41032 _this7.body.data.edges.getDataSet().update(finalizedData); 41033 _this7.selectionHandler.unselectAll(); 41034 _this7.showManipulatorToolbar(); 41035 } 41036 }); 41037 } else { 41038 throw new Error('The function for edit does not support two arguments (data, callback)'); 41039 } 41040 } else { 41041 this.body.data.edges.getDataSet().update(defaultData); 41042 this.selectionHandler.unselectAll(); 41043 this.showManipulatorToolbar(); 41044 } 41045 } 41046 }]); 41047 41048 return ManipulationSystem; 41049 })(); 41050 41051 exports['default'] = ManipulationSystem; 41052 module.exports = exports['default']; 41053 41054/***/ }, 41055/* 111 */
41056/***/ function(module, exports) { 41057 41058 /** 41059 * This object contains all possible options. It will check if the types are correct, if required if the option is one 41060 * of the allowed values. 41061 * 41062 * __any__ means that the name of the property does not matter. 41063 * __type__ is a required field for all objects and contains the allowed types of all objects 41064 */ 41065 'use strict'; 41066 41067 Object.defineProperty(exports, '__esModule', { 41068 value: true 41069 }); 41070 var string = 'string'; 41071 var boolean = 'boolean'; 41072 var number = 'number'; 41073 var array = 'array'; 41074 var object = 'object'; // should only be in a __type__ property 41075 var dom = 'dom'; 41076 var any = 'any'; 41077 41078 var allOptions = { 41079 configure: { 41080 enabled: { boolean: boolean }, 41081 filter: { boolean: boolean, string: string, array: array, 'function': 'function' }, 41082 container: { dom: dom }, 41083 showButton: { boolean: boolean }, 41084 __type__: { object: object, boolean: boolean, string: string, array: array, 'function': 'function' } 41085 }, 41086 edges: { 41087 arrows: { 41088 to: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object, boolean: boolean } }, 41089 middle: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object, boolean: boolean } }, 41090 from: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object, boolean: boolean } }, 41091 __type__: { string: ['from', 'to', 'middle'], object: object } 41092 }, 41093 color: { 41094 color: { string: string }, 41095 highlight: { string: string }, 41096 hover: { string: string }, 41097 inherit: { string: ['from', 'to', 'both'], boolean: boolean }, 41098 opacity: { number: number }, 41099 __type__: { object: object, string: string } 41100 }, 41101 dashes: { boolean: boolean, array: array }, 41102 font: { 41103 color: { string: string }, 41104 size: { number: number }, // px 41105 face: { string: string }, 41106 background: { string: string }, 41107 strokeWidth: { number: number }, // px 41108 strokeColor: { string: string }, 41109 align: { string: ['horizontal', 'top', 'middle', 'bottom'] }, 41110 __type__: { object: object, string: string } 41111 }, 41112 hidden: { boolean: boolean }, 41113 hoverWidth: { 'function': 'function', number: number }, 41114 label: { string: string, 'undefined': 'undefined' }, 41115 labelHighlightBold: { boolean: boolean }, 41116 length: { number: number, 'undefined': 'undefined' }, 41117 physics: { boolean: boolean }, 41118 scaling: { 41119 min: { number: number }, 41120 max: { number: number }, 41121 label: { 41122 enabled: { boolean: boolean }, 41123 min: { number: number }, 41124 max: { number: number }, 41125 maxVisible: { number: number }, 41126 drawThreshold: { number: number }, 41127 __type__: { object: object, boolean: boolean } 41128 }, 41129 customScalingFunction: { 'function': 'function' }, 41130 __type__: { object: object } 41131 }, 41132 selectionWidth: { 'function': 'function', number: number }, 41133 selfReferenceSize: { number: number }, 41134 shadow: { 41135 enabled: { boolean: boolean }, 41136 size: { number: number }, 41137 x: { number: number }, 41138 y: { number: number }, 41139 __type__: { object: object, boolean: boolean } 41140 }, 41141 smooth: { 41142 enabled: { boolean: boolean }, 41143 type: { string: ['dynamic', 'continuous', 'discrete', 'diagonalCross', 'straightCross', 'horizontal', 'vertical', 'curvedCW', 'curvedCCW', 'cubicBezier'] }, 41144 roundness: { number: number }, 41145 forceDirection: { string: ['horizontal', 'vertical', 'none'], boolean: boolean }, 41146 __type__: { object: object, boolean: boolean } 41147 }, 41148 title: { string: string, 'undefined': 'undefined' }, 41149 width: { number: number }, 41150 value: { number: number, 'undefined': 'undefined' }, 41151 __type__: { object: object } 41152 }, 41153 groups: { 41154 useDefaultGroups: { boolean: boolean }, 41155 __any__: 'get from nodes, will be overwritten below', 41156 __type__: { object: object } 41157 }, 41158 interaction: { 41159 dragNodes: { boolean: boolean }, 41160 dragView: { boolean: boolean }, 41161 hideEdgesOnDrag: { boolean: boolean }, 41162 hideNodesOnDrag: { boolean: boolean }, 41163 hover: { boolean: boolean }, 41164 keyboard: { 41165 enabled: { boolean: boolean }, 41166 speed: { x: { number: number }, y: { number: number }, zoom: { number: number }, __type__: { object: object } }, 41167 bindToWindow: { boolean: boolean }, 41168 __type__: { object: object, boolean: boolean } 41169 }, 41170 multiselect: { boolean: boolean }, 41171 navigationButtons: { boolean: boolean }, 41172 selectable: { boolean: boolean }, 41173 selectConnectedEdges: { boolean: boolean }, 41174 hoverConnectedEdges: { boolean: boolean }, 41175 tooltipDelay: { number: number }, 41176 zoomView: { boolean: boolean }, 41177 __type__: { object: object } 41178 }, 41179 layout: { 41180 randomSeed: { 'undefined': 'undefined', number: number }, 41181 improvedLayout: { boolean: boolean }, 41182 hierarchical: { 41183 enabled: { boolean: boolean }, 41184 levelSeparation: { number: number },
41185 direction: { string: ['UD', 'DU', 'LR', 'RL'] }, // UD, DU, LR, RL 41186 sortMethod: { string: ['hubsize', 'directed'] }, // hubsize, directed 41187 __type__: { object: object, boolean: boolean } 41188 }, 41189 __type__: { object: object } 41190 }, 41191 manipulation: { 41192 enabled: { boolean: boolean }, 41193 initiallyActive: { boolean: boolean }, 41194 addNode: { boolean: boolean, 'function': 'function' }, 41195 addEdge: { boolean: boolean, 'function': 'function' }, 41196 editNode: { 'function': 'function' }, 41197 editEdge: { boolean: boolean, 'function': 'function' }, 41198 deleteNode: { boolean: boolean, 'function': 'function' }, 41199 deleteEdge: { boolean: boolean, 'function': 'function' }, 41200 controlNodeStyle: 'get from nodes, will be overwritten below', 41201 __type__: { object: object, boolean: boolean } 41202 }, 41203 nodes: { 41204 borderWidth: { number: number }, 41205 borderWidthSelected: { number: number, 'undefined': 'undefined' }, 41206 brokenImage: { string: string, 'undefined': 'undefined' }, 41207 color: { 41208 border: { string: string }, 41209 background: { string: string }, 41210 highlight: { 41211 border: { string: string }, 41212 background: { string: string }, 41213 __type__: { object: object, string: string } 41214 }, 41215 hover: { 41216 border: { string: string }, 41217 background: { string: string }, 41218 __type__: { object: object, string: string } 41219 }, 41220 __type__: { object: object, string: string } 41221 }, 41222 fixed: { 41223 x: { boolean: boolean }, 41224 y: { boolean: boolean }, 41225 __type__: { object: object, boolean: boolean } 41226 }, 41227 font: { 41228 color: { string: string }, 41229 size: { number: number }, // px 41230 face: { string: string }, 41231 background: { string: string }, 41232 strokeWidth: { number: number }, // px 41233 strokeColor: { string: string }, 41234 __type__: { object: object, string: string } 41235 }, 41236 group: { string: string, number: number, 'undefined': 'undefined' }, 41237 hidden: { boolean: boolean }, 41238 icon: { 41239 face: { string: string }, 41240 code: { string: string }, //'\uf007', 41241 size: { number: number }, //50, 41242 color: { string: string }, 41243 __type__: { object: object } 41244 }, 41245 id: { string: string, number: number }, 41246 image: { string: string, 'undefined': 'undefined' }, // --> URL 41247 label: { string: string, 'undefined': 'undefined' }, 41248 labelHighlightBold: { boolean: boolean }, 41249 level: { number: number, 'undefined': 'undefined' }, 41250 mass: { number: number }, 41251 physics: { boolean: boolean }, 41252 scaling: { 41253 min: { number: number }, 41254 max: { number: number }, 41255 label: { 41256 enabled: { boolean: boolean }, 41257 min: { number: number }, 41258 max: { number: number }, 41259 maxVisible: { number: number }, 41260 drawThreshold: { number: number }, 41261 __type__: { object: object, boolean: boolean } 41262 }, 41263 customScalingFunction: { 'function': 'function' }, 41264 __type__: { object: object } 41265 }, 41266 shadow: { 41267 enabled: { boolean: boolean }, 41268 size: { number: number }, 41269 x: { number: number }, 41270 y: { number: number }, 41271 __type__: { object: object, boolean: boolean } 41272 }, 41273 shape: { string: ['ellipse', 'circle', 'database', 'box', 'text', 'image', 'circularImage', 'diamond', 'dot', 'star', 'triangle', 'triangleDown', 'square', 'icon'] }, 41274 shapeProperties: { 41275 borderDashes: { boolean: boolean, array: array }, 41276 borderRadius: { number: number }, 41277 useImageSize: { boolean: boolean }, 41278 useBorderWithImage: { boolean: boolean }, 41279 __type__: { object: object } 41280 }, 41281 size: { number: number }, 41282 title: { string: string, 'undefined': 'undefined' }, 41283 value: { number: number, 'undefined': 'undefined' }, 41284 x: { number: number }, 41285 y: { number: number }, 41286 __type__: { object: object } 41287 }, 41288 physics: { 41289 enabled: { boolean: boolean }, 41290 barnesHut: { 41291 gravitationalConstant: { number: number }, 41292 centralGravity: { number: number }, 41293 springLength: { number: number }, 41294 springConstant: { number: number }, 41295 damping: { number: number }, 41296 avoidOverlap: { number: number }, 41297 __type__: { object: object } 41298 }, 41299 forceAtlas2Based: { 41300 gravitationalConstant: { number: number }, 41301 centralGravity: { number: number }, 41302 springLength: { number: number }, 41303 springConstant: { number: number }, 41304 damping: { number: number }, 41305 avoidOverlap: { number: number }, 41306 __type__: { object: object } 41307 }, 41308 repulsion: { 41309 centralGravity: { number: number }, 41310 springLength: { number: number }, 41311 springConstant: { number: number }, 41312 nodeDistance: { number: number }, 41313 damping: { number: number }, 41314 __type__: { object: object } 41315 }, 41316 hierarchicalRepulsion: { 41317 centralGravity: { number: number }, 41318 springLength: { number: number }, 41319 springConstant: { number: number }, 41320 nodeDistance: { number: number }, 41321 damping: { number: number }, 41322 __type__: { object: object } 41323 }, 41324 maxVelocity: { number: number }, 41325 minVelocity: { number: number }, // px/s 41326 solver: { string: ['barnesHut', 'repulsion', 'hierarchicalRepulsion', 'forceAtlas2Based'] }, 41327 stabilization: { 41328 enabled: { boolean: boolean }, 41329 iterations: { number: number }, // maximum number of iteration to stabilize 41330 updateInterval: { number: number }, 41331 onlyDynamicEdges: { boolean: boolean }, 41332 fit: { boolean: boolean }, 41333 __type__: { object: object, boolean: boolean } 41334 }, 41335 timestep: { number: number }, 41336 adaptiveTimestep: { boolean: boolean }, 41337 __type__: { object: object, boolean: boolean } 41338 }, 41339 41340 //globals : 41341 autoResize: { boolean: boolean }, 41342 clickToUse: { boolean: boolean }, 41343 locale: { string: string }, 41344 locales: { 41345 __any__: { any: any }, 41346 __type__: { object: object } 41347 }, 41348 height: { string: string },
41349 width: { string: string }, 41350 __type__: { object: object } 41351 }; 41352 41353 allOptions.groups.__any__ = allOptions.nodes; 41354 allOptions.manipulation.controlNodeStyle = allOptions.nodes; 41355 41356 var configureOptions = { 41357 nodes: { 41358 borderWidth: [1, 0, 10, 1], 41359 borderWidthSelected: [2, 0, 10, 1], 41360 color: { 41361 border: ['color', '#2B7CE9'], 41362 background: ['color', '#97C2FC'], 41363 highlight: { 41364 border: ['color', '#2B7CE9'], 41365 background: ['color', '#D2E5FF'] 41366 }, 41367 hover: { 41368 border: ['color', '#2B7CE9'], 41369 background: ['color', '#D2E5FF'] 41370 } 41371 }, 41372 fixed: { 41373 x: false, 41374 y: false 41375 }, 41376 font: { 41377 color: ['color', '#343434'], 41378 size: [14, 0, 100, 1], // px 41379 face: ['arial', 'verdana', 'tahoma'], 41380 background: ['color', 'none'], 41381 strokeWidth: [0, 0, 50, 1], // px 41382 strokeColor: ['color', '#ffffff'] 41383 }, 41384 //group: 'string', 41385 hidden: false, 41386 labelHighlightBold: true, 41387 //icon: { 41388 // face: 'string', //'FontAwesome', 41389 // code: 'string', //'\uf007', 41390 // size: [50, 0, 200, 1], //50, 41391 // color: ['color','#2B7CE9'] //'#aa00ff' 41392 //}, 41393 //image: 'string', // --> URL 41394 physics: true, 41395 scaling: { 41396 min: [10, 0, 200, 1], 41397 max: [30, 0, 200, 1], 41398 label: { 41399 enabled: false, 41400 min: [14, 0, 200, 1], 41401 max: [30, 0, 200, 1], 41402 maxVisible: [30, 0, 200, 1], 41403 drawThreshold: [5, 0, 20, 1] 41404 } 41405 }, 41406 shadow: { 41407 enabled: false, 41408 size: [10, 0, 20, 1], 41409 x: [5, -30, 30, 1], 41410 y: [5, -30, 30, 1] 41411 }, 41412 shape: ['ellipse', 'box', 'circle', 'database', 'diamond', 'dot', 'square', 'star', 'text', 'triangle', 'triangleDown'], 41413 shapeProperties: { 41414 borderDashes: false, 41415 borderRadius: [6, 0, 20, 1], 41416 useImageSize: false 41417 }, 41418 size: [25, 0, 200, 1] 41419 }, 41420 edges: { 41421 arrows: { 41422 to: { enabled: false, scaleFactor: [1, 0, 3, 0.05] }, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1} 41423 middle: { enabled: false, scaleFactor: [1, 0, 3, 0.05] }, 41424 from: { enabled: false, scaleFactor: [1, 0, 3, 0.05] } 41425 }, 41426 color: { 41427 color: ['color', '#848484'], 41428 highlight: ['color', '#848484'], 41429 hover: ['color', '#848484'], 41430 inherit: ['from', 'to', 'both', true, false], 41431 opacity: [1, 0, 1, 0.05] 41432 }, 41433 dashes: false, 41434 font: { 41435 color: ['color', '#343434'], 41436 size: [14, 0, 100, 1], // px 41437 face: ['arial', 'verdana', 'tahoma'], 41438 background: ['color', 'none'], 41439 strokeWidth: [2, 0, 50, 1], // px 41440 strokeColor: ['color', '#ffffff'], 41441 align: ['horizontal', 'top', 'middle', 'bottom'] 41442 }, 41443 hidden: false, 41444 hoverWidth: [1.5, 0, 5, 0.1], 41445 labelHighlightBold: true, 41446 physics: true, 41447 scaling: { 41448 min: [1, 0, 100, 1], 41449 max: [15, 0, 100, 1], 41450 label: { 41451 enabled: true, 41452 min: [14, 0, 200, 1], 41453 max: [30, 0, 200, 1], 41454 maxVisible: [30, 0, 200, 1], 41455 drawThreshold: [5, 0, 20, 1] 41456 } 41457 }, 41458 selectionWidth: [1.5, 0, 5, 0.1], 41459 selfReferenceSize: [20, 0, 200, 1], 41460 shadow: { 41461 enabled: false, 41462 size: [10, 0, 20, 1], 41463 x: [5, -30, 30, 1], 41464 y: [5, -30, 30, 1] 41465 }, 41466 smooth: { 41467 enabled: true, 41468 type: ['dynamic', 'continuous', 'discrete', 'diagonalCross', 'straightCross', 'horizontal', 'vertical', 'curvedCW', 'curvedCCW', 'cubicBezier'], 41469 forceDirection: ['horizontal', 'vertical', 'none'], 41470 roundness: [0.5, 0, 1, 0.05] 41471 }, 41472 width: [1, 0, 30, 1] 41473 }, 41474 layout: { 41475 //randomSeed: [0, 0, 500, 1], 41476 //improvedLayout: true, 41477 hierarchical: { 41478 enabled: false, 41479 levelSeparation: [150, 20, 500, 5], 41480 direction: ['UD', 'DU', 'LR', 'RL'], // UD, DU, LR, RL 41481 sortMethod: ['hubsize', 'directed'] // hubsize, directed 41482 } 41483 }, 41484 interaction: { 41485 dragNodes: true, 41486 dragView: true, 41487 hideEdgesOnDrag: false, 41488 hideNodesOnDrag: false, 41489 hover: false, 41490 keyboard: { 41491 enabled: false, 41492 speed: { x: [10, 0, 40, 1], y: [10, 0, 40, 1], zoom: [0.02, 0, 0.1, 0.005] }, 41493 bindToWindow: true 41494 }, 41495 multiselect: false,
41496 navigationButtons: false, 41497 selectable: true, 41498 selectConnectedEdges: true, 41499 hoverConnectedEdges: true, 41500 tooltipDelay: [300, 0, 1000, 25], 41501 zoomView: true 41502 }, 41503 manipulation: { 41504 enabled: false, 41505 initiallyActive: false 41506 }, 41507 physics: { 41508 enabled: true, 41509 barnesHut: { 41510 //theta: [0.5, 0.1, 1, 0.05], 41511 gravitationalConstant: [-2000, -30000, 0, 50], 41512 centralGravity: [0.3, 0, 10, 0.05], 41513 springLength: [95, 0, 500, 5], 41514 springConstant: [0.04, 0, 1.2, 0.005], 41515 damping: [0.09, 0, 1, 0.01], 41516 avoidOverlap: [0, 0, 1, 0.01] 41517 }, 41518 forceAtlas2Based: { 41519 //theta: [0.5, 0.1, 1, 0.05], 41520 gravitationalConstant: [-50, -500, 0, 1], 41521 centralGravity: [0.01, 0, 1, 0.005], 41522 springLength: [95, 0, 500, 5], 41523 springConstant: [0.08, 0, 1.2, 0.005], 41524 damping: [0.4, 0, 1, 0.01], 41525 avoidOverlap: [0, 0, 1, 0.01] 41526 }, 41527 repulsion: { 41528 centralGravity: [0.2, 0, 10, 0.05], 41529 springLength: [200, 0, 500, 5], 41530 springConstant: [0.05, 0, 1.2, 0.005], 41531 nodeDistance: [100, 0, 500, 5], 41532 damping: [0.09, 0, 1, 0.01] 41533 }, 41534 hierarchicalRepulsion: { 41535 centralGravity: [0.2, 0, 10, 0.05], 41536 springLength: [100, 0, 500, 5], 41537 springConstant: [0.01, 0, 1.2, 0.005], 41538 nodeDistance: [120, 0, 500, 5], 41539 damping: [0.09, 0, 1, 0.01] 41540 }, 41541 maxVelocity: [50, 0, 150, 1], 41542 minVelocity: [0.1, 0.01, 0.5, 0.01], 41543 solver: ['barnesHut', 'forceAtlas2Based', 'repulsion', 'hierarchicalRepulsion'], 41544 timestep: [0.5, 0.01, 1, 0.01] 41545 }, 41546 global: { 41547 locale: ['en', 'nl'] 41548 } 41549 }; 41550 41551 exports.allOptions = allOptions; 41552 exports.configureOptions = configureOptions; 41553 //adaptiveTimestep: true 41554 41555/***/ }, 41556/* 112 */ 41557/***/ function(module, exports, __webpack_require__) { 41558 41559 /** 41560 * Created by Alex on 8/7/2015. 41561 */ 41562 41563 // distance finding algorithm 41564 "use strict"; 41565 41566 Object.defineProperty(exports, "__esModule", { 41567 value: true 41568 }); 41569 41570 var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; })(); 41571 41572 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 41573 41574 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 41575 41576 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 41577 41578 var _componentsAlgorithmsFloydWarshallJs = __webpack_require__(113); 41579 41580 var _componentsAlgorithmsFloydWarshallJs2 = _interopRequireDefault(_componentsAlgorithmsFloydWarshallJs); 41581 41582 /** 41583 * KamadaKawai positions the nodes initially based on 41584 * 41585 * "AN ALGORITHM FOR DRAWING GENERAL UNDIRECTED GRAPHS" 41586 * -- Tomihisa KAMADA and Satoru KAWAI in 1989 41587 * 41588 * Possible optimizations in the distance calculation can be implemented. 41589 */ 41590 41591 var KamadaKawai = (function () { 41592 function KamadaKawai(body, edgeLength, edgeStrength) { 41593 _classCallCheck(this, KamadaKawai); 41594 41595 this.body = body; 41596 this.springLength = edgeLength; 41597 this.springConstant = edgeStrength; 41598 this.distanceSolver = new _componentsAlgorithmsFloydWarshallJs2["default"](); 41599 } 41600 41601 _createClass(KamadaKawai, [{ 41602 key: "setOptions", 41603 41604 /** 41605 * Not sure if needed but can be used to update the spring length and spring constant 41606 * @param options 41607 */ 41608 value: function setOptions(options) { 41609 if (options) { 41610 if (options.springLength) { 41611 this.springLength = options.springLength; 41612 } 41613 if (options.springConstant) { 41614 this.springConstant = options.springConstant; 41615 } 41616 } 41617 } 41618 }, {
41619 key: "solve", 41620 41621 /** 41622 * Position the system 41623 * @param nodesArray 41624 * @param edgesArray 41625 */ 41626 value: function solve(nodesArray, edgesArray) { 41627 var ignoreClusters = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 41628 41629 // get distance matrix 41630 var D_matrix = this.distanceSolver.getDistances(this.body, nodesArray, edgesArray); // distance matrix 41631 41632 // get the L Matrix 41633 this._createL_matrix(D_matrix); 41634 41635 // get the K Matrix 41636 this._createK_matrix(D_matrix); 41637 41638 // calculate positions 41639 var threshold = 0.01; 41640 var innerThreshold = 1; 41641 var iterations = 0; 41642 var maxIterations = Math.max(1000, Math.min(10 * this.body.nodeIndices.length, 6000)); 41643 var maxInnerIterations = 5; 41644 41645 var maxEnergy = 1e9; 41646 var highE_nodeId = 0, 41647 dE_dx = 0, 41648 dE_dy = 0, 41649 delta_m = 0, 41650 subIterations = 0; 41651 41652 while (maxEnergy > threshold && iterations < maxIterations) { 41653 iterations += 1; 41654 41655 var _getHighestEnergyNode2 = this._getHighestEnergyNode(ignoreClusters); 41656 41657 var _getHighestEnergyNode22 = _slicedToArray(_getHighestEnergyNode2, 4); 41658 41659 highE_nodeId = _getHighestEnergyNode22[0]; 41660 maxEnergy = _getHighestEnergyNode22[1]; 41661 dE_dx = _getHighestEnergyNode22[2]; 41662 dE_dy = _getHighestEnergyNode22[3]; 41663 41664 delta_m = maxEnergy; 41665 subIterations = 0; 41666 while (delta_m > innerThreshold && subIterations < maxInnerIterations) { 41667 subIterations += 1; 41668 this._moveNode(highE_nodeId, dE_dx, dE_dy); 41669 41670 var _getEnergy2 = this._getEnergy(highE_nodeId); 41671 41672 var _getEnergy22 = _slicedToArray(_getEnergy2, 3); 41673 41674 delta_m = _getEnergy22[0]; 41675 dE_dx = _getEnergy22[1]; 41676 dE_dy = _getEnergy22[2]; 41677 } 41678 } 41679 } 41680 }, { 41681 key: "_getHighestEnergyNode", 41682 41683 /** 41684 * get the node with the highest energy 41685 * @returns {*[]} 41686 * @private 41687 */ 41688 value: function _getHighestEnergyNode(ignoreClusters) { 41689 var nodesArray = this.body.nodeIndices; 41690 var nodes = this.body.nodes; 41691 var maxEnergy = 0; 41692 var maxEnergyNodeId = nodesArray[0]; 41693 var dE_dx_max = 0, 41694 dE_dy_max = 0; 41695 41696 for (var nodeIdx = 0; nodeIdx < nodesArray.length; nodeIdx++) { 41697 var m = nodesArray[nodeIdx]; 41698 // by not evaluating nodes with predefined positions we should only move nodes that have no positions. 41699 if (nodes[m].predefinedPosition === false || nodes[m].isCluster === true && ignoreClusters === true || nodes[m].options.fixed.x === true || nodes[m].options.fixed.y === true) { 41700 var _getEnergy3 = this._getEnergy(m); 41701 41702 var _getEnergy32 = _slicedToArray(_getEnergy3, 3); 41703 41704 var delta_m = _getEnergy32[0]; 41705 var dE_dx = _getEnergy32[1]; 41706 var dE_dy = _getEnergy32[2]; 41707 41708 if (maxEnergy < delta_m) { 41709 maxEnergy = delta_m; 41710 maxEnergyNodeId = m; 41711 dE_dx_max = dE_dx; 41712 dE_dy_max = dE_dy; 41713 } 41714 } 41715 } 41716 41717 return [maxEnergyNodeId, maxEnergy, dE_dx_max, dE_dy_max]; 41718 } 41719 }, { 41720 key: "_getEnergy", 41721 41722 /** 41723 * calculate the energy of a single node 41724 * @param m 41725 * @returns {*[]} 41726 * @private 41727 */ 41728 value: function _getEnergy(m) { 41729 var nodesArray = this.body.nodeIndices; 41730 var nodes = this.body.nodes; 41731 41732 var x_m = nodes[m].x; 41733 var y_m = nodes[m].y; 41734 var dE_dx = 0; 41735 var dE_dy = 0; 41736 for (var iIdx = 0; iIdx < nodesArray.length; iIdx++) { 41737 var i = nodesArray[iIdx]; 41738 if (i !== m) { 41739 var x_i = nodes[i].x; 41740 var y_i = nodes[i].y; 41741 var denominator = 1.0 / Math.sqrt(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2)); 41742 dE_dx += this.K_matrix[m][i] * (x_m - x_i - this.L_matrix[m][i] * (x_m - x_i) * denominator); 41743 dE_dy += this.K_matrix[m][i] * (y_m - y_i - this.L_matrix[m][i] * (y_m - y_i) * denominator); 41744 } 41745 } 41746 41747 var delta_m = Math.sqrt(Math.pow(dE_dx, 2) + Math.pow(dE_dy, 2)); 41748 return [delta_m, dE_dx, dE_dy]; 41749 } 41750 }, {
41751 key: "_moveNode", 41752 41753 /** 41754 * move the node based on it's energy 41755 * the dx and dy are calculated from the linear system proposed by Kamada and Kawai 41756 * @param m 41757 * @param dE_dx 41758 * @param dE_dy 41759 * @private 41760 */ 41761 value: function _moveNode(m, dE_dx, dE_dy) { 41762 var nodesArray = this.body.nodeIndices; 41763 var nodes = this.body.nodes; 41764 var d2E_dx2 = 0; 41765 var d2E_dxdy = 0; 41766 var d2E_dy2 = 0; 41767 41768 var x_m = nodes[m].x; 41769 var y_m = nodes[m].y; 41770 for (var iIdx = 0; iIdx < nodesArray.length; iIdx++) { 41771 var i = nodesArray[iIdx]; 41772 if (i !== m) { 41773 var x_i = nodes[i].x; 41774 var y_i = nodes[i].y; 41775 var denominator = 1.0 / Math.pow(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2), 1.5); 41776 d2E_dx2 += this.K_matrix[m][i] * (1 - this.L_matrix[m][i] * Math.pow(y_m - y_i, 2) * denominator); 41777 d2E_dxdy += this.K_matrix[m][i] * (this.L_matrix[m][i] * (x_m - x_i) * (y_m - y_i) * denominator); 41778 d2E_dy2 += this.K_matrix[m][i] * (1 - this.L_matrix[m][i] * Math.pow(x_m - x_i, 2) * denominator); 41779 } 41780 } 41781 // make the variable names easier to make the solving of the linear system easier to read 41782 var A = d2E_dx2, 41783 B = d2E_dxdy, 41784 C = dE_dx, 41785 D = d2E_dy2, 41786 E = dE_dy; 41787 41788 // solve the linear system for dx and dy 41789 var dy = (C / A + E / B) / (B / A - D / B); 41790 var dx = -(B * dy + C) / A; 41791 41792 // move the node 41793 nodes[m].x += dx; 41794 nodes[m].y += dy; 41795 } 41796 }, { 41797 key: "_createL_matrix", 41798 41799 /** 41800 * Create the L matrix: edge length times shortest path 41801 * @param D_matrix 41802 * @private 41803 */ 41804 value: function _createL_matrix(D_matrix) { 41805 var nodesArray = this.body.nodeIndices; 41806 var edgeLength = this.springLength; 41807 41808 this.L_matrix = []; 41809 for (var i = 0; i < nodesArray.length; i++) { 41810 this.L_matrix[nodesArray[i]] = {}; 41811 for (var j = 0; j < nodesArray.length; j++) { 41812 this.L_matrix[nodesArray[i]][nodesArray[j]] = edgeLength * D_matrix[nodesArray[i]][nodesArray[j]]; 41813 } 41814 } 41815 } 41816 }, { 41817 key: "_createK_matrix", 41818 41819 /** 41820 * Create the K matrix: spring constants times shortest path 41821 * @param D_matrix 41822 * @private 41823 */ 41824 value: function _createK_matrix(D_matrix) { 41825 var nodesArray = this.body.nodeIndices; 41826 var edgeStrength = this.springConstant; 41827 41828 this.K_matrix = []; 41829 for (var i = 0; i < nodesArray.length; i++) { 41830 this.K_matrix[nodesArray[i]] = {}; 41831 for (var j = 0; j < nodesArray.length; j++) { 41832 this.K_matrix[nodesArray[i]][nodesArray[j]] = edgeStrength * Math.pow(D_matrix[nodesArray[i]][nodesArray[j]], -2); 41833 } 41834 } 41835 } 41836 }]); 41837 41838 return KamadaKawai; 41839 })(); 41840 41841 exports["default"] = KamadaKawai; 41842 module.exports = exports["default"]; 41843 41844/***/ }, 41845/* 113 */ 41846/***/ function(module, exports) { 41847 41848 /** 41849 * Created by Alex on 10-Aug-15. 41850 */ 41851 41852 "use strict"; 41853 41854 Object.defineProperty(exports, "__esModule", { 41855 value: true 41856 }); 41857 41858 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 41859 41860 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 41861 41862 var FloydWarshall = (function () { 41863 function FloydWarshall() { 41864 _classCallCheck(this, FloydWarshall); 41865 } 41866 41867 _createClass(FloydWarshall, [{ 41868 key: "getDistances", 41869 value: function getDistances(body, nodesArray, edgesArray) { 41870 var D_matrix = {}; 41871 var edges = body.edges; 41872 41873 // prepare matrix with large numbers 41874 for (var i = 0; i < nodesArray.length; i++) { 41875 D_matrix[nodesArray[i]] = {}; 41876 D_matrix[nodesArray[i]] = {}; 41877 for (var j = 0; j < nodesArray.length; j++) { 41878 D_matrix[nodesArray[i]][nodesArray[j]] = i == j ? 0 : 1e9; 41879 D_matrix[nodesArray[i]][nodesArray[j]] = i == j ? 0 : 1e9; 41880 } 41881 } 41882 41883 // put the weights for the edges in. This assumes unidirectionality. 41884 for (var i = 0; i < edgesArray.length; i++) { 41885 var edge = edges[edgesArray[i]]; 41886 if (edge.connected === true) { 41887 // edge has to be connected if it counts to the distances. 41888 D_matrix[edge.fromId][edge.toId] = 1; 41889 D_matrix[edge.toId][edge.fromId] = 1; 41890 } 41891 } 41892 41893 var nodeCount = nodesArray.length; 41894 41895 // Adapted FloydWarshall based on unidirectionality to greatly reduce complexity. 41896 for (var k = 0; k < nodeCount;
41896 k++) { 41897 for (var i = 0; i < nodeCount - 1; i++) { 41898 for (var j = i + 1; j < nodeCount; j++) { 41899 D_matrix[nodesArray[i]][nodesArray[j]] = Math.min(D_matrix[nodesArray[i]][nodesArray[j]], D_matrix[nodesArray[i]][nodesArray[k]] + D_matrix[nodesArray[k]][nodesArray[j]]); 41900 D_matrix[nodesArray[j]][nodesArray[i]] = D_matrix[nodesArray[i]][nodesArray[j]]; 41901 } 41902 } 41903 } 41904 41905 return D_matrix; 41906 } 41907 }]); 41908 41909 return FloydWarshall; 41910 })(); 41911 41912 exports["default"] = FloydWarshall; 41913 module.exports = exports["default"]; 41914 41915/***/ }, 41916/* 114 */ 41917/***/ function(module, exports) { 41918 41919 /** 41920 * Canvas shapes used by Network 41921 */ 41922 'use strict'; 41923 41924 if (typeof CanvasRenderingContext2D !== 'undefined') { 41925 41926 /** 41927 * Draw a circle shape 41928 */ 41929 CanvasRenderingContext2D.prototype.circle = function (x, y, r) { 41930 this.beginPath(); 41931 this.arc(x, y, r, 0, 2 * Math.PI, false); 41932 this.closePath(); 41933 }; 41934 41935 /** 41936 * Draw a square shape 41937 * @param {Number} x horizontal center 41938 * @param {Number} y vertical center 41939 * @param {Number} r size, width and height of the square 41940 */ 41941 CanvasRenderingContext2D.prototype.square = function (x, y, r) { 41942 this.beginPath(); 41943 this.rect(x - r, y - r, r * 2, r * 2); 41944 this.closePath(); 41945 }; 41946 41947 /** 41948 * Draw a triangle shape 41949 * @param {Number} x horizontal center 41950 * @param {Number} y vertical center 41951 * @param {Number} r radius, half the length of the sides of the triangle 41952 */ 41953 CanvasRenderingContext2D.prototype.triangle = function (x, y, r) { 41954 // http://en.wikipedia.org/wiki/Equilateral_triangle 41955 this.beginPath(); 41956 41957 // the change in radius and the offset is here to center the shape 41958 r *= 1.15; 41959 y += 0.275 * r; 41960 41961 var s = r * 2; 41962 var s2 = s / 2; 41963 var ir = Math.sqrt(3) / 6 * s; // radius of inner circle 41964 var h = Math.sqrt(s * s - s2 * s2); // height 41965 41966 this.moveTo(x, y - (h - ir)); 41967 this.lineTo(x + s2, y + ir); 41968 this.lineTo(x - s2, y + ir); 41969 this.lineTo(x, y - (h - ir)); 41970 this.closePath(); 41971 }; 41972 41973 /** 41974 * Draw a triangle shape in downward orientation 41975 * @param {Number} x horizontal center 41976 * @param {Number} y vertical center 41977 * @param {Number} r radius 41978 */ 41979 CanvasRenderingContext2D.prototype.triangleDown = function (x, y, r) { 41980 // http://en.wikipedia.org/wiki/Equilateral_triangle 41981 this.beginPath(); 41982 41983 // the change in radius and the offset is here to center the shape 41984 r *= 1.15; 41985 y -= 0.275 * r; 41986 41987 var s = r * 2; 41988 var s2 = s / 2; 41989 var ir = Math.sqrt(3) / 6 * s; // radius of inner circle 41990 var h = Math.sqrt(s * s - s2 * s2); // height 41991 41992 this.moveTo(x, y + (h - ir)); 41993 this.lineTo(x + s2, y - ir); 41994 this.lineTo(x - s2, y - ir); 41995 this.lineTo(x, y + (h - ir)); 41996 this.closePath(); 41997 }; 41998 41999 /** 42000 * Draw a star shape, a star with 5 points 42001 * @param {Number} x horizontal center 42002 * @param {Number} y vertical center 42003 * @param {Number} r radius, half the length of the sides of the triangle 42004 */ 42005 CanvasRenderingContext2D.prototype.star = function (x, y, r) { 42006 // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/ 42007 this.beginPath(); 42008 42009 // the change in radius and the offset is here to center the shape 42010 r *= 0.82; 42011 y += 0.1 * r; 42012 42013 for (var n = 0; n < 10; n++) { 42014 var radius = n % 2 === 0 ? r * 1.3 : r * 0.5; 42015 this.lineTo(x + radius * Math.sin(n * 2 * Math.PI / 10), y - radius * Math.cos(n * 2 * Math.PI / 10)); 42016 } 42017 42018 this.closePath(); 42019 }; 42020 42021 /** 42022 * Draw a Diamond shape 42023 * @param {Number} x horizontal center 42024 * @param {Number} y vertical center 42025 * @param {Number} r radius, half the length of the sides of the triangle 42026 */ 42027 CanvasRenderingContext2D.prototype.diamond = function (x, y, r) { 42028 // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/ 42029 this.beginPath(); 42030 42031 this.lineTo(x, y + r); 42032 this.lineTo(x + r, y); 42033 this.lineTo(x, y - r); 42034 this.lineTo(x - r, y); 42035 42036 this.closePath(); 42037 }; 42038 42039 /** 42040 * http://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas 42041 */ 42042 CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) { 42043 var r2d = Math.PI / 180; 42044 if (w - 2 * r < 0) { 42045 r = w / 2; 42046 } //ensure that the radius isn't too large for x 42047 if (h - 2 * r < 0) { 42048 r = h / 2; 42049 } //ensure that the radius isn't too large for y 42050 this.beginPath(); 42051 this.moveTo(x + r, y); 42052 this.lineTo(x + w - r, y); 42053 this.arc(x + w - r, y + r, r, r2d * 270, r2d * 360, false); 42054 this.lineTo(x + w, y + h - r); 42055 this.arc(x + w - r, y + h - r, r, 0, r2d * 90, false); 42056 this.lineTo(x + r, y + h); 42057 this.arc(x + r, y + h - r, r, r2d * 90, r2d * 180, false); 42058 this.lineTo(x, y + r); 42059 this.arc(x + r, y + r, r, r2d * 180, r2d * 270, false); 42060 this.closePath(); 42061 }; 42062 42063 /** 42064 * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas 42065 */ 42066 CanvasRenderingContext2D.prototype.ellipse = function (x, y, w, h) { 42067 var kappa = .5522848, 42068 ox = w / 2 * kappa, 42069 // control point offset horizontal 42070 oy = h / 2 * kappa, 42071 // control point offset vertical 42072 xe = x + w, 42073 // x-end 42074 ye = y + h, 42075 // y-end 42076 xm = x + w / 2, 42077 // x-middle 42078 ym = y + h / 2; // y-middle 42079 42080 this.beginPath(); 42081 this.moveTo(x, ym); 42082 this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); 42083 this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); 42084 this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); 42085 this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); 42086 this.closePath(); 42087 }; 42088 42089 /** 42090 * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas 42091 */ 42092 CanvasRenderingContext2D.prototype.database = function (x, y, w, h) { 42093 var f = 1 / 3; 42094 var wEllipse = w; 42095 var hEllipse = h * f; 42096 42097 var kappa = .5522848, 42098 ox = wEllipse / 2 * kappa, 42099 // control point offset horizontal 42100 oy = hEllipse / 2 * kappa, 42101 // control point offset vertical 42102 xe = x + wEllipse, 42103 // x-end 42104 ye = y + hEllipse, 42105 // y-end 42106 xm = x + wEllipse / 2, 42107 // x-middle 42108 ym = y + hEllipse / 2, 42109 // y-middle 42110 ymb = y + (h - hEllipse / 2), 42111 // y-midlle, bottom ellipse 42112 yeb = y + h; // y-end, bottom ellipse 42113 42114 this.beginPath(); 42115 this.moveTo(xe, ym); 42116 42117 this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); 42118 this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); 42119 42120 this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); 42121 this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); 42122 42123 this.lineTo(xe, ymb); 42124 42125 this.bezierCurveTo(xe, ymb + oy, xm + ox, yeb, xm, yeb); 42126 this.bezierCurveTo(xm - ox, yeb, x, ymb + oy, x, ymb); 42127 42128 this.lineTo(x, ym); 42129 }; 42130 42131 /** 42132 * Draw an arrow point (no line) 42133 */ 42134 CanvasRenderingContext2D.prototype.arrow = function (x, y, angle, length) { 42135 // tail 42136 var xt = x - length * Math.cos(angle); 42137 var yt = y - length * Math.sin(angle); 42138 42139 // inner tail 42140 var xi = x - length * 0.9 * Math.cos(angle); 42141 var yi = y - length * 0.9 * Math.sin(angle); 42142 42143 // left
42144 var xl = xt + length / 3 * Math.cos(angle + 0.5 * Math.PI); 42145 var yl = yt + length / 3 * Math.sin(angle + 0.5 * Math.PI); 42146 42147 // right 42148 var xr = xt + length / 3 * Math.cos(angle - 0.5 * Math.PI); 42149 var yr = yt + length / 3 * Math.sin(angle - 0.5 * Math.PI); 42150 42151 this.beginPath(); 42152 this.moveTo(x, y); 42153 this.lineTo(xl, yl); 42154 this.lineTo(xi, yi); 42155 this.lineTo(xr, yr); 42156 this.closePath(); 42157 }; 42158 42159 /** 42160 * Sets up the dashedLine functionality for drawing 42161 * Original code came from http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas 42162 * @author David Jordan 42163 * @date 2012-08-08 42164 */ 42165 CanvasRenderingContext2D.prototype.dashedLine = function (x, y, x2, y2, pattern) { 42166 this.beginPath(); 42167 this.moveTo(x, y); 42168 42169 var patternLength = pattern.length; 42170 var dx = x2 - x; 42171 var dy = y2 - y; 42172 var slope = dy / dx; 42173 var distRemaining = Math.sqrt(dx * dx + dy * dy); 42174 var patternIndex = 0; 42175 var draw = true; 42176 var xStep = 0; 42177 var dashLength = pattern[0]; 42178 42179 while (distRemaining >= 0.1) { 42180 dashLength = pattern[patternIndex++ % patternLength]; 42181 if (dashLength > distRemaining) { 42182 dashLength = distRemaining; 42183 } 42184 42185 xStep = Math.sqrt(dashLength * dashLength / (1 + slope * slope)); 42186 xStep = dx < 0 ? -xStep : xStep; 42187 x += xStep; 42188 y += slope * xStep; 42189 42190 if (draw === true) { 42191 this.lineTo(x, y); 42192 } else { 42193 this.moveTo(x, y); 42194 } 42195 42196 distRemaining -= dashLength; 42197 draw = !draw; 42198 } 42199 }; 42200 } 42201 42202/***/ }, 42203/* 115 */ 42204/***/ function(module, exports) { 42205 42206 /** 42207 * Parse a text source containing data in DOT language into a JSON object. 42208 * The object contains two lists: one with nodes and one with edges. 42209 * 42210 * DOT language reference: http://www.graphviz.org/doc/info/lang.html 42211 * 42212 * DOT language attributes: http://graphviz.org/content/attrs 42213 * 42214 * @param {String} data Text containing a graph in DOT-notation 42215 * @return {Object} graph An object containing two parameters: 42216 * {Object[]} nodes 42217 * {Object[]} edges 42218 */ 42219 'use strict'; 42220 42221 function parseDOT(data) { 42222 dot = data; 42223 return parseGraph(); 42224 } 42225 42226 // mapping of attributes from DOT (the keys) to vis.js (the values) 42227 var NODE_ATTR_MAPPING = { 42228 'fontsize': 'font.size', 42229 'fontcolor': 'font.color', 42230 'labelfontcolor': 'font.color', 42231 'fontname': 'font.face', 42232 'color': ['color.border', 'color.background'], 42233 'fillcolor': 'color.background', 42234 'tooltip': 'title', 42235 'labeltooltip': 'title' 42236 }; 42237 var EDGE_ATTR_MAPPING = Object.create(NODE_ATTR_MAPPING); 42238 EDGE_ATTR_MAPPING.color = 'color.color'; 42239 42240 // token types enumeration 42241 var TOKENTYPE = { 42242 NULL: 0, 42243 DELIMITER: 1, 42244 IDENTIFIER: 2, 42245 UNKNOWN: 3 42246 }; 42247 42248 // map with all delimiters 42249 var DELIMITERS = { 42250 '{': true, 42251 '}': true, 42252 '[': true, 42253 ']': true, 42254 ';': true, 42255 '=': true, 42256 ',': true, 42257 42258 '->': true, 42259 '--': true 42260 }; 42261 42262 var dot = ''; // current dot file 42263 var index = 0; // current index in dot file 42264 var c = ''; // current token character in expr 42265 var token = ''; // current token 42266 var tokenType = TOKENTYPE.NULL; // type of the token 42267 42268 /** 42269 * Get the first character from the dot file. 42270 * The character is stored into the char c. If the end of the dot file is 42271 * reached, the function puts an empty string in c. 42272 */ 42273 function first() { 42274 index = 0; 42275 c = dot.charAt(0); 42276 } 42277 42278 /** 42279 * Get the next character from the dot file. 42280 * The character is stored into the char c. If the end of the dot file is 42281 * reached, the function puts an empty string in c. 42282 */ 42283 function next() { 42284 index++; 42285 c = dot.charAt(index); 42286 } 42287 42288 /** 42289 * Preview the next character from the dot file. 42290 * @return {String} cNext 42291 */ 42292 function nextPreview() { 42293 return dot.charAt(index + 1); 42294 } 42295 42296 /** 42297 * Test whether given character is alphabetic or numeric 42298 * @param {String} c 42299 * @return {Boolean} isAlphaNumeric 42300 */ 42301 var regexAlphaNumeric = /[a-zA-Z_0-9.:#]/; 42302 function isAlphaNumeric(c) { 42303 return regexAlphaNumeric.test(c); 42304 } 42305 42306 /** 42307 * Merge all options of object b into object b 42308 * @param {Object} a 42309 * @param {Object} b 42310 * @return {Object} a 42311 */ 42312 function merge(a, b) { 42313 if (!a) { 42314 a = {}; 42315 } 42316 42317 if (b) { 42318 for (var name in b) { 42319 if (b.hasOwnProperty(name)) { 42320 a[name] = b[name]; 42321 } 42322 } 42323 } 42324 return a; 42325 } 42326 42327 /** 42328 * Set a value in an object, where the provided parameter name can be a
42329 * path with nested parameters. For example: 42330 * 42331 * var obj = {a: 2}; 42332 * setValue(obj, 'b.c', 3); // obj = {a: 2, b: {c: 3}} 42333 * 42334 * @param {Object} obj 42335 * @param {String} path A parameter name or dot-separated parameter path, 42336 * like "color.highlight.border". 42337 * @param {*} value 42338 */ 42339 function setValue(obj, path, value) { 42340 var keys = path.split('.'); 42341 var o = obj; 42342 while (keys.length) { 42343 var key = keys.shift(); 42344 if (keys.length) { 42345 // this isn't the end point 42346 if (!o[key]) { 42347 o[key] = {}; 42348 } 42349 o = o[key]; 42350 } else { 42351 // this is the end point 42352 o[key] = value; 42353 } 42354 } 42355 } 42356 42357 /** 42358 * Add a node to a graph object. If there is already a node with 42359 * the same id, their attributes will be merged. 42360 * @param {Object} graph 42361 * @param {Object} node 42362 */ 42363 function addNode(graph, node) { 42364 var i, len; 42365 var current = null; 42366 42367 // find root graph (in case of subgraph) 42368 var graphs = [graph]; // list with all graphs from current graph to root graph 42369 var root = graph; 42370 while (root.parent) { 42371 graphs.push(root.parent); 42372 root = root.parent; 42373 } 42374 42375 // find existing node (at root level) by its id 42376 if (root.nodes) { 42377 for (i = 0, len = root.nodes.length; i < len; i++) { 42378 if (node.id === root.nodes[i].id) { 42379 current = root.nodes[i]; 42380 break; 42381 } 42382 } 42383 } 42384 42385 if (!current) { 42386 // this is a new node 42387 current = { 42388 id: node.id 42389 }; 42390 if (graph.node) { 42391 // clone default attributes 42392 current.attr = merge(current.attr, graph.node); 42393 } 42394 } 42395 42396 // add node to this (sub)graph and all its parent graphs 42397 for (i = graphs.length - 1; i >= 0; i--) { 42398 var g = graphs[i]; 42399 42400 if (!g.nodes) { 42401 g.nodes = []; 42402 } 42403 if (g.nodes.indexOf(current) === -1) { 42404 g.nodes.push(current); 42405 } 42406 } 42407 42408 // merge attributes 42409 if (node.attr) { 42410 current.attr = merge(current.attr, node.attr); 42411 } 42412 } 42413 42414 /** 42415 * Add an edge to a graph object 42416 * @param {Object} graph 42417 * @param {Object} edge 42418 */ 42419 function addEdge(graph, edge) { 42420 if (!graph.edges) { 42421 graph.edges = []; 42422 } 42423 graph.edges.push(edge); 42424 if (graph.edge) { 42425 var attr = merge({}, graph.edge); // clone default attributes 42426 edge.attr = merge(attr, edge.attr); // merge attributes 42427 } 42428 } 42429 42430 /** 42431 * Create an edge to a graph object 42432 * @param {Object} graph 42433 * @param {String | Number | Object} from 42434 * @param {String | Number | Object} to 42435 * @param {String} type 42436 * @param {Object | null} attr 42437 * @return {Object} edge 42438 */ 42439 function createEdge(graph, from, to, type, attr) { 42440 var edge = { 42441 from: from, 42442 to: to, 42443 type: type 42444 }; 42445 42446 if (graph.edge) { 42447 edge.attr = merge({}, graph.edge); // clone default attributes 42448 } 42449 edge.attr = merge(edge.attr || {}, attr); // merge attributes 42450 42451 return edge; 42452 } 42453 42454 /** 42455 * Get next token in the current dot file. 42456 * The token and token type are available as token and tokenType 42457 */ 42458 function getToken() { 42459 tokenType = TOKENTYPE.NULL; 42460 token = ''; 42461 42462 // skip over whitespaces 42463 while (c === ' ' || c === '\t' || c === '\n' || c === '\r') { 42464 // space, tab, enter 42465 next(); 42466 } 42467 42468 do { 42469 var isComment = false; 42470 42471 // skip comment 42472 if (c === '#') { 42473 // find the previous non-space character 42474 var i = index - 1; 42475 while (dot.charAt(i) === ' ' || dot.charAt(i) === '\t') { 42476 i--; 42477 } 42478 if (dot.charAt(i) === '\n' || dot.charAt(i) === '') { 42479 // the # is at the start of a line, this is indeed a line comment 42480 while (c != '' && c != '\n') { 42481 next(); 42482 } 42483 isComment = true; 42484 } 42485 } 42486 if (c === '/' && nextPreview() === '/') { 42487 // skip line comment 42488 while (c != '' && c != '\n') { 42489 next(); 42490 } 42491 isComment = true; 42492 } 42493 if (c === '/' && nextPreview() === '*') { 42494 // skip block comment 42495 while (c != '') { 42496 if (c === '*' && nextPreview() === '/') { 42497 // end of block comment found. skip these last two characters 42498 next(); 42499 next(); 42500 break; 42501 } else { 42502 next(); 42503 } 42504 } 42505 isComment = true; 42506 } 42507
42508 // skip over whitespaces 42509 while (c === ' ' || c === '\t' || c === '\n' || c === '\r') { 42510 // space, tab, enter 42511 next(); 42512 } 42513 } while (isComment); 42514 42515 // check for end of dot file 42516 if (c === '') { 42517 // token is still empty 42518 tokenType = TOKENTYPE.DELIMITER; 42519 return; 42520 } 42521 42522 // check for delimiters consisting of 2 characters 42523 var c2 = c + nextPreview(); 42524 if (DELIMITERS[c2]) { 42525 tokenType = TOKENTYPE.DELIMITER; 42526 token = c2; 42527 next(); 42528 next(); 42529 return; 42530 } 42531 42532 // check for delimiters consisting of 1 character 42533 if (DELIMITERS[c]) { 42534 tokenType = TOKENTYPE.DELIMITER; 42535 token = c; 42536 next(); 42537 return; 42538 } 42539 42540 // check for an identifier (number or string) 42541 // TODO: more precise parsing of numbers/strings (and the port separator ':') 42542 if (isAlphaNumeric(c) || c === '-') { 42543 token += c; 42544 next(); 42545 42546 while (isAlphaNumeric(c)) { 42547 token += c; 42548 next(); 42549 } 42550 if (token === 'false') { 42551 token = false; // convert to boolean 42552 } else if (token === 'true') { 42553 token = true; // convert to boolean 42554 } else if (!isNaN(Number(token))) { 42555 token = Number(token); // convert to number 42556 } 42557 tokenType = TOKENTYPE.IDENTIFIER; 42558 return; 42559 } 42560 42561 // check for a string enclosed by double quotes 42562 if (c === '"') { 42563 next(); 42564 while (c != '' && (c != '"' || c === '"' && nextPreview() === '"')) { 42565 token += c; 42566 if (c === '"') { 42567 // skip the escape character 42568 next(); 42569 } 42570 next(); 42571 } 42572 if (c != '"') { 42573 throw newSyntaxError('End of string " expected'); 42574 } 42575 next(); 42576 tokenType = TOKENTYPE.IDENTIFIER; 42577 return; 42578 } 42579 42580 // something unknown is found, wrong characters, a syntax error 42581 tokenType = TOKENTYPE.UNKNOWN; 42582 while (c != '') { 42583 token += c; 42584 next(); 42585 } 42586 throw new SyntaxError('Syntax error in part "' + chop(token, 30) + '"'); 42587 } 42588 42589 /** 42590 * Parse a graph. 42591 * @returns {Object} graph 42592 */ 42593 function parseGraph() { 42594 var graph = {}; 42595 42596 first(); 42597 getToken(); 42598 42599 // optional strict keyword 42600 if (token === 'strict') { 42601 graph.strict = true; 42602 getToken(); 42603 } 42604 42605 // graph or digraph keyword 42606 if (token === 'graph' || token === 'digraph') { 42607 graph.type = token; 42608 getToken(); 42609 } 42610 42611 // optional graph id 42612 if (tokenType === TOKENTYPE.IDENTIFIER) { 42613 graph.id = token; 42614 getToken(); 42615 } 42616 42617 // open angle bracket 42618 if (token != '{') { 42619 throw newSyntaxError('Angle bracket { expected'); 42620 } 42621 getToken(); 42622 42623 // statements 42624 parseStatements(graph); 42625 42626 // close angle bracket 42627 if (token != '}') { 42628 throw newSyntaxError('Angle bracket } expected'); 42629 } 42630 getToken(); 42631 42632 // end of file 42633 if (token !== '') { 42634 throw newSyntaxError('End of file expected'); 42635 } 42636 getToken(); 42637 42638 // remove temporary default options 42639 delete graph.node; 42640 delete graph.edge; 42641 delete graph.graph; 42642 42643 return graph; 42644 } 42645 42646 /** 42647 * Parse a list with statements. 42648 * @param {Object} graph 42649 */ 42650 function parseStatements(graph) { 42651 while (token !== '' && token != '}') { 42652 parseStatement(graph); 42653 if (token === ';') { 42654 getToken(); 42655 } 42656 } 42657 } 42658 42659 /** 42660 * Parse a single statement. Can be a an attribute statement, node 42661 * statement, a series of node statements and edge statements, or a 42662 * parameter. 42663 * @param {Object} graph 42664 */ 42665 function parseStatement(graph) { 42666 // parse subgraph 42667 var subgraph = parseSubgraph(graph); 42668 if (subgraph) { 42669 // edge statements 42670 parseEdge(graph, subgraph); 42671 42672 return; 42673 } 42674 42675 // parse an attribute statement 42676 var attr = parseAttributeStatement(graph); 42677 if (attr) { 42678 return; 42679 } 42680 42681 // parse node 42682 if (tokenType != TOKENTYPE.IDENTIFIER) { 42683 throw newSyntaxError('Identifier expected'); 42684 } 42685 var id = token; // id can be a string or a number 42686 getToken(); 42687 42688 if (token === '=') { 42689 // id statement 42690 getToken(); 42691 if (tokenType != TOKENTYPE.IDENTIFIER) { 42692 throw newSyntaxError('Identifier expected'); 42693 } 42694 graph[id] = token; 42695 getToken(); 42696 // TODO: implement comma separated list with "a_list: ID=ID [','] [a_list] " 42697 } else {
42698 parseNodeStatement(graph, id); 42699 } 42700 } 42701 42702 /** 42703 * Parse a subgraph 42704 * @param {Object} graph parent graph object 42705 * @return {Object | null} subgraph 42706 */ 42707 function parseSubgraph(graph) { 42708 var subgraph = null; 42709 42710 // optional subgraph keyword 42711 if (token === 'subgraph') { 42712 subgraph = {}; 42713 subgraph.type = 'subgraph'; 42714 getToken(); 42715 42716 // optional graph id 42717 if (tokenType === TOKENTYPE.IDENTIFIER) { 42718 subgraph.id = token; 42719 getToken(); 42720 } 42721 } 42722 42723 // open angle bracket 42724 if (token === '{') { 42725 getToken(); 42726 42727 if (!subgraph) { 42728 subgraph = {}; 42729 } 42730 subgraph.parent = graph; 42731 subgraph.node = graph.node; 42732 subgraph.edge = graph.edge; 42733 subgraph.graph = graph.graph; 42734 42735 // statements 42736 parseStatements(subgraph); 42737 42738 // close angle bracket 42739 if (token != '}') { 42740 throw newSyntaxError('Angle bracket } expected'); 42741 } 42742 getToken(); 42743 42744 // remove temporary default options 42745 delete subgraph.node; 42746 delete subgraph.edge; 42747 delete subgraph.graph; 42748 delete subgraph.parent; 42749 42750 // register at the parent graph 42751 if (!graph.subgraphs) { 42752 graph.subgraphs = []; 42753 } 42754 graph.subgraphs.push(subgraph); 42755 } 42756 42757 return subgraph; 42758 } 42759 42760 /** 42761 * parse an attribute statement like "node [shape=circle fontSize=16]". 42762 * Available keywords are 'node', 'edge', 'graph'. 42763 * The previous list with default attributes will be replaced 42764 * @param {Object} graph 42765 * @returns {String | null} keyword Returns the name of the parsed attribute 42766 * (node, edge, graph), or null if nothing 42767 * is parsed. 42768 */ 42769 function parseAttributeStatement(graph) { 42770 // attribute statements 42771 if (token === 'node') { 42772 getToken(); 42773 42774 // node attributes 42775 graph.node = parseAttributeList(); 42776 return 'node'; 42777 } else if (token === 'edge') { 42778 getToken(); 42779 42780 // edge attributes 42781 graph.edge = parseAttributeList(); 42782 return 'edge'; 42783 } else if (token === 'graph') { 42784 getToken(); 42785 42786 // graph attributes 42787 graph.graph = parseAttributeList(); 42788 return 'graph'; 42789 } 42790 42791 return null; 42792 } 42793 42794 /** 42795 * parse a node statement 42796 * @param {Object} graph 42797 * @param {String | Number} id 42798 */ 42799 function parseNodeStatement(graph, id) { 42800 // node statement 42801 var node = { 42802 id: id 42803 }; 42804 var attr = parseAttributeList(); 42805 if (attr) { 42806 node.attr = attr; 42807 } 42808 addNode(graph, node); 42809 42810 // edge statements 42811 parseEdge(graph, id); 42812 } 42813 42814 /** 42815 * Parse an edge or a series of edges 42816 * @param {Object} graph 42817 * @param {String | Number} from Id of the from node 42818 */ 42819 function parseEdge(graph, from) { 42820 while (token === '->' || token === '--') { 42821 var to; 42822 var type = token; 42823 getToken(); 42824 42825 var subgraph = parseSubgraph(graph); 42826 if (subgraph) { 42827 to = subgraph; 42828 } else { 42829 if (tokenType != TOKENTYPE.IDENTIFIER) { 42830 throw newSyntaxError('Identifier or subgraph expected'); 42831 } 42832 to = token; 42833 addNode(graph, { 42834 id: to 42835 }); 42836 getToken(); 42837 } 42838 42839 // parse edge attributes 42840 var attr = parseAttributeList(); 42841 42842 // create edge 42843 var edge = createEdge(graph, from, to, type, attr); 42844 addEdge(graph, edge); 42845 42846 from = to; 42847 } 42848 } 42849 42850 /** 42851 * Parse a set with attributes, 42852 * for example [label="1.000", shape=solid] 42853 * @return {Object | null} attr 42854 */ 42855 function parseAttributeList() { 42856 var attr = null; 42857 42858 while (token === '[') { 42859 getToken(); 42860 attr = {}; 42861 while (token !== '' && token != ']') { 42862 if (tokenType != TOKENTYPE.IDENTIFIER) { 42863 throw newSyntaxError('Attribute name expected'); 42864 } 42865 var name = token; 42866 42867 getToken(); 42868 if (token != '=') { 42869 throw newSyntaxError('Equal sign = expected'); 42870 } 42871 getToken(); 42872 42873 if (tokenType != TOKENTYPE.IDENTIFIER) { 42874 throw newSyntaxError('Attribute value expected'); 42875 } 42876 var value = token; 42877 setValue(attr, name, value); // name can be a path 42878 42879 getToken(); 42880 if (token == ',') { 42881 getToken(); 42882 } 42883 } 42884 42885 if (token != ']') { 42886 throw newSyntaxError('Bracket ] expected'); 42887 } 42888 getToken(); 42889 } 42890 42891 return attr; 42892 } 42893 42894 /** 42895 * Create a syntax error with extra information on current token and index. 42896 * @param {String} message 42897 * @returns {SyntaxError} err 42898 */ 42899 function newSyntaxError(message) { 42900 return new SyntaxError(message + ', got "' + chop(token, 30) + '" (char ' + index + ')'); 42901 } 42902 42903 /** 42904 * Chop off text after a maximum length 42905 * @param {String} text 42906 * @param {Number} maxLength 42907 * @returns {String} 42908 */ 42909 function chop(text, maxLength) { 42910 return text.length <= maxLength ? text : text.substr(0, 27) + '...'; 42911 } 42912 42913 /** 42914 * Execute a function fn for each pair of elements in two arrays 42915 * @param {Array | *} array1 42916 * @param {Array | *} array2 42917 * @param {function} fn 42918 */ 42919 function forEach2(array1, array2, fn) { 42920 if (Array.isArray(array1)) { 42921 array1.forEach(function (elem1) { 42922 if (Array.isArray(array2)) { 42923 array2.forEach(function (elem2) { 42924 fn(elem1, elem2); 42925 }); 42926 } else { 42927 fn(elem1, array2); 42928 } 42929 }); 42930 } else { 42931 if (Array.isArray(array2)) { 42932 array2.forEach(function (elem2) { 42933 fn(array1, elem2); 42934 }); 42935 } else { 42936 fn(array1, array2); 42937 } 42938 } 42939 } 42940 42941 /** 42942 * Set a nested property on an object 42943 * When nested objects are missing, they will be created. 42944 * For example setProp({}, 'font.color', 'red') will return {font: {color: 'red'}} 42945 * @param {Object} object 42946 * @param {string} path A dot separated string like 'font.color' 42947 * @param {*} value Value for the property 42948 * @return {Object} Returns the original object, allows for chaining. 42949 */ 42950 function setProp(object, path, value) { 42951 var names = path.split('.'); 42952 var prop = names.pop(); 42953 42954 // traverse over the nested objects 42955 var obj = object; 42956 for (var i = 0; i < names.length; i++) { 42957 var name = names[i]; 42958 if (!(name in obj)) { 42959 obj[name] = {}; 42960 } 42961 obj = obj[name]; 42962 } 42963 42964 // set the property value 42965 obj[prop] = value; 42966 42967 return object; 42968 } 42969 42970 /** 42971 * Convert an object with DOT attributes to their vis.js equivalents. 42972 * @param {Object} attr Object with DOT attributes 42973 * @param {Object} mapping 42974 * @return {Object} Returns an object with vis.js attributes 42975 */ 42976 function convertAttr(attr, mapping) { 42977 var converted = {}; 42978 42979 for (var prop in attr) { 42980 if (attr.hasOwnProperty(prop)) { 42981 var visProp = mapping[prop]; 42982 if (Array.isArray(visProp)) {
42983 visProp.forEach(function (visPropI) { 42984 setProp(converted, visPropI, attr[prop]); 42985 }); 42986 } else if (typeof visProp === 'string') { 42987 setProp(converted, visProp, attr[prop]); 42988 } else { 42989 setProp(converted, prop, attr[prop]); 42990 } 42991 } 42992 } 42993 42994 return converted; 42995 } 42996 42997 /** 42998 * Convert a string containing a graph in DOT language into a map containing 42999 * with nodes and edges in the format of graph. 43000 * @param {String} data Text containing a graph in DOT-notation 43001 * @return {Object} graphData 43002 */ 43003 function DOTToGraph(data) { 43004 // parse the DOT file 43005 var dotData = parseDOT(data); 43006 var graphData = { 43007 nodes: [], 43008 edges: [], 43009 options: {} 43010 }; 43011 43012 // copy the nodes 43013 if (dotData.nodes) { 43014 dotData.nodes.forEach(function (dotNode) { 43015 var graphNode = { 43016 id: dotNode.id, 43017 label: String(dotNode.label || dotNode.id) 43018 }; 43019 merge(graphNode, convertAttr(dotNode.attr, NODE_ATTR_MAPPING)); 43020 if (graphNode.image) { 43021 graphNode.shape = 'image'; 43022 } 43023 graphData.nodes.push(graphNode); 43024 }); 43025 } 43026 43027 // copy the edges 43028 if (dotData.edges) { 43029 /** 43030 * Convert an edge in DOT format to an edge with VisGraph format 43031 * @param {Object} dotEdge 43032 * @returns {Object} graphEdge 43033 */ 43034 var convertEdge = function convertEdge(dotEdge) { 43035 var graphEdge = { 43036 from: dotEdge.from, 43037 to: dotEdge.to 43038 }; 43039 merge(graphEdge, convertAttr(dotEdge.attr, EDGE_ATTR_MAPPING)); 43040 graphEdge.arrows = dotEdge.type === '->' ? 'to' : undefined; 43041 43042 return graphEdge; 43043 }; 43044
43045 dotData.edges.forEach(function (dotEdge) { 43046 var from, to; 43047 if (dotEdge.from instanceof Object) { 43048 from = dotEdge.from.nodes; 43049 } else { 43050 from = { 43051 id: dotEdge.from 43052 }; 43053 } 43054 43055 // TODO: support of solid/dotted/dashed edges (attr = 'style') 43056 // TODO: support for attributes 'dir' and 'arrowhead' (edge arrows) 43057 43058 if (dotEdge.to instanceof Object) { 43059 to = dotEdge.to.nodes; 43060 } else { 43061 to = { 43062 id: dotEdge.to 43063 }; 43064 } 43065 43066 if (dotEdge.from instanceof Object && dotEdge.from.edges) { 43067 dotEdge.from.edges.forEach(function (subEdge) { 43068 var graphEdge = convertEdge(subEdge); 43069 graphData.edges.push(graphEdge); 43070 }); 43071 } 43072 43073 forEach2(from, to, function (from, to) { 43074 var subEdge = createEdge(graphData, from.id, to.id, dotEdge.type, dotEdge.attr); 43075 var graphEdge = convertEdge(subEdge); 43076 graphData.edges.push(graphEdge); 43077 }); 43078 43079 if (dotEdge.to instanceof Object && dotEdge.to.edges) {
43080 dotEdge.to.edges.forEach(function (subEdge) { 43081 var graphEdge = convertEdge(subEdge); 43082 graphData.edges.push(graphEdge); 43083 }); 43084 } 43085 }); 43086 } 43087 43088 // copy the options 43089 if (dotData.attr) { 43090 graphData.options = dotData.attr; 43091 } 43092 43093 return graphData; 43094 } 43095 43096 // exports 43097 exports.parseDOT = parseDOT; 43098 exports.DOTToGraph = DOTToGraph; 43099 43100/***/ }, 43101/* 116 */ 43102/***/ function(module, exports) { 43103 43104 'use strict'; 43105 43106 function parseGephi(gephiJSON, optionsObj) { 43107 var edges = []; 43108 var nodes = []; 43109 var options = { 43110 edges: { 43111 inheritColor: false 43112 }, 43113 nodes: { 43114 fixed: false, 43115 parseColor: false 43116 } 43117 }; 43118 43119 if (optionsObj !== undefined) { 43120 if (optionsObj.fixed !== undefined) { 43121 options.nodes.fixed = optionsObj.fixed; 43122 } 43123 if (optionsObj.parseColor !== undefined) { 43124 options.nodes.parseColor = optionsObj.parseColor; 43125 } 43126 if (optionsObj.inheritColor !== undefined) { 43127 options.edges.inheritColor = optionsObj.inheritColor; 43128 } 43129 } 43130 43131 var gEdges = gephiJSON.edges; 43132 var gNodes = gephiJSON.nodes; 43133 for (var i = 0; i < gEdges.length; i++) { 43134 var edge = {}; 43135 var gEdge = gEdges[i]; 43136 edge['id'] = gEdge.id; 43137 edge['from'] = gEdge.source; 43138 edge['to'] = gEdge.target; 43139 edge['attributes'] = gEdge.attributes; 43140 edge['label'] = gEdge.label; 43141 edge['title'] = gEdge.attributes !== undefined ? gEdge.attributes.title : undefined; 43142 if (gEdge['type'] === 'Directed') { 43143 edge['arrows'] = 'to'; 43144 } 43145 // edge['value'] = gEdge.attributes !== undefined ? gEdge.attributes.Weight : undefined; 43146 // edge['width'] = edge['value'] !== undefined ? undefined : edgegEdge.size; 43147 if (gEdge.color && options.inheritColor === false) { 43148 edge['color'] = gEdge.color; 43149 } 43150 edges.push(edge); 43151 } 43152 43153 for (var i = 0; i < gNodes.length; i++) { 43154 var node = {}; 43155 var gNode = gNodes[i]; 43156 node['id'] = gNode.id; 43157 node['attributes'] = gNode.attributes; 43158 node['title'] = gNode.title; 43159 node['x'] = gNode.x; 43160 node['y'] = gNode.y; 43161 node['label'] = gNode.label; 43162 node['title'] = gNode.attributes !== undefined ? gNode.attributes.title : undefined; 43163 if (options.nodes.parseColor === true) { 43164 node['color'] = gNode.color; 43165 } else { 43166 node['color'] = gNode.color !== undefined ? { background: gNode.color, border: gNode.color, highlight: { background: gNode.color, border: gNode.color }, hover: { background: gNode.color, border: gNode.color } } : undefined; 43167 } 43168 node['size'] = gNode.size; 43169 node['fixed'] = options.nodes.fixed && gNode.x !== undefined && gNode.y !== undefined; 43170 nodes.push(node); 43171 } 43172 43173 return { nodes: nodes, edges: edges }; 43174 } 43175 43176 exports.parseGephi = parseGephi; 43177 43178/***/ }, 43179/* 117 */ 43180/***/ function(module, exports) { 43181 43182 /** 43183 * @class Images 43184 * This class loads images and keeps them stored. 43185 */ 43186 "use strict"; 43187 43188 Object.defineProperty(exports, "__esModule", { 43189 value: true 43190 }); 43191 43192 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 43193 43194 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 43195 43196 var Images = (function () { 43197 function Images(callback) { 43198 _classCallCheck(this, Images); 43199 43200 this.images = {}; 43201 this.imageBroken = {}; 43202 this.callback = callback; 43203 } 43204 43205 _createClass(Images, [{ 43206 key: "_addImageToCache", 43207 43208 /** 43209 * @param {string} url The Url to cache the image as 43210 * @return {Image} imageToLoadBrokenUrlOn The image object 43211 */ 43212 value: function _addImageToCache(url, imageToCache) { 43213 // IE11 fix -- thanks dponch! 43214 if (imageToCache.width === 0) { 43215 document.body.appendChild(imageToCache); 43216 imageToCache.width = imageToCache.offsetWidth; 43217 imageToCache.height = imageToCache.offsetHeight; 43218 document.body.removeChild(imageToCache); 43219 } 43220 43221 this.images[url] = imageToCache; 43222 } 43223 }, { 43224 key: "_tryloadBrokenUrl", 43225 43226 /** 43227 * @param {string} url The original Url that failed to load, if the broken image is successfully loaded it will be added to the cache using this Url as the key so that subsequent requests for this Url will return the broken image 43228 * @param {string} brokenUrl Url the broken image to try and load 43229 * @return {Image} imageToLoadBrokenUrlOn The image object 43230 */ 43231 value: function _tryloadBrokenUrl(url, brokenUrl, imageToLoadBrokenUrlOn) { 43232 var _this = this; 43233 43234 //If any of the parameters aren't specified then exit the function because nothing constructive can be done 43235 if (url === undefined || brokenUrl === undefined || imageToLoadBrokenUrlOn === undefined) return; 43236 43237 //Clear the old subscription to the error event and put a new in place that only handle errors in loading the brokenImageUrl 43238 imageToLoadBrokenUrlOn.onerror = function () { 43239 console.error("Could not load brokenImage:", brokenUrl); 43240 //Add an empty image to the cache so that when subsequent load calls are made for the url we don't try load the image and broken image again 43241 _this._addImageToCache(url, new Image()); 43242 }; 43243 43244 //Set the source of the image to the brokenUrl, this is actually what kicks off the loading of the broken image 43245 imageToLoadBrokenUrlOn.src = brokenUrl; 43246 } 43247 }, { 43248 key: "_redrawWithImage", 43249 43250 /** 43251 * @return {Image} imageToRedrawWith The images that will be passed to the callback when it is invoked 43252 */ 43253 value: function _redrawWithImage(imageToRedrawWith) { 43254 if (this.callback) { 43255 this.callback(imageToRedrawWith); 43256 } 43257 } 43258 }, { 43259 key: "load", 43260 43261 /** 43262 * @param {string} url Url of the image 43263 * @param {string} brokenUrl Url of an image to use if the url image is not found 43264 * @return {Image} img The image object 43265 */ 43266 value: function load(url, brokenUrl, id) { 43267 var _this2 = this; 43268 43269 //Try and get the image from the cache, if successful then return the cached image 43270 var cachedImage = this.images[url]; 43271 if (cachedImage) return cachedImage; 43272 43273 //Create a new image 43274 var img = new Image(); 43275 43276 //Subscribe to the event that is raised if the image loads successfully 43277 img.onload = function () { 43278 //Add the image to the cache and then request a redraw 43279 _this2._addImageToCache(url, img); 43280 _this2._redrawWithImage(img); 43281 }; 43282 43283 //Subscribe to the event that is raised if the image fails to load 43284 img.onerror = function () { 43285 console.error("Could not load image:", url); 43286 //Try and load the image specified by the brokenUrl using 43287 _this2._tryloadBrokenUrl(url, brokenUrl, img); 43288 }; 43289 43290 //Set the source of the image to the url, this is actuall what kicks off the loading of the image 43291 img.src = url; 43292 43293 //Return the new image 43294 return img; 43295 } 43296 }]); 43297 43298 return Images; 43299 })(); 43300 43301 exports["default"] = Images; 43302 module.exports = exports["default"]; 43303 43304/***/ }, 43305/* 118 */
43306/***/ function(module, exports) { 43307 43308 // English 43309 'use strict'; 43310 43311 exports['en'] = { 43312 edit: 'Edit', 43313 del: 'Delete selected', 43314 back: 'Back', 43315 addNode: 'Add Node', 43316 addEdge: 'Add Edge', 43317 editNode: 'Edit Node', 43318 editEdge: 'Edit Edge', 43319 addDescription: 'Click in an empty space to place a new node.', 43320 edgeDescription: 'Click on a node and drag the edge to another node to connect them.', 43321 editEdgeDescription: 'Click on the control points and drag them to a node to connect to it.', 43322 createEdgeError: 'Cannot link edges to a cluster.', 43323 deleteClusterError: 'Clusters cannot be deleted.', 43324 editClusterError: 'Clusters cannot be edited.' 43325 }; 43326 exports['en_EN'] = exports['en']; 43327 exports['en_US'] = exports['en']; 43328 43329 // German 43330 exports['de'] = { 43331 edit: 'Editieren', 43332 del: 'Lösche Auswahl', 43333 back: 'Zurück', 43334 addNode: 'Knoten hinzufügen', 43335 addEdge: 'Kante hinzufügen', 43336 editNode: 'Knoten editieren', 43337 editEdge: 'Kante editieren', 43338 addDescription: 'Klicke auf eine freie Stelle, um einen neuen Knoten zu plazieren.', 43339 edgeDescription: 'Klicke auf einen Knoten und ziehe die Kante zu einem anderen Knoten, um diese zu verbinden.', 43340 editEdgeDescription: 'Klicke auf die Verbindungspunkte und ziehe diese auf einen Knoten, um sie zu verbinden.', 43341 createEdgeError: 'Es ist nicht möglich, Kanten mit Clustern zu verbinden.', 43342 deleteClusterError: 'Cluster können nicht gelöscht werden.', 43343 editClusterError: 'Cluster können nicht editiert werden.' 43344 }; 43345 exports['de_DE'] = exports['de']; 43346 43347 // Spanish 43348 exports['es'] = { 43349 edit: 'Editar', 43350 del: 'Eliminar selección', 43351 back: 'Ãtras', 43352 addNode: 'Añadir nodo', 43353 addEdge: 'Añadir arista', 43354 editNode: 'Editar nodo', 43355 editEdge: 'Editar arista', 43356 addDescription: 'Haga clic en un lugar vacÃo para colocar un nuevo nodo.', 43357 edgeDescription: 'Haga clic en un nodo y arrastre la arista hacia otro nodo para conectarlos.', 43358 editEdgeDescription: 'Haga clic en un punto de control y arrastrelo a un nodo para conectarlo.', 43359 createEdgeError: 'No se puede conectar una arista a un grupo.', 43360 deleteClusterError: 'No es posible eliminar grupos.', 43361 editClusterError: 'No es posible editar grupos.' 43362 }; 43363 exports['es_ES'] = exports['es']; 43364 43365 // Dutch 43366 exports['nl'] = { 43367 edit: 'Wijzigen', 43368 del: 'Selectie verwijderen', 43369 back: 'Terug', 43370 addNode: 'Node toevoegen', 43371 addEdge: 'Link toevoegen', 43372 editNode: 'Node wijzigen', 43373 editEdge: 'Link wijzigen', 43374 addDescription: 'Klik op een leeg gebied om een nieuwe node te maken.', 43375 edgeDescription: 'Klik op een node en sleep de link naar een andere node om ze te verbinden.', 43376 editEdgeDescription: 'Klik op de verbindingspunten en sleep ze naar een node om daarmee te verbinden.', 43377 createEdgeError: 'Kan geen link maken naar een cluster.', 43378 deleteClusterError: 'Clusters kunnen niet worden verwijderd.', 43379 editClusterError: 'Clusters kunnen niet worden aangepast.' 43380 }; 43381 exports['nl_NL'] = exports['nl']; 43382 exports['nl_BE'] = exports['nl']; 43383 43384/***/ } 43385/******/ ]) 43386}); 43387;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.